From 94eae24082cb80a8af3830e058d82967edf9be15 Mon Sep 17 00:00:00 2001 From: Victor Dibia Date: Fri, 7 Nov 2025 15:28:32 -0800 Subject: [PATCH] Python: DevUI: Add OpenAI Responses API proxy support + HIL for Workflows (#1737) * DevUI: Add OpenAI Responses API proxy support with enhanced UI features This commit adds support for proxying requests to OpenAI's Responses API, allowing DevUI to route conversations to OpenAI models when configured to enable testing. Backend changes: - Add OpenAI proxy executor with conversation routing logic - Enhance event mapper to support OpenAI Responses API format - Extend server endpoints to handle OpenAI proxy mode - Update models with OpenAI-specific response types - Remove emojis from logging and CLI output for cleaner text Frontend changes: - Add settings modal with OpenAI proxy configuration UI - Enhance agent and workflow views with improved state management - Add new UI components (separator, switch) for settings - Update debug panel with better event filtering - Improve message renderers for OpenAI content types - Update types and API client for OpenAI integration * update ui, settings modal and workflow input form, add register cleanup hooks. * add workflow HIL support, user mode, other fixes * feat(devui): add human-in-the-loop (HIL) support with dynamic response schemas Implement HIL workflow support allowing workflows to pause for user input with dynamically generated JSON schemas based on response handler type hints. Key Features: - Automatic response schema extraction from @response_handler decorators - Dynamic form generation in UI based on Pydantic/dataclass response types - Checkpoint-based conversation storage for HIL requests/responses - Resume workflow execution after user provides HIL response Backend Changes: - Add extract_response_type_from_executor() to introspect response handlers - Enrich RequestInfoEvent with response_schema via _enrich_request_info_event_with_response_schema() - Map RequestInfoEvent to response.input.requested OpenAI event format - Store HIL responses in conversation history and restore checkpoints Frontend Changes: - Add HILInputModal component with SchemaFormRenderer for dynamic forms - Support Pydantic BaseModel and dataclass response types - Render enum fields as dropdowns, strings as text/textarea, numbers, booleans, arrays, objects - Display original request context alongside response form Testing: - Add tests for checkpoint storage (test_checkpoints.py) - Add schema generation tests for all input types (test_schema_generation.py) - Validate end-to-end HIL flow with spam workflow sample This enables workflows to seamlessly pause execution and request structured user input with type-safe, validated forms generated automatically from response type annotations. * improve HIL support, improve workflow execution view * ui updates * ui updates * improve HIL for workflows, add auth and view modes * update workflow * security improvements , ui fixes * fix mypy error * update loading spinner in ui --------- Co-authored-by: Mark Wallace <127216156+markwallace-microsoft@users.noreply.github.com> --- python/packages/devui/README.md | 85 +- .../devui/agent_framework_devui/__init__.py | 128 +- .../devui/agent_framework_devui/_cli.py | 84 +- .../agent_framework_devui/_conversations.py | 91 +- .../agent_framework_devui/_deployment.py | 588 ++++++ .../devui/agent_framework_devui/_discovery.py | 134 +- .../devui/agent_framework_devui/_executor.py | 340 +++- .../devui/agent_framework_devui/_mapper.py | 769 +++++++- .../agent_framework_devui/_openai/__init__.py | 9 + .../_openai/_executor.py | 270 +++ .../devui/agent_framework_devui/_server.py | 599 +++++- .../devui/agent_framework_devui/_utils.py | 65 + .../agent_framework_devui/models/__init__.py | 13 +- .../models/_discovery_models.py | 148 +- .../models/_openai_custom.py | 186 +- .../agent_framework_devui/ui/assets/index.css | 2 +- .../agent_framework_devui/ui/assets/index.js | 295 +-- python/packages/devui/frontend/package.json | 2 + python/packages/devui/frontend/src/App.tsx | 354 +++- .../components/features/agent/agent-view.tsx | 385 +++- .../OpenAIContentRenderer.tsx | 140 +- .../features/workflow/execution-timeline.tsx | 523 ++++++ .../features/workflow/executor-node.tsx | 71 +- .../features/workflow/hil-input-modal.tsx | 150 ++ .../src/components/features/workflow/index.ts | 2 + .../workflow/schema-form-renderer.tsx | 546 ++++++ .../features/workflow/workflow-flow.tsx | 72 +- .../workflow/workflow-session-manager.tsx | 219 +++ .../features/workflow/workflow-view.tsx | 1612 ++++++++++------- .../src/components/layout/app-header.tsx | 38 +- .../src/components/layout/debug-panel.tsx | 124 +- .../components/layout/deployment-modal.tsx | 419 ++++- .../src/components/layout/entity-selector.tsx | 196 +- .../src/components/layout/settings-modal.tsx | 454 ++++- .../frontend/src/components/ui/separator.tsx | 31 + .../frontend/src/components/ui/switch.tsx | 29 + .../src/hooks/useWorkflowEventCorrelation.ts | 126 -- .../devui/frontend/src/services/api.ts | 377 +++- .../devui/frontend/src/stores/devuiStore.ts | 319 +++- .../frontend/src/types/agent-framework.ts | 8 + .../devui/frontend/src/types/index.ts | 82 + .../devui/frontend/src/types/openai.ts | 111 +- .../frontend/src/utils/workflow-utils.ts | 135 +- python/packages/devui/frontend/yarn.lock | 22 +- .../packages/devui/tests/test_checkpoints.py | 443 +++++ .../devui/tests/test_cleanup_hooks.py | 365 ++++ python/packages/devui/tests/test_mapper.py | 50 + .../devui/tests/test_schema_generation.py | 96 +- python/packages/devui/tests/test_server.py | 135 ++ .../samples/getting_started/devui/.gitignore | 19 + .../devui/spam_workflow/workflow.py | 290 ++- .../devui/weather_agent_azure/agent.py | 26 +- 52 files changed, 10178 insertions(+), 1599 deletions(-) create mode 100644 python/packages/devui/agent_framework_devui/_deployment.py create mode 100644 python/packages/devui/agent_framework_devui/_openai/__init__.py create mode 100644 python/packages/devui/agent_framework_devui/_openai/_executor.py create mode 100644 python/packages/devui/frontend/src/components/features/workflow/execution-timeline.tsx create mode 100644 python/packages/devui/frontend/src/components/features/workflow/hil-input-modal.tsx create mode 100644 python/packages/devui/frontend/src/components/features/workflow/schema-form-renderer.tsx create mode 100644 python/packages/devui/frontend/src/components/features/workflow/workflow-session-manager.tsx create mode 100644 python/packages/devui/frontend/src/components/ui/separator.tsx create mode 100644 python/packages/devui/frontend/src/components/ui/switch.tsx delete mode 100644 python/packages/devui/frontend/src/hooks/useWorkflowEventCorrelation.ts create mode 100644 python/packages/devui/tests/test_checkpoints.py create mode 100644 python/packages/devui/tests/test_cleanup_hooks.py create mode 100644 python/samples/getting_started/devui/.gitignore 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 @@ __all__ = [ "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 @@ Examples: 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 abc import ABC, abstractmethod 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 @@ from openai.types.responses import ( # 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 @@ class ConversationStore(ABC): @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 @@ class InMemoryConversationStore(ConversationStore): # 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 @@ class InMemoryConversationStore(ConversationStore): # 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 @@ class InMemoryConversationStore(ConversationStore): 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 @@ class InMemoryConversationStore(ConversationStore): ) ) 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 @@ class EntityDiscovery: 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 @@ class EntityDiscovery: """ 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 @@ class EntityDiscovery: 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 @@ class EntityDiscovery: # 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 @@ class EntityDiscovery: """ 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 @@ class EntityDiscovery: """ 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 @@ class EntityDiscovery: 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 @@ class EntityDiscovery: 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 @@ class EntityDiscovery: # 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 @@ class EntityDiscovery: 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 @@ class EntityDiscovery: 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 @@ class EntityDiscovery: 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 @@ class EntityDiscovery: 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 collections.abc import AsyncGenerator 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 @@ class AgentFrameworkExecutor: # 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 @@ class AgentFrameworkExecutor: # Configure Agent Framework tracing only if ENABLE_OTEL is set if os.environ.get("ENABLE_OTEL"): try: - from agent_framework.observability import setup_observability + from agent_framework.observability import OBSERVABILITY_SETTINGS, setup_observability - setup_observability(enable_sensitive_data=True) - logger.info("Enabled Agent Framework 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 @@ class AgentFrameworkExecutor: 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 @@ class AgentFrameworkExecutor: 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 @@ class AgentFrameworkExecutor: 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 @@ class AgentFrameworkExecutor: 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" - # Parse input based on workflow's expected input type - parsed_input = await self._parse_workflow_input(workflow, input_data) + # 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 - logger.debug(f"Executing workflow with parsed input type: {type(parsed_input)}") + conversation_id = f"session_{entity_id}_{uuid.uuid4().hex[:8]}" + logger.info(f"Created new workflow 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 + # 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 - # Then yield the workflow event - yield event + 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 + + 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} + + 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}") + + 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 + + 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 @@ class AgentFrameworkExecutor: 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 @@ class AgentFrameworkExecutor: 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 @@ from .models import ( ResponseFunctionCallArgumentsDeltaEvent, ResponseFunctionResultComplete, ResponseFunctionToolCall, + ResponseOutputData, + ResponseOutputFile, + ResponseOutputImage, ResponseOutputItemAddedEvent, ResponseOutputMessage, ResponseOutputText, @@ -160,7 +163,7 @@ class MessageMapper: 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 @@ class MessageMapper: 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 @@ class MessageMapper: 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 @@ class MessageMapper: 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 @@ class MessageMapper: ) ] - # 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 @@ class MessageMapper: 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 @@ class MessageMapper: 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 @@ class MessageMapper: # 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 @@ class MessageMapper: # 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 @@ class MessageMapper: ) 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 @@ class MessageMapper: 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.middleware.cors import CORSMiddleware 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 @@ class DevServer: 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 @@ class DevServer: 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 @@ class DevServer: 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 @@ class DevServer: closed_count = 0 mcp_tools_closed = 0 credentials_closed = 0 + hook_count = 0 for entity_info in entities: - try: - entity_obj = self.executor.entity_discovery.get_entity_object(entity_info.id) + entity_id = entity_info.id + + try: + # 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 @@ class DevServer: 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 @@ class DevServer: # 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 @@ class DevServer: ) # 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 @@ class DevServer: 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 @@ class DevServer: # 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 @@ class DevServer: 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 @@ class DevServer: 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 @@ class DevServer: 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 @@ class DevServer: 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 @@ class DevServer: 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 @@ class DevServer: 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 @@ class DevServer: 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 @@ class DevServer: 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 @@ class DevServer: 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 import ( 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 @@ __all__ = [ "ConversationItem", "CustomResponseOutputItemAddedEvent", "CustomResponseOutputItemDoneEvent", + "Deployment", + "DeploymentConfig", + "DeploymentEvent", "DiscoveryResponse", "EntityInfo", "ExecutorActionItem", "InputTokensDetails", + "MetaResponse", "Metadata", "OpenAIError", "OpenAIResponse", @@ -67,6 +75,9 @@ __all__ = [ "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. - type: Literal["response.trace.complete"] = "response.trace.complete" + 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.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 @@ class OpenAIError(BaseModel): 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="