diff --git a/python/packages/devui/README.md b/python/packages/devui/README.md index 245fffc5dd..05fe813276 100644 --- a/python/packages/devui/README.md +++ b/python/packages/devui/README.md @@ -62,6 +62,27 @@ serve(entities=[agent]) MCP tools use lazy initialization and connect automatically on first use. DevUI attempts to clean up connections on shutdown +## Resource Cleanup + +Register cleanup hooks to properly close credentials and resources on shutdown: + +```python +from azure.identity.aio import DefaultAzureCredential +from agent_framework import ChatAgent +from agent_framework.azure import AzureOpenAIChatClient +from agent_framework_devui import register_cleanup, serve + +credential = DefaultAzureCredential() +client = AzureOpenAIChatClient() +agent = ChatAgent(name="MyAgent", chat_client=client) + +# Register cleanup hook - credential will be closed on shutdown +register_cleanup(agent, credential.close) +serve(entities=[agent]) +``` + +Works with multiple resources and file-based discovery. See tests for more examples. + ## Directory Structure For your agents to be discovered by the DevUI, they must be organized in a directory structure like below. Each agent/workflow must have an `__init__.py` that exports the required variable (`agent` or `workflow`). @@ -150,6 +171,22 @@ response2 = client.responses.create( **How it works:** DevUI automatically retrieves the conversation's message history from the stored thread and passes it to the agent. You don't need to manually manage message history - just provide the same `conversation` ID for follow-up requests. +### OpenAI Proxy Mode + +DevUI provides an **OpenAI Proxy** feature for testing OpenAI models directly through the interface without creating custom agents. Enable via Settings → OpenAI Proxy tab. + +**How it works:** The UI sends requests to the DevUI backend (with `X-Proxy-Backend: openai` header), which then proxies them to OpenAI's Responses API (and Conversations API for multi-turn chats). This proxy approach keeps your `OPENAI_API_KEY` secure on the server—never exposed in the browser or client-side code. + +**Example:** + +```bash +curl -X POST http://localhost:8080/v1/responses \ + -H "X-Proxy-Backend: openai" \ + -d '{"model": "gpt-4.1-mini", "input": "Hello"}' +``` + +**Note:** Requires `OPENAI_API_KEY` environment variable configured on the backend. + ## CLI Options ```bash @@ -162,6 +199,21 @@ Options: --config YAML config file --tracing none|framework|workflow|all --reload Enable auto-reload + --mode developer|user (default: developer) + --auth Enable Bearer token authentication +``` + +### UI Modes + +- **developer** (default): Full access - debug panel, entity details, hot reload, deployment +- **user**: Simplified UI with restricted APIs - only chat and conversation management + +```bash +# Development +devui ./agents + +# Production (user-facing) +devui ./agents --mode user --auth ``` ## Key Endpoints @@ -187,18 +239,23 @@ Given that DevUI offers an OpenAI Responses API, it internally maps messages and | `response.function_result.complete` | `FunctionResultContent` | DevUI | | `response.function_approval.requested` | `FunctionApprovalRequestContent` | DevUI | | `response.function_approval.responded` | `FunctionApprovalResponseContent` | DevUI | +| `response.output_item.added` (ResponseOutputImage) | `DataContent` (images) | DevUI | +| `response.output_item.added` (ResponseOutputFile) | `DataContent` (files) | DevUI | +| `response.output_item.added` (ResponseOutputData) | `DataContent` (other) | DevUI | +| `response.output_item.added` (ResponseOutputImage/File) | `UriContent` (images/files) | DevUI | | `error` | `ErrorContent` | OpenAI | | Final `Response.usage` field (not streamed) | `UsageContent` | OpenAI | | | **Workflow Events** | | | `response.output_item.added` (ExecutorActionItem)* | `ExecutorInvokedEvent` | OpenAI | | `response.output_item.done` (ExecutorActionItem)* | `ExecutorCompletedEvent` | OpenAI | | `response.output_item.done` (ExecutorActionItem with error)* | `ExecutorFailedEvent` | OpenAI | +| `response.output_item.added` (ResponseOutputMessage) | `WorkflowOutputEvent` | OpenAI | | `response.workflow_event.complete` | `WorkflowEvent` (other) | DevUI | | `response.trace.complete` | `WorkflowStatusEvent` | DevUI | | `response.trace.complete` | `WorkflowWarningEvent` | DevUI | | | **Trace Content** | | -| `response.trace.complete` | `DataContent` | DevUI | -| `response.trace.complete` | `UriContent` | DevUI | +| `response.trace.complete` | `DataContent` (no data/errors) | DevUI | +| `response.trace.complete` | `UriContent` (unsupported MIME) | DevUI | | `response.trace.complete` | `HostedFileContent` | DevUI | | `response.trace.complete` | `HostedVectorStoreContent` | DevUI | @@ -213,15 +270,19 @@ DevUI follows the OpenAI Responses API specification for maximum compatibility: **OpenAI Standard Event Types Used:** -- `ResponseOutputItemAddedEvent` - Output item notifications (function calls and results) +- `ResponseOutputItemAddedEvent` - Output item notifications (function calls, images, files, data) - `ResponseOutputItemDoneEvent` - Output item completion notifications - `Response.usage` - Token usage (in final response, not streamed) -- All standard text, reasoning, and function call events **Custom DevUI Extensions:** +- `response.output_item.added` with custom item types: + - `ResponseOutputImage` - Agent-generated images (inline display) + - `ResponseOutputFile` - Agent-generated files (inline display) + - `ResponseOutputData` - Agent-generated structured data (inline display) - `response.function_approval.requested` - Function approval requests (for interactive approval workflows) - `response.function_approval.responded` - Function approval responses (user approval/rejection) +- `response.function_result.complete` - Server-side function execution results - `response.workflow_event.complete` - Agent Framework workflow events - `response.trace.complete` - Execution traces and internal content (DataContent, UriContent, hosted files/stores) @@ -254,18 +315,28 @@ These custom extensions are clearly namespaced and can be safely ignored by stan ## Security -DevUI is designed as a **sample application for local development** and should not be exposed to untrusted networks or used in production environments. +DevUI is designed as a **sample application for local development** and should not be exposed to untrusted networks without proper authentication. + +**For production deployments:** + +```bash +# User mode with authentication (recommended) +devui ./agents --mode user --auth --host 0.0.0.0 +``` + +This restricts developer APIs (reload, deployment, entity details) and requires Bearer token authentication. **Security features:** +- User mode restricts developer-facing APIs +- Optional Bearer token authentication via `--auth` - Only loads entities from local directories or in-memory registration - No remote code execution capabilities - Binds to localhost (127.0.0.1) by default -- All samples must be manually downloaded and reviewed before running **Best practices:** -- Never expose DevUI to the internet +- Use `--mode user --auth` for any deployment exposed to end users - Review all agent/workflow code before running - Only load entities from trusted sources - Use `.env` files for sensitive credentials (never commit them) diff --git a/python/packages/devui/agent_framework_devui/__init__.py b/python/packages/devui/agent_framework_devui/__init__.py index c259b33ae8..45d1ea8c2d 100644 --- a/python/packages/devui/agent_framework_devui/__init__.py +++ b/python/packages/devui/agent_framework_devui/__init__.py @@ -5,20 +5,87 @@ import importlib.metadata import logging import webbrowser +from collections.abc import Callable from typing import Any +from ._conversations import CheckpointConversationManager from ._server import DevServer from .models import AgentFrameworkRequest, OpenAIError, OpenAIResponse, ResponseStreamEvent from .models._discovery_models import DiscoveryResponse, EntityInfo, EnvVarRequirement logger = logging.getLogger(__name__) +# Module-level cleanup registry (before serve() is called) +_cleanup_registry: dict[int, list[Callable[[], Any]]] = {} + try: __version__ = importlib.metadata.version(__name__) except importlib.metadata.PackageNotFoundError: __version__ = "0.0.0" # Fallback for development mode +def register_cleanup(entity: Any, *hooks: Callable[[], Any]) -> None: + """Register cleanup hook(s) for an entity. + + Cleanup hooks execute during DevUI server shutdown, before entity + clients are closed. Supports both synchronous and asynchronous callables. + + Args: + entity: Agent, workflow, or other entity object + *hooks: One or more cleanup callables (sync or async) + + Raises: + ValueError: If no hooks provided + + Examples: + Single cleanup hook: + >>> from agent_framework.devui import serve, register_cleanup + >>> credential = DefaultAzureCredential() + >>> agent = ChatAgent(...) + >>> register_cleanup(agent, credential.close) + >>> serve(entities=[agent]) + + Multiple cleanup hooks: + >>> register_cleanup(agent, credential.close, session.close, db_pool.close) + + Works with file-based discovery: + >>> # In agents/my_agent/agent.py + >>> from agent_framework.devui import register_cleanup + >>> credential = DefaultAzureCredential() + >>> agent = ChatAgent(...) + >>> register_cleanup(agent, credential.close) + >>> # Run: devui ./agents + """ + if not hooks: + raise ValueError("At least one cleanup hook required") + + # Use id() to track entity identity (works across modules) + entity_id = id(entity) + + if entity_id not in _cleanup_registry: + _cleanup_registry[entity_id] = [] + + _cleanup_registry[entity_id].extend(hooks) + + logger.debug( + f"Registered {len(hooks)} cleanup hook(s) for {type(entity).__name__} " + f"(id: {entity_id}, total: {len(_cleanup_registry[entity_id])})" + ) + + +def _get_registered_cleanup_hooks(entity: Any) -> list[Callable[[], Any]]: + """Get cleanup hooks registered for an entity (internal use). + + Args: + entity: Entity object to get hooks for + + Returns: + List of cleanup hooks registered for the entity + """ + entity_id = id(entity) + return _cleanup_registry.get(entity_id, []) + + def serve( entities: list[Any] | None = None, entities_dir: str | None = None, @@ -28,6 +95,9 @@ def serve( cors_origins: list[str] | None = None, ui_enabled: bool = True, tracing_enabled: bool = False, + mode: str = "developer", + auth_enabled: bool = False, + auth_token: str | None = None, ) -> None: """Launch Agent Framework DevUI with simple API. @@ -40,6 +110,9 @@ def serve( cors_origins: List of allowed CORS origins ui_enabled: Whether to enable the UI tracing_enabled: Whether to enable OpenTelemetry tracing + mode: Server mode - 'developer' (full access, verbose errors) or 'user' (restricted APIs, generic errors) + auth_enabled: Whether to enable Bearer token authentication + auth_token: Custom authentication token (auto-generated if not provided with auth_enabled=True) """ import re @@ -53,6 +126,52 @@ def serve( if not isinstance(port, int) or not (1 <= port <= 65535): raise ValueError(f"Invalid port: {port}. Must be integer between 1 and 65535") + # Security check: Warn if network-exposed without authentication + if host not in ("127.0.0.1", "localhost") and not auth_enabled: + logger.warning("⚠️ WARNING: Exposing DevUI to network without authentication!") + logger.warning("⚠️ This is INSECURE - anyone on your network can access your agents") + logger.warning("💡 For network exposure, add --auth flag: devui --host 0.0.0.0 --auth") + + # Handle authentication configuration + if auth_enabled: + import os + import secrets + + # Check if token is in environment variable first + if not auth_token: + auth_token = os.environ.get("DEVUI_AUTH_TOKEN") + + # Auto-generate token if STILL not provided + if not auth_token: + # Check if we're in a production-like environment + is_production = ( + host not in ("127.0.0.1", "localhost") # Exposed to network + or os.environ.get("CI") == "true" # Running in CI + or os.environ.get("KUBERNETES_SERVICE_HOST") # Running in k8s + ) + + if is_production: + # REFUSE to start without explicit token + logger.error("❌ Authentication enabled but no token provided") + logger.error("❌ Auto-generated tokens are NOT secure for network-exposed deployments") + logger.error("💡 Set token: export DEVUI_AUTH_TOKEN=") + logger.error("💡 Or pass: serve(entities=[...], auth_token='your-token')") + raise ValueError("DEVUI_AUTH_TOKEN required when host is not localhost") + + # Development mode: auto-generate and show + auth_token = secrets.token_urlsafe(32) + logger.info("🔒 Authentication enabled with auto-generated token") + logger.info("\n" + "=" * 70) + logger.info("🔑 DEV TOKEN (localhost only, shown once):") + logger.info(f" {auth_token}") + logger.info("=" * 70 + "\n") + else: + logger.info("🔒 Authentication enabled with provided token") + + # Set environment variable for server to use + os.environ["AUTH_REQUIRED"] = "true" + os.environ["DEVUI_AUTH_TOKEN"] = auth_token + # Configure tracing environment variables if enabled if tracing_enabled: import os @@ -72,7 +191,12 @@ def serve( # Create server with direct parameters server = DevServer( - entities_dir=entities_dir, port=port, host=host, cors_origins=cors_origins, ui_enabled=ui_enabled + entities_dir=entities_dir, + port=port, + host=host, + cors_origins=cors_origins, + ui_enabled=ui_enabled, + mode=mode, ) # Register in-memory entities if provided @@ -139,6 +263,7 @@ def main() -> None: # Export main public API __all__ = [ "AgentFrameworkRequest", + "CheckpointConversationManager", "DevServer", "DiscoveryResponse", "EntityInfo", @@ -147,5 +272,6 @@ def main() -> None: "OpenAIResponse", "ResponseStreamEvent", "main", + "register_cleanup", "serve", ] diff --git a/python/packages/devui/agent_framework_devui/_cli.py b/python/packages/devui/agent_framework_devui/_cli.py index 2a36d0aa98..5bc06ac3c8 100644 --- a/python/packages/devui/agent_framework_devui/_cli.py +++ b/python/packages/devui/agent_framework_devui/_cli.py @@ -55,6 +55,41 @@ def create_cli_parser() -> argparse.ArgumentParser: parser.add_argument("--tracing", action="store_true", help="Enable OpenTelemetry tracing for Agent Framework") + parser.add_argument( + "--mode", + choices=["developer", "user"], + default=None, + help="Server mode - 'developer' (full access, verbose errors) or 'user' (restricted APIs, generic errors)", + ) + + # Add --dev/--no-dev as a convenient alternative to --mode + parser.add_argument( + "--dev", + dest="dev_mode", + action="store_true", + default=None, + help="Enable developer mode (shorthand for --mode developer)", + ) + + parser.add_argument( + "--no-dev", + dest="dev_mode", + action="store_false", + help="Disable developer mode (shorthand for --mode user)", + ) + + parser.add_argument( + "--auth", + action="store_true", + help="Enable authentication via Bearer token (required for deployed environments)", + ) + + parser.add_argument( + "--auth-token", + type=str, + help="Custom authentication token (auto-generated if not provided with --auth)", + ) + parser.add_argument("--version", action="version", version=f"Agent Framework DevUI {get_version()}") return parser @@ -78,26 +113,35 @@ def validate_directory(directory: str) -> str: abs_dir = os.path.abspath(directory) if not os.path.exists(abs_dir): - print(f"❌ Error: Directory '{directory}' does not exist", file=sys.stderr) # noqa: T201 + print(f"Error: Directory '{directory}' does not exist", file=sys.stderr) # noqa: T201 sys.exit(1) if not os.path.isdir(abs_dir): - print(f"❌ Error: '{directory}' is not a directory", file=sys.stderr) # noqa: T201 + print(f"Error: '{directory}' is not a directory", file=sys.stderr) # noqa: T201 sys.exit(1) return abs_dir -def print_startup_info(entities_dir: str, host: str, port: int, ui_enabled: bool, reload: bool) -> None: +def print_startup_info( + entities_dir: str, host: str, port: int, ui_enabled: bool, reload: bool, auth_token: str | None = None +) -> None: """Print startup information.""" - print("🤖 Agent Framework DevUI") # noqa: T201 + print("Agent Framework DevUI") # noqa: T201 print("=" * 50) # noqa: T201 - print(f"📁 Entities directory: {entities_dir}") # noqa: T201 - print(f"🌐 Server URL: http://{host}:{port}") # noqa: T201 - print(f"🎨 UI enabled: {'Yes' if ui_enabled else 'No'}") # noqa: T201 - print(f"🔄 Auto-reload: {'Yes' if reload else 'No'}") # noqa: T201 + print(f"Entities directory: {entities_dir}") # noqa: T201 + print(f"Server URL: http://{host}:{port}") # noqa: T201 + print(f"UI enabled: {'Yes' if ui_enabled else 'No'}") # noqa: T201 + print(f"Auto-reload: {'Yes' if reload else 'No'}") # noqa: T201 + + # Display auth token if authentication is enabled + if auth_token: + print("Authentication: Enabled") # noqa: T201 + print(f"Auth token: {auth_token}") # noqa: T201 + print("💡 Use this token in Authorization: Bearer header") # noqa: T201 + print("=" * 50) # noqa: T201 - print("🔍 Scanning for entities...") # noqa: T201 + print("Scanning for entities...") # noqa: T201 def main() -> None: @@ -114,8 +158,19 @@ def main() -> None: # Extract parameters directly from args ui_enabled = not args.headless - # Print startup info - print_startup_info(entities_dir, args.host, args.port, ui_enabled, args.reload) + # Determine mode from --mode or --dev/--no-dev flags + if args.dev_mode is not None: + # --dev or --no-dev was specified + mode = "developer" if args.dev_mode else "user" + elif args.mode is not None: + # --mode was specified + mode = args.mode + else: + # Default to developer mode + mode = "developer" + + # Print startup info (don't show token - serve() will handle it) + print_startup_info(entities_dir, args.host, args.port, ui_enabled, args.reload, None) # Import and start server try: @@ -128,14 +183,17 @@ def main() -> None: auto_open=not args.no_open, ui_enabled=ui_enabled, tracing_enabled=args.tracing, + mode=mode, + auth_enabled=args.auth, + auth_token=args.auth_token, # Pass through explicit token only ) except KeyboardInterrupt: - print("\n👋 Shutting down Agent Framework DevUI...") # noqa: T201 + print("\nShutting down Agent Framework DevUI...") # noqa: T201 sys.exit(0) except Exception as e: logger.exception("Failed to start server") - print(f"❌ Error: {e}", file=sys.stderr) # noqa: T201 + print(f"Error: {e}", file=sys.stderr) # noqa: T201 sys.exit(1) diff --git a/python/packages/devui/agent_framework_devui/_conversations.py b/python/packages/devui/agent_framework_devui/_conversations.py index 5b892c8f35..9762b55d0e 100644 --- a/python/packages/devui/agent_framework_devui/_conversations.py +++ b/python/packages/devui/agent_framework_devui/_conversations.py @@ -12,6 +12,7 @@ from typing import Any, Literal, cast from agent_framework import AgentThread, ChatMessage +from agent_framework._workflows._checkpoint import InMemoryCheckpointStorage from openai.types.conversations import Conversation, ConversationDeletedResource from openai.types.conversations.conversation_item import ConversationItem from openai.types.conversations.message import Message @@ -26,6 +27,10 @@ # Type alias for OpenAI Message role literals MessageRole = Literal["unknown", "user", "assistant", "system", "critic", "discriminator", "developer", "tool"] +# Checkpoint item type constants +CONVERSATION_ITEM_TYPE_CHECKPOINT = "checkpoint" +CONVERSATION_TYPE_CHECKPOINT_CONTAINER = "checkpoint_container" + class ConversationStore(ABC): """Abstract base class for conversation storage. @@ -35,14 +40,17 @@ class ConversationStore(ABC): """ @abstractmethod - def create_conversation(self, metadata: dict[str, str] | None = None) -> Conversation: + def create_conversation( + self, metadata: dict[str, str] | None = None, conversation_id: str | None = None + ) -> Conversation: """Create a new conversation (wraps AgentThread creation). Args: metadata: Optional metadata dict (e.g., {"agent_id": "weather_agent"}) + conversation_id: Optional conversation ID (if None, generates one) Returns: - Conversation object with generated ID + Conversation object with generated or provided ID """ pass @@ -127,7 +135,7 @@ async def list_items( @abstractmethod def get_item(self, conversation_id: str, item_id: str) -> ConversationItem | None: - """Get specific conversation item. + """Get a specific conversation item by ID. Args: conversation_id: Conversation ID @@ -184,17 +192,23 @@ def __init__(self) -> None: # Item index for O(1) lookup: {conversation_id: {item_id: ConversationItem}} self._item_index: dict[str, dict[str, ConversationItem]] = {} - def create_conversation(self, metadata: dict[str, str] | None = None) -> Conversation: - """Create a new conversation with underlying AgentThread.""" - conv_id = f"conv_{uuid.uuid4().hex}" + def create_conversation( + self, metadata: dict[str, str] | None = None, conversation_id: str | None = None + ) -> Conversation: + """Create a new conversation with underlying AgentThread and checkpoint storage.""" + conv_id = conversation_id or f"conv_{uuid.uuid4().hex}" created_at = int(time.time()) # Create AgentThread with default ChatMessageStore thread = AgentThread() + # Create session-scoped checkpoint storage (one per conversation) + checkpoint_storage = InMemoryCheckpointStorage() + self._conversations[conv_id] = { "id": conv_id, "thread": thread, + "checkpoint_storage": checkpoint_storage, # Stored alongside thread "metadata": metadata or {}, "created_at": created_at, "items": [], @@ -424,6 +438,23 @@ async def list_items( # Add function result items items.extend(function_results) + # Include checkpoints from checkpoint storage as conversation items + checkpoint_storage = conv_data.get("checkpoint_storage") + if checkpoint_storage: + # Get all checkpoints for this conversation + checkpoints = await checkpoint_storage.list_checkpoints() + for checkpoint in checkpoints: + # Create a conversation item for each checkpoint + checkpoint_item = { + "id": f"checkpoint_{checkpoint.checkpoint_id}", + "type": "checkpoint", + "checkpoint_id": checkpoint.checkpoint_id, + "workflow_id": checkpoint.workflow_id, + "timestamp": checkpoint.timestamp, + "status": "completed", + } + items.append(cast(ConversationItem, checkpoint_item)) + # Apply pagination if order == "desc": items = items[::-1] @@ -442,12 +473,9 @@ async def list_items( return paginated_items, has_more def get_item(self, conversation_id: str, item_id: str) -> ConversationItem | None: - """Get specific conversation item - O(1) lookup via index.""" - # Use index for O(1) lookup instead of linear search - conv_items = self._item_index.get(conversation_id) - if not conv_items: - return None - + """Get a specific conversation item by ID.""" + # Use the item index for O(1) lookup + conv_items = self._item_index.get(conversation_id, {}) return conv_items.get(item_id) def get_thread(self, conversation_id: str) -> AgentThread | None: @@ -471,3 +499,42 @@ def list_conversations_by_metadata(self, metadata_filter: dict[str, str]) -> lis ) ) return results + + +class CheckpointConversationManager: + """Manages checkpoint storage for workflow sessions - SESSION-SCOPED. + + Simplified architecture: Each conversation has its own InMemoryCheckpointStorage + stored in conv_data["checkpoint_storage"]. This manager just retrieves it. + Session isolation comes from each conversation having a separate storage instance. + """ + + def __init__(self, conversation_store: ConversationStore): + # Runtime validation since we need specific implementation details + if not isinstance(conversation_store, InMemoryConversationStore): + raise TypeError("CheckpointConversationManager currently requires InMemoryConversationStore") + self._store: InMemoryConversationStore = conversation_store + # Keep public reference for backward compatibility with tests + self.conversation_store = conversation_store + + def get_checkpoint_storage(self, conversation_id: str) -> InMemoryCheckpointStorage: + """Get the checkpoint storage for a specific conversation. + + Args: + conversation_id: Conversation ID + + Returns: + InMemoryCheckpointStorage instance for this conversation + + Raises: + ValueError: If conversation not found + """ + # Access internal conversations dict (we know it's InMemoryConversationStore) + conv_data = self._store._conversations.get(conversation_id) + if not conv_data: + raise ValueError(f"Conversation {conversation_id} not found") + + checkpoint_storage = conv_data["checkpoint_storage"] + if not isinstance(checkpoint_storage, InMemoryCheckpointStorage): + raise TypeError(f"Expected InMemoryCheckpointStorage but got {type(checkpoint_storage)}") + return checkpoint_storage diff --git a/python/packages/devui/agent_framework_devui/_deployment.py b/python/packages/devui/agent_framework_devui/_deployment.py new file mode 100644 index 0000000000..e1cf1d5c3d --- /dev/null +++ b/python/packages/devui/agent_framework_devui/_deployment.py @@ -0,0 +1,588 @@ +# Copyright (c) Microsoft. All rights reserved. + +"""Azure Container Apps deployment manager for DevUI entities.""" + +import asyncio +import logging +import re +import secrets +import uuid +from collections.abc import AsyncGenerator +from datetime import datetime, timezone +from pathlib import Path + +from .models._discovery_models import Deployment, DeploymentConfig, DeploymentEvent + +logger = logging.getLogger(__name__) + + +class DeploymentManager: + """Manages entity deployments to Azure Container Apps.""" + + def __init__(self) -> None: + """Initialize deployment manager.""" + self._deployments: dict[str, Deployment] = {} + + async def deploy(self, config: DeploymentConfig, entity_path: Path) -> AsyncGenerator[DeploymentEvent, None]: + """Deploy entity to Azure Container Apps with streaming events. + + Args: + config: Deployment configuration + entity_path: Path to entity directory + + Yields: + DeploymentEvent objects for real-time progress updates + + Raises: + ValueError: If prerequisites not met or deployment fails + """ + deployment_id = str(uuid.uuid4()) + + try: + # Step 1: Validate prerequisites + yield DeploymentEvent( + type="deploy.validating", + message="Checking prerequisites (Azure CLI, Docker, authentication)...", + ) + + await self._validate_prerequisites() + + # Step 2: Generate Dockerfile + yield DeploymentEvent( + type="deploy.dockerfile", + message="Generating Dockerfile with authentication enabled...", + ) + + _ = await self._generate_dockerfile(entity_path, config) + + # Step 3: Generate auth token + yield DeploymentEvent( + type="deploy.token", + message="Generating secure authentication token...", + ) + + auth_token = secrets.token_urlsafe(32) + + # Step 4: Discover existing Container App Environment + yield DeploymentEvent( + type="deploy.environment", + message="Checking for existing Container App Environment...", + ) + + # Step 5: Build and deploy with Azure CLI + yield DeploymentEvent( + type="deploy.building", + message=f"Deploying to Azure Container Apps ({config.region})...", + ) + + # Create a queue for streaming events from subprocess + event_queue: asyncio.Queue[DeploymentEvent] = asyncio.Queue() + + # Run deployment in background task with event queue + deployment_task = asyncio.create_task(self._deploy_to_azure(config, entity_path, auth_token, event_queue)) + + # Stream events from queue while deployment runs + while True: + try: + # Check if deployment task is done + if deployment_task.done(): + # Get the result or exception + deployment_url = await deployment_task + break + + # Get event from queue with short timeout + event = await asyncio.wait_for(event_queue.get(), timeout=0.1) + yield event + except asyncio.TimeoutError: + # No event in queue, continue waiting + continue + + # Step 5: Store deployment record + deployment = Deployment( + id=deployment_id, + entity_id=config.entity_id, + resource_group=config.resource_group, + app_name=config.app_name, + region=config.region, + url=deployment_url, + status="deployed", + created_at=datetime.now(timezone.utc).isoformat(), + ) + self._deployments[deployment_id] = deployment + + # Step 6: Success - return URL and token + yield DeploymentEvent( + type="deploy.completed", + message=f"Deployment successful! URL: {deployment_url}", + url=deployment_url, + auth_token=auth_token, # Shown once to user + ) + + except Exception as e: + error_msg = f"Deployment failed: {e!s}" + logger.exception(error_msg) + + # Store failed deployment + deployment = Deployment( + id=deployment_id, + entity_id=config.entity_id, + resource_group=config.resource_group, + app_name=config.app_name, + region=config.region, + url="", + status="failed", + created_at=datetime.now(timezone.utc).isoformat(), + error=str(e), + ) + self._deployments[deployment_id] = deployment + + yield DeploymentEvent( + type="deploy.failed", + message=error_msg, + ) + + async def _validate_prerequisites(self) -> None: + """Validate that Azure CLI, Docker, authentication, and resource providers are available. + + Raises: + ValueError: If prerequisites not met + """ + # Check Azure CLI + az_check = await asyncio.create_subprocess_exec( + "az", "--version", stdout=asyncio.subprocess.PIPE, stderr=asyncio.subprocess.PIPE + ) + await az_check.communicate() + if az_check.returncode != 0: + raise ValueError( + "Azure CLI not found. Install from: https://learn.microsoft.com/cli/azure/install-azure-cli" + ) + + # Check Docker + docker_check = await asyncio.create_subprocess_exec( + "docker", "--version", stdout=asyncio.subprocess.PIPE, stderr=asyncio.subprocess.PIPE + ) + await docker_check.communicate() + if docker_check.returncode != 0: + raise ValueError("Docker not found. Install from: https://www.docker.com/get-started") + + # Check Azure authentication + az_account_check = await asyncio.create_subprocess_exec( + "az", "account", "show", stdout=asyncio.subprocess.PIPE, stderr=asyncio.subprocess.PIPE + ) + stdout, _ = await az_account_check.communicate() + if az_account_check.returncode != 0: + raise ValueError("Not authenticated with Azure. Run: az login") + + # Check required resource providers are registered + required_providers = ["Microsoft.App", "Microsoft.ContainerRegistry", "Microsoft.OperationalInsights"] + unregistered_providers = [] + + # Get list of registered providers + provider_check = await asyncio.create_subprocess_exec( + "az", + "provider", + "list", + "--query", + "[?registrationState=='Registered'].namespace", + "--output", + "json", + stdout=asyncio.subprocess.PIPE, + stderr=asyncio.subprocess.PIPE, + ) + stdout, _stderr = await provider_check.communicate() + + if provider_check.returncode == 0: + import json + + try: + registered = json.loads(stdout.decode()) + for provider in required_providers: + if provider not in registered: + unregistered_providers.append(provider) + except json.JSONDecodeError: + logger.warning("Could not parse provider list, skipping provider validation") + else: + logger.warning("Could not check provider registration status") + + if unregistered_providers: + commands = [f"az provider register -n {p} --wait" for p in unregistered_providers] + raise ValueError( + f"Required Azure resource providers not registered: {', '.join(unregistered_providers)}\n\n" + f"Register them by running:\n" + "\n".join(commands) + "\n\n" + "This is a one-time setup per Azure subscription." + ) + + logger.info("All prerequisites validated successfully") + + async def _generate_dockerfile(self, entity_path: Path, config: DeploymentConfig) -> Path: + """Generate Dockerfile for entity deployment. + + Args: + entity_path: Path to entity directory + config: Deployment configuration + + Returns: + Path to generated Dockerfile + """ + # Validate ui_mode + if config.ui_mode not in ["user", "developer"]: + raise ValueError(f"Invalid ui_mode: {config.ui_mode}. Must be 'user' or 'developer'.") + + # Check if requirements.txt exists in the entity directory + has_requirements = (entity_path / "requirements.txt").exists() + + requirements_section = "" + if has_requirements: + logger.info(f"Found requirements.txt in {entity_path}, will include in Dockerfile") + requirements_section = """# Install entity dependencies +COPY requirements.txt ./ +RUN pip install -r requirements.txt +""" + else: + logger.info(f"No requirements.txt found in {entity_path}, skipping dependency installation") + + dockerfile_content = f"""FROM python:3.11-slim +WORKDIR /app + +{requirements_section}# Install DevUI from PyPI +RUN pip install agent-framework-devui --pre + +# Copy entity code +COPY . /app/entity/ + +ENV PORT=8080 +EXPOSE 8080 + +# Launch DevUI with auth enabled (token from environment variable) +CMD ["devui", "/app/entity", "--mode", "{config.ui_mode}", "--host", "0.0.0.0", "--port", "8080", "--auth"] +""" + + dockerfile_path = entity_path / "Dockerfile" + + # Warn if Dockerfile already exists + if dockerfile_path.exists(): + logger.warning(f"Dockerfile already exists at {dockerfile_path}, overwriting...") + + dockerfile_path.write_text(dockerfile_content) + logger.info(f"Generated Dockerfile at {dockerfile_path}") + + return dockerfile_path + + async def _discover_container_app_environment(self, resource_group: str, region: str) -> str | None: + """Discover existing Container App Environment in resource group. + + Args: + resource_group: Resource group name + region: Azure region (for filtering if needed) + + Returns: + Environment name if found, None otherwise + """ + cmd = [ + "az", + "containerapp", + "env", + "list", + "--resource-group", + resource_group, + "--query", + "[0].name", + "--output", + "tsv", + ] + + logger.info(f"Discovering existing Container App Environments in {resource_group}...") + + process = await asyncio.create_subprocess_exec( + *cmd, stdout=asyncio.subprocess.PIPE, stderr=asyncio.subprocess.PIPE + ) + + stdout, stderr = await process.communicate() + + if process.returncode == 0: + env_name = stdout.decode().strip() + if env_name: + logger.info(f"Found existing environment: {env_name}") + return env_name + logger.info("No existing environments found in resource group") + return None + logger.warning(f"Failed to query environments: {stderr.decode()}") + return None + + async def _deploy_to_azure( + self, config: DeploymentConfig, entity_path: Path, auth_token: str, event_queue: asyncio.Queue[DeploymentEvent] + ) -> str: + """Deploy to Azure Container Apps, reusing existing environments. + + Args: + config: Deployment configuration + entity_path: Path to entity directory + auth_token: Authentication token to inject + event_queue: Queue for streaming progress events + + Returns: + Deployment URL + + Raises: + ValueError: If deployment fails + """ + # Step 1: Try to discover existing Container App Environment + existing_env = await self._discover_container_app_environment(config.resource_group, config.region) + + if existing_env: + # Use existing environment - avoids needing environment creation permissions + logger.info(f"Reusing existing Container App Environment: {existing_env} (cost efficient, no side effects)") + cmd = [ + "az", + "containerapp", + "up", + "--name", + config.app_name, + "--resource-group", + config.resource_group, + "--environment", + existing_env, + "--source", + str(entity_path), + "--env-vars", + f"DEVUI_AUTH_TOKEN={auth_token}", + "--ingress", + "external", + "--target-port", + "8080", + ] + logger.info(f"Creating new Container App '{config.app_name}' in environment '{existing_env}'...") + else: + # No existing environment - try to create one (may fail if no permissions) + logger.warning( + "No existing Container App Environment found. " + "Attempting to create new environment (requires Microsoft.App/managedEnvironments/write permission)..." + ) + cmd = [ + "az", + "containerapp", + "up", + "--name", + config.app_name, + "--resource-group", + config.resource_group, + "--location", + config.region, + "--source", + str(entity_path), + "--env-vars", + f"DEVUI_AUTH_TOKEN={auth_token}", + "--ingress", + "external", + "--target-port", + "8080", + ] + + logger.info(f"Running: {' '.join(cmd)}") + + process = await asyncio.create_subprocess_exec( + *cmd, stdout=asyncio.subprocess.PIPE, stderr=asyncio.subprocess.STDOUT + ) + + # Stream output line by line + output_lines = [] + try: + if not process.stdout: + raise ValueError("Failed to capture process output") + + while True: + # Read with timeout + line = await asyncio.wait_for(process.stdout.readline(), timeout=600) + if not line: + break + + line_text = line.decode().strip() + if line_text: + output_lines.append(line_text) + + # Stream meaningful updates to user + if "WARNING:" in line_text: + # Parse and send user-friendly warnings + if "Creating resource group" in line_text: + await event_queue.put( + DeploymentEvent( + type="deploy.progress", + message=f"Creating resource group '{config.resource_group}'...", + ) + ) + elif "Creating ContainerAppEnvironment" in line_text: + await event_queue.put( + DeploymentEvent( + type="deploy.progress", + message="Setting up Container App Environment (this may take 2-3 minutes)...", + ) + ) + elif "Registering resource provider" in line_text: + provider = line_text.split("provider")[-1].strip() + if provider.endswith("..."): + provider = provider[:-3] + await event_queue.put( + DeploymentEvent( + type="deploy.progress", message=f"Registering Azure provider{provider}..." + ) + ) + elif "Creating Azure Container Registry" in line_text: + await event_queue.put( + DeploymentEvent( + type="deploy.progress", message="Creating Container Registry for your images..." + ) + ) + elif "No Log Analytics workspace" in line_text: + await event_queue.put( + DeploymentEvent( + type="deploy.progress", message="Creating Log Analytics workspace for monitoring..." + ) + ) + elif "Building image" in line_text: + await event_queue.put( + DeploymentEvent( + type="deploy.progress", + message="Building Docker image (this may take several minutes)...", + ) + ) + elif "Pushing image" in line_text: + await event_queue.put( + DeploymentEvent( + type="deploy.progress", message="Pushing image to Azure Container Registry..." + ) + ) + elif "Creating Container App" in line_text: + await event_queue.put( + DeploymentEvent(type="deploy.progress", message="Creating your Container App...") + ) + elif "Container app created" in line_text: + await event_queue.put( + DeploymentEvent(type="deploy.progress", message="Container app created successfully!") + ) + elif "ERROR:" in line_text: + # Stream errors immediately + await event_queue.put(DeploymentEvent(type="deploy.error", message=line_text)) + elif "Step" in line_text and "/" in line_text: + # Docker build steps + await event_queue.put( + DeploymentEvent(type="deploy.progress", message=f"Docker build: {line_text}") + ) + elif "https://" in line_text and ".azurecontainerapps.io" in line_text: + # Deployment URL detected + await event_queue.put( + DeploymentEvent(type="deploy.progress", message="Deployment URL generated!") + ) + + # Wait for process to complete + return_code = await process.wait() + + if return_code != 0: + error_output = "\n".join(output_lines[-10:]) # Last 10 lines for context + raise ValueError(f"Azure deployment failed:\n{error_output}") + + except asyncio.TimeoutError as e: + process.kill() + raise ValueError( + "Azure deployment timed out after 10 minutes. Please check Azure portal for status." + ) from e + + # Parse output to extract FQDN + output = "\n".join(output_lines) + logger.debug(f"Azure CLI output: {output}") + + # Extract FQDN from output (az containerapp up returns it) + # Format: https://...azurecontainerapps.io + deployment_url = self._extract_fqdn_from_output(output, config.app_name) + + logger.info(f"Deployment successful: {deployment_url}") + return deployment_url + + def _extract_fqdn_from_output(self, output: str, app_name: str) -> str: + """Extract FQDN from Azure CLI output. + + Args: + output: Azure CLI command output + app_name: Container app name + + Returns: + Full HTTPS URL to deployed app + """ + # Try to find FQDN in output + for line in output.split("\n"): + if "fqdn" in line.lower() or app_name in line: + # Extract URL-like string + match = re.search(r"https?://[\w\-\.]+\.azurecontainerapps\.io", line) + if match: + return match.group(0) + + # If we can't extract FQDN, fail explicitly rather than return a broken URL + logger.error(f"Could not extract FQDN from Azure CLI output. Output:\n{output}") + raise ValueError( + "Could not extract deployment URL from Azure CLI output. " + "The deployment may have succeeded - check the Azure portal for your container app URL." + ) + + async def list_deployments(self, entity_id: str | None = None) -> list[Deployment]: + """List all deployments, optionally filtered by entity. + + Args: + entity_id: Optional entity ID to filter by + + Returns: + List of deployment records + """ + if entity_id: + return [d for d in self._deployments.values() if d.entity_id == entity_id] + return list(self._deployments.values()) + + async def get_deployment(self, deployment_id: str) -> Deployment | None: + """Get deployment by ID. + + Args: + deployment_id: Deployment ID + + Returns: + Deployment record or None if not found + """ + return self._deployments.get(deployment_id) + + async def delete_deployment(self, deployment_id: str) -> None: + """Delete deployment from Azure Container Apps. + + Args: + deployment_id: Deployment ID to delete + + Raises: + ValueError: If deployment not found or deletion fails + """ + deployment = self._deployments.get(deployment_id) + if not deployment: + raise ValueError(f"Deployment {deployment_id} not found") + + # Execute: az containerapp delete + cmd = [ + "az", + "containerapp", + "delete", + "--name", + deployment.app_name, + "--resource-group", + deployment.resource_group, + "--yes", # Skip confirmation + ] + + logger.info(f"Deleting deployment: {' '.join(cmd)}") + + process = await asyncio.create_subprocess_exec( + *cmd, stdout=asyncio.subprocess.PIPE, stderr=asyncio.subprocess.PIPE + ) + + stdout, stderr = await process.communicate() + + if process.returncode != 0: + error_output = stderr.decode() if stderr else stdout.decode() + raise ValueError(f"Deployment deletion failed: {error_output}") + + # Remove from store + del self._deployments[deployment_id] + logger.info(f"Deployment {deployment_id} deleted successfully") diff --git a/python/packages/devui/agent_framework_devui/_discovery.py b/python/packages/devui/agent_framework_devui/_discovery.py index 213af1f0e1..99265b5d52 100644 --- a/python/packages/devui/agent_framework_devui/_discovery.py +++ b/python/packages/devui/agent_framework_devui/_discovery.py @@ -4,6 +4,7 @@ from __future__ import annotations +import ast import importlib import importlib.util import logging @@ -31,6 +32,7 @@ def __init__(self, entities_dir: str | None = None): self.entities_dir = entities_dir self._entities: dict[str, EntityInfo] = {} self._loaded_objects: dict[str, Any] = {} + self._cleanup_hooks: dict[str, list[Any]] = {} async def discover_entities(self) -> list[EntityInfo]: """Scan for Agent Framework entities. @@ -70,14 +72,15 @@ def get_entity_object(self, entity_id: str) -> Any | None: """ return self._loaded_objects.get(entity_id) - async def load_entity(self, entity_id: str) -> Any: - """Load entity on-demand (lazy loading). + async def load_entity(self, entity_id: str, checkpoint_manager: Any = None) -> Any: + """Load entity on-demand and inject checkpoint storage for workflows. This method implements lazy loading by importing the entity module only when needed. In-memory entities are returned from cache immediately. Args: entity_id: Entity identifier + checkpoint_manager: Optional checkpoint manager for workflow storage injection Returns: Loaded entity object @@ -107,9 +110,13 @@ async def load_entity(self, entity_id: str) -> Any: else: raise ValueError( f"Unsupported entity source: {entity_info.source}. " - f"Only 'directory' and 'in_memory' sources are supported." + f"Only 'directory' and 'in-memory' sources are supported." ) + # Note: Checkpoint storage is now injected at runtime via run_stream() parameter, + # not at load time. This provides cleaner architecture and explicit control flow. + # See _executor.py _execute_workflow() for runtime checkpoint storage injection. + # Enrich metadata with actual entity data # Don't pass entity_type if it's "unknown" - let inference determine the real type enriched_info = await self.create_entity_info_from_object( @@ -122,11 +129,27 @@ async def load_entity(self, entity_id: str) -> Any: # Preserve the original path from sparse metadata if "path" in entity_info.metadata: enriched_info.metadata["path"] = entity_info.metadata["path"] + # Now that we have the path, properly check deployment support + entity_path = Path(entity_info.metadata["path"]) + deployment_supported, deployment_reason = self._check_deployment_support(entity_path, entity_info.source) + enriched_info.deployment_supported = deployment_supported + enriched_info.deployment_reason = deployment_reason enriched_info.metadata["lazy_loaded"] = True self._entities[entity_id] = enriched_info # Cache the loaded object self._loaded_objects[entity_id] = entity_obj + + # Check module-level registry for cleanup hooks + from . import _get_registered_cleanup_hooks + + registered_hooks = _get_registered_cleanup_hooks(entity_obj) + if registered_hooks: + if entity_id not in self._cleanup_hooks: + self._cleanup_hooks[entity_id] = [] + self._cleanup_hooks[entity_id].extend(registered_hooks) + logger.debug(f"Discovered {len(registered_hooks)} registered cleanup hook(s) for: {entity_id}") + logger.info(f"Successfully loaded entity: {entity_id} (type: {enriched_info.type})") return entity_obj @@ -187,6 +210,17 @@ def list_entities(self) -> list[EntityInfo]: """ return list(self._entities.values()) + def get_cleanup_hooks(self, entity_id: str) -> list[Any]: + """Get cleanup hooks registered for an entity. + + Args: + entity_id: Entity identifier + + Returns: + List of cleanup hooks for the entity + """ + return self._cleanup_hooks.get(entity_id, []) + def invalidate_entity(self, entity_id: str) -> None: """Invalidate (clear cache for) an entity to enable hot reload. @@ -239,6 +273,17 @@ def register_entity(self, entity_id: str, entity_info: EntityInfo, entity_object """ self._entities[entity_id] = entity_info self._loaded_objects[entity_id] = entity_object + + # Check module-level registry for cleanup hooks + from . import _get_registered_cleanup_hooks + + registered_hooks = _get_registered_cleanup_hooks(entity_object) + if registered_hooks: + if entity_id not in self._cleanup_hooks: + self._cleanup_hooks[entity_id] = [] + self._cleanup_hooks[entity_id].extend(registered_hooks) + logger.debug(f"Discovered {len(registered_hooks)} registered cleanup hook(s) for: {entity_id}") + logger.debug(f"Registered entity: {entity_id} ({entity_info.type})") async def create_entity_info_from_object( @@ -305,6 +350,17 @@ async def create_entity_info_from_object( elif not has_run_stream and not has_run: logger.warning(f"Agent '{entity_id}' lacks both run() and run_stream() methods. May not work.") + # Check deployment support based on source + # For directory-based entities, we need the path to verify deployment support + deployment_supported = False + deployment_reason = "In-memory entities cannot be deployed (no source directory)" + + if source == "directory": + # Directory-based entity - will be checked properly after enrichment when path is available + # For now, mark as potentially deployable - will be re-evaluated after enrichment + deployment_supported = True + deployment_reason = "Ready for deployment (pending path verification)" + # Create EntityInfo with Agent Framework specifics return EntityInfo( id=entity_id, @@ -321,6 +377,8 @@ async def create_entity_info_from_object( executors=tools_list if entity_type == "workflow" else [], input_schema={"type": "string"}, # Default schema start_executor_id=tools_list[0] if tools_list and entity_type == "workflow" else None, + deployment_supported=deployment_supported, + deployment_reason=deployment_reason, metadata={ "source": "agent_framework_object", "class_name": entity_object.__class__.__name__ @@ -404,6 +462,31 @@ def _detect_entity_type(self, dir_path: Path) -> str: # Has __init__.py but no specific file return "unknown" + def _check_deployment_support(self, entity_path: Path, source: str) -> tuple[bool, str | None]: + """Check if entity can be deployed to Azure Container Apps. + + Args: + entity_path: Path to entity directory or file + source: Entity source ("directory" or "in_memory") + + Returns: + Tuple of (supported, reason) explaining deployment eligibility + """ + # In-memory entities cannot be deployed + if source == "in_memory": + return False, "In-memory entities cannot be deployed (no source directory)" + + # File-based entities need a directory structure for deployment + if not entity_path.is_dir(): + return False, "Only directory-based entities can be deployed" + + # Must have __init__.py + if not (entity_path / "__init__.py").exists(): + return False, "Missing __init__.py file" + + # Passed all checks + return True, "Ready for deployment" + def _register_sparse_entity(self, dir_path: Path) -> None: """Register entity with sparse metadata (no import). @@ -413,6 +496,9 @@ def _register_sparse_entity(self, dir_path: Path) -> None: entity_id = dir_path.name entity_type = self._detect_entity_type(dir_path) + # Check deployment support + deployment_supported, deployment_reason = self._check_deployment_support(dir_path, "directory") + entity_info = EntityInfo( id=entity_id, name=entity_id.replace("_", " ").title(), @@ -421,6 +507,8 @@ def _register_sparse_entity(self, dir_path: Path) -> None: tools=[], # Sparse - will be populated on load description="", # Sparse - will be populated on load source="directory", + deployment_supported=deployment_supported, + deployment_reason=deployment_reason, metadata={ "path": str(dir_path), "discovered": True, @@ -431,14 +519,52 @@ def _register_sparse_entity(self, dir_path: Path) -> None: self._entities[entity_id] = entity_info logger.debug(f"Registered sparse entity: {entity_id} (type: {entity_type})") + def _has_entity_exports(self, file_path: Path) -> bool: + """Check if a Python file has entity exports (agent or workflow) using AST parsing. + + This safely checks for module-level assignments like: + - agent = ChatAgent(...) + - workflow = WorkflowBuilder()... + + Args: + file_path: Python file to check + + Returns: + True if file has 'agent' or 'workflow' exports + """ + try: + # Read and parse the file's AST + source = file_path.read_text(encoding="utf-8") + tree = ast.parse(source, filename=str(file_path)) + + # Look for module-level assignments of 'agent' or 'workflow' + for node in ast.walk(tree): + if isinstance(node, ast.Assign): + for target in node.targets: + if isinstance(target, ast.Name) and target.id in ("agent", "workflow"): + return True + except Exception as e: + logger.debug(f"Could not parse {file_path} for entity exports: {e}") + return False + + return False + def _register_sparse_file_entity(self, file_path: Path) -> None: """Register file-based entity with sparse metadata (no import). Args: file_path: Entity Python file """ + # Check if file has valid entity exports using AST parsing + if not self._has_entity_exports(file_path): + logger.debug(f"Skipping {file_path.name} - no 'agent' or 'workflow' exports found") + return + entity_id = file_path.stem + # Check deployment support (file-based entities cannot be deployed) + deployment_supported, deployment_reason = self._check_deployment_support(file_path, "directory") + # File-based entities are typically agents, but we can't know for sure without importing entity_info = EntityInfo( id=entity_id, @@ -448,6 +574,8 @@ def _register_sparse_file_entity(self, file_path: Path) -> None: tools=[], description="", source="directory", + deployment_supported=deployment_supported, + deployment_reason=deployment_reason, metadata={ "path": str(file_path), "discovered": True, diff --git a/python/packages/devui/agent_framework_devui/_executor.py b/python/packages/devui/agent_framework_devui/_executor.py index 16d57c94b5..f1ca1c6a62 100644 --- a/python/packages/devui/agent_framework_devui/_executor.py +++ b/python/packages/devui/agent_framework_devui/_executor.py @@ -9,6 +9,7 @@ from typing import Any from agent_framework import AgentProtocol +from agent_framework._workflows._events import RequestInfoEvent from ._conversations import ConversationStore, InMemoryConversationStore from ._discovery import EntityDiscovery @@ -50,6 +51,11 @@ def __init__( # Use provided conversation store or default to in-memory self.conversation_store = conversation_store or InMemoryConversationStore() + # Create checkpoint manager (wraps conversation store) + from ._conversations import CheckpointConversationManager + + self.checkpoint_manager = CheckpointConversationManager(self.conversation_store) + def _setup_tracing_provider(self) -> None: """Set up our own TracerProvider so we can add processors.""" try: @@ -79,10 +85,20 @@ def _setup_agent_framework_tracing(self) -> None: # Configure Agent Framework tracing only if ENABLE_OTEL is set if os.environ.get("ENABLE_OTEL"): try: - from agent_framework.observability import setup_observability - - setup_observability(enable_sensitive_data=True) - logger.info("Enabled Agent Framework observability") + from agent_framework.observability import OBSERVABILITY_SETTINGS, setup_observability + + # Only configure if not already executed + if not OBSERVABILITY_SETTINGS._executed_setup: + # Get OTLP endpoint from either custom or standard env var + # This handles the case where env vars are set after ObservabilitySettings was imported + otlp_endpoint = os.environ.get("OTLP_ENDPOINT") or os.environ.get("OTEL_EXPORTER_OTLP_ENDPOINT") + + # Pass the endpoint explicitly to setup_observability + # This ensures OTLP exporters are created even if env vars were set late + setup_observability(enable_sensitive_data=True, otlp_endpoint=otlp_endpoint) + logger.info("Enabled Agent Framework observability") + else: + logger.debug("Agent Framework observability already configured") except Exception as e: logger.warning(f"Failed to enable Agent Framework observability: {e}") else: @@ -173,7 +189,7 @@ async def execute_entity(self, entity_id: str, request: AgentFrameworkRequest) - entity_info = self.get_entity_info(entity_id) # Trigger lazy loading (will return from cache if already loaded) - entity_obj = await self.entity_discovery.load_entity(entity_id) + entity_obj = await self.entity_discovery.load_entity(entity_id, checkpoint_manager=self.checkpoint_manager) if not entity_obj: raise EntityNotFoundError(f"Entity object for '{entity_id}' not found") @@ -190,6 +206,15 @@ async def execute_entity(self, entity_id: str, request: AgentFrameworkRequest) - yield event elif entity_info.type == "workflow": async for event in self._execute_workflow(entity_obj, request, trace_collector): + # Log RequestInfoEvent for debugging HIL flow + event_class = event.__class__.__name__ if hasattr(event, "__class__") else type(event).__name__ + if event_class == "RequestInfoEvent": + logger.info("🔔 [EXECUTOR] RequestInfoEvent detected from workflow!") + logger.info(f" request_id: {getattr(event, 'request_id', 'N/A')}") + logger.info(f" source_executor_id: {getattr(event, 'source_executor_id', 'N/A')}") + logger.info(f" request_type: {getattr(event, 'request_type', 'N/A')}") + data = getattr(event, "data", None) + logger.info(f" data type: {type(data).__name__ if data else 'None'}") yield event else: raise ValueError(f"Unsupported entity type: {entity_info.type}") @@ -289,7 +314,7 @@ async def _execute_agent( async def _execute_workflow( self, workflow: Any, request: AgentFrameworkRequest, trace_collector: Any ) -> AsyncGenerator[Any, None]: - """Execute Agent Framework workflow with trace collection. + """Execute Agent Framework workflow with checkpoint support via conversation items. Args: workflow: Workflow object to execute @@ -300,23 +325,199 @@ async def _execute_workflow( Workflow events and trace events """ try: - # Get input data directly from request.input field - input_data = request.input - logger.debug(f"Using input field: {type(input_data)}") + entity_id = request.get_entity_id() or "unknown" + + # Get or create session conversation for checkpoint storage + conversation_id = request.get_conversation_id() + if not conversation_id: + # Create default session if not provided + import time + import uuid + + conversation_id = f"session_{entity_id}_{uuid.uuid4().hex[:8]}" + logger.info(f"Created new workflow session: {conversation_id}") + + # Create conversation in store + self.conversation_store.create_conversation( + metadata={ + "entity_id": entity_id, + "type": "workflow_session", + "created_at": str(int(time.time())), + }, + conversation_id=conversation_id, + ) + else: + # Validate conversation exists, create if missing (handles deleted conversations) + import time + + existing = self.conversation_store.get_conversation(conversation_id) + if not existing: + logger.warning(f"Conversation {conversation_id} not found (may have been deleted), recreating") + self.conversation_store.create_conversation( + metadata={ + "entity_id": entity_id, + "type": "workflow_session", + "created_at": str(int(time.time())), + }, + conversation_id=conversation_id, + ) + + # Get session-scoped checkpoint storage (InMemoryCheckpointStorage from conv_data) + # Each conversation has its own storage instance, providing automatic session isolation. + # This storage is passed to workflow.run_stream() which sets it as runtime override, + # ensuring all checkpoint operations (save/load) use THIS conversation's storage. + # The framework guarantees runtime storage takes precedence over build-time storage. + checkpoint_storage = self.checkpoint_manager.get_checkpoint_storage(conversation_id) + + # Check for HIL responses first + hil_responses = self._extract_workflow_hil_responses(request.input) + + # Determine checkpoint_id (explicit or auto-latest for HIL responses) + checkpoint_id = None + if request.extra_body and "checkpoint_id" in request.extra_body: + checkpoint_id = request.extra_body["checkpoint_id"] + logger.debug(f"Using explicit checkpoint_id from request: {checkpoint_id}") + elif hil_responses: + # Only auto-resume from latest checkpoint when we have HIL responses + # Regular "Run" clicks should start fresh, not resume from checkpoints + checkpoints = await checkpoint_storage.list_checkpoints() # No workflow_id filter needed! + if checkpoints: + latest = max(checkpoints, key=lambda cp: cp.timestamp) + checkpoint_id = latest.checkpoint_id + logger.info(f"Auto-resuming from latest checkpoint in session {conversation_id}: {checkpoint_id}") + else: + logger.warning(f"HIL responses received but no checkpoints in session {conversation_id}") + + if hil_responses: + # HIL continuation mode requires checkpointing + if not checkpoint_id: + error_msg = ( + "Cannot process HIL responses without a checkpoint. " + "Workflows using HIL must be configured with .with_checkpointing() " + "and a checkpoint must exist before sending responses." + ) + logger.error(error_msg) + yield {"type": "error", "message": error_msg} + return + + logger.info(f"Resuming workflow with HIL responses for {len(hil_responses)} request(s)") + + # Unwrap primitive responses if they're wrapped in {response: value} format + from ._utils import parse_input_for_type + + unwrapped_responses = {} + for request_id, response_value in hil_responses.items(): + if isinstance(response_value, dict) and "response" in response_value: + response_value = response_value["response"] + unwrapped_responses[request_id] = response_value + + hil_responses = unwrapped_responses + + # NOTE: Two-step approach for stateless HTTP (framework limitation): + # 1. Restore checkpoint to load pending requests into workflow's in-memory state + # 2. Then send responses using send_responses_streaming + # Future: Framework should support run_stream(checkpoint_id, responses) in single call + # (checkpoint_id is guaranteed to exist due to earlier validation) + logger.debug(f"Restoring checkpoint {checkpoint_id} then sending HIL responses") + + try: + # Step 1: Restore checkpoint to populate workflow's in-memory pending requests + restored = False + async for _event in workflow.run_stream( + checkpoint_id=checkpoint_id, checkpoint_storage=checkpoint_storage + ): + restored = True + break # Stop immediately after restoration, don't process events + + if not restored: + raise RuntimeError("Checkpoint restoration did not yield any events") + + # Reset running flags so we can call send_responses_streaming + if hasattr(workflow, "_is_running"): + workflow._is_running = False + if hasattr(workflow, "_runner") and hasattr(workflow._runner, "_running"): + workflow._runner._running = False + + # Extract response types from restored workflow and convert responses to proper types + try: + if hasattr(workflow, "_runner") and hasattr(workflow._runner, "context"): + runner_context = workflow._runner.context + pending_requests_dict = await runner_context.get_pending_request_info_events() + + converted_responses = {} + for request_id, response_value in hil_responses.items(): + if request_id in pending_requests_dict: + pending_request = pending_requests_dict[request_id] + if hasattr(pending_request, "response_type"): + response_type = pending_request.response_type + try: + response_value = parse_input_for_type(response_value, response_type) + logger.debug( + f"Converted HIL response for {request_id} to {type(response_value)}" + ) + except Exception as e: + logger.warning(f"Failed to convert HIL response for {request_id}: {e}") + + converted_responses[request_id] = response_value + + hil_responses = converted_responses + except Exception as e: + logger.warning(f"Could not convert HIL responses to proper types: {e}") + + # Step 2: Now send responses to the in-memory workflow + async for event in workflow.send_responses_streaming(hil_responses): + for trace_event in trace_collector.get_pending_events(): + yield trace_event + yield event - # Parse input based on workflow's expected input type - parsed_input = await self._parse_workflow_input(workflow, input_data) + except (AttributeError, ValueError, RuntimeError) as e: + error_msg = f"Failed to send HIL responses: {e}" + logger.error(error_msg) + yield {"type": "error", "message": error_msg} - logger.debug(f"Executing workflow with parsed input type: {type(parsed_input)}") + elif checkpoint_id: + # Resume from checkpoint (explicit or auto-latest) using unified API + logger.info(f"Resuming workflow from checkpoint {checkpoint_id} in session {conversation_id}") - # Use Agent Framework workflow's native streaming - async for event in workflow.run_stream(parsed_input): - # Yield any pending trace events first - for trace_event in trace_collector.get_pending_events(): - yield trace_event + try: + async for event in workflow.run_stream( + checkpoint_id=checkpoint_id, checkpoint_storage=checkpoint_storage + ): + if isinstance(event, RequestInfoEvent): + self._enrich_request_info_event_with_response_schema(event, workflow) + + for trace_event in trace_collector.get_pending_events(): + yield trace_event - # Then yield the workflow event - yield event + yield event + + # Note: Removed break on RequestInfoEvent - continue yielding all events + # The workflow is already paused by ctx.request_info() in the framework + # DevUI should continue yielding events even during HIL pause + + except ValueError as e: + error_msg = f"Cannot resume from checkpoint: {e}" + logger.error(error_msg) + yield {"type": "error", "message": error_msg} + + else: + # First run - pass DevUI's checkpoint storage to enable checkpointing + logger.info(f"Starting fresh workflow in session {conversation_id}") + + parsed_input = await self._parse_workflow_input(workflow, request.input) + + async for event in workflow.run_stream(parsed_input, checkpoint_storage=checkpoint_storage): + if isinstance(event, RequestInfoEvent): + self._enrich_request_info_event_with_response_schema(event, workflow) + + for trace_event in trace_collector.get_pending_events(): + yield trace_event + + yield event + + # Note: Removed break on RequestInfoEvent - continue yielding all events + # The workflow is already paused by ctx.request_info() in the framework + # DevUI should continue yielding events even during HIL pause except Exception as e: logger.error(f"Error in workflow execution: {e}") @@ -569,6 +770,59 @@ def _get_start_executor_message_types(self, workflow: Any) -> tuple[Any | None, return start_executor, message_types + def _extract_workflow_hil_responses(self, input_data: Any) -> dict[str, Any] | None: + """Extract workflow HIL responses from OpenAI input format. + + Looks for special content type: workflow_hil_response + + Args: + input_data: OpenAI ResponseInputParam + + Returns: + Dict of {request_id: response_value} if found, None otherwise + """ + if not isinstance(input_data, list): + return None + + for item in input_data: + if isinstance(item, dict) and item.get("type") == "message": + message_content = item.get("content", []) + + if isinstance(message_content, list): + for content_item in message_content: + if isinstance(content_item, dict): + content_type = content_item.get("type") + + if content_type == "workflow_hil_response": + # Extract responses dict + # dict.get() returns Any, so we explicitly type it + responses: dict[str, Any] = content_item.get("responses", {}) # type: ignore[assignment] + logger.info(f"Found workflow HIL responses: {list(responses.keys())}") + return responses + + return None + + def _get_or_create_conversation(self, conversation_id: str, entity_id: str) -> Any: + """Get existing conversation or create a new one. + + Args: + conversation_id: Conversation ID from frontend + entity_id: Entity ID (e.g., "spam_workflow") for metadata filtering + + Returns: + Conversation object + """ + conversation = self.conversation_store.get_conversation(conversation_id) + if not conversation: + # Create conversation with frontend's ID + # Use agent_id in metadata so it can be filtered by list_conversations(agent_id=...) + conversation = self.conversation_store.create_conversation( + metadata={"agent_id": entity_id}, conversation_id=conversation_id + ) + logger.info(f"Created conversation {conversation_id} for entity {entity_id}") + + return conversation + def _parse_structured_workflow_input(self, workflow: Any, input_data: dict[str, Any]) -> Any: """Parse structured input data for workflow execution. @@ -644,3 +898,53 @@ def _parse_raw_workflow_input(self, workflow: Any, raw_input: str) -> Any: except Exception as e: logger.debug(f"Error parsing workflow input: {e}") return raw_input + + def _enrich_request_info_event_with_response_schema(self, event: Any, workflow: Any) -> None: + """Extract response type from workflow executor and attach response schema to RequestInfoEvent. + + Args: + event: RequestInfoEvent to enrich + workflow: Workflow object containing executors + """ + try: + from agent_framework_devui._utils import extract_response_type_from_executor, generate_input_schema + + # Get source executor ID and request type from event + source_executor_id = getattr(event, "source_executor_id", None) + request_type = getattr(event, "request_type", None) + + if not source_executor_id or not request_type: + logger.debug("RequestInfoEvent missing source_executor_id or request_type") + return + + # Find the source executor in the workflow + if not hasattr(workflow, "executors") or not isinstance(workflow.executors, dict): + logger.debug("Workflow doesn't have executors dict") + return + + source_executor = workflow.executors.get(source_executor_id) + if not source_executor: + logger.debug(f"Could not find executor '{source_executor_id}' in workflow") + return + + # Extract response type from the executor's handler signature + response_type = extract_response_type_from_executor(source_executor, request_type) + + if response_type: + # Generate JSON schema for response type + response_schema = generate_input_schema(response_type) + + # Attach response_schema to event for mapper to include in output + event._response_schema = response_schema + + logger.debug(f"Extracted response schema for {request_type.__name__}: {response_schema}") + else: + # Even if extraction fails, provide a reasonable default to avoid warnings + logger.debug( + f"Could not extract response type for {request_type.__name__}, using default string schema" + ) + response_schema = {"type": "string"} + event._response_schema = response_schema + + except Exception as e: + logger.warning(f"Failed to enrich RequestInfoEvent with response schema: {e}") diff --git a/python/packages/devui/agent_framework_devui/_mapper.py b/python/packages/devui/agent_framework_devui/_mapper.py index 8e234842c5..b8d5bf4526 100644 --- a/python/packages/devui/agent_framework_devui/_mapper.py +++ b/python/packages/devui/agent_framework_devui/_mapper.py @@ -34,6 +34,9 @@ ResponseFunctionCallArgumentsDeltaEvent, ResponseFunctionResultComplete, ResponseFunctionToolCall, + ResponseOutputData, + ResponseOutputFile, + ResponseOutputImage, ResponseOutputItemAddedEvent, ResponseOutputMessage, ResponseOutputText, @@ -160,7 +163,7 @@ async def convert_event(self, raw_event: Any, request: AgentFrameworkRequest) -> if isinstance(raw_event, ResponseTraceEvent): return [ ResponseTraceEventComplete( - type="response.trace.complete", + type="response.trace.completed", data=raw_event.data, item_id=context["item_id"], sequence_number=self._next_sequence(context), @@ -338,6 +341,147 @@ def _next_sequence(self, context: dict[str, Any]) -> int: context["sequence_counter"] += 1 return int(context["sequence_counter"]) + def _serialize_value(self, value: Any) -> Any: + """Recursively serialize a value, handling complex nested objects. + + Handles: + - Primitives (str, int, float, bool, None) + - Collections (list, tuple, set, dict) + - SerializationMixin objects (ChatMessage, etc.) - calls to_dict() + - Pydantic models - calls model_dump() + - Dataclasses - recursively serializes with asdict() + - Enums - extracts value + - datetime/date/UUID - converts to ISO string + + Args: + value: Value to serialize + + Returns: + JSON-serializable representation + """ + from dataclasses import is_dataclass + from datetime import date, datetime + from enum import Enum + from uuid import UUID + + # Handle None + if value is None: + return None + + # Handle primitives + if isinstance(value, (str, int, float, bool)): + return value + + # Handle datetime/date - convert to ISO format + if isinstance(value, datetime): + return value.isoformat() + if isinstance(value, date): + return value.isoformat() + + # Handle UUID - convert to string + if isinstance(value, UUID): + return str(value) + + # Handle Enums - extract value + if isinstance(value, Enum): + return value.value + + # Handle lists/tuples/sets - recursively serialize elements + if isinstance(value, (list, tuple)): + return [self._serialize_value(item) for item in value] + if isinstance(value, set): + return [self._serialize_value(item) for item in value] + + # Handle dicts - recursively serialize values + if isinstance(value, dict): + return {k: self._serialize_value(v) for k, v in value.items()} + + # Handle SerializationMixin (like ChatMessage) - call to_dict() + if hasattr(value, "to_dict") and callable(getattr(value, "to_dict", None)): + try: + return value.to_dict() # type: ignore[attr-defined, no-any-return] + except Exception as e: + logger.debug(f"Failed to serialize with to_dict(): {e}") + return str(value) + + # Handle Pydantic models - call model_dump() + if hasattr(value, "model_dump") and callable(getattr(value, "model_dump", None)): + try: + return value.model_dump() # type: ignore[attr-defined, no-any-return] + except Exception as e: + logger.debug(f"Failed to serialize Pydantic model: {e}") + return str(value) + + # Handle dataclasses - recursively serialize with asdict + if is_dataclass(value) and not isinstance(value, type): + try: + from dataclasses import asdict + + # Use our custom serializer as dict_factory + return asdict(value, dict_factory=lambda items: {k: self._serialize_value(v) for k, v in items}) + except Exception as e: + logger.debug(f"Failed to serialize nested dataclass: {e}") + return str(value) + + # Fallback: convert to string (for unknown types) + logger.debug(f"Serializing unknown type {type(value).__name__} as string") + return str(value) + + def _serialize_request_data(self, request_data: Any) -> dict[str, Any]: + """Serialize RequestInfoMessage to dict for JSON transmission. + + Handles nested SerializationMixin objects (like ChatMessage) within dataclasses. + + Args: + request_data: The RequestInfoMessage instance + + Returns: + Serialized dict representation + """ + from dataclasses import asdict, fields, is_dataclass + + if request_data is None: + return {} + + # Handle dict first (most common) + if isinstance(request_data, dict): + return {k: self._serialize_value(v) for k, v in request_data.items()} + + # Handle dataclasses with nested SerializationMixin objects + # We can't use asdict() directly because it doesn't handle ChatMessage + if is_dataclass(request_data) and not isinstance(request_data, type): + try: + # Manually serialize each field to handle nested SerializationMixin + result = {} + for field in fields(request_data): + field_value = getattr(request_data, field.name) + result[field.name] = self._serialize_value(field_value) + return result + except Exception as e: + logger.debug(f"Failed to serialize dataclass fields: {e}") + # Fallback to asdict() if our custom serialization fails + try: + return asdict(request_data) # type: ignore[arg-type] + except Exception as e2: + logger.debug(f"Failed to serialize dataclass with asdict(): {e2}") + + # Handle Pydantic models (have model_dump method) + if hasattr(request_data, "model_dump") and callable(getattr(request_data, "model_dump", None)): + try: + return request_data.model_dump() # type: ignore[attr-defined, no-any-return] + except Exception as e: + logger.debug(f"Failed to serialize Pydantic model: {e}") + + # Handle SerializationMixin (have to_dict method) + if hasattr(request_data, "to_dict") and callable(getattr(request_data, "to_dict", None)): + try: + return request_data.to_dict() # type: ignore[attr-defined, no-any-return] + except Exception as e: + logger.debug(f"Failed to serialize with to_dict(): {e}") + + # Fallback: string representation + return {"raw": str(request_data)} + async def _convert_agent_update(self, update: Any, context: dict[str, Any]) -> Sequence[Any]: """Convert agent text updates to proper content part events. @@ -638,7 +782,65 @@ async def _convert_workflow_event(self, event: Any, context: dict[str, Any]) -> return events - if event_class in ["WorkflowCompletedEvent", "WorkflowOutputEvent"]: + # Handle WorkflowOutputEvent separately to preserve output data + if event_class == "WorkflowOutputEvent": + output_data = getattr(event, "data", None) + source_executor_id = getattr(event, "source_executor_id", "unknown") + + if output_data is not None: + # Import required types + from openai.types.responses import ResponseOutputMessage, ResponseOutputText + from openai.types.responses.response_output_item_added_event import ResponseOutputItemAddedEvent + + # Increment output index for each yield_output + context["output_index"] = context.get("output_index", -1) + 1 + + # Extract text from output data based on type + text = None + if hasattr(output_data, "__class__") and output_data.__class__.__name__ == "ChatMessage": + # Handle ChatMessage (from Magentic and AgentExecutor with output_response=True) + text = getattr(output_data, "text", None) + if not text: + # Fallback to string representation + text = str(output_data) + elif isinstance(output_data, str): + # String output + text = output_data + else: + # Object/dict/list → JSON string + try: + text = json.dumps(output_data, indent=2) + except (TypeError, ValueError): + # Fallback to string representation if not JSON serializable + text = str(output_data) + + # Create output message with text content + text_content = ResponseOutputText(type="output_text", text=text, annotations=[]) + + output_message = ResponseOutputMessage( + type="message", + id=f"msg_{uuid4().hex[:8]}", + role="assistant", + content=[text_content], + status="completed", + ) + + # Emit output_item.added for each yield_output + logger.debug( + f"WorkflowOutputEvent converted to output_item.added " + f"(executor: {source_executor_id}, length: {len(text)})" + ) + return [ + ResponseOutputItemAddedEvent( + type="response.output_item.added", + item=output_message, + output_index=context["output_index"], + sequence_number=self._next_sequence(context), + ) + ] + + # Handle WorkflowCompletedEvent - emit response.completed + if event_class == "WorkflowCompletedEvent": workflow_id = context.get("workflow_id", str(uuid4())) # Import Response type for proper construction @@ -654,7 +856,7 @@ async def _convert_workflow_event(self, event: Any, context: dict[str, Any]) -> object="response", created_at=float(time.time()), model=model_name, - output=[], # Output should be populated by this point from text streaming + output=[], # Output items already sent via output_item.added events status="completed", parallel_tool_calls=False, tool_choice="none", @@ -781,8 +983,77 @@ async def _convert_workflow_event(self, event: Any, context: dict[str, Any]) -> ) ] - # Handle informational workflow events (status, warnings, errors) - if event_class in ["WorkflowStatusEvent", "WorkflowWarningEvent", "WorkflowErrorEvent", "RequestInfoEvent"]: + # Handle RequestInfoEvent specially - emit as HIL event with schema + if event_class == "RequestInfoEvent": + from .models._openai_custom import ResponseRequestInfoEvent + + request_id = getattr(event, "request_id", "") + source_executor_id = getattr(event, "source_executor_id", "") + request_type_class = getattr(event, "request_type", None) + request_data = getattr(event, "data", None) + + logger.info("📨 [MAPPER] Processing RequestInfoEvent") + logger.info(f" request_id: {request_id}") + logger.info(f" source_executor_id: {source_executor_id}") + logger.info(f" request_type_class: {request_type_class}") + logger.info(f" request_data: {request_data}") + + # Serialize request data + serialized_data = self._serialize_request_data(request_data) + logger.info(f" serialized_data: {serialized_data}") + + # Get request type name for debugging + request_type_name = "Unknown" + if request_type_class: + request_type_name = f"{request_type_class.__module__}:{request_type_class.__name__}" + + # Get response schema that was attached by executor + # This tells the UI what format to collect from the user + response_schema = getattr(event, "_response_schema", None) + if not response_schema: + # Fallback to string if somehow not set (shouldn't happen with current executor enrichment) + logger.warning(f"⚠️ Response schema not found for {request_type_name}, using default") + response_schema = {"type": "string"} + else: + logger.info(f" response_schema: {response_schema}") + + # Wrap primitive schemas in object for form rendering + # The UI's SchemaFormRenderer expects an object with properties + if response_schema.get("type") in ["string", "integer", "number", "boolean"]: + # Wrap primitive type in object with "response" field + wrapped_schema = { + "type": "object", + "properties": {"response": response_schema}, + "required": ["response"], + } + logger.info(" wrapped primitive schema in object") + else: + wrapped_schema = response_schema + + # Create HIL request event with response schema + hil_event = ResponseRequestInfoEvent( + type="response.request_info.requested", + request_id=request_id, + source_executor_id=source_executor_id, + request_type=request_type_name, + request_data=serialized_data, + request_schema=wrapped_schema, # Send wrapped schema for form rendering + response_schema=response_schema, # Keep original for reference + item_id=context["item_id"], + output_index=context.get("output_index", 0), + sequence_number=self._next_sequence(context), + timestamp=datetime.now().isoformat(), + ) + + logger.info("✅ [MAPPER] Created ResponseRequestInfoEvent:") + logger.info(f" type: {hil_event.type}") + logger.info(f" request_id: {hil_event.request_id}") + logger.info(f" sequence_number: {hil_event.sequence_number}") + + return [hil_event] + + # Handle other informational workflow events (status, warnings, errors) + if event_class in ["WorkflowStatusEvent", "WorkflowWarningEvent", "WorkflowErrorEvent"]: # These are informational events that don't map to OpenAI lifecycle events # Convert them to trace events for debugging visibility event_data: dict[str, Any] = {} @@ -795,13 +1066,10 @@ async def _convert_workflow_event(self, event: Any, context: dict[str, Any]) -> elif event_class == "WorkflowErrorEvent": event_data["message"] = str(getattr(event, "message", "")) event_data["error"] = str(getattr(event, "error", "")) - elif event_class == "RequestInfoEvent": - request_info = getattr(event, "data", {}) - event_data["request_info"] = request_info if isinstance(request_info, dict) else str(request_info) # Create a trace event for debugging trace_event = ResponseTraceEventComplete( - type="response.trace.complete", + type="response.trace.completed", data={ "trace_type": "workflow_info", "event_type": event_class, @@ -816,6 +1084,237 @@ async def _convert_workflow_event(self, event: Any, context: dict[str, Any]) -> return [trace_event] + # Handle Magentic-specific events + if event_class == "MagenticAgentDeltaEvent": + agent_id = getattr(event, "agent_id", "unknown_agent") + text = getattr(event, "text", None) + + if text: + events = [] + + # Track Magentic agent messages separately from regular messages + # Use timestamp to ensure uniqueness for multiple runs of same agent + magentic_key = f"magentic_message_{agent_id}" + + # Check if this is the first delta from this agent (need to create message container) + if magentic_key not in context: + # Create a unique message ID for this agent's streaming session + message_id = f"msg_{agent_id}_{uuid4().hex[:8]}" + context[magentic_key] = message_id + context["output_index"] = context.get("output_index", -1) + 1 + + # Import required types + from openai.types.responses import ResponseOutputMessage, ResponseOutputText + from openai.types.responses.response_content_part_added_event import ( + ResponseContentPartAddedEvent, + ) + from openai.types.responses.response_output_item_added_event import ResponseOutputItemAddedEvent + + # Emit message output item (container for the agent's message) + # This matches what _convert_agent_update does for regular agents + events.append( + ResponseOutputItemAddedEvent( + type="response.output_item.added", + output_index=context["output_index"], + sequence_number=self._next_sequence(context), + item=ResponseOutputMessage( + type="message", + id=message_id, + role="assistant", + content=[], + status="in_progress", + # Add metadata to identify this as a Magentic agent message + metadata={"agent_id": agent_id, "source": "magentic"}, # type: ignore[call-arg] + ), + ) + ) + + # Add content part for text (establishes the text container) + events.append( + ResponseContentPartAddedEvent( + type="response.content_part.added", + output_index=context["output_index"], + content_index=0, + item_id=message_id, + sequence_number=self._next_sequence(context), + part=ResponseOutputText(type="output_text", text="", annotations=[]), + ) + ) + + # Get the message ID for this agent + message_id = context[magentic_key] + + # Emit text delta event using the message ID (matches regular agent behavior) + events.append( + ResponseTextDeltaEvent( + type="response.output_text.delta", + output_index=context["output_index"], + content_index=0, # Always 0 for single text content + item_id=message_id, + delta=text, + logprobs=[], + sequence_number=self._next_sequence(context), + ) + ) + return events + + # Handle function calls from Magentic agents + if getattr(event, "function_call_id", None) and getattr(event, "function_call_name", None): + # Handle function call initiation + function_call_id = getattr(event, "function_call_id", None) + function_call_name = getattr(event, "function_call_name", None) + function_call_arguments = getattr(event, "function_call_arguments", None) + + # Track function call for accumulating arguments + context["active_function_calls"][function_call_id] = { + "item_id": function_call_id, + "name": function_call_name, + "arguments_chunks": [], + } + + # Emit function call output item + return [ + ResponseOutputItemAddedEvent( + type="response.output_item.added", + item=ResponseFunctionToolCall( + id=function_call_id, + call_id=function_call_id, + name=function_call_name, + arguments=json.dumps(function_call_arguments) if function_call_arguments else "", + type="function_call", + status="in_progress", + ), + output_index=context["output_index"], + sequence_number=self._next_sequence(context), + ) + ] + + # For other non-text deltas, emit as trace for debugging + return [ + ResponseTraceEventComplete( + type="response.trace.completed", + data={ + "trace_type": "magentic_delta", + "agent_id": agent_id, + "function_call_id": getattr(event, "function_call_id", None), + "function_call_name": getattr(event, "function_call_name", None), + "function_result_id": getattr(event, "function_result_id", None), + "timestamp": datetime.now().isoformat(), + }, + span_id=f"magentic_delta_{uuid4().hex[:8]}", + item_id=context["item_id"], + output_index=context.get("output_index", 0), + sequence_number=self._next_sequence(context), + ) + ] + + if event_class == "MagenticAgentMessageEvent": + agent_id = getattr(event, "agent_id", "unknown_agent") + message = getattr(event, "message", None) + + # Track Magentic agent messages + magentic_key = f"magentic_message_{agent_id}" + + # Check if we were streaming for this agent + if magentic_key in context: + # Mark the streaming message as complete + message_id = context[magentic_key] + + # Import required types + from openai.types.responses import ResponseOutputMessage + from openai.types.responses.response_output_item_done_event import ResponseOutputItemDoneEvent + + # Extract text from ChatMessage for the completed message + text = None + if message and hasattr(message, "text"): + text = message.text + + # Emit output_item.done to mark message as complete + events = [ + ResponseOutputItemDoneEvent( + type="response.output_item.done", + output_index=context["output_index"], + sequence_number=self._next_sequence(context), + item=ResponseOutputMessage( + type="message", + id=message_id, + role="assistant", + content=[], # Content already streamed via deltas + status="completed", + metadata={"agent_id": agent_id, "source": "magentic"}, # type: ignore[call-arg] + ), + ) + ] + + # Clean up context for this agent + del context[magentic_key] + + logger.debug(f"MagenticAgentMessageEvent from {agent_id} marked streaming message as complete") + return events + # No streaming occurred, create a complete message (shouldn't happen normally) + # Extract text from ChatMessage + text = None + if message and hasattr(message, "text"): + text = message.text + + if text: + # Emit as output item for this agent + from openai.types.responses import ResponseOutputMessage, ResponseOutputText + from openai.types.responses.response_output_item_added_event import ResponseOutputItemAddedEvent + + context["output_index"] = context.get("output_index", -1) + 1 + + text_content = ResponseOutputText(type="output_text", text=text, annotations=[]) + + output_message = ResponseOutputMessage( + type="message", + id=f"msg_{agent_id}_{uuid4().hex[:8]}", + role="assistant", + content=[text_content], + status="completed", + metadata={"agent_id": agent_id, "source": "magentic"}, # type: ignore[call-arg] + ) + + logger.debug( + f"MagenticAgentMessageEvent from {agent_id} converted to output_item.added (non-streaming)" + ) + return [ + ResponseOutputItemAddedEvent( + type="response.output_item.added", + item=output_message, + output_index=context["output_index"], + sequence_number=self._next_sequence(context), + ) + ] + + if event_class == "MagenticOrchestratorMessageEvent": + orchestrator_id = getattr(event, "orchestrator_id", "orchestrator") + message = getattr(event, "message", None) + kind = getattr(event, "kind", "unknown") + + # Extract text from ChatMessage + text = None + if message and hasattr(message, "text"): + text = message.text + + # Emit as trace event for orchestrator messages (typically task ledger, instructions) + return [ + ResponseTraceEventComplete( + type="response.trace.completed", + data={ + "trace_type": "magentic_orchestrator", + "orchestrator_id": orchestrator_id, + "kind": kind, + "text": text or str(message), + "timestamp": datetime.now().isoformat(), + }, + span_id=f"magentic_orch_{uuid4().hex[:8]}", + item_id=context["item_id"], + output_index=context.get("output_index", 0), + sequence_number=self._next_sequence(context), + ) + ] + # For unknown/legacy events, still emit as workflow event for backward compatibility # Get event data and serialize if it's a SerializationMixin raw_event_data = getattr(event, "data", None) @@ -830,7 +1329,7 @@ async def _convert_workflow_event(self, event: Any, context: dict[str, Any]) -> # Create structured workflow event (keeping for backward compatibility) workflow_event = ResponseWorkflowEventComplete( - type="response.workflow_event.complete", + type="response.workflow_event.completed", data={ "event_type": event.__class__.__name__, "data": serialized_event_data, @@ -1056,30 +1555,227 @@ async def _map_usage_content(self, content: Any, context: dict[str, Any]) -> Non # NO EVENT RETURNED - usage goes in final Response only return - async def _map_data_content(self, content: Any, context: dict[str, Any]) -> ResponseTraceEventComplete: - """Map DataContent to structured trace event.""" - return ResponseTraceEventComplete( - type="response.trace.complete", - data={ - "content_type": "data", - "data": getattr(content, "data", None), - "mime_type": getattr(content, "mime_type", "application/octet-stream"), - "size_bytes": len(str(getattr(content, "data", ""))) if getattr(content, "data", None) else 0, - "timestamp": datetime.now().isoformat(), - }, - item_id=context["item_id"], + async def _map_data_content( + self, content: Any, context: dict[str, Any] + ) -> ResponseOutputItemAddedEvent | ResponseTraceEventComplete: + """Map DataContent to proper output item (image/file/data) or fallback to trace. + + Maps Agent Framework DataContent to appropriate output types: + - Images (image/*) → ResponseOutputImage + - Common files (pdf, audio, video) → ResponseOutputFile + - Generic data → ResponseOutputData + - Unknown/debugging content → ResponseTraceEventComplete (fallback) + """ + mime_type = getattr(content, "mime_type", "application/octet-stream") + item_id = f"item_{uuid.uuid4().hex[:16]}" + + # Extract data/uri + data_value = getattr(content, "data", None) + uri_value = getattr(content, "uri", None) + + # Handle images + if mime_type.startswith("image/"): + # Prefer URI, but create data URI from data if needed + if uri_value: + image_url = uri_value + elif data_value: + # Convert bytes to base64 data URI + import base64 + + if isinstance(data_value, bytes): + b64_data = base64.b64encode(data_value).decode("utf-8") + else: + b64_data = str(data_value) + image_url = f"data:{mime_type};base64,{b64_data}" + else: + # No data available, fallback to trace + logger.warning(f"DataContent with {mime_type} has no data or uri, falling back to trace") + return ResponseTraceEventComplete( + type="response.trace.completed", + data={"content_type": "data", "mime_type": mime_type, "error": "No data or uri"}, + item_id=context["item_id"], + output_index=context["output_index"], + sequence_number=self._next_sequence(context), + ) + + return ResponseOutputItemAddedEvent( + type="response.output_item.added", + item=ResponseOutputImage( # type: ignore[arg-type] + id=item_id, + type="output_image", + image_url=image_url, + mime_type=mime_type, + alt_text=None, + ), + output_index=context["output_index"], + sequence_number=self._next_sequence(context), + ) + + # Handle common file types + if mime_type in [ + "application/pdf", + "audio/mp3", + "audio/wav", + "audio/m4a", + "audio/ogg", + "audio/flac", + "audio/aac", + "audio/mpeg", + "video/mp4", + "video/webm", + ]: + # Determine filename from mime type + ext = mime_type.split("/")[-1] + if ext == "mpeg": + ext = "mp3" # audio/mpeg → .mp3 + filename = f"output.{ext}" + + # Prefer URI + if uri_value: + file_url = uri_value + file_data = None + elif data_value: + # Convert bytes to base64 + import base64 + + if isinstance(data_value, bytes): + b64_data = base64.b64encode(data_value).decode("utf-8") + else: + b64_data = str(data_value) + file_url = f"data:{mime_type};base64,{b64_data}" + file_data = b64_data + else: + # No data available, fallback to trace + logger.warning(f"DataContent with {mime_type} has no data or uri, falling back to trace") + return ResponseTraceEventComplete( + type="response.trace.completed", + data={"content_type": "data", "mime_type": mime_type, "error": "No data or uri"}, + item_id=context["item_id"], + output_index=context["output_index"], + sequence_number=self._next_sequence(context), + ) + + return ResponseOutputItemAddedEvent( + type="response.output_item.added", + item=ResponseOutputFile( # type: ignore[arg-type] + id=item_id, + type="output_file", + filename=filename, + file_url=file_url, + file_data=file_data, + mime_type=mime_type, + ), + output_index=context["output_index"], + sequence_number=self._next_sequence(context), + ) + + # Handle generic data (structured data, JSON, etc.) + data_str = "" + if uri_value: + data_str = uri_value + elif data_value: + if isinstance(data_value, bytes): + try: + data_str = data_value.decode("utf-8") + except UnicodeDecodeError: + # Binary data, encode as base64 for display + import base64 + + data_str = base64.b64encode(data_value).decode("utf-8") + else: + data_str = str(data_value) + + return ResponseOutputItemAddedEvent( + type="response.output_item.added", + item=ResponseOutputData( # type: ignore[arg-type] + id=item_id, + type="output_data", + data=data_str, + mime_type=mime_type, + description=None, + ), output_index=context["output_index"], sequence_number=self._next_sequence(context), ) - async def _map_uri_content(self, content: Any, context: dict[str, Any]) -> ResponseTraceEventComplete: - """Map UriContent to structured trace event.""" + async def _map_uri_content( + self, content: Any, context: dict[str, Any] + ) -> ResponseOutputItemAddedEvent | ResponseTraceEventComplete: + """Map UriContent to proper output item (image/file) based on MIME type. + + UriContent has a URI and MIME type, so we can create appropriate output items: + - Images → ResponseOutputImage + - Common files → ResponseOutputFile + - Other URIs → ResponseTraceEventComplete (fallback for debugging) + """ + mime_type = getattr(content, "mime_type", "text/plain") + uri = getattr(content, "uri", "") + item_id = f"item_{uuid.uuid4().hex[:16]}" + + if not uri: + # No URI available, fallback to trace + logger.warning("UriContent has no uri, falling back to trace") + return ResponseTraceEventComplete( + type="response.trace.completed", + data={"content_type": "uri", "mime_type": mime_type, "error": "No uri"}, + item_id=context["item_id"], + output_index=context["output_index"], + sequence_number=self._next_sequence(context), + ) + + # Handle images + if mime_type.startswith("image/"): + return ResponseOutputItemAddedEvent( + type="response.output_item.added", + item=ResponseOutputImage( # type: ignore[arg-type] + id=item_id, + type="output_image", + image_url=uri, + mime_type=mime_type, + alt_text=None, + ), + output_index=context["output_index"], + sequence_number=self._next_sequence(context), + ) + + # Handle common file types + if mime_type in [ + "application/pdf", + "audio/mp3", + "audio/wav", + "audio/m4a", + "audio/ogg", + "audio/flac", + "audio/aac", + "audio/mpeg", + "video/mp4", + "video/webm", + ]: + # Extract filename from URI or use generic name + filename = uri.split("/")[-1] if "/" in uri else f"output.{mime_type.split('/')[-1]}" + + return ResponseOutputItemAddedEvent( + type="response.output_item.added", + item=ResponseOutputFile( # type: ignore[arg-type] + id=item_id, + type="output_file", + filename=filename, + file_url=uri, + file_data=None, + mime_type=mime_type, + ), + output_index=context["output_index"], + sequence_number=self._next_sequence(context), + ) + + # For other URI types (text/plain, application/json, etc.), use trace for now + logger.debug(f"UriContent with unsupported MIME type {mime_type}, using trace event") return ResponseTraceEventComplete( - type="response.trace.complete", + type="response.trace.completed", data={ "content_type": "uri", - "uri": getattr(content, "uri", ""), - "mime_type": getattr(content, "mime_type", "text/plain"), + "uri": uri, + "mime_type": mime_type, "timestamp": datetime.now().isoformat(), }, item_id=context["item_id"], @@ -1088,9 +1784,15 @@ async def _map_uri_content(self, content: Any, context: dict[str, Any]) -> Respo ) async def _map_hosted_file_content(self, content: Any, context: dict[str, Any]) -> ResponseTraceEventComplete: - """Map HostedFileContent to structured trace event.""" + """Map HostedFileContent to trace event. + + HostedFileContent references external file IDs (like OpenAI file IDs). + These remain as traces since they're metadata about hosted resources, + not direct content to display. To display them, agents should return + DataContent or UriContent with the actual file data/URL. + """ return ResponseTraceEventComplete( - type="response.trace.complete", + type="response.trace.completed", data={ "content_type": "hosted_file", "file_id": getattr(content, "file_id", "unknown"), @@ -1104,9 +1806,14 @@ async def _map_hosted_file_content(self, content: Any, context: dict[str, Any]) async def _map_hosted_vector_store_content( self, content: Any, context: dict[str, Any] ) -> ResponseTraceEventComplete: - """Map HostedVectorStoreContent to structured trace event.""" + """Map HostedVectorStoreContent to trace event. + + HostedVectorStoreContent references external vector store IDs. + These remain as traces since they're metadata about hosted resources, + not direct content to display. + """ return ResponseTraceEventComplete( - type="response.trace.complete", + type="response.trace.completed", data={ "content_type": "hosted_vector_store", "vector_store_id": getattr(content, "vector_store_id", "unknown"), diff --git a/python/packages/devui/agent_framework_devui/_openai/__init__.py b/python/packages/devui/agent_framework_devui/_openai/__init__.py new file mode 100644 index 0000000000..9eadb747c4 --- /dev/null +++ b/python/packages/devui/agent_framework_devui/_openai/__init__.py @@ -0,0 +1,9 @@ +# Copyright (c) Microsoft. All rights reserved. + +"""OpenAI integration for DevUI - proxy support for OpenAI Responses API.""" + +from ._executor import OpenAIExecutor + +__all__ = [ + "OpenAIExecutor", +] diff --git a/python/packages/devui/agent_framework_devui/_openai/_executor.py b/python/packages/devui/agent_framework_devui/_openai/_executor.py new file mode 100644 index 0000000000..1de05bfc2d --- /dev/null +++ b/python/packages/devui/agent_framework_devui/_openai/_executor.py @@ -0,0 +1,270 @@ +# Copyright (c) Microsoft. All rights reserved. + +"""OpenAI Executor - proxies requests to OpenAI Responses API. + +This executor mirrors the AgentFrameworkExecutor interface but routes +requests to OpenAI's API instead of executing local entities. +""" + +import logging +import os +from collections.abc import AsyncGenerator +from typing import Any + +from openai import APIStatusError, AsyncOpenAI, AsyncStream, AuthenticationError, PermissionDeniedError, RateLimitError +from openai.types.responses import Response, ResponseStreamEvent + +from .._conversations import ConversationStore +from ..models import AgentFrameworkRequest, OpenAIResponse + +logger = logging.getLogger(__name__) + + +class OpenAIExecutor: + """Executor for OpenAI Responses API - mirrors AgentFrameworkExecutor interface. + + This executor provides the same interface as AgentFrameworkExecutor but proxies + requests to OpenAI's Responses API instead of executing local entities. + + Key features: + - Same execute_streaming() and execute_sync() interface + - Shares ConversationStore with local executor + - Configured via OPENAI_API_KEY environment variable + - Supports all OpenAI Responses API parameters + """ + + def __init__(self, conversation_store: ConversationStore): + """Initialize OpenAI executor. + + Args: + conversation_store: Shared conversation store (works for both local and OpenAI) + """ + self.conversation_store = conversation_store + + # Load configuration from environment + self.api_key = os.getenv("OPENAI_API_KEY") + self.base_url = os.getenv("OPENAI_BASE_URL", "https://api.openai.com/v1") + self._client: AsyncOpenAI | None = None + + @property + def is_configured(self) -> bool: + """Check if OpenAI executor is properly configured. + + Returns: + True if OPENAI_API_KEY is set + """ + return self.api_key is not None + + def _get_client(self) -> AsyncOpenAI: + """Get or create OpenAI async client. + + Returns: + AsyncOpenAI client instance + + Raises: + ValueError: If OPENAI_API_KEY not configured + """ + if self._client is None: + if not self.api_key: + raise ValueError("OPENAI_API_KEY environment variable not set") + + self._client = AsyncOpenAI( + api_key=self.api_key, + base_url=self.base_url, + ) + logger.debug(f"Created OpenAI client with base_url: {self.base_url}") + + return self._client + + async def execute_streaming(self, request: AgentFrameworkRequest) -> AsyncGenerator[Any, None]: + """Execute request via OpenAI and stream results in OpenAI format. + + This mirrors AgentFrameworkExecutor.execute_streaming() interface. + + Args: + request: Request to execute + + Yields: + OpenAI ResponseStreamEvent objects (already in correct format!) + """ + if not self.is_configured: + logger.error("OpenAI executor not configured (missing OPENAI_API_KEY)") + # Emit proper response.failed event + yield { + "type": "response.failed", + "response": { + "id": f"resp_{os.urandom(16).hex()}", + "status": "failed", + "error": { + "message": "OpenAI not configured on server. Set OPENAI_API_KEY environment variable.", + "type": "configuration_error", + "code": "openai_not_configured", + }, + }, + } + return + + try: + client = self._get_client() + + # Convert AgentFrameworkRequest to OpenAI params + params = request.to_openai_params() + + # Remove DevUI-specific fields that OpenAI doesn't recognize + params.pop("extra_body", None) + + # Conversation ID is now from OpenAI (created via /v1/conversations proxy) + # so we can pass it through! + + # Force streaming mode (remove if already present to avoid duplicate) + params.pop("stream", None) + + logger.info(f"🔀 Proxying to OpenAI Responses API: model={params.get('model')}") + logger.debug(f"Request params: {params}") + + # Call OpenAI Responses API - returns AsyncStream[ResponseStreamEvent] + stream: AsyncStream[ResponseStreamEvent] = await client.responses.create( + **params, + stream=True, # Force streaming + ) + + # Yield events directly - they're already ResponseStreamEvent objects! + # No conversion needed - OpenAI SDK returns proper typed objects + async for event in stream: + yield event + + except AuthenticationError as e: + # 401 - Invalid API key or authentication issue + logger.error(f"OpenAI authentication error: {e}", exc_info=True) + error_body = e.body if hasattr(e, "body") else {} + error_data = error_body.get("error", {}) if isinstance(error_body, dict) else {} + yield { + "type": "response.failed", + "response": { + "id": f"resp_{os.urandom(16).hex()}", + "status": "failed", + "error": { + "message": error_data.get("message", str(e)), + "type": error_data.get("type", "authentication_error"), + "code": error_data.get("code", "invalid_api_key"), + }, + }, + } + except PermissionDeniedError as e: + # 403 - Permission denied + logger.error(f"OpenAI permission denied: {e}", exc_info=True) + error_body = e.body if hasattr(e, "body") else {} + error_data = error_body.get("error", {}) if isinstance(error_body, dict) else {} + yield { + "type": "response.failed", + "response": { + "id": f"resp_{os.urandom(16).hex()}", + "status": "failed", + "error": { + "message": error_data.get("message", str(e)), + "type": error_data.get("type", "permission_denied"), + "code": error_data.get("code", "insufficient_permissions"), + }, + }, + } + except RateLimitError as e: + # 429 - Rate limit exceeded + logger.error(f"OpenAI rate limit exceeded: {e}", exc_info=True) + error_body = e.body if hasattr(e, "body") else {} + error_data = error_body.get("error", {}) if isinstance(error_body, dict) else {} + yield { + "type": "response.failed", + "response": { + "id": f"resp_{os.urandom(16).hex()}", + "status": "failed", + "error": { + "message": error_data.get("message", str(e)), + "type": error_data.get("type", "rate_limit_error"), + "code": error_data.get("code", "rate_limit_exceeded"), + }, + }, + } + except APIStatusError as e: + # Other OpenAI API errors + logger.error(f"OpenAI API error: {e}", exc_info=True) + error_body = e.body if hasattr(e, "body") else {} + error_data = error_body.get("error", {}) if isinstance(error_body, dict) else {} + yield { + "type": "response.failed", + "response": { + "id": f"resp_{os.urandom(16).hex()}", + "status": "failed", + "error": { + "message": error_data.get("message", str(e)), + "type": error_data.get("type", "api_error"), + "code": error_data.get("code", "unknown_error"), + }, + }, + } + except Exception as e: + # Catch-all for unexpected errors + logger.error(f"Unexpected error in OpenAI proxy: {e}", exc_info=True) + yield { + "type": "response.failed", + "response": { + "id": f"resp_{os.urandom(16).hex()}", + "status": "failed", + "error": { + "message": f"Unexpected error: {e!s}", + "type": "internal_error", + "code": "unexpected_error", + }, + }, + } + + async def execute_sync(self, request: AgentFrameworkRequest) -> OpenAIResponse: + """Execute request via OpenAI and return complete response. + + This mirrors AgentFrameworkExecutor.execute_sync() interface. + + Args: + request: Request to execute + + Returns: + Final OpenAI Response object + + Raises: + ValueError: If OpenAI not configured + Exception: If OpenAI API call fails + """ + if not self.is_configured: + raise ValueError("OpenAI not configured on server. Set OPENAI_API_KEY environment variable.") + + try: + client = self._get_client() + + # Convert AgentFrameworkRequest to OpenAI params + params = request.to_openai_params() + + # Remove DevUI-specific fields + params.pop("extra_body", None) + + # Force non-streaming mode (remove if already present to avoid duplicate) + params.pop("stream", None) + + logger.info(f"🔀 Proxying to OpenAI Responses API (non-streaming): model={params.get('model')}") + logger.debug(f"Request params: {params}") + + # Call OpenAI Responses API - returns Response object + response: Response = await client.responses.create( + **params, + stream=False, # Force non-streaming + ) + + return response + + except Exception as e: + logger.error(f"OpenAI proxy error: {e}", exc_info=True) + raise + + async def close(self) -> None: + """Close the OpenAI client and release resources.""" + if self._client: + await self._client.close() + self._client = None + logger.debug("Closed OpenAI client") diff --git a/python/packages/devui/agent_framework_devui/_server.py b/python/packages/devui/agent_framework_devui/_server.py index 51ce0f9128..66bf3431d0 100644 --- a/python/packages/devui/agent_framework_devui/_server.py +++ b/python/packages/devui/agent_framework_devui/_server.py @@ -5,7 +5,9 @@ import inspect import json import logging -from collections.abc import AsyncGenerator +import os +import secrets +from collections.abc import AsyncGenerator, Awaitable, Callable from contextlib import asynccontextmanager from typing import Any @@ -14,15 +16,20 @@ from fastapi.responses import JSONResponse, StreamingResponse from fastapi.staticfiles import StaticFiles +from ._deployment import DeploymentManager from ._discovery import EntityDiscovery from ._executor import AgentFrameworkExecutor from ._mapper import MessageMapper -from .models import AgentFrameworkRequest, OpenAIError -from .models._discovery_models import DiscoveryResponse, EntityInfo +from ._openai import OpenAIExecutor +from .models import AgentFrameworkRequest, MetaResponse, OpenAIError +from .models._discovery_models import Deployment, DeploymentConfig, DiscoveryResponse, EntityInfo logger = logging.getLogger(__name__) +# No AuthMiddleware class needed - we'll use the decorator pattern instead + + class DevServer: """Development Server - OpenAI compatible API server for debugging agents.""" @@ -33,6 +40,7 @@ def __init__( host: str = "127.0.0.1", cors_origins: list[str] | None = None, ui_enabled: bool = True, + mode: str = "developer", ) -> None: """Initialize the development server. @@ -42,16 +50,79 @@ def __init__( host: Host to bind server to cors_origins: List of allowed CORS origins ui_enabled: Whether to enable the UI + mode: Server mode - 'developer' (full access, verbose errors) or 'user' (restricted APIs, generic errors) """ self.entities_dir = entities_dir self.port = port self.host = host - self.cors_origins = cors_origins or ["*"] + + # Smart CORS defaults: permissive for localhost, restrictive for network-exposed deployments + if cors_origins is None: + # Localhost development: allow cross-origin for dev tools (e.g., frontend dev server) + # Network-exposed: empty list (same-origin only, no CORS) + cors_origins = ["*"] if host in ("127.0.0.1", "localhost") else [] + + self.cors_origins = cors_origins self.ui_enabled = ui_enabled + self.mode = mode self.executor: AgentFrameworkExecutor | None = None + self.openai_executor: OpenAIExecutor | None = None + self.deployment_manager = DeploymentManager() self._app: FastAPI | None = None self._pending_entities: list[Any] | None = None + def _is_dev_mode(self) -> bool: + """Check if running in developer mode. + + Returns: + True if in developer mode, False if in user mode + """ + return self.mode == "developer" + + def _format_error(self, error: Exception, context: str = "Operation") -> str: + """Format error message based on server mode. + + In developer mode: Returns detailed error message for debugging. + In user mode: Returns generic message and logs details internally. + + Args: + error: The exception that occurred + context: Description of the operation that failed (e.g., "Request execution") + + Returns: + Formatted error message appropriate for the current mode + """ + if self._is_dev_mode(): + # Developer mode: Show full error details for debugging + return f"{context} failed: {error!s}" + + # User mode: Generic message to user, detailed logging internally + logger.error(f"{context} failed: {error}", exc_info=True) + return f"{context} failed" + + def _require_developer_mode(self, feature: str = "operation") -> None: + """Check if current mode allows developer operations. + + Args: + feature: Name of the feature being accessed (for error message) + + Raises: + HTTPException: If in user mode + """ + if self.mode == "user": + logger.warning(f"Blocked {feature} access in user mode") + raise HTTPException( + status_code=403, + detail={ + "error": { + "message": f"Access denied: {feature} requires developer mode", + "type": "permission_denied", + "code": "developer_mode_required", + "current_mode": self.mode, + } + }, + ) + async def _ensure_executor(self) -> AgentFrameworkExecutor: """Ensure executor is initialized.""" if self.executor is None: @@ -84,6 +155,29 @@ async def _ensure_executor(self) -> AgentFrameworkExecutor: return self.executor + async def _ensure_openai_executor(self) -> OpenAIExecutor: + """Ensure OpenAI executor is initialized. + + Returns: + OpenAI executor instance + + Raises: + ValueError: If OpenAI executor cannot be initialized + """ + if self.openai_executor is None: + # Initialize local executor first to get conversation_store + local_executor = await self._ensure_executor() + + # Create OpenAI executor with shared conversation store + self.openai_executor = OpenAIExecutor(local_executor.conversation_store) + + if self.openai_executor.is_configured: + logger.info("OpenAI proxy mode available (OPENAI_API_KEY configured)") + else: + logger.info("OpenAI proxy mode disabled (OPENAI_API_KEY not set)") + + return self.openai_executor + async def _cleanup_entities(self) -> None: """Cleanup entity resources (close clients, MCP tools, credentials, etc.).""" if not self.executor: @@ -94,12 +188,28 @@ async def _cleanup_entities(self) -> None: closed_count = 0 mcp_tools_closed = 0 credentials_closed = 0 + hook_count = 0 for entity_info in entities: + entity_id = entity_info.id + try: - entity_obj = self.executor.entity_discovery.get_entity_object(entity_info.id) + # Step 1: Execute registered cleanup hooks (NEW) + cleanup_hooks = self.executor.entity_discovery.get_cleanup_hooks(entity_id) + for hook in cleanup_hooks: + try: + if inspect.iscoroutinefunction(hook): + await hook() + else: + hook() + hook_count += 1 + logger.debug(f"✓ Executed cleanup hook for: {entity_id}") + except Exception as e: + logger.warning(f"⚠ Cleanup hook failed for {entity_id}: {e}") + + # Step 2: Close chat clients and their credentials (EXISTING) + entity_obj = self.executor.entity_discovery.get_entity_object(entity_id) - # Close chat clients and their credentials if entity_obj and hasattr(entity_obj, "chat_client"): client = entity_obj.chat_client @@ -144,14 +254,24 @@ async def _cleanup_entities(self) -> None: logger.warning(f"Error closing MCP tool for {entity_info.id}: {e}") except Exception as e: - logger.warning(f"Error closing entity {entity_info.id}: {e}") + logger.warning(f"Error cleaning up entity {entity_id}: {e}") + if hook_count > 0: + logger.info(f"✓ Executed {hook_count} cleanup hook(s)") if closed_count > 0: - logger.info(f"Closed {closed_count} entity client(s)") + logger.info(f"✓ Closed {closed_count} entity client(s)") if credentials_closed > 0: - logger.info(f"Closed {credentials_closed} credential(s)") + logger.info(f"✓ Closed {credentials_closed} credential(s)") if mcp_tools_closed > 0: - logger.info(f"Closed {mcp_tools_closed} MCP tool(s)") + logger.info(f"✓ Closed {mcp_tools_closed} MCP tool(s)") + + # Close OpenAI executor if it exists + if self.openai_executor: + try: + await self.openai_executor.close() + logger.info("Closed OpenAI executor") + except Exception as e: + logger.warning(f"Error closing OpenAI executor: {e}") def create_app(self) -> FastAPI: """Create the FastAPI application.""" @@ -161,6 +281,7 @@ async def lifespan(app: FastAPI) -> AsyncGenerator[None, None]: # Startup logger.info("Starting Agent Framework Server") await self._ensure_executor() + await self._ensure_openai_executor() # Initialize OpenAI executor yield # Shutdown logger.info("Shutting down Agent Framework Server") @@ -177,14 +298,74 @@ async def lifespan(app: FastAPI) -> AsyncGenerator[None, None]: ) # Add CORS middleware + # Note: allow_credentials cannot be True when allow_origins is ["*"] + # For localhost dev with wildcard origins, credentials are disabled + # For network deployments with specific origins or empty list, credentials can be enabled + allow_credentials = self.cors_origins != ["*"] + app.add_middleware( CORSMiddleware, allow_origins=self.cors_origins, - allow_credentials=True, + allow_credentials=allow_credentials, allow_methods=["*"], allow_headers=["*"], ) + # Add authentication middleware using decorator pattern + # Auth is enabled by presence of DEVUI_AUTH_TOKEN + auth_token = os.getenv("DEVUI_AUTH_TOKEN", "") + auth_required = bool(auth_token) + + if auth_required: + logger.info("Authentication middleware enabled") + + @app.middleware("http") + async def auth_middleware(request: Request, call_next: Callable[[Request], Awaitable[Any]]) -> Any: + """Validate Bearer token authentication. + + Skips authentication for health, meta, static UI endpoints, and OPTIONS requests. + """ + # Skip auth for OPTIONS (CORS preflight) requests + if request.method == "OPTIONS": + return await call_next(request) + + # Skip auth for health checks, meta endpoint, and static files + if request.url.path in ["/health", "/meta", "/"] or request.url.path.startswith("/assets"): + return await call_next(request) + + # Check Authorization header + auth_header = request.headers.get("Authorization") + if not auth_header or not auth_header.startswith("Bearer "): + return JSONResponse( + status_code=401, + content={ + "error": { + "message": ( + "Missing or invalid Authorization header. Expected: Authorization: Bearer " + ), + "type": "authentication_error", + "code": "missing_token", + } + }, + ) + + # Extract and validate token + token = auth_header.replace("Bearer ", "", 1).strip() + if not secrets.compare_digest(token, auth_token): + return JSONResponse( + status_code=401, + content={ + "error": { + "message": "Invalid authentication token", + "type": "authentication_error", + "code": "invalid_token", + } + }, + ) + + # Token valid, proceed + return await call_next(request) + self._register_routes(app) self._mount_ui(app) @@ -202,6 +383,28 @@ async def health_check() -> dict[str, Any]: return {"status": "healthy", "entities_count": len(entities), "framework": "agent_framework"} + @app.get("/meta", response_model=MetaResponse) + async def get_meta() -> MetaResponse: + """Get server metadata and configuration.""" + import os + + from . import __version__ + + # Ensure executors are initialized to check capabilities + openai_executor = await self._ensure_openai_executor() + + return MetaResponse( + ui_mode=self.mode, # type: ignore[arg-type] + version=__version__, + framework="agent_framework", + capabilities={ + "tracing": os.getenv("ENABLE_OTEL") == "true", + "openai_proxy": openai_executor.is_configured, + "deployment": True, # Deployment feature is available + }, + auth_required=bool(os.getenv("DEVUI_AUTH_TOKEN")), + ) + @app.get("/v1/entities", response_model=DiscoveryResponse) async def discover_entities() -> DiscoveryResponse: """List all registered entities.""" @@ -226,7 +429,10 @@ async def get_entity_info(entity_id: str) -> EntityInfo: # Trigger lazy loading if entity not yet loaded # This will import the module and enrich metadata - entity_obj = await executor.entity_discovery.load_entity(entity_id) + # Pass checkpoint_manager to ensure workflows get checkpoint storage injected + entity_obj = await executor.entity_discovery.load_entity( + entity_id, checkpoint_manager=executor.checkpoint_manager + ) # Get updated entity info (may have been enriched during load) entity_info = executor.get_entity_info(entity_id) or entity_info @@ -305,6 +511,7 @@ async def get_entity_info(entity_id: str) -> EntityInfo: executor_list = [getattr(ex, "executor_id", str(ex)) for ex in entity_obj.executors] # Create copy of entity info and populate workflow-specific fields + # Note: DevUI provides runtime checkpoint storage for ALL workflows via conversations update_payload: dict[str, Any] = { "workflow_dump": workflow_dump, "input_schema": input_schema, @@ -320,9 +527,13 @@ async def get_entity_info(entity_id: str) -> EntityInfo: except HTTPException: raise + except ValueError as e: + # ValueError from load_entity indicates entity not found or invalid + error_msg = self._format_error(e, "Entity loading") + raise HTTPException(status_code=404, detail=error_msg) from e except Exception as e: - logger.error(f"Error getting entity info for {entity_id}: {e}") - raise HTTPException(status_code=500, detail=f"Failed to get entity info: {e!s}") from e + error_msg = self._format_error(e, "Entity info retrieval") + raise HTTPException(status_code=500, detail=error_msg) from e @app.post("/v1/entities/{entity_id}/reload") async def reload_entity(entity_id: str) -> dict[str, Any]: @@ -331,6 +542,7 @@ async def reload_entity(entity_id: str) -> dict[str, Any]: This enables hot reload during development - edit entity code, call this endpoint, and the next execution will use the updated code without server restart. """ + self._require_developer_mode("entity hot reload") try: executor = await self._ensure_executor() @@ -353,10 +565,140 @@ async def reload_entity(entity_id: str) -> dict[str, Any]: logger.error(f"Error reloading entity {entity_id}: {e}") raise HTTPException(status_code=500, detail=f"Failed to reload entity: {e!s}") from e + # ============================================================================ + # Deployment Endpoints + # ============================================================================ + + @app.post("/v1/deployments") + async def create_deployment(config: DeploymentConfig) -> StreamingResponse: + """Deploy entity to Azure Container Apps with streaming events. + + Returns SSE stream of deployment progress events. + """ + self._require_developer_mode("deployment") + try: + executor = await self._ensure_executor() + + # Validate entity exists and supports deployment + entity_info = executor.get_entity_info(config.entity_id) + if not entity_info: + raise HTTPException(status_code=404, detail=f"Entity {config.entity_id} not found") + + if not entity_info.deployment_supported: + reason = entity_info.deployment_reason or "Deployment not supported for this entity" + raise HTTPException(status_code=400, detail=reason) + + # Get entity path from metadata + from pathlib import Path + + entity_path_str = entity_info.metadata.get("path") + if not entity_path_str: + raise HTTPException( + status_code=400, + detail="Entity path not found in metadata (in-memory entities cannot be deployed)", + ) + + entity_path = Path(entity_path_str) + + # Stream deployment events + async def event_generator() -> AsyncGenerator[str, None]: + async for event in self.deployment_manager.deploy(config, entity_path): + # Format as SSE + import json + + yield f"data: {json.dumps(event.model_dump())}\n\n" + + return StreamingResponse(event_generator(), media_type="text/event-stream") + + except HTTPException: + raise + except Exception as e: + error_msg = self._format_error(e, "Deployment creation") + raise HTTPException(status_code=500, detail=error_msg) from e + + @app.get("/v1/deployments") + async def list_deployments(entity_id: str | None = None) -> list[Deployment]: + """List all deployments, optionally filtered by entity.""" + self._require_developer_mode("deployment listing") + try: + return await self.deployment_manager.list_deployments(entity_id) + except Exception as e: + error_msg = self._format_error(e, "Deployment listing") + raise HTTPException(status_code=500, detail=error_msg) from e + + @app.get("/v1/deployments/{deployment_id}") + async def get_deployment(deployment_id: str) -> Deployment: + """Get deployment by ID.""" + self._require_developer_mode("deployment details") + try: + deployment = await self.deployment_manager.get_deployment(deployment_id) + if not deployment: + raise HTTPException(status_code=404, detail=f"Deployment {deployment_id} not found") + return deployment + except HTTPException: + raise + except Exception as e: + logger.error(f"Error getting deployment: {e}") + raise HTTPException(status_code=500, detail=f"Failed to get deployment: {e!s}") from e + + @app.delete("/v1/deployments/{deployment_id}") + async def delete_deployment(deployment_id: str) -> dict[str, Any]: + """Delete deployment from Azure Container Apps.""" + self._require_developer_mode("deployment deletion") + try: + await self.deployment_manager.delete_deployment(deployment_id) + return {"success": True, "message": f"Deployment {deployment_id} deleted successfully"} + except ValueError as e: + raise HTTPException(status_code=404, detail=str(e)) from e + except Exception as e: + logger.error(f"Error deleting deployment: {e}") + raise HTTPException(status_code=500, detail=f"Failed to delete deployment: {e!s}") from e + + # Convenience endpoint: deploy specific entity + @app.post("/v1/entities/{entity_id}/deploy") + async def deploy_entity(entity_id: str, config: DeploymentConfig) -> StreamingResponse: + """Convenience endpoint to deploy entity (shortcuts to /v1/deployments).""" + self._require_developer_mode("deployment") + # Override entity_id from path parameter + config.entity_id = entity_id + return await create_deployment(config) + + # ============================================================================ + # Response/Conversation Endpoints + # ============================================================================ + @app.post("/v1/responses") async def create_response(request: AgentFrameworkRequest, raw_request: Request) -> Any: - """OpenAI Responses API endpoint.""" + """OpenAI Responses API endpoint - routes to local or OpenAI executor.""" try: + # Check if frontend requested OpenAI proxy mode + proxy_mode = raw_request.headers.get("X-Proxy-Backend") + + if proxy_mode == "openai": + # Route to OpenAI executor + logger.info("🔀 Routing to OpenAI proxy mode") + openai_executor = await self._ensure_openai_executor() + + if not openai_executor.is_configured: + error = OpenAIError.create( + "OpenAI proxy mode not configured. Set OPENAI_API_KEY environment variable." + ) + return JSONResponse(status_code=503, content=error.to_dict()) + + # Execute via OpenAI with dedicated streaming method + if request.stream: + return StreamingResponse( + self._stream_openai_execution(openai_executor, request), + media_type="text/event-stream", + headers={ + "Cache-Control": "no-cache", + "Connection": "keep-alive", + "Access-Control-Allow-Origin": "*", + }, + ) + return await openai_executor.execute_sync(request) + + # Route to local Agent Framework executor (original behavior) raw_body = await raw_request.body() logger.info(f"Raw request body: {raw_body.decode()}") logger.info(f"Parsed request: metadata={request.metadata}") @@ -392,18 +734,86 @@ async def create_response(request: AgentFrameworkRequest, raw_request: Request) return await executor.execute_sync(request) except Exception as e: - logger.error(f"Error executing request: {e}") - error = OpenAIError.create(f"Execution failed: {e!s}") + error_msg = self._format_error(e, "Request execution") + error = OpenAIError.create(error_msg) return JSONResponse(status_code=500, content=error.to_dict()) # ======================================== # OpenAI Conversations API (Standard) # ======================================== - @app.post("/v1/conversations") - async def create_conversation(request_data: dict[str, Any]) -> dict[str, Any]: - """Create a new conversation - OpenAI standard.""" + @app.post("/v1/conversations", response_model=None) + async def create_conversation(raw_request: Request) -> dict[str, Any] | JSONResponse: + """Create a new conversation - routes to OpenAI or local based on mode.""" try: + # Parse request body + request_data = await raw_request.json() + + # Check if frontend requested OpenAI proxy mode + proxy_mode = raw_request.headers.get("X-Proxy-Backend") + + if proxy_mode == "openai": + # Create conversation in OpenAI + openai_executor = await self._ensure_openai_executor() + if not openai_executor.is_configured: + error = OpenAIError.create( + "OpenAI proxy mode not configured. Set OPENAI_API_KEY environment variable.", + type="configuration_error", + code="openai_not_configured", + ) + return JSONResponse(status_code=503, content=error.to_dict()) + + # Use OpenAI client to create conversation + from openai import APIStatusError, AsyncOpenAI, AuthenticationError, PermissionDeniedError + + client = AsyncOpenAI( + api_key=openai_executor.api_key, + base_url=openai_executor.base_url, + ) + + try: + metadata = request_data.get("metadata") + logger.debug(f"Creating OpenAI conversation with metadata: {metadata}") + conversation = await client.conversations.create(metadata=metadata) + logger.info(f"Created OpenAI conversation: {conversation.id}") + return conversation.model_dump() + except AuthenticationError as e: + # 401 - Invalid API key or authentication issue + logger.error(f"OpenAI authentication error creating conversation: {e}") + error_body = e.body if hasattr(e, "body") else {} + error_data = error_body.get("error", {}) if isinstance(error_body, dict) else {} + error = OpenAIError.create( + message=error_data.get("message", str(e)), + type=error_data.get("type", "authentication_error"), + code=error_data.get("code", "invalid_api_key"), + ) + return JSONResponse(status_code=401, content=error.to_dict()) + except PermissionDeniedError as e: + # 403 - Permission denied + logger.error(f"OpenAI permission denied creating conversation: {e}") + error_body = e.body if hasattr(e, "body") else {} + error_data = error_body.get("error", {}) if isinstance(error_body, dict) else {} + error = OpenAIError.create( + message=error_data.get("message", str(e)), + type=error_data.get("type", "permission_denied"), + code=error_data.get("code", "insufficient_permissions"), + ) + return JSONResponse(status_code=403, content=error.to_dict()) + except APIStatusError as e: + # Other OpenAI API errors (rate limit, etc.) + logger.error(f"OpenAI API error creating conversation: {e}") + error_body = e.body if hasattr(e, "body") else {} + error_data = error_body.get("error", {}) if isinstance(error_body, dict) else {} + error = OpenAIError.create( + message=error_data.get("message", str(e)), + type=error_data.get("type", "api_error"), + code=error_data.get("code", "unknown_error"), + ) + return JSONResponse( + status_code=e.status_code if hasattr(e, "status_code") else 500, content=error.to_dict() + ) + + # Local mode - use DevUI conversation store metadata = request_data.get("metadata") executor = await self._ensure_executor() conversation = executor.conversation_store.create_conversation(metadata=metadata) @@ -411,22 +821,39 @@ async def create_conversation(request_data: dict[str, Any]) -> dict[str, Any]: except HTTPException: raise except Exception as e: - logger.error(f"Error creating conversation: {e}") - raise HTTPException(status_code=500, detail=f"Failed to create conversation: {e!s}") from e + logger.error(f"Error creating conversation: {e}", exc_info=True) + error = OpenAIError.create(f"Failed to create conversation: {e!s}") + return JSONResponse(status_code=500, content=error.to_dict()) @app.get("/v1/conversations") - async def list_conversations(agent_id: str | None = None) -> dict[str, Any]: - """List conversations, optionally filtered by agent_id.""" + async def list_conversations( + agent_id: str | None = None, + entity_id: str | None = None, + type: str | None = None, + ) -> dict[str, Any]: + """List conversations, optionally filtered by agent_id, entity_id, and/or type. + + Query Parameters: + - agent_id: Filter by agent_id (for agent conversations) + - entity_id: Filter by entity_id (for workflow sessions or other entities) + - type: Filter by conversation type (e.g., "workflow_session") + + Multiple filters can be combined (AND logic). + """ try: executor = await self._ensure_executor() + # Build filter criteria + filters = {} if agent_id: - # Filter by agent_id metadata - conversations = executor.conversation_store.list_conversations_by_metadata({"agent_id": agent_id}) - else: - # Return all conversations (for InMemoryStore, list all) - # Note: This assumes list_conversations_by_metadata({}) returns all - conversations = executor.conversation_store.list_conversations_by_metadata({}) + filters["agent_id"] = agent_id + if entity_id: + filters["entity_id"] = entity_id + if type: + filters["type"] = type + + # Apply filters + conversations = executor.conversation_store.list_conversations_by_metadata(filters) return { "object": "list", @@ -511,9 +938,20 @@ async def list_conversation_items( items, has_more = await executor.conversation_store.list_items( conversation_id, limit=limit, after=after, order=order ) + # Handle both Pydantic models and dicts (some stores return raw dicts) + serialized_items = [] + for item in items: + if hasattr(item, "model_dump"): + serialized_items.append(item.model_dump()) + elif isinstance(item, dict): + serialized_items.append(item) + else: + logger.warning(f"Unexpected item type: {type(item)}, converting to dict") + serialized_items.append(dict(item)) + return { "object": "list", - "data": [item.model_dump() for item in items], + "data": serialized_items, "has_more": has_more, } except ValueError as e: @@ -532,13 +970,51 @@ async def retrieve_conversation_item(conversation_id: str, item_id: str) -> dict item = executor.conversation_store.get_item(conversation_id, item_id) if not item: raise HTTPException(status_code=404, detail="Item not found") - return item.model_dump() + result: dict[str, Any] = item.model_dump() + return result except HTTPException: raise except Exception as e: logger.error(f"Error getting item {item_id} from conversation {conversation_id}: {e}") raise HTTPException(status_code=500, detail=f"Failed to get item: {e!s}") from e + @app.delete("/v1/conversations/{conversation_id}/items/{item_id}") + async def delete_conversation_item(conversation_id: str, item_id: str) -> dict[str, Any]: + """Delete conversation item - supports checkpoint deletion.""" + try: + executor = await self._ensure_executor() + + # Check if this is a checkpoint item + if item_id.startswith("checkpoint_"): + # Extract checkpoint_id from item_id (format: "checkpoint_{checkpoint_id}") + checkpoint_id = item_id[len("checkpoint_") :] + storage = executor.checkpoint_manager.get_checkpoint_storage(conversation_id) + deleted = await storage.delete_checkpoint(checkpoint_id) + + if not deleted: + raise HTTPException(status_code=404, detail="Checkpoint not found") + + return { + "id": item_id, + "object": "item.deleted", + "deleted": True, + } + # For other items, delegate to conversation store (if it supports deletion) + raise HTTPException(status_code=501, detail="Deletion of non-checkpoint items not implemented") + + except HTTPException: + raise + except ValueError as e: + raise HTTPException(status_code=404, detail=str(e)) from e + except Exception as e: + logger.error(f"Error deleting item {item_id} from conversation {conversation_id}: {e}") + raise HTTPException(status_code=500, detail=f"Failed to delete item: {e!s}") from e + + # ============================================================================ + # Checkpoint Management - Now handled through conversation items API + # Checkpoints are exposed as conversation items with type="checkpoint" + # ============================================================================ + async def _stream_execution( self, executor: AgentFrameworkExecutor, request: AgentFrameworkRequest ) -> AsyncGenerator[str, None]: @@ -587,6 +1063,63 @@ async def _stream_execution( error_event = {"id": "error", "object": "error", "error": {"message": str(e), "type": "execution_error"}} yield f"data: {json.dumps(error_event)}\n\n" + async def _stream_openai_execution( + self, executor: OpenAIExecutor, request: AgentFrameworkRequest + ) -> AsyncGenerator[str, None]: + """Stream execution through OpenAI executor. + + OpenAI events are already in final format - no conversion or aggregation needed. + Just serialize and stream them as SSE. + + Args: + executor: OpenAI executor instance + request: Request to execute + + Yields: + SSE-formatted event strings + """ + try: + # Stream events from OpenAI - they're already ResponseStreamEvent objects + async for event in executor.execute_streaming(request): + # Handle error dicts from executor + if isinstance(event, dict): + payload = json.dumps(event) + yield f"data: {payload}\n\n" + continue + + # OpenAI SDK events have model_dump_json() - use it for single-line JSON + if hasattr(event, "model_dump_json"): + payload = event.model_dump_json() # type: ignore[attr-defined] + yield f"data: {payload}\n\n" + else: + # Fallback (shouldn't happen with OpenAI SDK) + logger.warning(f"Unexpected event type from OpenAI: {type(event)}") + payload = json.dumps(str(event)) + yield f"data: {payload}\n\n" + + # OpenAI already sends response.completed event - no aggregation needed! + # Just send [DONE] marker + yield "data: [DONE]\n\n" + + except Exception as e: + logger.error(f"Error in OpenAI streaming execution: {e}", exc_info=True) + # Emit proper response.failed event + import os + + error_event = { + "type": "response.failed", + "response": { + "id": f"resp_{os.urandom(16).hex()}", + "status": "failed", + "error": { + "message": str(e), + "type": "internal_error", + "code": "streaming_error", + }, + }, + } + yield f"data: {json.dumps(error_event)}\n\n" + def _mount_ui(self, app: FastAPI) -> None: """Mount the UI as static files.""" from pathlib import Path diff --git a/python/packages/devui/agent_framework_devui/_utils.py b/python/packages/devui/agent_framework_devui/_utils.py index 19be9d5f35..3c17c072f7 100644 --- a/python/packages/devui/agent_framework_devui/_utils.py +++ b/python/packages/devui/agent_framework_devui/_utils.py @@ -324,6 +324,71 @@ def generate_schema_from_dataclass(cls: type[Any]) -> dict[str, Any]: return schema +def extract_response_type_from_executor(executor: Any, request_type: type) -> type | None: + """Extract the expected response type from an executor's response handler. + + Looks for methods decorated with @response_handler that have signature: + async def handler(self, original_request: RequestType, response: ResponseType, ctx) + + Args: + executor: Executor object that should have a handler for the request type + request_type: The request message type + + Returns: + The response type class, or None if not found + """ + try: + from typing import get_type_hints + + # Introspect handler methods for @response_handler pattern + for attr_name in dir(executor): + if attr_name.startswith("_"): + continue + attr = getattr(executor, attr_name, None) + if not callable(attr): + continue + + # Get type hints for this method + try: + type_hints = get_type_hints(attr) + + # Check for @response_handler pattern: + # async def handler(self, original_request: RequestType, response: ResponseType, ctx) + type_hint_params = {k: v for k, v in type_hints.items() if k not in ("self", "return")} + + # Look for at least 2 parameters: original_request, response (ctx is optional) + if len(type_hint_params) >= 2: + param_items = list(type_hint_params.items()) + # First param should be original_request matching request_type + _, first_param_type = param_items[0] + _, second_param_type = param_items[1] if len(param_items) > 1 else (None, None) + + # Check if first param matches request_type + first_matches_request = first_param_type == request_type or ( + hasattr(first_param_type, "__name__") + and hasattr(request_type, "__name__") + and first_param_type.__name__ == request_type.__name__ + ) + + # Verify we have a matching request type and valid response type (must be a type class) + if first_matches_request and second_param_type is not None and isinstance(second_param_type, type): + response_type_class: type = second_param_type + logger.debug( + f"Found response type {response_type_class} for request {request_type} " + f"via @response_handler" + ) + return response_type_class + + except Exception as e: + logger.debug(f"Failed to get type hints for {attr_name}: {e}") + continue + + except Exception as e: + logger.debug(f"Failed to extract response type from executor: {e}") + + return None + + def generate_input_schema(input_type: type) -> dict[str, Any]: """Generate JSON schema for workflow input type. diff --git a/python/packages/devui/agent_framework_devui/models/__init__.py b/python/packages/devui/agent_framework_devui/models/__init__.py index 254bb4e4af..5dc3ba59b9 100644 --- a/python/packages/devui/agent_framework_devui/models/__init__.py +++ b/python/packages/devui/agent_framework_devui/models/__init__.py @@ -27,14 +27,18 @@ from openai.types.responses.response_usage import InputTokensDetails, OutputTokensDetails from openai.types.shared import Metadata, ResponsesModel -from ._discovery_models import DiscoveryResponse, EntityInfo +from ._discovery_models import Deployment, DeploymentConfig, DeploymentEvent, DiscoveryResponse, EntityInfo from ._openai_custom import ( AgentFrameworkRequest, CustomResponseOutputItemAddedEvent, CustomResponseOutputItemDoneEvent, ExecutorActionItem, + MetaResponse, OpenAIError, ResponseFunctionResultComplete, + ResponseOutputData, + ResponseOutputFile, + ResponseOutputImage, ResponseTraceEvent, ResponseTraceEventComplete, ResponseWorkflowEventComplete, @@ -51,10 +55,14 @@ "ConversationItem", "CustomResponseOutputItemAddedEvent", "CustomResponseOutputItemDoneEvent", + "Deployment", + "DeploymentConfig", + "DeploymentEvent", "DiscoveryResponse", "EntityInfo", "ExecutorActionItem", "InputTokensDetails", + "MetaResponse", "Metadata", "OpenAIError", "OpenAIResponse", @@ -67,6 +75,9 @@ "ResponseFunctionToolCall", "ResponseFunctionToolCallOutputItem", "ResponseInputParam", + "ResponseOutputData", + "ResponseOutputFile", + "ResponseOutputImage", "ResponseOutputItemAddedEvent", "ResponseOutputItemDoneEvent", "ResponseOutputMessage", diff --git a/python/packages/devui/agent_framework_devui/models/_discovery_models.py b/python/packages/devui/agent_framework_devui/models/_discovery_models.py index 690efa7f9f..cdb5d0619c 100644 --- a/python/packages/devui/agent_framework_devui/models/_discovery_models.py +++ b/python/packages/devui/agent_framework_devui/models/_discovery_models.py @@ -4,9 +4,10 @@ from __future__ import annotations +import re from typing import Any -from pydantic import BaseModel, Field +from pydantic import BaseModel, Field, field_validator class EnvVarRequirement(BaseModel): @@ -36,6 +37,10 @@ class EntityInfo(BaseModel): # Environment variable requirements required_env_vars: list[EnvVarRequirement] | None = None + # Deployment support + deployment_supported: bool = False # Whether entity can be deployed + deployment_reason: str | None = None # Explanation of why/why not entity can be deployed + # Agent-specific fields (optional, populated when available) instructions: str | None = None model_id: str | None = None @@ -55,3 +60,144 @@ class DiscoveryResponse(BaseModel): """Response model for entity discovery.""" entities: list[EntityInfo] = Field(default_factory=list) + + +# ============================================================================ +# Deployment Models +# ============================================================================ + + +class DeploymentConfig(BaseModel): + """Configuration for deploying an entity.""" + + entity_id: str = Field(description="Entity ID to deploy") + resource_group: str = Field(description="Azure resource group name") + app_name: str = Field(description="Azure Container App name") + region: str = Field(default="eastus", description="Azure region") + ui_mode: str = Field(default="user", description="UI mode (user or developer)") + ui_enabled: bool = Field(default=True, description="Whether to enable web interface") + stream: bool = Field(default=True, description="Stream deployment events") + + @field_validator("app_name") + @classmethod + def validate_app_name(cls, v: str) -> str: + """Validate Azure Container App name format. + + Azure Container App names must: + - Be 3-32 characters long + - Contain only lowercase letters, numbers, and hyphens + - Start with a lowercase letter + - End with a lowercase letter or number + - Not contain consecutive hyphens + """ + if not v: + raise ValueError("app_name cannot be empty") + + if len(v) < 3 or len(v) > 32: + raise ValueError("app_name must be between 3 and 32 characters") + + if not re.match(r"^[a-z][a-z0-9-]*[a-z0-9]$", v): + raise ValueError( + "app_name must start with a lowercase letter, " + "end with a letter or number, and contain only lowercase letters, numbers, and hyphens" + ) + + if "--" in v: + raise ValueError("app_name cannot contain consecutive hyphens") + + return v + + @field_validator("resource_group") + @classmethod + def validate_resource_group(cls, v: str) -> str: + """Validate Azure resource group name format. + + Azure resource group names must: + - Be 1-90 characters long + - Contain only alphanumeric, underscore, parentheses, hyphen, period (except at end) + - Not end with a period + """ + if not v: + raise ValueError("resource_group cannot be empty") + + if len(v) > 90: + raise ValueError("resource_group must be 90 characters or less") + + if not re.match(r"^[a-zA-Z0-9._()-]+$", v): + raise ValueError( + "resource_group can only contain alphanumeric characters, " + "underscores, hyphens, periods, and parentheses" + ) + + if v.endswith("."): + raise ValueError("resource_group cannot end with a period") + + return v + + @field_validator("region") + @classmethod + def validate_region(cls, v: str) -> str: + """Validate Azure region format. + + Validates that the region string is a reasonable format. + Does not validate against the full list of Azure regions (which changes). + """ + if not v: + raise ValueError("region cannot be empty") + + if len(v) > 50: + raise ValueError("region name too long") + + # Azure regions are typically lowercase with no spaces (e.g., eastus, westeurope) + if not re.match(r"^[a-z0-9]+$", v): + raise ValueError("region must contain only lowercase letters and numbers (e.g., eastus, westeurope)") + + return v + + @field_validator("entity_id") + @classmethod + def validate_entity_id(cls, v: str) -> str: + """Validate entity_id format to prevent injection attacks.""" + if not v: + raise ValueError("entity_id cannot be empty") + + if len(v) > 256: + raise ValueError("entity_id too long") + + # Allow alphanumeric, hyphens, underscores, and periods + if not re.match(r"^[a-zA-Z0-9._-]+$", v): + raise ValueError("entity_id contains invalid characters") + + return v + + @field_validator("ui_mode") + @classmethod + def validate_ui_mode(cls, v: str) -> str: + """Validate ui_mode is one of the allowed values.""" + if v not in ("user", "developer"): + raise ValueError("ui_mode must be 'user' or 'developer'") + + return v + + +class DeploymentEvent(BaseModel): + """Real-time deployment event (SSE).""" + + type: str = Field(description="Event type (e.g., deploy.validating, deploy.building)") + message: str = Field(description="Human-readable message") + url: str | None = Field(default=None, description="Deployment URL (on completion)") + auth_token: str | None = Field(default=None, description="Auth token (on completion, shown once)") + + +class Deployment(BaseModel): + """Deployment record.""" + + id: str = Field(description="Deployment ID (UUID)") + entity_id: str = Field(description="Entity ID that was deployed") + resource_group: str = Field(description="Azure resource group") + app_name: str = Field(description="Azure Container App name") + region: str = Field(description="Azure region") + url: str = Field(description="Deployment URL") + status: str = Field(description="Deployment status (deploying, deployed, failed)") + created_at: str = Field(description="ISO 8601 timestamp") + error: str | None = Field(default=None, description="Error message if failed") diff --git a/python/packages/devui/agent_framework_devui/models/_openai_custom.py b/python/packages/devui/agent_framework_devui/models/_openai_custom.py index d1ff6de0f6..b291080a01 100644 --- a/python/packages/devui/agent_framework_devui/models/_openai_custom.py +++ b/python/packages/devui/agent_framework_devui/models/_openai_custom.py @@ -80,9 +80,16 @@ class CustomResponseOutputItemDoneEvent(BaseModel): class ResponseWorkflowEventComplete(BaseModel): - """Complete workflow event data.""" + """Complete workflow event data. - type: Literal["response.workflow_event.complete"] = "response.workflow_event.complete" + DevUI extension for workflow execution events (debugging/observability). + Uses past-tense 'completed' to follow OpenAI's event naming pattern. + + Workflow events are shown in the debug panel for monitoring execution flow, + not in main chat. Use response.output_item.added for user-facing content. + """ + + type: Literal["response.workflow_event.completed"] = "response.workflow_event.completed" data: dict[str, Any] # Complete event data, not delta executor_id: str | None = None item_id: str @@ -91,9 +98,17 @@ class ResponseWorkflowEventComplete(BaseModel): class ResponseTraceEventComplete(BaseModel): - """Complete trace event data.""" + """Complete trace event data. + + DevUI extension for non-displayable debugging/metadata events. + Uses past-tense 'completed' to follow OpenAI's event naming pattern + (e.g., response.completed, response.output_item.added). + + Trace events are shown in the Traces debug panel, not in main chat. + Use response.output_item.added for user-facing content. + """ - type: Literal["response.trace.complete"] = "response.trace.complete" + type: Literal["response.trace.completed"] = "response.trace.completed" data: dict[str, Any] # Complete trace data, not delta span_id: str | None = None item_id: str @@ -124,6 +139,139 @@ class ResponseFunctionResultComplete(BaseModel): timestamp: str | None = None # Optional timestamp for UI display +class ResponseRequestInfoEvent(BaseModel): + """DevUI extension: Workflow requests human input. + + This is a DevUI extension because: + - OpenAI Responses API doesn't have a concept of workflow human-in-the-loop pausing + - Agent Framework workflows can pause via RequestInfoExecutor to collect external information + - Clients need to render forms and submit responses to continue workflow execution + + When a workflow emits this event, it enters IDLE_WITH_PENDING_REQUESTS state. + Client should render a form based on request_schema and submit responses via + a new request with workflow_hil_response content type. + """ + + type: Literal["response.request_info.requested"] = "response.request_info.requested" + request_id: str + """Unique identifier for correlating this request with the response.""" + + source_executor_id: str + """ID of the executor that is waiting for this response.""" + + request_type: str + """Fully qualified type name of the request (e.g., 'module.path:ClassName').""" + + request_data: dict[str, Any] + """Current data from the RequestInfoMessage (may contain defaults/context).""" + + request_schema: dict[str, Any] + """JSON schema describing the request data structure (what the workflow is asking about).""" + + response_schema: dict[str, Any] | None = None + """JSON schema describing the expected response structure for form rendering (what user should provide).""" + + item_id: str + """OpenAI item ID for correlation.""" + + output_index: int = 0 + """Output index for OpenAI compatibility.""" + + sequence_number: int + """Sequence number for ordering events.""" + + timestamp: str + """ISO timestamp when the request was made.""" + + +# DevUI Output Content Types - for agent-generated media/data +# These extend ResponseOutputItem to support rich content outputs that OpenAI's API doesn't natively support + + +class ResponseOutputImage(BaseModel): + """DevUI extension: Agent-generated image output. + + This is a DevUI extension because: + - OpenAI Responses API only supports text output in ResponseOutputMessage.content + - ImageGenerationCall exists but is for tool calls (generating images), not returning existing images + - Agent Framework agents can return images via DataContent/UriContent that need proper display + + This type allows images to be displayed inline in chat rather than hidden in trace logs. + """ + + id: str + """The unique ID of the image output.""" + + image_url: str + """The URL or data URI of the image (e.g., data:image/png;base64,...)""" + + type: Literal["output_image"] = "output_image" + """The type of the output. Always `output_image`.""" + + alt_text: str | None = None + """Optional alt text for accessibility.""" + + mime_type: str = "image/png" + """The MIME type of the image (e.g., image/png, image/jpeg).""" + + +class ResponseOutputFile(BaseModel): + """DevUI extension: Agent-generated file output. + + This is a DevUI extension because: + - OpenAI Responses API only supports text output in ResponseOutputMessage.content + - Agent Framework agents can return files via DataContent/UriContent that need proper display + - Supports PDFs, audio files, and other media types + + This type allows files to be displayed inline in chat with appropriate renderers. + """ + + id: str + """The unique ID of the file output.""" + + filename: str + """The filename (used to determine rendering and download).""" + + type: Literal["output_file"] = "output_file" + """The type of the output. Always `output_file`.""" + + file_url: str | None = None + """Optional URL to the file.""" + + file_data: str | None = None + """Optional base64-encoded file data.""" + + mime_type: str = "application/octet-stream" + """The MIME type of the file (e.g., application/pdf, audio/mp3).""" + + +class ResponseOutputData(BaseModel): + """DevUI extension: Agent-generated generic data output. + + This is a DevUI extension because: + - OpenAI Responses API only supports text output in ResponseOutputMessage.content + - Agent Framework agents can return arbitrary structured data that needs display + - Useful for debugging and displaying non-text content + + This type allows generic data to be displayed inline in chat. + """ + + id: str + """The unique ID of the data output.""" + + data: str + """The data payload (string representation).""" + + type: Literal["output_data"] = "output_data" + """The type of the output. Always `output_data`.""" + + mime_type: str + """The MIME type of the data.""" + + description: str | None = None + """Optional description of the data.""" + + # Agent Framework extension fields class AgentFrameworkExtraBody(BaseModel): """Agent Framework specific routing fields for OpenAI requests.""" @@ -156,8 +304,12 @@ class AgentFrameworkRequest(BaseModel): metadata: dict[str, Any] | None = None temperature: float | None = None max_output_tokens: int | None = None + top_p: float | None = None tools: list[dict[str, Any]] | None = None + # Reasoning parameters (for o-series models) + reasoning: dict[str, Any] | None = None # {"effort": "low" | "medium" | "high" | "minimal"} + # Optional extra_body for advanced use cases extra_body: dict[str, Any] | None = None @@ -219,11 +371,37 @@ def to_json(self) -> str: return self.model_dump_json() +class MetaResponse(BaseModel): + """Server metadata response for /meta endpoint. + + Provides information about the DevUI server configuration and capabilities. + """ + + ui_mode: Literal["developer", "user"] = "developer" + """UI interface mode - 'developer' shows debug tools, 'user' shows simplified interface.""" + + version: str + """DevUI version string.""" + + framework: str = "agent_framework" + """Backend framework identifier.""" + + capabilities: dict[str, bool] = {} + """Server capabilities (e.g., tracing, openai_proxy).""" + + auth_required: bool = False + """Whether the server requires Bearer token authentication.""" + + # Export all custom types __all__ = [ "AgentFrameworkRequest", + "MetaResponse", "OpenAIError", "ResponseFunctionResultComplete", + "ResponseOutputData", + "ResponseOutputFile", + "ResponseOutputImage", "ResponseTraceEvent", "ResponseTraceEventComplete", "ResponseWorkflowEventComplete", diff --git a/python/packages/devui/agent_framework_devui/ui/assets/index.css b/python/packages/devui/agent_framework_devui/ui/assets/index.css index df5d66375c..42d917c3ab 100644 --- a/python/packages/devui/agent_framework_devui/ui/assets/index.css +++ b/python/packages/devui/agent_framework_devui/ui/assets/index.css @@ -1 +1 @@ -/*! tailwindcss v4.1.12 | MIT License | https://tailwindcss.com */@layer properties{@supports (((-webkit-hyphens:none)) and (not (margin-trim:inline))) or ((-moz-orient:inline) and (not (color:rgb(from red r g b)))){*,:before,:after,::backdrop{--tw-translate-x:0;--tw-translate-y:0;--tw-translate-z:0;--tw-scale-x:1;--tw-scale-y:1;--tw-scale-z:1;--tw-rotate-x:initial;--tw-rotate-y:initial;--tw-rotate-z:initial;--tw-skew-x:initial;--tw-skew-y:initial;--tw-space-y-reverse:0;--tw-space-x-reverse:0;--tw-border-style:solid;--tw-leading:initial;--tw-font-weight:initial;--tw-tracking:initial;--tw-shadow:0 0 #0000;--tw-shadow-color:initial;--tw-shadow-alpha:100%;--tw-inset-shadow:0 0 #0000;--tw-inset-shadow-color:initial;--tw-inset-shadow-alpha:100%;--tw-ring-color:initial;--tw-ring-shadow:0 0 #0000;--tw-inset-ring-color:initial;--tw-inset-ring-shadow:0 0 #0000;--tw-ring-inset:initial;--tw-ring-offset-width:0px;--tw-ring-offset-color:#fff;--tw-ring-offset-shadow:0 0 #0000;--tw-outline-style:solid;--tw-blur:initial;--tw-brightness:initial;--tw-contrast:initial;--tw-grayscale:initial;--tw-hue-rotate:initial;--tw-invert:initial;--tw-opacity:initial;--tw-saturate:initial;--tw-sepia:initial;--tw-drop-shadow:initial;--tw-drop-shadow-color:initial;--tw-drop-shadow-alpha:100%;--tw-drop-shadow-size:initial;--tw-backdrop-blur:initial;--tw-backdrop-brightness:initial;--tw-backdrop-contrast:initial;--tw-backdrop-grayscale:initial;--tw-backdrop-hue-rotate:initial;--tw-backdrop-invert:initial;--tw-backdrop-opacity:initial;--tw-backdrop-saturate:initial;--tw-backdrop-sepia:initial;--tw-duration:initial;--tw-ease:initial;--tw-animation-delay:0s;--tw-animation-direction:normal;--tw-animation-duration:initial;--tw-animation-fill-mode:none;--tw-animation-iteration-count:1;--tw-enter-blur:0;--tw-enter-opacity:1;--tw-enter-rotate:0;--tw-enter-scale:1;--tw-enter-translate-x:0;--tw-enter-translate-y:0;--tw-exit-blur:0;--tw-exit-opacity:1;--tw-exit-rotate:0;--tw-exit-scale:1;--tw-exit-translate-x:0;--tw-exit-translate-y:0}}}@layer theme{:root,:host{--font-sans:ui-sans-serif,system-ui,sans-serif,"Apple Color Emoji","Segoe UI Emoji","Segoe UI Symbol","Noto Color Emoji";--font-mono:ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,"Liberation Mono","Courier New",monospace;--color-red-50:oklch(97.1% .013 17.38);--color-red-100:oklch(93.6% .032 17.717);--color-red-200:oklch(88.5% .062 18.334);--color-red-400:oklch(70.4% .191 22.216);--color-red-500:oklch(63.7% .237 25.331);--color-red-600:oklch(57.7% .245 27.325);--color-red-700:oklch(50.5% .213 27.518);--color-red-800:oklch(44.4% .177 26.899);--color-red-900:oklch(39.6% .141 25.723);--color-red-950:oklch(25.8% .092 26.042);--color-orange-50:oklch(98% .016 73.684);--color-orange-100:oklch(95.4% .038 75.164);--color-orange-200:oklch(90.1% .076 70.697);--color-orange-400:oklch(75% .183 55.934);--color-orange-500:oklch(70.5% .213 47.604);--color-orange-600:oklch(64.6% .222 41.116);--color-orange-800:oklch(47% .157 37.304);--color-orange-900:oklch(40.8% .123 38.172);--color-orange-950:oklch(26.6% .079 36.259);--color-amber-50:oklch(98.7% .022 95.277);--color-amber-100:oklch(96.2% .059 95.617);--color-amber-200:oklch(92.4% .12 95.746);--color-amber-300:oklch(87.9% .169 91.605);--color-amber-400:oklch(82.8% .189 84.429);--color-amber-500:oklch(76.9% .188 70.08);--color-amber-600:oklch(66.6% .179 58.318);--color-amber-700:oklch(55.5% .163 48.998);--color-amber-800:oklch(47.3% .137 46.201);--color-amber-900:oklch(41.4% .112 45.904);--color-amber-950:oklch(27.9% .077 45.635);--color-yellow-100:oklch(97.3% .071 103.193);--color-yellow-200:oklch(94.5% .129 101.54);--color-yellow-400:oklch(85.2% .199 91.936);--color-yellow-600:oklch(68.1% .162 75.834);--color-yellow-700:oklch(55.4% .135 66.442);--color-green-50:oklch(98.2% .018 155.826);--color-green-100:oklch(96.2% .044 156.743);--color-green-200:oklch(92.5% .084 155.995);--color-green-300:oklch(87.1% .15 154.449);--color-green-400:oklch(79.2% .209 151.711);--color-green-500:oklch(72.3% .219 149.579);--color-green-600:oklch(62.7% .194 149.214);--color-green-700:oklch(52.7% .154 150.069);--color-green-800:oklch(44.8% .119 151.328);--color-green-900:oklch(39.3% .095 152.535);--color-green-950:oklch(26.6% .065 152.934);--color-emerald-50:oklch(97.9% .021 166.113);--color-emerald-100:oklch(95% .052 163.051);--color-emerald-200:oklch(90.5% .093 164.15);--color-emerald-300:oklch(84.5% .143 164.978);--color-emerald-400:oklch(76.5% .177 163.223);--color-emerald-600:oklch(59.6% .145 163.225);--color-emerald-700:oklch(50.8% .118 165.612);--color-emerald-800:oklch(43.2% .095 166.913);--color-emerald-900:oklch(37.8% .077 168.94);--color-emerald-950:oklch(26.2% .051 172.552);--color-blue-50:oklch(97% .014 254.604);--color-blue-100:oklch(93.2% .032 255.585);--color-blue-200:oklch(88.2% .059 254.128);--color-blue-300:oklch(80.9% .105 251.813);--color-blue-400:oklch(70.7% .165 254.624);--color-blue-500:oklch(62.3% .214 259.815);--color-blue-600:oklch(54.6% .245 262.881);--color-blue-700:oklch(48.8% .243 264.376);--color-blue-800:oklch(42.4% .199 265.638);--color-blue-900:oklch(37.9% .146 265.522);--color-blue-950:oklch(28.2% .091 267.935);--color-purple-50:oklch(97.7% .014 308.299);--color-purple-100:oklch(94.6% .033 307.174);--color-purple-400:oklch(71.4% .203 305.504);--color-purple-500:oklch(62.7% .265 303.9);--color-purple-600:oklch(55.8% .288 302.321);--color-purple-900:oklch(38.1% .176 304.987);--color-gray-50:oklch(98.5% .002 247.839);--color-gray-100:oklch(96.7% .003 264.542);--color-gray-200:oklch(92.8% .006 264.531);--color-gray-300:oklch(87.2% .01 258.338);--color-gray-400:oklch(70.7% .022 261.325);--color-gray-500:oklch(55.1% .027 264.364);--color-gray-600:oklch(44.6% .03 256.802);--color-gray-700:oklch(37.3% .034 259.733);--color-gray-800:oklch(27.8% .033 256.848);--color-gray-900:oklch(21% .034 264.665);--color-black:#000;--color-white:#fff;--spacing:.25rem;--container-md:28rem;--container-lg:32rem;--container-2xl:42rem;--container-3xl:48rem;--container-4xl:56rem;--container-5xl:64rem;--container-7xl:80rem;--text-xs:.75rem;--text-xs--line-height:calc(1/.75);--text-sm:.875rem;--text-sm--line-height:calc(1.25/.875);--text-base:1rem;--text-base--line-height: 1.5 ;--text-lg:1.125rem;--text-lg--line-height:calc(1.75/1.125);--text-xl:1.25rem;--text-xl--line-height:calc(1.75/1.25);--text-2xl:1.5rem;--text-2xl--line-height:calc(2/1.5);--font-weight-medium:500;--font-weight-semibold:600;--font-weight-bold:700;--tracking-tight:-.025em;--tracking-widest:.1em;--leading-tight:1.25;--leading-relaxed:1.625;--drop-shadow-lg:0 4px 4px #00000026;--ease-in-out:cubic-bezier(.4,0,.2,1);--animate-spin:spin 1s linear infinite;--animate-pulse:pulse 2s cubic-bezier(.4,0,.6,1)infinite;--animate-bounce:bounce 1s infinite;--blur-sm:8px;--default-transition-duration:.15s;--default-transition-timing-function:cubic-bezier(.4,0,.2,1);--default-font-family:var(--font-sans);--default-mono-font-family:var(--font-mono)}}@layer base{*,:after,:before,::backdrop{box-sizing:border-box;border:0 solid;margin:0;padding:0}::file-selector-button{box-sizing:border-box;border:0 solid;margin:0;padding:0}html,:host{-webkit-text-size-adjust:100%;tab-size:4;line-height:1.5;font-family:var(--default-font-family,ui-sans-serif,system-ui,sans-serif,"Apple Color Emoji","Segoe UI Emoji","Segoe UI Symbol","Noto Color Emoji");font-feature-settings:var(--default-font-feature-settings,normal);font-variation-settings:var(--default-font-variation-settings,normal);-webkit-tap-highlight-color:transparent}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;-webkit-text-decoration:inherit;text-decoration:inherit}b,strong{font-weight:bolder}code,kbd,samp,pre{font-family:var(--default-mono-font-family,ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,"Liberation Mono","Courier New",monospace);font-feature-settings:var(--default-mono-font-feature-settings,normal);font-variation-settings:var(--default-mono-font-variation-settings,normal);font-size:1em}small{font-size:80%}sub,sup{vertical-align:baseline;font-size:75%;line-height:0;position:relative}sub{bottom:-.25em}sup{top:-.5em}table{text-indent:0;border-color:inherit;border-collapse:collapse}:-moz-focusring{outline:auto}progress{vertical-align:baseline}summary{display:list-item}ol,ul,menu{list-style:none}img,svg,video,canvas,audio,iframe,embed,object{vertical-align:middle;display:block}img,video{max-width:100%;height:auto}button,input,select,optgroup,textarea{font:inherit;font-feature-settings:inherit;font-variation-settings:inherit;letter-spacing:inherit;color:inherit;opacity:1;background-color:#0000;border-radius:0}::file-selector-button{font:inherit;font-feature-settings:inherit;font-variation-settings:inherit;letter-spacing:inherit;color:inherit;opacity:1;background-color:#0000;border-radius:0}:where(select:is([multiple],[size])) optgroup{font-weight:bolder}:where(select:is([multiple],[size])) optgroup option{padding-inline-start:20px}::file-selector-button{margin-inline-end:4px}::placeholder{opacity:1}@supports (not ((-webkit-appearance:-apple-pay-button))) or (contain-intrinsic-size:1px){::placeholder{color:currentColor}@supports (color:color-mix(in lab,red,red)){::placeholder{color:color-mix(in oklab,currentcolor 50%,transparent)}}}textarea{resize:vertical}::-webkit-search-decoration{-webkit-appearance:none}::-webkit-date-and-time-value{min-height:1lh;text-align:inherit}::-webkit-datetime-edit{display:inline-flex}::-webkit-datetime-edit-fields-wrapper{padding:0}::-webkit-datetime-edit{padding-block:0}::-webkit-datetime-edit-year-field{padding-block:0}::-webkit-datetime-edit-month-field{padding-block:0}::-webkit-datetime-edit-day-field{padding-block:0}::-webkit-datetime-edit-hour-field{padding-block:0}::-webkit-datetime-edit-minute-field{padding-block:0}::-webkit-datetime-edit-second-field{padding-block:0}::-webkit-datetime-edit-millisecond-field{padding-block:0}::-webkit-datetime-edit-meridiem-field{padding-block:0}::-webkit-calendar-picker-indicator{line-height:1}:-moz-ui-invalid{box-shadow:none}button,input:where([type=button],[type=reset],[type=submit]){appearance:button}::file-selector-button{appearance:button}::-webkit-inner-spin-button{height:auto}::-webkit-outer-spin-button{height:auto}[hidden]:where(:not([hidden=until-found])){display:none!important}*{border-color:var(--border);outline-color:var(--ring)}@supports (color:color-mix(in lab,red,red)){*{outline-color:color-mix(in oklab,var(--ring)50%,transparent)}}body{background-color:var(--background);color:var(--foreground)}}@layer components;@layer utilities{.\@container\/card-header{container:card-header/inline-size}.pointer-events-auto{pointer-events:auto}.pointer-events-none{pointer-events:none}.collapse{visibility:collapse}.visible{visibility:visible}.sr-only{clip:rect(0,0,0,0);white-space:nowrap;border-width:0;width:1px;height:1px;margin:-1px;padding:0;position:absolute;overflow:hidden}.absolute{position:absolute}.fixed{position:fixed}.relative{position:relative}.static{position:static}.inset-0{inset:calc(var(--spacing)*0)}.inset-2{inset:calc(var(--spacing)*2)}.inset-x-0{inset-inline:calc(var(--spacing)*0)}.inset-y-0{inset-block:calc(var(--spacing)*0)}.-top-1{top:calc(var(--spacing)*-1)}.-top-2{top:calc(var(--spacing)*-2)}.top-1{top:calc(var(--spacing)*1)}.top-2{top:calc(var(--spacing)*2)}.top-4{top:calc(var(--spacing)*4)}.-right-1{right:calc(var(--spacing)*-1)}.-right-2{right:calc(var(--spacing)*-2)}.right-0{right:calc(var(--spacing)*0)}.right-1{right:calc(var(--spacing)*1)}.right-2{right:calc(var(--spacing)*2)}.right-3{right:calc(var(--spacing)*3)}.right-4{right:calc(var(--spacing)*4)}.-bottom-2{bottom:calc(var(--spacing)*-2)}.bottom-0{bottom:calc(var(--spacing)*0)}.bottom-3{bottom:calc(var(--spacing)*3)}.bottom-14{bottom:calc(var(--spacing)*14)}.bottom-24{bottom:calc(var(--spacing)*24)}.-left-2{left:calc(var(--spacing)*-2)}.left-0{left:calc(var(--spacing)*0)}.left-1\/2{left:50%}.left-2{left:calc(var(--spacing)*2)}.z-10{z-index:10}.z-20{z-index:20}.z-50{z-index:50}.col-start-2{grid-column-start:2}.row-span-2{grid-row:span 2/span 2}.row-start-1{grid-row-start:1}.container{width:100%}@media (min-width:40rem){.container{max-width:40rem}}@media (min-width:48rem){.container{max-width:48rem}}@media (min-width:64rem){.container{max-width:64rem}}@media (min-width:80rem){.container{max-width:80rem}}@media (min-width:96rem){.container{max-width:96rem}}.container\!{width:100%!important}@media (min-width:40rem){.container\!{max-width:40rem!important}}@media (min-width:48rem){.container\!{max-width:48rem!important}}@media (min-width:64rem){.container\!{max-width:64rem!important}}@media (min-width:80rem){.container\!{max-width:80rem!important}}@media (min-width:96rem){.container\!{max-width:96rem!important}}.-mx-1{margin-inline:calc(var(--spacing)*-1)}.mx-auto{margin-inline:auto}.my-1{margin-block:calc(var(--spacing)*1)}.my-2{margin-block:calc(var(--spacing)*2)}.my-3{margin-block:calc(var(--spacing)*3)}.my-4{margin-block:calc(var(--spacing)*4)}.mt-0{margin-top:calc(var(--spacing)*0)}.mt-0\.5{margin-top:calc(var(--spacing)*.5)}.mt-1{margin-top:calc(var(--spacing)*1)}.mt-2{margin-top:calc(var(--spacing)*2)}.mt-3{margin-top:calc(var(--spacing)*3)}.mt-4{margin-top:calc(var(--spacing)*4)}.mt-12{margin-top:calc(var(--spacing)*12)}.mr-1{margin-right:calc(var(--spacing)*1)}.mr-1\.5{margin-right:calc(var(--spacing)*1.5)}.mr-2{margin-right:calc(var(--spacing)*2)}.mb-1{margin-bottom:calc(var(--spacing)*1)}.mb-2{margin-bottom:calc(var(--spacing)*2)}.mb-3{margin-bottom:calc(var(--spacing)*3)}.mb-4{margin-bottom:calc(var(--spacing)*4)}.mb-6{margin-bottom:calc(var(--spacing)*6)}.mb-8{margin-bottom:calc(var(--spacing)*8)}.ml-1{margin-left:calc(var(--spacing)*1)}.ml-1\.5{margin-left:calc(var(--spacing)*1.5)}.ml-2{margin-left:calc(var(--spacing)*2)}.ml-3{margin-left:calc(var(--spacing)*3)}.ml-4{margin-left:calc(var(--spacing)*4)}.ml-5{margin-left:calc(var(--spacing)*5)}.ml-auto{margin-left:auto}.line-clamp-2{-webkit-line-clamp:2;-webkit-box-orient:vertical;display:-webkit-box;overflow:hidden}.line-clamp-3{-webkit-line-clamp:3;-webkit-box-orient:vertical;display:-webkit-box;overflow:hidden}.block{display:block}.contents{display:contents}.flex{display:flex}.grid{display:grid}.hidden{display:none}.inline{display:inline}.inline-block{display:inline-block}.inline-flex{display:inline-flex}.table{display:table}.field-sizing-content{field-sizing:content}.size-2{width:calc(var(--spacing)*2);height:calc(var(--spacing)*2)}.size-3\.5{width:calc(var(--spacing)*3.5);height:calc(var(--spacing)*3.5)}.size-4{width:calc(var(--spacing)*4);height:calc(var(--spacing)*4)}.size-9{width:calc(var(--spacing)*9);height:calc(var(--spacing)*9)}.\!h-2{height:calc(var(--spacing)*2)!important}.h-0{height:calc(var(--spacing)*0)}.h-0\.5{height:calc(var(--spacing)*.5)}.h-1{height:calc(var(--spacing)*1)}.h-2{height:calc(var(--spacing)*2)}.h-2\.5{height:calc(var(--spacing)*2.5)}.h-3{height:calc(var(--spacing)*3)}.h-3\.5{height:calc(var(--spacing)*3.5)}.h-4{height:calc(var(--spacing)*4)}.h-5{height:calc(var(--spacing)*5)}.h-6{height:calc(var(--spacing)*6)}.h-7{height:calc(var(--spacing)*7)}.h-8{height:calc(var(--spacing)*8)}.h-9{height:calc(var(--spacing)*9)}.h-10{height:calc(var(--spacing)*10)}.h-12{height:calc(var(--spacing)*12)}.h-14{height:calc(var(--spacing)*14)}.h-16{height:calc(var(--spacing)*16)}.h-32{height:calc(var(--spacing)*32)}.h-96{height:calc(var(--spacing)*96)}.h-\[1\.2rem\]{height:1.2rem}.h-\[500px\]{height:500px}.h-\[calc\(100vh-3\.5rem\)\]{height:calc(100vh - 3.5rem)}.h-\[calc\(100vh-3\.7rem\)\]{height:calc(100vh - 3.7rem)}.h-\[var\(--radix-select-trigger-height\)\]{height:var(--radix-select-trigger-height)}.h-full{height:100%}.h-px{height:1px}.h-screen{height:100vh}.max-h-\(--radix-dropdown-menu-content-available-height\){max-height:var(--radix-dropdown-menu-content-available-height)}.max-h-\(--radix-select-content-available-height\){max-height:var(--radix-select-content-available-height)}.max-h-20{max-height:calc(var(--spacing)*20)}.max-h-24{max-height:calc(var(--spacing)*24)}.max-h-32{max-height:calc(var(--spacing)*32)}.max-h-64{max-height:calc(var(--spacing)*64)}.max-h-\[90vh\]{max-height:90vh}.max-h-\[200px\]{max-height:200px}.max-h-none{max-height:none}.max-h-screen{max-height:100vh}.\!min-h-0{min-height:calc(var(--spacing)*0)!important}.min-h-0{min-height:calc(var(--spacing)*0)}.min-h-16{min-height:calc(var(--spacing)*16)}.min-h-\[36px\]{min-height:36px}.min-h-\[40px\]{min-height:40px}.min-h-\[50vh\]{min-height:50vh}.min-h-\[240px\]{min-height:240px}.min-h-\[400px\]{min-height:400px}.min-h-screen{min-height:100vh}.\!w-2{width:calc(var(--spacing)*2)!important}.w-1{width:calc(var(--spacing)*1)}.w-2{width:calc(var(--spacing)*2)}.w-2\.5{width:calc(var(--spacing)*2.5)}.w-3{width:calc(var(--spacing)*3)}.w-3\.5{width:calc(var(--spacing)*3.5)}.w-4{width:calc(var(--spacing)*4)}.w-5{width:calc(var(--spacing)*5)}.w-6{width:calc(var(--spacing)*6)}.w-8{width:calc(var(--spacing)*8)}.w-10{width:calc(var(--spacing)*10)}.w-12{width:calc(var(--spacing)*12)}.w-16{width:calc(var(--spacing)*16)}.w-56{width:calc(var(--spacing)*56)}.w-64{width:calc(var(--spacing)*64)}.w-80{width:calc(var(--spacing)*80)}.w-\[1\.2rem\]{width:1.2rem}.w-\[600px\]{width:600px}.w-\[800px\]{width:800px}.w-fit{width:fit-content}.w-full{width:100%}.max-w-2xl{max-width:var(--container-2xl)}.max-w-3xl{max-width:var(--container-3xl)}.max-w-4xl{max-width:var(--container-4xl)}.max-w-7xl{max-width:var(--container-7xl)}.max-w-\[80\%\]{max-width:80%}.max-w-\[90vw\]{max-width:90vw}.max-w-full{max-width:100%}.max-w-lg{max-width:var(--container-lg)}.max-w-md{max-width:var(--container-md)}.max-w-none{max-width:none}.\!min-w-0{min-width:calc(var(--spacing)*0)!important}.min-w-0{min-width:calc(var(--spacing)*0)}.min-w-\[8rem\]{min-width:8rem}.min-w-\[300px\]{min-width:300px}.min-w-\[400px\]{min-width:400px}.min-w-\[var\(--radix-select-trigger-width\)\]{min-width:var(--radix-select-trigger-width)}.min-w-full{min-width:100%}.flex-1{flex:1}.flex-shrink-0,.shrink-0{flex-shrink:0}.origin-\(--radix-dropdown-menu-content-transform-origin\){transform-origin:var(--radix-dropdown-menu-content-transform-origin)}.origin-\(--radix-select-content-transform-origin\){transform-origin:var(--radix-select-content-transform-origin)}.-translate-x-1\/2{--tw-translate-x: -50% ;translate:var(--tw-translate-x)var(--tw-translate-y)}.translate-x-0{--tw-translate-x:calc(var(--spacing)*0);translate:var(--tw-translate-x)var(--tw-translate-y)}.translate-x-4{--tw-translate-x:calc(var(--spacing)*4);translate:var(--tw-translate-x)var(--tw-translate-y)}.scale-0{--tw-scale-x:0%;--tw-scale-y:0%;--tw-scale-z:0%;scale:var(--tw-scale-x)var(--tw-scale-y)}.scale-75{--tw-scale-x:75%;--tw-scale-y:75%;--tw-scale-z:75%;scale:var(--tw-scale-x)var(--tw-scale-y)}.scale-100{--tw-scale-x:100%;--tw-scale-y:100%;--tw-scale-z:100%;scale:var(--tw-scale-x)var(--tw-scale-y)}.rotate-0{rotate:none}.rotate-90{rotate:90deg}.transform{transform:var(--tw-rotate-x,)var(--tw-rotate-y,)var(--tw-rotate-z,)var(--tw-skew-x,)var(--tw-skew-y,)}.animate-bounce{animation:var(--animate-bounce)}.animate-in{animation:enter var(--tw-animation-duration,var(--tw-duration,.15s))var(--tw-ease,ease)var(--tw-animation-delay,0s)var(--tw-animation-iteration-count,1)var(--tw-animation-direction,normal)var(--tw-animation-fill-mode,none)}.animate-pulse{animation:var(--animate-pulse)}.animate-spin{animation:var(--animate-spin)}.cursor-col-resize{cursor:col-resize}.cursor-default{cursor:default}.cursor-pointer{cursor:pointer}.cursor-row-resize{cursor:row-resize}.touch-none{touch-action:none}.resize{resize:both}.resize-none{resize:none}.scroll-my-1{scroll-margin-block:calc(var(--spacing)*1)}.list-inside{list-style-position:inside}.list-decimal{list-style-type:decimal}.list-disc{list-style-type:disc}.list-none{list-style-type:none}.auto-rows-min{grid-auto-rows:min-content}.grid-cols-1{grid-template-columns:repeat(1,minmax(0,1fr))}.grid-rows-\[auto_auto\]{grid-template-rows:auto auto}.flex-col{flex-direction:column}.flex-row-reverse{flex-direction:row-reverse}.flex-wrap{flex-wrap:wrap}.items-center{align-items:center}.items-end{align-items:flex-end}.items-start{align-items:flex-start}.items-stretch{align-items:stretch}.justify-between{justify-content:space-between}.justify-center{justify-content:center}.justify-end{justify-content:flex-end}.gap-1{gap:calc(var(--spacing)*1)}.gap-1\.5{gap:calc(var(--spacing)*1.5)}.gap-2{gap:calc(var(--spacing)*2)}.gap-3{gap:calc(var(--spacing)*3)}.gap-4{gap:calc(var(--spacing)*4)}.gap-6{gap:calc(var(--spacing)*6)}:where(.space-y-1>:not(:last-child)){--tw-space-y-reverse:0;margin-block-start:calc(calc(var(--spacing)*1)*var(--tw-space-y-reverse));margin-block-end:calc(calc(var(--spacing)*1)*calc(1 - var(--tw-space-y-reverse)))}:where(.space-y-2>:not(:last-child)){--tw-space-y-reverse:0;margin-block-start:calc(calc(var(--spacing)*2)*var(--tw-space-y-reverse));margin-block-end:calc(calc(var(--spacing)*2)*calc(1 - var(--tw-space-y-reverse)))}:where(.space-y-3>:not(:last-child)){--tw-space-y-reverse:0;margin-block-start:calc(calc(var(--spacing)*3)*var(--tw-space-y-reverse));margin-block-end:calc(calc(var(--spacing)*3)*calc(1 - var(--tw-space-y-reverse)))}:where(.space-y-4>:not(:last-child)){--tw-space-y-reverse:0;margin-block-start:calc(calc(var(--spacing)*4)*var(--tw-space-y-reverse));margin-block-end:calc(calc(var(--spacing)*4)*calc(1 - var(--tw-space-y-reverse)))}:where(.space-y-6>:not(:last-child)){--tw-space-y-reverse:0;margin-block-start:calc(calc(var(--spacing)*6)*var(--tw-space-y-reverse));margin-block-end:calc(calc(var(--spacing)*6)*calc(1 - var(--tw-space-y-reverse)))}:where(.space-x-1>:not(:last-child)){--tw-space-x-reverse:0;margin-inline-start:calc(calc(var(--spacing)*1)*var(--tw-space-x-reverse));margin-inline-end:calc(calc(var(--spacing)*1)*calc(1 - var(--tw-space-x-reverse)))}:where(.space-x-2>:not(:last-child)){--tw-space-x-reverse:0;margin-inline-start:calc(calc(var(--spacing)*2)*var(--tw-space-x-reverse));margin-inline-end:calc(calc(var(--spacing)*2)*calc(1 - var(--tw-space-x-reverse)))}.self-start{align-self:flex-start}.justify-self-end{justify-self:flex-end}.truncate{text-overflow:ellipsis;white-space:nowrap;overflow:hidden}.overflow-auto{overflow:auto}.overflow-hidden{overflow:hidden}.overflow-x-auto{overflow-x:auto}.overflow-x-hidden{overflow-x:hidden}.overflow-y-auto{overflow-y:auto}.\!rounded-full{border-radius:3.40282e38px!important}.rounded{border-radius:.25rem}.rounded-\[4px\]{border-radius:4px}.rounded-\[inherit\]{border-radius:inherit}.rounded-full{border-radius:3.40282e38px}.rounded-lg{border-radius:var(--radius)}.rounded-md{border-radius:calc(var(--radius) - 2px)}.rounded-none{border-radius:0}.rounded-sm{border-radius:calc(var(--radius) - 4px)}.rounded-t{border-top-left-radius:.25rem;border-top-right-radius:.25rem}.rounded-l-none{border-top-left-radius:0;border-bottom-left-radius:0}.rounded-r-none{border-top-right-radius:0;border-bottom-right-radius:0}.\!border{border-style:var(--tw-border-style)!important;border-width:1px!important}.border{border-style:var(--tw-border-style);border-width:1px}.border-2{border-style:var(--tw-border-style);border-width:2px}.border-t{border-top-style:var(--tw-border-style);border-top-width:1px}.border-r{border-right-style:var(--tw-border-style);border-right-width:1px}.border-b{border-bottom-style:var(--tw-border-style);border-bottom-width:1px}.border-l{border-left-style:var(--tw-border-style);border-left-width:1px}.border-l-0{border-left-style:var(--tw-border-style);border-left-width:0}.border-l-2{border-left-style:var(--tw-border-style);border-left-width:2px}.border-l-4{border-left-style:var(--tw-border-style);border-left-width:4px}.border-dashed{--tw-border-style:dashed;border-style:dashed}.\!border-gray-600{border-color:var(--color-gray-600)!important}.border-\[\#643FB2\]{border-color:#643fb2}.border-\[\#643FB2\]\/30{border-color:#643fb24d}.border-\[\#643FB2\]\/40{border-color:#643fb266}.border-amber-200{border-color:var(--color-amber-200)}.border-blue-200{border-color:var(--color-blue-200)}.border-blue-300{border-color:var(--color-blue-300)}.border-blue-400{border-color:var(--color-blue-400)}.border-blue-500\/30{border-color:#3080ff4d}@supports (color:color-mix(in lab,red,red)){.border-blue-500\/30{border-color:color-mix(in oklab,var(--color-blue-500)30%,transparent)}}.border-blue-500\/40{border-color:#3080ff66}@supports (color:color-mix(in lab,red,red)){.border-blue-500\/40{border-color:color-mix(in oklab,var(--color-blue-500)40%,transparent)}}.border-border,.border-border\/50{border-color:var(--border)}@supports (color:color-mix(in lab,red,red)){.border-border\/50{border-color:color-mix(in oklab,var(--border)50%,transparent)}}.border-current\/30{border-color:currentColor}@supports (color:color-mix(in lab,red,red)){.border-current\/30{border-color:color-mix(in oklab,currentcolor 30%,transparent)}}.border-destructive\/30{border-color:var(--destructive)}@supports (color:color-mix(in lab,red,red)){.border-destructive\/30{border-color:color-mix(in oklab,var(--destructive)30%,transparent)}}.border-destructive\/50{border-color:var(--destructive)}@supports (color:color-mix(in lab,red,red)){.border-destructive\/50{border-color:color-mix(in oklab,var(--destructive)50%,transparent)}}.border-destructive\/70{border-color:var(--destructive)}@supports (color:color-mix(in lab,red,red)){.border-destructive\/70{border-color:color-mix(in oklab,var(--destructive)70%,transparent)}}.border-emerald-300{border-color:var(--color-emerald-300)}.border-foreground\/5{border-color:var(--foreground)}@supports (color:color-mix(in lab,red,red)){.border-foreground\/5{border-color:color-mix(in oklab,var(--foreground)5%,transparent)}}.border-foreground\/10{border-color:var(--foreground)}@supports (color:color-mix(in lab,red,red)){.border-foreground\/10{border-color:color-mix(in oklab,var(--foreground)10%,transparent)}}.border-foreground\/20{border-color:var(--foreground)}@supports (color:color-mix(in lab,red,red)){.border-foreground\/20{border-color:color-mix(in oklab,var(--foreground)20%,transparent)}}.border-gray-200{border-color:var(--color-gray-200)}.border-gray-300{border-color:var(--color-gray-300)}.border-green-200{border-color:var(--color-green-200)}.border-green-500{border-color:var(--color-green-500)}.border-green-500\/30{border-color:#00c7584d}@supports (color:color-mix(in lab,red,red)){.border-green-500\/30{border-color:color-mix(in oklab,var(--color-green-500)30%,transparent)}}.border-green-500\/40{border-color:#00c75866}@supports (color:color-mix(in lab,red,red)){.border-green-500\/40{border-color:color-mix(in oklab,var(--color-green-500)40%,transparent)}}.border-input{border-color:var(--input)}.border-muted{border-color:var(--muted)}.border-orange-200{border-color:var(--color-orange-200)}.border-orange-500{border-color:var(--color-orange-500)}.border-primary,.border-primary\/20{border-color:var(--primary)}@supports (color:color-mix(in lab,red,red)){.border-primary\/20{border-color:color-mix(in oklab,var(--primary)20%,transparent)}}.border-red-200{border-color:var(--color-red-200)}.border-red-500{border-color:var(--color-red-500)}.border-transparent{border-color:#0000}.border-yellow-200{border-color:var(--color-yellow-200)}.border-t-transparent{border-top-color:#0000}.border-l-transparent{border-left-color:#0000}.bg-\[\#643FB2\]{background-color:#643fb2}.bg-\[\#643FB2\]\/5{background-color:#643fb20d}.bg-\[\#643FB2\]\/10{background-color:#643fb21a}.bg-accent\/10{background-color:var(--accent)}@supports (color:color-mix(in lab,red,red)){.bg-accent\/10{background-color:color-mix(in oklab,var(--accent)10%,transparent)}}.bg-amber-50{background-color:var(--color-amber-50)}.bg-background{background-color:var(--background)}.bg-black{background-color:var(--color-black)}.bg-black\/50{background-color:#00000080}@supports (color:color-mix(in lab,red,red)){.bg-black\/50{background-color:color-mix(in oklab,var(--color-black)50%,transparent)}}.bg-black\/60{background-color:#0009}@supports (color:color-mix(in lab,red,red)){.bg-black\/60{background-color:color-mix(in oklab,var(--color-black)60%,transparent)}}.bg-blue-50{background-color:var(--color-blue-50)}.bg-blue-50\/80{background-color:#eff6ffcc}@supports (color:color-mix(in lab,red,red)){.bg-blue-50\/80{background-color:color-mix(in oklab,var(--color-blue-50)80%,transparent)}}.bg-blue-100{background-color:var(--color-blue-100)}.bg-blue-500{background-color:var(--color-blue-500)}.bg-blue-500\/5{background-color:#3080ff0d}@supports (color:color-mix(in lab,red,red)){.bg-blue-500\/5{background-color:color-mix(in oklab,var(--color-blue-500)5%,transparent)}}.bg-blue-500\/10{background-color:#3080ff1a}@supports (color:color-mix(in lab,red,red)){.bg-blue-500\/10{background-color:color-mix(in oklab,var(--color-blue-500)10%,transparent)}}.bg-border{background-color:var(--border)}.bg-card{background-color:var(--card)}.bg-current{background-color:currentColor}.bg-destructive,.bg-destructive\/5{background-color:var(--destructive)}@supports (color:color-mix(in lab,red,red)){.bg-destructive\/5{background-color:color-mix(in oklab,var(--destructive)5%,transparent)}}.bg-destructive\/10{background-color:var(--destructive)}@supports (color:color-mix(in lab,red,red)){.bg-destructive\/10{background-color:color-mix(in oklab,var(--destructive)10%,transparent)}}.bg-destructive\/80{background-color:var(--destructive)}@supports (color:color-mix(in lab,red,red)){.bg-destructive\/80{background-color:color-mix(in oklab,var(--destructive)80%,transparent)}}.bg-emerald-50{background-color:var(--color-emerald-50)}.bg-emerald-100{background-color:var(--color-emerald-100)}.bg-foreground\/5{background-color:var(--foreground)}@supports (color:color-mix(in lab,red,red)){.bg-foreground\/5{background-color:color-mix(in oklab,var(--foreground)5%,transparent)}}.bg-foreground\/10{background-color:var(--foreground)}@supports (color:color-mix(in lab,red,red)){.bg-foreground\/10{background-color:color-mix(in oklab,var(--foreground)10%,transparent)}}.bg-gray-50{background-color:var(--color-gray-50)}.bg-gray-100{background-color:var(--color-gray-100)}.bg-gray-200{background-color:var(--color-gray-200)}.bg-gray-400{background-color:var(--color-gray-400)}.bg-gray-900\/90{background-color:#101828e6}@supports (color:color-mix(in lab,red,red)){.bg-gray-900\/90{background-color:color-mix(in oklab,var(--color-gray-900)90%,transparent)}}.bg-green-50{background-color:var(--color-green-50)}.bg-green-100{background-color:var(--color-green-100)}.bg-green-500{background-color:var(--color-green-500)}.bg-green-500\/5{background-color:#00c7580d}@supports (color:color-mix(in lab,red,red)){.bg-green-500\/5{background-color:color-mix(in oklab,var(--color-green-500)5%,transparent)}}.bg-green-500\/10{background-color:#00c7581a}@supports (color:color-mix(in lab,red,red)){.bg-green-500\/10{background-color:color-mix(in oklab,var(--color-green-500)10%,transparent)}}.bg-muted,.bg-muted\/30{background-color:var(--muted)}@supports (color:color-mix(in lab,red,red)){.bg-muted\/30{background-color:color-mix(in oklab,var(--muted)30%,transparent)}}.bg-muted\/50{background-color:var(--muted)}@supports (color:color-mix(in lab,red,red)){.bg-muted\/50{background-color:color-mix(in oklab,var(--muted)50%,transparent)}}.bg-orange-50{background-color:var(--color-orange-50)}.bg-orange-100{background-color:var(--color-orange-100)}.bg-orange-500{background-color:var(--color-orange-500)}.bg-popover{background-color:var(--popover)}.bg-primary,.bg-primary\/10{background-color:var(--primary)}@supports (color:color-mix(in lab,red,red)){.bg-primary\/10{background-color:color-mix(in oklab,var(--primary)10%,transparent)}}.bg-primary\/30{background-color:var(--primary)}@supports (color:color-mix(in lab,red,red)){.bg-primary\/30{background-color:color-mix(in oklab,var(--primary)30%,transparent)}}.bg-primary\/40{background-color:var(--primary)}@supports (color:color-mix(in lab,red,red)){.bg-primary\/40{background-color:color-mix(in oklab,var(--primary)40%,transparent)}}.bg-purple-50{background-color:var(--color-purple-50)}.bg-purple-100{background-color:var(--color-purple-100)}.bg-red-50{background-color:var(--color-red-50)}.bg-red-100{background-color:var(--color-red-100)}.bg-red-500{background-color:var(--color-red-500)}.bg-secondary{background-color:var(--secondary)}.bg-transparent{background-color:#0000}.bg-white{background-color:var(--color-white)}.bg-white\/90{background-color:#ffffffe6}@supports (color:color-mix(in lab,red,red)){.bg-white\/90{background-color:color-mix(in oklab,var(--color-white)90%,transparent)}}.bg-yellow-100{background-color:var(--color-yellow-100)}.fill-current{fill:currentColor}.object-cover{object-fit:cover}.p-0{padding:calc(var(--spacing)*0)}.p-1{padding:calc(var(--spacing)*1)}.p-1\.5{padding:calc(var(--spacing)*1.5)}.p-2{padding:calc(var(--spacing)*2)}.p-3{padding:calc(var(--spacing)*3)}.p-4{padding:calc(var(--spacing)*4)}.p-6{padding:calc(var(--spacing)*6)}.p-8{padding:calc(var(--spacing)*8)}.p-\[1px\]{padding:1px}.px-1{padding-inline:calc(var(--spacing)*1)}.px-1\.5{padding-inline:calc(var(--spacing)*1.5)}.px-2{padding-inline:calc(var(--spacing)*2)}.px-2\.5{padding-inline:calc(var(--spacing)*2.5)}.px-3{padding-inline:calc(var(--spacing)*3)}.px-4{padding-inline:calc(var(--spacing)*4)}.px-6{padding-inline:calc(var(--spacing)*6)}.px-8{padding-inline:calc(var(--spacing)*8)}.py-0{padding-block:calc(var(--spacing)*0)}.py-0\.5{padding-block:calc(var(--spacing)*.5)}.py-1{padding-block:calc(var(--spacing)*1)}.py-1\.5{padding-block:calc(var(--spacing)*1.5)}.py-2{padding-block:calc(var(--spacing)*2)}.py-2\.5{padding-block:calc(var(--spacing)*2.5)}.py-3{padding-block:calc(var(--spacing)*3)}.py-4{padding-block:calc(var(--spacing)*4)}.py-6{padding-block:calc(var(--spacing)*6)}.py-8{padding-block:calc(var(--spacing)*8)}.pt-0{padding-top:calc(var(--spacing)*0)}.pt-1{padding-top:calc(var(--spacing)*1)}.pt-2{padding-top:calc(var(--spacing)*2)}.pt-3{padding-top:calc(var(--spacing)*3)}.pt-4{padding-top:calc(var(--spacing)*4)}.pt-6{padding-top:calc(var(--spacing)*6)}.pt-8{padding-top:calc(var(--spacing)*8)}.pr-2{padding-right:calc(var(--spacing)*2)}.pr-4{padding-right:calc(var(--spacing)*4)}.pr-8{padding-right:calc(var(--spacing)*8)}.pb-2{padding-bottom:calc(var(--spacing)*2)}.pb-3{padding-bottom:calc(var(--spacing)*3)}.pb-4{padding-bottom:calc(var(--spacing)*4)}.pb-6{padding-bottom:calc(var(--spacing)*6)}.pb-12{padding-bottom:calc(var(--spacing)*12)}.pl-2{padding-left:calc(var(--spacing)*2)}.pl-3{padding-left:calc(var(--spacing)*3)}.pl-4{padding-left:calc(var(--spacing)*4)}.pl-8{padding-left:calc(var(--spacing)*8)}.text-center{text-align:center}.text-left{text-align:left}.font-mono{font-family:var(--font-mono)}.text-2xl{font-size:var(--text-2xl);line-height:var(--tw-leading,var(--text-2xl--line-height))}.text-base{font-size:var(--text-base);line-height:var(--tw-leading,var(--text-base--line-height))}.text-lg{font-size:var(--text-lg);line-height:var(--tw-leading,var(--text-lg--line-height))}.text-sm{font-size:var(--text-sm);line-height:var(--tw-leading,var(--text-sm--line-height))}.text-xl{font-size:var(--text-xl);line-height:var(--tw-leading,var(--text-xl--line-height))}.text-xs{font-size:var(--text-xs);line-height:var(--tw-leading,var(--text-xs--line-height))}.text-\[10px\]{font-size:10px}.leading-none{--tw-leading:1;line-height:1}.leading-relaxed{--tw-leading:var(--leading-relaxed);line-height:var(--leading-relaxed)}.leading-tight{--tw-leading:var(--leading-tight);line-height:var(--leading-tight)}.font-bold{--tw-font-weight:var(--font-weight-bold);font-weight:var(--font-weight-bold)}.font-medium{--tw-font-weight:var(--font-weight-medium);font-weight:var(--font-weight-medium)}.font-semibold{--tw-font-weight:var(--font-weight-semibold);font-weight:var(--font-weight-semibold)}.tracking-tight{--tw-tracking:var(--tracking-tight);letter-spacing:var(--tracking-tight)}.tracking-widest{--tw-tracking:var(--tracking-widest);letter-spacing:var(--tracking-widest)}.break-words{overflow-wrap:break-word}.break-all{word-break:break-all}.whitespace-nowrap{white-space:nowrap}.whitespace-pre-wrap{white-space:pre-wrap}.text-\[\#643FB2\]{color:#643fb2}.text-amber-500{color:var(--color-amber-500)}.text-amber-600{color:var(--color-amber-600)}.text-amber-700{color:var(--color-amber-700)}.text-amber-800{color:var(--color-amber-800)}.text-amber-900{color:var(--color-amber-900)}.text-blue-500{color:var(--color-blue-500)}.text-blue-600{color:var(--color-blue-600)}.text-blue-700{color:var(--color-blue-700)}.text-blue-800{color:var(--color-blue-800)}.text-card-foreground{color:var(--card-foreground)}.text-current{color:currentColor}.text-destructive{color:var(--destructive)}.text-emerald-600{color:var(--color-emerald-600)}.text-emerald-700{color:var(--color-emerald-700)}.text-emerald-800{color:var(--color-emerald-800)}.text-foreground{color:var(--foreground)}.text-gray-300{color:var(--color-gray-300)}.text-gray-400{color:var(--color-gray-400)}.text-gray-500{color:var(--color-gray-500)}.text-gray-600{color:var(--color-gray-600)}.text-gray-700{color:var(--color-gray-700)}.text-gray-900{color:var(--color-gray-900)}.text-green-500{color:var(--color-green-500)}.text-green-600{color:var(--color-green-600)}.text-green-700{color:var(--color-green-700)}.text-green-800{color:var(--color-green-800)}.text-muted-foreground,.text-muted-foreground\/80{color:var(--muted-foreground)}@supports (color:color-mix(in lab,red,red)){.text-muted-foreground\/80{color:color-mix(in oklab,var(--muted-foreground)80%,transparent)}}.text-orange-500{color:var(--color-orange-500)}.text-orange-600{color:var(--color-orange-600)}.text-orange-800{color:var(--color-orange-800)}.text-popover-foreground{color:var(--popover-foreground)}.text-primary{color:var(--primary)}.text-primary-foreground{color:var(--primary-foreground)}.text-purple-500{color:var(--color-purple-500)}.text-purple-600{color:var(--color-purple-600)}.text-red-500{color:var(--color-red-500)}.text-red-600{color:var(--color-red-600)}.text-red-700{color:var(--color-red-700)}.text-red-800{color:var(--color-red-800)}.text-secondary-foreground{color:var(--secondary-foreground)}.text-white{color:var(--color-white)}.text-yellow-600{color:var(--color-yellow-600)}.text-yellow-700{color:var(--color-yellow-700)}.capitalize{text-transform:capitalize}.uppercase{text-transform:uppercase}.italic{font-style:italic}.underline-offset-4{text-underline-offset:4px}.opacity-0{opacity:0}.opacity-50{opacity:.5}.opacity-60{opacity:.6}.opacity-70{opacity:.7}.opacity-80{opacity:.8}.opacity-100{opacity:1}.shadow{--tw-shadow:0 1px 3px 0 var(--tw-shadow-color,#0000001a),0 1px 2px -1px var(--tw-shadow-color,#0000001a);box-shadow:var(--tw-inset-shadow),var(--tw-inset-ring-shadow),var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow)}.shadow-lg{--tw-shadow:0 10px 15px -3px var(--tw-shadow-color,#0000001a),0 4px 6px -4px var(--tw-shadow-color,#0000001a);box-shadow:var(--tw-inset-shadow),var(--tw-inset-ring-shadow),var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow)}.shadow-md{--tw-shadow:0 4px 6px -1px var(--tw-shadow-color,#0000001a),0 2px 4px -2px var(--tw-shadow-color,#0000001a);box-shadow:var(--tw-inset-shadow),var(--tw-inset-ring-shadow),var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow)}.shadow-sm{--tw-shadow:0 1px 3px 0 var(--tw-shadow-color,#0000001a),0 1px 2px -1px var(--tw-shadow-color,#0000001a);box-shadow:var(--tw-inset-shadow),var(--tw-inset-ring-shadow),var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow)}.shadow-xs{--tw-shadow:0 1px 2px 0 var(--tw-shadow-color,#0000000d);box-shadow:var(--tw-inset-shadow),var(--tw-inset-ring-shadow),var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow)}.ring{--tw-ring-shadow:var(--tw-ring-inset,)0 0 0 calc(1px + var(--tw-ring-offset-width))var(--tw-ring-color,currentcolor);box-shadow:var(--tw-inset-shadow),var(--tw-inset-ring-shadow),var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow)}.ring-2{--tw-ring-shadow:var(--tw-ring-inset,)0 0 0 calc(2px + var(--tw-ring-offset-width))var(--tw-ring-color,currentcolor);box-shadow:var(--tw-inset-shadow),var(--tw-inset-ring-shadow),var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow)}.shadow-\[\#643FB2\]\/20{--tw-shadow-color:#643fb233}@supports (color:color-mix(in lab,red,red)){.shadow-\[\#643FB2\]\/20{--tw-shadow-color:color-mix(in oklab,oklab(47.4316% .069152 -.159147/.2) var(--tw-shadow-alpha),transparent)}}.shadow-green-500\/20{--tw-shadow-color:#00c75833}@supports (color:color-mix(in lab,red,red)){.shadow-green-500\/20{--tw-shadow-color:color-mix(in oklab,color-mix(in oklab,var(--color-green-500)20%,transparent)var(--tw-shadow-alpha),transparent)}}.shadow-orange-500\/20{--tw-shadow-color:#fe6e0033}@supports (color:color-mix(in lab,red,red)){.shadow-orange-500\/20{--tw-shadow-color:color-mix(in oklab,color-mix(in oklab,var(--color-orange-500)20%,transparent)var(--tw-shadow-alpha),transparent)}}.shadow-primary\/25{--tw-shadow-color:var(--primary)}@supports (color:color-mix(in lab,red,red)){.shadow-primary\/25{--tw-shadow-color:color-mix(in oklab,color-mix(in oklab,var(--primary)25%,transparent)var(--tw-shadow-alpha),transparent)}}.shadow-red-500\/20{--tw-shadow-color:#fb2c3633}@supports (color:color-mix(in lab,red,red)){.shadow-red-500\/20{--tw-shadow-color:color-mix(in oklab,color-mix(in oklab,var(--color-red-500)20%,transparent)var(--tw-shadow-alpha),transparent)}}.ring-blue-500{--tw-ring-color:var(--color-blue-500)}.ring-offset-2{--tw-ring-offset-width:2px;--tw-ring-offset-shadow:var(--tw-ring-inset,)0 0 0 var(--tw-ring-offset-width)var(--tw-ring-offset-color)}.ring-offset-background{--tw-ring-offset-color:var(--background)}.outline-hidden{--tw-outline-style:none;outline-style:none}@media (forced-colors:active){.outline-hidden{outline-offset:2px;outline:2px solid #0000}}.outline{outline-style:var(--tw-outline-style);outline-width:1px}.drop-shadow-lg{--tw-drop-shadow-size:drop-shadow(0 4px 4px var(--tw-drop-shadow-color,#00000026));--tw-drop-shadow:drop-shadow(var(--drop-shadow-lg));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-sm{--tw-backdrop-blur:blur(var(--blur-sm));-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,outline-color,text-decoration-color,fill,stroke,--tw-gradient-from,--tw-gradient-via,--tw-gradient-to,opacity,box-shadow,transform,translate,scale,rotate,filter,-webkit-backdrop-filter,backdrop-filter,display,visibility,content-visibility,overlay,pointer-events;transition-timing-function:var(--tw-ease,var(--default-transition-timing-function));transition-duration:var(--tw-duration,var(--default-transition-duration))}.transition-\[color\,box-shadow\]{transition-property:color,box-shadow;transition-timing-function:var(--tw-ease,var(--default-transition-timing-function));transition-duration:var(--tw-duration,var(--default-transition-duration))}.transition-all{transition-property:all;transition-timing-function:var(--tw-ease,var(--default-transition-timing-function));transition-duration:var(--tw-duration,var(--default-transition-duration))}.transition-colors{transition-property:color,background-color,border-color,outline-color,text-decoration-color,fill,stroke,--tw-gradient-from,--tw-gradient-via,--tw-gradient-to;transition-timing-function:var(--tw-ease,var(--default-transition-timing-function));transition-duration:var(--tw-duration,var(--default-transition-duration))}.transition-opacity{transition-property:opacity;transition-timing-function:var(--tw-ease,var(--default-transition-timing-function));transition-duration:var(--tw-duration,var(--default-transition-duration))}.transition-shadow{transition-property:box-shadow;transition-timing-function:var(--tw-ease,var(--default-transition-timing-function));transition-duration:var(--tw-duration,var(--default-transition-duration))}.transition-transform{transition-property:transform,translate,scale,rotate;transition-timing-function:var(--tw-ease,var(--default-transition-timing-function));transition-duration:var(--tw-duration,var(--default-transition-duration))}.transition-none{transition-property:none}.duration-200{--tw-duration:.2s;transition-duration:.2s}.duration-300{--tw-duration:.3s;transition-duration:.3s}.ease-in-out{--tw-ease:var(--ease-in-out);transition-timing-function:var(--ease-in-out)}.outline-none{--tw-outline-style:none;outline-style:none}.select-none{-webkit-user-select:none;user-select:none}.\[animation-delay\:-0\.3s\]{animation-delay:-.3s}.\[animation-delay\:-0\.15s\]{animation-delay:-.15s}.fade-in{--tw-enter-opacity:0}.running{animation-play-state:running}.slide-in-from-bottom-2{--tw-enter-translate-y:calc(2*var(--spacing))}.group-open\:rotate-180:is(:where(.group):is([open],:popover-open,:open) *){rotate:180deg}@media (hover:hover){.group-hover\:bg-primary:is(:where(.group):hover *){background-color:var(--primary)}.group-hover\:opacity-100:is(:where(.group):hover *){opacity:1}.group-hover\:shadow-md:is(:where(.group):hover *){--tw-shadow:0 4px 6px -1px var(--tw-shadow-color,#0000001a),0 2px 4px -2px var(--tw-shadow-color,#0000001a);box-shadow:var(--tw-inset-shadow),var(--tw-inset-ring-shadow),var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow)}.group-hover\:shadow-primary\/20:is(:where(.group):hover *){--tw-shadow-color:var(--primary)}@supports (color:color-mix(in lab,red,red)){.group-hover\:shadow-primary\/20:is(:where(.group):hover *){--tw-shadow-color:color-mix(in oklab,color-mix(in oklab,var(--primary)20%,transparent)var(--tw-shadow-alpha),transparent)}}}.group-data-\[disabled\=true\]\:pointer-events-none:is(:where(.group)[data-disabled=true] *){pointer-events:none}.group-data-\[disabled\=true\]\:opacity-50:is(:where(.group)[data-disabled=true] *){opacity:.5}.peer-disabled\:cursor-not-allowed:is(:where(.peer):disabled~*){cursor:not-allowed}.peer-disabled\:opacity-50:is(:where(.peer):disabled~*){opacity:.5}.selection\:bg-primary ::selection{background-color:var(--primary)}.selection\:bg-primary::selection{background-color:var(--primary)}.selection\:text-primary-foreground ::selection{color:var(--primary-foreground)}.selection\:text-primary-foreground::selection{color:var(--primary-foreground)}.file\:inline-flex::file-selector-button{display:inline-flex}.file\:h-7::file-selector-button{height:calc(var(--spacing)*7)}.file\:border-0::file-selector-button{border-style:var(--tw-border-style);border-width:0}.file\:bg-transparent::file-selector-button{background-color:#0000}.file\:text-sm::file-selector-button{font-size:var(--text-sm);line-height:var(--tw-leading,var(--text-sm--line-height))}.file\:font-medium::file-selector-button{--tw-font-weight:var(--font-weight-medium);font-weight:var(--font-weight-medium)}.file\:text-foreground::file-selector-button{color:var(--foreground)}.placeholder\:text-muted-foreground::placeholder{color:var(--muted-foreground)}.first\:mt-0:first-child{margin-top:calc(var(--spacing)*0)}.last\:border-r-0:last-child{border-right-style:var(--tw-border-style);border-right-width:0}.last\:border-b-0:last-child{border-bottom-style:var(--tw-border-style);border-bottom-width:0}@media (hover:hover){.hover\:bg-\[\#643FB2\]\/10:hover{background-color:#643fb21a}.hover\:bg-accent:hover{background-color:var(--accent)}.hover\:bg-amber-100:hover{background-color:var(--color-amber-100)}.hover\:bg-blue-500\/10:hover{background-color:#3080ff1a}@supports (color:color-mix(in lab,red,red)){.hover\:bg-blue-500\/10:hover{background-color:color-mix(in oklab,var(--color-blue-500)10%,transparent)}}.hover\:bg-destructive\/20:hover{background-color:var(--destructive)}@supports (color:color-mix(in lab,red,red)){.hover\:bg-destructive\/20:hover{background-color:color-mix(in oklab,var(--destructive)20%,transparent)}}.hover\:bg-destructive\/80:hover{background-color:var(--destructive)}@supports (color:color-mix(in lab,red,red)){.hover\:bg-destructive\/80:hover{background-color:color-mix(in oklab,var(--destructive)80%,transparent)}}.hover\:bg-destructive\/90:hover{background-color:var(--destructive)}@supports (color:color-mix(in lab,red,red)){.hover\:bg-destructive\/90:hover{background-color:color-mix(in oklab,var(--destructive)90%,transparent)}}.hover\:bg-green-500\/10:hover{background-color:#00c7581a}@supports (color:color-mix(in lab,red,red)){.hover\:bg-green-500\/10:hover{background-color:color-mix(in oklab,var(--color-green-500)10%,transparent)}}.hover\:bg-muted:hover,.hover\:bg-muted\/30:hover{background-color:var(--muted)}@supports (color:color-mix(in lab,red,red)){.hover\:bg-muted\/30:hover{background-color:color-mix(in oklab,var(--muted)30%,transparent)}}.hover\:bg-muted\/50:hover{background-color:var(--muted)}@supports (color:color-mix(in lab,red,red)){.hover\:bg-muted\/50:hover{background-color:color-mix(in oklab,var(--muted)50%,transparent)}}.hover\:bg-primary\/20:hover{background-color:var(--primary)}@supports (color:color-mix(in lab,red,red)){.hover\:bg-primary\/20:hover{background-color:color-mix(in oklab,var(--primary)20%,transparent)}}.hover\:bg-primary\/80:hover{background-color:var(--primary)}@supports (color:color-mix(in lab,red,red)){.hover\:bg-primary\/80:hover{background-color:color-mix(in oklab,var(--primary)80%,transparent)}}.hover\:bg-primary\/90:hover{background-color:var(--primary)}@supports (color:color-mix(in lab,red,red)){.hover\:bg-primary\/90:hover{background-color:color-mix(in oklab,var(--primary)90%,transparent)}}.hover\:bg-secondary\/80:hover{background-color:var(--secondary)}@supports (color:color-mix(in lab,red,red)){.hover\:bg-secondary\/80:hover{background-color:color-mix(in oklab,var(--secondary)80%,transparent)}}.hover\:bg-white:hover{background-color:var(--color-white)}.hover\:text-accent-foreground:hover{color:var(--accent-foreground)}.hover\:text-foreground:hover{color:var(--foreground)}.hover\:underline:hover{text-decoration-line:underline}.hover\:opacity-70:hover{opacity:.7}.hover\:opacity-100:hover{opacity:1}.hover\:shadow-md:hover{--tw-shadow:0 4px 6px -1px var(--tw-shadow-color,#0000001a),0 2px 4px -2px var(--tw-shadow-color,#0000001a);box-shadow:var(--tw-inset-shadow),var(--tw-inset-ring-shadow),var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow)}}.focus\:bg-accent:focus{background-color:var(--accent)}.focus\:text-accent-foreground:focus{color:var(--accent-foreground)}.focus\:ring-2:focus{--tw-ring-shadow:var(--tw-ring-inset,)0 0 0 calc(2px + var(--tw-ring-offset-width))var(--tw-ring-color,currentcolor);box-shadow:var(--tw-inset-shadow),var(--tw-inset-ring-shadow),var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow)}.focus\:ring-ring:focus{--tw-ring-color:var(--ring)}.focus\:ring-offset-2:focus{--tw-ring-offset-width:2px;--tw-ring-offset-shadow:var(--tw-ring-inset,)0 0 0 var(--tw-ring-offset-width)var(--tw-ring-offset-color)}.focus\:outline-none:focus{--tw-outline-style:none;outline-style:none}.focus-visible\:border-ring:focus-visible{border-color:var(--ring)}.focus-visible\:ring-2:focus-visible{--tw-ring-shadow:var(--tw-ring-inset,)0 0 0 calc(2px + var(--tw-ring-offset-width))var(--tw-ring-color,currentcolor);box-shadow:var(--tw-inset-shadow),var(--tw-inset-ring-shadow),var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow)}.focus-visible\:ring-\[3px\]:focus-visible{--tw-ring-shadow:var(--tw-ring-inset,)0 0 0 calc(3px + var(--tw-ring-offset-width))var(--tw-ring-color,currentcolor);box-shadow:var(--tw-inset-shadow),var(--tw-inset-ring-shadow),var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow)}.focus-visible\:ring-destructive\/20:focus-visible{--tw-ring-color:var(--destructive)}@supports (color:color-mix(in lab,red,red)){.focus-visible\:ring-destructive\/20:focus-visible{--tw-ring-color:color-mix(in oklab,var(--destructive)20%,transparent)}}.focus-visible\:ring-ring:focus-visible,.focus-visible\:ring-ring\/50:focus-visible{--tw-ring-color:var(--ring)}@supports (color:color-mix(in lab,red,red)){.focus-visible\:ring-ring\/50:focus-visible{--tw-ring-color:color-mix(in oklab,var(--ring)50%,transparent)}}.focus-visible\:ring-offset-2:focus-visible{--tw-ring-offset-width:2px;--tw-ring-offset-shadow:var(--tw-ring-inset,)0 0 0 var(--tw-ring-offset-width)var(--tw-ring-offset-color)}.focus-visible\:outline-none:focus-visible{--tw-outline-style:none;outline-style:none}.disabled\:pointer-events-none:disabled{pointer-events:none}.disabled\:cursor-not-allowed:disabled{cursor:not-allowed}.disabled\:opacity-50:disabled{opacity:.5}.has-data-\[slot\=card-action\]\:grid-cols-\[1fr_auto\]:has([data-slot=card-action]){grid-template-columns:1fr auto}.has-\[\>svg\]\:px-2\.5:has(>svg){padding-inline:calc(var(--spacing)*2.5)}.has-\[\>svg\]\:px-3:has(>svg){padding-inline:calc(var(--spacing)*3)}.has-\[\>svg\]\:px-4:has(>svg){padding-inline:calc(var(--spacing)*4)}.aria-invalid\:border-destructive[aria-invalid=true]{border-color:var(--destructive)}.aria-invalid\:ring-destructive\/20[aria-invalid=true]{--tw-ring-color:var(--destructive)}@supports (color:color-mix(in lab,red,red)){.aria-invalid\:ring-destructive\/20[aria-invalid=true]{--tw-ring-color:color-mix(in oklab,var(--destructive)20%,transparent)}}.data-\[disabled\]\:pointer-events-none[data-disabled]{pointer-events:none}.data-\[disabled\]\:opacity-50[data-disabled]{opacity:.5}.data-\[inset\]\:pl-8[data-inset]{padding-left:calc(var(--spacing)*8)}.data-\[placeholder\]\:text-muted-foreground[data-placeholder]{color:var(--muted-foreground)}.data-\[side\=bottom\]\:translate-y-1[data-side=bottom]{--tw-translate-y:calc(var(--spacing)*1);translate:var(--tw-translate-x)var(--tw-translate-y)}.data-\[side\=bottom\]\:slide-in-from-top-2[data-side=bottom]{--tw-enter-translate-y:calc(2*var(--spacing)*-1)}.data-\[side\=left\]\:-translate-x-1[data-side=left]{--tw-translate-x:calc(var(--spacing)*-1);translate:var(--tw-translate-x)var(--tw-translate-y)}.data-\[side\=left\]\:slide-in-from-right-2[data-side=left]{--tw-enter-translate-x:calc(2*var(--spacing))}.data-\[side\=right\]\:translate-x-1[data-side=right]{--tw-translate-x:calc(var(--spacing)*1);translate:var(--tw-translate-x)var(--tw-translate-y)}.data-\[side\=right\]\:slide-in-from-left-2[data-side=right]{--tw-enter-translate-x:calc(2*var(--spacing)*-1)}.data-\[side\=top\]\:-translate-y-1[data-side=top]{--tw-translate-y:calc(var(--spacing)*-1);translate:var(--tw-translate-x)var(--tw-translate-y)}.data-\[side\=top\]\:slide-in-from-bottom-2[data-side=top]{--tw-enter-translate-y:calc(2*var(--spacing))}.data-\[size\=default\]\:h-9[data-size=default]{height:calc(var(--spacing)*9)}.data-\[size\=sm\]\:h-8[data-size=sm]{height:calc(var(--spacing)*8)}:is(.\*\:data-\[slot\=select-value\]\:line-clamp-1>*)[data-slot=select-value]{-webkit-line-clamp:1;-webkit-box-orient:vertical;display:-webkit-box;overflow:hidden}:is(.\*\:data-\[slot\=select-value\]\:flex>*)[data-slot=select-value]{display:flex}:is(.\*\:data-\[slot\=select-value\]\:items-center>*)[data-slot=select-value]{align-items:center}:is(.\*\:data-\[slot\=select-value\]\:gap-2>*)[data-slot=select-value]{gap:calc(var(--spacing)*2)}.data-\[state\=active\]\:bg-background[data-state=active]{background-color:var(--background)}.data-\[state\=active\]\:text-foreground[data-state=active]{color:var(--foreground)}.data-\[state\=active\]\:shadow[data-state=active]{--tw-shadow:0 1px 3px 0 var(--tw-shadow-color,#0000001a),0 1px 2px -1px var(--tw-shadow-color,#0000001a);box-shadow:var(--tw-inset-shadow),var(--tw-inset-ring-shadow),var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow)}.data-\[state\=checked\]\:border-primary[data-state=checked]{border-color:var(--primary)}.data-\[state\=checked\]\:bg-primary[data-state=checked]{background-color:var(--primary)}.data-\[state\=checked\]\:text-primary-foreground[data-state=checked]{color:var(--primary-foreground)}.data-\[state\=closed\]\:animate-out[data-state=closed]{animation:exit var(--tw-animation-duration,var(--tw-duration,.15s))var(--tw-ease,ease)var(--tw-animation-delay,0s)var(--tw-animation-iteration-count,1)var(--tw-animation-direction,normal)var(--tw-animation-fill-mode,none)}.data-\[state\=closed\]\:fade-out-0[data-state=closed]{--tw-exit-opacity:0}.data-\[state\=closed\]\:zoom-out-95[data-state=closed]{--tw-exit-scale:.95}.data-\[state\=open\]\:animate-in[data-state=open]{animation:enter var(--tw-animation-duration,var(--tw-duration,.15s))var(--tw-ease,ease)var(--tw-animation-delay,0s)var(--tw-animation-iteration-count,1)var(--tw-animation-direction,normal)var(--tw-animation-fill-mode,none)}.data-\[state\=open\]\:bg-accent[data-state=open]{background-color:var(--accent)}.data-\[state\=open\]\:text-accent-foreground[data-state=open]{color:var(--accent-foreground)}.data-\[state\=open\]\:fade-in-0[data-state=open]{--tw-enter-opacity:0}.data-\[state\=open\]\:zoom-in-95[data-state=open]{--tw-enter-scale:.95}.data-\[variant\=destructive\]\:text-destructive[data-variant=destructive]{color:var(--destructive)}.data-\[variant\=destructive\]\:focus\:bg-destructive\/10[data-variant=destructive]:focus{background-color:var(--destructive)}@supports (color:color-mix(in lab,red,red)){.data-\[variant\=destructive\]\:focus\:bg-destructive\/10[data-variant=destructive]:focus{background-color:color-mix(in oklab,var(--destructive)10%,transparent)}}.data-\[variant\=destructive\]\:focus\:text-destructive[data-variant=destructive]:focus{color:var(--destructive)}@media (min-width:40rem){.sm\:col-span-2{grid-column:span 2/span 2}.sm\:w-64{width:calc(var(--spacing)*64)}.sm\:max-w-lg{max-width:var(--container-lg)}.sm\:flex-none{flex:none}.sm\:grid-cols-2{grid-template-columns:repeat(2,minmax(0,1fr))}.sm\:flex-row{flex-direction:row}.sm\:items-center{align-items:center}}@media (min-width:48rem){.md\:col-span-2{grid-column:span 2/span 2}.md\:col-start-2{grid-column-start:2}.md\:inline{display:inline}.md\:max-w-2xl{max-width:var(--container-2xl)}.md\:grid-cols-2{grid-template-columns:repeat(2,minmax(0,1fr))}.md\:gap-8{gap:calc(var(--spacing)*8)}.md\:text-sm{font-size:var(--text-sm);line-height:var(--tw-leading,var(--text-sm--line-height))}}@media (min-width:64rem){.lg\:col-span-3{grid-column:span 3/span 3}.lg\:max-w-4xl{max-width:var(--container-4xl)}.lg\:grid-cols-3{grid-template-columns:repeat(3,minmax(0,1fr))}.lg\:flex-row{flex-direction:row}.lg\:items-center{align-items:center}.lg\:justify-between{justify-content:space-between}}@media (min-width:80rem){.xl\:col-span-2{grid-column:span 2/span 2}.xl\:col-span-4{grid-column:span 4/span 4}.xl\:max-w-5xl{max-width:var(--container-5xl)}.xl\:grid-cols-4{grid-template-columns:repeat(4,minmax(0,1fr))}}.dark\:scale-0:is(.dark *){--tw-scale-x:0%;--tw-scale-y:0%;--tw-scale-z:0%;scale:var(--tw-scale-x)var(--tw-scale-y)}.dark\:scale-100:is(.dark *){--tw-scale-x:100%;--tw-scale-y:100%;--tw-scale-z:100%;scale:var(--tw-scale-x)var(--tw-scale-y)}.dark\:-rotate-90:is(.dark *){rotate:-90deg}.dark\:rotate-0:is(.dark *){rotate:none}.dark\:\!border-gray-500:is(.dark *){border-color:var(--color-gray-500)!important}.dark\:\!border-gray-600:is(.dark *){border-color:var(--color-gray-600)!important}.dark\:border-\[\#8B5CF6\]:is(.dark *){border-color:#8b5cf6}.dark\:border-\[\#8B5CF6\]\/30:is(.dark *){border-color:#8b5cf64d}.dark\:border-\[\#8B5CF6\]\/40:is(.dark *){border-color:#8b5cf666}.dark\:border-amber-800:is(.dark *){border-color:var(--color-amber-800)}.dark\:border-amber-900:is(.dark *){border-color:var(--color-amber-900)}.dark\:border-blue-500:is(.dark *){border-color:var(--color-blue-500)}.dark\:border-blue-500\/30:is(.dark *){border-color:#3080ff4d}@supports (color:color-mix(in lab,red,red)){.dark\:border-blue-500\/30:is(.dark *){border-color:color-mix(in oklab,var(--color-blue-500)30%,transparent)}}.dark\:border-blue-500\/40:is(.dark *){border-color:#3080ff66}@supports (color:color-mix(in lab,red,red)){.dark\:border-blue-500\/40:is(.dark *){border-color:color-mix(in oklab,var(--color-blue-500)40%,transparent)}}.dark\:border-blue-600:is(.dark *){border-color:var(--color-blue-600)}.dark\:border-blue-800:is(.dark *){border-color:var(--color-blue-800)}.dark\:border-emerald-600:is(.dark *){border-color:var(--color-emerald-600)}.dark\:border-gray-600:is(.dark *){border-color:var(--color-gray-600)}.dark\:border-gray-700:is(.dark *){border-color:var(--color-gray-700)}.dark\:border-green-400:is(.dark *){border-color:var(--color-green-400)}.dark\:border-green-400\/30:is(.dark *){border-color:#05df724d}@supports (color:color-mix(in lab,red,red)){.dark\:border-green-400\/30:is(.dark *){border-color:color-mix(in oklab,var(--color-green-400)30%,transparent)}}.dark\:border-green-400\/40:is(.dark *){border-color:#05df7266}@supports (color:color-mix(in lab,red,red)){.dark\:border-green-400\/40:is(.dark *){border-color:color-mix(in oklab,var(--color-green-400)40%,transparent)}}.dark\:border-green-800:is(.dark *){border-color:var(--color-green-800)}.dark\:border-input:is(.dark *){border-color:var(--input)}.dark\:border-orange-400:is(.dark *){border-color:var(--color-orange-400)}.dark\:border-orange-800:is(.dark *){border-color:var(--color-orange-800)}.dark\:border-red-400:is(.dark *){border-color:var(--color-red-400)}.dark\:border-red-800:is(.dark *){border-color:var(--color-red-800)}.dark\:\!bg-gray-800\/90:is(.dark *){background-color:#1e2939e6!important}@supports (color:color-mix(in lab,red,red)){.dark\:\!bg-gray-800\/90:is(.dark *){background-color:color-mix(in oklab,var(--color-gray-800)90%,transparent)!important}}.dark\:bg-\[\#8B5CF6\]:is(.dark *){background-color:#8b5cf6}.dark\:bg-\[\#8B5CF6\]\/5:is(.dark *){background-color:#8b5cf60d}.dark\:bg-\[\#8B5CF6\]\/10:is(.dark *){background-color:#8b5cf61a}.dark\:bg-amber-950\/20:is(.dark *){background-color:#46190133}@supports (color:color-mix(in lab,red,red)){.dark\:bg-amber-950\/20:is(.dark *){background-color:color-mix(in oklab,var(--color-amber-950)20%,transparent)}}.dark\:bg-amber-950\/50:is(.dark *){background-color:#46190180}@supports (color:color-mix(in lab,red,red)){.dark\:bg-amber-950\/50:is(.dark *){background-color:color-mix(in oklab,var(--color-amber-950)50%,transparent)}}.dark\:bg-background:is(.dark *){background-color:var(--background)}.dark\:bg-blue-500\/5:is(.dark *){background-color:#3080ff0d}@supports (color:color-mix(in lab,red,red)){.dark\:bg-blue-500\/5:is(.dark *){background-color:color-mix(in oklab,var(--color-blue-500)5%,transparent)}}.dark\:bg-blue-500\/10:is(.dark *){background-color:#3080ff1a}@supports (color:color-mix(in lab,red,red)){.dark\:bg-blue-500\/10:is(.dark *){background-color:color-mix(in oklab,var(--color-blue-500)10%,transparent)}}.dark\:bg-blue-900:is(.dark *){background-color:var(--color-blue-900)}.dark\:bg-blue-900\/50:is(.dark *){background-color:#1c398e80}@supports (color:color-mix(in lab,red,red)){.dark\:bg-blue-900\/50:is(.dark *){background-color:color-mix(in oklab,var(--color-blue-900)50%,transparent)}}.dark\:bg-blue-950\/20:is(.dark *){background-color:#16245633}@supports (color:color-mix(in lab,red,red)){.dark\:bg-blue-950\/20:is(.dark *){background-color:color-mix(in oklab,var(--color-blue-950)20%,transparent)}}.dark\:bg-blue-950\/40:is(.dark *){background-color:#16245666}@supports (color:color-mix(in lab,red,red)){.dark\:bg-blue-950\/40:is(.dark *){background-color:color-mix(in oklab,var(--color-blue-950)40%,transparent)}}.dark\:bg-blue-950\/50:is(.dark *){background-color:#16245680}@supports (color:color-mix(in lab,red,red)){.dark\:bg-blue-950\/50:is(.dark *){background-color:color-mix(in oklab,var(--color-blue-950)50%,transparent)}}.dark\:bg-card:is(.dark *){background-color:var(--card)}.dark\:bg-destructive\/20:is(.dark *){background-color:var(--destructive)}@supports (color:color-mix(in lab,red,red)){.dark\:bg-destructive\/20:is(.dark *){background-color:color-mix(in oklab,var(--destructive)20%,transparent)}}.dark\:bg-destructive\/60:is(.dark *){background-color:var(--destructive)}@supports (color:color-mix(in lab,red,red)){.dark\:bg-destructive\/60:is(.dark *){background-color:color-mix(in oklab,var(--destructive)60%,transparent)}}.dark\:bg-emerald-900\/50:is(.dark *){background-color:#004e3b80}@supports (color:color-mix(in lab,red,red)){.dark\:bg-emerald-900\/50:is(.dark *){background-color:color-mix(in oklab,var(--color-emerald-900)50%,transparent)}}.dark\:bg-emerald-950\/50:is(.dark *){background-color:#002c2280}@supports (color:color-mix(in lab,red,red)){.dark\:bg-emerald-950\/50:is(.dark *){background-color:color-mix(in oklab,var(--color-emerald-950)50%,transparent)}}.dark\:bg-foreground\/10:is(.dark *){background-color:var(--foreground)}@supports (color:color-mix(in lab,red,red)){.dark\:bg-foreground\/10:is(.dark *){background-color:color-mix(in oklab,var(--foreground)10%,transparent)}}.dark\:bg-gray-500:is(.dark *){background-color:var(--color-gray-500)}.dark\:bg-gray-800:is(.dark *){background-color:var(--color-gray-800)}.dark\:bg-gray-800\/90:is(.dark *){background-color:#1e2939e6}@supports (color:color-mix(in lab,red,red)){.dark\:bg-gray-800\/90:is(.dark *){background-color:color-mix(in oklab,var(--color-gray-800)90%,transparent)}}.dark\:bg-gray-900:is(.dark *){background-color:var(--color-gray-900)}.dark\:bg-green-400:is(.dark *){background-color:var(--color-green-400)}.dark\:bg-green-400\/5:is(.dark *){background-color:#05df720d}@supports (color:color-mix(in lab,red,red)){.dark\:bg-green-400\/5:is(.dark *){background-color:color-mix(in oklab,var(--color-green-400)5%,transparent)}}.dark\:bg-green-400\/10:is(.dark *){background-color:#05df721a}@supports (color:color-mix(in lab,red,red)){.dark\:bg-green-400\/10:is(.dark *){background-color:color-mix(in oklab,var(--color-green-400)10%,transparent)}}.dark\:bg-green-900:is(.dark *){background-color:var(--color-green-900)}.dark\:bg-green-950:is(.dark *){background-color:var(--color-green-950)}.dark\:bg-green-950\/20:is(.dark *){background-color:#032e1533}@supports (color:color-mix(in lab,red,red)){.dark\:bg-green-950\/20:is(.dark *){background-color:color-mix(in oklab,var(--color-green-950)20%,transparent)}}.dark\:bg-green-950\/50:is(.dark *){background-color:#032e1580}@supports (color:color-mix(in lab,red,red)){.dark\:bg-green-950\/50:is(.dark *){background-color:color-mix(in oklab,var(--color-green-950)50%,transparent)}}.dark\:bg-input\/30:is(.dark *){background-color:var(--input)}@supports (color:color-mix(in lab,red,red)){.dark\:bg-input\/30:is(.dark *){background-color:color-mix(in oklab,var(--input)30%,transparent)}}.dark\:bg-orange-400:is(.dark *){background-color:var(--color-orange-400)}.dark\:bg-orange-900:is(.dark *){background-color:var(--color-orange-900)}.dark\:bg-orange-950:is(.dark *){background-color:var(--color-orange-950)}.dark\:bg-orange-950\/50:is(.dark *){background-color:#44130680}@supports (color:color-mix(in lab,red,red)){.dark\:bg-orange-950\/50:is(.dark *){background-color:color-mix(in oklab,var(--color-orange-950)50%,transparent)}}.dark\:bg-purple-900:is(.dark *){background-color:var(--color-purple-900)}.dark\:bg-red-400:is(.dark *){background-color:var(--color-red-400)}.dark\:bg-red-900:is(.dark *){background-color:var(--color-red-900)}.dark\:bg-red-950:is(.dark *){background-color:var(--color-red-950)}.dark\:bg-red-950\/20:is(.dark *){background-color:#46080933}@supports (color:color-mix(in lab,red,red)){.dark\:bg-red-950\/20:is(.dark *){background-color:color-mix(in oklab,var(--color-red-950)20%,transparent)}}.dark\:text-\[\#8B5CF6\]:is(.dark *){color:#8b5cf6}.dark\:text-amber-100:is(.dark *){color:var(--color-amber-100)}.dark\:text-amber-200:is(.dark *){color:var(--color-amber-200)}.dark\:text-amber-300:is(.dark *){color:var(--color-amber-300)}.dark\:text-amber-400:is(.dark *){color:var(--color-amber-400)}.dark\:text-amber-500:is(.dark *){color:var(--color-amber-500)}.dark\:text-blue-200:is(.dark *){color:var(--color-blue-200)}.dark\:text-blue-300:is(.dark *){color:var(--color-blue-300)}.dark\:text-blue-400:is(.dark *){color:var(--color-blue-400)}.dark\:text-blue-500:is(.dark *){color:var(--color-blue-500)}.dark\:text-emerald-200:is(.dark *){color:var(--color-emerald-200)}.dark\:text-emerald-300:is(.dark *){color:var(--color-emerald-300)}.dark\:text-emerald-400:is(.dark *){color:var(--color-emerald-400)}.dark\:text-gray-100:is(.dark *){color:var(--color-gray-100)}.dark\:text-gray-300:is(.dark *){color:var(--color-gray-300)}.dark\:text-gray-400:is(.dark *){color:var(--color-gray-400)}.dark\:text-green-200:is(.dark *){color:var(--color-green-200)}.dark\:text-green-300:is(.dark *){color:var(--color-green-300)}.dark\:text-green-400:is(.dark *){color:var(--color-green-400)}.dark\:text-orange-200:is(.dark *){color:var(--color-orange-200)}.dark\:text-orange-400:is(.dark *){color:var(--color-orange-400)}.dark\:text-purple-400:is(.dark *){color:var(--color-purple-400)}.dark\:text-red-200:is(.dark *){color:var(--color-red-200)}.dark\:text-red-400:is(.dark *){color:var(--color-red-400)}.dark\:text-yellow-400:is(.dark *){color:var(--color-yellow-400)}.dark\:opacity-30:is(.dark *){opacity:.3}@media (hover:hover){.dark\:hover\:bg-\[\#8B5CF6\]\/10:is(.dark *):hover{background-color:#8b5cf61a}.dark\:hover\:bg-accent\/50:is(.dark *):hover{background-color:var(--accent)}@supports (color:color-mix(in lab,red,red)){.dark\:hover\:bg-accent\/50:is(.dark *):hover{background-color:color-mix(in oklab,var(--accent)50%,transparent)}}.dark\:hover\:bg-amber-950\/30:is(.dark *):hover{background-color:#4619014d}@supports (color:color-mix(in lab,red,red)){.dark\:hover\:bg-amber-950\/30:is(.dark *):hover{background-color:color-mix(in oklab,var(--color-amber-950)30%,transparent)}}.dark\:hover\:bg-blue-500\/10:is(.dark *):hover{background-color:#3080ff1a}@supports (color:color-mix(in lab,red,red)){.dark\:hover\:bg-blue-500\/10:is(.dark *):hover{background-color:color-mix(in oklab,var(--color-blue-500)10%,transparent)}}.dark\:hover\:bg-destructive\/30:is(.dark *):hover{background-color:var(--destructive)}@supports (color:color-mix(in lab,red,red)){.dark\:hover\:bg-destructive\/30:is(.dark *):hover{background-color:color-mix(in oklab,var(--destructive)30%,transparent)}}.dark\:hover\:bg-gray-800:is(.dark *):hover{background-color:var(--color-gray-800)}.dark\:hover\:bg-green-400\/10:is(.dark *):hover{background-color:#05df721a}@supports (color:color-mix(in lab,red,red)){.dark\:hover\:bg-green-400\/10:is(.dark *):hover{background-color:color-mix(in oklab,var(--color-green-400)10%,transparent)}}.dark\:hover\:bg-input\/50:is(.dark *):hover{background-color:var(--input)}@supports (color:color-mix(in lab,red,red)){.dark\:hover\:bg-input\/50:is(.dark *):hover{background-color:color-mix(in oklab,var(--input)50%,transparent)}}}.dark\:focus-visible\:ring-destructive\/40:is(.dark *):focus-visible{--tw-ring-color:var(--destructive)}@supports (color:color-mix(in lab,red,red)){.dark\:focus-visible\:ring-destructive\/40:is(.dark *):focus-visible{--tw-ring-color:color-mix(in oklab,var(--destructive)40%,transparent)}}.dark\:aria-invalid\:ring-destructive\/40:is(.dark *)[aria-invalid=true]{--tw-ring-color:var(--destructive)}@supports (color:color-mix(in lab,red,red)){.dark\:aria-invalid\:ring-destructive\/40:is(.dark *)[aria-invalid=true]{--tw-ring-color:color-mix(in oklab,var(--destructive)40%,transparent)}}.dark\:data-\[state\=checked\]\:bg-primary:is(.dark *)[data-state=checked]{background-color:var(--primary)}.dark\:data-\[variant\=destructive\]\:focus\:bg-destructive\/20:is(.dark *)[data-variant=destructive]:focus{background-color:var(--destructive)}@supports (color:color-mix(in lab,red,red)){.dark\:data-\[variant\=destructive\]\:focus\:bg-destructive\/20:is(.dark *)[data-variant=destructive]:focus{background-color:color-mix(in oklab,var(--destructive)20%,transparent)}}.\[\&_p\]\:leading-relaxed p{--tw-leading:var(--leading-relaxed);line-height:var(--leading-relaxed)}.\[\&_svg\]\:pointer-events-none svg{pointer-events:none}.\[\&_svg\]\:shrink-0 svg{flex-shrink:0}.\[\&_svg\:not\(\[class\*\=\'size-\'\]\)\]\:size-4 svg:not([class*=size-]){width:calc(var(--spacing)*4);height:calc(var(--spacing)*4)}.\[\&_svg\:not\(\[class\*\=\'text-\'\]\)\]\:text-muted-foreground svg:not([class*=text-]){color:var(--muted-foreground)}.\[\.border-b\]\:pb-6.border-b{padding-bottom:calc(var(--spacing)*6)}.\[\.border-t\]\:pt-6.border-t{padding-top:calc(var(--spacing)*6)}:is(.\*\:\[span\]\:last\:flex>*):is(span):last-child{display:flex}:is(.\*\:\[span\]\:last\:items-center>*):is(span):last-child{align-items:center}:is(.\*\:\[span\]\:last\:gap-2>*):is(span):last-child{gap:calc(var(--spacing)*2)}:is(.data-\[variant\=destructive\]\:\*\:\[svg\]\:\!text-destructive[data-variant=destructive]>*):is(svg){color:var(--destructive)!important}.\[\&\>svg\]\:absolute>svg{position:absolute}.\[\&\>svg\]\:top-4>svg{top:calc(var(--spacing)*4)}.\[\&\>svg\]\:left-4>svg{left:calc(var(--spacing)*4)}.\[\&\>svg\]\:text-foreground>svg{color:var(--foreground)}.\[\&\>svg\+div\]\:translate-y-\[-3px\]>svg+div{--tw-translate-y:-3px;translate:var(--tw-translate-x)var(--tw-translate-y)}.\[\&\>svg\~\*\]\:pl-7>svg~*{padding-left:calc(var(--spacing)*7)}}@property --tw-animation-delay{syntax:"*";inherits:false;initial-value:0s}@property --tw-animation-direction{syntax:"*";inherits:false;initial-value:normal}@property --tw-animation-duration{syntax:"*";inherits:false}@property --tw-animation-fill-mode{syntax:"*";inherits:false;initial-value:none}@property --tw-animation-iteration-count{syntax:"*";inherits:false;initial-value:1}@property --tw-enter-blur{syntax:"*";inherits:false;initial-value:0}@property --tw-enter-opacity{syntax:"*";inherits:false;initial-value:1}@property --tw-enter-rotate{syntax:"*";inherits:false;initial-value:0}@property --tw-enter-scale{syntax:"*";inherits:false;initial-value:1}@property --tw-enter-translate-x{syntax:"*";inherits:false;initial-value:0}@property --tw-enter-translate-y{syntax:"*";inherits:false;initial-value:0}@property --tw-exit-blur{syntax:"*";inherits:false;initial-value:0}@property --tw-exit-opacity{syntax:"*";inherits:false;initial-value:1}@property --tw-exit-rotate{syntax:"*";inherits:false;initial-value:0}@property --tw-exit-scale{syntax:"*";inherits:false;initial-value:1}@property --tw-exit-translate-x{syntax:"*";inherits:false;initial-value:0}@property --tw-exit-translate-y{syntax:"*";inherits:false;initial-value:0}:root{--radius:.625rem;--background:oklch(100% 0 0);--foreground:oklch(14.5% 0 0);--card:oklch(100% 0 0);--card-foreground:oklch(14.5% 0 0);--popover:oklch(100% 0 0);--popover-foreground:oklch(14.5% 0 0);--primary:oklch(48% .18 290);--primary-foreground:oklch(98.5% 0 0);--secondary:oklch(97% 0 0);--secondary-foreground:oklch(20.5% 0 0);--muted:oklch(97% 0 0);--muted-foreground:oklch(55.6% 0 0);--accent:oklch(97% 0 0);--accent-foreground:oklch(20.5% 0 0);--destructive:oklch(57.7% .245 27.325);--border:oklch(92.2% 0 0);--input:oklch(92.2% 0 0);--ring:oklch(70.8% 0 0);--chart-1:oklch(64.6% .222 41.116);--chart-2:oklch(60% .118 184.704);--chart-3:oklch(39.8% .07 227.392);--chart-4:oklch(82.8% .189 84.429);--chart-5:oklch(76.9% .188 70.08);--sidebar:oklch(98.5% 0 0);--sidebar-foreground:oklch(14.5% 0 0);--sidebar-primary:oklch(20.5% 0 0);--sidebar-primary-foreground:oklch(98.5% 0 0);--sidebar-accent:oklch(97% 0 0);--sidebar-accent-foreground:oklch(20.5% 0 0);--sidebar-border:oklch(92.2% 0 0);--sidebar-ring:oklch(70.8% 0 0)}.dark{--background:oklch(14.5% 0 0);--foreground:oklch(98.5% 0 0);--card:oklch(20.5% 0 0);--card-foreground:oklch(98.5% 0 0);--popover:oklch(20.5% 0 0);--popover-foreground:oklch(98.5% 0 0);--primary:oklch(62% .2 290);--primary-foreground:oklch(98.5% 0 0);--secondary:oklch(26.9% 0 0);--secondary-foreground:oklch(98.5% 0 0);--muted:oklch(26.9% 0 0);--muted-foreground:oklch(70.8% 0 0);--accent:oklch(26.9% 0 0);--accent-foreground:oklch(98.5% 0 0);--destructive:oklch(70.4% .191 22.216);--border:oklch(100% 0 0/.1);--input:oklch(100% 0 0/.15);--ring:oklch(55.6% 0 0);--chart-1:oklch(48.8% .243 264.376);--chart-2:oklch(69.6% .17 162.48);--chart-3:oklch(76.9% .188 70.08);--chart-4:oklch(62.7% .265 303.9);--chart-5:oklch(64.5% .246 16.439);--sidebar:oklch(20.5% 0 0);--sidebar-foreground:oklch(98.5% 0 0);--sidebar-primary:oklch(48.8% .243 264.376);--sidebar-primary-foreground:oklch(98.5% 0 0);--sidebar-accent:oklch(26.9% 0 0);--sidebar-accent-foreground:oklch(98.5% 0 0);--sidebar-border:oklch(100% 0 0/.1);--sidebar-ring:oklch(55.6% 0 0)}.workflow-chat-view .border-green-200{border-color:var(--color-emerald-200)}.workflow-chat-view .bg-green-50{background-color:var(--color-emerald-50)}.workflow-chat-view .bg-green-100{background-color:var(--color-emerald-100)}.workflow-chat-view .text-green-600{color:var(--color-emerald-600)}.workflow-chat-view .text-green-700{color:var(--color-emerald-700)}.workflow-chat-view .text-green-800{color:var(--color-emerald-800)}@property --tw-translate-x{syntax:"*";inherits:false;initial-value:0}@property --tw-translate-y{syntax:"*";inherits:false;initial-value:0}@property --tw-translate-z{syntax:"*";inherits:false;initial-value:0}@property --tw-scale-x{syntax:"*";inherits:false;initial-value:1}@property --tw-scale-y{syntax:"*";inherits:false;initial-value:1}@property --tw-scale-z{syntax:"*";inherits:false;initial-value:1}@property --tw-rotate-x{syntax:"*";inherits:false}@property --tw-rotate-y{syntax:"*";inherits:false}@property --tw-rotate-z{syntax:"*";inherits:false}@property --tw-skew-x{syntax:"*";inherits:false}@property --tw-skew-y{syntax:"*";inherits:false}@property --tw-space-y-reverse{syntax:"*";inherits:false;initial-value:0}@property --tw-space-x-reverse{syntax:"*";inherits:false;initial-value:0}@property --tw-border-style{syntax:"*";inherits:false;initial-value:solid}@property --tw-leading{syntax:"*";inherits:false}@property --tw-font-weight{syntax:"*";inherits:false}@property --tw-tracking{syntax:"*";inherits:false}@property --tw-shadow{syntax:"*";inherits:false;initial-value:0 0 #0000}@property --tw-shadow-color{syntax:"*";inherits:false}@property --tw-shadow-alpha{syntax:"";inherits:false;initial-value:100%}@property --tw-inset-shadow{syntax:"*";inherits:false;initial-value:0 0 #0000}@property --tw-inset-shadow-color{syntax:"*";inherits:false}@property --tw-inset-shadow-alpha{syntax:"";inherits:false;initial-value:100%}@property --tw-ring-color{syntax:"*";inherits:false}@property --tw-ring-shadow{syntax:"*";inherits:false;initial-value:0 0 #0000}@property --tw-inset-ring-color{syntax:"*";inherits:false}@property --tw-inset-ring-shadow{syntax:"*";inherits:false;initial-value:0 0 #0000}@property --tw-ring-inset{syntax:"*";inherits:false}@property --tw-ring-offset-width{syntax:"";inherits:false;initial-value:0}@property --tw-ring-offset-color{syntax:"*";inherits:false;initial-value:#fff}@property --tw-ring-offset-shadow{syntax:"*";inherits:false;initial-value:0 0 #0000}@property --tw-outline-style{syntax:"*";inherits:false;initial-value:solid}@property --tw-blur{syntax:"*";inherits:false}@property --tw-brightness{syntax:"*";inherits:false}@property --tw-contrast{syntax:"*";inherits:false}@property --tw-grayscale{syntax:"*";inherits:false}@property --tw-hue-rotate{syntax:"*";inherits:false}@property --tw-invert{syntax:"*";inherits:false}@property --tw-opacity{syntax:"*";inherits:false}@property --tw-saturate{syntax:"*";inherits:false}@property --tw-sepia{syntax:"*";inherits:false}@property --tw-drop-shadow{syntax:"*";inherits:false}@property --tw-drop-shadow-color{syntax:"*";inherits:false}@property --tw-drop-shadow-alpha{syntax:"";inherits:false;initial-value:100%}@property --tw-drop-shadow-size{syntax:"*";inherits:false}@property --tw-backdrop-blur{syntax:"*";inherits:false}@property --tw-backdrop-brightness{syntax:"*";inherits:false}@property --tw-backdrop-contrast{syntax:"*";inherits:false}@property --tw-backdrop-grayscale{syntax:"*";inherits:false}@property --tw-backdrop-hue-rotate{syntax:"*";inherits:false}@property --tw-backdrop-invert{syntax:"*";inherits:false}@property --tw-backdrop-opacity{syntax:"*";inherits:false}@property --tw-backdrop-saturate{syntax:"*";inherits:false}@property --tw-backdrop-sepia{syntax:"*";inherits:false}@property --tw-duration{syntax:"*";inherits:false}@property --tw-ease{syntax:"*";inherits:false}@keyframes spin{to{transform:rotate(360deg)}}@keyframes pulse{50%{opacity:.5}}@keyframes bounce{0%,to{animation-timing-function:cubic-bezier(.8,0,1,1);transform:translateY(-25%)}50%{animation-timing-function:cubic-bezier(0,0,.2,1);transform:none}}@keyframes enter{0%{opacity:var(--tw-enter-opacity,1);transform:translate3d(var(--tw-enter-translate-x,0),var(--tw-enter-translate-y,0),0)scale3d(var(--tw-enter-scale,1),var(--tw-enter-scale,1),var(--tw-enter-scale,1))rotate(var(--tw-enter-rotate,0));filter:blur(var(--tw-enter-blur,0))}}@keyframes exit{to{opacity:var(--tw-exit-opacity,1);transform:translate3d(var(--tw-exit-translate-x,0),var(--tw-exit-translate-y,0),0)scale3d(var(--tw-exit-scale,1),var(--tw-exit-scale,1),var(--tw-exit-scale,1))rotate(var(--tw-exit-rotate,0));filter:blur(var(--tw-exit-blur,0))}}.react-flow{direction:ltr;--xy-edge-stroke-default: #b1b1b7;--xy-edge-stroke-width-default: 1;--xy-edge-stroke-selected-default: #555;--xy-connectionline-stroke-default: #b1b1b7;--xy-connectionline-stroke-width-default: 1;--xy-attribution-background-color-default: rgba(255, 255, 255, .5);--xy-minimap-background-color-default: #fff;--xy-minimap-mask-background-color-default: rgba(240, 240, 240, .6);--xy-minimap-mask-stroke-color-default: transparent;--xy-minimap-mask-stroke-width-default: 1;--xy-minimap-node-background-color-default: #e2e2e2;--xy-minimap-node-stroke-color-default: transparent;--xy-minimap-node-stroke-width-default: 2;--xy-background-color-default: transparent;--xy-background-pattern-dots-color-default: #91919a;--xy-background-pattern-lines-color-default: #eee;--xy-background-pattern-cross-color-default: #e2e2e2;background-color:var(--xy-background-color, var(--xy-background-color-default));--xy-node-color-default: inherit;--xy-node-border-default: 1px solid #1a192b;--xy-node-background-color-default: #fff;--xy-node-group-background-color-default: rgba(240, 240, 240, .25);--xy-node-boxshadow-hover-default: 0 1px 4px 1px rgba(0, 0, 0, .08);--xy-node-boxshadow-selected-default: 0 0 0 .5px #1a192b;--xy-node-border-radius-default: 3px;--xy-handle-background-color-default: #1a192b;--xy-handle-border-color-default: #fff;--xy-selection-background-color-default: rgba(0, 89, 220, .08);--xy-selection-border-default: 1px dotted rgba(0, 89, 220, .8);--xy-controls-button-background-color-default: #fefefe;--xy-controls-button-background-color-hover-default: #f4f4f4;--xy-controls-button-color-default: inherit;--xy-controls-button-color-hover-default: inherit;--xy-controls-button-border-color-default: #eee;--xy-controls-box-shadow-default: 0 0 2px 1px rgba(0, 0, 0, .08);--xy-edge-label-background-color-default: #ffffff;--xy-edge-label-color-default: inherit;--xy-resize-background-color-default: #3367d9}.react-flow.dark{--xy-edge-stroke-default: #3e3e3e;--xy-edge-stroke-width-default: 1;--xy-edge-stroke-selected-default: #727272;--xy-connectionline-stroke-default: #b1b1b7;--xy-connectionline-stroke-width-default: 1;--xy-attribution-background-color-default: rgba(150, 150, 150, .25);--xy-minimap-background-color-default: #141414;--xy-minimap-mask-background-color-default: rgba(60, 60, 60, .6);--xy-minimap-mask-stroke-color-default: transparent;--xy-minimap-mask-stroke-width-default: 1;--xy-minimap-node-background-color-default: #2b2b2b;--xy-minimap-node-stroke-color-default: transparent;--xy-minimap-node-stroke-width-default: 2;--xy-background-color-default: #141414;--xy-background-pattern-dots-color-default: #777;--xy-background-pattern-lines-color-default: #777;--xy-background-pattern-cross-color-default: #777;--xy-node-color-default: #f8f8f8;--xy-node-border-default: 1px solid #3c3c3c;--xy-node-background-color-default: #1e1e1e;--xy-node-group-background-color-default: rgba(240, 240, 240, .25);--xy-node-boxshadow-hover-default: 0 1px 4px 1px rgba(255, 255, 255, .08);--xy-node-boxshadow-selected-default: 0 0 0 .5px #999;--xy-handle-background-color-default: #bebebe;--xy-handle-border-color-default: #1e1e1e;--xy-selection-background-color-default: rgba(200, 200, 220, .08);--xy-selection-border-default: 1px dotted rgba(200, 200, 220, .8);--xy-controls-button-background-color-default: #2b2b2b;--xy-controls-button-background-color-hover-default: #3e3e3e;--xy-controls-button-color-default: #f8f8f8;--xy-controls-button-color-hover-default: #fff;--xy-controls-button-border-color-default: #5b5b5b;--xy-controls-box-shadow-default: 0 0 2px 1px rgba(0, 0, 0, .08);--xy-edge-label-background-color-default: #141414;--xy-edge-label-color-default: #f8f8f8}.react-flow__background{background-color:var(--xy-background-color-props, var(--xy-background-color, var(--xy-background-color-default)));pointer-events:none;z-index:-1}.react-flow__container{position:absolute;width:100%;height:100%;top:0;left:0}.react-flow__pane{z-index:1}.react-flow__pane.draggable{cursor:grab}.react-flow__pane.dragging{cursor:grabbing}.react-flow__pane.selection{cursor:pointer}.react-flow__viewport{transform-origin:0 0;z-index:2;pointer-events:none}.react-flow__renderer{z-index:4}.react-flow__selection{z-index:6}.react-flow__nodesselection-rect:focus,.react-flow__nodesselection-rect:focus-visible{outline:none}.react-flow__edge-path{stroke:var(--xy-edge-stroke, var(--xy-edge-stroke-default));stroke-width:var(--xy-edge-stroke-width, var(--xy-edge-stroke-width-default));fill:none}.react-flow__connection-path{stroke:var(--xy-connectionline-stroke, var(--xy-connectionline-stroke-default));stroke-width:var(--xy-connectionline-stroke-width, var(--xy-connectionline-stroke-width-default));fill:none}.react-flow .react-flow__edges{position:absolute}.react-flow .react-flow__edges svg{overflow:visible;position:absolute;pointer-events:none}.react-flow__edge{pointer-events:visibleStroke}.react-flow__edge.selectable{cursor:pointer}.react-flow__edge.animated path{stroke-dasharray:5;animation:dashdraw .5s linear infinite}.react-flow__edge.animated path.react-flow__edge-interaction{stroke-dasharray:none;animation:none}.react-flow__edge.inactive{pointer-events:none}.react-flow__edge.selected,.react-flow__edge:focus,.react-flow__edge:focus-visible{outline:none}.react-flow__edge.selected .react-flow__edge-path,.react-flow__edge.selectable:focus .react-flow__edge-path,.react-flow__edge.selectable:focus-visible .react-flow__edge-path{stroke:var(--xy-edge-stroke-selected, var(--xy-edge-stroke-selected-default))}.react-flow__edge-textwrapper{pointer-events:all}.react-flow__edge .react-flow__edge-text{pointer-events:none;-webkit-user-select:none;-moz-user-select:none;user-select:none}.react-flow__arrowhead polyline{stroke:var(--xy-edge-stroke, var(--xy-edge-stroke-default))}.react-flow__arrowhead polyline.arrowclosed{fill:var(--xy-edge-stroke, var(--xy-edge-stroke-default))}.react-flow__connection{pointer-events:none}.react-flow__connection .animated{stroke-dasharray:5;animation:dashdraw .5s linear infinite}svg.react-flow__connectionline{z-index:1001;overflow:visible;position:absolute}.react-flow__nodes{pointer-events:none;transform-origin:0 0}.react-flow__node{position:absolute;-webkit-user-select:none;-moz-user-select:none;user-select:none;pointer-events:all;transform-origin:0 0;box-sizing:border-box;cursor:default}.react-flow__node.selectable{cursor:pointer}.react-flow__node.draggable{cursor:grab;pointer-events:all}.react-flow__node.draggable.dragging{cursor:grabbing}.react-flow__nodesselection{z-index:3;transform-origin:left top;pointer-events:none}.react-flow__nodesselection-rect{position:absolute;pointer-events:all;cursor:grab}.react-flow__handle{position:absolute;pointer-events:none;min-width:5px;min-height:5px;width:6px;height:6px;background-color:var(--xy-handle-background-color, var(--xy-handle-background-color-default));border:1px solid var(--xy-handle-border-color, var(--xy-handle-border-color-default));border-radius:100%}.react-flow__handle.connectingfrom{pointer-events:all}.react-flow__handle.connectionindicator{pointer-events:all;cursor:crosshair}.react-flow__handle-bottom{top:auto;left:50%;bottom:0;transform:translate(-50%,50%)}.react-flow__handle-top{top:0;left:50%;transform:translate(-50%,-50%)}.react-flow__handle-left{top:50%;left:0;transform:translate(-50%,-50%)}.react-flow__handle-right{top:50%;right:0;transform:translate(50%,-50%)}.react-flow__edgeupdater{cursor:move;pointer-events:all}.react-flow__pane.selection .react-flow__panel{pointer-events:none}.react-flow__panel{position:absolute;z-index:5;margin:15px}.react-flow__panel.top{top:0}.react-flow__panel.bottom{bottom:0}.react-flow__panel.top.center,.react-flow__panel.bottom.center{left:50%;transform:translate(-15px) translate(-50%)}.react-flow__panel.left{left:0}.react-flow__panel.right{right:0}.react-flow__panel.left.center,.react-flow__panel.right.center{top:50%;transform:translateY(-15px) translateY(-50%)}.react-flow__attribution{font-size:10px;background:var(--xy-attribution-background-color, var(--xy-attribution-background-color-default));padding:2px 3px;margin:0}.react-flow__attribution a{text-decoration:none;color:#999}@keyframes dashdraw{0%{stroke-dashoffset:10}}.react-flow__edgelabel-renderer{position:absolute;width:100%;height:100%;pointer-events:none;-webkit-user-select:none;-moz-user-select:none;user-select:none;left:0;top:0}.react-flow__viewport-portal{position:absolute;width:100%;height:100%;left:0;top:0;-webkit-user-select:none;-moz-user-select:none;user-select:none}.react-flow__minimap{background:var( --xy-minimap-background-color-props, var(--xy-minimap-background-color, var(--xy-minimap-background-color-default)) )}.react-flow__minimap-svg{display:block}.react-flow__minimap-mask{fill:var( --xy-minimap-mask-background-color-props, var(--xy-minimap-mask-background-color, var(--xy-minimap-mask-background-color-default)) );stroke:var( --xy-minimap-mask-stroke-color-props, var(--xy-minimap-mask-stroke-color, var(--xy-minimap-mask-stroke-color-default)) );stroke-width:var( --xy-minimap-mask-stroke-width-props, var(--xy-minimap-mask-stroke-width, var(--xy-minimap-mask-stroke-width-default)) )}.react-flow__minimap-node{fill:var( --xy-minimap-node-background-color-props, var(--xy-minimap-node-background-color, var(--xy-minimap-node-background-color-default)) );stroke:var( --xy-minimap-node-stroke-color-props, var(--xy-minimap-node-stroke-color, var(--xy-minimap-node-stroke-color-default)) );stroke-width:var( --xy-minimap-node-stroke-width-props, var(--xy-minimap-node-stroke-width, var(--xy-minimap-node-stroke-width-default)) )}.react-flow__background-pattern.dots{fill:var( --xy-background-pattern-color-props, var(--xy-background-pattern-color, var(--xy-background-pattern-dots-color-default)) )}.react-flow__background-pattern.lines{stroke:var( --xy-background-pattern-color-props, var(--xy-background-pattern-color, var(--xy-background-pattern-lines-color-default)) )}.react-flow__background-pattern.cross{stroke:var( --xy-background-pattern-color-props, var(--xy-background-pattern-color, var(--xy-background-pattern-cross-color-default)) )}.react-flow__controls{display:flex;flex-direction:column;box-shadow:var(--xy-controls-box-shadow, var(--xy-controls-box-shadow-default))}.react-flow__controls.horizontal{flex-direction:row}.react-flow__controls-button{display:flex;justify-content:center;align-items:center;height:26px;width:26px;padding:4px;border:none;background:var(--xy-controls-button-background-color, var(--xy-controls-button-background-color-default));border-bottom:1px solid var( --xy-controls-button-border-color-props, var(--xy-controls-button-border-color, var(--xy-controls-button-border-color-default)) );color:var( --xy-controls-button-color-props, var(--xy-controls-button-color, var(--xy-controls-button-color-default)) );cursor:pointer;-webkit-user-select:none;-moz-user-select:none;user-select:none}.react-flow__controls-button svg{width:100%;max-width:12px;max-height:12px;fill:currentColor}.react-flow__edge.updating .react-flow__edge-path{stroke:#777}.react-flow__edge-text{font-size:10px}.react-flow__node.selectable:focus,.react-flow__node.selectable:focus-visible{outline:none}.react-flow__node-input,.react-flow__node-default,.react-flow__node-output,.react-flow__node-group{padding:10px;border-radius:var(--xy-node-border-radius, var(--xy-node-border-radius-default));width:150px;font-size:12px;color:var(--xy-node-color, var(--xy-node-color-default));text-align:center;border:var(--xy-node-border, var(--xy-node-border-default));background-color:var(--xy-node-background-color, var(--xy-node-background-color-default))}.react-flow__node-input.selectable:hover,.react-flow__node-default.selectable:hover,.react-flow__node-output.selectable:hover,.react-flow__node-group.selectable:hover{box-shadow:var(--xy-node-boxshadow-hover, var(--xy-node-boxshadow-hover-default))}.react-flow__node-input.selectable.selected,.react-flow__node-input.selectable:focus,.react-flow__node-input.selectable:focus-visible,.react-flow__node-default.selectable.selected,.react-flow__node-default.selectable:focus,.react-flow__node-default.selectable:focus-visible,.react-flow__node-output.selectable.selected,.react-flow__node-output.selectable:focus,.react-flow__node-output.selectable:focus-visible,.react-flow__node-group.selectable.selected,.react-flow__node-group.selectable:focus,.react-flow__node-group.selectable:focus-visible{box-shadow:var(--xy-node-boxshadow-selected, var(--xy-node-boxshadow-selected-default))}.react-flow__node-group{background-color:var(--xy-node-group-background-color, var(--xy-node-group-background-color-default))}.react-flow__nodesselection-rect,.react-flow__selection{background:var(--xy-selection-background-color, var(--xy-selection-background-color-default));border:var(--xy-selection-border, var(--xy-selection-border-default))}.react-flow__nodesselection-rect:focus,.react-flow__nodesselection-rect:focus-visible,.react-flow__selection:focus,.react-flow__selection:focus-visible{outline:none}.react-flow__controls-button:hover{background:var( --xy-controls-button-background-color-hover-props, var(--xy-controls-button-background-color-hover, var(--xy-controls-button-background-color-hover-default)) );color:var( --xy-controls-button-color-hover-props, var(--xy-controls-button-color-hover, var(--xy-controls-button-color-hover-default)) )}.react-flow__controls-button:disabled{pointer-events:none}.react-flow__controls-button:disabled svg{fill-opacity:.4}.react-flow__controls-button:last-child{border-bottom:none}.react-flow__controls.horizontal .react-flow__controls-button{border-bottom:none;border-right:1px solid var( --xy-controls-button-border-color-props, var(--xy-controls-button-border-color, var(--xy-controls-button-border-color-default)) )}.react-flow__controls.horizontal .react-flow__controls-button:last-child{border-right:none}.react-flow__resize-control{position:absolute}.react-flow__resize-control.left,.react-flow__resize-control.right{cursor:ew-resize}.react-flow__resize-control.top,.react-flow__resize-control.bottom{cursor:ns-resize}.react-flow__resize-control.top.left,.react-flow__resize-control.bottom.right{cursor:nwse-resize}.react-flow__resize-control.bottom.left,.react-flow__resize-control.top.right{cursor:nesw-resize}.react-flow__resize-control.handle{width:5px;height:5px;border:1px solid #fff;border-radius:1px;background-color:var(--xy-resize-background-color, var(--xy-resize-background-color-default));translate:-50% -50%}.react-flow__resize-control.handle.left{left:0;top:50%}.react-flow__resize-control.handle.right{left:100%;top:50%}.react-flow__resize-control.handle.top{left:50%;top:0}.react-flow__resize-control.handle.bottom{left:50%;top:100%}.react-flow__resize-control.handle.top.left,.react-flow__resize-control.handle.bottom.left{left:0}.react-flow__resize-control.handle.top.right,.react-flow__resize-control.handle.bottom.right{left:100%}.react-flow__resize-control.line{border-color:var(--xy-resize-background-color, var(--xy-resize-background-color-default));border-width:0;border-style:solid}.react-flow__resize-control.line.left,.react-flow__resize-control.line.right{width:1px;transform:translate(-50%);top:0;height:100%}.react-flow__resize-control.line.left{left:0;border-left-width:1px}.react-flow__resize-control.line.right{left:100%;border-right-width:1px}.react-flow__resize-control.line.top,.react-flow__resize-control.line.bottom{height:1px;transform:translateY(-50%);left:0;width:100%}.react-flow__resize-control.line.top{top:0;border-top-width:1px}.react-flow__resize-control.line.bottom{border-bottom-width:1px;top:100%}.react-flow__edge-textbg{fill:var(--xy-edge-label-background-color, var(--xy-edge-label-background-color-default))}.react-flow__edge-text{fill:var(--xy-edge-label-color, var(--xy-edge-label-color-default))} +/*! tailwindcss v4.1.12 | MIT License | https://tailwindcss.com */@layer properties{@supports (((-webkit-hyphens:none)) and (not (margin-trim:inline))) or ((-moz-orient:inline) and (not (color:rgb(from red r g b)))){*,:before,:after,::backdrop{--tw-translate-x:0;--tw-translate-y:0;--tw-translate-z:0;--tw-scale-x:1;--tw-scale-y:1;--tw-scale-z:1;--tw-rotate-x:initial;--tw-rotate-y:initial;--tw-rotate-z:initial;--tw-skew-x:initial;--tw-skew-y:initial;--tw-space-y-reverse:0;--tw-space-x-reverse:0;--tw-border-style:solid;--tw-leading:initial;--tw-font-weight:initial;--tw-tracking:initial;--tw-shadow:0 0 #0000;--tw-shadow-color:initial;--tw-shadow-alpha:100%;--tw-inset-shadow:0 0 #0000;--tw-inset-shadow-color:initial;--tw-inset-shadow-alpha:100%;--tw-ring-color:initial;--tw-ring-shadow:0 0 #0000;--tw-inset-ring-color:initial;--tw-inset-ring-shadow:0 0 #0000;--tw-ring-inset:initial;--tw-ring-offset-width:0px;--tw-ring-offset-color:#fff;--tw-ring-offset-shadow:0 0 #0000;--tw-outline-style:solid;--tw-blur:initial;--tw-brightness:initial;--tw-contrast:initial;--tw-grayscale:initial;--tw-hue-rotate:initial;--tw-invert:initial;--tw-opacity:initial;--tw-saturate:initial;--tw-sepia:initial;--tw-drop-shadow:initial;--tw-drop-shadow-color:initial;--tw-drop-shadow-alpha:100%;--tw-drop-shadow-size:initial;--tw-backdrop-blur:initial;--tw-backdrop-brightness:initial;--tw-backdrop-contrast:initial;--tw-backdrop-grayscale:initial;--tw-backdrop-hue-rotate:initial;--tw-backdrop-invert:initial;--tw-backdrop-opacity:initial;--tw-backdrop-saturate:initial;--tw-backdrop-sepia:initial;--tw-duration:initial;--tw-ease:initial;--tw-animation-delay:0s;--tw-animation-direction:normal;--tw-animation-duration:initial;--tw-animation-fill-mode:none;--tw-animation-iteration-count:1;--tw-enter-blur:0;--tw-enter-opacity:1;--tw-enter-rotate:0;--tw-enter-scale:1;--tw-enter-translate-x:0;--tw-enter-translate-y:0;--tw-exit-blur:0;--tw-exit-opacity:1;--tw-exit-rotate:0;--tw-exit-scale:1;--tw-exit-translate-x:0;--tw-exit-translate-y:0}}}@layer theme{:root,:host{--font-sans:ui-sans-serif,system-ui,sans-serif,"Apple Color Emoji","Segoe UI Emoji","Segoe UI Symbol","Noto Color Emoji";--font-mono:ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,"Liberation Mono","Courier New",monospace;--color-red-50:oklch(97.1% .013 17.38);--color-red-100:oklch(93.6% .032 17.717);--color-red-200:oklch(88.5% .062 18.334);--color-red-300:oklch(80.8% .114 19.571);--color-red-400:oklch(70.4% .191 22.216);--color-red-500:oklch(63.7% .237 25.331);--color-red-600:oklch(57.7% .245 27.325);--color-red-700:oklch(50.5% .213 27.518);--color-red-800:oklch(44.4% .177 26.899);--color-red-900:oklch(39.6% .141 25.723);--color-red-950:oklch(25.8% .092 26.042);--color-orange-50:oklch(98% .016 73.684);--color-orange-100:oklch(95.4% .038 75.164);--color-orange-200:oklch(90.1% .076 70.697);--color-orange-400:oklch(75% .183 55.934);--color-orange-500:oklch(70.5% .213 47.604);--color-orange-600:oklch(64.6% .222 41.116);--color-orange-800:oklch(47% .157 37.304);--color-orange-900:oklch(40.8% .123 38.172);--color-orange-950:oklch(26.6% .079 36.259);--color-amber-50:oklch(98.7% .022 95.277);--color-amber-100:oklch(96.2% .059 95.617);--color-amber-200:oklch(92.4% .12 95.746);--color-amber-300:oklch(87.9% .169 91.605);--color-amber-400:oklch(82.8% .189 84.429);--color-amber-500:oklch(76.9% .188 70.08);--color-amber-600:oklch(66.6% .179 58.318);--color-amber-700:oklch(55.5% .163 48.998);--color-amber-800:oklch(47.3% .137 46.201);--color-amber-900:oklch(41.4% .112 45.904);--color-amber-950:oklch(27.9% .077 45.635);--color-yellow-100:oklch(97.3% .071 103.193);--color-yellow-200:oklch(94.5% .129 101.54);--color-yellow-400:oklch(85.2% .199 91.936);--color-yellow-600:oklch(68.1% .162 75.834);--color-yellow-700:oklch(55.4% .135 66.442);--color-green-50:oklch(98.2% .018 155.826);--color-green-100:oklch(96.2% .044 156.743);--color-green-200:oklch(92.5% .084 155.995);--color-green-300:oklch(87.1% .15 154.449);--color-green-400:oklch(79.2% .209 151.711);--color-green-500:oklch(72.3% .219 149.579);--color-green-600:oklch(62.7% .194 149.214);--color-green-700:oklch(52.7% .154 150.069);--color-green-800:oklch(44.8% .119 151.328);--color-green-900:oklch(39.3% .095 152.535);--color-green-950:oklch(26.6% .065 152.934);--color-emerald-50:oklch(97.9% .021 166.113);--color-emerald-100:oklch(95% .052 163.051);--color-emerald-200:oklch(90.5% .093 164.15);--color-emerald-600:oklch(59.6% .145 163.225);--color-emerald-700:oklch(50.8% .118 165.612);--color-emerald-800:oklch(43.2% .095 166.913);--color-blue-50:oklch(97% .014 254.604);--color-blue-100:oklch(93.2% .032 255.585);--color-blue-200:oklch(88.2% .059 254.128);--color-blue-300:oklch(80.9% .105 251.813);--color-blue-400:oklch(70.7% .165 254.624);--color-blue-500:oklch(62.3% .214 259.815);--color-blue-600:oklch(54.6% .245 262.881);--color-blue-700:oklch(48.8% .243 264.376);--color-blue-800:oklch(42.4% .199 265.638);--color-blue-900:oklch(37.9% .146 265.522);--color-blue-950:oklch(28.2% .091 267.935);--color-purple-50:oklch(97.7% .014 308.299);--color-purple-100:oklch(94.6% .033 307.174);--color-purple-400:oklch(71.4% .203 305.504);--color-purple-500:oklch(62.7% .265 303.9);--color-purple-600:oklch(55.8% .288 302.321);--color-purple-900:oklch(38.1% .176 304.987);--color-gray-50:oklch(98.5% .002 247.839);--color-gray-100:oklch(96.7% .003 264.542);--color-gray-200:oklch(92.8% .006 264.531);--color-gray-300:oklch(87.2% .01 258.338);--color-gray-400:oklch(70.7% .022 261.325);--color-gray-500:oklch(55.1% .027 264.364);--color-gray-600:oklch(44.6% .03 256.802);--color-gray-700:oklch(37.3% .034 259.733);--color-gray-800:oklch(27.8% .033 256.848);--color-gray-900:oklch(21% .034 264.665);--color-black:#000;--color-white:#fff;--spacing:.25rem;--container-md:28rem;--container-lg:32rem;--container-2xl:42rem;--container-3xl:48rem;--container-4xl:56rem;--container-5xl:64rem;--container-7xl:80rem;--text-xs:.75rem;--text-xs--line-height:calc(1/.75);--text-sm:.875rem;--text-sm--line-height:calc(1.25/.875);--text-base:1rem;--text-base--line-height: 1.5 ;--text-lg:1.125rem;--text-lg--line-height:calc(1.75/1.125);--text-xl:1.25rem;--text-xl--line-height:calc(1.75/1.25);--text-2xl:1.5rem;--text-2xl--line-height:calc(2/1.5);--font-weight-medium:500;--font-weight-semibold:600;--font-weight-bold:700;--tracking-tight:-.025em;--tracking-widest:.1em;--leading-tight:1.25;--leading-relaxed:1.625;--drop-shadow-lg:0 4px 4px #00000026;--ease-out:cubic-bezier(0,0,.2,1);--ease-in-out:cubic-bezier(.4,0,.2,1);--animate-spin:spin 1s linear infinite;--animate-pulse:pulse 2s cubic-bezier(.4,0,.6,1)infinite;--animate-bounce:bounce 1s infinite;--blur-sm:8px;--default-transition-duration:.15s;--default-transition-timing-function:cubic-bezier(.4,0,.2,1);--default-font-family:var(--font-sans);--default-mono-font-family:var(--font-mono)}}@layer base{*,:after,:before,::backdrop{box-sizing:border-box;border:0 solid;margin:0;padding:0}::file-selector-button{box-sizing:border-box;border:0 solid;margin:0;padding:0}html,:host{-webkit-text-size-adjust:100%;tab-size:4;line-height:1.5;font-family:var(--default-font-family,ui-sans-serif,system-ui,sans-serif,"Apple Color Emoji","Segoe UI Emoji","Segoe UI Symbol","Noto Color Emoji");font-feature-settings:var(--default-font-feature-settings,normal);font-variation-settings:var(--default-font-variation-settings,normal);-webkit-tap-highlight-color:transparent}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;-webkit-text-decoration:inherit;text-decoration:inherit}b,strong{font-weight:bolder}code,kbd,samp,pre{font-family:var(--default-mono-font-family,ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,"Liberation Mono","Courier New",monospace);font-feature-settings:var(--default-mono-font-feature-settings,normal);font-variation-settings:var(--default-mono-font-variation-settings,normal);font-size:1em}small{font-size:80%}sub,sup{vertical-align:baseline;font-size:75%;line-height:0;position:relative}sub{bottom:-.25em}sup{top:-.5em}table{text-indent:0;border-color:inherit;border-collapse:collapse}:-moz-focusring{outline:auto}progress{vertical-align:baseline}summary{display:list-item}ol,ul,menu{list-style:none}img,svg,video,canvas,audio,iframe,embed,object{vertical-align:middle;display:block}img,video{max-width:100%;height:auto}button,input,select,optgroup,textarea{font:inherit;font-feature-settings:inherit;font-variation-settings:inherit;letter-spacing:inherit;color:inherit;opacity:1;background-color:#0000;border-radius:0}::file-selector-button{font:inherit;font-feature-settings:inherit;font-variation-settings:inherit;letter-spacing:inherit;color:inherit;opacity:1;background-color:#0000;border-radius:0}:where(select:is([multiple],[size])) optgroup{font-weight:bolder}:where(select:is([multiple],[size])) optgroup option{padding-inline-start:20px}::file-selector-button{margin-inline-end:4px}::placeholder{opacity:1}@supports (not ((-webkit-appearance:-apple-pay-button))) or (contain-intrinsic-size:1px){::placeholder{color:currentColor}@supports (color:color-mix(in lab,red,red)){::placeholder{color:color-mix(in oklab,currentcolor 50%,transparent)}}}textarea{resize:vertical}::-webkit-search-decoration{-webkit-appearance:none}::-webkit-date-and-time-value{min-height:1lh;text-align:inherit}::-webkit-datetime-edit{display:inline-flex}::-webkit-datetime-edit-fields-wrapper{padding:0}::-webkit-datetime-edit{padding-block:0}::-webkit-datetime-edit-year-field{padding-block:0}::-webkit-datetime-edit-month-field{padding-block:0}::-webkit-datetime-edit-day-field{padding-block:0}::-webkit-datetime-edit-hour-field{padding-block:0}::-webkit-datetime-edit-minute-field{padding-block:0}::-webkit-datetime-edit-second-field{padding-block:0}::-webkit-datetime-edit-millisecond-field{padding-block:0}::-webkit-datetime-edit-meridiem-field{padding-block:0}::-webkit-calendar-picker-indicator{line-height:1}:-moz-ui-invalid{box-shadow:none}button,input:where([type=button],[type=reset],[type=submit]){appearance:button}::file-selector-button{appearance:button}::-webkit-inner-spin-button{height:auto}::-webkit-outer-spin-button{height:auto}[hidden]:where(:not([hidden=until-found])){display:none!important}*{border-color:var(--border);outline-color:var(--ring)}@supports (color:color-mix(in lab,red,red)){*{outline-color:color-mix(in oklab,var(--ring)50%,transparent)}}body{background-color:var(--background);color:var(--foreground)}}@layer components;@layer utilities{.\@container\/card-header{container:card-header/inline-size}.pointer-events-none{pointer-events:none}.collapse{visibility:collapse}.visible{visibility:visible}.sr-only{clip:rect(0,0,0,0);white-space:nowrap;border-width:0;width:1px;height:1px;margin:-1px;padding:0;position:absolute;overflow:hidden}.absolute{position:absolute}.fixed{position:fixed}.relative{position:relative}.static{position:static}.inset-0{inset:calc(var(--spacing)*0)}.inset-2{inset:calc(var(--spacing)*2)}.inset-y-0{inset-block:calc(var(--spacing)*0)}.top-1{top:calc(var(--spacing)*1)}.top-2{top:calc(var(--spacing)*2)}.top-4{top:calc(var(--spacing)*4)}.-right-2{right:calc(var(--spacing)*-2)}.right-0{right:calc(var(--spacing)*0)}.right-1{right:calc(var(--spacing)*1)}.right-2{right:calc(var(--spacing)*2)}.right-4{right:calc(var(--spacing)*4)}.bottom-0{bottom:calc(var(--spacing)*0)}.bottom-24{bottom:calc(var(--spacing)*24)}.-left-2{left:calc(var(--spacing)*-2)}.left-0{left:calc(var(--spacing)*0)}.left-1\/2{left:50%}.left-2{left:calc(var(--spacing)*2)}.z-10{z-index:10}.z-20{z-index:20}.z-50{z-index:50}.col-start-2{grid-column-start:2}.row-span-2{grid-row:span 2/span 2}.row-start-1{grid-row-start:1}.container{width:100%}@media (min-width:40rem){.container{max-width:40rem}}@media (min-width:48rem){.container{max-width:48rem}}@media (min-width:64rem){.container{max-width:64rem}}@media (min-width:80rem){.container{max-width:80rem}}@media (min-width:96rem){.container{max-width:96rem}}.container\!{width:100%!important}@media (min-width:40rem){.container\!{max-width:40rem!important}}@media (min-width:48rem){.container\!{max-width:48rem!important}}@media (min-width:64rem){.container\!{max-width:64rem!important}}@media (min-width:80rem){.container\!{max-width:80rem!important}}@media (min-width:96rem){.container\!{max-width:96rem!important}}.-mx-1{margin-inline:calc(var(--spacing)*-1)}.mx-4{margin-inline:calc(var(--spacing)*4)}.mx-auto{margin-inline:auto}.my-1{margin-block:calc(var(--spacing)*1)}.my-2{margin-block:calc(var(--spacing)*2)}.my-3{margin-block:calc(var(--spacing)*3)}.my-4{margin-block:calc(var(--spacing)*4)}.mt-0{margin-top:calc(var(--spacing)*0)}.mt-0\.5{margin-top:calc(var(--spacing)*.5)}.mt-1{margin-top:calc(var(--spacing)*1)}.mt-2{margin-top:calc(var(--spacing)*2)}.mt-3{margin-top:calc(var(--spacing)*3)}.mt-4{margin-top:calc(var(--spacing)*4)}.mt-12{margin-top:calc(var(--spacing)*12)}.mr-1{margin-right:calc(var(--spacing)*1)}.mr-2{margin-right:calc(var(--spacing)*2)}.mb-1{margin-bottom:calc(var(--spacing)*1)}.mb-2{margin-bottom:calc(var(--spacing)*2)}.mb-3{margin-bottom:calc(var(--spacing)*3)}.mb-4{margin-bottom:calc(var(--spacing)*4)}.mb-6{margin-bottom:calc(var(--spacing)*6)}.mb-8{margin-bottom:calc(var(--spacing)*8)}.ml-1{margin-left:calc(var(--spacing)*1)}.ml-1\.5{margin-left:calc(var(--spacing)*1.5)}.ml-2{margin-left:calc(var(--spacing)*2)}.ml-3{margin-left:calc(var(--spacing)*3)}.ml-4{margin-left:calc(var(--spacing)*4)}.ml-5{margin-left:calc(var(--spacing)*5)}.ml-auto{margin-left:auto}.line-clamp-2{-webkit-line-clamp:2;-webkit-box-orient:vertical;display:-webkit-box;overflow:hidden}.line-clamp-3{-webkit-line-clamp:3;-webkit-box-orient:vertical;display:-webkit-box;overflow:hidden}.block{display:block}.contents{display:contents}.flex{display:flex}.grid{display:grid}.hidden{display:none}.inline{display:inline}.inline-block{display:inline-block}.inline-flex{display:inline-flex}.table{display:table}.field-sizing-content{field-sizing:content}.size-2{width:calc(var(--spacing)*2);height:calc(var(--spacing)*2)}.size-3\.5{width:calc(var(--spacing)*3.5);height:calc(var(--spacing)*3.5)}.size-4{width:calc(var(--spacing)*4);height:calc(var(--spacing)*4)}.size-9{width:calc(var(--spacing)*9);height:calc(var(--spacing)*9)}.\!h-2{height:calc(var(--spacing)*2)!important}.h-0{height:calc(var(--spacing)*0)}.h-0\.5{height:calc(var(--spacing)*.5)}.h-1{height:calc(var(--spacing)*1)}.h-2{height:calc(var(--spacing)*2)}.h-2\.5{height:calc(var(--spacing)*2.5)}.h-3{height:calc(var(--spacing)*3)}.h-3\.5{height:calc(var(--spacing)*3.5)}.h-4{height:calc(var(--spacing)*4)}.h-5{height:calc(var(--spacing)*5)}.h-6{height:calc(var(--spacing)*6)}.h-7{height:calc(var(--spacing)*7)}.h-8{height:calc(var(--spacing)*8)}.h-9{height:calc(var(--spacing)*9)}.h-10{height:calc(var(--spacing)*10)}.h-12{height:calc(var(--spacing)*12)}.h-14{height:calc(var(--spacing)*14)}.h-16{height:calc(var(--spacing)*16)}.h-32{height:calc(var(--spacing)*32)}.h-96{height:calc(var(--spacing)*96)}.h-\[1\.2rem\]{height:1.2rem}.h-\[1px\]{height:1px}.h-\[500px\]{height:500px}.h-\[calc\(100vh-3\.5rem\)\]{height:calc(100vh - 3.5rem)}.h-\[calc\(100vh-3\.7rem\)\]{height:calc(100vh - 3.7rem)}.h-\[var\(--radix-select-trigger-height\)\]{height:var(--radix-select-trigger-height)}.h-full{height:100%}.h-px{height:1px}.h-screen{height:100vh}.max-h-\(--radix-dropdown-menu-content-available-height\){max-height:var(--radix-dropdown-menu-content-available-height)}.max-h-\(--radix-select-content-available-height\){max-height:var(--radix-select-content-available-height)}.max-h-20{max-height:calc(var(--spacing)*20)}.max-h-32{max-height:calc(var(--spacing)*32)}.max-h-40{max-height:calc(var(--spacing)*40)}.max-h-48{max-height:calc(var(--spacing)*48)}.max-h-60{max-height:calc(var(--spacing)*60)}.max-h-64{max-height:calc(var(--spacing)*64)}.max-h-\[80vh\]{max-height:80vh}.max-h-\[85vh\]{max-height:85vh}.max-h-\[90vh\]{max-height:90vh}.max-h-\[200px\]{max-height:200px}.max-h-none{max-height:none}.max-h-screen{max-height:100vh}.\!min-h-0{min-height:calc(var(--spacing)*0)!important}.min-h-0{min-height:calc(var(--spacing)*0)}.min-h-16{min-height:calc(var(--spacing)*16)}.min-h-\[36px\]{min-height:36px}.min-h-\[40px\]{min-height:40px}.min-h-\[50vh\]{min-height:50vh}.min-h-\[400px\]{min-height:400px}.min-h-screen{min-height:100vh}.\!w-2{width:calc(var(--spacing)*2)!important}.w-1{width:calc(var(--spacing)*1)}.w-2{width:calc(var(--spacing)*2)}.w-2\.5{width:calc(var(--spacing)*2.5)}.w-3{width:calc(var(--spacing)*3)}.w-3\.5{width:calc(var(--spacing)*3.5)}.w-4{width:calc(var(--spacing)*4)}.w-5{width:calc(var(--spacing)*5)}.w-6{width:calc(var(--spacing)*6)}.w-8{width:calc(var(--spacing)*8)}.w-9{width:calc(var(--spacing)*9)}.w-10{width:calc(var(--spacing)*10)}.w-12{width:calc(var(--spacing)*12)}.w-16{width:calc(var(--spacing)*16)}.w-56{width:calc(var(--spacing)*56)}.w-64{width:calc(var(--spacing)*64)}.w-80{width:calc(var(--spacing)*80)}.w-96{width:calc(var(--spacing)*96)}.w-\[1\.2rem\]{width:1.2rem}.w-\[1px\]{width:1px}.w-\[200px\]{width:200px}.w-\[600px\]{width:600px}.w-\[800px\]{width:800px}.w-fit{width:fit-content}.w-full{width:100%}.max-w-2xl{max-width:var(--container-2xl)}.max-w-3xl{max-width:var(--container-3xl)}.max-w-4xl{max-width:var(--container-4xl)}.max-w-7xl{max-width:var(--container-7xl)}.max-w-\[80\%\]{max-width:80%}.max-w-\[90vw\]{max-width:90vw}.max-w-full{max-width:100%}.max-w-lg{max-width:var(--container-lg)}.max-w-md{max-width:var(--container-md)}.max-w-none{max-width:none}.\!min-w-0{min-width:calc(var(--spacing)*0)!important}.min-w-0{min-width:calc(var(--spacing)*0)}.min-w-\[8rem\]{min-width:8rem}.min-w-\[300px\]{min-width:300px}.min-w-\[400px\]{min-width:400px}.min-w-\[var\(--radix-select-trigger-width\)\]{min-width:var(--radix-select-trigger-width)}.min-w-full{min-width:100%}.flex-1{flex:1}.flex-shrink-0,.shrink-0{flex-shrink:0}.origin-\(--radix-dropdown-menu-content-transform-origin\){transform-origin:var(--radix-dropdown-menu-content-transform-origin)}.origin-\(--radix-select-content-transform-origin\){transform-origin:var(--radix-select-content-transform-origin)}.-translate-x-1\/2{--tw-translate-x: -50% ;translate:var(--tw-translate-x)var(--tw-translate-y)}.translate-x-0{--tw-translate-x:calc(var(--spacing)*0);translate:var(--tw-translate-x)var(--tw-translate-y)}.translate-x-4{--tw-translate-x:calc(var(--spacing)*4);translate:var(--tw-translate-x)var(--tw-translate-y)}.scale-0{--tw-scale-x:0%;--tw-scale-y:0%;--tw-scale-z:0%;scale:var(--tw-scale-x)var(--tw-scale-y)}.scale-75{--tw-scale-x:75%;--tw-scale-y:75%;--tw-scale-z:75%;scale:var(--tw-scale-x)var(--tw-scale-y)}.scale-100{--tw-scale-x:100%;--tw-scale-y:100%;--tw-scale-z:100%;scale:var(--tw-scale-x)var(--tw-scale-y)}.rotate-0{rotate:none}.rotate-90{rotate:90deg}.transform{transform:var(--tw-rotate-x,)var(--tw-rotate-y,)var(--tw-rotate-z,)var(--tw-skew-x,)var(--tw-skew-y,)}.animate-bounce{animation:var(--animate-bounce)}.animate-in{animation:enter var(--tw-animation-duration,var(--tw-duration,.15s))var(--tw-ease,ease)var(--tw-animation-delay,0s)var(--tw-animation-iteration-count,1)var(--tw-animation-direction,normal)var(--tw-animation-fill-mode,none)}.animate-pulse{animation:var(--animate-pulse)}.animate-spin{animation:var(--animate-spin)}.cursor-col-resize{cursor:col-resize}.cursor-default{cursor:default}.cursor-pointer{cursor:pointer}.touch-none{touch-action:none}.resize{resize:both}.resize-none{resize:none}.scroll-my-1{scroll-margin-block:calc(var(--spacing)*1)}.list-inside{list-style-position:inside}.list-decimal{list-style-type:decimal}.list-disc{list-style-type:disc}.list-none{list-style-type:none}.auto-rows-min{grid-auto-rows:min-content}.grid-cols-1{grid-template-columns:repeat(1,minmax(0,1fr))}.grid-rows-\[auto_auto\]{grid-template-rows:auto auto}.flex-col{flex-direction:column}.flex-row-reverse{flex-direction:row-reverse}.flex-wrap{flex-wrap:wrap}.items-center{align-items:center}.items-end{align-items:flex-end}.items-start{align-items:flex-start}.items-stretch{align-items:stretch}.justify-between{justify-content:space-between}.justify-center{justify-content:center}.justify-end{justify-content:flex-end}.gap-0{gap:calc(var(--spacing)*0)}.gap-1{gap:calc(var(--spacing)*1)}.gap-1\.5{gap:calc(var(--spacing)*1.5)}.gap-2{gap:calc(var(--spacing)*2)}.gap-3{gap:calc(var(--spacing)*3)}.gap-4{gap:calc(var(--spacing)*4)}.gap-6{gap:calc(var(--spacing)*6)}:where(.space-y-0\.5>:not(:last-child)){--tw-space-y-reverse:0;margin-block-start:calc(calc(var(--spacing)*.5)*var(--tw-space-y-reverse));margin-block-end:calc(calc(var(--spacing)*.5)*calc(1 - var(--tw-space-y-reverse)))}:where(.space-y-1>:not(:last-child)){--tw-space-y-reverse:0;margin-block-start:calc(calc(var(--spacing)*1)*var(--tw-space-y-reverse));margin-block-end:calc(calc(var(--spacing)*1)*calc(1 - var(--tw-space-y-reverse)))}:where(.space-y-1\.5>:not(:last-child)){--tw-space-y-reverse:0;margin-block-start:calc(calc(var(--spacing)*1.5)*var(--tw-space-y-reverse));margin-block-end:calc(calc(var(--spacing)*1.5)*calc(1 - var(--tw-space-y-reverse)))}:where(.space-y-2>:not(:last-child)){--tw-space-y-reverse:0;margin-block-start:calc(calc(var(--spacing)*2)*var(--tw-space-y-reverse));margin-block-end:calc(calc(var(--spacing)*2)*calc(1 - var(--tw-space-y-reverse)))}:where(.space-y-3>:not(:last-child)){--tw-space-y-reverse:0;margin-block-start:calc(calc(var(--spacing)*3)*var(--tw-space-y-reverse));margin-block-end:calc(calc(var(--spacing)*3)*calc(1 - var(--tw-space-y-reverse)))}:where(.space-y-4>:not(:last-child)){--tw-space-y-reverse:0;margin-block-start:calc(calc(var(--spacing)*4)*var(--tw-space-y-reverse));margin-block-end:calc(calc(var(--spacing)*4)*calc(1 - var(--tw-space-y-reverse)))}:where(.space-y-6>:not(:last-child)){--tw-space-y-reverse:0;margin-block-start:calc(calc(var(--spacing)*6)*var(--tw-space-y-reverse));margin-block-end:calc(calc(var(--spacing)*6)*calc(1 - var(--tw-space-y-reverse)))}:where(.space-x-1>:not(:last-child)){--tw-space-x-reverse:0;margin-inline-start:calc(calc(var(--spacing)*1)*var(--tw-space-x-reverse));margin-inline-end:calc(calc(var(--spacing)*1)*calc(1 - var(--tw-space-x-reverse)))}:where(.space-x-2>:not(:last-child)){--tw-space-x-reverse:0;margin-inline-start:calc(calc(var(--spacing)*2)*var(--tw-space-x-reverse));margin-inline-end:calc(calc(var(--spacing)*2)*calc(1 - var(--tw-space-x-reverse)))}.self-start{align-self:flex-start}.justify-self-end{justify-self:flex-end}.truncate{text-overflow:ellipsis;white-space:nowrap;overflow:hidden}.overflow-auto{overflow:auto}.overflow-hidden{overflow:hidden}.overflow-x-auto{overflow-x:auto}.overflow-x-hidden{overflow-x:hidden}.overflow-y-auto{overflow-y:auto}.\!rounded-full{border-radius:3.40282e38px!important}.rounded{border-radius:.25rem}.rounded-\[4px\]{border-radius:4px}.rounded-\[inherit\]{border-radius:inherit}.rounded-full{border-radius:3.40282e38px}.rounded-lg{border-radius:var(--radius)}.rounded-md{border-radius:calc(var(--radius) - 2px)}.rounded-none{border-radius:0}.rounded-sm{border-radius:calc(var(--radius) - 4px)}.rounded-l-none{border-top-left-radius:0;border-bottom-left-radius:0}.rounded-r-none{border-top-right-radius:0;border-bottom-right-radius:0}.\!border{border-style:var(--tw-border-style)!important;border-width:1px!important}.border{border-style:var(--tw-border-style);border-width:1px}.border-2{border-style:var(--tw-border-style);border-width:2px}.border-t{border-top-style:var(--tw-border-style);border-top-width:1px}.border-r{border-right-style:var(--tw-border-style);border-right-width:1px}.border-b{border-bottom-style:var(--tw-border-style);border-bottom-width:1px}.border-l{border-left-style:var(--tw-border-style);border-left-width:1px}.border-l-0{border-left-style:var(--tw-border-style);border-left-width:0}.border-l-2{border-left-style:var(--tw-border-style);border-left-width:2px}.border-l-4{border-left-style:var(--tw-border-style);border-left-width:4px}.border-dashed{--tw-border-style:dashed;border-style:dashed}.\!border-gray-600{border-color:var(--color-gray-600)!important}.border-\[\#643FB2\]{border-color:#643fb2}.border-\[\#643FB2\]\/20{border-color:#643fb233}.border-\[\#643FB2\]\/30{border-color:#643fb24d}.border-amber-200{border-color:var(--color-amber-200)}.border-blue-200{border-color:var(--color-blue-200)}.border-blue-300{border-color:var(--color-blue-300)}.border-blue-400{border-color:var(--color-blue-400)}.border-blue-500{border-color:var(--color-blue-500)}.border-border,.border-border\/50{border-color:var(--border)}@supports (color:color-mix(in lab,red,red)){.border-border\/50{border-color:color-mix(in oklab,var(--border)50%,transparent)}}.border-current\/30{border-color:currentColor}@supports (color:color-mix(in lab,red,red)){.border-current\/30{border-color:color-mix(in oklab,currentcolor 30%,transparent)}}.border-destructive\/30{border-color:var(--destructive)}@supports (color:color-mix(in lab,red,red)){.border-destructive\/30{border-color:color-mix(in oklab,var(--destructive)30%,transparent)}}.border-foreground\/5{border-color:var(--foreground)}@supports (color:color-mix(in lab,red,red)){.border-foreground\/5{border-color:color-mix(in oklab,var(--foreground)5%,transparent)}}.border-foreground\/10{border-color:var(--foreground)}@supports (color:color-mix(in lab,red,red)){.border-foreground\/10{border-color:color-mix(in oklab,var(--foreground)10%,transparent)}}.border-foreground\/20{border-color:var(--foreground)}@supports (color:color-mix(in lab,red,red)){.border-foreground\/20{border-color:color-mix(in oklab,var(--foreground)20%,transparent)}}.border-gray-200{border-color:var(--color-gray-200)}.border-gray-300{border-color:var(--color-gray-300)}.border-gray-400{border-color:var(--color-gray-400)}.border-gray-500\/20{border-color:#6a728233}@supports (color:color-mix(in lab,red,red)){.border-gray-500\/20{border-color:color-mix(in oklab,var(--color-gray-500)20%,transparent)}}.border-green-200{border-color:var(--color-green-200)}.border-green-500{border-color:var(--color-green-500)}.border-green-500\/20{border-color:#00c75833}@supports (color:color-mix(in lab,red,red)){.border-green-500\/20{border-color:color-mix(in oklab,var(--color-green-500)20%,transparent)}}.border-green-500\/40{border-color:#00c75866}@supports (color:color-mix(in lab,red,red)){.border-green-500\/40{border-color:color-mix(in oklab,var(--color-green-500)40%,transparent)}}.border-input{border-color:var(--input)}.border-muted{border-color:var(--muted)}.border-orange-200{border-color:var(--color-orange-200)}.border-orange-500{border-color:var(--color-orange-500)}.border-orange-500\/20{border-color:#fe6e0033}@supports (color:color-mix(in lab,red,red)){.border-orange-500\/20{border-color:color-mix(in oklab,var(--color-orange-500)20%,transparent)}}.border-primary,.border-primary\/20{border-color:var(--primary)}@supports (color:color-mix(in lab,red,red)){.border-primary\/20{border-color:color-mix(in oklab,var(--primary)20%,transparent)}}.border-red-200{border-color:var(--color-red-200)}.border-red-500{border-color:var(--color-red-500)}.border-red-500\/20{border-color:#fb2c3633}@supports (color:color-mix(in lab,red,red)){.border-red-500\/20{border-color:color-mix(in oklab,var(--color-red-500)20%,transparent)}}.border-transparent{border-color:#0000}.border-yellow-200{border-color:var(--color-yellow-200)}.border-t-transparent{border-top-color:#0000}.border-l-transparent{border-left-color:#0000}.bg-\[\#643FB2\]{background-color:#643fb2}.bg-\[\#643FB2\]\/10{background-color:#643fb21a}.bg-accent\/10{background-color:var(--accent)}@supports (color:color-mix(in lab,red,red)){.bg-accent\/10{background-color:color-mix(in oklab,var(--accent)10%,transparent)}}.bg-amber-50{background-color:var(--color-amber-50)}.bg-background{background-color:var(--background)}.bg-black{background-color:var(--color-black)}.bg-black\/50{background-color:#00000080}@supports (color:color-mix(in lab,red,red)){.bg-black\/50{background-color:color-mix(in oklab,var(--color-black)50%,transparent)}}.bg-black\/60{background-color:#0009}@supports (color:color-mix(in lab,red,red)){.bg-black\/60{background-color:color-mix(in oklab,var(--color-black)60%,transparent)}}.bg-blue-50{background-color:var(--color-blue-50)}.bg-blue-50\/80{background-color:#eff6ffcc}@supports (color:color-mix(in lab,red,red)){.bg-blue-50\/80{background-color:color-mix(in oklab,var(--color-blue-50)80%,transparent)}}.bg-blue-100{background-color:var(--color-blue-100)}.bg-blue-500{background-color:var(--color-blue-500)}.bg-blue-500\/5{background-color:#3080ff0d}@supports (color:color-mix(in lab,red,red)){.bg-blue-500\/5{background-color:color-mix(in oklab,var(--color-blue-500)5%,transparent)}}.bg-blue-600{background-color:var(--color-blue-600)}.bg-border{background-color:var(--border)}.bg-card{background-color:var(--card)}.bg-current{background-color:currentColor}.bg-destructive,.bg-destructive\/10{background-color:var(--destructive)}@supports (color:color-mix(in lab,red,red)){.bg-destructive\/10{background-color:color-mix(in oklab,var(--destructive)10%,transparent)}}.bg-foreground\/5{background-color:var(--foreground)}@supports (color:color-mix(in lab,red,red)){.bg-foreground\/5{background-color:color-mix(in oklab,var(--foreground)5%,transparent)}}.bg-foreground\/10{background-color:var(--foreground)}@supports (color:color-mix(in lab,red,red)){.bg-foreground\/10{background-color:color-mix(in oklab,var(--foreground)10%,transparent)}}.bg-gray-50{background-color:var(--color-gray-50)}.bg-gray-100{background-color:var(--color-gray-100)}.bg-gray-200{background-color:var(--color-gray-200)}.bg-gray-400{background-color:var(--color-gray-400)}.bg-gray-500\/10{background-color:#6a72821a}@supports (color:color-mix(in lab,red,red)){.bg-gray-500\/10{background-color:color-mix(in oklab,var(--color-gray-500)10%,transparent)}}.bg-gray-900\/90{background-color:#101828e6}@supports (color:color-mix(in lab,red,red)){.bg-gray-900\/90{background-color:color-mix(in oklab,var(--color-gray-900)90%,transparent)}}.bg-green-50{background-color:var(--color-green-50)}.bg-green-100{background-color:var(--color-green-100)}.bg-green-500{background-color:var(--color-green-500)}.bg-green-500\/5{background-color:#00c7580d}@supports (color:color-mix(in lab,red,red)){.bg-green-500\/5{background-color:color-mix(in oklab,var(--color-green-500)5%,transparent)}}.bg-green-500\/10{background-color:#00c7581a}@supports (color:color-mix(in lab,red,red)){.bg-green-500\/10{background-color:color-mix(in oklab,var(--color-green-500)10%,transparent)}}.bg-muted,.bg-muted\/30{background-color:var(--muted)}@supports (color:color-mix(in lab,red,red)){.bg-muted\/30{background-color:color-mix(in oklab,var(--muted)30%,transparent)}}.bg-muted\/50{background-color:var(--muted)}@supports (color:color-mix(in lab,red,red)){.bg-muted\/50{background-color:color-mix(in oklab,var(--muted)50%,transparent)}}.bg-orange-50{background-color:var(--color-orange-50)}.bg-orange-100{background-color:var(--color-orange-100)}.bg-orange-500{background-color:var(--color-orange-500)}.bg-orange-500\/10{background-color:#fe6e001a}@supports (color:color-mix(in lab,red,red)){.bg-orange-500\/10{background-color:color-mix(in oklab,var(--color-orange-500)10%,transparent)}}.bg-popover{background-color:var(--popover)}.bg-primary,.bg-primary\/10{background-color:var(--primary)}@supports (color:color-mix(in lab,red,red)){.bg-primary\/10{background-color:color-mix(in oklab,var(--primary)10%,transparent)}}.bg-primary\/30{background-color:var(--primary)}@supports (color:color-mix(in lab,red,red)){.bg-primary\/30{background-color:color-mix(in oklab,var(--primary)30%,transparent)}}.bg-primary\/40{background-color:var(--primary)}@supports (color:color-mix(in lab,red,red)){.bg-primary\/40{background-color:color-mix(in oklab,var(--primary)40%,transparent)}}.bg-purple-50{background-color:var(--color-purple-50)}.bg-purple-100{background-color:var(--color-purple-100)}.bg-red-50{background-color:var(--color-red-50)}.bg-red-100{background-color:var(--color-red-100)}.bg-red-500{background-color:var(--color-red-500)}.bg-red-500\/10{background-color:#fb2c361a}@supports (color:color-mix(in lab,red,red)){.bg-red-500\/10{background-color:color-mix(in oklab,var(--color-red-500)10%,transparent)}}.bg-secondary{background-color:var(--secondary)}.bg-transparent{background-color:#0000}.bg-white{background-color:var(--color-white)}.bg-white\/90{background-color:#ffffffe6}@supports (color:color-mix(in lab,red,red)){.bg-white\/90{background-color:color-mix(in oklab,var(--color-white)90%,transparent)}}.bg-yellow-100{background-color:var(--color-yellow-100)}.fill-current{fill:currentColor}.object-cover{object-fit:cover}.p-0{padding:calc(var(--spacing)*0)}.p-1{padding:calc(var(--spacing)*1)}.p-1\.5{padding:calc(var(--spacing)*1.5)}.p-2{padding:calc(var(--spacing)*2)}.p-3{padding:calc(var(--spacing)*3)}.p-4{padding:calc(var(--spacing)*4)}.p-6{padding:calc(var(--spacing)*6)}.p-8{padding:calc(var(--spacing)*8)}.p-\[1px\]{padding:1px}.px-1{padding-inline:calc(var(--spacing)*1)}.px-1\.5{padding-inline:calc(var(--spacing)*1.5)}.px-2{padding-inline:calc(var(--spacing)*2)}.px-2\.5{padding-inline:calc(var(--spacing)*2.5)}.px-3{padding-inline:calc(var(--spacing)*3)}.px-4{padding-inline:calc(var(--spacing)*4)}.px-6{padding-inline:calc(var(--spacing)*6)}.px-8{padding-inline:calc(var(--spacing)*8)}.py-0{padding-block:calc(var(--spacing)*0)}.py-0\.5{padding-block:calc(var(--spacing)*.5)}.py-1{padding-block:calc(var(--spacing)*1)}.py-1\.5{padding-block:calc(var(--spacing)*1.5)}.py-2{padding-block:calc(var(--spacing)*2)}.py-2\.5{padding-block:calc(var(--spacing)*2.5)}.py-3{padding-block:calc(var(--spacing)*3)}.py-4{padding-block:calc(var(--spacing)*4)}.py-6{padding-block:calc(var(--spacing)*6)}.py-8{padding-block:calc(var(--spacing)*8)}.pt-0{padding-top:calc(var(--spacing)*0)}.pt-1{padding-top:calc(var(--spacing)*1)}.pt-2{padding-top:calc(var(--spacing)*2)}.pt-3{padding-top:calc(var(--spacing)*3)}.pt-4{padding-top:calc(var(--spacing)*4)}.pt-6{padding-top:calc(var(--spacing)*6)}.pt-8{padding-top:calc(var(--spacing)*8)}.pr-2{padding-right:calc(var(--spacing)*2)}.pr-4{padding-right:calc(var(--spacing)*4)}.pr-8{padding-right:calc(var(--spacing)*8)}.pb-2{padding-bottom:calc(var(--spacing)*2)}.pb-3{padding-bottom:calc(var(--spacing)*3)}.pb-4{padding-bottom:calc(var(--spacing)*4)}.pb-6{padding-bottom:calc(var(--spacing)*6)}.pl-2{padding-left:calc(var(--spacing)*2)}.pl-3{padding-left:calc(var(--spacing)*3)}.pl-4{padding-left:calc(var(--spacing)*4)}.pl-8{padding-left:calc(var(--spacing)*8)}.text-center{text-align:center}.text-left{text-align:left}.font-mono{font-family:var(--font-mono)}.text-2xl{font-size:var(--text-2xl);line-height:var(--tw-leading,var(--text-2xl--line-height))}.text-base{font-size:var(--text-base);line-height:var(--tw-leading,var(--text-base--line-height))}.text-lg{font-size:var(--text-lg);line-height:var(--tw-leading,var(--text-lg--line-height))}.text-sm{font-size:var(--text-sm);line-height:var(--tw-leading,var(--text-sm--line-height))}.text-xl{font-size:var(--text-xl);line-height:var(--tw-leading,var(--text-xl--line-height))}.text-xs{font-size:var(--text-xs);line-height:var(--tw-leading,var(--text-xs--line-height))}.text-\[10px\]{font-size:10px}.leading-none{--tw-leading:1;line-height:1}.leading-relaxed{--tw-leading:var(--leading-relaxed);line-height:var(--leading-relaxed)}.leading-tight{--tw-leading:var(--leading-tight);line-height:var(--leading-tight)}.font-bold{--tw-font-weight:var(--font-weight-bold);font-weight:var(--font-weight-bold)}.font-medium{--tw-font-weight:var(--font-weight-medium);font-weight:var(--font-weight-medium)}.font-semibold{--tw-font-weight:var(--font-weight-semibold);font-weight:var(--font-weight-semibold)}.tracking-tight{--tw-tracking:var(--tracking-tight);letter-spacing:var(--tracking-tight)}.tracking-widest{--tw-tracking:var(--tracking-widest);letter-spacing:var(--tracking-widest)}.break-words{overflow-wrap:break-word}.break-all{word-break:break-all}.whitespace-nowrap{white-space:nowrap}.whitespace-pre-wrap{white-space:pre-wrap}.text-\[\#643FB2\]{color:#643fb2}.text-amber-500{color:var(--color-amber-500)}.text-amber-600{color:var(--color-amber-600)}.text-amber-700{color:var(--color-amber-700)}.text-amber-800{color:var(--color-amber-800)}.text-amber-900{color:var(--color-amber-900)}.text-blue-500{color:var(--color-blue-500)}.text-blue-600{color:var(--color-blue-600)}.text-blue-700{color:var(--color-blue-700)}.text-blue-800{color:var(--color-blue-800)}.text-blue-900{color:var(--color-blue-900)}.text-card-foreground{color:var(--card-foreground)}.text-current{color:currentColor}.text-destructive,.text-destructive\/70{color:var(--destructive)}@supports (color:color-mix(in lab,red,red)){.text-destructive\/70{color:color-mix(in oklab,var(--destructive)70%,transparent)}}.text-destructive\/90{color:var(--destructive)}@supports (color:color-mix(in lab,red,red)){.text-destructive\/90{color:color-mix(in oklab,var(--destructive)90%,transparent)}}.text-foreground{color:var(--foreground)}.text-gray-300{color:var(--color-gray-300)}.text-gray-400{color:var(--color-gray-400)}.text-gray-500{color:var(--color-gray-500)}.text-gray-600{color:var(--color-gray-600)}.text-gray-700{color:var(--color-gray-700)}.text-gray-900{color:var(--color-gray-900)}.text-green-500{color:var(--color-green-500)}.text-green-600{color:var(--color-green-600)}.text-green-700{color:var(--color-green-700)}.text-green-800{color:var(--color-green-800)}.text-green-900{color:var(--color-green-900)}.text-muted-foreground,.text-muted-foreground\/80{color:var(--muted-foreground)}@supports (color:color-mix(in lab,red,red)){.text-muted-foreground\/80{color:color-mix(in oklab,var(--muted-foreground)80%,transparent)}}.text-orange-500{color:var(--color-orange-500)}.text-orange-600{color:var(--color-orange-600)}.text-orange-800{color:var(--color-orange-800)}.text-popover-foreground{color:var(--popover-foreground)}.text-primary{color:var(--primary)}.text-primary-foreground{color:var(--primary-foreground)}.text-purple-500{color:var(--color-purple-500)}.text-purple-600{color:var(--color-purple-600)}.text-red-500{color:var(--color-red-500)}.text-red-600{color:var(--color-red-600)}.text-red-700{color:var(--color-red-700)}.text-red-800{color:var(--color-red-800)}.text-secondary-foreground{color:var(--secondary-foreground)}.text-white{color:var(--color-white)}.text-yellow-600{color:var(--color-yellow-600)}.text-yellow-700{color:var(--color-yellow-700)}.lowercase{text-transform:lowercase}.uppercase{text-transform:uppercase}.italic{font-style:italic}.underline-offset-4{text-underline-offset:4px}.opacity-0{opacity:0}.opacity-50{opacity:.5}.opacity-60{opacity:.6}.opacity-70{opacity:.7}.opacity-80{opacity:.8}.opacity-100{opacity:1}.shadow{--tw-shadow:0 1px 3px 0 var(--tw-shadow-color,#0000001a),0 1px 2px -1px var(--tw-shadow-color,#0000001a);box-shadow:var(--tw-inset-shadow),var(--tw-inset-ring-shadow),var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow)}.shadow-lg{--tw-shadow:0 10px 15px -3px var(--tw-shadow-color,#0000001a),0 4px 6px -4px var(--tw-shadow-color,#0000001a);box-shadow:var(--tw-inset-shadow),var(--tw-inset-ring-shadow),var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow)}.shadow-md{--tw-shadow:0 4px 6px -1px var(--tw-shadow-color,#0000001a),0 2px 4px -2px var(--tw-shadow-color,#0000001a);box-shadow:var(--tw-inset-shadow),var(--tw-inset-ring-shadow),var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow)}.shadow-sm{--tw-shadow:0 1px 3px 0 var(--tw-shadow-color,#0000001a),0 1px 2px -1px var(--tw-shadow-color,#0000001a);box-shadow:var(--tw-inset-shadow),var(--tw-inset-ring-shadow),var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow)}.shadow-xs{--tw-shadow:0 1px 2px 0 var(--tw-shadow-color,#0000000d);box-shadow:var(--tw-inset-shadow),var(--tw-inset-ring-shadow),var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow)}.ring-0{--tw-ring-shadow:var(--tw-ring-inset,)0 0 0 calc(0px + var(--tw-ring-offset-width))var(--tw-ring-color,currentcolor);box-shadow:var(--tw-inset-shadow),var(--tw-inset-ring-shadow),var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow)}.ring-2{--tw-ring-shadow:var(--tw-ring-inset,)0 0 0 calc(2px + var(--tw-ring-offset-width))var(--tw-ring-color,currentcolor);box-shadow:var(--tw-inset-shadow),var(--tw-inset-ring-shadow),var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow)}.shadow-\[\#643FB2\]\/20{--tw-shadow-color:#643fb233}@supports (color:color-mix(in lab,red,red)){.shadow-\[\#643FB2\]\/20{--tw-shadow-color:color-mix(in oklab,oklab(47.4316% .069152 -.159147/.2) var(--tw-shadow-alpha),transparent)}}.shadow-green-500\/20{--tw-shadow-color:#00c75833}@supports (color:color-mix(in lab,red,red)){.shadow-green-500\/20{--tw-shadow-color:color-mix(in oklab,color-mix(in oklab,var(--color-green-500)20%,transparent)var(--tw-shadow-alpha),transparent)}}.shadow-orange-500\/20{--tw-shadow-color:#fe6e0033}@supports (color:color-mix(in lab,red,red)){.shadow-orange-500\/20{--tw-shadow-color:color-mix(in oklab,color-mix(in oklab,var(--color-orange-500)20%,transparent)var(--tw-shadow-alpha),transparent)}}.shadow-primary\/25{--tw-shadow-color:var(--primary)}@supports (color:color-mix(in lab,red,red)){.shadow-primary\/25{--tw-shadow-color:color-mix(in oklab,color-mix(in oklab,var(--primary)25%,transparent)var(--tw-shadow-alpha),transparent)}}.shadow-red-500\/20{--tw-shadow-color:#fb2c3633}@supports (color:color-mix(in lab,red,red)){.shadow-red-500\/20{--tw-shadow-color:color-mix(in oklab,color-mix(in oklab,var(--color-red-500)20%,transparent)var(--tw-shadow-alpha),transparent)}}.ring-blue-500{--tw-ring-color:var(--color-blue-500)}.ring-offset-2{--tw-ring-offset-width:2px;--tw-ring-offset-shadow:var(--tw-ring-inset,)0 0 0 var(--tw-ring-offset-width)var(--tw-ring-offset-color)}.ring-offset-background{--tw-ring-offset-color:var(--background)}.outline-hidden{--tw-outline-style:none;outline-style:none}@media (forced-colors:active){.outline-hidden{outline-offset:2px;outline:2px solid #0000}}.outline{outline-style:var(--tw-outline-style);outline-width:1px}.drop-shadow-lg{--tw-drop-shadow-size:drop-shadow(0 4px 4px var(--tw-drop-shadow-color,#00000026));--tw-drop-shadow:drop-shadow(var(--drop-shadow-lg));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-sm{--tw-backdrop-blur:blur(var(--blur-sm));-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,outline-color,text-decoration-color,fill,stroke,--tw-gradient-from,--tw-gradient-via,--tw-gradient-to,opacity,box-shadow,transform,translate,scale,rotate,filter,-webkit-backdrop-filter,backdrop-filter,display,visibility,content-visibility,overlay,pointer-events;transition-timing-function:var(--tw-ease,var(--default-transition-timing-function));transition-duration:var(--tw-duration,var(--default-transition-duration))}.transition-\[color\,box-shadow\]{transition-property:color,box-shadow;transition-timing-function:var(--tw-ease,var(--default-transition-timing-function));transition-duration:var(--tw-duration,var(--default-transition-duration))}.transition-all{transition-property:all;transition-timing-function:var(--tw-ease,var(--default-transition-timing-function));transition-duration:var(--tw-duration,var(--default-transition-duration))}.transition-colors{transition-property:color,background-color,border-color,outline-color,text-decoration-color,fill,stroke,--tw-gradient-from,--tw-gradient-via,--tw-gradient-to;transition-timing-function:var(--tw-ease,var(--default-transition-timing-function));transition-duration:var(--tw-duration,var(--default-transition-duration))}.transition-opacity{transition-property:opacity;transition-timing-function:var(--tw-ease,var(--default-transition-timing-function));transition-duration:var(--tw-duration,var(--default-transition-duration))}.transition-shadow{transition-property:box-shadow;transition-timing-function:var(--tw-ease,var(--default-transition-timing-function));transition-duration:var(--tw-duration,var(--default-transition-duration))}.transition-transform{transition-property:transform,translate,scale,rotate;transition-timing-function:var(--tw-ease,var(--default-transition-timing-function));transition-duration:var(--tw-duration,var(--default-transition-duration))}.transition-none{transition-property:none}.duration-200{--tw-duration:.2s;transition-duration:.2s}.duration-300{--tw-duration:.3s;transition-duration:.3s}.ease-in-out{--tw-ease:var(--ease-in-out);transition-timing-function:var(--ease-in-out)}.ease-out{--tw-ease:var(--ease-out);transition-timing-function:var(--ease-out)}.outline-none{--tw-outline-style:none;outline-style:none}.select-none{-webkit-user-select:none;user-select:none}.\[animation-delay\:-0\.3s\]{animation-delay:-.3s}.\[animation-delay\:-0\.15s\]{animation-delay:-.15s}.fade-in{--tw-enter-opacity:0}.paused{animation-play-state:paused}.running{animation-play-state:running}.slide-in-from-bottom-2{--tw-enter-translate-y:calc(2*var(--spacing))}.group-open\:rotate-90:is(:where(.group):is([open],:popover-open,:open) *){rotate:90deg}.group-open\:rotate-180:is(:where(.group):is([open],:popover-open,:open) *){rotate:180deg}@media (hover:hover){.group-hover\:bg-primary:is(:where(.group):hover *){background-color:var(--primary)}.group-hover\:opacity-100:is(:where(.group):hover *){opacity:1}.group-hover\:shadow-md:is(:where(.group):hover *){--tw-shadow:0 4px 6px -1px var(--tw-shadow-color,#0000001a),0 2px 4px -2px var(--tw-shadow-color,#0000001a);box-shadow:var(--tw-inset-shadow),var(--tw-inset-ring-shadow),var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow)}.group-hover\:shadow-primary\/20:is(:where(.group):hover *){--tw-shadow-color:var(--primary)}@supports (color:color-mix(in lab,red,red)){.group-hover\:shadow-primary\/20:is(:where(.group):hover *){--tw-shadow-color:color-mix(in oklab,color-mix(in oklab,var(--primary)20%,transparent)var(--tw-shadow-alpha),transparent)}}}.group-data-\[disabled\=true\]\:pointer-events-none:is(:where(.group)[data-disabled=true] *){pointer-events:none}.group-data-\[disabled\=true\]\:opacity-50:is(:where(.group)[data-disabled=true] *){opacity:.5}.peer-disabled\:cursor-not-allowed:is(:where(.peer):disabled~*){cursor:not-allowed}.peer-disabled\:opacity-50:is(:where(.peer):disabled~*){opacity:.5}.selection\:bg-primary ::selection{background-color:var(--primary)}.selection\:bg-primary::selection{background-color:var(--primary)}.selection\:text-primary-foreground ::selection{color:var(--primary-foreground)}.selection\:text-primary-foreground::selection{color:var(--primary-foreground)}.file\:inline-flex::file-selector-button{display:inline-flex}.file\:h-7::file-selector-button{height:calc(var(--spacing)*7)}.file\:border-0::file-selector-button{border-style:var(--tw-border-style);border-width:0}.file\:bg-transparent::file-selector-button{background-color:#0000}.file\:text-sm::file-selector-button{font-size:var(--text-sm);line-height:var(--tw-leading,var(--text-sm--line-height))}.file\:font-medium::file-selector-button{--tw-font-weight:var(--font-weight-medium);font-weight:var(--font-weight-medium)}.file\:text-foreground::file-selector-button{color:var(--foreground)}.placeholder\:text-muted-foreground::placeholder{color:var(--muted-foreground)}.first\:mt-0:first-child{margin-top:calc(var(--spacing)*0)}.last\:border-r-0:last-child{border-right-style:var(--tw-border-style);border-right-width:0}.last\:border-b-0:last-child{border-bottom-style:var(--tw-border-style);border-bottom-width:0}@media (hover:hover){.hover\:border-gray-300:hover{border-color:var(--color-gray-300)}.hover\:border-muted-foreground\/30:hover{border-color:var(--muted-foreground)}@supports (color:color-mix(in lab,red,red)){.hover\:border-muted-foreground\/30:hover{border-color:color-mix(in oklab,var(--muted-foreground)30%,transparent)}}.hover\:bg-accent:hover,.hover\:bg-accent\/50:hover{background-color:var(--accent)}@supports (color:color-mix(in lab,red,red)){.hover\:bg-accent\/50:hover{background-color:color-mix(in oklab,var(--accent)50%,transparent)}}.hover\:bg-amber-100:hover{background-color:var(--color-amber-100)}.hover\:bg-blue-700:hover{background-color:var(--color-blue-700)}.hover\:bg-destructive\/80:hover{background-color:var(--destructive)}@supports (color:color-mix(in lab,red,red)){.hover\:bg-destructive\/80:hover{background-color:color-mix(in oklab,var(--destructive)80%,transparent)}}.hover\:bg-destructive\/90:hover{background-color:var(--destructive)}@supports (color:color-mix(in lab,red,red)){.hover\:bg-destructive\/90:hover{background-color:color-mix(in oklab,var(--destructive)90%,transparent)}}.hover\:bg-muted:hover,.hover\:bg-muted\/50:hover{background-color:var(--muted)}@supports (color:color-mix(in lab,red,red)){.hover\:bg-muted\/50:hover{background-color:color-mix(in oklab,var(--muted)50%,transparent)}}.hover\:bg-primary\/20:hover{background-color:var(--primary)}@supports (color:color-mix(in lab,red,red)){.hover\:bg-primary\/20:hover{background-color:color-mix(in oklab,var(--primary)20%,transparent)}}.hover\:bg-primary\/80:hover{background-color:var(--primary)}@supports (color:color-mix(in lab,red,red)){.hover\:bg-primary\/80:hover{background-color:color-mix(in oklab,var(--primary)80%,transparent)}}.hover\:bg-primary\/90:hover{background-color:var(--primary)}@supports (color:color-mix(in lab,red,red)){.hover\:bg-primary\/90:hover{background-color:color-mix(in oklab,var(--primary)90%,transparent)}}.hover\:bg-red-50:hover{background-color:var(--color-red-50)}.hover\:bg-secondary\/80:hover{background-color:var(--secondary)}@supports (color:color-mix(in lab,red,red)){.hover\:bg-secondary\/80:hover{background-color:color-mix(in oklab,var(--secondary)80%,transparent)}}.hover\:bg-white:hover{background-color:var(--color-white)}.hover\:text-accent-foreground:hover{color:var(--accent-foreground)}.hover\:text-destructive\/80:hover{color:var(--destructive)}@supports (color:color-mix(in lab,red,red)){.hover\:text-destructive\/80:hover{color:color-mix(in oklab,var(--destructive)80%,transparent)}}.hover\:text-foreground:hover{color:var(--foreground)}.hover\:text-red-600:hover{color:var(--color-red-600)}.hover\:underline:hover{text-decoration-line:underline}.hover\:opacity-70:hover{opacity:.7}.hover\:opacity-100:hover{opacity:1}.hover\:shadow-md:hover{--tw-shadow:0 4px 6px -1px var(--tw-shadow-color,#0000001a),0 2px 4px -2px var(--tw-shadow-color,#0000001a);box-shadow:var(--tw-inset-shadow),var(--tw-inset-ring-shadow),var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow)}}.focus\:bg-accent:focus{background-color:var(--accent)}.focus\:text-accent-foreground:focus{color:var(--accent-foreground)}.focus\:ring-2:focus{--tw-ring-shadow:var(--tw-ring-inset,)0 0 0 calc(2px + var(--tw-ring-offset-width))var(--tw-ring-color,currentcolor);box-shadow:var(--tw-inset-shadow),var(--tw-inset-ring-shadow),var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow)}.focus\:ring-ring:focus{--tw-ring-color:var(--ring)}.focus\:ring-offset-2:focus{--tw-ring-offset-width:2px;--tw-ring-offset-shadow:var(--tw-ring-inset,)0 0 0 var(--tw-ring-offset-width)var(--tw-ring-offset-color)}.focus\:outline-none:focus{--tw-outline-style:none;outline-style:none}.focus-visible\:border-ring:focus-visible{border-color:var(--ring)}.focus-visible\:ring-1:focus-visible{--tw-ring-shadow:var(--tw-ring-inset,)0 0 0 calc(1px + var(--tw-ring-offset-width))var(--tw-ring-color,currentcolor);box-shadow:var(--tw-inset-shadow),var(--tw-inset-ring-shadow),var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow)}.focus-visible\:ring-2:focus-visible{--tw-ring-shadow:var(--tw-ring-inset,)0 0 0 calc(2px + var(--tw-ring-offset-width))var(--tw-ring-color,currentcolor);box-shadow:var(--tw-inset-shadow),var(--tw-inset-ring-shadow),var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow)}.focus-visible\:ring-\[3px\]:focus-visible{--tw-ring-shadow:var(--tw-ring-inset,)0 0 0 calc(3px + var(--tw-ring-offset-width))var(--tw-ring-color,currentcolor);box-shadow:var(--tw-inset-shadow),var(--tw-inset-ring-shadow),var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow)}.focus-visible\:ring-destructive\/20:focus-visible{--tw-ring-color:var(--destructive)}@supports (color:color-mix(in lab,red,red)){.focus-visible\:ring-destructive\/20:focus-visible{--tw-ring-color:color-mix(in oklab,var(--destructive)20%,transparent)}}.focus-visible\:ring-ring:focus-visible,.focus-visible\:ring-ring\/50:focus-visible{--tw-ring-color:var(--ring)}@supports (color:color-mix(in lab,red,red)){.focus-visible\:ring-ring\/50:focus-visible{--tw-ring-color:color-mix(in oklab,var(--ring)50%,transparent)}}.focus-visible\:ring-offset-2:focus-visible{--tw-ring-offset-width:2px;--tw-ring-offset-shadow:var(--tw-ring-inset,)0 0 0 var(--tw-ring-offset-width)var(--tw-ring-offset-color)}.focus-visible\:ring-offset-background:focus-visible{--tw-ring-offset-color:var(--background)}.focus-visible\:outline-none:focus-visible{--tw-outline-style:none;outline-style:none}.disabled\:pointer-events-none:disabled{pointer-events:none}.disabled\:cursor-not-allowed:disabled{cursor:not-allowed}.disabled\:opacity-50:disabled{opacity:.5}.has-data-\[slot\=card-action\]\:grid-cols-\[1fr_auto\]:has([data-slot=card-action]){grid-template-columns:1fr auto}.has-\[\>svg\]\:px-2\.5:has(>svg){padding-inline:calc(var(--spacing)*2.5)}.has-\[\>svg\]\:px-3:has(>svg){padding-inline:calc(var(--spacing)*3)}.has-\[\>svg\]\:px-4:has(>svg){padding-inline:calc(var(--spacing)*4)}.aria-invalid\:border-destructive[aria-invalid=true]{border-color:var(--destructive)}.aria-invalid\:ring-destructive\/20[aria-invalid=true]{--tw-ring-color:var(--destructive)}@supports (color:color-mix(in lab,red,red)){.aria-invalid\:ring-destructive\/20[aria-invalid=true]{--tw-ring-color:color-mix(in oklab,var(--destructive)20%,transparent)}}.data-\[disabled\]\:pointer-events-none[data-disabled]{pointer-events:none}.data-\[disabled\]\:opacity-50[data-disabled]{opacity:.5}.data-\[inset\]\:pl-8[data-inset]{padding-left:calc(var(--spacing)*8)}.data-\[placeholder\]\:text-muted-foreground[data-placeholder]{color:var(--muted-foreground)}.data-\[side\=bottom\]\:translate-y-1[data-side=bottom]{--tw-translate-y:calc(var(--spacing)*1);translate:var(--tw-translate-x)var(--tw-translate-y)}.data-\[side\=bottom\]\:slide-in-from-top-2[data-side=bottom]{--tw-enter-translate-y:calc(2*var(--spacing)*-1)}.data-\[side\=left\]\:-translate-x-1[data-side=left]{--tw-translate-x:calc(var(--spacing)*-1);translate:var(--tw-translate-x)var(--tw-translate-y)}.data-\[side\=left\]\:slide-in-from-right-2[data-side=left]{--tw-enter-translate-x:calc(2*var(--spacing))}.data-\[side\=right\]\:translate-x-1[data-side=right]{--tw-translate-x:calc(var(--spacing)*1);translate:var(--tw-translate-x)var(--tw-translate-y)}.data-\[side\=right\]\:slide-in-from-left-2[data-side=right]{--tw-enter-translate-x:calc(2*var(--spacing)*-1)}.data-\[side\=top\]\:-translate-y-1[data-side=top]{--tw-translate-y:calc(var(--spacing)*-1);translate:var(--tw-translate-x)var(--tw-translate-y)}.data-\[side\=top\]\:slide-in-from-bottom-2[data-side=top]{--tw-enter-translate-y:calc(2*var(--spacing))}.data-\[size\=default\]\:h-9[data-size=default]{height:calc(var(--spacing)*9)}.data-\[size\=sm\]\:h-8[data-size=sm]{height:calc(var(--spacing)*8)}:is(.\*\:data-\[slot\=select-value\]\:line-clamp-1>*)[data-slot=select-value]{-webkit-line-clamp:1;-webkit-box-orient:vertical;display:-webkit-box;overflow:hidden}:is(.\*\:data-\[slot\=select-value\]\:flex>*)[data-slot=select-value]{display:flex}:is(.\*\:data-\[slot\=select-value\]\:items-center>*)[data-slot=select-value]{align-items:center}:is(.\*\:data-\[slot\=select-value\]\:gap-2>*)[data-slot=select-value]{gap:calc(var(--spacing)*2)}.data-\[state\=active\]\:bg-background[data-state=active]{background-color:var(--background)}.data-\[state\=active\]\:text-foreground[data-state=active]{color:var(--foreground)}.data-\[state\=active\]\:shadow[data-state=active]{--tw-shadow:0 1px 3px 0 var(--tw-shadow-color,#0000001a),0 1px 2px -1px var(--tw-shadow-color,#0000001a);box-shadow:var(--tw-inset-shadow),var(--tw-inset-ring-shadow),var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow)}.data-\[state\=checked\]\:translate-x-4[data-state=checked]{--tw-translate-x:calc(var(--spacing)*4);translate:var(--tw-translate-x)var(--tw-translate-y)}.data-\[state\=checked\]\:border-primary[data-state=checked]{border-color:var(--primary)}.data-\[state\=checked\]\:bg-primary[data-state=checked]{background-color:var(--primary)}.data-\[state\=checked\]\:text-primary-foreground[data-state=checked]{color:var(--primary-foreground)}.data-\[state\=closed\]\:animate-out[data-state=closed]{animation:exit var(--tw-animation-duration,var(--tw-duration,.15s))var(--tw-ease,ease)var(--tw-animation-delay,0s)var(--tw-animation-iteration-count,1)var(--tw-animation-direction,normal)var(--tw-animation-fill-mode,none)}.data-\[state\=closed\]\:fade-out-0[data-state=closed]{--tw-exit-opacity:0}.data-\[state\=closed\]\:zoom-out-95[data-state=closed]{--tw-exit-scale:.95}.data-\[state\=open\]\:animate-in[data-state=open]{animation:enter var(--tw-animation-duration,var(--tw-duration,.15s))var(--tw-ease,ease)var(--tw-animation-delay,0s)var(--tw-animation-iteration-count,1)var(--tw-animation-direction,normal)var(--tw-animation-fill-mode,none)}.data-\[state\=open\]\:bg-accent[data-state=open]{background-color:var(--accent)}.data-\[state\=open\]\:text-accent-foreground[data-state=open]{color:var(--accent-foreground)}.data-\[state\=open\]\:fade-in-0[data-state=open]{--tw-enter-opacity:0}.data-\[state\=open\]\:zoom-in-95[data-state=open]{--tw-enter-scale:.95}.data-\[state\=unchecked\]\:translate-x-0[data-state=unchecked]{--tw-translate-x:calc(var(--spacing)*0);translate:var(--tw-translate-x)var(--tw-translate-y)}.data-\[state\=unchecked\]\:bg-input[data-state=unchecked]{background-color:var(--input)}.data-\[variant\=destructive\]\:text-destructive[data-variant=destructive]{color:var(--destructive)}.data-\[variant\=destructive\]\:focus\:bg-destructive\/10[data-variant=destructive]:focus{background-color:var(--destructive)}@supports (color:color-mix(in lab,red,red)){.data-\[variant\=destructive\]\:focus\:bg-destructive\/10[data-variant=destructive]:focus{background-color:color-mix(in oklab,var(--destructive)10%,transparent)}}.data-\[variant\=destructive\]\:focus\:text-destructive[data-variant=destructive]:focus{color:var(--destructive)}@media (min-width:40rem){.sm\:col-span-2{grid-column:span 2/span 2}.sm\:w-64{width:calc(var(--spacing)*64)}.sm\:max-w-lg{max-width:var(--container-lg)}.sm\:flex-none{flex:none}.sm\:grid-cols-2{grid-template-columns:repeat(2,minmax(0,1fr))}.sm\:flex-row{flex-direction:row}.sm\:items-center{align-items:center}}@media (min-width:48rem){.md\:col-span-2{grid-column:span 2/span 2}.md\:col-start-2{grid-column-start:2}.md\:inline{display:inline}.md\:max-w-2xl{max-width:var(--container-2xl)}.md\:grid-cols-2{grid-template-columns:repeat(2,minmax(0,1fr))}.md\:gap-6{gap:calc(var(--spacing)*6)}.md\:gap-8{gap:calc(var(--spacing)*8)}.md\:text-sm{font-size:var(--text-sm);line-height:var(--tw-leading,var(--text-sm--line-height))}}@media (min-width:64rem){.lg\:col-span-3{grid-column:span 3/span 3}.lg\:max-w-4xl{max-width:var(--container-4xl)}.lg\:grid-cols-3{grid-template-columns:repeat(3,minmax(0,1fr))}.lg\:flex-row{flex-direction:row}.lg\:items-center{align-items:center}.lg\:justify-between{justify-content:space-between}}@media (min-width:80rem){.xl\:col-span-2{grid-column:span 2/span 2}.xl\:col-span-4{grid-column:span 4/span 4}.xl\:max-w-5xl{max-width:var(--container-5xl)}.xl\:grid-cols-4{grid-template-columns:repeat(4,minmax(0,1fr))}}.dark\:scale-0:is(.dark *){--tw-scale-x:0%;--tw-scale-y:0%;--tw-scale-z:0%;scale:var(--tw-scale-x)var(--tw-scale-y)}.dark\:scale-100:is(.dark *){--tw-scale-x:100%;--tw-scale-y:100%;--tw-scale-z:100%;scale:var(--tw-scale-x)var(--tw-scale-y)}.dark\:-rotate-90:is(.dark *){rotate:-90deg}.dark\:rotate-0:is(.dark *){rotate:none}.dark\:\!border-gray-500:is(.dark *){border-color:var(--color-gray-500)!important}.dark\:\!border-gray-600:is(.dark *){border-color:var(--color-gray-600)!important}.dark\:border-\[\#8B5CF6\]:is(.dark *){border-color:#8b5cf6}.dark\:border-\[\#8B5CF6\]\/20:is(.dark *){border-color:#8b5cf633}.dark\:border-\[\#8B5CF6\]\/30:is(.dark *){border-color:#8b5cf64d}.dark\:border-amber-800:is(.dark *){border-color:var(--color-amber-800)}.dark\:border-amber-900:is(.dark *){border-color:var(--color-amber-900)}.dark\:border-blue-400:is(.dark *){border-color:var(--color-blue-400)}.dark\:border-blue-500:is(.dark *){border-color:var(--color-blue-500)}.dark\:border-blue-700:is(.dark *){border-color:var(--color-blue-700)}.dark\:border-blue-800:is(.dark *){border-color:var(--color-blue-800)}.dark\:border-gray-500:is(.dark *){border-color:var(--color-gray-500)}.dark\:border-gray-600:is(.dark *){border-color:var(--color-gray-600)}.dark\:border-gray-700:is(.dark *){border-color:var(--color-gray-700)}.dark\:border-green-400:is(.dark *){border-color:var(--color-green-400)}.dark\:border-green-800:is(.dark *){border-color:var(--color-green-800)}.dark\:border-input:is(.dark *){border-color:var(--input)}.dark\:border-orange-400:is(.dark *){border-color:var(--color-orange-400)}.dark\:border-orange-800:is(.dark *){border-color:var(--color-orange-800)}.dark\:border-red-400:is(.dark *){border-color:var(--color-red-400)}.dark\:border-red-800:is(.dark *){border-color:var(--color-red-800)}.dark\:\!bg-gray-800\/90:is(.dark *){background-color:#1e2939e6!important}@supports (color:color-mix(in lab,red,red)){.dark\:\!bg-gray-800\/90:is(.dark *){background-color:color-mix(in oklab,var(--color-gray-800)90%,transparent)!important}}.dark\:bg-\[\#8B5CF6\]:is(.dark *){background-color:#8b5cf6}.dark\:bg-\[\#8B5CF6\]\/10:is(.dark *){background-color:#8b5cf61a}.dark\:bg-amber-950\/20:is(.dark *){background-color:#46190133}@supports (color:color-mix(in lab,red,red)){.dark\:bg-amber-950\/20:is(.dark *){background-color:color-mix(in oklab,var(--color-amber-950)20%,transparent)}}.dark\:bg-amber-950\/50:is(.dark *){background-color:#46190180}@supports (color:color-mix(in lab,red,red)){.dark\:bg-amber-950\/50:is(.dark *){background-color:color-mix(in oklab,var(--color-amber-950)50%,transparent)}}.dark\:bg-blue-500\/10:is(.dark *){background-color:#3080ff1a}@supports (color:color-mix(in lab,red,red)){.dark\:bg-blue-500\/10:is(.dark *){background-color:color-mix(in oklab,var(--color-blue-500)10%,transparent)}}.dark\:bg-blue-900:is(.dark *){background-color:var(--color-blue-900)}.dark\:bg-blue-900\/20:is(.dark *){background-color:#1c398e33}@supports (color:color-mix(in lab,red,red)){.dark\:bg-blue-900\/20:is(.dark *){background-color:color-mix(in oklab,var(--color-blue-900)20%,transparent)}}.dark\:bg-blue-950\/20:is(.dark *){background-color:#16245633}@supports (color:color-mix(in lab,red,red)){.dark\:bg-blue-950\/20:is(.dark *){background-color:color-mix(in oklab,var(--color-blue-950)20%,transparent)}}.dark\:bg-blue-950\/30:is(.dark *){background-color:#1624564d}@supports (color:color-mix(in lab,red,red)){.dark\:bg-blue-950\/30:is(.dark *){background-color:color-mix(in oklab,var(--color-blue-950)30%,transparent)}}.dark\:bg-blue-950\/40:is(.dark *){background-color:#16245666}@supports (color:color-mix(in lab,red,red)){.dark\:bg-blue-950\/40:is(.dark *){background-color:color-mix(in oklab,var(--color-blue-950)40%,transparent)}}.dark\:bg-blue-950\/50:is(.dark *){background-color:#16245680}@supports (color:color-mix(in lab,red,red)){.dark\:bg-blue-950\/50:is(.dark *){background-color:color-mix(in oklab,var(--color-blue-950)50%,transparent)}}.dark\:bg-card:is(.dark *){background-color:var(--card)}.dark\:bg-destructive\/60:is(.dark *){background-color:var(--destructive)}@supports (color:color-mix(in lab,red,red)){.dark\:bg-destructive\/60:is(.dark *){background-color:color-mix(in oklab,var(--destructive)60%,transparent)}}.dark\:bg-foreground\/10:is(.dark *){background-color:var(--foreground)}@supports (color:color-mix(in lab,red,red)){.dark\:bg-foreground\/10:is(.dark *){background-color:color-mix(in oklab,var(--foreground)10%,transparent)}}.dark\:bg-gray-500:is(.dark *){background-color:var(--color-gray-500)}.dark\:bg-gray-800:is(.dark *){background-color:var(--color-gray-800)}.dark\:bg-gray-800\/90:is(.dark *){background-color:#1e2939e6}@supports (color:color-mix(in lab,red,red)){.dark\:bg-gray-800\/90:is(.dark *){background-color:color-mix(in oklab,var(--color-gray-800)90%,transparent)}}.dark\:bg-gray-900:is(.dark *){background-color:var(--color-gray-900)}.dark\:bg-green-400:is(.dark *){background-color:var(--color-green-400)}.dark\:bg-green-500\/10:is(.dark *){background-color:#00c7581a}@supports (color:color-mix(in lab,red,red)){.dark\:bg-green-500\/10:is(.dark *){background-color:color-mix(in oklab,var(--color-green-500)10%,transparent)}}.dark\:bg-green-900:is(.dark *){background-color:var(--color-green-900)}.dark\:bg-green-950:is(.dark *){background-color:var(--color-green-950)}.dark\:bg-green-950\/20:is(.dark *){background-color:#032e1533}@supports (color:color-mix(in lab,red,red)){.dark\:bg-green-950\/20:is(.dark *){background-color:color-mix(in oklab,var(--color-green-950)20%,transparent)}}.dark\:bg-green-950\/50:is(.dark *){background-color:#032e1580}@supports (color:color-mix(in lab,red,red)){.dark\:bg-green-950\/50:is(.dark *){background-color:color-mix(in oklab,var(--color-green-950)50%,transparent)}}.dark\:bg-input\/30:is(.dark *){background-color:var(--input)}@supports (color:color-mix(in lab,red,red)){.dark\:bg-input\/30:is(.dark *){background-color:color-mix(in oklab,var(--input)30%,transparent)}}.dark\:bg-orange-400:is(.dark *){background-color:var(--color-orange-400)}.dark\:bg-orange-900:is(.dark *){background-color:var(--color-orange-900)}.dark\:bg-orange-950:is(.dark *){background-color:var(--color-orange-950)}.dark\:bg-orange-950\/50:is(.dark *){background-color:#44130680}@supports (color:color-mix(in lab,red,red)){.dark\:bg-orange-950\/50:is(.dark *){background-color:color-mix(in oklab,var(--color-orange-950)50%,transparent)}}.dark\:bg-purple-900:is(.dark *){background-color:var(--color-purple-900)}.dark\:bg-red-400:is(.dark *){background-color:var(--color-red-400)}.dark\:bg-red-900:is(.dark *){background-color:var(--color-red-900)}.dark\:bg-red-950:is(.dark *){background-color:var(--color-red-950)}.dark\:bg-red-950\/20:is(.dark *){background-color:#46080933}@supports (color:color-mix(in lab,red,red)){.dark\:bg-red-950\/20:is(.dark *){background-color:color-mix(in oklab,var(--color-red-950)20%,transparent)}}.dark\:text-\[\#8B5CF6\]:is(.dark *){color:#8b5cf6}.dark\:text-amber-100:is(.dark *){color:var(--color-amber-100)}.dark\:text-amber-200:is(.dark *){color:var(--color-amber-200)}.dark\:text-amber-300:is(.dark *){color:var(--color-amber-300)}.dark\:text-amber-400:is(.dark *){color:var(--color-amber-400)}.dark\:text-amber-500:is(.dark *){color:var(--color-amber-500)}.dark\:text-blue-100:is(.dark *){color:var(--color-blue-100)}.dark\:text-blue-200:is(.dark *){color:var(--color-blue-200)}.dark\:text-blue-300:is(.dark *){color:var(--color-blue-300)}.dark\:text-blue-400:is(.dark *){color:var(--color-blue-400)}.dark\:text-blue-500:is(.dark *){color:var(--color-blue-500)}.dark\:text-gray-100:is(.dark *){color:var(--color-gray-100)}.dark\:text-gray-300:is(.dark *){color:var(--color-gray-300)}.dark\:text-gray-400:is(.dark *){color:var(--color-gray-400)}.dark\:text-green-100:is(.dark *){color:var(--color-green-100)}.dark\:text-green-200:is(.dark *){color:var(--color-green-200)}.dark\:text-green-300:is(.dark *){color:var(--color-green-300)}.dark\:text-green-400:is(.dark *){color:var(--color-green-400)}.dark\:text-orange-200:is(.dark *){color:var(--color-orange-200)}.dark\:text-orange-400:is(.dark *){color:var(--color-orange-400)}.dark\:text-purple-400:is(.dark *){color:var(--color-purple-400)}.dark\:text-red-200:is(.dark *){color:var(--color-red-200)}.dark\:text-red-300:is(.dark *){color:var(--color-red-300)}.dark\:text-red-400:is(.dark *){color:var(--color-red-400)}.dark\:text-yellow-400:is(.dark *){color:var(--color-yellow-400)}.dark\:opacity-30:is(.dark *){opacity:.3}@media (hover:hover){.dark\:hover\:border-gray-600:is(.dark *):hover{border-color:var(--color-gray-600)}.dark\:hover\:bg-accent\/50:is(.dark *):hover{background-color:var(--accent)}@supports (color:color-mix(in lab,red,red)){.dark\:hover\:bg-accent\/50:is(.dark *):hover{background-color:color-mix(in oklab,var(--accent)50%,transparent)}}.dark\:hover\:bg-amber-950\/30:is(.dark *):hover{background-color:#4619014d}@supports (color:color-mix(in lab,red,red)){.dark\:hover\:bg-amber-950\/30:is(.dark *):hover{background-color:color-mix(in oklab,var(--color-amber-950)30%,transparent)}}.dark\:hover\:bg-gray-800:is(.dark *):hover{background-color:var(--color-gray-800)}.dark\:hover\:bg-input\/50:is(.dark *):hover{background-color:var(--input)}@supports (color:color-mix(in lab,red,red)){.dark\:hover\:bg-input\/50:is(.dark *):hover{background-color:color-mix(in oklab,var(--input)50%,transparent)}}.dark\:hover\:bg-red-900\/20:is(.dark *):hover{background-color:#82181a33}@supports (color:color-mix(in lab,red,red)){.dark\:hover\:bg-red-900\/20:is(.dark *):hover{background-color:color-mix(in oklab,var(--color-red-900)20%,transparent)}}}.dark\:focus-visible\:ring-destructive\/40:is(.dark *):focus-visible{--tw-ring-color:var(--destructive)}@supports (color:color-mix(in lab,red,red)){.dark\:focus-visible\:ring-destructive\/40:is(.dark *):focus-visible{--tw-ring-color:color-mix(in oklab,var(--destructive)40%,transparent)}}.dark\:aria-invalid\:ring-destructive\/40:is(.dark *)[aria-invalid=true]{--tw-ring-color:var(--destructive)}@supports (color:color-mix(in lab,red,red)){.dark\:aria-invalid\:ring-destructive\/40:is(.dark *)[aria-invalid=true]{--tw-ring-color:color-mix(in oklab,var(--destructive)40%,transparent)}}.dark\:data-\[state\=checked\]\:bg-primary:is(.dark *)[data-state=checked]{background-color:var(--primary)}.dark\:data-\[variant\=destructive\]\:focus\:bg-destructive\/20:is(.dark *)[data-variant=destructive]:focus{background-color:var(--destructive)}@supports (color:color-mix(in lab,red,red)){.dark\:data-\[variant\=destructive\]\:focus\:bg-destructive\/20:is(.dark *)[data-variant=destructive]:focus{background-color:color-mix(in oklab,var(--destructive)20%,transparent)}}.\[\&_p\]\:leading-relaxed p{--tw-leading:var(--leading-relaxed);line-height:var(--leading-relaxed)}.\[\&_svg\]\:pointer-events-none svg{pointer-events:none}.\[\&_svg\]\:shrink-0 svg{flex-shrink:0}.\[\&_svg\:not\(\[class\*\=\'size-\'\]\)\]\:size-4 svg:not([class*=size-]){width:calc(var(--spacing)*4);height:calc(var(--spacing)*4)}.\[\&_svg\:not\(\[class\*\=\'text-\'\]\)\]\:text-muted-foreground svg:not([class*=text-]){color:var(--muted-foreground)}.\[\.border-b\]\:pb-6.border-b{padding-bottom:calc(var(--spacing)*6)}.\[\.border-t\]\:pt-6.border-t{padding-top:calc(var(--spacing)*6)}:is(.\*\:\[span\]\:last\:flex>*):is(span):last-child{display:flex}:is(.\*\:\[span\]\:last\:items-center>*):is(span):last-child{align-items:center}:is(.\*\:\[span\]\:last\:gap-2>*):is(span):last-child{gap:calc(var(--spacing)*2)}:is(.data-\[variant\=destructive\]\:\*\:\[svg\]\:\!text-destructive[data-variant=destructive]>*):is(svg){color:var(--destructive)!important}.\[\&\>svg\]\:absolute>svg{position:absolute}.\[\&\>svg\]\:top-4>svg{top:calc(var(--spacing)*4)}.\[\&\>svg\]\:left-4>svg{left:calc(var(--spacing)*4)}.\[\&\>svg\]\:text-foreground>svg{color:var(--foreground)}.\[\&\>svg\+div\]\:translate-y-\[-3px\]>svg+div{--tw-translate-y:-3px;translate:var(--tw-translate-x)var(--tw-translate-y)}.\[\&\>svg\~\*\]\:pl-7>svg~*{padding-left:calc(var(--spacing)*7)}}@property --tw-animation-delay{syntax:"*";inherits:false;initial-value:0s}@property --tw-animation-direction{syntax:"*";inherits:false;initial-value:normal}@property --tw-animation-duration{syntax:"*";inherits:false}@property --tw-animation-fill-mode{syntax:"*";inherits:false;initial-value:none}@property --tw-animation-iteration-count{syntax:"*";inherits:false;initial-value:1}@property --tw-enter-blur{syntax:"*";inherits:false;initial-value:0}@property --tw-enter-opacity{syntax:"*";inherits:false;initial-value:1}@property --tw-enter-rotate{syntax:"*";inherits:false;initial-value:0}@property --tw-enter-scale{syntax:"*";inherits:false;initial-value:1}@property --tw-enter-translate-x{syntax:"*";inherits:false;initial-value:0}@property --tw-enter-translate-y{syntax:"*";inherits:false;initial-value:0}@property --tw-exit-blur{syntax:"*";inherits:false;initial-value:0}@property --tw-exit-opacity{syntax:"*";inherits:false;initial-value:1}@property --tw-exit-rotate{syntax:"*";inherits:false;initial-value:0}@property --tw-exit-scale{syntax:"*";inherits:false;initial-value:1}@property --tw-exit-translate-x{syntax:"*";inherits:false;initial-value:0}@property --tw-exit-translate-y{syntax:"*";inherits:false;initial-value:0}:root{--radius:.625rem;--background:oklch(100% 0 0);--foreground:oklch(14.5% 0 0);--card:oklch(100% 0 0);--card-foreground:oklch(14.5% 0 0);--popover:oklch(100% 0 0);--popover-foreground:oklch(14.5% 0 0);--primary:oklch(48% .18 290);--primary-foreground:oklch(98.5% 0 0);--secondary:oklch(97% 0 0);--secondary-foreground:oklch(20.5% 0 0);--muted:oklch(97% 0 0);--muted-foreground:oklch(55.6% 0 0);--accent:oklch(97% 0 0);--accent-foreground:oklch(20.5% 0 0);--destructive:oklch(57.7% .245 27.325);--border:oklch(92.2% 0 0);--input:oklch(92.2% 0 0);--ring:oklch(70.8% 0 0);--chart-1:oklch(64.6% .222 41.116);--chart-2:oklch(60% .118 184.704);--chart-3:oklch(39.8% .07 227.392);--chart-4:oklch(82.8% .189 84.429);--chart-5:oklch(76.9% .188 70.08);--sidebar:oklch(98.5% 0 0);--sidebar-foreground:oklch(14.5% 0 0);--sidebar-primary:oklch(20.5% 0 0);--sidebar-primary-foreground:oklch(98.5% 0 0);--sidebar-accent:oklch(97% 0 0);--sidebar-accent-foreground:oklch(20.5% 0 0);--sidebar-border:oklch(92.2% 0 0);--sidebar-ring:oklch(70.8% 0 0)}.dark{--background:oklch(14.5% 0 0);--foreground:oklch(98.5% 0 0);--card:oklch(20.5% 0 0);--card-foreground:oklch(98.5% 0 0);--popover:oklch(20.5% 0 0);--popover-foreground:oklch(98.5% 0 0);--primary:oklch(62% .2 290);--primary-foreground:oklch(98.5% 0 0);--secondary:oklch(26.9% 0 0);--secondary-foreground:oklch(98.5% 0 0);--muted:oklch(26.9% 0 0);--muted-foreground:oklch(70.8% 0 0);--accent:oklch(26.9% 0 0);--accent-foreground:oklch(98.5% 0 0);--destructive:oklch(70.4% .191 22.216);--border:oklch(100% 0 0/.1);--input:oklch(100% 0 0/.15);--ring:oklch(55.6% 0 0);--chart-1:oklch(48.8% .243 264.376);--chart-2:oklch(69.6% .17 162.48);--chart-3:oklch(76.9% .188 70.08);--chart-4:oklch(62.7% .265 303.9);--chart-5:oklch(64.5% .246 16.439);--sidebar:oklch(20.5% 0 0);--sidebar-foreground:oklch(98.5% 0 0);--sidebar-primary:oklch(48.8% .243 264.376);--sidebar-primary-foreground:oklch(98.5% 0 0);--sidebar-accent:oklch(26.9% 0 0);--sidebar-accent-foreground:oklch(98.5% 0 0);--sidebar-border:oklch(100% 0 0/.1);--sidebar-ring:oklch(55.6% 0 0)}.workflow-chat-view .border-green-200{border-color:var(--color-emerald-200)}.workflow-chat-view .bg-green-50{background-color:var(--color-emerald-50)}.workflow-chat-view .bg-green-100{background-color:var(--color-emerald-100)}.workflow-chat-view .text-green-600{color:var(--color-emerald-600)}.workflow-chat-view .text-green-700{color:var(--color-emerald-700)}.workflow-chat-view .text-green-800{color:var(--color-emerald-800)}@property --tw-translate-x{syntax:"*";inherits:false;initial-value:0}@property --tw-translate-y{syntax:"*";inherits:false;initial-value:0}@property --tw-translate-z{syntax:"*";inherits:false;initial-value:0}@property --tw-scale-x{syntax:"*";inherits:false;initial-value:1}@property --tw-scale-y{syntax:"*";inherits:false;initial-value:1}@property --tw-scale-z{syntax:"*";inherits:false;initial-value:1}@property --tw-rotate-x{syntax:"*";inherits:false}@property --tw-rotate-y{syntax:"*";inherits:false}@property --tw-rotate-z{syntax:"*";inherits:false}@property --tw-skew-x{syntax:"*";inherits:false}@property --tw-skew-y{syntax:"*";inherits:false}@property --tw-space-y-reverse{syntax:"*";inherits:false;initial-value:0}@property --tw-space-x-reverse{syntax:"*";inherits:false;initial-value:0}@property --tw-border-style{syntax:"*";inherits:false;initial-value:solid}@property --tw-leading{syntax:"*";inherits:false}@property --tw-font-weight{syntax:"*";inherits:false}@property --tw-tracking{syntax:"*";inherits:false}@property --tw-shadow{syntax:"*";inherits:false;initial-value:0 0 #0000}@property --tw-shadow-color{syntax:"*";inherits:false}@property --tw-shadow-alpha{syntax:"";inherits:false;initial-value:100%}@property --tw-inset-shadow{syntax:"*";inherits:false;initial-value:0 0 #0000}@property --tw-inset-shadow-color{syntax:"*";inherits:false}@property --tw-inset-shadow-alpha{syntax:"";inherits:false;initial-value:100%}@property --tw-ring-color{syntax:"*";inherits:false}@property --tw-ring-shadow{syntax:"*";inherits:false;initial-value:0 0 #0000}@property --tw-inset-ring-color{syntax:"*";inherits:false}@property --tw-inset-ring-shadow{syntax:"*";inherits:false;initial-value:0 0 #0000}@property --tw-ring-inset{syntax:"*";inherits:false}@property --tw-ring-offset-width{syntax:"";inherits:false;initial-value:0}@property --tw-ring-offset-color{syntax:"*";inherits:false;initial-value:#fff}@property --tw-ring-offset-shadow{syntax:"*";inherits:false;initial-value:0 0 #0000}@property --tw-outline-style{syntax:"*";inherits:false;initial-value:solid}@property --tw-blur{syntax:"*";inherits:false}@property --tw-brightness{syntax:"*";inherits:false}@property --tw-contrast{syntax:"*";inherits:false}@property --tw-grayscale{syntax:"*";inherits:false}@property --tw-hue-rotate{syntax:"*";inherits:false}@property --tw-invert{syntax:"*";inherits:false}@property --tw-opacity{syntax:"*";inherits:false}@property --tw-saturate{syntax:"*";inherits:false}@property --tw-sepia{syntax:"*";inherits:false}@property --tw-drop-shadow{syntax:"*";inherits:false}@property --tw-drop-shadow-color{syntax:"*";inherits:false}@property --tw-drop-shadow-alpha{syntax:"";inherits:false;initial-value:100%}@property --tw-drop-shadow-size{syntax:"*";inherits:false}@property --tw-backdrop-blur{syntax:"*";inherits:false}@property --tw-backdrop-brightness{syntax:"*";inherits:false}@property --tw-backdrop-contrast{syntax:"*";inherits:false}@property --tw-backdrop-grayscale{syntax:"*";inherits:false}@property --tw-backdrop-hue-rotate{syntax:"*";inherits:false}@property --tw-backdrop-invert{syntax:"*";inherits:false}@property --tw-backdrop-opacity{syntax:"*";inherits:false}@property --tw-backdrop-saturate{syntax:"*";inherits:false}@property --tw-backdrop-sepia{syntax:"*";inherits:false}@property --tw-duration{syntax:"*";inherits:false}@property --tw-ease{syntax:"*";inherits:false}@keyframes spin{to{transform:rotate(360deg)}}@keyframes pulse{50%{opacity:.5}}@keyframes bounce{0%,to{animation-timing-function:cubic-bezier(.8,0,1,1);transform:translateY(-25%)}50%{animation-timing-function:cubic-bezier(0,0,.2,1);transform:none}}@keyframes enter{0%{opacity:var(--tw-enter-opacity,1);transform:translate3d(var(--tw-enter-translate-x,0),var(--tw-enter-translate-y,0),0)scale3d(var(--tw-enter-scale,1),var(--tw-enter-scale,1),var(--tw-enter-scale,1))rotate(var(--tw-enter-rotate,0));filter:blur(var(--tw-enter-blur,0))}}@keyframes exit{to{opacity:var(--tw-exit-opacity,1);transform:translate3d(var(--tw-exit-translate-x,0),var(--tw-exit-translate-y,0),0)scale3d(var(--tw-exit-scale,1),var(--tw-exit-scale,1),var(--tw-exit-scale,1))rotate(var(--tw-exit-rotate,0));filter:blur(var(--tw-exit-blur,0))}}.react-flow{direction:ltr;--xy-edge-stroke-default: #b1b1b7;--xy-edge-stroke-width-default: 1;--xy-edge-stroke-selected-default: #555;--xy-connectionline-stroke-default: #b1b1b7;--xy-connectionline-stroke-width-default: 1;--xy-attribution-background-color-default: rgba(255, 255, 255, .5);--xy-minimap-background-color-default: #fff;--xy-minimap-mask-background-color-default: rgba(240, 240, 240, .6);--xy-minimap-mask-stroke-color-default: transparent;--xy-minimap-mask-stroke-width-default: 1;--xy-minimap-node-background-color-default: #e2e2e2;--xy-minimap-node-stroke-color-default: transparent;--xy-minimap-node-stroke-width-default: 2;--xy-background-color-default: transparent;--xy-background-pattern-dots-color-default: #91919a;--xy-background-pattern-lines-color-default: #eee;--xy-background-pattern-cross-color-default: #e2e2e2;background-color:var(--xy-background-color, var(--xy-background-color-default));--xy-node-color-default: inherit;--xy-node-border-default: 1px solid #1a192b;--xy-node-background-color-default: #fff;--xy-node-group-background-color-default: rgba(240, 240, 240, .25);--xy-node-boxshadow-hover-default: 0 1px 4px 1px rgba(0, 0, 0, .08);--xy-node-boxshadow-selected-default: 0 0 0 .5px #1a192b;--xy-node-border-radius-default: 3px;--xy-handle-background-color-default: #1a192b;--xy-handle-border-color-default: #fff;--xy-selection-background-color-default: rgba(0, 89, 220, .08);--xy-selection-border-default: 1px dotted rgba(0, 89, 220, .8);--xy-controls-button-background-color-default: #fefefe;--xy-controls-button-background-color-hover-default: #f4f4f4;--xy-controls-button-color-default: inherit;--xy-controls-button-color-hover-default: inherit;--xy-controls-button-border-color-default: #eee;--xy-controls-box-shadow-default: 0 0 2px 1px rgba(0, 0, 0, .08);--xy-edge-label-background-color-default: #ffffff;--xy-edge-label-color-default: inherit;--xy-resize-background-color-default: #3367d9}.react-flow.dark{--xy-edge-stroke-default: #3e3e3e;--xy-edge-stroke-width-default: 1;--xy-edge-stroke-selected-default: #727272;--xy-connectionline-stroke-default: #b1b1b7;--xy-connectionline-stroke-width-default: 1;--xy-attribution-background-color-default: rgba(150, 150, 150, .25);--xy-minimap-background-color-default: #141414;--xy-minimap-mask-background-color-default: rgba(60, 60, 60, .6);--xy-minimap-mask-stroke-color-default: transparent;--xy-minimap-mask-stroke-width-default: 1;--xy-minimap-node-background-color-default: #2b2b2b;--xy-minimap-node-stroke-color-default: transparent;--xy-minimap-node-stroke-width-default: 2;--xy-background-color-default: #141414;--xy-background-pattern-dots-color-default: #777;--xy-background-pattern-lines-color-default: #777;--xy-background-pattern-cross-color-default: #777;--xy-node-color-default: #f8f8f8;--xy-node-border-default: 1px solid #3c3c3c;--xy-node-background-color-default: #1e1e1e;--xy-node-group-background-color-default: rgba(240, 240, 240, .25);--xy-node-boxshadow-hover-default: 0 1px 4px 1px rgba(255, 255, 255, .08);--xy-node-boxshadow-selected-default: 0 0 0 .5px #999;--xy-handle-background-color-default: #bebebe;--xy-handle-border-color-default: #1e1e1e;--xy-selection-background-color-default: rgba(200, 200, 220, .08);--xy-selection-border-default: 1px dotted rgba(200, 200, 220, .8);--xy-controls-button-background-color-default: #2b2b2b;--xy-controls-button-background-color-hover-default: #3e3e3e;--xy-controls-button-color-default: #f8f8f8;--xy-controls-button-color-hover-default: #fff;--xy-controls-button-border-color-default: #5b5b5b;--xy-controls-box-shadow-default: 0 0 2px 1px rgba(0, 0, 0, .08);--xy-edge-label-background-color-default: #141414;--xy-edge-label-color-default: #f8f8f8}.react-flow__background{background-color:var(--xy-background-color-props, var(--xy-background-color, var(--xy-background-color-default)));pointer-events:none;z-index:-1}.react-flow__container{position:absolute;width:100%;height:100%;top:0;left:0}.react-flow__pane{z-index:1}.react-flow__pane.draggable{cursor:grab}.react-flow__pane.dragging{cursor:grabbing}.react-flow__pane.selection{cursor:pointer}.react-flow__viewport{transform-origin:0 0;z-index:2;pointer-events:none}.react-flow__renderer{z-index:4}.react-flow__selection{z-index:6}.react-flow__nodesselection-rect:focus,.react-flow__nodesselection-rect:focus-visible{outline:none}.react-flow__edge-path{stroke:var(--xy-edge-stroke, var(--xy-edge-stroke-default));stroke-width:var(--xy-edge-stroke-width, var(--xy-edge-stroke-width-default));fill:none}.react-flow__connection-path{stroke:var(--xy-connectionline-stroke, var(--xy-connectionline-stroke-default));stroke-width:var(--xy-connectionline-stroke-width, var(--xy-connectionline-stroke-width-default));fill:none}.react-flow .react-flow__edges{position:absolute}.react-flow .react-flow__edges svg{overflow:visible;position:absolute;pointer-events:none}.react-flow__edge{pointer-events:visibleStroke}.react-flow__edge.selectable{cursor:pointer}.react-flow__edge.animated path{stroke-dasharray:5;animation:dashdraw .5s linear infinite}.react-flow__edge.animated path.react-flow__edge-interaction{stroke-dasharray:none;animation:none}.react-flow__edge.inactive{pointer-events:none}.react-flow__edge.selected,.react-flow__edge:focus,.react-flow__edge:focus-visible{outline:none}.react-flow__edge.selected .react-flow__edge-path,.react-flow__edge.selectable:focus .react-flow__edge-path,.react-flow__edge.selectable:focus-visible .react-flow__edge-path{stroke:var(--xy-edge-stroke-selected, var(--xy-edge-stroke-selected-default))}.react-flow__edge-textwrapper{pointer-events:all}.react-flow__edge .react-flow__edge-text{pointer-events:none;-webkit-user-select:none;-moz-user-select:none;user-select:none}.react-flow__arrowhead polyline{stroke:var(--xy-edge-stroke, var(--xy-edge-stroke-default))}.react-flow__arrowhead polyline.arrowclosed{fill:var(--xy-edge-stroke, var(--xy-edge-stroke-default))}.react-flow__connection{pointer-events:none}.react-flow__connection .animated{stroke-dasharray:5;animation:dashdraw .5s linear infinite}svg.react-flow__connectionline{z-index:1001;overflow:visible;position:absolute}.react-flow__nodes{pointer-events:none;transform-origin:0 0}.react-flow__node{position:absolute;-webkit-user-select:none;-moz-user-select:none;user-select:none;pointer-events:all;transform-origin:0 0;box-sizing:border-box;cursor:default}.react-flow__node.selectable{cursor:pointer}.react-flow__node.draggable{cursor:grab;pointer-events:all}.react-flow__node.draggable.dragging{cursor:grabbing}.react-flow__nodesselection{z-index:3;transform-origin:left top;pointer-events:none}.react-flow__nodesselection-rect{position:absolute;pointer-events:all;cursor:grab}.react-flow__handle{position:absolute;pointer-events:none;min-width:5px;min-height:5px;width:6px;height:6px;background-color:var(--xy-handle-background-color, var(--xy-handle-background-color-default));border:1px solid var(--xy-handle-border-color, var(--xy-handle-border-color-default));border-radius:100%}.react-flow__handle.connectingfrom{pointer-events:all}.react-flow__handle.connectionindicator{pointer-events:all;cursor:crosshair}.react-flow__handle-bottom{top:auto;left:50%;bottom:0;transform:translate(-50%,50%)}.react-flow__handle-top{top:0;left:50%;transform:translate(-50%,-50%)}.react-flow__handle-left{top:50%;left:0;transform:translate(-50%,-50%)}.react-flow__handle-right{top:50%;right:0;transform:translate(50%,-50%)}.react-flow__edgeupdater{cursor:move;pointer-events:all}.react-flow__pane.selection .react-flow__panel{pointer-events:none}.react-flow__panel{position:absolute;z-index:5;margin:15px}.react-flow__panel.top{top:0}.react-flow__panel.bottom{bottom:0}.react-flow__panel.top.center,.react-flow__panel.bottom.center{left:50%;transform:translate(-15px) translate(-50%)}.react-flow__panel.left{left:0}.react-flow__panel.right{right:0}.react-flow__panel.left.center,.react-flow__panel.right.center{top:50%;transform:translateY(-15px) translateY(-50%)}.react-flow__attribution{font-size:10px;background:var(--xy-attribution-background-color, var(--xy-attribution-background-color-default));padding:2px 3px;margin:0}.react-flow__attribution a{text-decoration:none;color:#999}@keyframes dashdraw{0%{stroke-dashoffset:10}}.react-flow__edgelabel-renderer{position:absolute;width:100%;height:100%;pointer-events:none;-webkit-user-select:none;-moz-user-select:none;user-select:none;left:0;top:0}.react-flow__viewport-portal{position:absolute;width:100%;height:100%;left:0;top:0;-webkit-user-select:none;-moz-user-select:none;user-select:none}.react-flow__minimap{background:var( --xy-minimap-background-color-props, var(--xy-minimap-background-color, var(--xy-minimap-background-color-default)) )}.react-flow__minimap-svg{display:block}.react-flow__minimap-mask{fill:var( --xy-minimap-mask-background-color-props, var(--xy-minimap-mask-background-color, var(--xy-minimap-mask-background-color-default)) );stroke:var( --xy-minimap-mask-stroke-color-props, var(--xy-minimap-mask-stroke-color, var(--xy-minimap-mask-stroke-color-default)) );stroke-width:var( --xy-minimap-mask-stroke-width-props, var(--xy-minimap-mask-stroke-width, var(--xy-minimap-mask-stroke-width-default)) )}.react-flow__minimap-node{fill:var( --xy-minimap-node-background-color-props, var(--xy-minimap-node-background-color, var(--xy-minimap-node-background-color-default)) );stroke:var( --xy-minimap-node-stroke-color-props, var(--xy-minimap-node-stroke-color, var(--xy-minimap-node-stroke-color-default)) );stroke-width:var( --xy-minimap-node-stroke-width-props, var(--xy-minimap-node-stroke-width, var(--xy-minimap-node-stroke-width-default)) )}.react-flow__background-pattern.dots{fill:var( --xy-background-pattern-color-props, var(--xy-background-pattern-color, var(--xy-background-pattern-dots-color-default)) )}.react-flow__background-pattern.lines{stroke:var( --xy-background-pattern-color-props, var(--xy-background-pattern-color, var(--xy-background-pattern-lines-color-default)) )}.react-flow__background-pattern.cross{stroke:var( --xy-background-pattern-color-props, var(--xy-background-pattern-color, var(--xy-background-pattern-cross-color-default)) )}.react-flow__controls{display:flex;flex-direction:column;box-shadow:var(--xy-controls-box-shadow, var(--xy-controls-box-shadow-default))}.react-flow__controls.horizontal{flex-direction:row}.react-flow__controls-button{display:flex;justify-content:center;align-items:center;height:26px;width:26px;padding:4px;border:none;background:var(--xy-controls-button-background-color, var(--xy-controls-button-background-color-default));border-bottom:1px solid var( --xy-controls-button-border-color-props, var(--xy-controls-button-border-color, var(--xy-controls-button-border-color-default)) );color:var( --xy-controls-button-color-props, var(--xy-controls-button-color, var(--xy-controls-button-color-default)) );cursor:pointer;-webkit-user-select:none;-moz-user-select:none;user-select:none}.react-flow__controls-button svg{width:100%;max-width:12px;max-height:12px;fill:currentColor}.react-flow__edge.updating .react-flow__edge-path{stroke:#777}.react-flow__edge-text{font-size:10px}.react-flow__node.selectable:focus,.react-flow__node.selectable:focus-visible{outline:none}.react-flow__node-input,.react-flow__node-default,.react-flow__node-output,.react-flow__node-group{padding:10px;border-radius:var(--xy-node-border-radius, var(--xy-node-border-radius-default));width:150px;font-size:12px;color:var(--xy-node-color, var(--xy-node-color-default));text-align:center;border:var(--xy-node-border, var(--xy-node-border-default));background-color:var(--xy-node-background-color, var(--xy-node-background-color-default))}.react-flow__node-input.selectable:hover,.react-flow__node-default.selectable:hover,.react-flow__node-output.selectable:hover,.react-flow__node-group.selectable:hover{box-shadow:var(--xy-node-boxshadow-hover, var(--xy-node-boxshadow-hover-default))}.react-flow__node-input.selectable.selected,.react-flow__node-input.selectable:focus,.react-flow__node-input.selectable:focus-visible,.react-flow__node-default.selectable.selected,.react-flow__node-default.selectable:focus,.react-flow__node-default.selectable:focus-visible,.react-flow__node-output.selectable.selected,.react-flow__node-output.selectable:focus,.react-flow__node-output.selectable:focus-visible,.react-flow__node-group.selectable.selected,.react-flow__node-group.selectable:focus,.react-flow__node-group.selectable:focus-visible{box-shadow:var(--xy-node-boxshadow-selected, var(--xy-node-boxshadow-selected-default))}.react-flow__node-group{background-color:var(--xy-node-group-background-color, var(--xy-node-group-background-color-default))}.react-flow__nodesselection-rect,.react-flow__selection{background:var(--xy-selection-background-color, var(--xy-selection-background-color-default));border:var(--xy-selection-border, var(--xy-selection-border-default))}.react-flow__nodesselection-rect:focus,.react-flow__nodesselection-rect:focus-visible,.react-flow__selection:focus,.react-flow__selection:focus-visible{outline:none}.react-flow__controls-button:hover{background:var( --xy-controls-button-background-color-hover-props, var(--xy-controls-button-background-color-hover, var(--xy-controls-button-background-color-hover-default)) );color:var( --xy-controls-button-color-hover-props, var(--xy-controls-button-color-hover, var(--xy-controls-button-color-hover-default)) )}.react-flow__controls-button:disabled{pointer-events:none}.react-flow__controls-button:disabled svg{fill-opacity:.4}.react-flow__controls-button:last-child{border-bottom:none}.react-flow__controls.horizontal .react-flow__controls-button{border-bottom:none;border-right:1px solid var( --xy-controls-button-border-color-props, var(--xy-controls-button-border-color, var(--xy-controls-button-border-color-default)) )}.react-flow__controls.horizontal .react-flow__controls-button:last-child{border-right:none}.react-flow__resize-control{position:absolute}.react-flow__resize-control.left,.react-flow__resize-control.right{cursor:ew-resize}.react-flow__resize-control.top,.react-flow__resize-control.bottom{cursor:ns-resize}.react-flow__resize-control.top.left,.react-flow__resize-control.bottom.right{cursor:nwse-resize}.react-flow__resize-control.bottom.left,.react-flow__resize-control.top.right{cursor:nesw-resize}.react-flow__resize-control.handle{width:5px;height:5px;border:1px solid #fff;border-radius:1px;background-color:var(--xy-resize-background-color, var(--xy-resize-background-color-default));translate:-50% -50%}.react-flow__resize-control.handle.left{left:0;top:50%}.react-flow__resize-control.handle.right{left:100%;top:50%}.react-flow__resize-control.handle.top{left:50%;top:0}.react-flow__resize-control.handle.bottom{left:50%;top:100%}.react-flow__resize-control.handle.top.left,.react-flow__resize-control.handle.bottom.left{left:0}.react-flow__resize-control.handle.top.right,.react-flow__resize-control.handle.bottom.right{left:100%}.react-flow__resize-control.line{border-color:var(--xy-resize-background-color, var(--xy-resize-background-color-default));border-width:0;border-style:solid}.react-flow__resize-control.line.left,.react-flow__resize-control.line.right{width:1px;transform:translate(-50%);top:0;height:100%}.react-flow__resize-control.line.left{left:0;border-left-width:1px}.react-flow__resize-control.line.right{left:100%;border-right-width:1px}.react-flow__resize-control.line.top,.react-flow__resize-control.line.bottom{height:1px;transform:translateY(-50%);left:0;width:100%}.react-flow__resize-control.line.top{top:0;border-top-width:1px}.react-flow__resize-control.line.bottom{border-bottom-width:1px;top:100%}.react-flow__edge-textbg{fill:var(--xy-edge-label-background-color, var(--xy-edge-label-background-color-default))}.react-flow__edge-text{fill:var(--xy-edge-label-color, var(--xy-edge-label-color-default))} diff --git a/python/packages/devui/agent_framework_devui/ui/assets/index.js b/python/packages/devui/agent_framework_devui/ui/assets/index.js index d276ef3b02..955abd5d9a 100644 --- a/python/packages/devui/agent_framework_devui/ui/assets/index.js +++ b/python/packages/devui/agent_framework_devui/ui/assets/index.js @@ -1,4 +1,4 @@ -function ZE(e,n){for(var o=0;os[l]})}}}return Object.freeze(Object.defineProperty(e,Symbol.toStringTag,{value:"Module"}))}(function(){const n=document.createElement("link").relList;if(n&&n.supports&&n.supports("modulepreload"))return;for(const l of document.querySelectorAll('link[rel="modulepreload"]'))s(l);new MutationObserver(l=>{for(const u of l)if(u.type==="childList")for(const d of u.addedNodes)d.tagName==="LINK"&&d.rel==="modulepreload"&&s(d)}).observe(document,{childList:!0,subtree:!0});function o(l){const u={};return l.integrity&&(u.integrity=l.integrity),l.referrerPolicy&&(u.referrerPolicy=l.referrerPolicy),l.crossOrigin==="use-credentials"?u.credentials="include":l.crossOrigin==="anonymous"?u.credentials="omit":u.credentials="same-origin",u}function s(l){if(l.ep)return;l.ep=!0;const u=o(l);fetch(l.href,u)}})();function Zh(e){return e&&e.__esModule&&Object.prototype.hasOwnProperty.call(e,"default")?e.default:e}var Cm={exports:{}},Ni={};/** +function pE(e,n){for(var s=0;so[l]})}}}return Object.freeze(Object.defineProperty(e,Symbol.toStringTag,{value:"Module"}))}(function(){const n=document.createElement("link").relList;if(n&&n.supports&&n.supports("modulepreload"))return;for(const l of document.querySelectorAll('link[rel="modulepreload"]'))o(l);new MutationObserver(l=>{for(const c of l)if(c.type==="childList")for(const d of c.addedNodes)d.tagName==="LINK"&&d.rel==="modulepreload"&&o(d)}).observe(document,{childList:!0,subtree:!0});function s(l){const c={};return l.integrity&&(c.integrity=l.integrity),l.referrerPolicy&&(c.referrerPolicy=l.referrerPolicy),l.crossOrigin==="use-credentials"?c.credentials="include":l.crossOrigin==="anonymous"?c.credentials="omit":c.credentials="same-origin",c}function o(l){if(l.ep)return;l.ep=!0;const c=s(l);fetch(l.href,c)}})();function up(e){return e&&e.__esModule&&Object.prototype.hasOwnProperty.call(e,"default")?e.default:e}var Hm={exports:{}},Oi={};/** * @license React * react-jsx-runtime.production.js * @@ -6,7 +6,7 @@ function ZE(e,n){for(var o=0;o>>1,T=_[B];if(0>>1;Bl(ee,z))sel(he,ee)?(_[B]=he,_[se]=z,B=se):(_[B]=ee,_[X]=z,B=X);else if(sel(he,z))_[B]=he,_[se]=z,B=se;else break e}}return O}function l(_,O){var z=_.sortIndex-O.sortIndex;return z!==0?z:_.id-O.id}if(e.unstable_now=void 0,typeof performance=="object"&&typeof performance.now=="function"){var u=performance;e.unstable_now=function(){return u.now()}}else{var d=Date,f=d.now();e.unstable_now=function(){return d.now()-f}}var h=[],p=[],g=1,v=null,y=3,b=!1,N=!1,w=!1,j=!1,k=typeof setTimeout=="function"?setTimeout:null,M=typeof clearTimeout=="function"?clearTimeout:null,E=typeof setImmediate<"u"?setImmediate:null;function A(_){for(var O=o(p);O!==null;){if(O.callback===null)s(p);else if(O.startTime<=_)s(p),O.sortIndex=O.expirationTime,n(h,O);else break;O=o(p)}}function D(_){if(w=!1,A(_),!N)if(o(h)!==null)N=!0,L||(L=!0,G());else{var O=o(p);O!==null&&V(D,O.startTime-_)}}var L=!1,H=-1,U=5,q=-1;function F(){return j?!0:!(e.unstable_now()-q_&&F());){var B=v.callback;if(typeof B=="function"){v.callback=null,y=v.priorityLevel;var T=B(v.expirationTime<=_);if(_=e.unstable_now(),typeof T=="function"){v.callback=T,A(_),O=!0;break t}v===o(h)&&s(h),A(_)}else s(h);v=o(h)}if(v!==null)O=!0;else{var P=o(p);P!==null&&V(D,P.startTime-_),O=!1}}break e}finally{v=null,y=z,b=!1}O=void 0}}finally{O?G():L=!1}}}var G;if(typeof E=="function")G=function(){E(K)};else if(typeof MessageChannel<"u"){var te=new MessageChannel,I=te.port2;te.port1.onmessage=K,G=function(){I.postMessage(null)}}else G=function(){k(K,0)};function V(_,O){H=k(function(){_(e.unstable_now())},O)}e.unstable_IdlePriority=5,e.unstable_ImmediatePriority=1,e.unstable_LowPriority=4,e.unstable_NormalPriority=3,e.unstable_Profiling=null,e.unstable_UserBlockingPriority=2,e.unstable_cancelCallback=function(_){_.callback=null},e.unstable_forceFrameRate=function(_){0>_||125<_?console.error("forceFrameRate takes a positive int between 0 and 125, forcing frame rates higher than 125 fps is not supported"):U=0<_?Math.floor(1e3/_):5},e.unstable_getCurrentPriorityLevel=function(){return y},e.unstable_next=function(_){switch(y){case 1:case 2:case 3:var O=3;break;default:O=y}var z=y;y=O;try{return _()}finally{y=z}},e.unstable_requestPaint=function(){j=!0},e.unstable_runWithPriority=function(_,O){switch(_){case 1:case 2:case 3:case 4:case 5:break;default:_=3}var z=y;y=_;try{return O()}finally{y=z}},e.unstable_scheduleCallback=function(_,O,z){var B=e.unstable_now();switch(typeof z=="object"&&z!==null?(z=z.delay,z=typeof z=="number"&&0B?(_.sortIndex=z,n(p,_),o(h)===null&&_===o(p)&&(w?(M(H),H=-1):w=!0,V(D,z-B))):(_.sortIndex=T,n(h,_),N||b||(N=!0,L||(L=!0,G()))),_},e.unstable_shouldYield=F,e.unstable_wrapCallback=function(_){var O=y;return function(){var z=y;y=O;try{return _.apply(this,arguments)}finally{y=z}}}})(Tm)),Tm}var My;function ej(){return My||(My=1,Mm.exports=JE()),Mm.exports}var Rm={exports:{}},Bt={};/** + */var Xy;function vE(){return Xy||(Xy=1,(function(e){function n(A,I){var B=A.length;A.push(I);e:for(;0>>1,T=A[U];if(0>>1;Ul(W,B))sel(ce,W)?(A[U]=ce,A[se]=B,U=se):(A[U]=W,A[K]=B,U=K);else if(sel(ce,B))A[U]=ce,A[se]=B,U=se;else break e}}return I}function l(A,I){var B=A.sortIndex-I.sortIndex;return B!==0?B:A.id-I.id}if(e.unstable_now=void 0,typeof performance=="object"&&typeof performance.now=="function"){var c=performance;e.unstable_now=function(){return c.now()}}else{var d=Date,f=d.now();e.unstable_now=function(){return d.now()-f}}var m=[],p=[],g=1,v=null,y=3,b=!1,S=!1,N=!1,_=!1,E=typeof setTimeout=="function"?setTimeout:null,M=typeof clearTimeout=="function"?clearTimeout:null,j=typeof setImmediate<"u"?setImmediate:null;function k(A){for(var I=s(p);I!==null;){if(I.callback===null)o(p);else if(I.startTime<=A)o(p),I.sortIndex=I.expirationTime,n(m,I);else break;I=s(p)}}function R(A){if(N=!1,k(A),!S)if(s(m)!==null)S=!0,D||(D=!0,G());else{var I=s(p);I!==null&&V(R,I.startTime-A)}}var D=!1,z=-1,H=5,$=-1;function X(){return _?!0:!(e.unstable_now()-$A&&X());){var U=v.callback;if(typeof U=="function"){v.callback=null,y=v.priorityLevel;var T=U(v.expirationTime<=A);if(A=e.unstable_now(),typeof T=="function"){v.callback=T,k(A),I=!0;break t}v===s(m)&&o(m),k(A)}else o(m);v=s(m)}if(v!==null)I=!0;else{var P=s(p);P!==null&&V(R,P.startTime-A),I=!1}}break e}finally{v=null,y=B,b=!1}I=void 0}}finally{I?G():D=!1}}}var G;if(typeof j=="function")G=function(){j(Q)};else if(typeof MessageChannel<"u"){var re=new MessageChannel,L=re.port2;re.port1.onmessage=Q,G=function(){L.postMessage(null)}}else G=function(){E(Q,0)};function V(A,I){z=E(function(){A(e.unstable_now())},I)}e.unstable_IdlePriority=5,e.unstable_ImmediatePriority=1,e.unstable_LowPriority=4,e.unstable_NormalPriority=3,e.unstable_Profiling=null,e.unstable_UserBlockingPriority=2,e.unstable_cancelCallback=function(A){A.callback=null},e.unstable_forceFrameRate=function(A){0>A||125U?(A.sortIndex=B,n(p,A),s(m)===null&&A===s(p)&&(N?(M(z),z=-1):N=!0,V(R,B-U))):(A.sortIndex=T,n(m,A),S||b||(S=!0,D||(D=!0,G()))),A},e.unstable_shouldYield=X,e.unstable_wrapCallback=function(A){var I=y;return function(){var B=y;y=I;try{return A.apply(this,arguments)}finally{y=B}}}})(Um)),Um}var Zy;function bE(){return Zy||(Zy=1,Pm.exports=vE()),Pm.exports}var Vm={exports:{}},Gt={};/** * @license React * react-dom.production.js * @@ -30,7 +30,7 @@ function ZE(e,n){for(var o=0;o"u"||typeof __REACT_DEVTOOLS_GLOBAL_HOOK__.checkDCE!="function"))try{__REACT_DEVTOOLS_GLOBAL_HOOK__.checkDCE(e)}catch(n){console.error(n)}}return e(),Rm.exports=tj(),Rm.exports}/** + */var Wy;function wE(){if(Wy)return Gt;Wy=1;var e=dl();function n(m){var p="https://react.dev/errors/"+m;if(1"u"||typeof __REACT_DEVTOOLS_GLOBAL_HOOK__.checkDCE!="function"))try{__REACT_DEVTOOLS_GLOBAL_HOOK__.checkDCE(e)}catch(n){console.error(n)}}return e(),Vm.exports=wE(),Vm.exports}/** * @license React * react-dom-client.production.js * @@ -38,392 +38,414 @@ function ZE(e,n){for(var o=0;oT||(t.current=B[T],B[T]=null,T--)}function ee(t,r){T++,B[T]=t.current,t.current=r}var se=P(null),he=P(null),fe=P(null),Q=P(null);function ae(t,r){switch(ee(fe,r),ee(he,t),ee(se,null),r.nodeType){case 9:case 11:t=(t=r.documentElement)&&(t=t.namespaceURI)?J0(t):0;break;default:if(t=r.tagName,r=r.namespaceURI)r=J0(r),t=ey(r,t);else switch(t){case"svg":t=1;break;case"math":t=2;break;default:t=0}}X(se),ee(se,t)}function xe(){X(se),X(he),X(fe)}function le(t){t.memoizedState!==null&&ee(Q,t);var r=se.current,a=ey(r,t.type);r!==a&&(ee(he,t),ee(se,a))}function ce(t){he.current===t&&(X(se),X(he)),Q.current===t&&(X(Q),yi._currentValue=z)}var ue=Object.prototype.hasOwnProperty,ge=e.unstable_scheduleCallback,pe=e.unstable_cancelCallback,Ye=e.unstable_shouldYield,it=e.unstable_requestPaint,re=e.unstable_now,Ce=e.unstable_getCurrentPriorityLevel,ke=e.unstable_ImmediatePriority,ze=e.unstable_UserBlockingPriority,Ne=e.unstable_NormalPriority,je=e.unstable_LowPriority,Le=e.unstable_IdlePriority,Ue=e.log,ct=e.unstable_setDisableYieldValue,$e=null,ve=null;function Te(t){if(typeof Ue=="function"&&ct(t),ve&&typeof ve.setStrictMode=="function")try{ve.setStrictMode($e,t)}catch{}}var Ie=Math.clz32?Math.clz32:Tr,Nt=Math.log,Tt=Math.LN2;function Tr(t){return t>>>=0,t===0?32:31-(Nt(t)/Tt|0)|0}var os=256,ss=4194304;function Qn(t){var r=t&42;if(r!==0)return r;switch(t&-t){case 1:return 1;case 2:return 2;case 4:return 4;case 8:return 8;case 16:return 16;case 32:return 32;case 64:return 64;case 128:return 128;case 256:case 512:case 1024:case 2048:case 4096:case 8192:case 16384:case 32768:case 65536:case 131072:case 262144:case 524288:case 1048576:case 2097152:return t&4194048;case 4194304:case 8388608:case 16777216:case 33554432:return t&62914560;case 67108864:return 67108864;case 134217728:return 134217728;case 268435456:return 268435456;case 536870912:return 536870912;case 1073741824:return 0;default:return t}}function as(t,r,a){var c=t.pendingLanes;if(c===0)return 0;var m=0,x=t.suspendedLanes,C=t.pingedLanes;t=t.warmLanes;var R=c&134217727;return R!==0?(c=R&~x,c!==0?m=Qn(c):(C&=R,C!==0?m=Qn(C):a||(a=R&~t,a!==0&&(m=Qn(a))))):(R=c&~x,R!==0?m=Qn(R):C!==0?m=Qn(C):a||(a=c&~t,a!==0&&(m=Qn(a)))),m===0?0:r!==0&&r!==m&&(r&x)===0&&(x=m&-m,a=r&-r,x>=a||x===32&&(a&4194048)!==0)?r:m}function vo(t,r){return(t.pendingLanes&~(t.suspendedLanes&~t.pingedLanes)&r)===0}function pd(t,r){switch(t){case 1:case 2:case 4:case 8:case 64:return r+250;case 16:case 32:case 128:case 256:case 512:case 1024:case 2048:case 4096:case 8192:case 16384:case 32768:case 65536:case 131072:case 262144:case 524288:case 1048576:case 2097152:return r+5e3;case 4194304:case 8388608:case 16777216:case 33554432:return-1;case 67108864:case 134217728:case 268435456:case 536870912:case 1073741824:return-1;default:return-1}}function ml(){var t=os;return os<<=1,(os&4194048)===0&&(os=256),t}function hl(){var t=ss;return ss<<=1,(ss&62914560)===0&&(ss=4194304),t}function va(t){for(var r=[],a=0;31>a;a++)r.push(t);return r}function bo(t,r){t.pendingLanes|=r,r!==268435456&&(t.suspendedLanes=0,t.pingedLanes=0,t.warmLanes=0)}function gd(t,r,a,c,m,x){var C=t.pendingLanes;t.pendingLanes=a,t.suspendedLanes=0,t.pingedLanes=0,t.warmLanes=0,t.expiredLanes&=a,t.entangledLanes&=a,t.errorRecoveryDisabledLanes&=a,t.shellSuspendCounter=0;var R=t.entanglements,$=t.expirationTimes,J=t.hiddenUpdates;for(a=C&~a;0)":-1m||$[c]!==J[m]){var ie=` -`+$[c].replace(" at new "," at ");return t.displayName&&ie.includes("")&&(ie=ie.replace("",t.displayName)),ie}while(1<=c&&0<=m);break}}}finally{Ca=!1,Error.prepareStackTrace=a}return(a=t?t.displayName||t.name:"")?nr(a):""}function Sd(t){switch(t.tag){case 26:case 27:case 5:return nr(t.type);case 16:return nr("Lazy");case 13:return nr("Suspense");case 19:return nr("SuspenseList");case 0:case 15:return ka(t.type,!1);case 11:return ka(t.type.render,!1);case 1:return ka(t.type,!0);case 31:return nr("Activity");default:return""}}function Sl(t){try{var r="";do r+=Sd(t),t=t.return;while(t);return r}catch(a){return` -Error generating stack: `+a.message+` -`+a.stack}}function $t(t){switch(typeof t){case"bigint":case"boolean":case"number":case"string":case"undefined":return t;case"object":return t;default:return""}}function Nl(t){var r=t.type;return(t=t.nodeName)&&t.toLowerCase()==="input"&&(r==="checkbox"||r==="radio")}function Nd(t){var r=Nl(t)?"checked":"value",a=Object.getOwnPropertyDescriptor(t.constructor.prototype,r),c=""+t[r];if(!t.hasOwnProperty(r)&&typeof a<"u"&&typeof a.get=="function"&&typeof a.set=="function"){var m=a.get,x=a.set;return Object.defineProperty(t,r,{configurable:!0,get:function(){return m.call(this)},set:function(C){c=""+C,x.call(this,C)}}),Object.defineProperty(t,r,{enumerable:a.enumerable}),{getValue:function(){return c},setValue:function(C){c=""+C},stopTracking:function(){t._valueTracker=null,delete t[r]}}}}function cs(t){t._valueTracker||(t._valueTracker=Nd(t))}function Aa(t){if(!t)return!1;var r=t._valueTracker;if(!r)return!0;var a=r.getValue(),c="";return t&&(c=Nl(t)?t.checked?"true":"false":t.value),t=c,t!==a?(r.setValue(t),!0):!1}function us(t){if(t=t||(typeof document<"u"?document:void 0),typeof t>"u")return null;try{return t.activeElement||t.body}catch{return t.body}}var _d=/[\n"\\]/g;function Vt(t){return t.replace(_d,function(r){return"\\"+r.charCodeAt(0).toString(16)+" "})}function So(t,r,a,c,m,x,C,R){t.name="",C!=null&&typeof C!="function"&&typeof C!="symbol"&&typeof C!="boolean"?t.type=C:t.removeAttribute("type"),r!=null?C==="number"?(r===0&&t.value===""||t.value!=r)&&(t.value=""+$t(r)):t.value!==""+$t(r)&&(t.value=""+$t(r)):C!=="submit"&&C!=="reset"||t.removeAttribute("value"),r!=null?Ma(t,C,$t(r)):a!=null?Ma(t,C,$t(a)):c!=null&&t.removeAttribute("value"),m==null&&x!=null&&(t.defaultChecked=!!x),m!=null&&(t.checked=m&&typeof m!="function"&&typeof m!="symbol"),R!=null&&typeof R!="function"&&typeof R!="symbol"&&typeof R!="boolean"?t.name=""+$t(R):t.removeAttribute("name")}function _l(t,r,a,c,m,x,C,R){if(x!=null&&typeof x!="function"&&typeof x!="symbol"&&typeof x!="boolean"&&(t.type=x),r!=null||a!=null){if(!(x!=="submit"&&x!=="reset"||r!=null))return;a=a!=null?""+$t(a):"",r=r!=null?""+$t(r):a,R||r===t.value||(t.value=r),t.defaultValue=r}c=c??m,c=typeof c!="function"&&typeof c!="symbol"&&!!c,t.checked=R?t.checked:!!c,t.defaultChecked=!!c,C!=null&&typeof C!="function"&&typeof C!="symbol"&&typeof C!="boolean"&&(t.name=C)}function Ma(t,r,a){r==="number"&&us(t.ownerDocument)===t||t.defaultValue===""+a||(t.defaultValue=""+a)}function rr(t,r,a,c){if(t=t.options,r){r={};for(var m=0;m"u"||typeof window.document>"u"||typeof window.document.createElement>"u"),Ad=!1;if(or)try{var Ra={};Object.defineProperty(Ra,"passive",{get:function(){Ad=!0}}),window.addEventListener("test",Ra,Ra),window.removeEventListener("test",Ra,Ra)}catch{Ad=!1}var Lr=null,Md=null,jl=null;function rg(){if(jl)return jl;var t,r=Md,a=r.length,c,m="value"in Lr?Lr.value:Lr.textContent,x=m.length;for(t=0;t=za),cg=" ",ug=!1;function dg(t,r){switch(t){case"keyup":return v_.indexOf(r.keyCode)!==-1;case"keydown":return r.keyCode!==229;case"keypress":case"mousedown":case"focusout":return!0;default:return!1}}function fg(t){return t=t.detail,typeof t=="object"&&"data"in t?t.data:null}var hs=!1;function w_(t,r){switch(t){case"compositionend":return fg(r);case"keypress":return r.which!==32?null:(ug=!0,cg);case"textInput":return t=r.data,t===cg&&ug?null:t;default:return null}}function S_(t,r){if(hs)return t==="compositionend"||!zd&&dg(t,r)?(t=rg(),jl=Md=Lr=null,hs=!1,t):null;switch(t){case"paste":return null;case"keypress":if(!(r.ctrlKey||r.altKey||r.metaKey)||r.ctrlKey&&r.altKey){if(r.char&&1=r)return{node:a,offset:r-t};t=c}e:{for(;a;){if(a.nextSibling){a=a.nextSibling;break e}a=a.parentNode}a=void 0}a=bg(a)}}function Sg(t,r){return t&&r?t===r?!0:t&&t.nodeType===3?!1:r&&r.nodeType===3?Sg(t,r.parentNode):"contains"in t?t.contains(r):t.compareDocumentPosition?!!(t.compareDocumentPosition(r)&16):!1:!1}function Ng(t){t=t!=null&&t.ownerDocument!=null&&t.ownerDocument.defaultView!=null?t.ownerDocument.defaultView:window;for(var r=us(t.document);r instanceof t.HTMLIFrameElement;){try{var a=typeof r.contentWindow.location.href=="string"}catch{a=!1}if(a)t=r.contentWindow;else break;r=us(t.document)}return r}function Hd(t){var r=t&&t.nodeName&&t.nodeName.toLowerCase();return r&&(r==="input"&&(t.type==="text"||t.type==="search"||t.type==="tel"||t.type==="url"||t.type==="password")||r==="textarea"||t.contentEditable==="true")}var M_=or&&"documentMode"in document&&11>=document.documentMode,ps=null,Bd=null,Ba=null,Ud=!1;function _g(t,r,a){var c=a.window===a?a.document:a.nodeType===9?a:a.ownerDocument;Ud||ps==null||ps!==us(c)||(c=ps,"selectionStart"in c&&Hd(c)?c={start:c.selectionStart,end:c.selectionEnd}:(c=(c.ownerDocument&&c.ownerDocument.defaultView||window).getSelection(),c={anchorNode:c.anchorNode,anchorOffset:c.anchorOffset,focusNode:c.focusNode,focusOffset:c.focusOffset}),Ba&&Ha(Ba,c)||(Ba=c,c=gc(Bd,"onSelect"),0>=C,m-=C,ar=1<<32-Ie(r)+m|a<x?x:8;var C=_.T,R={};_.T=R,jf(t,!1,r,a);try{var $=m(),J=_.S;if(J!==null&&J(R,$),$!==null&&typeof $=="object"&&typeof $.then=="function"){var ie=B_($,c);ei(t,r,ie,nn(t))}else ei(t,r,c,nn(t))}catch(me){ei(t,r,{then:function(){},status:"rejected",reason:me},nn())}finally{O.p=x,_.T=C}}function q_(){}function _f(t,r,a,c){if(t.tag!==5)throw Error(s(476));var m=Ex(t).queue;_x(t,m,r,z,a===null?q_:function(){return jx(t),a(c)})}function Ex(t){var r=t.memoizedState;if(r!==null)return r;r={memoizedState:z,baseState:z,baseQueue:null,queue:{pending:null,lanes:0,dispatch:null,lastRenderedReducer:ur,lastRenderedState:z},next:null};var a={};return r.next={memoizedState:a,baseState:a,baseQueue:null,queue:{pending:null,lanes:0,dispatch:null,lastRenderedReducer:ur,lastRenderedState:a},next:null},t.memoizedState=r,t=t.alternate,t!==null&&(t.memoizedState=r),r}function jx(t){var r=Ex(t).next.queue;ei(t,r,{},nn())}function Ef(){return Ht(yi)}function Cx(){return bt().memoizedState}function kx(){return bt().memoizedState}function Y_(t){for(var r=t.return;r!==null;){switch(r.tag){case 24:case 3:var a=nn();t=Br(a);var c=Ur(r,t,a);c!==null&&(rn(c,r,a),Fa(c,r,a)),r={cache:ef()},t.payload=r;return}r=r.return}}function G_(t,r,a){var c=nn();a={lane:c,revertLane:0,action:a,hasEagerState:!1,eagerState:null,next:null},Kl(t)?Mx(r,a):(a=qd(t,r,a,c),a!==null&&(rn(a,t,c),Tx(a,r,c)))}function Ax(t,r,a){var c=nn();ei(t,r,a,c)}function ei(t,r,a,c){var m={lane:c,revertLane:0,action:a,hasEagerState:!1,eagerState:null,next:null};if(Kl(t))Mx(r,m);else{var x=t.alternate;if(t.lanes===0&&(x===null||x.lanes===0)&&(x=r.lastRenderedReducer,x!==null))try{var C=r.lastRenderedState,R=x(C,a);if(m.hasEagerState=!0,m.eagerState=R,Wt(R,C))return Dl(t,r,m,0),lt===null&&Rl(),!1}catch{}finally{}if(a=qd(t,r,m,c),a!==null)return rn(a,t,c),Tx(a,r,c),!0}return!1}function jf(t,r,a,c){if(c={lane:2,revertLane:om(),action:c,hasEagerState:!1,eagerState:null,next:null},Kl(t)){if(r)throw Error(s(479))}else r=qd(t,a,c,2),r!==null&&rn(r,t,2)}function Kl(t){var r=t.alternate;return t===Ve||r!==null&&r===Ve}function Mx(t,r){Es=ql=!0;var a=t.pending;a===null?r.next=r:(r.next=a.next,a.next=r),t.pending=r}function Tx(t,r,a){if((a&4194048)!==0){var c=r.lanes;c&=t.pendingLanes,a|=c,r.lanes=a,ba(t,a)}}var Wl={readContext:Ht,use:Gl,useCallback:gt,useContext:gt,useEffect:gt,useImperativeHandle:gt,useLayoutEffect:gt,useInsertionEffect:gt,useMemo:gt,useReducer:gt,useRef:gt,useState:gt,useDebugValue:gt,useDeferredValue:gt,useTransition:gt,useSyncExternalStore:gt,useId:gt,useHostTransitionStatus:gt,useFormState:gt,useActionState:gt,useOptimistic:gt,useMemoCache:gt,useCacheRefresh:gt},Rx={readContext:Ht,use:Gl,useCallback:function(t,r){return Yt().memoizedState=[t,r===void 0?null:r],t},useContext:Ht,useEffect:px,useImperativeHandle:function(t,r,a){a=a!=null?a.concat([t]):null,Zl(4194308,4,vx.bind(null,r,t),a)},useLayoutEffect:function(t,r){return Zl(4194308,4,t,r)},useInsertionEffect:function(t,r){Zl(4,2,t,r)},useMemo:function(t,r){var a=Yt();r=r===void 0?null:r;var c=t();if(Oo){Te(!0);try{t()}finally{Te(!1)}}return a.memoizedState=[c,r],c},useReducer:function(t,r,a){var c=Yt();if(a!==void 0){var m=a(r);if(Oo){Te(!0);try{a(r)}finally{Te(!1)}}}else m=r;return c.memoizedState=c.baseState=m,t={pending:null,lanes:0,dispatch:null,lastRenderedReducer:t,lastRenderedState:m},c.queue=t,t=t.dispatch=G_.bind(null,Ve,t),[c.memoizedState,t]},useRef:function(t){var r=Yt();return t={current:t},r.memoizedState=t},useState:function(t){t=bf(t);var r=t.queue,a=Ax.bind(null,Ve,r);return r.dispatch=a,[t.memoizedState,a]},useDebugValue:Sf,useDeferredValue:function(t,r){var a=Yt();return Nf(a,t,r)},useTransition:function(){var t=bf(!1);return t=_x.bind(null,Ve,t.queue,!0,!1),Yt().memoizedState=t,[!1,t]},useSyncExternalStore:function(t,r,a){var c=Ve,m=Yt();if(et){if(a===void 0)throw Error(s(407));a=a()}else{if(a=r(),lt===null)throw Error(s(349));(Ke&124)!==0||Jg(c,r,a)}m.memoizedState=a;var x={value:a,getSnapshot:r};return m.queue=x,px(tx.bind(null,c,x,t),[t]),c.flags|=2048,Cs(9,Fl(),ex.bind(null,c,x,a,r),null),a},useId:function(){var t=Yt(),r=lt.identifierPrefix;if(et){var a=ir,c=ar;a=(c&~(1<<32-Ie(c)-1)).toString(32)+a,r="«"+r+"R"+a,a=Yl++,0De?(Mt=Ae,Ae=null):Mt=Ae.sibling;var Qe=ne(Z,Ae,W[De],de);if(Qe===null){Ae===null&&(Ae=Mt);break}t&&Ae&&Qe.alternate===null&&r(Z,Ae),Y=x(Qe,Y,De),Ge===null?be=Qe:Ge.sibling=Qe,Ge=Qe,Ae=Mt}if(De===W.length)return a(Z,Ae),et&&ko(Z,De),be;if(Ae===null){for(;DeDe?(Mt=Ae,Ae=null):Mt=Ae.sibling;var ro=ne(Z,Ae,Qe.value,de);if(ro===null){Ae===null&&(Ae=Mt);break}t&&Ae&&ro.alternate===null&&r(Z,Ae),Y=x(ro,Y,De),Ge===null?be=ro:Ge.sibling=ro,Ge=ro,Ae=Mt}if(Qe.done)return a(Z,Ae),et&&ko(Z,De),be;if(Ae===null){for(;!Qe.done;De++,Qe=W.next())Qe=me(Z,Qe.value,de),Qe!==null&&(Y=x(Qe,Y,De),Ge===null?be=Qe:Ge.sibling=Qe,Ge=Qe);return et&&ko(Z,De),be}for(Ae=c(Ae);!Qe.done;De++,Qe=W.next())Qe=oe(Ae,Z,De,Qe.value,de),Qe!==null&&(t&&Qe.alternate!==null&&Ae.delete(Qe.key===null?De:Qe.key),Y=x(Qe,Y,De),Ge===null?be=Qe:Ge.sibling=Qe,Ge=Qe);return t&&Ae.forEach(function(FE){return r(Z,FE)}),et&&ko(Z,De),be}function st(Z,Y,W,de){if(typeof W=="object"&&W!==null&&W.type===N&&W.key===null&&(W=W.props.children),typeof W=="object"&&W!==null){switch(W.$$typeof){case y:e:{for(var be=W.key;Y!==null;){if(Y.key===be){if(be=W.type,be===N){if(Y.tag===7){a(Z,Y.sibling),de=m(Y,W.props.children),de.return=Z,Z=de;break e}}else if(Y.elementType===be||typeof be=="object"&&be!==null&&be.$$typeof===U&&Ox(be)===Y.type){a(Z,Y.sibling),de=m(Y,W.props),ni(de,W),de.return=Z,Z=de;break e}a(Z,Y);break}else r(Z,Y);Y=Y.sibling}W.type===N?(de=jo(W.props.children,Z.mode,de,W.key),de.return=Z,Z=de):(de=zl(W.type,W.key,W.props,null,Z.mode,de),ni(de,W),de.return=Z,Z=de)}return C(Z);case b:e:{for(be=W.key;Y!==null;){if(Y.key===be)if(Y.tag===4&&Y.stateNode.containerInfo===W.containerInfo&&Y.stateNode.implementation===W.implementation){a(Z,Y.sibling),de=m(Y,W.children||[]),de.return=Z,Z=de;break e}else{a(Z,Y);break}else r(Z,Y);Y=Y.sibling}de=Xd(W,Z.mode,de),de.return=Z,Z=de}return C(Z);case U:return be=W._init,W=be(W._payload),st(Z,Y,W,de)}if(V(W))return Oe(Z,Y,W,de);if(G(W)){if(be=G(W),typeof be!="function")throw Error(s(150));return W=be.call(W),Re(Z,Y,W,de)}if(typeof W.then=="function")return st(Z,Y,Ql(W),de);if(W.$$typeof===E)return st(Z,Y,Bl(Z,W),de);Jl(Z,W)}return typeof W=="string"&&W!==""||typeof W=="number"||typeof W=="bigint"?(W=""+W,Y!==null&&Y.tag===6?(a(Z,Y.sibling),de=m(Y,W),de.return=Z,Z=de):(a(Z,Y),de=Gd(W,Z.mode,de),de.return=Z,Z=de),C(Z)):a(Z,Y)}return function(Z,Y,W,de){try{ti=0;var be=st(Z,Y,W,de);return ks=null,be}catch(Ae){if(Ae===Ga||Ae===Pl)throw Ae;var Ge=Qt(29,Ae,null,Z.mode);return Ge.lanes=de,Ge.return=Z,Ge}finally{}}}var As=zx(!0),Lx=zx(!1),pn=P(null),zn=null;function $r(t){var r=t.alternate;ee(Et,Et.current&1),ee(pn,t),zn===null&&(r===null||_s.current!==null||r.memoizedState!==null)&&(zn=t)}function Ix(t){if(t.tag===22){if(ee(Et,Et.current),ee(pn,t),zn===null){var r=t.alternate;r!==null&&r.memoizedState!==null&&(zn=t)}}else Vr()}function Vr(){ee(Et,Et.current),ee(pn,pn.current)}function dr(t){X(pn),zn===t&&(zn=null),X(Et)}var Et=P(0);function ec(t){for(var r=t;r!==null;){if(r.tag===13){var a=r.memoizedState;if(a!==null&&(a=a.dehydrated,a===null||a.data==="$?"||gm(a)))return r}else if(r.tag===19&&r.memoizedProps.revealOrder!==void 0){if((r.flags&128)!==0)return r}else if(r.child!==null){r.child.return=r,r=r.child;continue}if(r===t)break;for(;r.sibling===null;){if(r.return===null||r.return===t)return null;r=r.return}r.sibling.return=r.return,r=r.sibling}return null}function Cf(t,r,a,c){r=t.memoizedState,a=a(c,r),a=a==null?r:g({},r,a),t.memoizedState=a,t.lanes===0&&(t.updateQueue.baseState=a)}var kf={enqueueSetState:function(t,r,a){t=t._reactInternals;var c=nn(),m=Br(c);m.payload=r,a!=null&&(m.callback=a),r=Ur(t,m,c),r!==null&&(rn(r,t,c),Fa(r,t,c))},enqueueReplaceState:function(t,r,a){t=t._reactInternals;var c=nn(),m=Br(c);m.tag=1,m.payload=r,a!=null&&(m.callback=a),r=Ur(t,m,c),r!==null&&(rn(r,t,c),Fa(r,t,c))},enqueueForceUpdate:function(t,r){t=t._reactInternals;var a=nn(),c=Br(a);c.tag=2,r!=null&&(c.callback=r),r=Ur(t,c,a),r!==null&&(rn(r,t,a),Fa(r,t,a))}};function Hx(t,r,a,c,m,x,C){return t=t.stateNode,typeof t.shouldComponentUpdate=="function"?t.shouldComponentUpdate(c,x,C):r.prototype&&r.prototype.isPureReactComponent?!Ha(a,c)||!Ha(m,x):!0}function Bx(t,r,a,c){t=r.state,typeof r.componentWillReceiveProps=="function"&&r.componentWillReceiveProps(a,c),typeof r.UNSAFE_componentWillReceiveProps=="function"&&r.UNSAFE_componentWillReceiveProps(a,c),r.state!==t&&kf.enqueueReplaceState(r,r.state,null)}function zo(t,r){var a=r;if("ref"in r){a={};for(var c in r)c!=="ref"&&(a[c]=r[c])}if(t=t.defaultProps){a===r&&(a=g({},a));for(var m in t)a[m]===void 0&&(a[m]=t[m])}return a}var tc=typeof reportError=="function"?reportError:function(t){if(typeof window=="object"&&typeof window.ErrorEvent=="function"){var r=new window.ErrorEvent("error",{bubbles:!0,cancelable:!0,message:typeof t=="object"&&t!==null&&typeof t.message=="string"?String(t.message):String(t),error:t});if(!window.dispatchEvent(r))return}else if(typeof process=="object"&&typeof process.emit=="function"){process.emit("uncaughtException",t);return}console.error(t)};function Ux(t){tc(t)}function Px(t){console.error(t)}function $x(t){tc(t)}function nc(t,r){try{var a=t.onUncaughtError;a(r.value,{componentStack:r.stack})}catch(c){setTimeout(function(){throw c})}}function Vx(t,r,a){try{var c=t.onCaughtError;c(a.value,{componentStack:a.stack,errorBoundary:r.tag===1?r.stateNode:null})}catch(m){setTimeout(function(){throw m})}}function Af(t,r,a){return a=Br(a),a.tag=3,a.payload={element:null},a.callback=function(){nc(t,r)},a}function qx(t){return t=Br(t),t.tag=3,t}function Yx(t,r,a,c){var m=a.type.getDerivedStateFromError;if(typeof m=="function"){var x=c.value;t.payload=function(){return m(x)},t.callback=function(){Vx(r,a,c)}}var C=a.stateNode;C!==null&&typeof C.componentDidCatch=="function"&&(t.callback=function(){Vx(r,a,c),typeof m!="function"&&(Zr===null?Zr=new Set([this]):Zr.add(this));var R=c.stack;this.componentDidCatch(c.value,{componentStack:R!==null?R:""})})}function F_(t,r,a,c,m){if(a.flags|=32768,c!==null&&typeof c=="object"&&typeof c.then=="function"){if(r=a.alternate,r!==null&&Va(r,a,m,!0),a=pn.current,a!==null){switch(a.tag){case 13:return zn===null?Jf():a.alternate===null&&pt===0&&(pt=3),a.flags&=-257,a.flags|=65536,a.lanes=m,c===rf?a.flags|=16384:(r=a.updateQueue,r===null?a.updateQueue=new Set([c]):r.add(c),tm(t,c,m)),!1;case 22:return a.flags|=65536,c===rf?a.flags|=16384:(r=a.updateQueue,r===null?(r={transitions:null,markerInstances:null,retryQueue:new Set([c])},a.updateQueue=r):(a=r.retryQueue,a===null?r.retryQueue=new Set([c]):a.add(c)),tm(t,c,m)),!1}throw Error(s(435,a.tag))}return tm(t,c,m),Jf(),!1}if(et)return r=pn.current,r!==null?((r.flags&65536)===0&&(r.flags|=256),r.flags|=65536,r.lanes=m,c!==Kd&&(t=Error(s(422),{cause:c}),$a(dn(t,a)))):(c!==Kd&&(r=Error(s(423),{cause:c}),$a(dn(r,a))),t=t.current.alternate,t.flags|=65536,m&=-m,t.lanes|=m,c=dn(c,a),m=Af(t.stateNode,c,m),af(t,m),pt!==4&&(pt=2)),!1;var x=Error(s(520),{cause:c});if(x=dn(x,a),ci===null?ci=[x]:ci.push(x),pt!==4&&(pt=2),r===null)return!0;c=dn(c,a),a=r;do{switch(a.tag){case 3:return a.flags|=65536,t=m&-m,a.lanes|=t,t=Af(a.stateNode,c,t),af(a,t),!1;case 1:if(r=a.type,x=a.stateNode,(a.flags&128)===0&&(typeof r.getDerivedStateFromError=="function"||x!==null&&typeof x.componentDidCatch=="function"&&(Zr===null||!Zr.has(x))))return a.flags|=65536,m&=-m,a.lanes|=m,m=qx(m),Yx(m,t,a,c),af(a,m),!1}a=a.return}while(a!==null);return!1}var Gx=Error(s(461)),kt=!1;function Rt(t,r,a,c){r.child=t===null?Lx(r,null,a,c):As(r,t.child,a,c)}function Xx(t,r,a,c,m){a=a.render;var x=r.ref;if("ref"in c){var C={};for(var R in c)R!=="ref"&&(C[R]=c[R])}else C=c;return Ro(r),c=ff(t,r,a,C,x,m),R=mf(),t!==null&&!kt?(hf(t,r,m),fr(t,r,m)):(et&&R&&Fd(r),r.flags|=1,Rt(t,r,c,m),r.child)}function Fx(t,r,a,c,m){if(t===null){var x=a.type;return typeof x=="function"&&!Yd(x)&&x.defaultProps===void 0&&a.compare===null?(r.tag=15,r.type=x,Zx(t,r,x,c,m)):(t=zl(a.type,null,c,r,r.mode,m),t.ref=r.ref,t.return=r,r.child=t)}if(x=t.child,!If(t,m)){var C=x.memoizedProps;if(a=a.compare,a=a!==null?a:Ha,a(C,c)&&t.ref===r.ref)return fr(t,r,m)}return r.flags|=1,t=sr(x,c),t.ref=r.ref,t.return=r,r.child=t}function Zx(t,r,a,c,m){if(t!==null){var x=t.memoizedProps;if(Ha(x,c)&&t.ref===r.ref)if(kt=!1,r.pendingProps=c=x,If(t,m))(t.flags&131072)!==0&&(kt=!0);else return r.lanes=t.lanes,fr(t,r,m)}return Mf(t,r,a,c,m)}function Kx(t,r,a){var c=r.pendingProps,m=c.children,x=t!==null?t.memoizedState:null;if(c.mode==="hidden"){if((r.flags&128)!==0){if(c=x!==null?x.baseLanes|a:a,t!==null){for(m=r.child=t.child,x=0;m!==null;)x=x|m.lanes|m.childLanes,m=m.sibling;r.childLanes=x&~c}else r.childLanes=0,r.child=null;return Wx(t,r,c,a)}if((a&536870912)!==0)r.memoizedState={baseLanes:0,cachePool:null},t!==null&&Ul(r,x!==null?x.cachePool:null),x!==null?Zg(r,x):cf(),Ix(r);else return r.lanes=r.childLanes=536870912,Wx(t,r,x!==null?x.baseLanes|a:a,a)}else x!==null?(Ul(r,x.cachePool),Zg(r,x),Vr(),r.memoizedState=null):(t!==null&&Ul(r,null),cf(),Vr());return Rt(t,r,m,a),r.child}function Wx(t,r,a,c){var m=nf();return m=m===null?null:{parent:_t._currentValue,pool:m},r.memoizedState={baseLanes:a,cachePool:m},t!==null&&Ul(r,null),cf(),Ix(r),t!==null&&Va(t,r,c,!0),null}function rc(t,r){var a=r.ref;if(a===null)t!==null&&t.ref!==null&&(r.flags|=4194816);else{if(typeof a!="function"&&typeof a!="object")throw Error(s(284));(t===null||t.ref!==a)&&(r.flags|=4194816)}}function Mf(t,r,a,c,m){return Ro(r),a=ff(t,r,a,c,void 0,m),c=mf(),t!==null&&!kt?(hf(t,r,m),fr(t,r,m)):(et&&c&&Fd(r),r.flags|=1,Rt(t,r,a,m),r.child)}function Qx(t,r,a,c,m,x){return Ro(r),r.updateQueue=null,a=Wg(r,c,a,m),Kg(t),c=mf(),t!==null&&!kt?(hf(t,r,x),fr(t,r,x)):(et&&c&&Fd(r),r.flags|=1,Rt(t,r,a,x),r.child)}function Jx(t,r,a,c,m){if(Ro(r),r.stateNode===null){var x=vs,C=a.contextType;typeof C=="object"&&C!==null&&(x=Ht(C)),x=new a(c,x),r.memoizedState=x.state!==null&&x.state!==void 0?x.state:null,x.updater=kf,r.stateNode=x,x._reactInternals=r,x=r.stateNode,x.props=c,x.state=r.memoizedState,x.refs={},of(r),C=a.contextType,x.context=typeof C=="object"&&C!==null?Ht(C):vs,x.state=r.memoizedState,C=a.getDerivedStateFromProps,typeof C=="function"&&(Cf(r,a,C,c),x.state=r.memoizedState),typeof a.getDerivedStateFromProps=="function"||typeof x.getSnapshotBeforeUpdate=="function"||typeof x.UNSAFE_componentWillMount!="function"&&typeof x.componentWillMount!="function"||(C=x.state,typeof x.componentWillMount=="function"&&x.componentWillMount(),typeof x.UNSAFE_componentWillMount=="function"&&x.UNSAFE_componentWillMount(),C!==x.state&&kf.enqueueReplaceState(x,x.state,null),Ka(r,c,x,m),Za(),x.state=r.memoizedState),typeof x.componentDidMount=="function"&&(r.flags|=4194308),c=!0}else if(t===null){x=r.stateNode;var R=r.memoizedProps,$=zo(a,R);x.props=$;var J=x.context,ie=a.contextType;C=vs,typeof ie=="object"&&ie!==null&&(C=Ht(ie));var me=a.getDerivedStateFromProps;ie=typeof me=="function"||typeof x.getSnapshotBeforeUpdate=="function",R=r.pendingProps!==R,ie||typeof x.UNSAFE_componentWillReceiveProps!="function"&&typeof x.componentWillReceiveProps!="function"||(R||J!==C)&&Bx(r,x,c,C),Hr=!1;var ne=r.memoizedState;x.state=ne,Ka(r,c,x,m),Za(),J=r.memoizedState,R||ne!==J||Hr?(typeof me=="function"&&(Cf(r,a,me,c),J=r.memoizedState),($=Hr||Hx(r,a,$,c,ne,J,C))?(ie||typeof x.UNSAFE_componentWillMount!="function"&&typeof x.componentWillMount!="function"||(typeof x.componentWillMount=="function"&&x.componentWillMount(),typeof x.UNSAFE_componentWillMount=="function"&&x.UNSAFE_componentWillMount()),typeof x.componentDidMount=="function"&&(r.flags|=4194308)):(typeof x.componentDidMount=="function"&&(r.flags|=4194308),r.memoizedProps=c,r.memoizedState=J),x.props=c,x.state=J,x.context=C,c=$):(typeof x.componentDidMount=="function"&&(r.flags|=4194308),c=!1)}else{x=r.stateNode,sf(t,r),C=r.memoizedProps,ie=zo(a,C),x.props=ie,me=r.pendingProps,ne=x.context,J=a.contextType,$=vs,typeof J=="object"&&J!==null&&($=Ht(J)),R=a.getDerivedStateFromProps,(J=typeof R=="function"||typeof x.getSnapshotBeforeUpdate=="function")||typeof x.UNSAFE_componentWillReceiveProps!="function"&&typeof x.componentWillReceiveProps!="function"||(C!==me||ne!==$)&&Bx(r,x,c,$),Hr=!1,ne=r.memoizedState,x.state=ne,Ka(r,c,x,m),Za();var oe=r.memoizedState;C!==me||ne!==oe||Hr||t!==null&&t.dependencies!==null&&Hl(t.dependencies)?(typeof R=="function"&&(Cf(r,a,R,c),oe=r.memoizedState),(ie=Hr||Hx(r,a,ie,c,ne,oe,$)||t!==null&&t.dependencies!==null&&Hl(t.dependencies))?(J||typeof x.UNSAFE_componentWillUpdate!="function"&&typeof x.componentWillUpdate!="function"||(typeof x.componentWillUpdate=="function"&&x.componentWillUpdate(c,oe,$),typeof x.UNSAFE_componentWillUpdate=="function"&&x.UNSAFE_componentWillUpdate(c,oe,$)),typeof x.componentDidUpdate=="function"&&(r.flags|=4),typeof x.getSnapshotBeforeUpdate=="function"&&(r.flags|=1024)):(typeof x.componentDidUpdate!="function"||C===t.memoizedProps&&ne===t.memoizedState||(r.flags|=4),typeof x.getSnapshotBeforeUpdate!="function"||C===t.memoizedProps&&ne===t.memoizedState||(r.flags|=1024),r.memoizedProps=c,r.memoizedState=oe),x.props=c,x.state=oe,x.context=$,c=ie):(typeof x.componentDidUpdate!="function"||C===t.memoizedProps&&ne===t.memoizedState||(r.flags|=4),typeof x.getSnapshotBeforeUpdate!="function"||C===t.memoizedProps&&ne===t.memoizedState||(r.flags|=1024),c=!1)}return x=c,rc(t,r),c=(r.flags&128)!==0,x||c?(x=r.stateNode,a=c&&typeof a.getDerivedStateFromError!="function"?null:x.render(),r.flags|=1,t!==null&&c?(r.child=As(r,t.child,null,m),r.child=As(r,null,a,m)):Rt(t,r,a,m),r.memoizedState=x.state,t=r.child):t=fr(t,r,m),t}function e0(t,r,a,c){return Pa(),r.flags|=256,Rt(t,r,a,c),r.child}var Tf={dehydrated:null,treeContext:null,retryLane:0,hydrationErrors:null};function Rf(t){return{baseLanes:t,cachePool:Pg()}}function Df(t,r,a){return t=t!==null?t.childLanes&~a:0,r&&(t|=gn),t}function t0(t,r,a){var c=r.pendingProps,m=!1,x=(r.flags&128)!==0,C;if((C=x)||(C=t!==null&&t.memoizedState===null?!1:(Et.current&2)!==0),C&&(m=!0,r.flags&=-129),C=(r.flags&32)!==0,r.flags&=-33,t===null){if(et){if(m?$r(r):Vr(),et){var R=ht,$;if($=R){e:{for($=R,R=On;$.nodeType!==8;){if(!R){R=null;break e}if($=Nn($.nextSibling),$===null){R=null;break e}}R=$}R!==null?(r.memoizedState={dehydrated:R,treeContext:Co!==null?{id:ar,overflow:ir}:null,retryLane:536870912,hydrationErrors:null},$=Qt(18,null,null,0),$.stateNode=R,$.return=r,r.child=$,Ut=r,ht=null,$=!0):$=!1}$||Mo(r)}if(R=r.memoizedState,R!==null&&(R=R.dehydrated,R!==null))return gm(R)?r.lanes=32:r.lanes=536870912,null;dr(r)}return R=c.children,c=c.fallback,m?(Vr(),m=r.mode,R=oc({mode:"hidden",children:R},m),c=jo(c,m,a,null),R.return=r,c.return=r,R.sibling=c,r.child=R,m=r.child,m.memoizedState=Rf(a),m.childLanes=Df(t,C,a),r.memoizedState=Tf,c):($r(r),Of(r,R))}if($=t.memoizedState,$!==null&&(R=$.dehydrated,R!==null)){if(x)r.flags&256?($r(r),r.flags&=-257,r=zf(t,r,a)):r.memoizedState!==null?(Vr(),r.child=t.child,r.flags|=128,r=null):(Vr(),m=c.fallback,R=r.mode,c=oc({mode:"visible",children:c.children},R),m=jo(m,R,a,null),m.flags|=2,c.return=r,m.return=r,c.sibling=m,r.child=c,As(r,t.child,null,a),c=r.child,c.memoizedState=Rf(a),c.childLanes=Df(t,C,a),r.memoizedState=Tf,r=m);else if($r(r),gm(R)){if(C=R.nextSibling&&R.nextSibling.dataset,C)var J=C.dgst;C=J,c=Error(s(419)),c.stack="",c.digest=C,$a({value:c,source:null,stack:null}),r=zf(t,r,a)}else if(kt||Va(t,r,a,!1),C=(a&t.childLanes)!==0,kt||C){if(C=lt,C!==null&&(c=a&-a,c=(c&42)!==0?1:wa(c),c=(c&(C.suspendedLanes|a))!==0?0:c,c!==0&&c!==$.retryLane))throw $.retryLane=c,ys(t,c),rn(C,t,c),Gx;R.data==="$?"||Jf(),r=zf(t,r,a)}else R.data==="$?"?(r.flags|=192,r.child=t.child,r=null):(t=$.treeContext,ht=Nn(R.nextSibling),Ut=r,et=!0,Ao=null,On=!1,t!==null&&(mn[hn++]=ar,mn[hn++]=ir,mn[hn++]=Co,ar=t.id,ir=t.overflow,Co=r),r=Of(r,c.children),r.flags|=4096);return r}return m?(Vr(),m=c.fallback,R=r.mode,$=t.child,J=$.sibling,c=sr($,{mode:"hidden",children:c.children}),c.subtreeFlags=$.subtreeFlags&65011712,J!==null?m=sr(J,m):(m=jo(m,R,a,null),m.flags|=2),m.return=r,c.return=r,c.sibling=m,r.child=c,c=m,m=r.child,R=t.child.memoizedState,R===null?R=Rf(a):($=R.cachePool,$!==null?(J=_t._currentValue,$=$.parent!==J?{parent:J,pool:J}:$):$=Pg(),R={baseLanes:R.baseLanes|a,cachePool:$}),m.memoizedState=R,m.childLanes=Df(t,C,a),r.memoizedState=Tf,c):($r(r),a=t.child,t=a.sibling,a=sr(a,{mode:"visible",children:c.children}),a.return=r,a.sibling=null,t!==null&&(C=r.deletions,C===null?(r.deletions=[t],r.flags|=16):C.push(t)),r.child=a,r.memoizedState=null,a)}function Of(t,r){return r=oc({mode:"visible",children:r},t.mode),r.return=t,t.child=r}function oc(t,r){return t=Qt(22,t,null,r),t.lanes=0,t.stateNode={_visibility:1,_pendingMarkers:null,_retryCache:null,_transitions:null},t}function zf(t,r,a){return As(r,t.child,null,a),t=Of(r,r.pendingProps.children),t.flags|=2,r.memoizedState=null,t}function n0(t,r,a){t.lanes|=r;var c=t.alternate;c!==null&&(c.lanes|=r),Qd(t.return,r,a)}function Lf(t,r,a,c,m){var x=t.memoizedState;x===null?t.memoizedState={isBackwards:r,rendering:null,renderingStartTime:0,last:c,tail:a,tailMode:m}:(x.isBackwards=r,x.rendering=null,x.renderingStartTime=0,x.last=c,x.tail=a,x.tailMode=m)}function r0(t,r,a){var c=r.pendingProps,m=c.revealOrder,x=c.tail;if(Rt(t,r,c.children,a),c=Et.current,(c&2)!==0)c=c&1|2,r.flags|=128;else{if(t!==null&&(t.flags&128)!==0)e:for(t=r.child;t!==null;){if(t.tag===13)t.memoizedState!==null&&n0(t,a,r);else if(t.tag===19)n0(t,a,r);else if(t.child!==null){t.child.return=t,t=t.child;continue}if(t===r)break e;for(;t.sibling===null;){if(t.return===null||t.return===r)break e;t=t.return}t.sibling.return=t.return,t=t.sibling}c&=1}switch(ee(Et,c),m){case"forwards":for(a=r.child,m=null;a!==null;)t=a.alternate,t!==null&&ec(t)===null&&(m=a),a=a.sibling;a=m,a===null?(m=r.child,r.child=null):(m=a.sibling,a.sibling=null),Lf(r,!1,m,a,x);break;case"backwards":for(a=null,m=r.child,r.child=null;m!==null;){if(t=m.alternate,t!==null&&ec(t)===null){r.child=m;break}t=m.sibling,m.sibling=a,a=m,m=t}Lf(r,!0,a,null,x);break;case"together":Lf(r,!1,null,null,void 0);break;default:r.memoizedState=null}return r.child}function fr(t,r,a){if(t!==null&&(r.dependencies=t.dependencies),Fr|=r.lanes,(a&r.childLanes)===0)if(t!==null){if(Va(t,r,a,!1),(a&r.childLanes)===0)return null}else return null;if(t!==null&&r.child!==t.child)throw Error(s(153));if(r.child!==null){for(t=r.child,a=sr(t,t.pendingProps),r.child=a,a.return=r;t.sibling!==null;)t=t.sibling,a=a.sibling=sr(t,t.pendingProps),a.return=r;a.sibling=null}return r.child}function If(t,r){return(t.lanes&r)!==0?!0:(t=t.dependencies,!!(t!==null&&Hl(t)))}function Z_(t,r,a){switch(r.tag){case 3:ae(r,r.stateNode.containerInfo),Ir(r,_t,t.memoizedState.cache),Pa();break;case 27:case 5:le(r);break;case 4:ae(r,r.stateNode.containerInfo);break;case 10:Ir(r,r.type,r.memoizedProps.value);break;case 13:var c=r.memoizedState;if(c!==null)return c.dehydrated!==null?($r(r),r.flags|=128,null):(a&r.child.childLanes)!==0?t0(t,r,a):($r(r),t=fr(t,r,a),t!==null?t.sibling:null);$r(r);break;case 19:var m=(t.flags&128)!==0;if(c=(a&r.childLanes)!==0,c||(Va(t,r,a,!1),c=(a&r.childLanes)!==0),m){if(c)return r0(t,r,a);r.flags|=128}if(m=r.memoizedState,m!==null&&(m.rendering=null,m.tail=null,m.lastEffect=null),ee(Et,Et.current),c)break;return null;case 22:case 23:return r.lanes=0,Kx(t,r,a);case 24:Ir(r,_t,t.memoizedState.cache)}return fr(t,r,a)}function o0(t,r,a){if(t!==null)if(t.memoizedProps!==r.pendingProps)kt=!0;else{if(!If(t,a)&&(r.flags&128)===0)return kt=!1,Z_(t,r,a);kt=(t.flags&131072)!==0}else kt=!1,et&&(r.flags&1048576)!==0&&Og(r,Il,r.index);switch(r.lanes=0,r.tag){case 16:e:{t=r.pendingProps;var c=r.elementType,m=c._init;if(c=m(c._payload),r.type=c,typeof c=="function")Yd(c)?(t=zo(c,t),r.tag=1,r=Jx(null,r,c,t,a)):(r.tag=0,r=Mf(null,r,c,t,a));else{if(c!=null){if(m=c.$$typeof,m===A){r.tag=11,r=Xx(null,r,c,t,a);break e}else if(m===H){r.tag=14,r=Fx(null,r,c,t,a);break e}}throw r=I(c)||c,Error(s(306,r,""))}}return r;case 0:return Mf(t,r,r.type,r.pendingProps,a);case 1:return c=r.type,m=zo(c,r.pendingProps),Jx(t,r,c,m,a);case 3:e:{if(ae(r,r.stateNode.containerInfo),t===null)throw Error(s(387));c=r.pendingProps;var x=r.memoizedState;m=x.element,sf(t,r),Ka(r,c,null,a);var C=r.memoizedState;if(c=C.cache,Ir(r,_t,c),c!==x.cache&&Jd(r,[_t],a,!0),Za(),c=C.element,x.isDehydrated)if(x={element:c,isDehydrated:!1,cache:C.cache},r.updateQueue.baseState=x,r.memoizedState=x,r.flags&256){r=e0(t,r,c,a);break e}else if(c!==m){m=dn(Error(s(424)),r),$a(m),r=e0(t,r,c,a);break e}else{switch(t=r.stateNode.containerInfo,t.nodeType){case 9:t=t.body;break;default:t=t.nodeName==="HTML"?t.ownerDocument.body:t}for(ht=Nn(t.firstChild),Ut=r,et=!0,Ao=null,On=!0,a=Lx(r,null,c,a),r.child=a;a;)a.flags=a.flags&-3|4096,a=a.sibling}else{if(Pa(),c===m){r=fr(t,r,a);break e}Rt(t,r,c,a)}r=r.child}return r;case 26:return rc(t,r),t===null?(a=ly(r.type,null,r.pendingProps,null))?r.memoizedState=a:et||(a=r.type,t=r.pendingProps,c=yc(fe.current).createElement(a),c[Ct]=r,c[It]=t,Ot(c,a,t),yt(c),r.stateNode=c):r.memoizedState=ly(r.type,t.memoizedProps,r.pendingProps,t.memoizedState),null;case 27:return le(r),t===null&&et&&(c=r.stateNode=sy(r.type,r.pendingProps,fe.current),Ut=r,On=!0,m=ht,Qr(r.type)?(xm=m,ht=Nn(c.firstChild)):ht=m),Rt(t,r,r.pendingProps.children,a),rc(t,r),t===null&&(r.flags|=4194304),r.child;case 5:return t===null&&et&&((m=c=ht)&&(c=NE(c,r.type,r.pendingProps,On),c!==null?(r.stateNode=c,Ut=r,ht=Nn(c.firstChild),On=!1,m=!0):m=!1),m||Mo(r)),le(r),m=r.type,x=r.pendingProps,C=t!==null?t.memoizedProps:null,c=x.children,mm(m,x)?c=null:C!==null&&mm(m,C)&&(r.flags|=32),r.memoizedState!==null&&(m=ff(t,r,P_,null,null,a),yi._currentValue=m),rc(t,r),Rt(t,r,c,a),r.child;case 6:return t===null&&et&&((t=a=ht)&&(a=_E(a,r.pendingProps,On),a!==null?(r.stateNode=a,Ut=r,ht=null,t=!0):t=!1),t||Mo(r)),null;case 13:return t0(t,r,a);case 4:return ae(r,r.stateNode.containerInfo),c=r.pendingProps,t===null?r.child=As(r,null,c,a):Rt(t,r,c,a),r.child;case 11:return Xx(t,r,r.type,r.pendingProps,a);case 7:return Rt(t,r,r.pendingProps,a),r.child;case 8:return Rt(t,r,r.pendingProps.children,a),r.child;case 12:return Rt(t,r,r.pendingProps.children,a),r.child;case 10:return c=r.pendingProps,Ir(r,r.type,c.value),Rt(t,r,c.children,a),r.child;case 9:return m=r.type._context,c=r.pendingProps.children,Ro(r),m=Ht(m),c=c(m),r.flags|=1,Rt(t,r,c,a),r.child;case 14:return Fx(t,r,r.type,r.pendingProps,a);case 15:return Zx(t,r,r.type,r.pendingProps,a);case 19:return r0(t,r,a);case 31:return c=r.pendingProps,a=r.mode,c={mode:c.mode,children:c.children},t===null?(a=oc(c,a),a.ref=r.ref,r.child=a,a.return=r,r=a):(a=sr(t.child,c),a.ref=r.ref,r.child=a,a.return=r,r=a),r;case 22:return Kx(t,r,a);case 24:return Ro(r),c=Ht(_t),t===null?(m=nf(),m===null&&(m=lt,x=ef(),m.pooledCache=x,x.refCount++,x!==null&&(m.pooledCacheLanes|=a),m=x),r.memoizedState={parent:c,cache:m},of(r),Ir(r,_t,m)):((t.lanes&a)!==0&&(sf(t,r),Ka(r,null,null,a),Za()),m=t.memoizedState,x=r.memoizedState,m.parent!==c?(m={parent:c,cache:c},r.memoizedState=m,r.lanes===0&&(r.memoizedState=r.updateQueue.baseState=m),Ir(r,_t,c)):(c=x.cache,Ir(r,_t,c),c!==m.cache&&Jd(r,[_t],a,!0))),Rt(t,r,r.pendingProps.children,a),r.child;case 29:throw r.pendingProps}throw Error(s(156,r.tag))}function mr(t){t.flags|=4}function s0(t,r){if(r.type!=="stylesheet"||(r.state.loading&4)!==0)t.flags&=-16777217;else if(t.flags|=16777216,!my(r)){if(r=pn.current,r!==null&&((Ke&4194048)===Ke?zn!==null:(Ke&62914560)!==Ke&&(Ke&536870912)===0||r!==zn))throw Xa=rf,$g;t.flags|=8192}}function sc(t,r){r!==null&&(t.flags|=4),t.flags&16384&&(r=t.tag!==22?hl():536870912,t.lanes|=r,Ds|=r)}function ri(t,r){if(!et)switch(t.tailMode){case"hidden":r=t.tail;for(var a=null;r!==null;)r.alternate!==null&&(a=r),r=r.sibling;a===null?t.tail=null:a.sibling=null;break;case"collapsed":a=t.tail;for(var c=null;a!==null;)a.alternate!==null&&(c=a),a=a.sibling;c===null?r||t.tail===null?t.tail=null:t.tail.sibling=null:c.sibling=null}}function ft(t){var r=t.alternate!==null&&t.alternate.child===t.child,a=0,c=0;if(r)for(var m=t.child;m!==null;)a|=m.lanes|m.childLanes,c|=m.subtreeFlags&65011712,c|=m.flags&65011712,m.return=t,m=m.sibling;else for(m=t.child;m!==null;)a|=m.lanes|m.childLanes,c|=m.subtreeFlags,c|=m.flags,m.return=t,m=m.sibling;return t.subtreeFlags|=c,t.childLanes=a,r}function K_(t,r,a){var c=r.pendingProps;switch(Zd(r),r.tag){case 31:case 16:case 15:case 0:case 11:case 7:case 8:case 12:case 9:case 14:return ft(r),null;case 1:return ft(r),null;case 3:return a=r.stateNode,c=null,t!==null&&(c=t.memoizedState.cache),r.memoizedState.cache!==c&&(r.flags|=2048),cr(_t),xe(),a.pendingContext&&(a.context=a.pendingContext,a.pendingContext=null),(t===null||t.child===null)&&(Ua(r)?mr(r):t===null||t.memoizedState.isDehydrated&&(r.flags&256)===0||(r.flags|=1024,Ig())),ft(r),null;case 26:return a=r.memoizedState,t===null?(mr(r),a!==null?(ft(r),s0(r,a)):(ft(r),r.flags&=-16777217)):a?a!==t.memoizedState?(mr(r),ft(r),s0(r,a)):(ft(r),r.flags&=-16777217):(t.memoizedProps!==c&&mr(r),ft(r),r.flags&=-16777217),null;case 27:ce(r),a=fe.current;var m=r.type;if(t!==null&&r.stateNode!=null)t.memoizedProps!==c&&mr(r);else{if(!c){if(r.stateNode===null)throw Error(s(166));return ft(r),null}t=se.current,Ua(r)?zg(r):(t=sy(m,c,a),r.stateNode=t,mr(r))}return ft(r),null;case 5:if(ce(r),a=r.type,t!==null&&r.stateNode!=null)t.memoizedProps!==c&&mr(r);else{if(!c){if(r.stateNode===null)throw Error(s(166));return ft(r),null}if(t=se.current,Ua(r))zg(r);else{switch(m=yc(fe.current),t){case 1:t=m.createElementNS("http://www.w3.org/2000/svg",a);break;case 2:t=m.createElementNS("http://www.w3.org/1998/Math/MathML",a);break;default:switch(a){case"svg":t=m.createElementNS("http://www.w3.org/2000/svg",a);break;case"math":t=m.createElementNS("http://www.w3.org/1998/Math/MathML",a);break;case"script":t=m.createElement("div"),t.innerHTML="