diff --git a/python/packages/devui/README.md b/python/packages/devui/README.md index 8c70a71f39..d9a17392b2 100644 --- a/python/packages/devui/README.md +++ b/python/packages/devui/README.md @@ -47,7 +47,7 @@ devui ./agents --port 8080 # → API: http://localhost:8080/v1/* ``` -When DevUI starts with no discovered entities, it displays a **sample entity gallery** with curated examples from the Agent Framework repository to help you get started quickly. +When DevUI starts with no discovered entities, it displays a **sample entity gallery** with curated examples from the Agent Framework repository. You can download these samples, review them, and run them locally to get started quickly. ## Directory Structure @@ -160,31 +160,37 @@ Given that DevUI offers an OpenAI Responses API, it internally maps messages and | Agent Framework Content | OpenAI Event/Type | Status | | ------------------------------- | ---------------------------------------- | -------- | | `TextContent` | `response.output_text.delta` | Standard | -| `TextReasoningContent` | `response.reasoning.delta` | Standard | +| `TextReasoningContent` | `response.reasoning_text.delta` | Standard | | `FunctionCallContent` (initial) | `response.output_item.added` | Standard | | `FunctionCallContent` (args) | `response.function_call_arguments.delta` | Standard | | `FunctionResultContent` | `response.function_result.complete` | DevUI | -| `ErrorContent` | `response.error` | Standard | +| `FunctionApprovalRequestContent`| `response.function_approval.requested` | DevUI | +| `FunctionApprovalResponseContent`| `response.function_approval.responded` | DevUI | +| `ErrorContent` | `error` | Standard | | `UsageContent` | Final `Response.usage` field (not streamed) | Standard | | `WorkflowEvent` | `response.workflow_event.complete` | DevUI | -| `DataContent`, `UriContent` | `response.trace.complete` | DevUI | +| `DataContent` | `response.trace.complete` | DevUI | +| `UriContent` | `response.trace.complete` | DevUI | +| `HostedFileContent` | `response.trace.complete` | DevUI | +| `HostedVectorStoreContent` | `response.trace.complete` | DevUI | - **Standard** = OpenAI Responses API spec -- **DevUI** = Custom extensions for Agent Framework features (workflows, traces, function results) +- **DevUI** = Custom extensions for Agent Framework features (workflows, traces, function approvals) ### OpenAI Responses API Compliance DevUI follows the OpenAI Responses API specification for maximum compatibility: **Standard OpenAI Types Used:** -- `ResponseOutputItemAddedEvent` - Output item notifications (function calls) +- `ResponseOutputItemAddedEvent` - Output item notifications (function calls and results) - `Response.usage` - Token usage (in final response, not streamed) - All standard text, reasoning, and function call events **Custom DevUI Extensions:** -- `response.function_result.complete` - Function execution results (DevUI executes functions, OpenAI doesn't) +- `response.function_approval.requested` - Function approval requests (for interactive approval workflows) +- `response.function_approval.responded` - Function approval responses (user approval/rejection) - `response.workflow_event.complete` - Agent Framework workflow events -- `response.trace.complete` - Execution traces for debugging +- `response.trace.complete` - Execution traces and internal content (DataContent, UriContent, hosted files/stores) These custom extensions are clearly namespaced and can be safely ignored by standard OpenAI clients. @@ -192,8 +198,7 @@ These custom extensions are clearly namespaced and can be safely ignored by stan - `GET /v1/entities` - List discovered agents/workflows - `GET /v1/entities/{entity_id}/info` - Get detailed entity information -- `POST /v1/entities/add` - Add entity from URL (for gallery samples) -- `DELETE /v1/entities/{entity_id}` - Remove remote entity +- `POST /v1/entities/{entity_id}/reload` - Hot reload entity (for development) ### Execution (OpenAI Responses API) @@ -214,6 +219,22 @@ These custom extensions are clearly namespaced and can be safely ignored by stan - `GET /health` - Health check +## Security + +DevUI is designed as a **sample application for local development** and should not be exposed to untrusted networks or used in production environments. + +**Security features:** +- 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 +- Review all agent/workflow code before running +- Only load entities from trusted sources +- Use `.env` files for sensitive credentials (never commit them) + ## Implementation - **Discovery**: `agent_framework_devui/_discovery.py` diff --git a/python/packages/devui/agent_framework_devui/_discovery.py b/python/packages/devui/agent_framework_devui/_discovery.py index 631ba60412..175109c7a0 100644 --- a/python/packages/devui/agent_framework_devui/_discovery.py +++ b/python/packages/devui/agent_framework_devui/_discovery.py @@ -4,7 +4,6 @@ from __future__ import annotations -import hashlib import importlib import importlib.util import logging @@ -13,17 +12,12 @@ import uuid from pathlib import Path from typing import Any -import httpx from dotenv import load_dotenv from .models._discovery_models import EntityInfo logger = logging.getLogger(__name__) -# Constants for remote entity fetching -REMOTE_FETCH_TIMEOUT_SECONDS = 30.0 -REMOTE_FETCH_MAX_SIZE_MB = 10 - class EntityDiscovery: """Discovery for Agent Framework entities - agents and workflows.""" @@ -37,7 +31,6 @@ class EntityDiscovery: self.entities_dir = entities_dir self._entities: dict[str, EntityInfo] = {} self._loaded_objects: dict[str, Any] = {} - self._remote_cache_dir = Path.home() / ".agent_framework_devui" / "remote_cache" async def discover_entities(self) -> list[EntityInfo]: """Scan for Agent Framework entities. @@ -77,6 +70,115 @@ class EntityDiscovery: """ return self._loaded_objects.get(entity_id) + async def load_entity(self, entity_id: str) -> Any: + """Load entity on-demand (lazy loading). + + 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 + + Returns: + Loaded entity object + + Raises: + ValueError: If entity not found or cannot be loaded + """ + # Check if already loaded (includes in-memory entities) + if entity_id in self._loaded_objects: + logger.debug(f"Entity {entity_id} already loaded (cache hit)") + return self._loaded_objects[entity_id] + + # Get entity metadata + entity_info = self._entities.get(entity_id) + if not entity_info: + raise ValueError(f"Entity {entity_id} not found in registry") + + # In-memory entities should never reach here (they're pre-loaded) + if entity_info.source == "in_memory": + raise ValueError(f"In-memory entity {entity_id} missing from loaded objects cache") + + logger.info(f"Lazy loading entity: {entity_id} (source: {entity_info.source})") + + # Load based on source - only directory and in-memory are supported + if entity_info.source == "directory": + entity_obj = await self._load_directory_entity(entity_id, entity_info) + else: + raise ValueError( + f"Unsupported entity source: {entity_info.source}. " + f"Only 'directory' and 'in_memory' sources are supported." + ) + + # 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( + entity_obj, + entity_type=entity_info.type if entity_info.type != "unknown" else None, + source=entity_info.source, + ) + # IMPORTANT: Preserve the original entity_id (enrichment generates a new one) + enriched_info.id = entity_id + # Preserve the original path from sparse metadata + if "path" in entity_info.metadata: + enriched_info.metadata["path"] = entity_info.metadata["path"] + enriched_info.metadata["lazy_loaded"] = True + self._entities[entity_id] = enriched_info + + # Cache the loaded object + self._loaded_objects[entity_id] = entity_obj + logger.info(f"✅ Successfully loaded entity: {entity_id} (type: {enriched_info.type})") + + return entity_obj + + async def _load_directory_entity(self, entity_id: str, entity_info: EntityInfo) -> Any: + """Load entity from directory (imports module). + + Args: + entity_id: Entity identifier + entity_info: Entity metadata + + Returns: + Loaded entity object + """ + # Get directory path from metadata + dir_path = Path(entity_info.metadata.get("path", "")) + if not dir_path.exists(): # noqa: ASYNC240 + raise ValueError(f"Entity directory not found: {dir_path}") + + # Load .env if it exists + if dir_path.is_dir(): # noqa: ASYNC240 + self._load_env_for_entity(dir_path) + else: + self._load_env_for_entity(dir_path.parent) + + # Import the module + if dir_path.is_dir(): # noqa: ASYNC240 + # Directory-based entity - try different import patterns + import_patterns = [ + entity_id, + f"{entity_id}.agent", + f"{entity_id}.workflow", + ] + + for pattern in import_patterns: + module = self._load_module_from_pattern(pattern) + if module: + # Find entity in module - pass entity_id so registration uses correct ID + entity_obj = await self._find_entity_in_module(module, entity_id, str(dir_path)) + if entity_obj: + return entity_obj + + raise ValueError(f"No valid entity found in {dir_path}") + # File-based entity + module = self._load_module_from_file(dir_path, entity_id) + if module: + entity_obj = await self._find_entity_in_module(module, entity_id, str(dir_path)) + if entity_obj: + return entity_obj + + raise ValueError(f"No valid entity found in {dir_path}") + def list_entities(self) -> list[EntityInfo]: """List all discovered entities. @@ -85,6 +187,48 @@ class EntityDiscovery: """ return list(self._entities.values()) + def invalidate_entity(self, entity_id: str) -> None: + """Invalidate (clear cache for) an entity to enable hot reload. + + This removes the entity from the loaded objects cache and clears its module + from Python's sys.modules cache. The entity metadata remains, so it will be + reimported on next access. + + Args: + entity_id: Entity identifier to invalidate + """ + # Remove from loaded objects cache + if entity_id in self._loaded_objects: + del self._loaded_objects[entity_id] + logger.info(f"Cleared loaded object cache for: {entity_id}") + + # Clear from Python's module cache (including submodules) + keys_to_delete = [ + module_name + for module_name in sys.modules + if module_name == entity_id or module_name.startswith(f"{entity_id}.") + ] + for key in keys_to_delete: + del sys.modules[key] + logger.debug(f"Cleared module cache: {key}") + + # Reset lazy_loaded flag in metadata + entity_info = self._entities.get(entity_id) + if entity_info and "lazy_loaded" in entity_info.metadata: + entity_info.metadata["lazy_loaded"] = False + + logger.info(f"♻️ Entity invalidated: {entity_id} (will reload on next access)") + + def invalidate_all(self) -> None: + """Invalidate all cached entities. + + Useful for forcing a complete reload of all entities. + """ + entity_ids = list(self._loaded_objects.keys()) + for entity_id in entity_ids: + self.invalidate_entity(entity_id) + logger.info(f"Invalidated {len(entity_ids)} entities") + def register_entity(self, entity_id: str, entity_info: EntityInfo, entity_object: Any) -> None: """Register an entity with both metadata and object. @@ -187,7 +331,10 @@ class EntityDiscovery: ) async def _scan_entities_directory(self, entities_dir: Path) -> None: - """Scan the entities directory for Agent Framework entities. + """Scan the entities directory for Agent Framework entities (lazy loading). + + This method scans the filesystem WITHOUT importing modules, creating sparse + metadata that will be enriched on-demand when entities are accessed. Args: entities_dir: Directory to scan for entities @@ -196,78 +343,120 @@ class EntityDiscovery: logger.warning(f"Entities directory not found: {entities_dir}") return - logger.info(f"Scanning {entities_dir} for Agent Framework entities...") + logger.info(f"Scanning {entities_dir} for Agent Framework entities (lazy mode)...") # Add entities directory to Python path if not already there entities_dir_str = str(entities_dir) if entities_dir_str not in sys.path: sys.path.insert(0, entities_dir_str) - # Scan for directories and Python files + # Scan for directories and Python files WITHOUT importing for item in entities_dir.iterdir(): # noqa: ASYNC240 if item.name.startswith(".") or item.name == "__pycache__": continue - if item.is_dir(): - # Directory-based entity - await self._discover_entities_in_directory(item) + if item.is_dir() and self._looks_like_entity(item): + # Directory-based entity - create sparse metadata + self._register_sparse_entity(item) elif item.is_file() and item.suffix == ".py" and not item.name.startswith("_"): - # Single file entity - await self._discover_entities_in_file(item) + # Single file entity - create sparse metadata + self._register_sparse_file_entity(item) - async def _discover_entities_in_directory(self, dir_path: Path) -> None: - """Discover entities in a directory using module import. + def _looks_like_entity(self, dir_path: Path) -> bool: + """Check if directory contains an entity (without importing). Args: - dir_path: Directory containing entity + dir_path: Directory to check + + Returns: + True if directory appears to contain an entity + """ + return ( + (dir_path / "agent.py").exists() + or (dir_path / "workflow.py").exists() + or (dir_path / "__init__.py").exists() + ) + + def _detect_entity_type(self, dir_path: Path) -> str: + """Detect entity type from directory structure (without importing). + + Uses filename conventions to determine entity type: + - workflow.py → "workflow" + - agent.py → "agent" + - both or neither → "unknown" + + Args: + dir_path: Directory to analyze + + Returns: + Entity type: "workflow", "agent", or "unknown" + """ + has_agent = (dir_path / "agent.py").exists() + has_workflow = (dir_path / "workflow.py").exists() + + if has_agent and has_workflow: + # Both files exist - ambiguous, mark as unknown + return "unknown" + if has_workflow: + return "workflow" + if has_agent: + return "agent" + # Has __init__.py but no specific file + return "unknown" + + def _register_sparse_entity(self, dir_path: Path) -> None: + """Register entity with sparse metadata (no import). + + Args: + dir_path: Entity directory """ entity_id = dir_path.name - logger.debug(f"Scanning directory: {entity_id}") + entity_type = self._detect_entity_type(dir_path) - try: - # Load environment variables for this entity first - self._load_env_for_entity(dir_path) + entity_info = EntityInfo( + id=entity_id, + name=entity_id.replace("_", " ").title(), + type=entity_type, + framework="agent_framework", + tools=[], # Sparse - will be populated on load + description="", # Sparse - will be populated on load + source="directory", + metadata={ + "path": str(dir_path), + "discovered": True, + "lazy_loaded": False, + }, + ) - # Try different import patterns - import_patterns = [ - entity_id, # Direct module import - f"{entity_id}.agent", # agent.py submodule - f"{entity_id}.workflow", # workflow.py submodule - ] + self._entities[entity_id] = entity_info + logger.debug(f"Registered sparse entity: {entity_id} (type: {entity_type})") - for pattern in import_patterns: - module = self._load_module_from_pattern(pattern) - if module: - entities_found = await self._find_entities_in_module(module, entity_id, str(dir_path)) - if entities_found: - logger.debug(f"Found {len(entities_found)} entities in {pattern}") - break - - except Exception as e: - logger.warning(f"Error scanning directory {entity_id}: {e}") - - async def _discover_entities_in_file(self, file_path: Path) -> None: - """Discover entities in a single Python file. + def _register_sparse_file_entity(self, file_path: Path) -> None: + """Register file-based entity with sparse metadata (no import). Args: - file_path: Python file to scan + file_path: Entity Python file """ - try: - # Load environment variables for this entity's directory first - self._load_env_for_entity(file_path.parent) + entity_id = file_path.stem - # Create module name from file path - base_name = file_path.stem + # File-based entities are typically agents, but we can't know for sure without importing + entity_info = EntityInfo( + id=entity_id, + name=entity_id.replace("_", " ").title(), + type="unknown", # Will be determined on load + framework="agent_framework", + tools=[], + description="", + source="directory", + metadata={ + "path": str(file_path), + "discovered": True, + "lazy_loaded": False, + }, + ) - # Load the module directly from file - module = self._load_module_from_file(file_path, base_name) - if module: - entities_found = await self._find_entities_in_module(module, base_name, str(file_path)) - if entities_found: - logger.debug(f"Found {len(entities_found)} entities in {file_path.name}") - - except Exception as e: - logger.warning(f"Error scanning file {file_path}: {e}") + self._entities[entity_id] = entity_info + logger.debug(f"Registered sparse file entity: {entity_id}") def _load_env_for_entity(self, entity_path: Path) -> bool: """Load .env file for an entity. @@ -359,19 +548,17 @@ class EntityDiscovery: logger.warning(f"Error loading module from {file_path}: {e}") return None - async def _find_entities_in_module(self, module: Any, base_id: str, module_path: str) -> list[str]: - """Find agent and workflow entities in a loaded module. + async def _find_entity_in_module(self, module: Any, entity_id: str, module_path: str) -> Any: + """Find agent or workflow entity in a loaded module. Args: module: Loaded Python module - base_id: Base identifier for entities + entity_id: Expected entity identifier to register with module_path: Path to module for metadata Returns: - List of entity IDs that were found and registered + Loaded entity object, or None if not found """ - entities_found = [] - # Look for explicit variable names first candidates = [ ("agent", getattr(module, "agent", None)), @@ -383,11 +570,12 @@ class EntityDiscovery: continue if self._is_valid_entity(obj, obj_type): - # Pass source as "directory" for directory-discovered entities - await self._register_entity_from_object(obj, obj_type, module_path, source="directory") - entities_found.append(obj_type) + # Register with the correct entity_id (from directory name) + # Store the object directly in _loaded_objects so we can return it + self._loaded_objects[entity_id] = obj + return obj - return entities_found + return None def _is_valid_entity(self, obj: Any, expected_type: str) -> bool: """Check if object is a valid agent or workflow using duck typing. @@ -602,173 +790,3 @@ class EntityDiscovery: full_uuid = uuid.uuid4().hex return f"{entity_type}_{source}_{base_name}_{full_uuid}" - - async def fetch_remote_entity( - self, url: str, metadata: dict[str, Any] | None = None - ) -> tuple[EntityInfo | None, str | None]: - """Fetch and register entity from URL. - - Args: - url: URL to Python file containing entity - metadata: Additional metadata (source, sampleId, etc.) - - Returns: - Tuple of (EntityInfo if successful, error_message if failed) - """ - try: - normalized_url = self._normalize_url(url) - logger.info(f"Normalized URL: {normalized_url}") - - content = await self._fetch_url_content(normalized_url) - if not content: - error_msg = "Failed to fetch content from URL. The file may not exist or is not accessible." - logger.warning(error_msg) - return None, error_msg - - if not self._validate_python_syntax(content): - error_msg = "Invalid Python syntax in the file. Please check the file contains valid Python code." - logger.warning(error_msg) - return None, error_msg - - entity_object = await self._load_entity_from_content(content, url) - if not entity_object: - error_msg = ( - "No valid agent or workflow found in the file. " - "Make sure the file contains an 'agent' or 'workflow' variable." - ) - logger.warning(error_msg) - return None, error_msg - - entity_info = await self.create_entity_info_from_object( - entity_object, - entity_type=None, # Auto-detect - source="remote", - ) - - entity_info.source = metadata.get("source", "remote_gallery") if metadata else "remote_gallery" - entity_info.original_url = url - if metadata: - entity_info.metadata.update(metadata) - - self.register_entity(entity_info.id, entity_info, entity_object) - - logger.info(f"Successfully added remote entity: {entity_info.id}") - return entity_info, None - - except Exception as e: - error_msg = f"Unexpected error: {e!s}" - logger.error(f"Error fetching remote entity from {url}: {e}", exc_info=True) - return None, error_msg - - def _normalize_url(self, url: str) -> str: - """Convert various Git hosting URLs to raw content URLs.""" - # GitHub: blob -> raw - if "github.com" in url and "/blob/" in url: - return url.replace("github.com", "raw.githubusercontent.com").replace("/blob/", "/") - - # GitLab: blob -> raw - if "gitlab.com" in url and "/-/blob/" in url: - return url.replace("/-/blob/", "/-/raw/") - - # Bitbucket: src -> raw - if "bitbucket.org" in url and "/src/" in url: - return url.replace("/src/", "/raw/") - - return url - - async def _fetch_url_content(self, url: str, max_size_mb: int = REMOTE_FETCH_MAX_SIZE_MB) -> str | None: - """Fetch content from URL with size and timeout limits.""" - try: - async with httpx.AsyncClient(timeout=REMOTE_FETCH_TIMEOUT_SECONDS) as client: - response = await client.get(url) - - if response.status_code != 200: - logger.warning(f"HTTP {response.status_code} for {url}") - return None - - # Check content length - content_length = response.headers.get("content-length") - if content_length and int(content_length) > max_size_mb * 1024 * 1024: - logger.warning(f"File too large: {content_length} bytes") - return None - - # Read with size limit - content = response.text - if len(content.encode("utf-8")) > max_size_mb * 1024 * 1024: - logger.warning("Content too large after reading") - return None - - return content - - except Exception as e: - logger.error(f"Error fetching {url}: {e}") - return None - - def _validate_python_syntax(self, content: str) -> bool: - """Validate that content is valid Python code.""" - try: - compile(content, "", "exec") - return True - except SyntaxError as e: - logger.warning(f"Python syntax error: {e}") - return False - - async def _load_entity_from_content(self, content: str, source_url: str) -> Any | None: - """Load entity object from Python content string using disk-based import. - - This method caches remote entities to disk and uses importlib for loading, - making it consistent with local entity discovery and avoiding exec() security warnings. - """ - try: - # Create cache directory if it doesn't exist - self._remote_cache_dir.mkdir(parents=True, exist_ok=True) - - # Generate a unique filename based on URL hash - url_hash = hashlib.sha256(source_url.encode()).hexdigest()[:16] - module_name = f"remote_entity_{url_hash}" - cached_file = self._remote_cache_dir / f"{module_name}.py" - - # Write content to cache file - cached_file.write_text(content, encoding="utf-8") - logger.debug(f"Cached remote entity to {cached_file}") - - # Load module from cached file using importlib (same as local scanning) - module = self._load_module_from_file(cached_file, module_name) - if not module: - logger.warning(f"Failed to load module from cached file: {cached_file}") - return None - - # Look for agent or workflow objects in the loaded module - for name in dir(module): - if name.startswith("_"): - continue - - obj = getattr(module, name) - - # Check for explicitly named entities first - if name in ["agent", "workflow"] and self._is_valid_entity(obj, name): - return obj - - # Also check if any object looks like an agent/workflow - if self._is_valid_agent(obj) or self._is_valid_workflow(obj): - return obj - - return None - - except Exception as e: - logger.error(f"Error loading entity from content: {e}") - return None - - def remove_remote_entity(self, entity_id: str) -> bool: - """Remove a remote entity by ID.""" - if entity_id in self._entities: - entity_info = self._entities[entity_id] - if entity_info.source in ["remote_gallery", "remote"]: - del self._entities[entity_id] - if entity_id in self._loaded_objects: - del self._loaded_objects[entity_id] - logger.info(f"Removed remote entity: {entity_id}") - return True - logger.warning(f"Cannot remove local entity: {entity_id}") - return False - return False diff --git a/python/packages/devui/agent_framework_devui/_executor.py b/python/packages/devui/agent_framework_devui/_executor.py index 68740732e9..6c02d91cf0 100644 --- a/python/packages/devui/agent_framework_devui/_executor.py +++ b/python/packages/devui/agent_framework_devui/_executor.py @@ -169,9 +169,11 @@ class AgentFrameworkExecutor: Raw Agent Framework events and trace events """ try: - # Get entity info and object + # Get entity info entity_info = self.get_entity_info(entity_id) - entity_obj = self.entity_discovery.get_entity_object(entity_id) + + # Trigger lazy loading (will return from cache if already loaded) + entity_obj = await self.entity_discovery.load_entity(entity_id) if not entity_obj: raise EntityNotFoundError(f"Entity object for '{entity_id}' not found") diff --git a/python/packages/devui/agent_framework_devui/_mapper.py b/python/packages/devui/agent_framework_devui/_mapper.py index e950216ae2..488b1be10b 100644 --- a/python/packages/devui/agent_framework_devui/_mapper.py +++ b/python/packages/devui/agent_framework_devui/_mapper.py @@ -524,21 +524,15 @@ class MessageMapper: async def _map_function_result_content( self, content: Any, context: dict[str, Any] ) -> ResponseFunctionResultComplete: - """Map FunctionResultContent to custom DevUI event. + """Map FunctionResultContent to DevUI custom event. - This is a DevUI extension - OpenAI doesn't stream function execution results - because in their model, applications execute functions, not the API. - Agent Framework executes functions, so we emit this event for debugging visibility. - - IMPORTANT: Always use Agent Framework's call_id from the content. - Do NOT generate a new call_id - it must match the one from the function call event. + DevUI extension: The OpenAI Responses API doesn't stream function execution results + (in OpenAI's model, the application executes functions, not the API). """ - # Get call_id from content - this MUST match the call_id from the function call + # Get call_id from content call_id = getattr(content, "call_id", None) - if not call_id: - logger.warning("FunctionResultContent missing call_id - this will break call/result pairing") - call_id = f"call_{uuid.uuid4().hex[:8]}" # Fallback only if truly missing + call_id = f"call_{uuid.uuid4().hex[:8]}" # Extract result result = getattr(content, "result", None) @@ -547,16 +541,19 @@ class MessageMapper: # Convert result to string output = result if isinstance(result, str) else json.dumps(result) if result is not None else "" - # Determine status + # Determine status based on exception status = "incomplete" if exception else "completed" - # Return custom DevUI event + # Generate item_id + item_id = f"item_{uuid.uuid4().hex[:8]}" + + # Return DevUI custom event return ResponseFunctionResultComplete( type="response.function_result.complete", call_id=call_id, output=output, status=status, - item_id=context["item_id"], + item_id=item_id, output_index=context["output_index"], sequence_number=self._next_sequence(context), ) @@ -663,15 +660,24 @@ class MessageMapper: async def _map_approval_request_content(self, content: Any, context: dict[str, Any]) -> dict[str, Any]: """Map FunctionApprovalRequestContent to custom event.""" + # Parse arguments to ensure they're always a dict, not a JSON string + # This prevents double-escaping when the frontend calls JSON.stringify() + arguments: dict[str, Any] = {} + if hasattr(content, "function_call"): + if hasattr(content.function_call, "parse_arguments"): + # Use parse_arguments() to convert string arguments to dict + arguments = content.function_call.parse_arguments() or {} + else: + # Fallback to direct access if parse_arguments doesn't exist + arguments = getattr(content.function_call, "arguments", {}) + return { "type": "response.function_approval.requested", "request_id": getattr(content, "id", "unknown"), "function_call": { "id": getattr(content.function_call, "call_id", "") if hasattr(content, "function_call") else "", "name": getattr(content.function_call, "name", "") if hasattr(content, "function_call") else "", - "arguments": getattr(content.function_call, "arguments", {}) - if hasattr(content, "function_call") - else {}, + "arguments": arguments, }, "item_id": context["item_id"], "output_index": context["output_index"], diff --git a/python/packages/devui/agent_framework_devui/_server.py b/python/packages/devui/agent_framework_devui/_server.py index e0c4f2a565..e6fd871ca2 100644 --- a/python/packages/devui/agent_framework_devui/_server.py +++ b/python/packages/devui/agent_framework_devui/_server.py @@ -174,7 +174,7 @@ class DevServer: @app.get("/v1/entities/{entity_id}/info", response_model=EntityInfo) async def get_entity_info(entity_id: str) -> EntityInfo: - """Get detailed information about a specific entity.""" + """Get detailed information about a specific entity (triggers lazy loading).""" try: executor = await self._ensure_executor() entity_info = executor.get_entity_info(entity_id) @@ -182,90 +182,96 @@ class DevServer: if not entity_info: raise HTTPException(status_code=404, detail=f"Entity {entity_id} not found") + # 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) + + # Get updated entity info (may have been enriched during load) + entity_info = executor.get_entity_info(entity_id) or entity_info + # For workflows, populate additional detailed information - if entity_info.type == "workflow": - entity_obj = executor.entity_discovery.get_entity_object(entity_id) - if entity_obj: - # Get workflow structure - workflow_dump = None - if hasattr(entity_obj, "to_dict") and callable(getattr(entity_obj, "to_dict", None)): - try: - workflow_dump = entity_obj.to_dict() # type: ignore[attr-defined] - except Exception: - workflow_dump = None - elif hasattr(entity_obj, "to_json") and callable(getattr(entity_obj, "to_json", None)): - try: - raw_dump = entity_obj.to_json() # type: ignore[attr-defined] - except Exception: - workflow_dump = None - else: - if isinstance(raw_dump, (bytes, bytearray)): - try: - raw_dump = raw_dump.decode() - except Exception: - raw_dump = raw_dump.decode(errors="replace") - if isinstance(raw_dump, str): - try: - parsed_dump = json.loads(raw_dump) - except Exception: - workflow_dump = raw_dump - else: - workflow_dump = parsed_dump if isinstance(parsed_dump, dict) else raw_dump - else: - workflow_dump = raw_dump - elif hasattr(entity_obj, "__dict__"): - workflow_dump = {k: v for k, v in entity_obj.__dict__.items() if not k.startswith("_")} - - # Get input schema information - input_schema = {} - input_type_name = "Unknown" - start_executor_id = "" - + if entity_info.type == "workflow" and entity_obj: + # Entity object already loaded by load_entity() above + # Get workflow structure + workflow_dump = None + if hasattr(entity_obj, "to_dict") and callable(getattr(entity_obj, "to_dict", None)): try: - from ._utils import ( - extract_executor_message_types, - generate_input_schema, - select_primary_input_type, + workflow_dump = entity_obj.to_dict() # type: ignore[attr-defined] + except Exception: + workflow_dump = None + elif hasattr(entity_obj, "to_json") and callable(getattr(entity_obj, "to_json", None)): + try: + raw_dump = entity_obj.to_json() # type: ignore[attr-defined] + except Exception: + workflow_dump = None + else: + if isinstance(raw_dump, (bytes, bytearray)): + try: + raw_dump = raw_dump.decode() + except Exception: + raw_dump = raw_dump.decode(errors="replace") + if isinstance(raw_dump, str): + try: + parsed_dump = json.loads(raw_dump) + except Exception: + workflow_dump = raw_dump + else: + workflow_dump = parsed_dump if isinstance(parsed_dump, dict) else raw_dump + else: + workflow_dump = raw_dump + elif hasattr(entity_obj, "__dict__"): + workflow_dump = {k: v for k, v in entity_obj.__dict__.items() if not k.startswith("_")} + + # Get input schema information + input_schema = {} + input_type_name = "Unknown" + start_executor_id = "" + + try: + from ._utils import ( + extract_executor_message_types, + generate_input_schema, + select_primary_input_type, + ) + + start_executor = entity_obj.get_start_executor() + except Exception as e: + logger.debug(f"Could not extract input info for workflow {entity_id}: {e}") + else: + if start_executor: + start_executor_id = getattr(start_executor, "executor_id", "") or getattr( + start_executor, "id", "" ) - start_executor = entity_obj.get_start_executor() - except Exception as e: - logger.debug(f"Could not extract input info for workflow {entity_id}: {e}") - else: - if start_executor: - start_executor_id = getattr(start_executor, "executor_id", "") or getattr( - start_executor, "id", "" - ) + message_types = extract_executor_message_types(start_executor) + input_type = select_primary_input_type(message_types) - message_types = extract_executor_message_types(start_executor) - input_type = select_primary_input_type(message_types) + if input_type: + input_type_name = getattr(input_type, "__name__", str(input_type)) - if input_type: - input_type_name = getattr(input_type, "__name__", str(input_type)) + # Generate schema using comprehensive schema generation + input_schema = generate_input_schema(input_type) - # Generate schema using comprehensive schema generation - input_schema = generate_input_schema(input_type) + if not input_schema: + input_schema = {"type": "string"} + if input_type_name == "Unknown": + input_type_name = "string" - if not input_schema: - input_schema = {"type": "string"} - if input_type_name == "Unknown": - input_type_name = "string" + # Get executor list + executor_list = [] + if hasattr(entity_obj, "executors") and entity_obj.executors: + executor_list = [getattr(ex, "executor_id", str(ex)) for ex in entity_obj.executors] - # Get executor list - executor_list = [] - if hasattr(entity_obj, "executors") and entity_obj.executors: - executor_list = [getattr(ex, "executor_id", str(ex)) for ex in entity_obj.executors] - - # Create copy of entity info and populate workflow-specific fields - update_payload: dict[str, Any] = { - "workflow_dump": workflow_dump, - "input_schema": input_schema, - "input_type_name": input_type_name, - "start_executor_id": start_executor_id, - } - if executor_list: - update_payload["executors"] = executor_list - return entity_info.model_copy(update=update_payload) + # Create copy of entity info and populate workflow-specific fields + update_payload: dict[str, Any] = { + "workflow_dump": workflow_dump, + "input_schema": input_schema, + "input_type_name": input_type_name, + "start_executor_id": start_executor_id, + } + if executor_list: + update_payload["executors"] = executor_list + return entity_info.model_copy(update=update_payload) # For non-workflow entities, return as-is return entity_info @@ -276,70 +282,34 @@ class DevServer: 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 - @app.post("/v1/entities/add") - async def add_entity(request: dict[str, Any]) -> dict[str, Any]: - """Add entity from URL.""" + @app.post("/v1/entities/{entity_id}/reload") + async def reload_entity(entity_id: str) -> dict[str, Any]: + """Hot reload entity (clears cache, will reimport on next access). + + This enables hot reload during development - edit entity code, call this endpoint, + and the next execution will use the updated code without server restart. + """ try: - url = request.get("url") - metadata = request.get("metadata", {}) - - if not url: - raise HTTPException(status_code=400, detail="URL is required") - - logger.info(f"Attempting to add entity from URL: {url}") executor = await self._ensure_executor() - entity_info, error_msg = await executor.entity_discovery.fetch_remote_entity(url, metadata) + # Check if entity exists + entity_info = executor.get_entity_info(entity_id) if not entity_info: - # Sanitize error message - only return safe, user-friendly errors - logger.error(f"Failed to fetch or validate entity from {url}: {error_msg}") - safe_error = error_msg if error_msg else "Failed to fetch or validate entity" - raise HTTPException(status_code=400, detail=safe_error) + raise HTTPException(status_code=404, detail=f"Entity {entity_id} not found") - logger.info(f"Successfully added entity: {entity_info.id}") - return {"success": True, "entity": entity_info.model_dump()} + # Invalidate cache + executor.entity_discovery.invalidate_entity(entity_id) + + return { + "success": True, + "message": f"Entity '{entity_id}' cache cleared. Will reload on next access.", + } except HTTPException: raise except Exception as e: - logger.error(f"Error adding entity: {e}", exc_info=True) - # Don't expose internal error details to client - raise HTTPException( - status_code=500, detail="An unexpected error occurred while adding the entity" - ) from e - - @app.delete("/v1/entities/{entity_id}") - async def remove_entity(entity_id: str) -> dict[str, Any]: - """Remove entity by ID.""" - try: - executor = await self._ensure_executor() - - # Cleanup entity resources before removal - try: - entity_obj = executor.entity_discovery.get_entity_object(entity_id) - if entity_obj and hasattr(entity_obj, "chat_client"): - client = entity_obj.chat_client - if hasattr(client, "close") and callable(client.close): - if inspect.iscoroutinefunction(client.close): - await client.close() - else: - client.close() - logger.info(f"Closed client for entity: {entity_id}") - except Exception as e: - logger.warning(f"Error closing entity {entity_id} during removal: {e}") - - # Remove entity from registry - success = executor.entity_discovery.remove_remote_entity(entity_id) - - if success: - return {"success": True} - raise HTTPException(status_code=404, detail="Entity not found or cannot be removed") - - except HTTPException: - raise - except Exception as e: - logger.error(f"Error removing entity {entity_id}: {e}") - raise HTTPException(status_code=500, detail=f"Failed to remove entity: {e!s}") from e + logger.error(f"Error reloading entity {entity_id}: {e}") + raise HTTPException(status_code=500, detail=f"Failed to reload entity: {e!s}") from e @app.post("/v1/responses") async def create_response(request: AgentFrameworkRequest, raw_request: Request) -> Any: 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 f4faaf6065..690efa7f9f 100644 --- a/python/packages/devui/agent_framework_devui/models/_discovery_models.py +++ b/python/packages/devui/agent_framework_devui/models/_discovery_models.py @@ -31,8 +31,7 @@ class EntityInfo(BaseModel): metadata: dict[str, Any] = Field(default_factory=dict) # Source information - source: str = "directory" # "directory", "in_memory", "remote_gallery" - original_url: str | None = None + source: str = "directory" # "directory" or "in_memory" # Environment variable requirements required_env_vars: list[EnvVarRequirement] | None = None 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 aa41ea2522..f07f9c7b9c 100644 --- a/python/packages/devui/agent_framework_devui/models/_openai_custom.py +++ b/python/packages/devui/agent_framework_devui/models/_openai_custom.py @@ -38,11 +38,16 @@ class ResponseTraceEventComplete(BaseModel): class ResponseFunctionResultComplete(BaseModel): - """Custom DevUI event for function execution results. + """DevUI extension: Stream function execution results. - This is a DevUI extension - OpenAI doesn't stream function execution results - because in their model, the application executes functions, not the API. - Agent Framework executes functions, so we emit this event for debugging visibility. + This is a DevUI extension because: + - OpenAI Responses API doesn't stream function results (clients execute functions) + - Agent Framework executes functions server-side, so we stream results for debugging visibility + - ResponseFunctionToolCallOutputItem exists in OpenAI SDK but isn't in ResponseOutputItem union + (it's for Conversations API input, not Responses API streaming output) + + This event provides the same structure as OpenAI's function output items but wrapped + in a custom event type since standard events don't support streaming function results. """ type: Literal["response.function_result.complete"] = "response.function_result.complete" diff --git a/python/packages/devui/agent_framework_devui/ui/assets/index-BhFnsoso.css b/python/packages/devui/agent_framework_devui/ui/assets/index-BhFnsoso.css deleted file mode 100644 index 41a2f21902..0000000000 --- a/python/packages/devui/agent_framework_devui/ui/assets/index-BhFnsoso.css +++ /dev/null @@ -1 +0,0 @@ -/*! 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-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;--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}.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-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-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}}.-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)}.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-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}.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-\[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-96{max-height:calc(var(--spacing)*96)}.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-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-fit{width:fit-content}.w-full{width:100%}.max-w-2xl{max-width:var(--container-2xl)}.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-\[var\(--radix-select-trigger-width\)\]{min-width:var(--radix-select-trigger-width)}.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-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-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-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-destructive,.border-destructive\/20{border-color:var(--destructive)}@supports (color:color-mix(in lab,red,red)){.border-destructive\/20{border-color:color-mix(in oklab,var(--destructive)20%,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-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\/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,.bg-background\/80{background-color:var(--background)}@supports (color:color-mix(in lab,red,red)){.bg-background\/80{background-color:color-mix(in oklab,var(--background)80%,transparent)}}.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-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-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-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-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-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-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-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-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-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}.underline-offset-4{text-underline-offset:4px}.opacity-0{opacity:0}.opacity-50{opacity:.5}.opacity-60{opacity:.6}.opacity-70{opacity:.7}.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)}@media (hover:hover){.hover\:border-border:hover{border-color:var(--border)}.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-background:hover{background-color:var(--background)}.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-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-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\:bg-red-950\/50:is(.dark *){background-color:#46080980}@supports (color:color-mix(in lab,red,red)){.dark\:bg-red-950\/50:is(.dark *){background-color:color-mix(in oklab,var(--color-red-950)50%,transparent)}}.dark\:text-\[\#8B5CF6\]:is(.dark *){color:#8b5cf6}.dark\:text-amber-100:is(.dark *){color:var(--color-amber-100)}.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)}}.\[\&_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}}@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-CE4pGoXh.css b/python/packages/devui/agent_framework_devui/ui/assets/index-CE4pGoXh.css new file mode 100644 index 0000000000..c86e173c41 --- /dev/null +++ b/python/packages/devui/agent_framework_devui/ui/assets/index-CE4pGoXh.css @@ -0,0 +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}.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))} diff --git a/python/packages/devui/agent_framework_devui/ui/assets/index-DmL7WSFa.js b/python/packages/devui/agent_framework_devui/ui/assets/index-DmL7WSFa.js new file mode 100644 index 0000000000..cd628003b6 --- /dev/null +++ b/python/packages/devui/agent_framework_devui/ui/assets/index-DmL7WSFa.js @@ -0,0 +1,577 @@ +function H_(e,r){for(var o=0;oa[l]})}}}return Object.freeze(Object.defineProperty(e,Symbol.toStringTag,{value:"Module"}))}(function(){const r=document.createElement("link").relList;if(r&&r.supports&&r.supports("modulepreload"))return;for(const l of document.querySelectorAll('link[rel="modulepreload"]'))a(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"&&a(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 a(l){if(l.ep)return;l.ep=!0;const u=o(l);fetch(l.href,u)}})();function qh(e){return e&&e.__esModule&&Object.prototype.hasOwnProperty.call(e,"default")?e.default:e}var Em={exports:{}},wi={};/** + * @license React + * react-jsx-runtime.production.js + * + * Copyright (c) Meta Platforms, Inc. and affiliates. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + */var bv;function B_(){if(bv)return wi;bv=1;var e=Symbol.for("react.transitional.element"),r=Symbol.for("react.fragment");function o(a,l,u){var d=null;if(u!==void 0&&(d=""+u),l.key!==void 0&&(d=""+l.key),"key"in l){u={};for(var f in l)f!=="key"&&(u[f]=l[f])}else u=l;return l=u.ref,{$$typeof:e,type:a,key:d,ref:l!==void 0?l:null,props:u}}return wi.Fragment=r,wi.jsx=o,wi.jsxs=o,wi}var wv;function U_(){return wv||(wv=1,Em.exports=B_()),Em.exports}var i=U_(),_m={exports:{}},Le={};/** + * @license React + * react.production.js + * + * Copyright (c) Meta Platforms, Inc. and affiliates. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + */var Nv;function P_(){if(Nv)return Le;Nv=1;var e=Symbol.for("react.transitional.element"),r=Symbol.for("react.portal"),o=Symbol.for("react.fragment"),a=Symbol.for("react.strict_mode"),l=Symbol.for("react.profiler"),u=Symbol.for("react.consumer"),d=Symbol.for("react.context"),f=Symbol.for("react.forward_ref"),h=Symbol.for("react.suspense"),p=Symbol.for("react.memo"),g=Symbol.for("react.lazy"),y=Symbol.iterator;function v(T){return T===null||typeof T!="object"?null:(T=y&&T[y]||T["@@iterator"],typeof T=="function"?T:null)}var b={isMounted:function(){return!1},enqueueForceUpdate:function(){},enqueueReplaceState:function(){},enqueueSetState:function(){}},S=Object.assign,w={};function j(T,U,X){this.props=T,this.context=U,this.refs=w,this.updater=X||b}j.prototype.isReactComponent={},j.prototype.setState=function(T,U){if(typeof T!="object"&&typeof T!="function"&&T!=null)throw Error("takes an object of state variables to update or a function which returns an object of state variables.");this.updater.enqueueSetState(this,T,U,"setState")},j.prototype.forceUpdate=function(T){this.updater.enqueueForceUpdate(this,T,"forceUpdate")};function k(){}k.prototype=j.prototype;function M(T,U,X){this.props=T,this.context=U,this.refs=w,this.updater=X||b}var _=M.prototype=new k;_.constructor=M,S(_,j.prototype),_.isPureReactComponent=!0;var A=Array.isArray,D={H:null,A:null,T:null,S:null,V:null},L=Object.prototype.hasOwnProperty;function H(T,U,X,ee,ae,me){return X=me.ref,{$$typeof:e,type:T,key:U,ref:X!==void 0?X:null,props:me}}function B(T,U){return H(T.type,U,void 0,void 0,void 0,T.props)}function q(T){return typeof T=="object"&&T!==null&&T.$$typeof===e}function Z(T){var U={"=":"=0",":":"=2"};return"$"+T.replace(/[=:]/g,function(X){return U[X]})}var K=/\/+/g;function G(T,U){return typeof T=="object"&&T!==null&&T.key!=null?Z(""+T.key):U.toString(36)}function te(){}function I(T){switch(T.status){case"fulfilled":return T.value;case"rejected":throw T.reason;default:switch(typeof T.status=="string"?T.then(te,te):(T.status="pending",T.then(function(U){T.status==="pending"&&(T.status="fulfilled",T.value=U)},function(U){T.status==="pending"&&(T.status="rejected",T.reason=U)})),T.status){case"fulfilled":return T.value;case"rejected":throw T.reason}}throw T}function V(T,U,X,ee,ae){var me=typeof T;(me==="undefined"||me==="boolean")&&(T=null);var ue=!1;if(T===null)ue=!0;else switch(me){case"bigint":case"string":case"number":ue=!0;break;case"object":switch(T.$$typeof){case e:case r:ue=!0;break;case g:return ue=T._init,V(ue(T._payload),U,X,ee,ae)}}if(ue)return ae=ae(T),ue=ee===""?"."+G(T,0):ee,A(ae)?(X="",ue!=null&&(X=ue.replace(K,"$&/")+"/"),V(ae,U,X,"",function(pe){return pe})):ae!=null&&(q(ae)&&(ae=B(ae,X+(ae.key==null||T&&T.key===ae.key?"":(""+ae.key).replace(K,"$&/")+"/")+ue)),U.push(ae)),1;ue=0;var Q=ee===""?".":ee+":";if(A(T))for(var se=0;se>>1,T=E[P];if(0>>1;Pl(ee,z))ael(me,ee)?(E[P]=me,E[ae]=z,P=ae):(E[P]=ee,E[X]=z,P=X);else if(ael(me,z))E[P]=me,E[ae]=z,P=ae;else break e}}return O}function l(E,O){var z=E.sortIndex-O.sortIndex;return z!==0?z:E.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,y=null,v=3,b=!1,S=!1,w=!1,j=!1,k=typeof setTimeout=="function"?setTimeout:null,M=typeof clearTimeout=="function"?clearTimeout:null,_=typeof setImmediate<"u"?setImmediate:null;function A(E){for(var O=o(p);O!==null;){if(O.callback===null)a(p);else if(O.startTime<=E)a(p),O.sortIndex=O.expirationTime,r(h,O);else break;O=o(p)}}function D(E){if(w=!1,A(E),!S)if(o(h)!==null)S=!0,L||(L=!0,G());else{var O=o(p);O!==null&&V(D,O.startTime-E)}}var L=!1,H=-1,B=5,q=-1;function Z(){return j?!0:!(e.unstable_now()-qE&&Z());){var P=y.callback;if(typeof P=="function"){y.callback=null,v=y.priorityLevel;var T=P(y.expirationTime<=E);if(E=e.unstable_now(),typeof T=="function"){y.callback=T,A(E),O=!0;break t}y===o(h)&&a(h),A(E)}else a(h);y=o(h)}if(y!==null)O=!0;else{var U=o(p);U!==null&&V(D,U.startTime-E),O=!1}}break e}finally{y=null,v=z,b=!1}O=void 0}}finally{O?G():L=!1}}}var G;if(typeof _=="function")G=function(){_(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(E,O){H=k(function(){E(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(E){E.callback=null},e.unstable_forceFrameRate=function(E){0>E||125P?(E.sortIndex=z,r(p,E),o(h)===null&&E===o(p)&&(w?(M(H),H=-1):w=!0,V(D,z-P))):(E.sortIndex=T,r(h,E),S||b||(S=!0,L||(L=!0,G()))),E},e.unstable_shouldYield=Z,e.unstable_wrapCallback=function(E){var O=v;return function(){var z=v;v=O;try{return E.apply(this,arguments)}finally{v=z}}}})(km)),km}var _v;function V_(){return _v||(_v=1,Cm.exports=$_()),Cm.exports}var Am={exports:{}},zt={};/** + * @license React + * react-dom.production.js + * + * Copyright (c) Meta Platforms, Inc. and affiliates. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + */var jv;function q_(){if(jv)return zt;jv=1;var e=Wi();function r(h){var p="https://react.dev/errors/"+h;if(1"u"||typeof __REACT_DEVTOOLS_GLOBAL_HOOK__.checkDCE!="function"))try{__REACT_DEVTOOLS_GLOBAL_HOOK__.checkDCE(e)}catch(r){console.error(r)}}return e(),Am.exports=q_(),Am.exports}/** + * @license React + * react-dom-client.production.js + * + * Copyright (c) Meta Platforms, Inc. and affiliates. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + */var kv;function Y_(){if(kv)return Ni;kv=1;var e=V_(),r=Wi(),o=_b();function a(t){var n="https://react.dev/errors/"+t;if(1T||(t.current=P[T],P[T]=null,T--)}function ee(t,n){T++,P[T]=t.current,t.current=n}var ae=U(null),me=U(null),ue=U(null),Q=U(null);function se(t,n){switch(ee(ue,n),ee(me,t),ee(ae,null),n.nodeType){case 9:case 11:t=(t=n.documentElement)&&(t=t.namespaceURI)?F0(t):0;break;default:if(t=n.tagName,n=n.namespaceURI)n=F0(n),t=Z0(n,t);else switch(t){case"svg":t=1;break;case"math":t=2;break;default:t=0}}X(ae),ee(ae,t)}function pe(){X(ae),X(me),X(ue)}function le(t){t.memoizedState!==null&&ee(Q,t);var n=ae.current,s=Z0(n,t.type);n!==s&&(ee(me,t),ee(ae,s))}function de(t){me.current===t&&(X(ae),X(me)),Q.current===t&&(X(Q),gi._currentValue=z)}var he=Object.prototype.hasOwnProperty,xe=e.unstable_scheduleCallback,ge=e.unstable_cancelCallback,Be=e.unstable_shouldYield,at=e.unstable_requestPaint,re=e.unstable_now,ve=e.unstable_getCurrentPriorityLevel,ke=e.unstable_ImmediatePriority,De=e.unstable_UserBlockingPriority,be=e.unstable_NormalPriority,Te=e.unstable_LowPriority,Ye=e.unstable_IdlePriority,it=e.log,Tn=e.unstable_setDisableYieldValue,Fe=null,Ue=null;function Qe(t){if(typeof it=="function"&&Tn(t),Ue&&typeof Ue.setStrictMode=="function")try{Ue.setStrictMode(Fe,t)}catch{}}var ht=Math.clz32?Math.clz32:dd,Ft=Math.log,gs=Math.LN2;function dd(t){return t>>>=0,t===0?32:31-(Ft(t)/gs|0)|0}var na=256,ra=4194304;function Wn(t){var n=t&42;if(n!==0)return n;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 oa(t,n,s){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=Wn(c):(C&=R,C!==0?m=Wn(C):s||(s=R&~t,s!==0&&(m=Wn(s))))):(R=c&~x,R!==0?m=Wn(R):C!==0?m=Wn(C):s||(s=c&~t,s!==0&&(m=Wn(s)))),m===0?0:n!==0&&n!==m&&(n&x)===0&&(x=m&-m,s=n&-n,x>=s||x===32&&(s&4194048)!==0)?n:m}function xo(t,n){return(t.pendingLanes&~(t.suspendedLanes&~t.pingedLanes)&n)===0}function fd(t,n){switch(t){case 1:case 2:case 4:case 8:case 64:return n+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 n+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 dl(){var t=na;return na<<=1,(na&4194048)===0&&(na=256),t}function fl(){var t=ra;return ra<<=1,(ra&62914560)===0&&(ra=4194304),t}function xs(t){for(var n=[],s=0;31>s;s++)n.push(t);return n}function vo(t,n){t.pendingLanes|=n,n!==268435456&&(t.suspendedLanes=0,t.pingedLanes=0,t.warmLanes=0)}function md(t,n,s,c,m,x){var C=t.pendingLanes;t.pendingLanes=s,t.suspendedLanes=0,t.pingedLanes=0,t.warmLanes=0,t.expiredLanes&=s,t.entangledLanes&=s,t.errorRecoveryDisabledLanes&=s,t.shellSuspendCounter=0;var R=t.entanglements,$=t.expirationTimes,J=t.hiddenUpdates;for(s=C&~s;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{_s=!1,Error.prepareStackTrace=s}return(s=t?t.displayName||t.name:"")?tr(s):""}function yd(t){switch(t.tag){case 26:case 27:case 5:return tr(t.type);case 16:return tr("Lazy");case 13:return tr("Suspense");case 19:return tr("SuspenseList");case 0:case 15:return js(t.type,!1);case 11:return js(t.type.render,!1);case 1:return js(t.type,!0);case 31:return tr("Activity");default:return""}}function bl(t){try{var n="";do n+=yd(t),t=t.return;while(t);return n}catch(s){return` +Error generating stack: `+s.message+` +`+s.stack}}function Bt(t){switch(typeof t){case"bigint":case"boolean":case"number":case"string":case"undefined":return t;case"object":return t;default:return""}}function wl(t){var n=t.type;return(t=t.nodeName)&&t.toLowerCase()==="input"&&(n==="checkbox"||n==="radio")}function bd(t){var n=wl(t)?"checked":"value",s=Object.getOwnPropertyDescriptor(t.constructor.prototype,n),c=""+t[n];if(!t.hasOwnProperty(n)&&typeof s<"u"&&typeof s.get=="function"&&typeof s.set=="function"){var m=s.get,x=s.set;return Object.defineProperty(t,n,{configurable:!0,get:function(){return m.call(this)},set:function(C){c=""+C,x.call(this,C)}}),Object.defineProperty(t,n,{enumerable:s.enumerable}),{getValue:function(){return c},setValue:function(C){c=""+C},stopTracking:function(){t._valueTracker=null,delete t[n]}}}}function ia(t){t._valueTracker||(t._valueTracker=bd(t))}function Cs(t){if(!t)return!1;var n=t._valueTracker;if(!n)return!0;var s=n.getValue(),c="";return t&&(c=wl(t)?t.checked?"true":"false":t.value),t=c,t!==s?(n.setValue(t),!0):!1}function la(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 wd=/[\n"\\]/g;function Ut(t){return t.replace(wd,function(n){return"\\"+n.charCodeAt(0).toString(16)+" "})}function bo(t,n,s,c,m,x,C,R){t.name="",C!=null&&typeof C!="function"&&typeof C!="symbol"&&typeof C!="boolean"?t.type=C:t.removeAttribute("type"),n!=null?C==="number"?(n===0&&t.value===""||t.value!=n)&&(t.value=""+Bt(n)):t.value!==""+Bt(n)&&(t.value=""+Bt(n)):C!=="submit"&&C!=="reset"||t.removeAttribute("value"),n!=null?ks(t,C,Bt(n)):s!=null?ks(t,C,Bt(s)):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=""+Bt(R):t.removeAttribute("name")}function Nl(t,n,s,c,m,x,C,R){if(x!=null&&typeof x!="function"&&typeof x!="symbol"&&typeof x!="boolean"&&(t.type=x),n!=null||s!=null){if(!(x!=="submit"&&x!=="reset"||n!=null))return;s=s!=null?""+Bt(s):"",n=n!=null?""+Bt(n):s,R||n===t.value||(t.value=n),t.defaultValue=n}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 ks(t,n,s){n==="number"&&la(t.ownerDocument)===t||t.defaultValue===""+s||(t.defaultValue=""+s)}function nr(t,n,s,c){if(t=t.options,n){n={};for(var m=0;m"u"||typeof window.document>"u"||typeof window.document.createElement>"u"),jd=!1;if(rr)try{var Ms={};Object.defineProperty(Ms,"passive",{get:function(){jd=!0}}),window.addEventListener("test",Ms,Ms),window.removeEventListener("test",Ms,Ms)}catch{jd=!1}var Or=null,Cd=null,El=null;function Qp(){if(El)return El;var t,n=Cd,s=n.length,c,m="value"in Or?Or.value:Or.textContent,x=m.length;for(t=0;t=Ds),og=" ",ag=!1;function sg(t,n){switch(t){case"keyup":return lE.indexOf(n.keyCode)!==-1;case"keydown":return n.keyCode!==229;case"keypress":case"mousedown":case"focusout":return!0;default:return!1}}function ig(t){return t=t.detail,typeof t=="object"&&"data"in t?t.data:null}var fa=!1;function uE(t,n){switch(t){case"compositionend":return ig(n);case"keypress":return n.which!==32?null:(ag=!0,og);case"textInput":return t=n.data,t===og&&ag?null:t;default:return null}}function dE(t,n){if(fa)return t==="compositionend"||!Rd&&sg(t,n)?(t=Qp(),El=Cd=Or=null,fa=!1,t):null;switch(t){case"paste":return null;case"keypress":if(!(n.ctrlKey||n.altKey||n.metaKey)||n.ctrlKey&&n.altKey){if(n.char&&1=n)return{node:s,offset:n-t};t=c}e:{for(;s;){if(s.nextSibling){s=s.nextSibling;break e}s=s.parentNode}s=void 0}s=pg(s)}}function xg(t,n){return t&&n?t===n?!0:t&&t.nodeType===3?!1:n&&n.nodeType===3?xg(t,n.parentNode):"contains"in t?t.contains(n):t.compareDocumentPosition?!!(t.compareDocumentPosition(n)&16):!1:!1}function vg(t){t=t!=null&&t.ownerDocument!=null&&t.ownerDocument.defaultView!=null?t.ownerDocument.defaultView:window;for(var n=la(t.document);n instanceof t.HTMLIFrameElement;){try{var s=typeof n.contentWindow.location.href=="string"}catch{s=!1}if(s)t=n.contentWindow;else break;n=la(t.document)}return n}function zd(t){var n=t&&t.nodeName&&t.nodeName.toLowerCase();return n&&(n==="input"&&(t.type==="text"||t.type==="search"||t.type==="tel"||t.type==="url"||t.type==="password")||n==="textarea"||t.contentEditable==="true")}var yE=rr&&"documentMode"in document&&11>=document.documentMode,ma=null,Ld=null,Is=null,Id=!1;function yg(t,n,s){var c=s.window===s?s.document:s.nodeType===9?s:s.ownerDocument;Id||ma==null||ma!==la(c)||(c=ma,"selectionStart"in c&&zd(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}),Is&&Ls(Is,c)||(Is=c,c=hc(Ld,"onSelect"),0>=C,m-=C,ar=1<<32-ht(n)+m|s<x?x:8;var C=E.T,R={};E.T=R,Sf(t,!1,n,s);try{var $=m(),J=E.S;if(J!==null&&J(R,$),$!==null&&typeof $=="object"&&typeof $.then=="function"){var ie=kE($,c);Qs(t,n,ie,en(t))}else Qs(t,n,c,en(t))}catch(fe){Qs(t,n,{then:function(){},status:"rejected",reason:fe},en())}finally{O.p=x,E.T=C}}function DE(){}function wf(t,n,s,c){if(t.tag!==5)throw Error(a(476));var m=bx(t).queue;yx(t,m,n,z,s===null?DE:function(){return wx(t),s(c)})}function bx(t){var n=t.memoizedState;if(n!==null)return n;n={memoizedState:z,baseState:z,baseQueue:null,queue:{pending:null,lanes:0,dispatch:null,lastRenderedReducer:cr,lastRenderedState:z},next:null};var s={};return n.next={memoizedState:s,baseState:s,baseQueue:null,queue:{pending:null,lanes:0,dispatch:null,lastRenderedReducer:cr,lastRenderedState:s},next:null},t.memoizedState=n,t=t.alternate,t!==null&&(t.memoizedState=n),n}function wx(t){var n=bx(t).next.queue;Qs(t,n,{},en())}function Nf(){return Ot(gi)}function Nx(){return yt().memoizedState}function Sx(){return yt().memoizedState}function OE(t){for(var n=t.return;n!==null;){switch(n.tag){case 24:case 3:var s=en();t=Ir(s);var c=Hr(n,t,s);c!==null&&(tn(c,n,s),Gs(c,n,s)),n={cache:Wd()},t.payload=n;return}n=n.return}}function zE(t,n,s){var c=en();s={lane:c,revertLane:0,action:s,hasEagerState:!1,eagerState:null,next:null},Fl(t)?_x(n,s):(s=Pd(t,n,s,c),s!==null&&(tn(s,t,c),jx(s,n,c)))}function Ex(t,n,s){var c=en();Qs(t,n,s,c)}function Qs(t,n,s,c){var m={lane:c,revertLane:0,action:s,hasEagerState:!1,eagerState:null,next:null};if(Fl(t))_x(n,m);else{var x=t.alternate;if(t.lanes===0&&(x===null||x.lanes===0)&&(x=n.lastRenderedReducer,x!==null))try{var C=n.lastRenderedState,R=x(C,s);if(m.hasEagerState=!0,m.eagerState=R,Zt(R,C))return Tl(t,n,m,0),st===null&&Ml(),!1}catch{}finally{}if(s=Pd(t,n,m,c),s!==null)return tn(s,t,c),jx(s,n,c),!0}return!1}function Sf(t,n,s,c){if(c={lane:2,revertLane:tm(),action:c,hasEagerState:!1,eagerState:null,next:null},Fl(t)){if(n)throw Error(a(479))}else n=Pd(t,s,c,2),n!==null&&tn(n,t,2)}function Fl(t){var n=t.alternate;return t===Ie||n!==null&&n===Ie}function _x(t,n){Sa=$l=!0;var s=t.pending;s===null?n.next=n:(n.next=s.next,s.next=n),t.pending=n}function jx(t,n,s){if((s&4194048)!==0){var c=n.lanes;c&=t.pendingLanes,s|=c,n.lanes=s,vs(t,s)}}var Zl={readContext:Ot,use:ql,useCallback:pt,useContext:pt,useEffect:pt,useImperativeHandle:pt,useLayoutEffect:pt,useInsertionEffect:pt,useMemo:pt,useReducer:pt,useRef:pt,useState:pt,useDebugValue:pt,useDeferredValue:pt,useTransition:pt,useSyncExternalStore:pt,useId:pt,useHostTransitionStatus:pt,useFormState:pt,useActionState:pt,useOptimistic:pt,useMemoCache:pt,useCacheRefresh:pt},Cx={readContext:Ot,use:ql,useCallback:function(t,n){return $t().memoizedState=[t,n===void 0?null:n],t},useContext:Ot,useEffect:ux,useImperativeHandle:function(t,n,s){s=s!=null?s.concat([t]):null,Xl(4194308,4,hx.bind(null,n,t),s)},useLayoutEffect:function(t,n){return Xl(4194308,4,t,n)},useInsertionEffect:function(t,n){Xl(4,2,t,n)},useMemo:function(t,n){var s=$t();n=n===void 0?null:n;var c=t();if(Ro){Qe(!0);try{t()}finally{Qe(!1)}}return s.memoizedState=[c,n],c},useReducer:function(t,n,s){var c=$t();if(s!==void 0){var m=s(n);if(Ro){Qe(!0);try{s(n)}finally{Qe(!1)}}}else m=n;return c.memoizedState=c.baseState=m,t={pending:null,lanes:0,dispatch:null,lastRenderedReducer:t,lastRenderedState:m},c.queue=t,t=t.dispatch=zE.bind(null,Ie,t),[c.memoizedState,t]},useRef:function(t){var n=$t();return t={current:t},n.memoizedState=t},useState:function(t){t=xf(t);var n=t.queue,s=Ex.bind(null,Ie,n);return n.dispatch=s,[t.memoizedState,s]},useDebugValue:yf,useDeferredValue:function(t,n){var s=$t();return bf(s,t,n)},useTransition:function(){var t=xf(!1);return t=yx.bind(null,Ie,t.queue,!0,!1),$t().memoizedState=t,[!1,t]},useSyncExternalStore:function(t,n,s){var c=Ie,m=$t();if(We){if(s===void 0)throw Error(a(407));s=s()}else{if(s=n(),st===null)throw Error(a(349));(Ge&124)!==0||Fg(c,n,s)}m.memoizedState=s;var x={value:s,getSnapshot:n};return m.queue=x,ux(Kg.bind(null,c,x,t),[t]),c.flags|=2048,_a(9,Gl(),Zg.bind(null,c,x,s,n),null),s},useId:function(){var t=$t(),n=st.identifierPrefix;if(We){var s=sr,c=ar;s=(c&~(1<<32-ht(c)-1)).toString(32)+s,n="«"+n+"R"+s,s=Vl++,0Me?(Ct=je,je=null):Ct=je.sibling;var Ze=ne(F,je,W[Me],ce);if(Ze===null){je===null&&(je=Ct);break}t&&je&&Ze.alternate===null&&n(F,je),Y=x(Ze,Y,Me),Pe===null?we=Ze:Pe.sibling=Ze,Pe=Ze,je=Ct}if(Me===W.length)return s(F,je),We&&jo(F,Me),we;if(je===null){for(;MeMe?(Ct=je,je=null):Ct=je.sibling;var to=ne(F,je,Ze.value,ce);if(to===null){je===null&&(je=Ct);break}t&&je&&to.alternate===null&&n(F,je),Y=x(to,Y,Me),Pe===null?we=to:Pe.sibling=to,Pe=to,je=Ct}if(Ze.done)return s(F,je),We&&jo(F,Me),we;if(je===null){for(;!Ze.done;Me++,Ze=W.next())Ze=fe(F,Ze.value,ce),Ze!==null&&(Y=x(Ze,Y,Me),Pe===null?we=Ze:Pe.sibling=Ze,Pe=Ze);return We&&jo(F,Me),we}for(je=c(je);!Ze.done;Me++,Ze=W.next())Ze=oe(je,F,Me,Ze.value,ce),Ze!==null&&(t&&Ze.alternate!==null&&je.delete(Ze.key===null?Me:Ze.key),Y=x(Ze,Y,Me),Pe===null?we=Ze:Pe.sibling=Ze,Pe=Ze);return t&&je.forEach(function(I_){return n(F,I_)}),We&&jo(F,Me),we}function rt(F,Y,W,ce){if(typeof W=="object"&&W!==null&&W.type===S&&W.key===null&&(W=W.props.children),typeof W=="object"&&W!==null){switch(W.$$typeof){case v:e:{for(var we=W.key;Y!==null;){if(Y.key===we){if(we=W.type,we===S){if(Y.tag===7){s(F,Y.sibling),ce=m(Y,W.props.children),ce.return=F,F=ce;break e}}else if(Y.elementType===we||typeof we=="object"&&we!==null&&we.$$typeof===B&&Ax(we)===Y.type){s(F,Y.sibling),ce=m(Y,W.props),ei(ce,W),ce.return=F,F=ce;break e}s(F,Y);break}else n(F,Y);Y=Y.sibling}W.type===S?(ce=Eo(W.props.children,F.mode,ce,W.key),ce.return=F,F=ce):(ce=Dl(W.type,W.key,W.props,null,F.mode,ce),ei(ce,W),ce.return=F,F=ce)}return C(F);case b:e:{for(we=W.key;Y!==null;){if(Y.key===we)if(Y.tag===4&&Y.stateNode.containerInfo===W.containerInfo&&Y.stateNode.implementation===W.implementation){s(F,Y.sibling),ce=m(Y,W.children||[]),ce.return=F,F=ce;break e}else{s(F,Y);break}else n(F,Y);Y=Y.sibling}ce=qd(W,F.mode,ce),ce.return=F,F=ce}return C(F);case B:return we=W._init,W=we(W._payload),rt(F,Y,W,ce)}if(V(W))return Re(F,Y,W,ce);if(G(W)){if(we=G(W),typeof we!="function")throw Error(a(150));return W=we.call(W),Ae(F,Y,W,ce)}if(typeof W.then=="function")return rt(F,Y,Kl(W),ce);if(W.$$typeof===_)return rt(F,Y,Il(F,W),ce);Wl(F,W)}return typeof W=="string"&&W!==""||typeof W=="number"||typeof W=="bigint"?(W=""+W,Y!==null&&Y.tag===6?(s(F,Y.sibling),ce=m(Y,W),ce.return=F,F=ce):(s(F,Y),ce=Vd(W,F.mode,ce),ce.return=F,F=ce),C(F)):s(F,Y)}return function(F,Y,W,ce){try{Js=0;var we=rt(F,Y,W,ce);return ja=null,we}catch(je){if(je===qs||je===Bl)throw je;var Pe=Kt(29,je,null,F.mode);return Pe.lanes=ce,Pe.return=F,Pe}finally{}}}var Ca=Mx(!0),Tx=Mx(!1),mn=U(null),On=null;function Ur(t){var n=t.alternate;ee(Nt,Nt.current&1),ee(mn,t),On===null&&(n===null||Na.current!==null||n.memoizedState!==null)&&(On=t)}function Rx(t){if(t.tag===22){if(ee(Nt,Nt.current),ee(mn,t),On===null){var n=t.alternate;n!==null&&n.memoizedState!==null&&(On=t)}}else Pr()}function Pr(){ee(Nt,Nt.current),ee(mn,mn.current)}function ur(t){X(mn),On===t&&(On=null),X(Nt)}var Nt=U(0);function Ql(t){for(var n=t;n!==null;){if(n.tag===13){var s=n.memoizedState;if(s!==null&&(s=s.dehydrated,s===null||s.data==="$?"||mm(s)))return n}else if(n.tag===19&&n.memoizedProps.revealOrder!==void 0){if((n.flags&128)!==0)return n}else if(n.child!==null){n.child.return=n,n=n.child;continue}if(n===t)break;for(;n.sibling===null;){if(n.return===null||n.return===t)return null;n=n.return}n.sibling.return=n.return,n=n.sibling}return null}function Ef(t,n,s,c){n=t.memoizedState,s=s(c,n),s=s==null?n:g({},n,s),t.memoizedState=s,t.lanes===0&&(t.updateQueue.baseState=s)}var _f={enqueueSetState:function(t,n,s){t=t._reactInternals;var c=en(),m=Ir(c);m.payload=n,s!=null&&(m.callback=s),n=Hr(t,m,c),n!==null&&(tn(n,t,c),Gs(n,t,c))},enqueueReplaceState:function(t,n,s){t=t._reactInternals;var c=en(),m=Ir(c);m.tag=1,m.payload=n,s!=null&&(m.callback=s),n=Hr(t,m,c),n!==null&&(tn(n,t,c),Gs(n,t,c))},enqueueForceUpdate:function(t,n){t=t._reactInternals;var s=en(),c=Ir(s);c.tag=2,n!=null&&(c.callback=n),n=Hr(t,c,s),n!==null&&(tn(n,t,s),Gs(n,t,s))}};function Dx(t,n,s,c,m,x,C){return t=t.stateNode,typeof t.shouldComponentUpdate=="function"?t.shouldComponentUpdate(c,x,C):n.prototype&&n.prototype.isPureReactComponent?!Ls(s,c)||!Ls(m,x):!0}function Ox(t,n,s,c){t=n.state,typeof n.componentWillReceiveProps=="function"&&n.componentWillReceiveProps(s,c),typeof n.UNSAFE_componentWillReceiveProps=="function"&&n.UNSAFE_componentWillReceiveProps(s,c),n.state!==t&&_f.enqueueReplaceState(n,n.state,null)}function Do(t,n){var s=n;if("ref"in n){s={};for(var c in n)c!=="ref"&&(s[c]=n[c])}if(t=t.defaultProps){s===n&&(s=g({},s));for(var m in t)s[m]===void 0&&(s[m]=t[m])}return s}var Jl=typeof reportError=="function"?reportError:function(t){if(typeof window=="object"&&typeof window.ErrorEvent=="function"){var n=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(n))return}else if(typeof process=="object"&&typeof process.emit=="function"){process.emit("uncaughtException",t);return}console.error(t)};function zx(t){Jl(t)}function Lx(t){console.error(t)}function Ix(t){Jl(t)}function ec(t,n){try{var s=t.onUncaughtError;s(n.value,{componentStack:n.stack})}catch(c){setTimeout(function(){throw c})}}function Hx(t,n,s){try{var c=t.onCaughtError;c(s.value,{componentStack:s.stack,errorBoundary:n.tag===1?n.stateNode:null})}catch(m){setTimeout(function(){throw m})}}function jf(t,n,s){return s=Ir(s),s.tag=3,s.payload={element:null},s.callback=function(){ec(t,n)},s}function Bx(t){return t=Ir(t),t.tag=3,t}function Ux(t,n,s,c){var m=s.type.getDerivedStateFromError;if(typeof m=="function"){var x=c.value;t.payload=function(){return m(x)},t.callback=function(){Hx(n,s,c)}}var C=s.stateNode;C!==null&&typeof C.componentDidCatch=="function"&&(t.callback=function(){Hx(n,s,c),typeof m!="function"&&(Xr===null?Xr=new Set([this]):Xr.add(this));var R=c.stack;this.componentDidCatch(c.value,{componentStack:R!==null?R:""})})}function IE(t,n,s,c,m){if(s.flags|=32768,c!==null&&typeof c=="object"&&typeof c.then=="function"){if(n=s.alternate,n!==null&&Ps(n,s,m,!0),s=mn.current,s!==null){switch(s.tag){case 13:return On===null?Kf():s.alternate===null&&mt===0&&(mt=3),s.flags&=-257,s.flags|=65536,s.lanes=m,c===ef?s.flags|=16384:(n=s.updateQueue,n===null?s.updateQueue=new Set([c]):n.add(c),Qf(t,c,m)),!1;case 22:return s.flags|=65536,c===ef?s.flags|=16384:(n=s.updateQueue,n===null?(n={transitions:null,markerInstances:null,retryQueue:new Set([c])},s.updateQueue=n):(s=n.retryQueue,s===null?n.retryQueue=new Set([c]):s.add(c)),Qf(t,c,m)),!1}throw Error(a(435,s.tag))}return Qf(t,c,m),Kf(),!1}if(We)return n=mn.current,n!==null?((n.flags&65536)===0&&(n.flags|=256),n.flags|=65536,n.lanes=m,c!==Xd&&(t=Error(a(422),{cause:c}),Us(cn(t,s)))):(c!==Xd&&(n=Error(a(423),{cause:c}),Us(cn(n,s))),t=t.current.alternate,t.flags|=65536,m&=-m,t.lanes|=m,c=cn(c,s),m=jf(t.stateNode,c,m),rf(t,m),mt!==4&&(mt=2)),!1;var x=Error(a(520),{cause:c});if(x=cn(x,s),ii===null?ii=[x]:ii.push(x),mt!==4&&(mt=2),n===null)return!0;c=cn(c,s),s=n;do{switch(s.tag){case 3:return s.flags|=65536,t=m&-m,s.lanes|=t,t=jf(s.stateNode,c,t),rf(s,t),!1;case 1:if(n=s.type,x=s.stateNode,(s.flags&128)===0&&(typeof n.getDerivedStateFromError=="function"||x!==null&&typeof x.componentDidCatch=="function"&&(Xr===null||!Xr.has(x))))return s.flags|=65536,m&=-m,s.lanes|=m,m=Bx(m),Ux(m,t,s,c),rf(s,m),!1}s=s.return}while(s!==null);return!1}var Px=Error(a(461)),_t=!1;function kt(t,n,s,c){n.child=t===null?Tx(n,null,s,c):Ca(n,t.child,s,c)}function $x(t,n,s,c,m){s=s.render;var x=n.ref;if("ref"in c){var C={};for(var R in c)R!=="ref"&&(C[R]=c[R])}else C=c;return Mo(n),c=cf(t,n,s,C,x,m),R=uf(),t!==null&&!_t?(df(t,n,m),dr(t,n,m)):(We&&R&&Yd(n),n.flags|=1,kt(t,n,c,m),n.child)}function Vx(t,n,s,c,m){if(t===null){var x=s.type;return typeof x=="function"&&!$d(x)&&x.defaultProps===void 0&&s.compare===null?(n.tag=15,n.type=x,qx(t,n,x,c,m)):(t=Dl(s.type,null,c,n,n.mode,m),t.ref=n.ref,t.return=n,n.child=t)}if(x=t.child,!Of(t,m)){var C=x.memoizedProps;if(s=s.compare,s=s!==null?s:Ls,s(C,c)&&t.ref===n.ref)return dr(t,n,m)}return n.flags|=1,t=or(x,c),t.ref=n.ref,t.return=n,n.child=t}function qx(t,n,s,c,m){if(t!==null){var x=t.memoizedProps;if(Ls(x,c)&&t.ref===n.ref)if(_t=!1,n.pendingProps=c=x,Of(t,m))(t.flags&131072)!==0&&(_t=!0);else return n.lanes=t.lanes,dr(t,n,m)}return Cf(t,n,s,c,m)}function Yx(t,n,s){var c=n.pendingProps,m=c.children,x=t!==null?t.memoizedState:null;if(c.mode==="hidden"){if((n.flags&128)!==0){if(c=x!==null?x.baseLanes|s:s,t!==null){for(m=n.child=t.child,x=0;m!==null;)x=x|m.lanes|m.childLanes,m=m.sibling;n.childLanes=x&~c}else n.childLanes=0,n.child=null;return Gx(t,n,c,s)}if((s&536870912)!==0)n.memoizedState={baseLanes:0,cachePool:null},t!==null&&Hl(n,x!==null?x.cachePool:null),x!==null?qg(n,x):af(),Rx(n);else return n.lanes=n.childLanes=536870912,Gx(t,n,x!==null?x.baseLanes|s:s,s)}else x!==null?(Hl(n,x.cachePool),qg(n,x),Pr(),n.memoizedState=null):(t!==null&&Hl(n,null),af(),Pr());return kt(t,n,m,s),n.child}function Gx(t,n,s,c){var m=Jd();return m=m===null?null:{parent:wt._currentValue,pool:m},n.memoizedState={baseLanes:s,cachePool:m},t!==null&&Hl(n,null),af(),Rx(n),t!==null&&Ps(t,n,c,!0),null}function tc(t,n){var s=n.ref;if(s===null)t!==null&&t.ref!==null&&(n.flags|=4194816);else{if(typeof s!="function"&&typeof s!="object")throw Error(a(284));(t===null||t.ref!==s)&&(n.flags|=4194816)}}function Cf(t,n,s,c,m){return Mo(n),s=cf(t,n,s,c,void 0,m),c=uf(),t!==null&&!_t?(df(t,n,m),dr(t,n,m)):(We&&c&&Yd(n),n.flags|=1,kt(t,n,s,m),n.child)}function Xx(t,n,s,c,m,x){return Mo(n),n.updateQueue=null,s=Gg(n,c,s,m),Yg(t),c=uf(),t!==null&&!_t?(df(t,n,x),dr(t,n,x)):(We&&c&&Yd(n),n.flags|=1,kt(t,n,s,x),n.child)}function Fx(t,n,s,c,m){if(Mo(n),n.stateNode===null){var x=xa,C=s.contextType;typeof C=="object"&&C!==null&&(x=Ot(C)),x=new s(c,x),n.memoizedState=x.state!==null&&x.state!==void 0?x.state:null,x.updater=_f,n.stateNode=x,x._reactInternals=n,x=n.stateNode,x.props=c,x.state=n.memoizedState,x.refs={},tf(n),C=s.contextType,x.context=typeof C=="object"&&C!==null?Ot(C):xa,x.state=n.memoizedState,C=s.getDerivedStateFromProps,typeof C=="function"&&(Ef(n,s,C,c),x.state=n.memoizedState),typeof s.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&&_f.enqueueReplaceState(x,x.state,null),Fs(n,c,x,m),Xs(),x.state=n.memoizedState),typeof x.componentDidMount=="function"&&(n.flags|=4194308),c=!0}else if(t===null){x=n.stateNode;var R=n.memoizedProps,$=Do(s,R);x.props=$;var J=x.context,ie=s.contextType;C=xa,typeof ie=="object"&&ie!==null&&(C=Ot(ie));var fe=s.getDerivedStateFromProps;ie=typeof fe=="function"||typeof x.getSnapshotBeforeUpdate=="function",R=n.pendingProps!==R,ie||typeof x.UNSAFE_componentWillReceiveProps!="function"&&typeof x.componentWillReceiveProps!="function"||(R||J!==C)&&Ox(n,x,c,C),Lr=!1;var ne=n.memoizedState;x.state=ne,Fs(n,c,x,m),Xs(),J=n.memoizedState,R||ne!==J||Lr?(typeof fe=="function"&&(Ef(n,s,fe,c),J=n.memoizedState),($=Lr||Dx(n,s,$,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"&&(n.flags|=4194308)):(typeof x.componentDidMount=="function"&&(n.flags|=4194308),n.memoizedProps=c,n.memoizedState=J),x.props=c,x.state=J,x.context=C,c=$):(typeof x.componentDidMount=="function"&&(n.flags|=4194308),c=!1)}else{x=n.stateNode,nf(t,n),C=n.memoizedProps,ie=Do(s,C),x.props=ie,fe=n.pendingProps,ne=x.context,J=s.contextType,$=xa,typeof J=="object"&&J!==null&&($=Ot(J)),R=s.getDerivedStateFromProps,(J=typeof R=="function"||typeof x.getSnapshotBeforeUpdate=="function")||typeof x.UNSAFE_componentWillReceiveProps!="function"&&typeof x.componentWillReceiveProps!="function"||(C!==fe||ne!==$)&&Ox(n,x,c,$),Lr=!1,ne=n.memoizedState,x.state=ne,Fs(n,c,x,m),Xs();var oe=n.memoizedState;C!==fe||ne!==oe||Lr||t!==null&&t.dependencies!==null&&Ll(t.dependencies)?(typeof R=="function"&&(Ef(n,s,R,c),oe=n.memoizedState),(ie=Lr||Dx(n,s,ie,c,ne,oe,$)||t!==null&&t.dependencies!==null&&Ll(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"&&(n.flags|=4),typeof x.getSnapshotBeforeUpdate=="function"&&(n.flags|=1024)):(typeof x.componentDidUpdate!="function"||C===t.memoizedProps&&ne===t.memoizedState||(n.flags|=4),typeof x.getSnapshotBeforeUpdate!="function"||C===t.memoizedProps&&ne===t.memoizedState||(n.flags|=1024),n.memoizedProps=c,n.memoizedState=oe),x.props=c,x.state=oe,x.context=$,c=ie):(typeof x.componentDidUpdate!="function"||C===t.memoizedProps&&ne===t.memoizedState||(n.flags|=4),typeof x.getSnapshotBeforeUpdate!="function"||C===t.memoizedProps&&ne===t.memoizedState||(n.flags|=1024),c=!1)}return x=c,tc(t,n),c=(n.flags&128)!==0,x||c?(x=n.stateNode,s=c&&typeof s.getDerivedStateFromError!="function"?null:x.render(),n.flags|=1,t!==null&&c?(n.child=Ca(n,t.child,null,m),n.child=Ca(n,null,s,m)):kt(t,n,s,m),n.memoizedState=x.state,t=n.child):t=dr(t,n,m),t}function Zx(t,n,s,c){return Bs(),n.flags|=256,kt(t,n,s,c),n.child}var kf={dehydrated:null,treeContext:null,retryLane:0,hydrationErrors:null};function Af(t){return{baseLanes:t,cachePool:Lg()}}function Mf(t,n,s){return t=t!==null?t.childLanes&~s:0,n&&(t|=hn),t}function Kx(t,n,s){var c=n.pendingProps,m=!1,x=(n.flags&128)!==0,C;if((C=x)||(C=t!==null&&t.memoizedState===null?!1:(Nt.current&2)!==0),C&&(m=!0,n.flags&=-129),C=(n.flags&32)!==0,n.flags&=-33,t===null){if(We){if(m?Ur(n):Pr(),We){var R=ft,$;if($=R){e:{for($=R,R=Dn;$.nodeType!==8;){if(!R){R=null;break e}if($=wn($.nextSibling),$===null){R=null;break e}}R=$}R!==null?(n.memoizedState={dehydrated:R,treeContext:_o!==null?{id:ar,overflow:sr}:null,retryLane:536870912,hydrationErrors:null},$=Kt(18,null,null,0),$.stateNode=R,$.return=n,n.child=$,It=n,ft=null,$=!0):$=!1}$||ko(n)}if(R=n.memoizedState,R!==null&&(R=R.dehydrated,R!==null))return mm(R)?n.lanes=32:n.lanes=536870912,null;ur(n)}return R=c.children,c=c.fallback,m?(Pr(),m=n.mode,R=nc({mode:"hidden",children:R},m),c=Eo(c,m,s,null),R.return=n,c.return=n,R.sibling=c,n.child=R,m=n.child,m.memoizedState=Af(s),m.childLanes=Mf(t,C,s),n.memoizedState=kf,c):(Ur(n),Tf(n,R))}if($=t.memoizedState,$!==null&&(R=$.dehydrated,R!==null)){if(x)n.flags&256?(Ur(n),n.flags&=-257,n=Rf(t,n,s)):n.memoizedState!==null?(Pr(),n.child=t.child,n.flags|=128,n=null):(Pr(),m=c.fallback,R=n.mode,c=nc({mode:"visible",children:c.children},R),m=Eo(m,R,s,null),m.flags|=2,c.return=n,m.return=n,c.sibling=m,n.child=c,Ca(n,t.child,null,s),c=n.child,c.memoizedState=Af(s),c.childLanes=Mf(t,C,s),n.memoizedState=kf,n=m);else if(Ur(n),mm(R)){if(C=R.nextSibling&&R.nextSibling.dataset,C)var J=C.dgst;C=J,c=Error(a(419)),c.stack="",c.digest=C,Us({value:c,source:null,stack:null}),n=Rf(t,n,s)}else if(_t||Ps(t,n,s,!1),C=(s&t.childLanes)!==0,_t||C){if(C=st,C!==null&&(c=s&-s,c=(c&42)!==0?1:ys(c),c=(c&(C.suspendedLanes|s))!==0?0:c,c!==0&&c!==$.retryLane))throw $.retryLane=c,ga(t,c),tn(C,t,c),Px;R.data==="$?"||Kf(),n=Rf(t,n,s)}else R.data==="$?"?(n.flags|=192,n.child=t.child,n=null):(t=$.treeContext,ft=wn(R.nextSibling),It=n,We=!0,Co=null,Dn=!1,t!==null&&(dn[fn++]=ar,dn[fn++]=sr,dn[fn++]=_o,ar=t.id,sr=t.overflow,_o=n),n=Tf(n,c.children),n.flags|=4096);return n}return m?(Pr(),m=c.fallback,R=n.mode,$=t.child,J=$.sibling,c=or($,{mode:"hidden",children:c.children}),c.subtreeFlags=$.subtreeFlags&65011712,J!==null?m=or(J,m):(m=Eo(m,R,s,null),m.flags|=2),m.return=n,c.return=n,c.sibling=m,n.child=c,c=m,m=n.child,R=t.child.memoizedState,R===null?R=Af(s):($=R.cachePool,$!==null?(J=wt._currentValue,$=$.parent!==J?{parent:J,pool:J}:$):$=Lg(),R={baseLanes:R.baseLanes|s,cachePool:$}),m.memoizedState=R,m.childLanes=Mf(t,C,s),n.memoizedState=kf,c):(Ur(n),s=t.child,t=s.sibling,s=or(s,{mode:"visible",children:c.children}),s.return=n,s.sibling=null,t!==null&&(C=n.deletions,C===null?(n.deletions=[t],n.flags|=16):C.push(t)),n.child=s,n.memoizedState=null,s)}function Tf(t,n){return n=nc({mode:"visible",children:n},t.mode),n.return=t,t.child=n}function nc(t,n){return t=Kt(22,t,null,n),t.lanes=0,t.stateNode={_visibility:1,_pendingMarkers:null,_retryCache:null,_transitions:null},t}function Rf(t,n,s){return Ca(n,t.child,null,s),t=Tf(n,n.pendingProps.children),t.flags|=2,n.memoizedState=null,t}function Wx(t,n,s){t.lanes|=n;var c=t.alternate;c!==null&&(c.lanes|=n),Zd(t.return,n,s)}function Df(t,n,s,c,m){var x=t.memoizedState;x===null?t.memoizedState={isBackwards:n,rendering:null,renderingStartTime:0,last:c,tail:s,tailMode:m}:(x.isBackwards=n,x.rendering=null,x.renderingStartTime=0,x.last=c,x.tail=s,x.tailMode=m)}function Qx(t,n,s){var c=n.pendingProps,m=c.revealOrder,x=c.tail;if(kt(t,n,c.children,s),c=Nt.current,(c&2)!==0)c=c&1|2,n.flags|=128;else{if(t!==null&&(t.flags&128)!==0)e:for(t=n.child;t!==null;){if(t.tag===13)t.memoizedState!==null&&Wx(t,s,n);else if(t.tag===19)Wx(t,s,n);else if(t.child!==null){t.child.return=t,t=t.child;continue}if(t===n)break e;for(;t.sibling===null;){if(t.return===null||t.return===n)break e;t=t.return}t.sibling.return=t.return,t=t.sibling}c&=1}switch(ee(Nt,c),m){case"forwards":for(s=n.child,m=null;s!==null;)t=s.alternate,t!==null&&Ql(t)===null&&(m=s),s=s.sibling;s=m,s===null?(m=n.child,n.child=null):(m=s.sibling,s.sibling=null),Df(n,!1,m,s,x);break;case"backwards":for(s=null,m=n.child,n.child=null;m!==null;){if(t=m.alternate,t!==null&&Ql(t)===null){n.child=m;break}t=m.sibling,m.sibling=s,s=m,m=t}Df(n,!0,s,null,x);break;case"together":Df(n,!1,null,null,void 0);break;default:n.memoizedState=null}return n.child}function dr(t,n,s){if(t!==null&&(n.dependencies=t.dependencies),Gr|=n.lanes,(s&n.childLanes)===0)if(t!==null){if(Ps(t,n,s,!1),(s&n.childLanes)===0)return null}else return null;if(t!==null&&n.child!==t.child)throw Error(a(153));if(n.child!==null){for(t=n.child,s=or(t,t.pendingProps),n.child=s,s.return=n;t.sibling!==null;)t=t.sibling,s=s.sibling=or(t,t.pendingProps),s.return=n;s.sibling=null}return n.child}function Of(t,n){return(t.lanes&n)!==0?!0:(t=t.dependencies,!!(t!==null&&Ll(t)))}function HE(t,n,s){switch(n.tag){case 3:se(n,n.stateNode.containerInfo),zr(n,wt,t.memoizedState.cache),Bs();break;case 27:case 5:le(n);break;case 4:se(n,n.stateNode.containerInfo);break;case 10:zr(n,n.type,n.memoizedProps.value);break;case 13:var c=n.memoizedState;if(c!==null)return c.dehydrated!==null?(Ur(n),n.flags|=128,null):(s&n.child.childLanes)!==0?Kx(t,n,s):(Ur(n),t=dr(t,n,s),t!==null?t.sibling:null);Ur(n);break;case 19:var m=(t.flags&128)!==0;if(c=(s&n.childLanes)!==0,c||(Ps(t,n,s,!1),c=(s&n.childLanes)!==0),m){if(c)return Qx(t,n,s);n.flags|=128}if(m=n.memoizedState,m!==null&&(m.rendering=null,m.tail=null,m.lastEffect=null),ee(Nt,Nt.current),c)break;return null;case 22:case 23:return n.lanes=0,Yx(t,n,s);case 24:zr(n,wt,t.memoizedState.cache)}return dr(t,n,s)}function Jx(t,n,s){if(t!==null)if(t.memoizedProps!==n.pendingProps)_t=!0;else{if(!Of(t,s)&&(n.flags&128)===0)return _t=!1,HE(t,n,s);_t=(t.flags&131072)!==0}else _t=!1,We&&(n.flags&1048576)!==0&&Ag(n,zl,n.index);switch(n.lanes=0,n.tag){case 16:e:{t=n.pendingProps;var c=n.elementType,m=c._init;if(c=m(c._payload),n.type=c,typeof c=="function")$d(c)?(t=Do(c,t),n.tag=1,n=Fx(null,n,c,t,s)):(n.tag=0,n=Cf(null,n,c,t,s));else{if(c!=null){if(m=c.$$typeof,m===A){n.tag=11,n=$x(null,n,c,t,s);break e}else if(m===H){n.tag=14,n=Vx(null,n,c,t,s);break e}}throw n=I(c)||c,Error(a(306,n,""))}}return n;case 0:return Cf(t,n,n.type,n.pendingProps,s);case 1:return c=n.type,m=Do(c,n.pendingProps),Fx(t,n,c,m,s);case 3:e:{if(se(n,n.stateNode.containerInfo),t===null)throw Error(a(387));c=n.pendingProps;var x=n.memoizedState;m=x.element,nf(t,n),Fs(n,c,null,s);var C=n.memoizedState;if(c=C.cache,zr(n,wt,c),c!==x.cache&&Kd(n,[wt],s,!0),Xs(),c=C.element,x.isDehydrated)if(x={element:c,isDehydrated:!1,cache:C.cache},n.updateQueue.baseState=x,n.memoizedState=x,n.flags&256){n=Zx(t,n,c,s);break e}else if(c!==m){m=cn(Error(a(424)),n),Us(m),n=Zx(t,n,c,s);break e}else{switch(t=n.stateNode.containerInfo,t.nodeType){case 9:t=t.body;break;default:t=t.nodeName==="HTML"?t.ownerDocument.body:t}for(ft=wn(t.firstChild),It=n,We=!0,Co=null,Dn=!0,s=Tx(n,null,c,s),n.child=s;s;)s.flags=s.flags&-3|4096,s=s.sibling}else{if(Bs(),c===m){n=dr(t,n,s);break e}kt(t,n,c,s)}n=n.child}return n;case 26:return tc(t,n),t===null?(s=rv(n.type,null,n.pendingProps,null))?n.memoizedState=s:We||(s=n.type,t=n.pendingProps,c=gc(ue.current).createElement(s),c[Et]=n,c[Dt]=t,Mt(c,s,t),xt(c),n.stateNode=c):n.memoizedState=rv(n.type,t.memoizedProps,n.pendingProps,t.memoizedState),null;case 27:return le(n),t===null&&We&&(c=n.stateNode=ev(n.type,n.pendingProps,ue.current),It=n,Dn=!0,m=ft,Kr(n.type)?(hm=m,ft=wn(c.firstChild)):ft=m),kt(t,n,n.pendingProps.children,s),tc(t,n),t===null&&(n.flags|=4194304),n.child;case 5:return t===null&&We&&((m=c=ft)&&(c=f_(c,n.type,n.pendingProps,Dn),c!==null?(n.stateNode=c,It=n,ft=wn(c.firstChild),Dn=!1,m=!0):m=!1),m||ko(n)),le(n),m=n.type,x=n.pendingProps,C=t!==null?t.memoizedProps:null,c=x.children,um(m,x)?c=null:C!==null&&um(m,C)&&(n.flags|=32),n.memoizedState!==null&&(m=cf(t,n,ME,null,null,s),gi._currentValue=m),tc(t,n),kt(t,n,c,s),n.child;case 6:return t===null&&We&&((t=s=ft)&&(s=m_(s,n.pendingProps,Dn),s!==null?(n.stateNode=s,It=n,ft=null,t=!0):t=!1),t||ko(n)),null;case 13:return Kx(t,n,s);case 4:return se(n,n.stateNode.containerInfo),c=n.pendingProps,t===null?n.child=Ca(n,null,c,s):kt(t,n,c,s),n.child;case 11:return $x(t,n,n.type,n.pendingProps,s);case 7:return kt(t,n,n.pendingProps,s),n.child;case 8:return kt(t,n,n.pendingProps.children,s),n.child;case 12:return kt(t,n,n.pendingProps.children,s),n.child;case 10:return c=n.pendingProps,zr(n,n.type,c.value),kt(t,n,c.children,s),n.child;case 9:return m=n.type._context,c=n.pendingProps.children,Mo(n),m=Ot(m),c=c(m),n.flags|=1,kt(t,n,c,s),n.child;case 14:return Vx(t,n,n.type,n.pendingProps,s);case 15:return qx(t,n,n.type,n.pendingProps,s);case 19:return Qx(t,n,s);case 31:return c=n.pendingProps,s=n.mode,c={mode:c.mode,children:c.children},t===null?(s=nc(c,s),s.ref=n.ref,n.child=s,s.return=n,n=s):(s=or(t.child,c),s.ref=n.ref,n.child=s,s.return=n,n=s),n;case 22:return Yx(t,n,s);case 24:return Mo(n),c=Ot(wt),t===null?(m=Jd(),m===null&&(m=st,x=Wd(),m.pooledCache=x,x.refCount++,x!==null&&(m.pooledCacheLanes|=s),m=x),n.memoizedState={parent:c,cache:m},tf(n),zr(n,wt,m)):((t.lanes&s)!==0&&(nf(t,n),Fs(n,null,null,s),Xs()),m=t.memoizedState,x=n.memoizedState,m.parent!==c?(m={parent:c,cache:c},n.memoizedState=m,n.lanes===0&&(n.memoizedState=n.updateQueue.baseState=m),zr(n,wt,c)):(c=x.cache,zr(n,wt,c),c!==m.cache&&Kd(n,[wt],s,!0))),kt(t,n,n.pendingProps.children,s),n.child;case 29:throw n.pendingProps}throw Error(a(156,n.tag))}function fr(t){t.flags|=4}function e0(t,n){if(n.type!=="stylesheet"||(n.state.loading&4)!==0)t.flags&=-16777217;else if(t.flags|=16777216,!lv(n)){if(n=mn.current,n!==null&&((Ge&4194048)===Ge?On!==null:(Ge&62914560)!==Ge&&(Ge&536870912)===0||n!==On))throw Ys=ef,Ig;t.flags|=8192}}function rc(t,n){n!==null&&(t.flags|=4),t.flags&16384&&(n=t.tag!==22?fl():536870912,t.lanes|=n,Ta|=n)}function ti(t,n){if(!We)switch(t.tailMode){case"hidden":n=t.tail;for(var s=null;n!==null;)n.alternate!==null&&(s=n),n=n.sibling;s===null?t.tail=null:s.sibling=null;break;case"collapsed":s=t.tail;for(var c=null;s!==null;)s.alternate!==null&&(c=s),s=s.sibling;c===null?n||t.tail===null?t.tail=null:t.tail.sibling=null:c.sibling=null}}function ut(t){var n=t.alternate!==null&&t.alternate.child===t.child,s=0,c=0;if(n)for(var m=t.child;m!==null;)s|=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;)s|=m.lanes|m.childLanes,c|=m.subtreeFlags,c|=m.flags,m.return=t,m=m.sibling;return t.subtreeFlags|=c,t.childLanes=s,n}function BE(t,n,s){var c=n.pendingProps;switch(Gd(n),n.tag){case 31:case 16:case 15:case 0:case 11:case 7:case 8:case 12:case 9:case 14:return ut(n),null;case 1:return ut(n),null;case 3:return s=n.stateNode,c=null,t!==null&&(c=t.memoizedState.cache),n.memoizedState.cache!==c&&(n.flags|=2048),lr(wt),pe(),s.pendingContext&&(s.context=s.pendingContext,s.pendingContext=null),(t===null||t.child===null)&&(Hs(n)?fr(n):t===null||t.memoizedState.isDehydrated&&(n.flags&256)===0||(n.flags|=1024,Rg())),ut(n),null;case 26:return s=n.memoizedState,t===null?(fr(n),s!==null?(ut(n),e0(n,s)):(ut(n),n.flags&=-16777217)):s?s!==t.memoizedState?(fr(n),ut(n),e0(n,s)):(ut(n),n.flags&=-16777217):(t.memoizedProps!==c&&fr(n),ut(n),n.flags&=-16777217),null;case 27:de(n),s=ue.current;var m=n.type;if(t!==null&&n.stateNode!=null)t.memoizedProps!==c&&fr(n);else{if(!c){if(n.stateNode===null)throw Error(a(166));return ut(n),null}t=ae.current,Hs(n)?Mg(n):(t=ev(m,c,s),n.stateNode=t,fr(n))}return ut(n),null;case 5:if(de(n),s=n.type,t!==null&&n.stateNode!=null)t.memoizedProps!==c&&fr(n);else{if(!c){if(n.stateNode===null)throw Error(a(166));return ut(n),null}if(t=ae.current,Hs(n))Mg(n);else{switch(m=gc(ue.current),t){case 1:t=m.createElementNS("http://www.w3.org/2000/svg",s);break;case 2:t=m.createElementNS("http://www.w3.org/1998/Math/MathML",s);break;default:switch(s){case"svg":t=m.createElementNS("http://www.w3.org/2000/svg",s);break;case"math":t=m.createElementNS("http://www.w3.org/1998/Math/MathML",s);break;case"script":t=m.createElement("div"),t.innerHTML=" - + +
diff --git a/python/packages/devui/dev.md b/python/packages/devui/dev.md index a41b2d8aac..838e3536fc 100644 --- a/python/packages/devui/dev.md +++ b/python/packages/devui/dev.md @@ -120,7 +120,7 @@ Agent Framework content types → OpenAI Responses API events (in `_mapper.py`): | `TextReasoningContent` | `response.reasoning.delta` | Standard | | `FunctionCallContent` (initial) | `response.output_item.added` | Standard | | `FunctionCallContent` (args) | `response.function_call_arguments.delta` | Standard | -| `FunctionResultContent` | `response.function_result.complete` | Standard | +| `FunctionResultContent` | `response.function_result.complete` | DevUI | | `ErrorContent` | `response.error` | Standard | | `UsageContent` | `response.usage.complete` | Extended | | `WorkflowEvent` | `response.workflow.event` | DevUI | diff --git a/python/packages/devui/frontend/package.json b/python/packages/devui/frontend/package.json index 049c532213..59c43b45d4 100644 --- a/python/packages/devui/frontend/package.json +++ b/python/packages/devui/frontend/package.json @@ -26,7 +26,8 @@ "react": "^19.1.1", "react-dom": "^19.1.1", "tailwind-merge": "^3.3.1", - "tailwindcss": "^4.1.12" + "tailwindcss": "^4.1.12", + "zustand": "^5.0.8" }, "devDependencies": { "@eslint/js": "^9.33.0", diff --git a/python/packages/devui/frontend/src/App.tsx b/python/packages/devui/frontend/src/App.tsx index fdb6c0d52e..caddc4ffe4 100644 --- a/python/packages/devui/frontend/src/App.tsx +++ b/python/packages/devui/frontend/src/App.tsx @@ -3,90 +3,105 @@ * Features: Entity selection, layout management, debug coordination */ -import { useState, useEffect, useCallback } from "react"; -import { AppHeader, DebugPanel, SettingsModal } from "@/components/layout"; +import { useEffect, useCallback } from "react"; +import { AppHeader, DebugPanel, SettingsModal, DeploymentModal } from "@/components/layout"; import { GalleryView } from "@/components/features/gallery"; import { AgentView } from "@/components/features/agent"; import { WorkflowView } from "@/components/features/workflow"; -import { LoadingState } from "@/components/ui/loading-state"; import { Toast } from "@/components/ui/toast"; import { apiClient } from "@/services/api"; -import { PanelRightOpen, ChevronDown, ServerOff } from "lucide-react"; -import type { SampleEntity } from "@/data/gallery"; +import { PanelRightOpen, ChevronDown, ServerOff, Rocket } from "lucide-react"; import type { AgentInfo, WorkflowInfo, - AppState, ExtendedResponseStreamEvent, } from "@/types"; import { Button } from "./components/ui/button"; +import { useDevUIStore } from "@/stores"; export default function App() { - const [appState, setAppState] = useState({ - agents: [], - workflows: [], - isLoading: true, - }); + // Entity state from Zustand + const agents = useDevUIStore((state) => state.agents); + const workflows = useDevUIStore((state) => state.workflows); + const selectedAgent = useDevUIStore((state) => state.selectedAgent); + const isLoadingEntities = useDevUIStore((state) => state.isLoadingEntities); + const entityError = useDevUIStore((state) => state.entityError); - const [debugEvents, setDebugEvents] = useState( - [] - ); - const [showDebugPanel, setShowDebugPanel] = useState(() => { - const saved = localStorage.getItem("showDebugPanel"); - return saved !== null ? saved === "true" : true; - }); - const [debugPanelWidth, setDebugPanelWidth] = useState(() => { - const savedWidth = localStorage.getItem("debugPanelWidth"); - return savedWidth ? parseInt(savedWidth, 10) : 320; - }); - const [isResizing, setIsResizing] = useState(false); - const [showAboutModal, setShowAboutModal] = useState(false); - const [showGallery, setShowGallery] = useState(false); - const [addingEntityId, setAddingEntityId] = useState(null); - const [errorEntityId, setErrorEntityId] = useState(null); - const [errorMessage, setErrorMessage] = useState(null); - const [showEntityNotFoundToast, setShowEntityNotFoundToast] = useState(false); + // Entity actions + const setAgents = useDevUIStore((state) => state.setAgents); + const setWorkflows = useDevUIStore((state) => state.setWorkflows); + const selectEntity = useDevUIStore((state) => state.selectEntity); + const updateAgent = useDevUIStore((state) => state.updateAgent); + const updateWorkflow = useDevUIStore((state) => state.updateWorkflow); + const setIsLoadingEntities = useDevUIStore((state) => state.setIsLoadingEntities); + const setEntityError = useDevUIStore((state) => state.setEntityError); + + // UI state from Zustand + const showDebugPanel = useDevUIStore((state) => state.showDebugPanel); + const debugPanelWidth = useDevUIStore((state) => state.debugPanelWidth); + const debugEvents = useDevUIStore((state) => state.debugEvents); + const isResizing = useDevUIStore((state) => state.isResizing); + + // UI actions + const setShowDebugPanel = useDevUIStore((state) => state.setShowDebugPanel); + const setDebugPanelWidth = useDevUIStore((state) => state.setDebugPanelWidth); + const addDebugEvent = useDevUIStore((state) => state.addDebugEvent); + const clearDebugEvents = useDevUIStore((state) => state.clearDebugEvents); + const setIsResizing = useDevUIStore((state) => state.setIsResizing); + + // Modal state + const showAboutModal = useDevUIStore((state) => state.showAboutModal); + const showGallery = useDevUIStore((state) => state.showGallery); + const showDeployModal = useDevUIStore((state) => state.showDeployModal); + const showEntityNotFoundToast = useDevUIStore((state) => state.showEntityNotFoundToast); + + // Modal actions + const setShowAboutModal = useDevUIStore((state) => state.setShowAboutModal); + const setShowGallery = useDevUIStore((state) => state.setShowGallery); + const setShowDeployModal = useDevUIStore((state) => state.setShowDeployModal); + const setShowEntityNotFoundToast = useDevUIStore((state) => state.setShowEntityNotFoundToast); // Initialize app - load agents and workflows useEffect(() => { const loadData = async () => { try { - const [agents, workflows] = await Promise.all([ - apiClient.getAgents(), - apiClient.getWorkflows(), - ]); + // Single API call instead of two parallel calls to same endpoint + const { agents: agentList, workflows: workflowList } = await apiClient.getEntities(); + + setAgents(agentList); + setWorkflows(workflowList); // Check if there's an entity_id in the URL const urlParams = new URLSearchParams(window.location.search); const entityId = urlParams.get("entity_id"); - let selectedAgent: AgentInfo | WorkflowInfo | undefined; + let selectedEntity: AgentInfo | WorkflowInfo | undefined; // Try to find entity from URL parameter first if (entityId) { - selectedAgent = - agents.find((a) => a.id === entityId) || - workflows.find((w) => w.id === entityId); + selectedEntity = + agentList.find((a) => a.id === entityId) || + workflowList.find((w) => w.id === entityId); // If entity not found but was requested, show notification - if (!selectedAgent) { + if (!selectedEntity) { setShowEntityNotFoundToast(true); } } // Fallback to first available entity if URL entity not found - if (!selectedAgent) { - selectedAgent = - agents.length > 0 - ? agents[0] - : workflows.length > 0 - ? workflows[0] + if (!selectedEntity) { + selectedEntity = + agentList.length > 0 + ? agentList[0] + : workflowList.length > 0 + ? workflowList[0] : undefined; // Update URL to match actual selected entity (or clear if none) - if (selectedAgent) { + if (selectedEntity) { const url = new URL(window.location.href); - url.searchParams.set("entity_id", selectedAgent.id); + url.searchParams.set("entity_id", selectedEntity.id); window.history.replaceState({}, "", url); } else { // Clear entity_id if no entities available @@ -96,34 +111,44 @@ export default function App() { } } - setAppState((prev) => ({ - ...prev, - agents, - workflows, - selectedAgent, - isLoading: false, - })); + if (selectedEntity) { + selectEntity(selectedEntity); + + // Load full info for the first entity immediately + if (selectedEntity.metadata?.lazy_loaded === false) { + try { + if (selectedEntity.type === "agent") { + const fullAgent = await apiClient.getAgentInfo( + selectedEntity.id + ); + updateAgent(fullAgent); + } else { + const fullWorkflow = await apiClient.getWorkflowInfo( + selectedEntity.id + ); + updateWorkflow(fullWorkflow); + } + } catch (error) { + console.error( + `Failed to load full info for first entity ${selectedEntity.id}:`, + error + ); + } + } + } + + setIsLoadingEntities(false); } catch (error) { console.error("Failed to load agents/workflows:", error); - setAppState((prev) => ({ - ...prev, - error: error instanceof Error ? error.message : "Failed to load data", - isLoading: false, - })); + setEntityError( + error instanceof Error ? error.message : "Failed to load data" + ); + setIsLoadingEntities(false); } }; loadData(); - }, []); - - // Save debug panel state to localStorage - useEffect(() => { - localStorage.setItem("showDebugPanel", showDebugPanel.toString()); - }, [showDebugPanel]); - - useEffect(() => { - localStorage.setItem("debugPanelWidth", debugPanelWidth.toString()); - }, [debugPanelWidth]); + }, [setAgents, setWorkflows, selectEntity, updateAgent, updateWorkflow, setIsLoadingEntities, setEntityError, setShowEntityNotFoundToast]); // Handle resize drag const handleMouseDown = useCallback( @@ -155,161 +180,43 @@ export default function App() { [debugPanelWidth] ); - // Handle entity selection - const handleEntitySelect = useCallback((item: AgentInfo | WorkflowInfo) => { - setAppState((prev) => ({ - ...prev, - selectedAgent: item, - currentThread: undefined, - })); + // Handle entity selection - uses Zustand's selectEntity which handles ALL side effects + const handleEntitySelect = useCallback( + async (item: AgentInfo | WorkflowInfo) => { + selectEntity(item); // This clears conversation state, debug events, and updates URL! - // Update URL with selected entity ID - const url = new URL(window.location.href); - url.searchParams.set("entity_id", item.id); - window.history.pushState({}, "", url); - - // Clear debug events when switching entities - setDebugEvents([]); - }, []); + // If entity is sparse (not fully loaded), load full details + if (item.metadata?.lazy_loaded === false) { + try { + if (item.type === "agent") { + const fullAgent = await apiClient.getAgentInfo(item.id); + updateAgent(fullAgent); + } else { + const fullWorkflow = await apiClient.getWorkflowInfo(item.id); + updateWorkflow(fullWorkflow); + } + } catch (error) { + console.error(`Failed to load full info for ${item.id}:`, error); + } + } + }, + [selectEntity, updateAgent, updateWorkflow] + ); // Handle debug events from active view const handleDebugEvent = useCallback( (event: ExtendedResponseStreamEvent | "clear") => { if (event === "clear") { - setDebugEvents([]); + clearDebugEvents(); } else { - setDebugEvents((prev) => [...prev, event]); + addDebugEvent(event); } }, - [] - ); - - // Handle adding sample entity - const handleAddSample = useCallback(async (sample: SampleEntity) => { - setAddingEntityId(sample.id); - setErrorEntityId(null); - setErrorMessage(null); - - try { - // Call backend to fetch and add entity - const newEntity = await apiClient.addEntity(sample.url, { - source: "remote_gallery", - originalUrl: sample.url, - sampleId: sample.id, - }); - - // Convert backend entity to frontend format - const convertedEntity = { - id: newEntity.id, - name: newEntity.name, - description: newEntity.description, - type: newEntity.type, - source: - (newEntity.source as "directory" | "in_memory" | "remote_gallery") || - "remote_gallery", - has_env: false, - module_path: undefined, - }; - - // Update app state - if (newEntity.type === "agent") { - const agentEntity = { - ...convertedEntity, - tools: (newEntity.tools || []).map((tool) => - typeof tool === "string" ? tool : JSON.stringify(tool) - ), - } as AgentInfo; - - setAppState((prev) => ({ - ...prev, - agents: [...prev.agents, agentEntity], - selectedAgent: agentEntity, - })); - - // Update URL with new entity - const url = new URL(window.location.href); - url.searchParams.set("entity_id", agentEntity.id); - window.history.pushState({}, "", url); - } else { - const workflowEntity = { - ...convertedEntity, - executors: (newEntity.tools || []).map((tool) => - typeof tool === "string" ? tool : JSON.stringify(tool) - ), - input_schema: { type: "string" }, - input_type_name: "Input", - start_executor_id: - newEntity.tools && newEntity.tools.length > 0 - ? typeof newEntity.tools[0] === "string" - ? newEntity.tools[0] - : JSON.stringify(newEntity.tools[0]) - : "unknown", - } as WorkflowInfo; - - setAppState((prev) => ({ - ...prev, - workflows: [...prev.workflows, workflowEntity], - selectedAgent: workflowEntity, - })); - - // Update URL with new entity - const url = new URL(window.location.href); - url.searchParams.set("entity_id", workflowEntity.id); - window.history.pushState({}, "", url); - } - - // Close gallery and clear debug events - setShowGallery(false); - setDebugEvents([]); - } catch (error) { - const errMsg = - error instanceof Error ? error.message : "Failed to add sample entity"; - console.error("Failed to add sample entity:", errMsg); - setErrorEntityId(sample.id); - setErrorMessage(errMsg); - } finally { - setAddingEntityId(null); - } - }, []); - - const handleClearError = useCallback(() => { - setErrorEntityId(null); - setErrorMessage(null); - }, []); - - // Handle removing entity - const handleRemoveEntity = useCallback( - async (entityId: string) => { - try { - await apiClient.removeEntity(entityId); - - // Update app state - setAppState((prev) => ({ - ...prev, - agents: prev.agents.filter((a) => a.id !== entityId), - workflows: prev.workflows.filter((w) => w.id !== entityId), - selectedAgent: - prev.selectedAgent?.id === entityId - ? undefined - : prev.selectedAgent, - })); - - // Update URL - clear entity_id if we removed the selected entity - if (appState.selectedAgent?.id === entityId) { - const url = new URL(window.location.href); - url.searchParams.delete("entity_id"); - window.history.pushState({}, "", url); - setDebugEvents([]); - } - } catch (error) { - console.error("Failed to remove entity:", error); - } - }, - [appState.selectedAgent?.id] + [addDebugEvent, clearDebugEvents] ); // Show loading state while initializing - if (appState.isLoading) { + if (isLoadingEntities) { return (
{/* Top Bar - Skeleton */} @@ -322,17 +229,18 @@ export default function App() { {/* Loading Content */} - +
+
+
Initializing DevUI...
+
Loading agents and workflows from your configuration
+
+
); } // Show error state if loading failed - if (appState.error) { + if (entityError) { return (
{}} - onRemove={handleRemoveEntity} isLoading={false} onSettingsClick={() => setShowAboutModal(true)} /> @@ -388,14 +295,14 @@ export default function App() {
{/* Error Details (Collapsible) */} - {appState.error && ( + {entityError && (
Error details

- {appState.error} + {entityError}

)} @@ -420,13 +327,12 @@ export default function App() { return (
setShowGallery(true)} - isLoading={appState.isLoading} + isLoading={isLoadingEntities} onSettingsClick={() => setShowAboutModal(true)} /> @@ -437,40 +343,28 @@ export default function App() {
setShowGallery(false)} hasExistingEntities={ - appState.agents.length > 0 || appState.workflows.length > 0 + agents.length > 0 || workflows.length > 0 } />
- ) : appState.agents.length === 0 && appState.workflows.length === 0 ? ( + ) : agents.length === 0 && workflows.length === 0 ? ( // Empty state - show gallery inline (full width, no debug panel) - + ) : ( <> {/* Left Panel - Main View */}
- {appState.selectedAgent ? ( - appState.selectedAgent.type === "agent" ? ( + {selectedAgent ? ( + selectedAgent.type === "agent" ? ( ) : ( ) @@ -503,7 +397,7 @@ export default function App() { {/* Right Panel - Debug */}
setShowDebugPanel(false)} /> + + {/* Deploy Footer - Pinned to bottom */} +
+ +
) : ( @@ -534,6 +443,13 @@ export default function App() { {/* Settings Modal */} + {/* Deployment Modal */} + setShowDeployModal(false)} + agentName={selectedAgent?.name} + /> + {/* Toast Notification */} {showEntityNotFoundToast && ( void; @@ -61,14 +58,46 @@ interface ConversationItemBubbleProps { } function ConversationItemBubble({ item }: ConversationItemBubbleProps) { + const [isHovered, setIsHovered] = useState(false); + const [copied, setCopied] = useState(false); + + // Extract text content from message for copying + const getMessageText = () => { + if (item.type === "message") { + return item.content + .filter((c) => c.type === "text") + .map((c) => (c as import("@/types/openai").MessageTextContent).text) + .join("\n"); + } + return ""; + }; + + const handleCopy = async () => { + const text = getMessageText(); + if (!text) return; + + try { + await navigator.clipboard.writeText(text); + setCopied(true); + setTimeout(() => setCopied(false), 2000); + } catch (err) { + console.error("Failed to copy:", err); + } + }; + // Handle different item types if (item.type === "message") { const isUser = item.role === "user"; const isError = item.status === "incomplete"; const Icon = isUser ? User : isError ? AlertCircle : Bot; + const messageText = getMessageText(); return ( -
+
setIsHovered(true)} + onMouseLeave={() => setIsHovered(false)} + >
-
- {isError && ( -
- - - Unable to process request - +
+
+ {isError && ( +
+ + + Unable to process request + +
+ )} +
+
- )} -
-
+ + {/* Copy button - appears on hover, always top-right inside */} + {messageText && isHovered && ( + + )}
@@ -115,10 +166,10 @@ function ConversationItemBubble({ item }: ConversationItemBubbleProps) { - ↓{item.usage.input_tokens} + ↑{item.usage.input_tokens} - ↑{item.usage.output_tokens} + ↓{item.usage.output_tokens} ({item.usage.total_tokens} tokens) @@ -146,33 +197,35 @@ function ConversationItemBubble({ item }: ConversationItemBubbleProps) { } export function AgentView({ selectedAgent, onDebugEvent }: AgentViewProps) { - const [chatState, setChatState] = useState({ - items: [], - isStreaming: false, - }); - const [currentConversation, setCurrentConversation] = useState< - Conversation | undefined - >(undefined); - const [availableConversations, setAvailableConversations] = useState< - Conversation[] - >([]); - const [inputValue, setInputValue] = useState(""); - const [isSubmitting, setIsSubmitting] = useState(false); - const [attachments, setAttachments] = useState([]); - const [loadingConversations, setLoadingConversations] = useState(false); + // Get conversation state from Zustand + const currentConversation = useDevUIStore((state) => state.currentConversation); + const availableConversations = useDevUIStore((state) => state.availableConversations); + const chatItems = useDevUIStore((state) => state.chatItems); + const isStreaming = useDevUIStore((state) => state.isStreaming); + const isSubmitting = useDevUIStore((state) => state.isSubmitting); + const loadingConversations = useDevUIStore((state) => state.loadingConversations); + const inputValue = useDevUIStore((state) => state.inputValue); + const attachments = useDevUIStore((state) => state.attachments); + const conversationUsage = useDevUIStore((state) => state.conversationUsage); + const pendingApprovals = useDevUIStore((state) => state.pendingApprovals); + + // Get conversation actions from Zustand (only the ones we actually use) + const setCurrentConversation = useDevUIStore((state) => state.setCurrentConversation); + const setAvailableConversations = useDevUIStore((state) => state.setAvailableConversations); + const setChatItems = useDevUIStore((state) => state.setChatItems); + const setIsStreaming = useDevUIStore((state) => state.setIsStreaming); + const setIsSubmitting = useDevUIStore((state) => state.setIsSubmitting); + const setLoadingConversations = useDevUIStore((state) => state.setLoadingConversations); + const setInputValue = useDevUIStore((state) => state.setInputValue); + const setAttachments = useDevUIStore((state) => state.setAttachments); + const updateConversationUsage = useDevUIStore((state) => state.updateConversationUsage); + const setPendingApprovals = useDevUIStore((state) => state.setPendingApprovals); + + // Local UI state (not in Zustand - component-specific) const [isDragOver, setIsDragOver] = useState(false); const [dragCounter, setDragCounter] = useState(0); - const [pasteNotification, setPasteNotification] = useState( - null - ); + const [pasteNotification, setPasteNotification] = useState(null); const [detailsModalOpen, setDetailsModalOpen] = useState(false); - const [conversationUsage, setConversationUsage] = useState<{ - total_tokens: number; - message_count: number; - }>({ total_tokens: 0, message_count: 0 }); - const [pendingApprovals, setPendingApprovals] = useState( - [] - ); const scrollAreaRef = useRef(null); const messagesEndRef = useRef(null); @@ -183,18 +236,48 @@ export function AgentView({ selectedAgent, onDebugEvent }: AgentViewProps) { input_tokens: number; output_tokens: number; } | null>(null); + const userJustSentMessage = useRef(false); // Auto-scroll to bottom when new items arrive useEffect(() => { - messagesEndRef.current?.scrollIntoView({ behavior: "smooth" }); - }, [chatState.items, chatState.isStreaming]); + if (!messagesEndRef.current) return; + + // Check if user is near bottom (within 100px) + const scrollContainer = scrollAreaRef.current?.querySelector('[data-radix-scroll-area-viewport]'); + + let shouldScroll = false; + + if (scrollContainer) { + const { scrollTop, scrollHeight, clientHeight } = scrollContainer; + const isNearBottom = scrollHeight - scrollTop - clientHeight < 100; + + // Always scroll if user just sent a message, otherwise only if near bottom + shouldScroll = userJustSentMessage.current || isNearBottom; + } else { + // Fallback if scroll container not found - always scroll + shouldScroll = true; + } + + if (shouldScroll) { + // Use instant scroll during streaming for smooth chunk additions + // Use smooth scroll when not streaming (new messages) + messagesEndRef.current.scrollIntoView({ + behavior: isStreaming ? "instant" : "smooth" + }); + } + + // Reset the flag after first scroll + if (userJustSentMessage.current && !isStreaming) { + userJustSentMessage.current = false; + } + }, [chatItems, isStreaming]); // Return focus to input after streaming completes useEffect(() => { - if (!chatState.isStreaming && !isSubmitting) { + if (!isStreaming && !isSubmitting) { textareaRef.current?.focus(); } - }, [chatState.isStreaming, isSubmitting]); + }, [isStreaming, isSubmitting]); // Load conversations when agent changes useEffect(() => { @@ -223,12 +306,14 @@ export function AgentView({ selectedAgent, onDebugEvent }: AgentViewProps) { ); // Use OpenAI ConversationItems directly (no conversion!) - setChatState({ - items: items as import("@/types/openai").ConversationItem[], - isStreaming: false - }); + setChatItems(items as import("@/types/openai").ConversationItem[]); + setIsStreaming(false); } catch { - setChatState({ items: [], isStreaming: false }); + // 404 means conversation exists but has no items yet (newly created) + // This is normal - just start with empty chat + console.debug(`No items found for conversation ${mostRecent.id}, starting fresh`); + setChatItems([]); + setIsStreaming(false); } // Cache to localStorage for faster future loads @@ -252,11 +337,24 @@ export function AgentView({ selectedAgent, onDebugEvent }: AgentViewProps) { const convs = JSON.parse(cached) as Conversation[]; if (convs.length > 0) { - // Use most recent cached conversation - setAvailableConversations(convs); - setCurrentConversation(convs[0]); - setChatState({ items: [], isStreaming: false }); - return; + // Validate that cached conversations still exist in backend + // Try to load items for the most recent one to verify it exists + try { + await apiClient.listConversationItems(convs[0].id); + + // Success! Conversation exists in backend + setAvailableConversations(convs); + setCurrentConversation(convs[0]); + setChatItems([]); + setIsStreaming(false); + return; + } catch { + // Cached conversation doesn't exist anymore (server restarted) + // Clear stale cache and create new conversation + console.debug(`Cached conversation ${convs[0].id} no longer exists, clearing cache`); + localStorage.removeItem(cachedKey); + // Fall through to Step 3 + } } } catch { // Invalid cache - clear it @@ -271,28 +369,28 @@ export function AgentView({ selectedAgent, onDebugEvent }: AgentViewProps) { setCurrentConversation(newConversation); setAvailableConversations([newConversation]); - setChatState({ items: [], isStreaming: false }); + setChatItems([]); + setIsStreaming(false); // Save to localStorage localStorage.setItem(cachedKey, JSON.stringify([newConversation])); } catch { setAvailableConversations([]); - setChatState({ items: [], isStreaming: false }); + setChatItems([]); + setIsStreaming(false); } finally { setLoadingConversations(false); } }; // Clear chat when agent changes - setChatState({ - items: [], - isStreaming: false, - }); + setChatItems([]); + setIsStreaming(false); setCurrentConversation(undefined); accumulatedText.current = ""; loadConversations(); - }, [selectedAgent]); + }, [selectedAgent, setLoadingConversations, setAvailableConversations, setCurrentConversation, setChatItems, setIsStreaming]); // Handle file uploads const handleFilesSelected = async (files: File[]) => { @@ -315,11 +413,11 @@ export function AgentView({ selectedAgent, onDebugEvent }: AgentViewProps) { }); } - setAttachments((prev) => [...prev, ...newAttachments]); + setAttachments([...useDevUIStore.getState().attachments, ...newAttachments]); }; const handleRemoveAttachment = (id: string) => { - setAttachments((prev) => prev.filter((att) => att.id !== id)); + setAttachments(useDevUIStore.getState().attachments.filter((att) => att.id !== id)); }; // Drag and drop handlers @@ -353,7 +451,7 @@ export function AgentView({ selectedAgent, onDebugEvent }: AgentViewProps) { setIsDragOver(false); setDragCounter(0); - if (isSubmitting || chatState.isStreaming) return; + if (isSubmitting || isStreaming) return; const files = Array.from(e.dataTransfer.files); if (files.length > 0) { @@ -523,12 +621,11 @@ export function AgentView({ selectedAgent, onDebugEvent }: AgentViewProps) { agent_id: selectedAgent.id, }); setCurrentConversation(newConversation); - setAvailableConversations((prev) => [newConversation, ...prev]); - setChatState({ - items: [], - isStreaming: false, - }); - setConversationUsage({ total_tokens: 0, message_count: 0 }); + setAvailableConversations([newConversation, ...useDevUIStore.getState().availableConversations]); + setChatItems([]); + setIsStreaming(false); + // Reset conversation usage by setting it to initial state + useDevUIStore.setState({ conversationUsage: { total_tokens: 0, message_count: 0 } }); accumulatedText.current = ""; // Update localStorage cache with new conversation @@ -538,7 +635,7 @@ export function AgentView({ selectedAgent, onDebugEvent }: AgentViewProps) { } catch { // Failed to create conversation } - }, [selectedAgent, availableConversations]); + }, [selectedAgent, availableConversations, setCurrentConversation, setAvailableConversations, setChatItems, setIsStreaming]); // Handle conversation deletion const handleDeleteConversation = useCallback( @@ -578,18 +675,14 @@ export function AgentView({ selectedAgent, onDebugEvent }: AgentViewProps) { // Select the most recent remaining conversation const nextConversation = updatedConversations[0]; setCurrentConversation(nextConversation); - setChatState({ - items: [], - isStreaming: false, - }); + setChatItems([]); + setIsStreaming(false); } else { // No conversations left, clear everything setCurrentConversation(undefined); - setChatState({ - items: [], - isStreaming: false, - }); - setConversationUsage({ total_tokens: 0, message_count: 0 }); + setChatItems([]); + setIsStreaming(false); + useDevUIStore.setState({ conversationUsage: { total_tokens: 0, message_count: 0 } }); accumulatedText.current = ""; } } @@ -601,7 +694,7 @@ export function AgentView({ selectedAgent, onDebugEvent }: AgentViewProps) { alert("Failed to delete conversation. Please try again."); } }, - [availableConversations, currentConversation, selectedAgent, onDebugEvent] + [availableConversations, currentConversation, selectedAgent, onDebugEvent, setAvailableConversations, setCurrentConversation, setChatItems, setIsStreaming] ); // Handle conversation selection @@ -624,28 +717,28 @@ export function AgentView({ selectedAgent, onDebugEvent }: AgentViewProps) { // Use OpenAI ConversationItems directly (no conversion!) const items = result.data as import("@/types/openai").ConversationItem[]; - setChatState({ - items, - isStreaming: false, - }); + setChatItems(items); + setIsStreaming(false); // Calculate usage from loaded items - setConversationUsage({ - total_tokens: 0, // We don't have usage info in stored items - message_count: items.length, + useDevUIStore.setState({ + conversationUsage: { + total_tokens: 0, // We don't have usage info in stored items + message_count: items.length, + } }); } catch { - // Fallback to clearing items - setChatState({ - items: [], - isStreaming: false, - }); - setConversationUsage({ total_tokens: 0, message_count: 0 }); + // 404 means conversation doesn't exist or has no items yet + // This can happen if server restarted (in-memory store cleared) + console.debug(`No items found for conversation ${conversationId}, starting with empty chat`); + setChatItems([]); + setIsStreaming(false); + useDevUIStore.setState({ conversationUsage: { total_tokens: 0, message_count: 0 } }); } accumulatedText.current = ""; }, - [availableConversations, onDebugEvent] + [availableConversations, onDebugEvent, setCurrentConversation, setChatItems, setIsStreaming] ); // Handle function approval responses @@ -677,8 +770,8 @@ export function AgentView({ selectedAgent, onDebugEvent }: AgentViewProps) { }; // Remove from pending immediately (will be confirmed by backend event) - setPendingApprovals((prev) => - prev.filter((a) => a.request_id !== request_id) + setPendingApprovals( + useDevUIStore.getState().pendingApprovals.filter((a) => a.request_id !== request_id) ); // Trigger send (we'll call this from the UI button handler) @@ -729,11 +822,8 @@ export function AgentView({ selectedAgent, onDebugEvent }: AgentViewProps) { status: "completed", }; - setChatState((prev) => ({ - ...prev, - items: [...prev.items, userMessage], - isStreaming: true, - })); + setChatItems([...useDevUIStore.getState().chatItems, userMessage]); + setIsStreaming(true); // Create assistant message placeholder const assistantMessage: import("@/types/openai").ConversationMessage = { @@ -744,10 +834,7 @@ export function AgentView({ selectedAgent, onDebugEvent }: AgentViewProps) { status: "in_progress", }; - setChatState((prev) => ({ - ...prev, - items: [...prev.items, assistantMessage], - })); + setChatItems([...useDevUIStore.getState().chatItems, assistantMessage]); try { // If no conversation selected, create one automatically @@ -758,7 +845,7 @@ export function AgentView({ selectedAgent, onDebugEvent }: AgentViewProps) { agent_id: selectedAgent.id, }); setCurrentConversation(conversationToUse); - setAvailableConversations((prev) => [conversationToUse!, ...prev]); + setAvailableConversations([conversationToUse, ...useDevUIStore.getState().availableConversations]); } catch { // Failed to create conversation } @@ -772,9 +859,6 @@ export function AgentView({ selectedAgent, onDebugEvent }: AgentViewProps) { // Clear text accumulator for new response accumulatedText.current = ""; - // Clear debug panel events for new agent run - onDebugEvent("clear"); - // Use OpenAI-compatible API streaming - direct event handling const streamGenerator = apiClient.streamAgentExecutionOpenAI( selectedAgent.id, @@ -805,8 +889,8 @@ export function AgentView({ selectedAgent, onDebugEvent }: AgentViewProps) { const approvalEvent = openAIEvent as import("@/types/openai").ResponseFunctionApprovalRequestedEvent; // Add to pending approvals - setPendingApprovals((prev) => [ - ...prev, + setPendingApprovals([ + ...useDevUIStore.getState().pendingApprovals, { request_id: approvalEvent.request_id, function_call: approvalEvent.function_call, @@ -820,8 +904,8 @@ export function AgentView({ selectedAgent, onDebugEvent }: AgentViewProps) { const responseEvent = openAIEvent as import("@/types/openai").ResponseFunctionApprovalRespondedEvent; // Remove from pending approvals - setPendingApprovals((prev) => - prev.filter((a) => a.request_id !== responseEvent.request_id) + setPendingApprovals( + useDevUIStore.getState().pendingApprovals.filter((a) => a.request_id !== responseEvent.request_id) ); continue; } @@ -834,24 +918,22 @@ export function AgentView({ selectedAgent, onDebugEvent }: AgentViewProps) { const errorMessage = errorEvent.message || "An error occurred"; // Update assistant message with error and stop streaming - setChatState((prev) => ({ - ...prev, - isStreaming: false, - items: prev.items.map((item) => - item.id === assistantMessage.id && item.type === "message" - ? { - ...item, - content: [ - { - type: "text", - text: errorMessage, - } as import("@/types/openai").MessageTextContent, - ], - status: "incomplete" as const, - } - : item - ), - })); + const currentItems = useDevUIStore.getState().chatItems; + setChatItems(currentItems.map((item) => + item.id === assistantMessage.id && item.type === "message" + ? { + ...item, + content: [ + { + type: "text", + text: errorMessage, + } as import("@/types/openai").MessageTextContent, + ], + status: "incomplete" as const, + } + : item + )); + setIsStreaming(false); return; // Exit stream processing early on error } @@ -864,23 +946,21 @@ export function AgentView({ selectedAgent, onDebugEvent }: AgentViewProps) { accumulatedText.current += openAIEvent.delta; // Update assistant message with accumulated content - setChatState((prev) => ({ - ...prev, - items: prev.items.map((item) => - item.id === assistantMessage.id && item.type === "message" - ? { - ...item, - content: [ - { - type: "text", - text: accumulatedText.current, - } as import("@/types/openai").MessageTextContent, - ], - status: "in_progress" as const, - } - : item - ), - })); + const currentItems = useDevUIStore.getState().chatItems; + setChatItems(currentItems.map((item) => + item.id === assistantMessage.id && item.type === "message" + ? { + ...item, + content: [ + { + type: "text", + text: accumulatedText.current, + } as import("@/types/openai").MessageTextContent, + ], + status: "in_progress" as const, + } + : item + )); } // Handle completion/error by detecting when streaming stops @@ -891,56 +971,49 @@ export function AgentView({ selectedAgent, onDebugEvent }: AgentViewProps) { // Usage is provided via response.completed event (OpenAI standard) const finalUsage = currentMessageUsage.current; - setChatState((prev) => ({ - ...prev, - isStreaming: false, - items: prev.items.map((item) => - item.id === assistantMessage.id && item.type === "message" - ? { - ...item, - status: "completed" as const, - usage: finalUsage || undefined, - } - : item - ), - })); + const currentItems = useDevUIStore.getState().chatItems; + setChatItems(currentItems.map((item) => + item.id === assistantMessage.id && item.type === "message" + ? { + ...item, + status: "completed" as const, + usage: finalUsage || undefined, + } + : item + )); + setIsStreaming(false); // Update conversation-level usage stats if (finalUsage) { - setConversationUsage((prev) => ({ - total_tokens: prev.total_tokens + finalUsage.total_tokens, - message_count: prev.message_count + 1, - })); + updateConversationUsage(finalUsage.total_tokens); } // Reset usage for next message currentMessageUsage.current = null; } catch (error) { - setChatState((prev) => ({ - ...prev, - isStreaming: false, - items: prev.items.map((item) => - item.id === assistantMessage.id && item.type === "message" - ? { - ...item, - content: [ - { - type: "text", - text: `Error: ${ - error instanceof Error - ? error.message - : "Failed to get response" - }`, - } as import("@/types/openai").MessageTextContent, - ], - status: "incomplete" as const, - } - : item - ), - })); + const currentItems = useDevUIStore.getState().chatItems; + setChatItems(currentItems.map((item) => + item.id === assistantMessage.id && item.type === "message" + ? { + ...item, + content: [ + { + type: "text", + text: `Error: ${ + error instanceof Error + ? error.message + : "Failed to get response" + }`, + } as import("@/types/openai").MessageTextContent, + ], + status: "incomplete" as const, + } + : item + )); + setIsStreaming(false); } }, - [selectedAgent, currentConversation, onDebugEvent] + [selectedAgent, currentConversation, onDebugEvent, setChatItems, setIsStreaming, setCurrentConversation, setAvailableConversations, setPendingApprovals, updateConversationUsage] ); const handleSubmit = async (e: React.FormEvent) => { @@ -953,6 +1026,9 @@ export function AgentView({ selectedAgent, onDebugEvent }: AgentViewProps) { ) return; + // Set flag to force scroll when user sends message + userJustSentMessage.current = true; + setIsSubmitting(true); const messageText = inputValue.trim(); setInputValue(""); @@ -1056,7 +1132,7 @@ export function AgentView({ selectedAgent, onDebugEvent }: AgentViewProps) { const canSendMessage = selectedAgent && !isSubmitting && - !chatState.isStreaming && + !isStreaming && (inputValue.trim() || attachments.length > 0); return ( @@ -1183,7 +1259,7 @@ export function AgentView({ selectedAgent, onDebugEvent }: AgentViewProps) { {/* Messages */}
- {chatState.items.length === 0 ? ( + {chatItems.length === 0 ? (
Start a conversation with{" "} @@ -1194,7 +1270,7 @@ export function AgentView({ selectedAgent, onDebugEvent }: AgentViewProps) {
) : ( - chatState.items.map((item) => ( + chatItems.map((item) => ( )) )} @@ -1339,13 +1415,13 @@ export function AgentView({ selectedAgent, onDebugEvent }: AgentViewProps) { placeholder={`Message ${ selectedAgent.name || selectedAgent.id }... (Shift+Enter for new line)`} - disabled={isSubmitting || chatState.isStreaming} + disabled={isSubmitting || isStreaming} className="flex-1 min-h-[40px] max-h-[200px] resize-none" style={{ fieldSizing: "content" } as React.CSSProperties} /> -
+
+ + {isStreaming && text.length > 0 && ( + )}
); @@ -223,7 +188,7 @@ export function FunctionCallRenderer({ name, arguments: args, className }: Funct } return ( -
+
setIsExpanded(!isExpanded)} @@ -232,7 +197,11 @@ export function FunctionCallRenderer({ name, arguments: args, className }: Funct Function Call: {name} - {isExpanded ? "▼" : "▶"} + {isExpanded ? ( + + ) : ( + + )}
{isExpanded && (
@@ -264,7 +233,7 @@ export function FunctionResultRenderer({ output, call_id, className }: FunctionR } return ( -
+
setIsExpanded(!isExpanded)} @@ -273,7 +242,11 @@ export function FunctionResultRenderer({ output, call_id, className }: FunctionR Function Result - {isExpanded ? "▼" : "▶"} + {isExpanded ? ( + + ) : ( + + )}
{isExpanded && (
diff --git a/python/packages/devui/frontend/src/components/features/gallery/gallery-view.tsx b/python/packages/devui/frontend/src/components/features/gallery/gallery-view.tsx index 82a370069c..7ffe68043a 100644 --- a/python/packages/devui/frontend/src/components/features/gallery/gallery-view.tsx +++ b/python/packages/devui/frontend/src/components/features/gallery/gallery-view.tsx @@ -17,15 +17,13 @@ import { import { Bot, Workflow, - Plus, - Loader2, User, TriangleAlert, - AlertCircle, - X, Key, ChevronDown, ArrowLeft, + Download, + BookOpen, } from "lucide-react"; import { cn } from "@/lib/utils"; import { @@ -33,13 +31,9 @@ import { type SampleEntity, getDifficultyColor, } from "@/data/gallery"; +import { SetupInstructionsModal } from "./setup-instructions-modal"; interface GalleryViewProps { - onAdd: (sample: SampleEntity) => Promise; - addingEntityId?: string | null; - errorEntityId?: string | null; - errorMessage?: string | null; - onClearError?: (sampleId: string) => void; onClose?: () => void; variant?: "inline" | "route" | "modal"; hasExistingEntities?: boolean; @@ -48,91 +42,41 @@ interface GalleryViewProps { // Internal: Sample Entity Card Component function SampleEntityCard({ sample, - onAdd, - isAdding = false, - hasError = false, - errorMessage, - onClearError, }: { sample: SampleEntity; - onAdd: (sample: SampleEntity) => Promise; - isAdding?: boolean; - hasError?: boolean; - errorMessage?: string | null; - onClearError?: (sampleId: string) => void; }) { - const [isLoading, setIsLoading] = useState(false); - - const handleAdd = async () => { - if (isLoading || isAdding) return; - - setIsLoading(true); - try { - await onAdd(sample); - } finally { - setIsLoading(false); - } - }; - + const [showInstructions, setShowInstructions] = useState(false); const TypeIcon = sample.type === "workflow" ? Workflow : Bot; - const isDisabled = isLoading || isAdding; return ( - - -
-
- - - {sample.type} + <> + + +
+
+ + + {sample.type} + +
+ + {sample.difficulty}
- - {sample.difficulty} - -
- {sample.name} - - {sample.description} - - + {sample.name} + + {sample.description} + + - - {/* Error Banner */} - {hasError && errorMessage && ( -
-
- -
-

- Failed to add -

-

{errorMessage}

-
- {onClearError && ( - - )} -
-
- )} +
{/* Tags */} @@ -199,73 +143,57 @@ function SampleEntityCard({
- - {/* Metadata */} -
-
- - {sample.author} + + {/* Metadata */} +
+
+ + {sample.author} +
-
- {/* Add Button - Full width on its own line */} - - - + {/* Action Buttons */} +
+ + +
+ + + + + ); } // Internal: Sample Entity Grid Component -function SampleEntityGrid({ - samples, - onAdd, - addingEntityId, - errorEntityId, - errorMessage, - onClearError, -}: { - samples: SampleEntity[]; - onAdd: (sample: SampleEntity) => Promise; - addingEntityId?: string | null; - errorEntityId?: string | null; - errorMessage?: string | null; - onClearError?: (sampleId: string) => void; -}) { +function SampleEntityGrid({ samples }: { samples: SampleEntity[] }) { return (
{samples.map((sample) => (
- +
))}
@@ -274,11 +202,6 @@ function SampleEntityGrid({ // Main: Gallery View Component export function GalleryView({ - onAdd, - addingEntityId, - errorEntityId, - errorMessage, - onClearError, onClose, variant = "inline", hasExistingEntities = false, @@ -304,8 +227,8 @@ export function GalleryView({ in a directory containing them.

- You can also import any of the sample agents and workflows - below to get started quickly. + Browse the sample agents and workflows below. Download them, + review the code, and run them locally to get started quickly.

@@ -314,14 +237,7 @@ export function GalleryView({ {/* Sample Gallery */}

Sample Gallery

- +
{/* Footer */} @@ -362,22 +278,15 @@ export function GalleryView({

Sample Gallery

- Browse and add sample agents and workflows to learn the Agent - Framework. These are curated examples ranging from beginner to - advanced. + Browse sample agents and workflows to learn the Agent + Framework. Download these curated examples and run them locally. + Examples range from beginner to advanced.

{/* Sample Gallery */} - + {/* Footer */}
@@ -399,14 +308,5 @@ export function GalleryView({ } // Modal variant - for dropdown trigger (simplified, just the grid) - return ( - - ); + return ; } diff --git a/python/packages/devui/frontend/src/components/features/gallery/setup-instructions-modal.tsx b/python/packages/devui/frontend/src/components/features/gallery/setup-instructions-modal.tsx new file mode 100644 index 0000000000..f6549fe545 --- /dev/null +++ b/python/packages/devui/frontend/src/components/features/gallery/setup-instructions-modal.tsx @@ -0,0 +1,207 @@ +/** + * SetupInstructionsModal - Shows step-by-step instructions for running a sample entity + */ + +import { useState } from "react"; +import { + Dialog, + DialogContent, + DialogDescription, + DialogHeader, + DialogTitle, +} from "@/components/ui/dialog"; +import { Button } from "@/components/ui/button"; +import { Alert, AlertDescription, AlertTitle } from "@/components/ui/alert"; +import { ScrollArea } from "@/components/ui/scroll-area"; +import { + Download, + ExternalLink, + Copy, + Check, + Lightbulb, + BookOpen, +} from "lucide-react"; +import type { SampleEntity } from "@/data/gallery"; + +interface SetupInstructionsModalProps { + sample: SampleEntity; + open: boolean; + onOpenChange: (open: boolean) => void; +} + +function CodeBlock({ children, copyable = false }: { children: string; copyable?: boolean }) { + const [copied, setCopied] = useState(false); + + const handleCopy = () => { + navigator.clipboard.writeText(children); + setCopied(true); + setTimeout(() => setCopied(false), 2000); + }; + + return ( +
+
+        {children}
+      
+ {copyable && ( + + )} +
+ ); +} + +function SetupStep({ + number, + title, + description, + code, + action, + copyable = false, +}: { + number: number; + title: string; + description?: string; + code?: string; + action?: React.ReactNode; + copyable?: boolean; +}) { + return ( +
+
+
+ {number} +
+
+
+

{title}

+ {description &&

{description}

} + {code && {code}} + {action &&
{action}
} +
+
+ ); +} + +export function SetupInstructionsModal({ + sample, + open, + onOpenChange, +}: SetupInstructionsModalProps) { + const hasEnvVars = sample.requiredEnvVars && sample.requiredEnvVars.length > 0; + const stepOffset = hasEnvVars ? 0 : -1; + + return ( + + + + Setup: {sample.name} + + Follow these steps to run this sample {sample.type} locally + + + +
+ +
+ {/* Step 1: Download */} + + + + Download {sample.id}.py + + + } + /> + + {/* Step 2: Create folder */} + + + {/* Step 3: Environment variables (conditional) */} + {hasEnvVars && ( + `${v.name}=${v.example || "your-value-here"}\n# ${v.description}`) + .join("\n\n")} + copyable + /> + )} + + {/* Step 4: Run DevUI */} + + + {/* Alternative: Direct run */} + + + Alternative: Run Programmatically + +

You can also run the {sample.type} directly in Python:

+ + {`from ${sample.id} import ${sample.type} +import asyncio + +async def main(): + response = await ${sample.type}.run("Hello!") + print(response) + +asyncio.run(main())`} + +
+
+ + {/* Help links */} + +
+
+
+
+
+ ); +} diff --git a/python/packages/devui/frontend/src/components/features/workflow/workflow-input-form.tsx b/python/packages/devui/frontend/src/components/features/workflow/workflow-input-form.tsx index bba588c454..15316dd880 100644 --- a/python/packages/devui/frontend/src/components/features/workflow/workflow-input-form.tsx +++ b/python/packages/devui/frontend/src/components/features/workflow/workflow-input-form.tsx @@ -32,22 +32,29 @@ interface FormFieldProps { isRequired?: boolean; } +// Helper: Determine if field is likely a short metadata field (not multiline text) +function isShortField(fieldName: string): boolean { + const shortFieldNames = ['name', 'title', 'id', 'key', 'label', 'type', 'status', 'tag', 'category', 'code', 'username', 'password']; + return shortFieldNames.includes(fieldName.toLowerCase()); +} + function FormField({ name, schema, value, onChange, isRequired = false }: FormFieldProps) { const { type, description, enum: enumValues, default: defaultValue } = schema; - // For text/message/content fields, treat as textarea for better UX - const isTextContentField = ['text', 'message', 'content', 'query', 'prompt'].includes(name.toLowerCase()); + // Determine if this should be a textarea based on JSON Schema format field + // or heuristics (long descriptions, specific field types) + const shouldBeTextarea = + schema.format === "textarea" || // Explicit format from backend + (description && description.length > 100) || // Long description suggests multiline + (type === "string" && !enumValues && !isShortField(name)); // Default strings to textarea unless they're short metadata fields // Determine if this field should span full width - // Only span full if it's a textarea or has very long description const shouldSpanFullWidth = - schema.format === "textarea" || - isTextContentField || // text/message fields span full width + shouldBeTextarea || (description && description.length > 150); const shouldSpanTwoColumns = - schema.format === "textarea" || - isTextContentField || + shouldBeTextarea || (description && description.length > 80) || type === "array"; // Arrays might need more space for comma-separated values @@ -90,8 +97,7 @@ function FormField({ name, schema, value, onChange, isRequired = false }: FormFi
); } else if ( - schema.format === "textarea" || - isTextContentField || + shouldBeTextarea || (description && description.length > 100) ) { // Multi-line text (including text/message/content fields) @@ -110,7 +116,8 @@ function FormField({ name, schema, value, onChange, isRequired = false }: FormFi ? defaultValue : `Enter ${name}` } - rows={isTextContentField ? 4 : 2} + rows={shouldBeTextarea ? 4 : 2} + className="min-w-[300px] w-full" /> {description && (

{description}

diff --git a/python/packages/devui/frontend/src/components/features/workflow/workflow-view.tsx b/python/packages/devui/frontend/src/components/features/workflow/workflow-view.tsx index 19c4e53fa2..eb45753665 100644 --- a/python/packages/devui/frontend/src/components/features/workflow/workflow-view.tsx +++ b/python/packages/devui/frontend/src/components/features/workflow/workflow-view.tsx @@ -201,7 +201,7 @@ function RunWorkflowButton({ {/* Modal with proper Dialog component - matching WorkflowInputForm structure */} - + Configure Workflow Inputs setShowModal(false)} /> diff --git a/python/packages/devui/frontend/src/components/layout/app-header.tsx b/python/packages/devui/frontend/src/components/layout/app-header.tsx index 2ba5b7f913..68ec005f28 100644 --- a/python/packages/devui/frontend/src/components/layout/app-header.tsx +++ b/python/packages/devui/frontend/src/components/layout/app-header.tsx @@ -14,7 +14,6 @@ interface AppHeaderProps { workflows: WorkflowInfo[]; selectedItem?: AgentInfo | WorkflowInfo; onSelect: (item: AgentInfo | WorkflowInfo) => void; - onRemove?: (entityId: string) => void; onBrowseGallery?: () => void; isLoading?: boolean; onSettingsClick?: () => void; @@ -25,7 +24,6 @@ export function AppHeader({ workflows, selectedItem, onSelect, - onRemove, onBrowseGallery, isLoading = false, onSettingsClick, @@ -66,7 +64,6 @@ export function AppHeader({ workflows={workflows} selectedItem={selectedItem} onSelect={onSelect} - onRemove={onRemove} onBrowseGallery={onBrowseGallery} isLoading={isLoading} /> diff --git a/python/packages/devui/frontend/src/components/layout/debug-panel.tsx b/python/packages/devui/frontend/src/components/layout/debug-panel.tsx index 75cbcb5c22..64866ae728 100644 --- a/python/packages/devui/frontend/src/components/layout/debug-panel.tsx +++ b/python/packages/devui/frontend/src/components/layout/debug-panel.tsx @@ -24,6 +24,40 @@ import { } from "lucide-react"; import type { ExtendedResponseStreamEvent } from "@/types"; +// Simple visual separator component +function MessageSeparator() { + return ( +
+
+
+ ); +} + +// Helper to add separators between message rounds +function addSeparatorsToEvents(events: ExtendedResponseStreamEvent[]): (ExtendedResponseStreamEvent | { type: "separator"; id: string })[] { + const result: (ExtendedResponseStreamEvent | { type: "separator"; id: string })[] = []; + let lastWasResponseDone = false; + + for (let i = 0; i < events.length; i++) { + const event = events[i]; + + // Add separator before first event after response.done + if (lastWasResponseDone && event.type !== "response.done") { + result.push({ type: "separator", id: `sep-${i}` }); + lastWasResponseDone = false; + } + + result.push(event); + + // Track when we see response.done + if (event.type === "response.done" || event.type === "response.completed") { + lastWasResponseDone = true; + } + } + + return result; +} + // Type definitions for event data structures interface EventDataBase { call_id?: string; @@ -64,7 +98,7 @@ interface DebugPanelProps { onClose?: () => void; } -// Helper: Extract function result from DevUI custom format +// Helper: Extract function result from DevUI custom event function getFunctionResultFromEvent(event: ExtendedResponseStreamEvent): { call_id: string; output: string; @@ -101,9 +135,18 @@ function processEventsForDisplay( let accumulatedText = ""; for (const event of events) { + // Skip trace events - they belong in the Traces tab only + if ( + event.type === "response.trace_event.complete" || + event.type === "response.trace.complete" + ) { + continue; + } + // Handle response.output_item.added - NEW! Extract function call metadata if (event.type === "response.output_item.added") { - const outputEvent = event as import("@/types").ResponseOutputItemAddedEvent; + const outputEvent = + event as import("@/types").ResponseOutputItemAddedEvent; const item = outputEvent.item; // If it's a function call item, extract metadata @@ -388,15 +431,17 @@ function getEventSummary(event: ExtendedResponseStreamEvent): string { } return "Function arguments..."; + case "response.function_result.complete": { + const resultEvent = + event as import("@/types").ResponseFunctionResultComplete; + const truncated = resultEvent.output.slice(0, 40); + return `Function result: ${truncated}${ + truncated.length >= 40 ? "..." : "" + }`; + } + case "response.output_item.added": { - const result = getFunctionResultFromEvent(event); - if (result) { - const truncated = result.output.slice(0, 40); - return `Tool result: ${truncated}${ - truncated.length >= 40 ? "..." : "" - }`; - } - // Could also be a function call + // Could be a function call const addedEvent = event as import("@/types").ResponseOutputItemAddedEvent; if (addedEvent.item.type === "function_call") { @@ -422,7 +467,8 @@ function getEventSummary(event: ExtendedResponseStreamEvent): string { case "response.completed": if ("response" in event && event.response && "usage" in event.response) { - const completedEvent = event as import("@/types").ResponseCompletedEvent; + const completedEvent = + event as import("@/types").ResponseCompletedEvent; const usage = completedEvent.response.usage; if (usage) { return `Response complete (${usage.total_tokens} tokens)`; @@ -453,6 +499,8 @@ function getEventIcon(type: string) { case "response.function_call.delta": case "response.function_call_arguments.delta": return Wrench; + case "response.function_result.complete": + return CheckCircle2; case "response.output_item.added": return CheckCircle2; case "response.workflow_event.complete": @@ -479,6 +527,8 @@ function getEventColor(type: string) { case "response.function_call.delta": case "response.function_call_arguments.delta": return "text-blue-600 dark:text-blue-400"; + case "response.function_result.complete": + return "text-green-600 dark:text-green-400"; case "response.output_item.added": return "text-green-600 dark:text-green-400"; case "response.workflow_event.complete": @@ -509,6 +559,7 @@ function EventItem({ event }: EventItemProps) { (event.type === "response.function_call.complete" && "data" in event && event.data) || + event.type === "response.function_result.complete" || (event.type === "response.output_item.added" && getFunctionResultFromEvent(event) !== null) || (event.type === "response.workflow_event.complete" && @@ -681,6 +732,53 @@ function EventExpandedContent({ } break; + case "response.function_result.complete": { + const resultEvent = + event as import("@/types").ResponseFunctionResultComplete; + return ( +
+
+ + Function Result +
+
+
+ + Call ID: + + + {resultEvent.call_id} + +
+
+ + Status: + + + {resultEvent.status} + +
+
+ + Output: + +
+
+                  {resultEvent.output}
+                
+
+
+
+
+ ); + } + case "response.output_item.added": { const result = getFunctionResultFromEvent(event); if (result) { @@ -915,7 +1013,8 @@ function EventExpandedContent({ case "response.completed": if ("response" in event && event.response) { - const completedEvent = event as import("@/types").ResponseCompletedEvent; + const completedEvent = + event as import("@/types").ResponseCompletedEvent; const response = completedEvent.response; return (
@@ -1006,11 +1105,14 @@ function EventsTab({ // Process events to accumulate tool calls and reduce noise const processedEvents = processEventsForDisplay(events); + // Add separators between message rounds + const eventsWithSeparators = addSeparatorsToEvents(processedEvents); + // Reverse events so latest appears at top - const reversedEvents = [...processedEvents].reverse(); + const reversedEvents = [...eventsWithSeparators].reverse(); return ( -
+
@@ -1030,7 +1132,7 @@ function EventsTab({ )}
- +
{processedEvents.length === 0 ? (
@@ -1040,9 +1142,12 @@ function EventsTab({
) : (
- {reversedEvents.map((event, index) => ( - - ))} + {reversedEvents.map((event, index) => { + if ('type' in event && event.type === "separator") { + return ; + } + return ; + })}
)}
@@ -1059,18 +1164,21 @@ function TracesTab({ events }: { events: ExtendedResponseStreamEvent[] }) { e.type === "response.trace.complete" ); + // Add separators between message rounds + const tracesWithSeparators = addSeparatorsToEvents(traceEvents); + // Reverse to show latest traces at the top - const reversedTraceEvents = [...traceEvents].reverse(); + const reversedTraceEvents = [...tracesWithSeparators].reverse(); return ( -
+
Traces {traceEvents.length}
- +
{traceEvents.length === 0 ? (
@@ -1094,9 +1202,12 @@ function TracesTab({ events }: { events: ExtendedResponseStreamEvent[] }) {
) : (
- {reversedTraceEvents.map((event, index) => ( - - ))} + {reversedTraceEvents.map((event, index) => { + if ('type' in event && event.type === "separator") { + return ; + } + return ; + })}
)}
@@ -1165,7 +1276,7 @@ function TraceEventItem({ event }: { event: ExtendedResponseStreamEvent }) { )}
-
+
{operationName} {entityId && ({entityId})}
@@ -1193,7 +1304,7 @@ function TraceEventItem({ event }: { event: ExtendedResponseStreamEvent }) { Span ID: - + {data.span_id}
@@ -1203,7 +1314,7 @@ function TraceEventItem({ event }: { event: ExtendedResponseStreamEvent }) { Trace ID: - + {data.trace_id}
@@ -1213,7 +1324,7 @@ function TraceEventItem({ event }: { event: ExtendedResponseStreamEvent }) { Parent Span: - + {data.parent_span_id}
@@ -1250,7 +1361,7 @@ function TraceEventItem({ event }: { event: ExtendedResponseStreamEvent }) { Entity: - + {data.entity_id}
@@ -1338,18 +1449,21 @@ function ToolsTab({ events }: { events: ExtendedResponseStreamEvent[] }) { toolEvents.push(result); }); + // Add separators between message rounds + const toolsWithSeparators = addSeparatorsToEvents(toolEvents); + // Reverse to show latest tools at the top - const reversedToolEvents = [...toolEvents].reverse(); + const reversedToolEvents = [...toolsWithSeparators].reverse(); return ( -
+
Tools {toolEvents.length}
- +
{toolEvents.length === 0 ? (
@@ -1358,9 +1472,12 @@ function ToolsTab({ events }: { events: ExtendedResponseStreamEvent[] }) {
) : (
- {reversedToolEvents.map((event, index) => ( - - ))} + {reversedToolEvents.map((event, index) => { + if ('type' in event && event.type === "separator") { + return ; + } + return ; + })}
)}
@@ -1370,21 +1487,21 @@ function ToolsTab({ events }: { events: ExtendedResponseStreamEvent[] }) { } function ToolEventItem({ event }: { event: ExtendedResponseStreamEvent }) { - if (!("data" in event)) { - return null; - } - - const data = event.data as EventDataBase; const timestamp = new Date().toLocaleTimeString(); - // Check if this is a function call event + // Check if this is a function call or result event const isFunctionCall = event.type === "response.function_call.complete"; - const isFunctionResult = getFunctionResultFromEvent(event) !== null; + const resultData = getFunctionResultFromEvent(event); + const isFunctionResult = resultData !== null; if (!isFunctionCall && !isFunctionResult) { return null; } + // For function calls: extract data field + const callData = + isFunctionCall && "data" in event ? (event.data as EventDataBase) : null; + return (
@@ -1393,9 +1510,9 @@ function ToolEventItem({ event }: { event: ExtendedResponseStreamEvent }) { {isFunctionCall ? "Tool Call" : "Tool Result"} - {isFunctionCall && data.name !== undefined && ( + {isFunctionCall && callData && callData.name !== undefined && ( - ({String(data.name)}) + ({String(callData.name)}) )}
@@ -1405,7 +1522,7 @@ function ToolEventItem({ event }: { event: ExtendedResponseStreamEvent }) {
{/* Function Calls */} - {isFunctionCall && ( + {isFunctionCall && callData && (
@@ -1413,19 +1530,19 @@ function ToolEventItem({ event }: { event: ExtendedResponseStreamEvent }) { CALL - {String(data.name || "unknown")} + {String(callData.name || "unknown")}
- {data.arguments !== undefined && ( + {callData.arguments !== undefined && (
Arguments:
-                {typeof data.arguments === "string"
-                  ? data.arguments
-                  : JSON.stringify(data.arguments, null, 1)}
+                {typeof callData.arguments === "string"
+                  ? callData.arguments
+                  : JSON.stringify(callData.arguments, null, 1)}
               
)} @@ -1433,31 +1550,35 @@ function ToolEventItem({ event }: { event: ExtendedResponseStreamEvent }) { )} {/* Function Results */} - {isFunctionResult && ( + {isFunctionResult && resultData && (
RESULT + {/* Only show status badge for non-completed states (errors/incomplete) */} + {resultData.status !== "completed" && ( + + {resultData.status} + + )}
-
- Result: -
-              {typeof data.result === "string"
-                ? data.result
-                : JSON.stringify(data.result, null, 1)}
-            
-
- - {data.exception !== null && data.exception !== undefined && ( -
- - Error: {String(data.exception)} +
+
+ Call ID: + + {resultData.call_id}
- )} +
+ Output: +
+                {resultData.output}
+              
+
+
)}
@@ -1470,9 +1591,9 @@ export function DebugPanel({ onClose, }: DebugPanelProps) { return ( -
- -
+
+ +
Events @@ -1497,15 +1618,15 @@ export function DebugPanel({ )}
- + - + - +
diff --git a/python/packages/devui/frontend/src/components/layout/deployment-modal.tsx b/python/packages/devui/frontend/src/components/layout/deployment-modal.tsx new file mode 100644 index 0000000000..0f1583c181 --- /dev/null +++ b/python/packages/devui/frontend/src/components/layout/deployment-modal.tsx @@ -0,0 +1,519 @@ +/** + * DeploymentModal - Shows Azure deployment instructions and Docker templates + * Features: Docker setup files, Azure Container Apps deployment guide + */ + +import { useState, useEffect, useRef } from "react"; +import { + Dialog, + DialogContent, + DialogHeader, + DialogTitle, + DialogClose, +} from "@/components/ui/dialog"; +import { Button } from "@/components/ui/button"; +import { ScrollArea } from "@/components/ui/scroll-area"; +import { + Rocket, + Container, + Cloud, + Copy, + CheckCircle2, + ExternalLink, +} from "lucide-react"; + +interface DeploymentModalProps { + open: boolean; + onClose: () => void; + agentName?: string; +} + +type Tab = "docker" | "azure"; + +export function DeploymentModal({ + open, + onClose, + agentName = "Agent", +}: DeploymentModalProps) { + const [activeTab, setActiveTab] = useState("docker"); + const [copiedTemplate, setCopiedTemplate] = useState(null); + const timeoutRef = useRef(null); + + // Cleanup timeout on unmount + useEffect(() => { + return () => { + if (timeoutRef.current) { + clearTimeout(timeoutRef.current); + } + }; + }, []); + + const handleCopy = async (template: string, templateName: string) => { + try { + await navigator.clipboard.writeText(template); + setCopiedTemplate(templateName); + + // Clear any existing timeout + if (timeoutRef.current) { + clearTimeout(timeoutRef.current); + } + + // Set new timeout with cleanup + timeoutRef.current = setTimeout(() => { + setCopiedTemplate(null); + timeoutRef.current = null; + }, 2000); + } catch (err) { + console.error("Failed to copy template:", err); + // Reset state on error + setCopiedTemplate(null); + } + }; + + const dockerfileTemplate = `# Dockerfile for ${agentName} +FROM python:3.11-slim + +WORKDIR /app + +# Install dependencies +COPY requirements.txt . +RUN pip install --no-cache-dir -r requirements.txt + +# Copy agent/workflow directories +COPY . . + +# Expose DevUI default port +EXPOSE 8080 + +# Run DevUI server +CMD ["devui", ".", "--port", "8080", "--host", "0.0.0.0"] +`; + + const dockerComposeTemplate = `# docker-compose.yml +version: '3.8' + +services: + ${agentName.toLowerCase().replace(/\s+/g, "-")}: + build: . + environment: + # OpenAI + - OPENAI_API_KEY=\${OPENAI_API_KEY} + - OPENAI_CHAT_MODEL_ID=\${OPENAI_CHAT_MODEL_ID:-gpt-4o-mini} + # Or Azure OpenAI + - AZURE_OPENAI_API_KEY=\${AZURE_OPENAI_API_KEY} + - AZURE_OPENAI_ENDPOINT=\${AZURE_OPENAI_ENDPOINT} + - AZURE_OPENAI_CHAT_DEPLOYMENT_NAME=\${AZURE_OPENAI_CHAT_DEPLOYMENT_NAME} + # Optional: Enable tracing + - ENABLE_OTEL=\${ENABLE_OTEL:-false} + ports: + - "8080:8080" + restart: unless-stopped +`; + + const requirementsTemplate = `# requirements.txt +agent-framework-devui>=0.1.0 +agent-framework>=0.1.0 +# Chat clients (install what you need) +openai>=1.0.0 +# azure-openai +# anthropic +`; + + return ( + + + + + + + Deploy {agentName} + +

+ Get started with containerizing your agent for deployment. +

+
+ + {/* Tabs */} +
+ + +
+ + {/* Tab Content */} +
+ +
+ {activeTab === "docker" && ( +
+
+

+ Containerize with Docker +

+

+ Package your agent as a Docker container for consistent + deployment anywhere. +

+
+ + {/* Dockerfile */} +
+
+ Dockerfile + +
+
+                      {dockerfileTemplate}
+                    
+
+ + {/* docker-compose.yml */} +
+
+ + docker-compose.yml + + +
+
+                      {dockerComposeTemplate}
+                    
+
+ + {/* requirements.txt */} +
+
+ + requirements.txt + + +
+
+                      {requirementsTemplate}
+                    
+
+ + {/* Quick Start */} +
+

Quick Start

+
    +
  1. Save the files above to your project directory
  2. +
  3. + Build:{" "} + + docker build -t {agentName.toLowerCase()}-agent . + +
  4. +
  5. + Run:{" "} + + docker-compose up + +
  6. +
  7. Your agent is now running in a container!
  8. +
+
+ + {/* Production Warnings */} +
+

+ ⚠️ Production Considerations +

+
    +
  • + In-memory state: Conversations are lost + when container restarts +
  • +
  • + No authentication: Add reverse proxy + (nginx, Caddy) with auth for production +
  • +
  • + Security: Use Azure Key Vault for + secrets management +
  • +
  • + Scaling: Single instance only due to + in-memory conversation store +
  • +
+
+ + {/* Deployment Checklist */} +
+

+ Pre-Deployment Checklist +

+
+
+ + + Set environment variables (API keys, secrets) + +
+
+ + + Test agent locally in container + +
+
+ + + Configure logging and monitoring + +
+
+ + + Set up error handling and retries + +
+
+
+
+ )} + + {activeTab === "azure" && ( +
+
+

+ Deploy to Azure Container Apps +

+

+ Azure Container Apps provides serverless containers with + auto-scaling and integrated monitoring. +

+
+ + {/* Prerequisites */} +
+

Prerequisites

+
    +
  • Azure subscription
  • +
  • + Azure CLI installed ( + + az --version + + ) +
  • +
  • Docker installed and running
  • +
  • + Logged in to Azure:{" "} + az login +
  • +
+
+ + {/* Step-by-step */} +
+

Deployment Steps

+ +
+ {/* Step 1 */} +
+
+
+ 1 +
+
+ Create Azure Container Registry +
+
+
+                          {`# Create resource group
+az group create --name myResourceGroup --location eastus
+
+# Create container registry
+az acr create --resource-group myResourceGroup \\
+  --name myregistry --sku Basic`}
+                        
+
+ + {/* Step 2 */} +
+
+
+ 2 +
+
+ Build and Push Docker Image +
+
+
+                          {`# Build and push in one command
+az acr build --registry myregistry \\
+  --image ${agentName.toLowerCase()}-agent:latest .`}
+                        
+
+ + {/* Step 3 */} +
+
+
+ 3 +
+
+ Create Container Apps Environment +
+
+
+                          {`az containerapp env create --name myEnvironment \\
+  --resource-group myResourceGroup \\
+  --location eastus`}
+                        
+
+ + {/* Step 4 */} +
+
+
+ 4 +
+
+ Deploy Container App +
+
+
+                          {`az containerapp create --name ${agentName.toLowerCase()}-app \\
+  --resource-group myResourceGroup \\
+  --environment myEnvironment \\
+  --image myregistry.azurecr.io/${agentName.toLowerCase()}-agent:latest \\
+  --target-port 8080 \\
+  --ingress 'external' \\
+  --registry-server myregistry.azurecr.io \\
+  --env-vars OPENAI_API_KEY=secretref:openai-key OPENAI_CHAT_MODEL_ID=gpt-4o-mini`}
+                        
+
+ + {/* Step 5 */} +
+
+
+ 5 +
+
+ Get Application URL +
+
+
+                          {`az containerapp show --name ${agentName.toLowerCase()}-app \\
+  --resource-group myResourceGroup \\
+  --query properties.configuration.ingress.fqdn`}
+                        
+
+
+
+ + {/* Learn More */} +
+

Learn More

+

+ Explore Azure Container Apps documentation for advanced + features like scaling, monitoring, and CI/CD integration. +

+ +
+
+ )} +
+
+
+
+
+ ); +} diff --git a/python/packages/devui/frontend/src/components/layout/entity-selector.tsx b/python/packages/devui/frontend/src/components/layout/entity-selector.tsx index fbcfe5717e..b30c1e27b5 100644 --- a/python/packages/devui/frontend/src/components/layout/entity-selector.tsx +++ b/python/packages/devui/frontend/src/components/layout/entity-selector.tsx @@ -1,6 +1,6 @@ /** - * EntitySelector - High-quality dropdown for selecting agents/workflows - * Features: Type indicators, tool counts, keyboard navigation, search + * EntitySelector - Dropdown for selecting agents/workflows + * Features: Loading states, descriptions, lazy loading indicators */ import { useState } from "react"; @@ -13,9 +13,8 @@ import { DropdownMenuSeparator, DropdownMenuTrigger, } from "@/components/ui/dropdown-menu"; -import { Badge } from "@/components/ui/badge"; import { LoadingSpinner } from "@/components/ui/loading-spinner"; -import { ChevronDown, Bot, Workflow, FolderOpen, Database, Globe, X, Plus } from "lucide-react"; +import { ChevronDown, Bot, Workflow, Plus, Loader2 } from "lucide-react"; import type { AgentInfo, WorkflowInfo } from "@/types"; interface EntitySelectorProps { @@ -23,7 +22,6 @@ interface EntitySelectorProps { workflows: WorkflowInfo[]; selectedItem?: AgentInfo | WorkflowInfo; onSelect: (item: AgentInfo | WorkflowInfo) => void; - onRemove?: (entityId: string) => void; onBrowseGallery?: () => void; isLoading?: boolean; } @@ -32,30 +30,11 @@ const getTypeIcon = (type: "agent" | "workflow") => { return type === "workflow" ? Workflow : Bot; }; -const getSourceIcon = (source: "directory" | "in_memory" | "remote_gallery") => { - switch (source) { - case "directory": return FolderOpen; - case "in_memory": return Database; - case "remote_gallery": return Globe; - default: return Database; - } -}; - -const getSourceLabel = (source: "directory" | "in_memory" | "remote_gallery") => { - switch (source) { - case "directory": return "Local"; - case "in_memory": return "Memory"; - case "remote_gallery": return "Gallery"; - default: return "Unknown"; - } -}; - export function EntitySelector({ agents, workflows, selectedItem, onSelect, - onRemove, onBrowseGallery, isLoading = false, }: EntitySelectorProps) { @@ -72,11 +51,7 @@ export function EntitySelector({ const TypeIcon = selectedItem ? getTypeIcon(selectedItem.type) : Bot; const displayName = selectedItem?.name || selectedItem?.id || "Select Agent or Workflow"; - const itemCount = - selectedItem?.type === "workflow" - ? (selectedItem as WorkflowInfo).executors?.length || 0 - : (selectedItem as AgentInfo)?.tools?.length || 0; - const itemLabel = selectedItem?.type === "workflow" ? "executors" : "tools"; + const isLoaded = selectedItem?.metadata?.lazy_loaded !== false; return ( @@ -96,10 +71,8 @@ export function EntitySelector({
{displayName} - {selectedItem && ( - - {itemCount} {itemLabel} - + {selectedItem && !isLoaded && ( + )}
@@ -116,53 +89,29 @@ export function EntitySelector({ Agents ({agents.length}) {agents.map((agent) => { - const SourceIcon = getSourceIcon(agent.source); + const isAgentLoaded = agent.metadata?.lazy_loaded !== false; return ( -
+
handleSelect(agent)} > -
-
+
+ {agent.name || agent.id} -
- {agent.description && ( -
+ + {isAgentLoaded && agent.description && ( +
{agent.description}
)}
-
- - - {getSourceLabel(agent.source)} - - - {agent.tools.length} - - - {/* Remove button for gallery entities */} - {agent.source === 'remote_gallery' && onRemove && ( - - )} -
); @@ -178,53 +127,29 @@ export function EntitySelector({ Workflows ({workflows.length}) {workflows.map((workflow) => { - const SourceIcon = getSourceIcon(workflow.source); + const isWorkflowLoaded = workflow.metadata?.lazy_loaded !== false; return ( -
+
handleSelect(workflow)} > -
-
+
+ {workflow.name || workflow.id} -
- {workflow.description && ( -
+ + {isWorkflowLoaded && workflow.description && ( +
{workflow.description}
)}
-
- - - {getSourceLabel(workflow.source)} - - - {workflow.executors.length} - - - {/* Remove button for gallery entities */} - {workflow.source === 'remote_gallery' && onRemove && ( - - )} -
); diff --git a/python/packages/devui/frontend/src/components/layout/index.ts b/python/packages/devui/frontend/src/components/layout/index.ts index 48499a8347..9391883b12 100644 --- a/python/packages/devui/frontend/src/components/layout/index.ts +++ b/python/packages/devui/frontend/src/components/layout/index.ts @@ -7,3 +7,4 @@ export { EntitySelector } from "./entity-selector"; export { DebugPanel } from "./debug-panel"; export { SettingsModal } from "./settings-modal"; export { AboutModal } from "./about-modal"; +export { DeploymentModal } from "./deployment-modal"; diff --git a/python/packages/devui/frontend/src/components/layout/settings-modal.tsx b/python/packages/devui/frontend/src/components/layout/settings-modal.tsx index c2a56401ce..3caf7af80b 100644 --- a/python/packages/devui/frontend/src/components/layout/settings-modal.tsx +++ b/python/packages/devui/frontend/src/components/layout/settings-modal.tsx @@ -24,7 +24,7 @@ interface SettingsModalProps { type Tab = "about" | "settings"; export function SettingsModal({ open, onOpenChange, onBackendUrlChange }: SettingsModalProps) { - const [activeTab, setActiveTab] = useState("about"); + const [activeTab, setActiveTab] = useState("settings"); // Get current backend URL from localStorage or default const defaultUrl = import.meta.env.VITE_API_BASE_URL || "http://localhost:8080"; @@ -73,19 +73,6 @@ export function SettingsModal({ open, onOpenChange, onBackendUrlChange }: Settin {/* Tabs */}
- +
{/* Tab Content */}
- {activeTab === "about" && ( -
-

- DevUI is a sample app for getting started with Agent Framework. -

- -
- -
-
- )} - {activeTab === "settings" && (
{/* Backend URL Setting */} @@ -188,6 +163,31 @@ export function SettingsModal({ open, onOpenChange, onBackendUrlChange }: Settin
)} + + {activeTab === "about" && ( +
+

+ DevUI is a sample app for getting started with Agent Framework. +

+ +
+ +
+
+ )}
diff --git a/python/packages/devui/frontend/src/components/ui/alert.tsx b/python/packages/devui/frontend/src/components/ui/alert.tsx new file mode 100644 index 0000000000..70107d040d --- /dev/null +++ b/python/packages/devui/frontend/src/components/ui/alert.tsx @@ -0,0 +1,48 @@ +/** + * Alert component - Simple alert/callout component + */ + +import * as React from "react"; +import { cn } from "@/lib/utils"; + +const Alert = React.forwardRef< + HTMLDivElement, + React.HTMLAttributes +>(({ className, ...props }, ref) => ( +
svg~*]:pl-7 [&>svg+div]:translate-y-[-3px] [&>svg]:absolute [&>svg]:left-4 [&>svg]:top-4 [&>svg]:text-foreground", + className + )} + {...props} + /> +)); +Alert.displayName = "Alert"; + +const AlertTitle = React.forwardRef< + HTMLParagraphElement, + React.HTMLAttributes +>(({ className, ...props }, ref) => ( +
+)); +AlertTitle.displayName = "AlertTitle"; + +const AlertDescription = React.forwardRef< + HTMLParagraphElement, + React.HTMLAttributes +>(({ className, ...props }, ref) => ( +
+)); +AlertDescription.displayName = "AlertDescription"; + +export { Alert, AlertTitle, AlertDescription }; diff --git a/python/packages/devui/frontend/src/components/ui/markdown-renderer.tsx b/python/packages/devui/frontend/src/components/ui/markdown-renderer.tsx new file mode 100644 index 0000000000..952dbc1aae --- /dev/null +++ b/python/packages/devui/frontend/src/components/ui/markdown-renderer.tsx @@ -0,0 +1,565 @@ +/** + * Lightweight Markdown Renderer + * + * A minimal markdown renderer with zero dependencies for rendering LLM responses. + * Handles the most common markdown patterns without bloating bundle size. + * + * Supported syntax: + * - **bold** and __bold__ + * - *italic* and _italic_ + * - `inline code` + * - ```code blocks``` (with copy button on hover) + * - [links](url) + * - **[bold links](url)** and *[italic links](url)* + * - # Headers (H1-H6) + * - Lists (ordered and unordered) + * - > Blockquotes + * - Tables (| col1 | col2 |) + * - Horizontal rules (---) + */ + +import React, { useState } from "react"; + +interface MarkdownRendererProps { + content: string; + className?: string; +} + +interface CodeBlockProps { + code: string; + language?: string; +} + +/** + * Code block component with copy button + */ +function CodeBlock({ code, language }: CodeBlockProps) { + const [copied, setCopied] = useState(false); + const timeoutRef = React.useRef(null); + + // Cleanup timeout on unmount + React.useEffect(() => { + return () => { + if (timeoutRef.current) { + clearTimeout(timeoutRef.current); + } + }; + }, []); + + const handleCopy = async () => { + try { + await navigator.clipboard.writeText(code); + setCopied(true); + + // Clear any existing timeout + if (timeoutRef.current) { + clearTimeout(timeoutRef.current); + } + + // Set new timeout and store reference + timeoutRef.current = setTimeout(() => { + setCopied(false); + timeoutRef.current = null; + }, 2000); + } catch (err) { + console.error("Failed to copy code:", err); + } + }; + + return ( +
+
+        
+          {language && (
+            
+              {language}
+            
+          )}
+          {code}
+        
+      
+ +
+ ); +} + +/** + * Parse markdown text into React elements + */ +export function MarkdownRenderer({ + content, + className = "", +}: MarkdownRendererProps) { + const lines = content.split("\n"); + const elements: React.ReactNode[] = []; + let i = 0; + + while (i < lines.length) { + const line = lines[i]; + + // Code blocks (multiline) + if (line.trim().startsWith("```")) { + const codeLines: string[] = []; + const langMatch = line.trim().match(/^```(\w+)?/); + const language = langMatch?.[1] || ""; + i++; // Skip opening ``` + + while (i < lines.length && !lines[i].trim().startsWith("```")) { + codeLines.push(lines[i]); + i++; + } + i++; // Skip closing ``` + + elements.push( + + ); + continue; + } + + // Headers + const headerMatch = line.match(/^(#{1,6})\s+(.+)$/); + if (headerMatch) { + const level = headerMatch[1].length; + const text = headerMatch[2]; + const sizes = [ + "text-2xl", + "text-xl", + "text-lg", + "text-base", + "text-sm", + "text-sm", + ]; + const className = `${ + sizes[level - 1] + } font-semibold mt-4 mb-2 first:mt-0 break-words`; + + // Render appropriate header level + const header = + level === 1 ? ( +

+ {parseInlineMarkdown(text)} +

+ ) : level === 2 ? ( +

+ {parseInlineMarkdown(text)} +

+ ) : level === 3 ? ( +

+ {parseInlineMarkdown(text)} +

+ ) : level === 4 ? ( +

+ {parseInlineMarkdown(text)} +

+ ) : level === 5 ? ( +
+ {parseInlineMarkdown(text)} +
+ ) : ( +
+ {parseInlineMarkdown(text)} +
+ ); + + elements.push(header); + i++; + continue; + } + + // Unordered lists + if (line.match(/^[\s]*[-*+]\s+/)) { + const listItems: string[] = []; + + while (i < lines.length && lines[i].match(/^[\s]*[-*+]\s+/)) { + const itemText = lines[i].replace(/^[\s]*[-*+]\s+/, ""); + listItems.push(itemText); + i++; + } + + elements.push( +
    + {listItems.map((item, idx) => ( +
  • + {parseInlineMarkdown(item)} +
  • + ))} +
+ ); + continue; + } + + // Ordered lists + if (line.match(/^[\s]*\d+\.\s+/)) { + const listItems: string[] = []; + + while (i < lines.length && lines[i].match(/^[\s]*\d+\.\s+/)) { + const itemText = lines[i].replace(/^[\s]*\d+\.\s+/, ""); + listItems.push(itemText); + i++; + } + + elements.push( +
    + {listItems.map((item, idx) => ( +
  1. + {parseInlineMarkdown(item)} +
  2. + ))} +
+ ); + continue; + } + + // Tables + if (line.trim().startsWith("|") && line.trim().endsWith("|")) { + const tableLines: string[] = []; + + // Collect all table lines + while ( + i < lines.length && + lines[i].trim().startsWith("|") && + lines[i].trim().endsWith("|") + ) { + tableLines.push(lines[i].trim()); + i++; + } + + // Parse table (need at least 2 lines: header + separator) + if (tableLines.length >= 2) { + const headerCells = tableLines[0] + .split("|") + .slice(1, -1) + .map((cell) => cell.trim()); + + // Check if second line is a separator (contains dashes) + const isSeparator = tableLines[1].match(/^\|[\s\-:|]+\|$/); + + if (isSeparator) { + const bodyRows = tableLines.slice(2).map((row) => + row + .split("|") + .slice(1, -1) + .map((cell) => cell.trim()) + ); + + elements.push( +
+ + + + {headerCells.map((header, idx) => ( + + ))} + + + + {bodyRows.map((row, rowIdx) => ( + + {row.map((cell, cellIdx) => ( + + ))} + + ))} + +
+ {parseInlineMarkdown(header)} +
+ {parseInlineMarkdown(cell)} +
+
+ ); + continue; + } + } + + // Not a valid table, render as regular paragraphs + for (const tableLine of tableLines) { + elements.push( +

+ {parseInlineMarkdown(tableLine)} +

+ ); + } + continue; + } + + // Blockquotes + if (line.trim().startsWith(">")) { + const quoteLines: string[] = []; + + while (i < lines.length && lines[i].trim().startsWith(">")) { + quoteLines.push(lines[i].replace(/^>\s?/, "")); + i++; + } + + elements.push( +
+ {quoteLines.map((quoteLine, idx) => ( +
+ {parseInlineMarkdown(quoteLine)} +
+ ))} +
+ ); + continue; + } + + // Horizontal rule + if (line.match(/^[\s]*[-*_]{3,}[\s]*$/)) { + elements.push( +
+ ); + i++; + continue; + } + + // Empty line + if (line.trim() === "") { + elements.push(
); + i++; + continue; + } + + // Regular paragraph + elements.push( +

+ {parseInlineMarkdown(line)} +

+ ); + i++; + } + + return ( +
+ {elements} +
+ ); +} + +/** + * Parse inline markdown patterns (bold, italic, code, links) + */ +function parseInlineMarkdown(text: string): React.ReactNode[] { + const parts: React.ReactNode[] = []; + let remaining = text; + let key = 0; + + // Pattern priority: code > bold > italic > links + // This prevents conflicts between overlapping patterns + + while (remaining.length > 0) { + // Inline code (highest priority to avoid parsing inside code) + const codeMatch = remaining.match(/`([^`]+)`/); + if (codeMatch && codeMatch.index !== undefined) { + // Add text before code + if (codeMatch.index > 0) { + parts.push( + + {parseBoldItalicLinks(remaining.slice(0, codeMatch.index))} + + ); + } + + // Add code + parts.push( + + {codeMatch[1]} + + ); + + remaining = remaining.slice(codeMatch.index + codeMatch[0].length); + continue; + } + + // No more special patterns, parse remaining text for bold/italic/links + parts.push({parseBoldItalicLinks(remaining)}); + break; + } + + return parts; +} + +/** + * Parse bold, italic, and links (after code has been extracted) + */ +function parseBoldItalicLinks(text: string): React.ReactNode[] { + const parts: React.ReactNode[] = []; + let remaining = text; + let key = 0; + + while (remaining.length > 0) { + // Try to match patterns in order + // IMPORTANT: Handle **[link](url)** pattern first (bold markers around link) + const patterns = [ + { regex: /\*\*\[([^\]]+)\]\(([^)]+)\)\*\*/, component: "strong-link" }, // **[text](url)** + { regex: /__\[([^\]]+)\]\(([^)]+)\)__/, component: "strong-link" }, // __[text](url)__ + { regex: /\*\[([^\]]+)\]\(([^)]+)\)\*/, component: "em-link" }, // *[text](url)* + { regex: /_\[([^\]]+)\]\(([^)]+)\)_/, component: "em-link" }, // _[text](url)_ + { regex: /\[([^\]]+)\]\(([^)]+)\)/, component: "link" }, // [text](url) + { regex: /\*\*(.+?)\*\*/, component: "strong" }, // **bold** + { regex: /__(.+?)__/, component: "strong" }, // __bold__ + { regex: /\*(.+?)\*/, component: "em" }, // *italic* + { regex: /_(.+?)_/, component: "em" }, // _italic_ + ]; + + let matched = false; + + for (const pattern of patterns) { + const match = remaining.match(pattern.regex); + + if (match && match.index !== undefined) { + // Add text before match + if (match.index > 0) { + parts.push(remaining.slice(0, match.index)); + } + + // Add matched element + if (pattern.component === "strong") { + parts.push( + + {match[1]} + + ); + } else if (pattern.component === "em") { + parts.push( + + {match[1]} + + ); + } else if (pattern.component === "strong-link") { + // **[text](url)** - Bold link + const linkText = match[1]; + const linkUrl = match[2]; + const formattedLinkText = parseBoldItalicLinks(linkText); + + parts.push( + + + {formattedLinkText} + + + ); + } else if (pattern.component === "em-link") { + // *[text](url)* - Italic link + const linkText = match[1]; + const linkUrl = match[2]; + const formattedLinkText = parseBoldItalicLinks(linkText); + + parts.push( + + + {formattedLinkText} + + + ); + } else if (pattern.component === "link") { + // [text](url) - Regular link + const linkText = match[1]; + const linkUrl = match[2]; + const formattedLinkText = parseBoldItalicLinks(linkText); + + parts.push( + + {formattedLinkText} + + ); + } + + remaining = remaining.slice(match.index + match[0].length); + matched = true; + break; + } + } + + // No pattern matched, add remaining text and exit + if (!matched) { + if (remaining.length > 0) { + parts.push(remaining); + } + break; + } + } + + return parts; +} diff --git a/python/packages/devui/frontend/src/services/api.ts b/python/packages/devui/frontend/src/services/api.ts index 5822a14b85..c94920222e 100644 --- a/python/packages/devui/frontend/src/services/api.ts +++ b/python/packages/devui/frontend/src/services/api.ts @@ -26,7 +26,6 @@ interface BackendEntityInfo { tools?: (string | Record)[]; metadata: Record; source?: string; - original_url?: string; // Agent-specific fields (present when type === "agent") instructions?: string; model?: string; @@ -143,6 +142,7 @@ class ApiClient { typeof entity.metadata?.module_path === "string" ? entity.metadata.module_path : undefined, + metadata: entity.metadata, // Preserve metadata including lazy_loaded flag // Agent-specific fields instructions: entity.instructions, model: entity.model, @@ -168,6 +168,7 @@ class ApiClient { typeof entity.metadata?.module_path === "string" ? entity.metadata.module_path : undefined, + metadata: entity.metadata, // Preserve metadata including lazy_loaded flag input_schema: (entity.input_schema as unknown as import("@/types").JSONSchema) || { type: "string", @@ -504,31 +505,6 @@ class ApiClient { body: JSON.stringify(request), }); } - - // Add entity from URL - async addEntity(url: string, metadata?: Record): Promise { - const response = await this.request<{ success: boolean; entity: BackendEntityInfo }>("/v1/entities/add", { - method: "POST", - body: JSON.stringify({ url, metadata }), - }); - - if (!response.success || !response.entity) { - throw new Error("Failed to add entity"); - } - - return response.entity; - } - - // Remove entity by ID - async removeEntity(entityId: string): Promise { - const response = await this.request<{ success: boolean }>(`/v1/entities/${entityId}`, { - method: "DELETE", - }); - - if (!response.success) { - throw new Error("Failed to remove entity"); - } - } } // Export singleton instance diff --git a/python/packages/devui/frontend/src/stores/devuiStore.ts b/python/packages/devui/frontend/src/stores/devuiStore.ts new file mode 100644 index 0000000000..f9cb5ec0ce --- /dev/null +++ b/python/packages/devui/frontend/src/stores/devuiStore.ts @@ -0,0 +1,309 @@ +/** + * DevUI Unified Store - Single source of truth for all app state + * Organized into logical slices: entity, conversation, UI, gallery, modals + */ + +import { create } from "zustand"; +import { devtools, persist } from "zustand/middleware"; +import type { + AgentInfo, + WorkflowInfo, + ExtendedResponseStreamEvent, + Conversation, + PendingApproval, +} from "@/types"; +import type { ConversationItem } from "@/types/openai"; +import type { AttachmentItem } from "@/components/ui/attachment-gallery"; + +// ======================================== +// State Interface +// ======================================== + +interface DevUIState { + // Entity Management Slice + agents: AgentInfo[]; + workflows: WorkflowInfo[]; + selectedAgent: AgentInfo | WorkflowInfo | undefined; + isLoadingEntities: boolean; + entityError: string | null; + + // Conversation Slice (per-agent state) + currentConversation: Conversation | undefined; + availableConversations: Conversation[]; + chatItems: ConversationItem[]; + isStreaming: boolean; + isSubmitting: boolean; + loadingConversations: boolean; + inputValue: string; + attachments: AttachmentItem[]; + conversationUsage: { + total_tokens: number; + message_count: number; + }; + pendingApprovals: PendingApproval[]; + + // UI Slice + showDebugPanel: boolean; + debugPanelWidth: number; + debugEvents: ExtendedResponseStreamEvent[]; + isResizing: boolean; + + // Modal Slice + showAboutModal: boolean; + showGallery: boolean; + showDeployModal: boolean; + showEntityNotFoundToast: boolean; +} + +// ======================================== +// Actions Interface +// ======================================== + +interface DevUIActions { + // Entity Actions + setAgents: (agents: AgentInfo[]) => void; + setWorkflows: (workflows: WorkflowInfo[]) => void; + setSelectedAgent: (agent: AgentInfo | WorkflowInfo | undefined) => void; + addAgent: (agent: AgentInfo) => void; + addWorkflow: (workflow: WorkflowInfo) => void; + updateAgent: (agent: AgentInfo) => void; + updateWorkflow: (workflow: WorkflowInfo) => void; + removeEntity: (entityId: string) => void; + setEntityError: (error: string | null) => void; + setIsLoadingEntities: (loading: boolean) => void; + + // Conversation Actions + setCurrentConversation: (conv: Conversation | undefined) => void; + setAvailableConversations: (convs: Conversation[]) => void; + setChatItems: (items: ConversationItem[]) => void; + setIsStreaming: (streaming: boolean) => void; + setIsSubmitting: (submitting: boolean) => void; + setLoadingConversations: (loading: boolean) => void; + setInputValue: (value: string) => void; + setAttachments: (files: AttachmentItem[]) => void; + updateConversationUsage: (tokens: number) => void; + setPendingApprovals: (approvals: PendingApproval[]) => void; + + // UI Actions + setShowDebugPanel: (show: boolean) => void; + setDebugPanelWidth: (width: number) => void; + addDebugEvent: (event: ExtendedResponseStreamEvent) => void; + clearDebugEvents: () => void; + setIsResizing: (resizing: boolean) => void; + + // Modal Actions + setShowAboutModal: (show: boolean) => void; + setShowGallery: (show: boolean) => void; + setShowDeployModal: (show: boolean) => void; + setShowEntityNotFoundToast: (show: boolean) => void; + + // Combined Actions (handle multiple state updates + side effects) + selectEntity: (entity: AgentInfo | WorkflowInfo) => void; +} + +type DevUIStore = DevUIState & DevUIActions; + +// ======================================== +// Store Implementation +// ======================================== + +export const useDevUIStore = create()( + devtools( + persist( + (set) => ({ + // ======================================== + // Initial State + // ======================================== + + // Entity State + agents: [], + workflows: [], + selectedAgent: undefined, + isLoadingEntities: true, + entityError: null, + + // Conversation State + currentConversation: undefined, + availableConversations: [], + chatItems: [], + isStreaming: false, + isSubmitting: false, + loadingConversations: false, + inputValue: "", + attachments: [], + conversationUsage: { total_tokens: 0, message_count: 0 }, + pendingApprovals: [], + + // UI State + showDebugPanel: true, + debugPanelWidth: 320, + debugEvents: [], + isResizing: false, + + // Modal State + showAboutModal: false, + showGallery: false, + showDeployModal: false, + showEntityNotFoundToast: false, + + // ======================================== + // Entity Actions + // ======================================== + + setAgents: (agents) => set({ agents }), + setWorkflows: (workflows) => set({ workflows }), + setSelectedAgent: (agent) => set({ selectedAgent: agent }), + addAgent: (agent) => + set((state) => ({ agents: [...state.agents, agent] })), + addWorkflow: (workflow) => + set((state) => ({ workflows: [...state.workflows, workflow] })), + updateAgent: (updatedAgent) => + set((state) => ({ + agents: state.agents.map((a) => + a.id === updatedAgent.id ? updatedAgent : a + ), + // Also update selectedAgent if it's the same one + selectedAgent: + state.selectedAgent?.id === updatedAgent.id && + state.selectedAgent.type === "agent" + ? updatedAgent + : state.selectedAgent, + })), + updateWorkflow: (updatedWorkflow) => + set((state) => ({ + workflows: state.workflows.map((w) => + w.id === updatedWorkflow.id ? updatedWorkflow : w + ), + // Also update selectedAgent if it's the same one + selectedAgent: + state.selectedAgent?.id === updatedWorkflow.id && + state.selectedAgent.type === "workflow" + ? updatedWorkflow + : state.selectedAgent, + })), + removeEntity: (entityId) => + set((state) => ({ + agents: state.agents.filter((a) => a.id !== entityId), + workflows: state.workflows.filter((w) => w.id !== entityId), + selectedAgent: + state.selectedAgent?.id === entityId + ? undefined + : state.selectedAgent, + })), + setEntityError: (error) => set({ entityError: error }), + setIsLoadingEntities: (loading) => set({ isLoadingEntities: loading }), + + // ======================================== + // Conversation Actions + // ======================================== + + setCurrentConversation: (conv) => set({ currentConversation: conv }), + setAvailableConversations: (convs) => + set({ availableConversations: convs }), + setChatItems: (items) => set({ chatItems: items }), + setIsStreaming: (streaming) => set({ isStreaming: streaming }), + setIsSubmitting: (submitting) => set({ isSubmitting: submitting }), + setLoadingConversations: (loading) => + set({ loadingConversations: loading }), + setInputValue: (value) => set({ inputValue: value }), + setAttachments: (files) => set({ attachments: files }), + updateConversationUsage: (tokens) => + set((state) => ({ + conversationUsage: { + total_tokens: state.conversationUsage.total_tokens + tokens, + message_count: state.conversationUsage.message_count + 1, + }, + })), + setPendingApprovals: (approvals) => set({ pendingApprovals: approvals }), + + // ======================================== + // UI Actions + // ======================================== + + setShowDebugPanel: (show) => set({ showDebugPanel: show }), + setDebugPanelWidth: (width) => set({ debugPanelWidth: width }), + addDebugEvent: (event) => + set((state) => ({ debugEvents: [...state.debugEvents, event] })), + clearDebugEvents: () => set({ debugEvents: [] }), + setIsResizing: (resizing) => set({ isResizing: resizing }), + + // ======================================== + // Modal Actions + // ======================================== + + setShowAboutModal: (show) => set({ showAboutModal: show }), + setShowGallery: (show) => set({ showGallery: show }), + setShowDeployModal: (show) => set({ showDeployModal: show }), + setShowEntityNotFoundToast: (show) => + set({ showEntityNotFoundToast: show }), + + // ======================================== + // Combined Actions + // ======================================== + + /** + * Select an entity (agent/workflow) and handle all side effects: + * - Update selected entity + * - Clear conversation state (FIXES THE BUG!) + * - Clear debug events + * - Update URL + */ + selectEntity: (entity) => { + set({ + selectedAgent: entity, + // CRITICAL: Clear all conversation state when switching entities + currentConversation: undefined, + availableConversations: [], // Let AgentView reload conversations + chatItems: [], + inputValue: "", + attachments: [], + conversationUsage: { total_tokens: 0, message_count: 0 }, + isStreaming: false, + isSubmitting: false, + pendingApprovals: [], + // Clear debug events when switching + debugEvents: [], + }); + + // Update URL with selected entity ID + const url = new URL(window.location.href); + url.searchParams.set("entity_id", entity.id); + window.history.pushState({}, "", url); + }, + }), + { + name: "devui-storage", + // Only persist UI preferences, not runtime state + partialize: (state) => ({ + showDebugPanel: state.showDebugPanel, + debugPanelWidth: state.debugPanelWidth, + }), + } + ), + { name: "DevUI Store" } + ) +); + +// ======================================== +// Usage Notes +// ======================================== + +/** + * How to use the store: + * + * 1. For state access, use direct selectors: + * const agents = useDevUIStore((state) => state.agents); + * + * 2. For actions, extract them: + * const setAgents = useDevUIStore((state) => state.setAgents); + * + * 3. For combined state access (use sparingly, can cause unnecessary re-renders): + * const { agents, workflows } = useDevUIStore((state) => ({ + * agents: state.agents, + * workflows: state.workflows + * })); + * + * 4. To access state outside React components: + * useDevUIStore.getState().agents + * useDevUIStore.getState().setAgents([...]) + */ diff --git a/python/packages/devui/frontend/src/stores/index.ts b/python/packages/devui/frontend/src/stores/index.ts new file mode 100644 index 0000000000..e0350506bb --- /dev/null +++ b/python/packages/devui/frontend/src/stores/index.ts @@ -0,0 +1,5 @@ +/** + * Store exports - single entry point for all store hooks + */ + +export { useDevUIStore } from "./devuiStore"; diff --git a/python/packages/devui/frontend/src/types/index.ts b/python/packages/devui/frontend/src/types/index.ts index 8cbfc010eb..6d1a494cf5 100644 --- a/python/packages/devui/frontend/src/types/index.ts +++ b/python/packages/devui/frontend/src/types/index.ts @@ -31,6 +31,7 @@ export interface AgentInfo { has_env: boolean; module_path?: string; required_env_vars?: EnvVarRequirement[]; + metadata?: Record; // Backend metadata including lazy_loaded flag // Agent-specific fields instructions?: string; model?: string; @@ -103,8 +104,8 @@ export type { ResponseWorkflowEventComplete, ResponseTraceEventComplete, ResponseOutputItemAddedEvent, - ResponseFunctionResultComplete, ResponseCompletedEvent, + ResponseFunctionResultComplete, StructuredEvent, } from "./openai"; diff --git a/python/packages/devui/frontend/src/types/openai.ts b/python/packages/devui/frontend/src/types/openai.ts index 0e73f08940..9842e37bcc 100644 --- a/python/packages/devui/frontend/src/types/openai.ts +++ b/python/packages/devui/frontend/src/types/openai.ts @@ -36,17 +36,6 @@ export interface ResponseWorkflowEventComplete { sequence_number: number; } -// Custom DevUI: Function result event -// This is a DevUI extension - OpenAI doesn't stream function execution results -export interface ResponseFunctionResultComplete { - type: "response.function_result.complete"; - call_id: string; - output: string; - status: "in_progress" | "completed" | "incomplete"; - item_id: string; - output_index: number; - sequence_number: number; -} // Function call event types - matching actual backend output export interface ResponseFunctionCallComplete { @@ -173,6 +162,24 @@ export interface ResponseFunctionApprovalRespondedEvent { sequence_number: number; } +// DevUI Extension: Function Result Complete +export interface ResponseFunctionResultComplete { + type: "response.function_result.complete"; + call_id: string; + output: string; + status: "in_progress" | "completed" | "incomplete"; + item_id: string; + output_index: number; + sequence_number: number; +} + +// DevUI Extension: Turn Separator (UI-only event for grouping) +export interface TurnSeparatorEvent { + type: "debug.turn_separator"; + timestamp: string; + collapsed?: boolean; +} + // Union type for all structured events export type StructuredEvent = | ResponseCompletedEvent @@ -180,13 +187,14 @@ export type StructuredEvent = | ResponseTraceEventComplete | ResponseTraceComplete | ResponseOutputItemAddedEvent - | ResponseFunctionResultComplete | ResponseFunctionCallComplete | ResponseFunctionCallDelta | ResponseFunctionCallArgumentsDelta + | ResponseFunctionResultComplete | ResponseErrorEvent | ResponseFunctionApprovalRequestedEvent - | ResponseFunctionApprovalRespondedEvent; + | ResponseFunctionApprovalRespondedEvent + | TurnSeparatorEvent; // Extended stream event that includes our structured events export type ExtendedResponseStreamEvent = ResponseStreamEvent | StructuredEvent; diff --git a/python/packages/devui/frontend/vite.config.ts b/python/packages/devui/frontend/vite.config.ts index 904bc0456d..010f098001 100644 --- a/python/packages/devui/frontend/vite.config.ts +++ b/python/packages/devui/frontend/vite.config.ts @@ -16,19 +16,9 @@ export default defineConfig({ emptyOutDir: true, rollupOptions: { output: { - // Minimize to just 2 files: main app + CSS manualChunks: undefined, - // Ensure everything goes into a single JS file inlineDynamicImports: true, }, }, }, - // Ensure proper tree-shaking - optimizeDeps: { - include: ["lucide-react", "@xyflow/react"], - }, - // Enable aggressive tree-shaking - esbuild: { - treeShaking: true, - }, }); diff --git a/python/packages/devui/frontend/yarn.lock b/python/packages/devui/frontend/yarn.lock index 7294563dfc..7e279848c9 100644 --- a/python/packages/devui/frontend/yarn.lock +++ b/python/packages/devui/frontend/yarn.lock @@ -2063,7 +2063,7 @@ natural-compare@^1.4.0: next-themes@^0.4.6: version "0.4.6" - resolved "https://registry.npmjs.org/next-themes/-/next-themes-0.4.6.tgz" + resolved "https://registry.yarnpkg.com/next-themes/-/next-themes-0.4.6.tgz#8d7e92d03b8fea6582892a50a928c9b23502e8b6" integrity sha512-pZvgD5L0IEvX5/9GWyHMf3m8BKiVQwsCMHfoFosXtXBMnaS0ZnIJ9ST4b4NqLVKDEm8QBxoNNGNaBv2JNF6XNA== node-releases@^2.0.19: @@ -2453,3 +2453,8 @@ zustand@^4.4.0: integrity sha512-CHOUy7mu3lbD6o6LJLfllpjkzhHXSBlX8B9+qPddUsIfeF5S/UZ5q0kmCsnRqT1UHFQZchNFDDzMbQsuesHWlw== dependencies: use-sync-external-store "^1.2.2" + +zustand@^5.0.8: + version "5.0.8" + resolved "https://registry.yarnpkg.com/zustand/-/zustand-5.0.8.tgz#b998a0c088c7027a20f2709141a91cb07ac57f8a" + integrity sha512-gyPKpIaxY9XcO2vSMrLbiER7QMAMGOQZVRdJ6Zi782jkbzZygq5GI9nG8g+sMgitRtndwaBSl7uiqC49o1SSiw== diff --git a/python/packages/devui/tests/test_discovery.py b/python/packages/devui/tests/test_discovery.py index 33ef20c965..d063347a88 100644 --- a/python/packages/devui/tests/test_discovery.py +++ b/python/packages/devui/tests/test_discovery.py @@ -68,7 +68,11 @@ async def test_empty_directory(): async def test_discovery_accepts_agents_with_only_run(): - """Test that discovery accepts agents with only run() method.""" + """Test that discovery accepts agents with only run() method. + + With lazy loading, entities with only __init__.py are discovered + but marked as "unknown" type until loaded. + """ import tempfile from pathlib import Path @@ -110,13 +114,224 @@ agent = NonStreamingAgent() discovery = EntityDiscovery(str(temp_path)) entities = await discovery.discover_entities() - # Should discover the non-streaming agent - agents = [e for e in entities if e.type == "agent"] - assert len(agents) == 1 - # ID is auto-generated, just check it exists and starts with agent_ - assert agents[0].id.startswith("agent_") - assert agents[0].name == "Non-Streaming Agent" - assert not agents[0].metadata.get("has_run_stream") + # With lazy loading, entity is discovered but type is "unknown" + # (no agent.py or workflow.py to detect type from) + assert len(entities) == 1 + entity = entities[0] + assert entity.id == "non_streaming_agent" + assert entity.type == "unknown" # Type not yet determined + assert entity.tools == [] # Sparse metadata + + # Trigger lazy loading to get full metadata + agent_obj = await discovery.load_entity(entity.id) + assert agent_obj is not None + + # Now check enriched metadata after loading + enriched = discovery.get_entity_info(entity.id) + assert enriched.type == "agent" # Now correctly identified + assert enriched.name == "Non-Streaming Agent" + assert not enriched.metadata.get("has_run_stream") + + +async def test_lazy_loading(): + """Test that entities are loaded on-demand, not at discovery time.""" + import tempfile + from pathlib import Path + + with tempfile.TemporaryDirectory() as temp_dir: + temp_path = Path(temp_dir) + + # Create test workflow + workflow_dir = temp_path / "test_workflow" + workflow_dir.mkdir() + (workflow_dir / "workflow.py").write_text(""" +from agent_framework import WorkflowBuilder, FunctionExecutor + +# Create a simple workflow with a start executor +def test_func(input: str) -> str: + return f"Processed: {input}" + +builder = WorkflowBuilder() +executor = FunctionExecutor(id="test_executor", func=test_func) +builder.set_start_executor(executor) +workflow = builder.build() +""") + + discovery = EntityDiscovery(str(temp_path)) + + # Discovery should NOT import module + entities = await discovery.discover_entities() + assert len(entities) == 1 + assert entities[0].id == "test_workflow" + assert entities[0].type == "workflow" # Type detected from filename + assert entities[0].tools == [] # Sparse metadata (not loaded yet) + + # Entity should NOT be in loaded_objects yet + assert discovery.get_entity_object("test_workflow") is None + + # Trigger lazy load + workflow_obj = await discovery.load_entity("test_workflow") + assert workflow_obj is not None + + # Now in cache + assert discovery.get_entity_object("test_workflow") is workflow_obj + + # Second load is instant (from cache) + workflow_obj2 = await discovery.load_entity("test_workflow") + assert workflow_obj2 is workflow_obj # Same object + + +async def test_type_detection(): + """Test that entity types are detected from filenames.""" + import tempfile + from pathlib import Path + + with tempfile.TemporaryDirectory() as temp_dir: + temp_path = Path(temp_dir) + + # Create workflow with workflow.py + workflow_dir = temp_path / "my_workflow" + workflow_dir.mkdir() + (workflow_dir / "workflow.py").write_text(""" +from agent_framework import WorkflowBuilder, FunctionExecutor + +def test_func(input: str) -> str: + return f"Processed: {input}" + +builder = WorkflowBuilder() +executor = FunctionExecutor(id="test_executor", func=test_func) +builder.set_start_executor(executor) +workflow = builder.build() +""") + + # Create agent with agent.py + agent_dir = temp_path / "my_agent" + agent_dir.mkdir() + (agent_dir / "agent.py").write_text(""" +from agent_framework import AgentRunResponse, AgentThread, ChatMessage, Role, TextContent + +class TestAgent: + name = "Test Agent" + + async def run(self, messages=None, *, thread=None, **kwargs): + return AgentRunResponse( + messages=[ChatMessage(role=Role.ASSISTANT, contents=[TextContent(text="test")])], + response_id="test" + ) + + def get_new_thread(self, **kwargs): + return AgentThread() + +agent = TestAgent() +""") + + # Create ambiguous entity with __init__.py only + unknown_dir = temp_path / "my_thing" + unknown_dir.mkdir() + (unknown_dir / "__init__.py").write_text("# thing") + + discovery = EntityDiscovery(str(temp_path)) + entities = await discovery.discover_entities() + + # Check types detected correctly + by_id = {e.id: e for e in entities} + + assert by_id["my_workflow"].type == "workflow" + assert by_id["my_agent"].type == "agent" + assert by_id["my_thing"].type == "unknown" + + +async def test_hot_reload(): + """Test that invalidate_entity() enables hot reload.""" + import tempfile + from pathlib import Path + + with tempfile.TemporaryDirectory() as temp_dir: + temp_path = Path(temp_dir) + + # Create workflow + workflow_dir = temp_path / "test_workflow" + workflow_dir.mkdir() + workflow_file = workflow_dir / "workflow.py" + workflow_file.write_text(""" +from agent_framework import WorkflowBuilder, FunctionExecutor + +def test_func(input: str) -> str: + return "v1" + +builder = WorkflowBuilder() +executor = FunctionExecutor(id="test_executor", func=test_func) +builder.set_start_executor(executor) +workflow = builder.build() +""") + + discovery = EntityDiscovery(str(temp_path)) + await discovery.discover_entities() + + # Load entity + workflow1 = await discovery.load_entity("test_workflow") + assert workflow1 is not None + + # Modify file to create a different workflow + workflow_file.write_text(""" +from agent_framework import WorkflowBuilder, FunctionExecutor + +def test_func(input: str) -> str: + return "v2" + +def test_func2(input: str) -> str: + return "v2_extra" + +builder = WorkflowBuilder() +executor1 = FunctionExecutor(id="test_executor", func=test_func) +executor2 = FunctionExecutor(id="test_executor2", func=test_func2) +builder.set_start_executor(executor1) +builder.add_edge(executor1, executor2) +workflow = builder.build() +""") + + # Without invalidation, gets cached version + workflow2 = await discovery.load_entity("test_workflow") + assert workflow2 is workflow1 # Same object (cached) + # Old workflow has 1 executor + assert len(workflow2.get_executors_list()) == 1 + + # Invalidate cache + discovery.invalidate_entity("test_workflow") + + # Now reloads from disk + workflow3 = await discovery.load_entity("test_workflow") + assert workflow3 is not workflow1 # Different object + # New workflow has 2 executors + assert len(workflow3.get_executors_list()) == 2 + + +async def test_in_memory_entities_bypass_lazy_loading(): + """Test that in-memory entities work as before (no lazy loading needed).""" + from agent_framework import FunctionExecutor, WorkflowBuilder + + # Create in-memory workflow + def test_func(input: str) -> str: + return f"Processed: {input}" + + builder = WorkflowBuilder() + executor = FunctionExecutor(id="test_executor", func=test_func) + builder.set_start_executor(executor) + workflow = builder.build() + + discovery = EntityDiscovery() + + # Register in-memory entity + entity_info = await discovery.create_entity_info_from_object(workflow, entity_type="workflow", source="in_memory") + discovery.register_entity(entity_info.id, entity_info, workflow) + + # Should be immediately available (no lazy loading) + loaded = discovery.get_entity_object(entity_info.id) + assert loaded is workflow + + # load_entity() should return immediately from cache + loaded2 = await discovery.load_entity(entity_info.id) + assert loaded2 is workflow # Same object (cache hit) if __name__ == "__main__": diff --git a/python/packages/devui/tests/test_execution.py b/python/packages/devui/tests/test_execution.py index cf775d7388..404e1a4797 100644 --- a/python/packages/devui/tests/test_execution.py +++ b/python/packages/devui/tests/test_execution.py @@ -73,7 +73,11 @@ async def test_executor_entity_discovery(executor): for entity in entities: assert entity.id, "Entity should have an ID" assert entity.name, "Entity should have a name" - assert entity.type in ["agent", "workflow"], "Entity should have valid type" + # Entities with only an `__init__.py` file cannot have their type determined + # until the module is imported during lazy loading. This is why 'unknown' type exists. + assert entity.type in ["agent", "workflow", "unknown"], ( + "Entity should have valid type (unknown allowed during discovery phase)" + ) async def test_executor_get_entity_info(executor): @@ -84,7 +88,7 @@ async def test_executor_get_entity_info(executor): entity_info = executor.get_entity_info(entity_id) assert entity_info is not None assert entity_info.id == entity_id - assert entity_info.type in ["agent", "workflow"] + assert entity_info.type in ["agent", "workflow", "unknown"] @pytest.mark.skipif(not os.getenv("OPENAI_API_KEY"), reason="requires OpenAI API key")