diff --git a/docs/cross_process_events.md b/docs/cross_process_events.md new file mode 100644 index 00000000..53a83eb5 --- /dev/null +++ b/docs/cross_process_events.md @@ -0,0 +1,226 @@ +# Event Bus System + +The eval protocol includes a flexible event bus system that supports both in-process and cross-process event communication. This is particularly useful for scenarios where you have: + +- An evaluation test running in one process +- A logs server running in another process +- Real-time updates between processes + +## Architecture + +The event bus system consists of: + +1. **EventBus**: The core interface for event communication +2. **SqliteEventBus**: An implementation that adds cross-process capabilities using SQLite + +### Core EventBus Interface + +The `EventBus` class provides the basic event bus functionality: + +```python +from eval_protocol.event_bus import EventBus + +event_bus = EventBus() + +def handle_event(event_type: str, data): + print(f"Received {event_type}: {data}") + +event_bus.subscribe(handle_event) +event_bus.emit("test_event", {"data": "value"}) +``` + +### SqliteEventBus Implementation + +The `SqliteEventBus` extends `EventBus` to add cross-process communication capabilities using the existing SQLite database infrastructure. Events are stored in the same database as evaluation rows, providing: + +- **No additional dependencies** - Uses existing peewee/SQLite infrastructure +- **Reliable delivery** - Database transactions ensure event persistence +- **Automatic cleanup** - Old events are automatically cleaned up +- **Process isolation** - Each process has a unique ID to avoid processing its own events + +### Database Schema + +Events are stored in a new `Event` table with the following structure: + +- `event_id`: Unique identifier for each event +- `event_type`: Type of event (e.g., "row_upserted") +- `data`: JSON data payload +- `timestamp`: When the event was created +- `process_id`: ID of the process that created the event +- `processed`: Whether the event has been processed by other processes + +## Usage + +### Basic Usage (In-Process) + +```python +from eval_protocol.event_bus import EventBus + +# Create a basic event bus for in-process communication +event_bus = EventBus() + +# Subscribe to events +def handle_event(event_type: str, data): + print(f"Received {event_type}: {data}") + +event_bus.subscribe(handle_event) + +# Emit events +event_bus.emit("test_event", {"data": "value"}) +``` + +### Cross-Process Usage + +```python +from eval_protocol.event_bus import SqliteEventBus + +# Create a cross-process event bus +event_bus = SqliteEventBus() + +# Subscribe to events +def handle_event(event_type: str, data): + print(f"Received {event_type}: {data}") + +event_bus.subscribe(handle_event) + +# Start listening for cross-process events +event_bus.start_listening() + +# Emit events (will be broadcast to other processes) +event_bus.emit("row_upserted", evaluation_row) +``` + +### Using the Global Event Bus + +The global `event_bus` instance is a `SqliteEventBus` that provides cross-process functionality: + +```python +from eval_protocol.event_bus import event_bus + +# Subscribe to events +def handle_event(event_type: str, data): + print(f"Received {event_type}: {data}") + +event_bus.subscribe(handle_event) + +# Start listening for cross-process events +event_bus.start_listening() + +# Emit events +event_bus.emit("row_upserted", evaluation_row) +``` + +### In Evaluation Tests + +The event bus is automatically used by the dataset logger. When you log evaluation rows, they are automatically broadcast to all listening processes: + +```python +from eval_protocol.dataset_logger import default_logger + +# This will automatically emit a "row_upserted" event +default_logger.log(evaluation_row) +``` + +### In Logs Server + +The logs server automatically starts listening for cross-process events and broadcasts them to connected WebSocket clients: + +```python +from eval_protocol.utils.logs_server import serve_logs + +# This will start the server and listen for cross-process events +serve_logs() +``` + +## Configuration + +### EventBus Configuration + +The basic `EventBus` requires no configuration - it works entirely in-memory. + +### SqliteEventBus Configuration + +The `SqliteEventBus` automatically uses the same SQLite database as the evaluation row store, so no additional configuration is required. The database is located at: + +- Default: `~/.eval_protocol/logs.db` +- Custom: Can be specified when creating the event bus + +#### Custom Database Path + +```python +from eval_protocol.event_bus import SqliteEventBus + +# Use a custom database path +event_bus = SqliteEventBus(db_path="/path/to/custom.db") +``` + +## Performance Considerations + +### EventBus Performance + +- **In-memory**: Events are processed immediately with no latency +- **Memory usage**: Events are not persisted, so memory usage is minimal +- **Scalability**: Suitable for high-frequency events within a single process + +### SqliteEventBus Performance + +- **Database-based**: Events are stored in SQLite with proper indexing +- **Polling frequency**: Events are checked every 100ms by default +- **Memory usage**: Events are automatically cleaned up after 24 hours +- **Latency**: ~100ms latency due to polling interval +- **Scalability**: Suitable for moderate event volumes (< 1000 events/second) + +## Event Types + +The following event types are currently supported: + +- `row_upserted`: Emitted when an evaluation row is logged +- `log`: Legacy event type (handled the same as `row_upserted`) + +## Testing + +You can test the cross-process event bus using the provided example: + +1. Start the logs server in one terminal: + ```bash + python examples/cross_process_events_example.py server + ``` + +2. Run the evaluation in another terminal: + ```bash + python examples/cross_process_events_example.py eval + ``` + +## Troubleshooting + +### Events Not Received + +1. Check that the event bus is started listening: `event_bus.start_listening()` +2. Verify the database is accessible and writable +3. Check for database lock issues (multiple processes accessing the same database) +4. Ensure both processes are using the same database path + +### Database Lock Issues + +SQLite has limitations with concurrent access. If you experience database locks: + +1. Ensure processes are not writing to the database simultaneously +2. Consider using a different database backend for high-concurrency scenarios +3. The event bus automatically handles some concurrency issues + +### High Database Size + +The system automatically cleans up old processed events after 24 hours. If you're seeing high database size: + +1. Check the database file size: `~/.eval_protocol/logs.db` +2. Manually clean up old events if needed +3. Adjust the cleanup interval in the code if necessary + +### Performance Issues + +If you're experiencing performance issues: + +1. Check the polling interval (currently 100ms) +2. Monitor database size and cleanup frequency +3. Consider reducing the number of events emitted +4. Profile the database queries for bottlenecks diff --git a/eval_protocol/dataset_logger/__init__.py b/eval_protocol/dataset_logger/__init__.py index c7cdbf6a..d60fe513 100644 --- a/eval_protocol/dataset_logger/__init__.py +++ b/eval_protocol/dataset_logger/__init__.py @@ -1,3 +1,3 @@ -from eval_protocol.dataset_logger.local_fs_dataset_logger_adapter import LocalFSDatasetLoggerAdapter +from eval_protocol.dataset_logger.sqlite_dataset_logger_adapter import SqliteDatasetLoggerAdapter -default_logger = LocalFSDatasetLoggerAdapter() +default_logger = SqliteDatasetLoggerAdapter() diff --git a/eval_protocol/dataset_logger/dataset_logger.py b/eval_protocol/dataset_logger/dataset_logger.py index a19f2613..ac735b10 100644 --- a/eval_protocol/dataset_logger/dataset_logger.py +++ b/eval_protocol/dataset_logger/dataset_logger.py @@ -4,6 +4,8 @@ if TYPE_CHECKING: from eval_protocol.models import EvaluationRow +LOG_EVENT_TYPE = "log" + class DatasetLogger(ABC): """ diff --git a/eval_protocol/dataset_logger/sqlite_dataset_logger_adapter.py b/eval_protocol/dataset_logger/sqlite_dataset_logger_adapter.py new file mode 100644 index 00000000..51ee9006 --- /dev/null +++ b/eval_protocol/dataset_logger/sqlite_dataset_logger_adapter.py @@ -0,0 +1,39 @@ +import os +from typing import List, Optional + +from eval_protocol.dataset_logger.dataset_logger import LOG_EVENT_TYPE, DatasetLogger +from eval_protocol.dataset_logger.sqlite_evaluation_row_store import SqliteEvaluationRowStore +from eval_protocol.directory_utils import find_eval_protocol_dir +from eval_protocol.event_bus import event_bus +from eval_protocol.event_bus.logger import logger +from eval_protocol.models import EvaluationRow + + +class SqliteDatasetLoggerAdapter(DatasetLogger): + def __init__(self, db_path: Optional[str] = None, store: Optional[SqliteEvaluationRowStore] = None): + eval_protocol_dir = find_eval_protocol_dir() + if db_path is not None and store is not None: + raise ValueError("Provide only one of db_path or store, not both.") + if store is not None: + self.db_path = store.db_path + self._store = store + else: + self.db_path = db_path if db_path is not None else os.path.join(eval_protocol_dir, "logs.db") + self._store = SqliteEvaluationRowStore(self.db_path) + + def log(self, row: "EvaluationRow") -> None: + row_id = row.input_metadata.row_id + data = row.model_dump(exclude_none=True, mode="json") + self._store.upsert_row(row_id=row_id, data=data) + try: + event_bus.emit(LOG_EVENT_TYPE, EvaluationRow(**data)) + except Exception as e: + # Avoid breaking storage due to event emission issues + logger.error(f"Failed to emit row_upserted event: {e}") + pass + + def read(self, row_id: Optional[str] = None) -> List["EvaluationRow"]: + from eval_protocol.models import EvaluationRow + + results = self._store.read_rows(row_id=row_id) + return [EvaluationRow(**data) for data in results] diff --git a/eval_protocol/dataset_logger/sqlite_evaluation_row_store.py b/eval_protocol/dataset_logger/sqlite_evaluation_row_store.py new file mode 100644 index 00000000..bce74264 --- /dev/null +++ b/eval_protocol/dataset_logger/sqlite_evaluation_row_store.py @@ -0,0 +1,57 @@ +import os +from typing import List, Optional + +from peewee import CharField, Model, SqliteDatabase +from playhouse.sqlite_ext import JSONField + +from eval_protocol.models import EvaluationRow + + +class SqliteEvaluationRowStore: + """ + Lightweight reusable SQLite store for evaluation rows. + + Stores arbitrary row data as JSON keyed by a unique string `row_id`. + """ + + def __init__(self, db_path: str): + os.makedirs(os.path.dirname(db_path), exist_ok=True) + self._db_path = db_path + self._db = SqliteDatabase(self._db_path) + + class BaseModel(Model): + class Meta: + database = self._db + + class EvaluationRow(BaseModel): # type: ignore + row_id = CharField(unique=True) + data = JSONField() + + self._EvaluationRow = EvaluationRow + + self._db.connect() + self._db.create_tables([EvaluationRow]) + + @property + def db_path(self) -> str: + return self._db_path + + def upsert_row(self, row_id: str, data: dict) -> None: + if self._EvaluationRow.select().where(self._EvaluationRow.row_id == row_id).exists(): + self._EvaluationRow.update(data=data).where(self._EvaluationRow.row_id == row_id).execute() + else: + self._EvaluationRow.create(row_id=row_id, data=data) + + def read_rows(self, row_id: Optional[str] = None) -> List[dict]: + if row_id is None: + query = self._EvaluationRow.select().dicts() + else: + query = self._EvaluationRow.select().dicts().where(self._EvaluationRow.row_id == row_id) + results = list(query) + return [result["data"] for result in results] + + def delete_row(self, row_id: str) -> int: + return self._EvaluationRow.delete().where(self._EvaluationRow.row_id == row_id).execute() + + def delete_all_rows(self) -> int: + return self._EvaluationRow.delete().execute() diff --git a/eval_protocol/dataset_logger/directory_utils.py b/eval_protocol/directory_utils.py similarity index 100% rename from eval_protocol/dataset_logger/directory_utils.py rename to eval_protocol/directory_utils.py diff --git a/eval_protocol/event_bus/__init__.py b/eval_protocol/event_bus/__init__.py new file mode 100644 index 00000000..40eedde8 --- /dev/null +++ b/eval_protocol/event_bus/__init__.py @@ -0,0 +1,5 @@ +# Global event bus instance - uses SqliteEventBus for cross-process functionality +from eval_protocol.event_bus.event_bus import EventBus +from eval_protocol.event_bus.sqlite_event_bus import SqliteEventBus + +event_bus: EventBus = SqliteEventBus() diff --git a/eval_protocol/event_bus/event_bus.py b/eval_protocol/event_bus/event_bus.py new file mode 100644 index 00000000..8f356a03 --- /dev/null +++ b/eval_protocol/event_bus/event_bus.py @@ -0,0 +1,50 @@ +from typing import Any, Callable, List + +from eval_protocol.event_bus.logger import logger + + +class EventBus: + """Core event bus interface for decoupling components in the evaluation system.""" + + def __init__(self): + self._listeners: List[Callable[[str, Any], None]] = [] + + def subscribe(self, callback: Callable[[str, Any], None]) -> None: + """Subscribe to events. + + Args: + callback: Function that takes (event_type, data) parameters + """ + self._listeners.append(callback) + + def unsubscribe(self, callback: Callable[[str, Any], None]) -> None: + """Unsubscribe from events. + + Args: + callback: The callback function to remove + """ + try: + self._listeners.remove(callback) + except ValueError: + pass # Callback wasn't subscribed + + def emit(self, event_type: str, data: Any) -> None: + """Emit an event to all subscribers. + + Args: + event_type: Type of event (e.g., "row_upserted") + data: Event data + """ + for listener in self._listeners: + try: + listener(event_type, data) + except Exception as e: + logger.debug(f"Event listener failed for {event_type}: {e}") + + def start_listening(self) -> None: + """Start listening for cross-process events. Override in subclasses.""" + pass + + def stop_listening(self) -> None: + """Stop listening for cross-process events. Override in subclasses.""" + pass diff --git a/eval_protocol/event_bus/logger.py b/eval_protocol/event_bus/logger.py new file mode 100644 index 00000000..eea436a3 --- /dev/null +++ b/eval_protocol/event_bus/logger.py @@ -0,0 +1,3 @@ +import logging + +logger = logging.getLogger(__name__) diff --git a/eval_protocol/event_bus/sqlite_event_bus.py b/eval_protocol/event_bus/sqlite_event_bus.py new file mode 100644 index 00000000..2925abe4 --- /dev/null +++ b/eval_protocol/event_bus/sqlite_event_bus.py @@ -0,0 +1,109 @@ +import threading +import time +from typing import Any, Optional +from uuid import uuid4 + +from eval_protocol.event_bus.event_bus import EventBus +from eval_protocol.event_bus.logger import logger +from eval_protocol.event_bus.sqlite_event_bus_database import SqliteEventBusDatabase + + +class SqliteEventBus(EventBus): + """SQLite-based event bus implementation that supports cross-process communication.""" + + def __init__(self, db_path: Optional[str] = None): + super().__init__() + + # Use the same database as the evaluation row store + if db_path is None: + import os + + from eval_protocol.directory_utils import find_eval_protocol_dir + + eval_protocol_dir = find_eval_protocol_dir() + db_path = os.path.join(eval_protocol_dir, "logs.db") + + self._db = SqliteEventBusDatabase(db_path) + self._running = False + self._listener_thread: Optional[threading.Thread] = None + self._process_id = str(uuid4()) + + def emit(self, event_type: str, data: Any) -> None: + """Emit an event to all subscribers. + + Args: + event_type: Type of event (e.g., "log") + data: Event data + """ + # Call local listeners immediately + super().emit(event_type, data) + + # Publish to cross-process subscribers + self._publish_cross_process(event_type, data) + + def _publish_cross_process(self, event_type: str, data: Any) -> None: + """Publish event to cross-process subscribers via database.""" + self._db.publish_event(event_type, data, self._process_id) + + def start_listening(self) -> None: + """Start listening for cross-process events.""" + if self._running: + return + + self._running = True + self._start_database_listener() + + def stop_listening(self) -> None: + """Stop listening for cross-process events.""" + self._running = False + if self._listener_thread and self._listener_thread.is_alive(): + self._listener_thread.join(timeout=1) + + def _start_database_listener(self) -> None: + """Start database-based event listener.""" + + def database_listener(): + last_cleanup = time.time() + + while self._running: + try: + # Get unprocessed events from other processes + events = self._db.get_unprocessed_events(self._process_id) + + for event in events: + if not self._running: + break + + try: + # Handle the event + self._handle_cross_process_event(event["event_type"], event["data"]) + + # Mark as processed + self._db.mark_event_processed(event["event_id"]) + + except Exception as e: + logger.debug(f"Failed to process event {event['event_id']}: {e}") + + # Clean up old events every hour + current_time = time.time() + if current_time - last_cleanup >= 3600: + self._db.cleanup_old_events() + last_cleanup = current_time + + # Small sleep to prevent busy waiting + time.sleep(0.1) + + except Exception as e: + logger.debug(f"Database listener error: {e}") + time.sleep(1) + + self._listener_thread = threading.Thread(target=database_listener, daemon=True) + self._listener_thread.start() + + def _handle_cross_process_event(self, event_type: str, data: Any) -> None: + """Handle events received from other processes.""" + for listener in self._listeners: + try: + listener(event_type, data) + except Exception as e: + logger.debug(f"Cross-process event listener failed for {event_type}: {e}") diff --git a/eval_protocol/event_bus/sqlite_event_bus_database.py b/eval_protocol/event_bus/sqlite_event_bus_database.py new file mode 100644 index 00000000..f7a96f84 --- /dev/null +++ b/eval_protocol/event_bus/sqlite_event_bus_database.py @@ -0,0 +1,95 @@ +import time +from typing import Any, List +from uuid import uuid4 + +from peewee import CharField, DateTimeField, Model, SqliteDatabase +from playhouse.sqlite_ext import JSONField + +from eval_protocol.event_bus.logger import logger + + +class SqliteEventBusDatabase: + """SQLite database for cross-process event communication.""" + + def __init__(self, db_path: str): + self._db_path = db_path + self._db = SqliteDatabase(db_path) + + class BaseModel(Model): + class Meta: + database = self._db + + class Event(BaseModel): # type: ignore + event_id = CharField(unique=True) + event_type = CharField() + data = JSONField() + timestamp = DateTimeField() + process_id = CharField() + processed = CharField(default="false") # Track if event has been processed + + self._Event = Event + self._db.connect() + self._db.create_tables([Event]) + + def publish_event(self, event_type: str, data: Any, process_id: str) -> None: + """Publish an event to the database.""" + try: + # Serialize data, handling pydantic models + if hasattr(data, "model_dump"): + serialized_data = data.model_dump(mode="json", exclude_none=True) + else: + serialized_data = data + + self._Event.create( + event_id=str(uuid4()), + event_type=event_type, + data=serialized_data, + timestamp=time.time(), + process_id=process_id, + processed="false", + ) + except Exception as e: + logger.warning(f"Failed to publish event to database: {e}") + + def get_unprocessed_events(self, process_id: str) -> List[dict]: + """Get unprocessed events from other processes.""" + try: + query = ( + self._Event.select() + .where((self._Event.process_id != process_id) & (self._Event.processed == "false")) + .order_by(self._Event.timestamp) + ) + + events = [] + for event in query: + events.append( + { + "event_id": event.event_id, + "event_type": event.event_type, + "data": event.data, + "timestamp": event.timestamp, + "process_id": event.process_id, + } + ) + + return events + except Exception as e: + logger.warning(f"Failed to get unprocessed events: {e}") + return [] + + def mark_event_processed(self, event_id: str) -> None: + """Mark an event as processed.""" + try: + self._Event.update(processed="true").where(self._Event.event_id == event_id).execute() + except Exception as e: + logger.debug(f"Failed to mark event as processed: {e}") + + def cleanup_old_events(self, max_age_hours: int = 24) -> None: + """Clean up old processed events.""" + try: + cutoff_time = time.time() - (max_age_hours * 3600) + self._Event.delete().where( + (self._Event.processed == "true") & (self._Event.timestamp < cutoff_time) + ).execute() + except Exception as e: + logger.debug(f"Failed to cleanup old events: {e}") diff --git a/eval_protocol/logging_utils.py b/eval_protocol/logging_utils.py index 36519a7c..33ce8057 100644 --- a/eval_protocol/logging_utils.py +++ b/eval_protocol/logging_utils.py @@ -12,7 +12,7 @@ from pathlib import Path from typing import Optional -from eval_protocol.dataset_logger.directory_utils import find_eval_protocol_dir +from eval_protocol.directory_utils import find_eval_protocol_dir def setup_logger( diff --git a/eval_protocol/utils/logs_server.py b/eval_protocol/utils/logs_server.py index ce48205a..4338882c 100644 --- a/eval_protocol/utils/logs_server.py +++ b/eval_protocol/utils/logs_server.py @@ -2,139 +2,202 @@ import json import logging import os +import threading import time from contextlib import asynccontextmanager -from typing import List, Optional +from queue import Queue +from typing import TYPE_CHECKING, Any, List, Optional +import psutil import uvicorn from fastapi import FastAPI, WebSocket, WebSocketDisconnect -from watchdog.events import FileSystemEvent, FileSystemEventHandler -from watchdog.observers import Observer from eval_protocol.dataset_logger import default_logger +from eval_protocol.dataset_logger.dataset_logger import LOG_EVENT_TYPE +from eval_protocol.event_bus import event_bus from eval_protocol.utils.vite_server import ViteServer -default_logger +if TYPE_CHECKING: + from eval_protocol.models import EvaluationRow logger = logging.getLogger(__name__) -class FileWatcher(FileSystemEventHandler): - """File system watcher that tracks file changes.""" - - def __init__(self, websocket_manager): - self.websocket_manager: WebSocketManager = websocket_manager - self.ignored_patterns = { - ".git", - "__pycache__", - ".pytest_cache", - "node_modules", - ".DS_Store", - "*.pyc", - "*.pyo", - "*.pyd", - ".coverage", - "*.log", - "*.tmp", - "*.swp", - "*.swo", - "*~", - } - - def should_ignore(self, path: str) -> bool: - """Check if a path should be ignored.""" - path_lower = path.lower() - for pattern in self.ignored_patterns: - if pattern.startswith("*"): - if path_lower.endswith(pattern[1:]): - return True - elif pattern in path_lower: - return True - return False - - def on_created(self, event: FileSystemEvent): - if not event.is_directory and not self.should_ignore(event.src_path): - self.websocket_manager.broadcast_file_update("file_created", event.src_path) - - def on_modified(self, event: FileSystemEvent): - if not event.is_directory and not self.should_ignore(event.src_path): - self.websocket_manager.broadcast_file_update("file_changed", event.src_path) - - def on_deleted(self, event: FileSystemEvent): - if not event.is_directory and not self.should_ignore(event.src_path): - self.websocket_manager.broadcast_file_update("file_deleted", event.src_path) - - class WebSocketManager: """Manages WebSocket connections and broadcasts messages.""" def __init__(self): self.active_connections: List[WebSocket] = [] - self._loop = None + self._broadcast_queue: Queue = Queue() + self._broadcast_task: Optional[asyncio.Task] = None + self._lock = threading.Lock() async def connect(self, websocket: WebSocket): await websocket.accept() - self.active_connections.append(websocket) - logger.info(f"WebSocket connected. Total connections: {len(self.active_connections)}") + with self._lock: + self.active_connections.append(websocket) + connection_count = len(self.active_connections) + logger.info(f"WebSocket connected. Total connections: {connection_count}") logs = default_logger.read() - asyncio.run_coroutine_threadsafe( - websocket.send_text( - json.dumps( - {"type": "initialize_logs", "logs": [log.model_dump_json(exclude_none=True) for log in logs]} - ) - ), - self._loop, + await websocket.send_text( + json.dumps({"type": "initialize_logs", "logs": [log.model_dump_json(exclude_none=True) for log in logs]}) ) def disconnect(self, websocket: WebSocket): - if websocket in self.active_connections: - self.active_connections.remove(websocket) - logger.info(f"WebSocket disconnected. Total connections: {len(self.active_connections)}") - - def broadcast_file_update(self, update_type: str, file_path: str): - """Broadcast file update to all connected clients.""" - if not file_path.startswith(default_logger.datasets_dir) or not file_path.endswith(".jsonl"): - """ - .lock files are often created and deleted by the singleton lock - mechanism so we only broadcast .jsonl files - """ - return - logger.info(f"Broadcasting file update: {update_type} {file_path}") + with self._lock: + if websocket in self.active_connections: + self.active_connections.remove(websocket) + connection_count = len(self.active_connections) + logger.info(f"WebSocket disconnected. Total connections: {connection_count}") - logs = default_logger.read() - # send initialize_logs message to all connected clients - for connection in self.active_connections: - asyncio.run_coroutine_threadsafe( - connection.send_text( - json.dumps( - {"type": "initialize_logs", "logs": [log.model_dump_json(exclude_none=True) for log in logs]} - ) - ), - self._loop, - ) + def broadcast_row_upserted(self, row: "EvaluationRow"): + """Broadcast a row-upsert event to all connected clients. + + Safe no-op if server loop is not running or there are no connections. + """ + try: + # Serialize pydantic model + json_message = json.dumps({"type": "log", "row": json.loads(row.model_dump_json(exclude_none=True))}) + # Queue the message for broadcasting in the main event loop + self._broadcast_queue.put(json_message) + except Exception as e: + logger.error(f"Failed to serialize row for broadcast: {e}") + + async def _start_broadcast_loop(self): + """Start the broadcast loop that processes queued messages.""" + while True: + try: + # Wait for a message to be queued + message = await asyncio.get_event_loop().run_in_executor(None, self._broadcast_queue.get) + await self._send_text_to_all_connections(message) + except Exception as e: + logger.error(f"Error in broadcast loop: {e}") + await asyncio.sleep(0.1) + except asyncio.CancelledError: + logger.info("Broadcast loop cancelled") + break + + async def _send_text_to_all_connections(self, text: str): + with self._lock: + connections = list(self.active_connections) + + if not connections: + return - message = {"type": update_type, "path": file_path, "timestamp": time.time()} - # Include file contents for created and modified events - if update_type in ["file_created", "file_changed"] and os.path.exists(file_path): + tasks = [] + for connection in connections: try: - with open(file_path, "r", encoding="utf-8") as f: - message["contents"] = f.read() + tasks.append(connection.send_text(text)) except Exception as e: - logger.warning(f"Failed to read file contents for {file_path}: {e}") - message["contents"] = None - elif update_type == "file_deleted": - message["contents"] = None + logger.error(f"Failed to send text to WebSocket: {e}") + with self._lock: + try: + self.active_connections.remove(connection) + except ValueError: + pass + if tasks: + await asyncio.gather(*tasks, return_exceptions=True) + + def start_broadcast_loop(self): + """Start the broadcast loop in the current event loop.""" + if self._broadcast_task is None or self._broadcast_task.done(): + self._broadcast_task = asyncio.create_task(self._start_broadcast_loop()) + + def stop_broadcast_loop(self): + """Stop the broadcast loop.""" + if self._broadcast_task and not self._broadcast_task.done(): + self._broadcast_task.cancel() + + +class EvaluationWatcher: + """Monitors running evaluations and updates their status when processes stop.""" + + def __init__(self, websocket_manager: "WebSocketManager"): + self.websocket_manager = websocket_manager + self._running = False + self._thread: Optional[threading.Thread] = None + self._stop_event = threading.Event() + + def start(self): + """Start the evaluation watcher thread.""" + if self._running: + return + + self._running = True + self._stop_event.clear() + self._thread = threading.Thread(target=self._watch_loop, daemon=True) + self._thread.start() + logger.info("Evaluation watcher started") - json_message = json.dumps(message) + def stop(self): + """Stop the evaluation watcher thread.""" + if not self._running: + return - # Broadcast to all active connections - for connection in self.active_connections: + self._running = False + self._stop_event.set() + if self._thread and self._thread.is_alive(): + self._thread.join(timeout=5) + logger.info("Evaluation watcher stopped") + + def _watch_loop(self): + """Main loop that checks for stopped processes every 2 seconds.""" + while self._running and not self._stop_event.is_set(): try: - asyncio.run_coroutine_threadsafe(connection.send_text(json_message), self._loop) + self._check_running_evaluations() + # Wait 2 seconds before next check + self._stop_event.wait(2) except Exception as e: - logger.error(f"Failed to send message to WebSocket: {e}") - # Remove broken connection - self.active_connections.remove(connection) + logger.error(f"Error in evaluation watcher loop: {e}") + # Continue running even if there's an error + time.sleep(1) + + def _check_running_evaluations(self): + """Check all running evaluations and update status for stopped processes.""" + try: + logs = default_logger.read() + updated_rows = [] + + for row in logs: + if self._should_update_status(row): + logger.info(f"Updating status to 'stopped' for row {row.input_metadata.row_id} (PID {row.pid})") + if row.eval_metadata is not None: + row.eval_metadata.status = "stopped" + updated_rows.append(row) + + # Log all updated rows + for row in updated_rows: + default_logger.log(row) + # Broadcast the update to connected clients + self.websocket_manager.broadcast_row_upserted(row) + + except Exception as e: + logger.error(f"Error checking running evaluations: {e}") + + def _should_update_status(self, row: "EvaluationRow") -> bool: + """Check if a row's status should be updated to 'stopped'.""" + # Check if the row has running status and a PID + if row.eval_metadata and row.eval_metadata.status == "running" and row.pid is not None: + + # Check if the process is still running + try: + process = psutil.Process(row.pid) + # Check if process is still running + if not process.is_running(): + return True + except psutil.NoSuchProcess: + # Process no longer exists + return True + except psutil.AccessDenied: + # Can't access process info, assume it's stopped + logger.warning(f"Access denied to process {row.pid}, assuming stopped") + return True + except Exception as e: + logger.error(f"Error checking process {row.pid}: {e}") + # On error, assume process is still running to be safe + return False + + return False class LogsServer(ViteServer): @@ -142,7 +205,6 @@ class LogsServer(ViteServer): Enhanced server for serving Vite-built SPA with file watching and WebSocket support. This server extends ViteServer to add: - - File system watching - WebSocket connections for real-time updates - Live log streaming """ @@ -155,31 +217,23 @@ def __init__( host: str = "localhost", port: Optional[int] = 8000, index_file: str = "index.html", - watch_paths: Optional[List[str]] = None, ): # Initialize WebSocket manager self.websocket_manager = WebSocketManager() - # Set up file watching - self.watch_paths = watch_paths or [os.getcwd()] - self.observer = Observer() - self.file_watcher = FileWatcher(self.websocket_manager) - self._file_watching_started = False + super().__init__(build_dir, host, port, index_file) - @asynccontextmanager - async def lifespan(app: FastAPI): - self.start_file_watching() - self.websocket_manager._loop = asyncio.get_running_loop() - yield - self.stop_file_watching() - - super().__init__(build_dir, host, port, index_file, lifespan=lifespan) + # Initialize evaluation watcher + self.evaluation_watcher = EvaluationWatcher(self.websocket_manager) # Add WebSocket endpoint self._setup_websocket_routes() + # Subscribe to events and start listening for cross-process events + event_bus.subscribe(self._handle_event) + event_bus.start_listening() + logger.info(f"LogsServer initialized on {host}:{port}") - logger.info(f"Watching paths: {self.watch_paths}") def _setup_websocket_routes(self): """Set up WebSocket routes for real-time communication.""" @@ -200,38 +254,22 @@ async def websocket_endpoint(websocket: WebSocket): @self.app.get("/api/status") async def status(): """Get server status including active connections.""" + with self.websocket_manager._lock: + active_connections_count = len(self.websocket_manager.active_connections) return { "status": "ok", "build_dir": str(self.build_dir), - "active_connections": len(self.websocket_manager.active_connections), + "active_connections": active_connections_count, "watch_paths": self.watch_paths, } - def start_file_watching(self): - """Start watching file system for changes.""" - # Check if file watching has already been started - if self._file_watching_started: - logger.info("File watching already started, skipping") - return + def _handle_event(self, event_type: str, data: Any) -> None: + """Handle events from the event bus.""" + if event_type in [LOG_EVENT_TYPE]: + from eval_protocol.models import EvaluationRow - for path in self.watch_paths: - if os.path.exists(path): - self.observer.schedule(self.file_watcher, path, recursive=True) - logger.info(f"Started watching: {path}") - else: - logger.warning(f"Watch path does not exist: {path}") - - self.observer.start() - self._file_watching_started = True - logger.info("File watching started") - - def stop_file_watching(self): - """Stop watching file system.""" - if self._file_watching_started: - self.observer.stop() - self.observer.join() - self._file_watching_started = False - logger.info("File watching stopped") + data = EvaluationRow(**data) + self.websocket_manager.broadcast_row_upserted(data) async def run_async(self): """ @@ -241,15 +279,15 @@ async def run_async(self): reload: Whether to enable auto-reload (default: False) """ try: - # Start file watching - self.start_file_watching() - logger.info(f"Starting LogsServer on {self.host}:{self.port}") logger.info(f"Serving files from: {self.build_dir}") logger.info("WebSocket endpoint available at /ws") - # Store the event loop for WebSocket manager - self.websocket_manager._loop = asyncio.get_running_loop() + # Start the broadcast loop + self.websocket_manager.start_broadcast_loop() + + # Start the evaluation watcher + self.evaluation_watcher.start() config = uvicorn.Config( self.app, @@ -264,7 +302,10 @@ async def run_async(self): except KeyboardInterrupt: logger.info("Shutting down LogsServer...") finally: - self.stop_file_watching() + # Clean up evaluation watcher + self.evaluation_watcher.stop() + # Clean up broadcast loop + self.websocket_manager.stop_broadcast_loop() def run(self): """ @@ -283,15 +324,11 @@ def run(self): def serve_logs(): """ Convenience function to create and run a LogsServer. - - Args: - build_dir: Path to the Vite build output directory - host: Host to bind the server to - port: Port to bind the server to (default: 4789 for logs) - index_file: Name of the main index file - watch_paths: List of paths to watch for file changes - reload: Whether to enable auto-reload """ + global server, app + if server is None: + server = LogsServer() + app = server.app server.run() diff --git a/pyproject.toml b/pyproject.toml index 9e6112a3..63a1e8a0 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -46,10 +46,10 @@ dependencies = [ "addict>=2.4.0", "deepdiff>=6.0.0", "pandas>=1.5.0", - "watchdog>=2.1.0", "websockets>=15.0.1", "fastapi>=0.116.1", "pytest>=6.0.0", + "peewee>=3.18.2", ] [project.urls] diff --git a/tests/dataset_logger/.gitignore b/tests/dataset_logger/.gitignore new file mode 100644 index 00000000..98e6ef67 --- /dev/null +++ b/tests/dataset_logger/.gitignore @@ -0,0 +1 @@ +*.db diff --git a/tests/dataset_logger/test_sqlite_dataset_logger_adapter.py b/tests/dataset_logger/test_sqlite_dataset_logger_adapter.py new file mode 100644 index 00000000..3b8c42b5 --- /dev/null +++ b/tests/dataset_logger/test_sqlite_dataset_logger_adapter.py @@ -0,0 +1,80 @@ +import os + +from eval_protocol.dataset_logger.sqlite_dataset_logger_adapter import SqliteDatasetLoggerAdapter +from eval_protocol.dataset_logger.sqlite_evaluation_row_store import SqliteEvaluationRowStore +from eval_protocol.models import EvaluationRow, InputMetadata, Message + + +def get_db_path(test_name: str) -> str: + return os.path.join(os.path.dirname(__file__), f"{test_name}.db") + + +def test_update_log_and_read(): + db_path = get_db_path("test_update_log_and_read") + # delete the db file if it exists + if os.path.exists(db_path): + os.remove(db_path) + store = SqliteEvaluationRowStore(db_path=db_path) + messages = [Message(role="user", content="Hello")] + input_metadata = InputMetadata(row_id="1") + row = EvaluationRow(input_metadata=input_metadata, messages=messages) + store.upsert_row(row_id="1", data=row.model_dump(exclude_none=True, mode="json")) + + row.messages.append(Message(role="assistant", content="Hello")) + + logger = SqliteDatasetLoggerAdapter() + logger.log(row) + saved = logger.read(row_id="1")[0] + assert row.messages == saved.messages + assert row.input_metadata == saved.input_metadata + + +def test_create_log_and_read(): + db_path = get_db_path("test_create_log_and_read") + # delete the db file if it exists + if os.path.exists(db_path): + os.remove(db_path) + store = SqliteEvaluationRowStore(db_path=db_path) + + logger = SqliteDatasetLoggerAdapter(store=store) + messages = [Message(role="user", content="Hello")] + input_metadata = InputMetadata(row_id="1") + row = EvaluationRow(input_metadata=input_metadata, messages=messages) + + logger.log(row) + saved = logger.read(row_id="1")[0] + assert row.messages == saved.messages + assert row.input_metadata == saved.input_metadata + + +def test_create_multiple_logs_and_read_all(): + db_path = get_db_path("test_create_multiple_logs_and_read_all") + # delete the db file if it exists + if os.path.exists(db_path): + os.remove(db_path) + store = SqliteEvaluationRowStore(db_path=db_path) + logger = SqliteDatasetLoggerAdapter(store=store) + + # Create multiple evaluation rows with different row_ids + rows = [] + for i in range(3): + messages = [Message(role="user", content=f"Hello {i}")] + input_metadata = InputMetadata(row_id=f"row_{i}") + row = EvaluationRow(input_metadata=input_metadata, messages=messages) + rows.append(row) + + # Log each row + logger.log(row) + + # Read all logs (without specifying row_id) + saved_rows = logger.read() + + # Verify we got all 3 rows back + assert len(saved_rows) == 3 + + # Verify each row matches the original + for i, original_row in enumerate(rows): + saved_row = saved_rows[i] + assert original_row.messages == saved_row.messages + assert original_row.input_metadata == saved_row.input_metadata + assert original_row.input_metadata.row_id == f"row_{i}" diff --git a/tests/test_event_bus.py b/tests/test_event_bus.py new file mode 100644 index 00000000..49d74d12 --- /dev/null +++ b/tests/test_event_bus.py @@ -0,0 +1,265 @@ +import tempfile +import time + +from eval_protocol.event_bus import SqliteEventBus +from eval_protocol.event_bus.event_bus import EventBus +from eval_protocol.models import EvaluationRow, InputMetadata + + +class TestSqliteEventBus: + def test_basic_event_emission(self): + """Test basic event emission and subscription.""" + with tempfile.NamedTemporaryFile(suffix=".db", delete=False) as tmp: + db_path = tmp.name + + try: + event_bus = SqliteEventBus(db_path=db_path) + + # Test event listener + received_events = [] + + def test_listener(event_type: str, data): + received_events.append((event_type, data)) + + event_bus.subscribe(test_listener) + + # Emit an event + test_data = {"test": "data"} + event_bus.emit("test_event", test_data) + + # Check that local listener received the event + assert len(received_events) == 1 + assert received_events[0][0] == "test_event" + assert received_events[0][1] == test_data + + finally: + import os + + os.unlink(db_path) + + def test_cross_process_events(self): + """Test cross-process event communication.""" + with tempfile.NamedTemporaryFile(suffix=".db", delete=False) as tmp: + db_path = tmp.name + + try: + # Create two event buses (simulating different processes) + event_bus1 = SqliteEventBus(db_path=db_path) + event_bus2 = SqliteEventBus(db_path=db_path) + + # Set up listener on event_bus2 + received_events = [] + + def test_listener(event_type: str, data): + received_events.append((event_type, data)) + + event_bus2.subscribe(test_listener) + event_bus2.start_listening() + + # Emit event from event_bus1 + test_data = {"test": "cross_process"} + event_bus1.emit("cross_process_event", test_data) + + # Wait a bit for the event to be processed + time.sleep(0.2) + + # Check that event_bus2 received the event + assert len(received_events) == 1 + assert received_events[0][0] == "cross_process_event" + assert received_events[0][1] == test_data + + event_bus2.stop_listening() + + finally: + import os + + os.unlink(db_path) + + def test_evaluation_row_events(self): + """Test that EvaluationRow objects can be emitted and received.""" + with tempfile.NamedTemporaryFile(suffix=".db", delete=False) as tmp: + db_path = tmp.name + + try: + event_bus1 = SqliteEventBus(db_path=db_path) + event_bus2 = SqliteEventBus(db_path=db_path) + + received_events = [] + + def test_listener(event_type: str, data): + received_events.append((event_type, data)) + + event_bus2.subscribe(test_listener) + event_bus2.start_listening() + + # Create and emit an EvaluationRow + test_row = EvaluationRow( + messages=[{"role": "user", "content": "test"}], input_metadata=InputMetadata(row_id="test-123") + ) + + event_bus1.emit("row_upserted", test_row) + + # Wait for processing + time.sleep(0.2) + + # Check that the event was received + assert len(received_events) == 1 + assert received_events[0][0] == "row_upserted" + # The event data should be a dict, but it should be deserializable by EvaluationRow + assert isinstance(received_events[0][1], dict) + # Try to deserialize to EvaluationRow to ensure it's compatible + event = EvaluationRow(**received_events[0][1]) + assert event.input_metadata.row_id == "test-123" + + event_bus2.stop_listening() + + finally: + import os + + os.unlink(db_path) + + def test_process_isolation(self): + """Test that processes receive their own events locally but not via cross-process mechanism.""" + with tempfile.NamedTemporaryFile(suffix=".db", delete=False) as tmp: + db_path = tmp.name + + try: + event_bus = SqliteEventBus(db_path=db_path) + + received_events = [] + + def test_listener(event_type: str, data): + received_events.append((event_type, data)) + + event_bus.subscribe(test_listener) + event_bus.start_listening() + + # Emit an event + event_bus.emit("self_event", {"test": "data"}) + + # Wait for processing + time.sleep(0.2) + + # Should receive the event from its own process via local delivery + assert len(received_events) == 1 + assert received_events[0] == ("self_event", {"test": "data"}) + + event_bus.stop_listening() + + finally: + import os + + os.unlink(db_path) + + def test_multiple_listeners(self): + """Test that multiple listeners can subscribe to events.""" + with tempfile.NamedTemporaryFile(suffix=".db", delete=False) as tmp: + db_path = tmp.name + + try: + event_bus = SqliteEventBus(db_path=db_path) + + listener1_events = [] + listener2_events = [] + + def listener1(event_type: str, data): + listener1_events.append((event_type, data)) + + def listener2(event_type: str, data): + listener2_events.append((event_type, data)) + + event_bus.subscribe(listener1) + event_bus.subscribe(listener2) + + # Emit an event + test_data = {"test": "multiple_listeners"} + event_bus.emit("multi_event", test_data) + + # Check that both listeners received the event + assert len(listener1_events) == 1 + assert len(listener2_events) == 1 + assert listener1_events[0] == ("multi_event", test_data) + assert listener2_events[0] == ("multi_event", test_data) + + finally: + import os + + os.unlink(db_path) + + +class TestEventBus: + def test_basic_event_bus(self): + """Test the core EventBus interface.""" + event_bus = EventBus() + + received_events = [] + + def test_listener(event_type: str, data): + received_events.append((event_type, data)) + + event_bus.subscribe(test_listener) + + # Test local event emission + test_data = {"test": "basic_event_bus"} + event_bus.emit("test_event", test_data) + + assert len(received_events) == 1 + assert received_events[0] == ("test_event", test_data) + + def test_multiple_listeners(self): + """Test that multiple listeners can subscribe to events.""" + event_bus = EventBus() + + listener1_events = [] + listener2_events = [] + + def listener1(event_type: str, data): + listener1_events.append((event_type, data)) + + def listener2(event_type: str, data): + listener2_events.append((event_type, data)) + + event_bus.subscribe(listener1) + event_bus.subscribe(listener2) + + # Emit an event + test_data = {"test": "multiple_listeners"} + event_bus.emit("multi_event", test_data) + + # Check that both listeners received the event + assert len(listener1_events) == 1 + assert len(listener2_events) == 1 + assert listener1_events[0] == ("multi_event", test_data) + assert listener2_events[0] == ("multi_event", test_data) + + def test_unsubscribe(self): + """Test that unsubscribing works correctly.""" + event_bus = EventBus() + + received_events = [] + + def test_listener(event_type: str, data): + received_events.append((event_type, data)) + + event_bus.subscribe(test_listener) + + # Emit an event + event_bus.emit("test_event", {"test": "data"}) + assert len(received_events) == 1 + + # Unsubscribe + event_bus.unsubscribe(test_listener) + + # Emit another event + event_bus.emit("test_event2", {"test": "data2"}) + assert len(received_events) == 1 # Should not receive the second event + + def test_start_stop_listening_noop(self): + """Test that start_listening and stop_listening are no-ops in base EventBus.""" + event_bus = EventBus() + + # Should not raise any exceptions (these are no-ops in base class) + event_bus.start_listening() + event_bus.stop_listening() + event_bus.start_listening() + event_bus.stop_listening() diff --git a/uv.lock b/uv.lock index 09e3cda9..cb9c8e6b 100644 --- a/uv.lock +++ b/uv.lock @@ -1142,6 +1142,7 @@ dependencies = [ { name = "omegaconf" }, { name = "openai" }, { name = "pandas" }, + { name = "peewee" }, { name = "psutil" }, { name = "pydantic" }, { name = "pytest" }, @@ -1151,7 +1152,6 @@ dependencies = [ { name = "rich" }, { name = "toml" }, { name = "uvicorn" }, - { name = "watchdog" }, { name = "websockets" }, ] @@ -1266,6 +1266,7 @@ requires-dist = [ { name = "openai", marker = "extra == 'dev'", specifier = "==1.78.1" }, { name = "openevals", marker = "extra == 'openevals'", specifier = ">=0.1.0" }, { name = "pandas", specifier = ">=1.5.0" }, + { name = "peewee", specifier = ">=3.18.2" }, { name = "peft", marker = "extra == 'trl'", specifier = ">=0.7.0" }, { name = "pillow", marker = "extra == 'box2d'" }, { name = "pip", marker = "extra == 'dev'", specifier = ">=25.1.1" }, @@ -1296,7 +1297,6 @@ requires-dist = [ { name = "types-setuptools", marker = "extra == 'dev'" }, { name = "uvicorn", specifier = ">=0.15.0" }, { name = "versioneer", marker = "extra == 'dev'", specifier = ">=0.20" }, - { name = "watchdog", specifier = ">=2.1.0" }, { name = "websockets", specifier = ">=15.0.1" }, { name = "werkzeug", marker = "extra == 'dev'", specifier = ">=2.0.0" }, ] @@ -3892,6 +3892,12 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/cc/20/ff623b09d963f88bfde16306a54e12ee5ea43e9b597108672ff3a408aad6/pathspec-0.12.1-py3-none-any.whl", hash = "sha256:a0d503e138a4c123b27490a4f7beda6a01c6f288df0e4a8b79c7eb0dc7b4cc08", size = 31191, upload-time = "2023-12-10T22:30:43.14Z" }, ] +[[package]] +name = "peewee" +version = "3.18.2" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/04/89/76f6f1b744c8608e0d416b588b9d63c2a500ff800065ae610f7c80f532d6/peewee-3.18.2.tar.gz", hash = "sha256:77a54263eb61aff2ea72f63d2eeb91b140c25c1884148e28e4c0f7c4f64996a0", size = 949220, upload-time = "2025-07-08T12:52:03.941Z" } + [[package]] name = "peft" version = "0.16.0" @@ -6045,38 +6051,6 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/5c/c6/f8f28009920a736d0df434b52e9feebfb4d702ba942f15338cb4a83eafc1/virtualenv-20.32.0-py3-none-any.whl", hash = "sha256:2c310aecb62e5aa1b06103ed7c2977b81e042695de2697d01017ff0f1034af56", size = 6057761, upload-time = "2025-07-21T04:09:48.059Z" }, ] -[[package]] -name = "watchdog" -version = "6.0.0" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/db/7d/7f3d619e951c88ed75c6037b246ddcf2d322812ee8ea189be89511721d54/watchdog-6.0.0.tar.gz", hash = "sha256:9ddf7c82fda3ae8e24decda1338ede66e1c99883db93711d8fb941eaa2d8c282", size = 131220, upload-time = "2024-11-01T14:07:13.037Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/0c/56/90994d789c61df619bfc5ce2ecdabd5eeff564e1eb47512bd01b5e019569/watchdog-6.0.0-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:d1cdb490583ebd691c012b3d6dae011000fe42edb7a82ece80965b42abd61f26", size = 96390, upload-time = "2024-11-01T14:06:24.793Z" }, - { url = "https://files.pythonhosted.org/packages/55/46/9a67ee697342ddf3c6daa97e3a587a56d6c4052f881ed926a849fcf7371c/watchdog-6.0.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:bc64ab3bdb6a04d69d4023b29422170b74681784ffb9463ed4870cf2f3e66112", size = 88389, upload-time = "2024-11-01T14:06:27.112Z" }, - { url = "https://files.pythonhosted.org/packages/44/65/91b0985747c52064d8701e1075eb96f8c40a79df889e59a399453adfb882/watchdog-6.0.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:c897ac1b55c5a1461e16dae288d22bb2e412ba9807df8397a635d88f671d36c3", size = 89020, upload-time = "2024-11-01T14:06:29.876Z" }, - { url = "https://files.pythonhosted.org/packages/e0/24/d9be5cd6642a6aa68352ded4b4b10fb0d7889cb7f45814fb92cecd35f101/watchdog-6.0.0-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:6eb11feb5a0d452ee41f824e271ca311a09e250441c262ca2fd7ebcf2461a06c", size = 96393, upload-time = "2024-11-01T14:06:31.756Z" }, - { url = "https://files.pythonhosted.org/packages/63/7a/6013b0d8dbc56adca7fdd4f0beed381c59f6752341b12fa0886fa7afc78b/watchdog-6.0.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:ef810fbf7b781a5a593894e4f439773830bdecb885e6880d957d5b9382a960d2", size = 88392, upload-time = "2024-11-01T14:06:32.99Z" }, - { url = "https://files.pythonhosted.org/packages/d1/40/b75381494851556de56281e053700e46bff5b37bf4c7267e858640af5a7f/watchdog-6.0.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:afd0fe1b2270917c5e23c2a65ce50c2a4abb63daafb0d419fde368e272a76b7c", size = 89019, upload-time = "2024-11-01T14:06:34.963Z" }, - { url = "https://files.pythonhosted.org/packages/39/ea/3930d07dafc9e286ed356a679aa02d777c06e9bfd1164fa7c19c288a5483/watchdog-6.0.0-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:bdd4e6f14b8b18c334febb9c4425a878a2ac20efd1e0b231978e7b150f92a948", size = 96471, upload-time = "2024-11-01T14:06:37.745Z" }, - { url = "https://files.pythonhosted.org/packages/12/87/48361531f70b1f87928b045df868a9fd4e253d9ae087fa4cf3f7113be363/watchdog-6.0.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:c7c15dda13c4eb00d6fb6fc508b3c0ed88b9d5d374056b239c4ad1611125c860", size = 88449, upload-time = "2024-11-01T14:06:39.748Z" }, - { url = "https://files.pythonhosted.org/packages/5b/7e/8f322f5e600812e6f9a31b75d242631068ca8f4ef0582dd3ae6e72daecc8/watchdog-6.0.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:6f10cb2d5902447c7d0da897e2c6768bca89174d0c6e1e30abec5421af97a5b0", size = 89054, upload-time = "2024-11-01T14:06:41.009Z" }, - { url = "https://files.pythonhosted.org/packages/68/98/b0345cabdce2041a01293ba483333582891a3bd5769b08eceb0d406056ef/watchdog-6.0.0-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:490ab2ef84f11129844c23fb14ecf30ef3d8a6abafd3754a6f75ca1e6654136c", size = 96480, upload-time = "2024-11-01T14:06:42.952Z" }, - { url = "https://files.pythonhosted.org/packages/85/83/cdf13902c626b28eedef7ec4f10745c52aad8a8fe7eb04ed7b1f111ca20e/watchdog-6.0.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:76aae96b00ae814b181bb25b1b98076d5fc84e8a53cd8885a318b42b6d3a5134", size = 88451, upload-time = "2024-11-01T14:06:45.084Z" }, - { url = "https://files.pythonhosted.org/packages/fe/c4/225c87bae08c8b9ec99030cd48ae9c4eca050a59bf5c2255853e18c87b50/watchdog-6.0.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:a175f755fc2279e0b7312c0035d52e27211a5bc39719dd529625b1930917345b", size = 89057, upload-time = "2024-11-01T14:06:47.324Z" }, - { url = "https://files.pythonhosted.org/packages/30/ad/d17b5d42e28a8b91f8ed01cb949da092827afb9995d4559fd448d0472763/watchdog-6.0.0-pp310-pypy310_pp73-macosx_10_15_x86_64.whl", hash = "sha256:c7ac31a19f4545dd92fc25d200694098f42c9a8e391bc00bdd362c5736dbf881", size = 87902, upload-time = "2024-11-01T14:06:53.119Z" }, - { url = "https://files.pythonhosted.org/packages/5c/ca/c3649991d140ff6ab67bfc85ab42b165ead119c9e12211e08089d763ece5/watchdog-6.0.0-pp310-pypy310_pp73-macosx_11_0_arm64.whl", hash = "sha256:9513f27a1a582d9808cf21a07dae516f0fab1cf2d7683a742c498b93eedabb11", size = 88380, upload-time = "2024-11-01T14:06:55.19Z" }, - { url = "https://files.pythonhosted.org/packages/a9/c7/ca4bf3e518cb57a686b2feb4f55a1892fd9a3dd13f470fca14e00f80ea36/watchdog-6.0.0-py3-none-manylinux2014_aarch64.whl", hash = "sha256:7607498efa04a3542ae3e05e64da8202e58159aa1fa4acddf7678d34a35d4f13", size = 79079, upload-time = "2024-11-01T14:06:59.472Z" }, - { url = "https://files.pythonhosted.org/packages/5c/51/d46dc9332f9a647593c947b4b88e2381c8dfc0942d15b8edc0310fa4abb1/watchdog-6.0.0-py3-none-manylinux2014_armv7l.whl", hash = "sha256:9041567ee8953024c83343288ccc458fd0a2d811d6a0fd68c4c22609e3490379", size = 79078, upload-time = "2024-11-01T14:07:01.431Z" }, - { url = "https://files.pythonhosted.org/packages/d4/57/04edbf5e169cd318d5f07b4766fee38e825d64b6913ca157ca32d1a42267/watchdog-6.0.0-py3-none-manylinux2014_i686.whl", hash = "sha256:82dc3e3143c7e38ec49d61af98d6558288c415eac98486a5c581726e0737c00e", size = 79076, upload-time = "2024-11-01T14:07:02.568Z" }, - { url = "https://files.pythonhosted.org/packages/ab/cc/da8422b300e13cb187d2203f20b9253e91058aaf7db65b74142013478e66/watchdog-6.0.0-py3-none-manylinux2014_ppc64.whl", hash = "sha256:212ac9b8bf1161dc91bd09c048048a95ca3a4c4f5e5d4a7d1b1a7d5752a7f96f", size = 79077, upload-time = "2024-11-01T14:07:03.893Z" }, - { url = "https://files.pythonhosted.org/packages/2c/3b/b8964e04ae1a025c44ba8e4291f86e97fac443bca31de8bd98d3263d2fcf/watchdog-6.0.0-py3-none-manylinux2014_ppc64le.whl", hash = "sha256:e3df4cbb9a450c6d49318f6d14f4bbc80d763fa587ba46ec86f99f9e6876bb26", size = 79078, upload-time = "2024-11-01T14:07:05.189Z" }, - { url = "https://files.pythonhosted.org/packages/62/ae/a696eb424bedff7407801c257d4b1afda455fe40821a2be430e173660e81/watchdog-6.0.0-py3-none-manylinux2014_s390x.whl", hash = "sha256:2cce7cfc2008eb51feb6aab51251fd79b85d9894e98ba847408f662b3395ca3c", size = 79077, upload-time = "2024-11-01T14:07:06.376Z" }, - { url = "https://files.pythonhosted.org/packages/b5/e8/dbf020b4d98251a9860752a094d09a65e1b436ad181faf929983f697048f/watchdog-6.0.0-py3-none-manylinux2014_x86_64.whl", hash = "sha256:20ffe5b202af80ab4266dcd3e91aae72bf2da48c0d33bdb15c66658e685e94e2", size = 79078, upload-time = "2024-11-01T14:07:07.547Z" }, - { url = "https://files.pythonhosted.org/packages/07/f6/d0e5b343768e8bcb4cda79f0f2f55051bf26177ecd5651f84c07567461cf/watchdog-6.0.0-py3-none-win32.whl", hash = "sha256:07df1fdd701c5d4c8e55ef6cf55b8f0120fe1aef7ef39a1c6fc6bc2e606d517a", size = 79065, upload-time = "2024-11-01T14:07:09.525Z" }, - { url = "https://files.pythonhosted.org/packages/db/d9/c495884c6e548fce18a8f40568ff120bc3a4b7b99813081c8ac0c936fa64/watchdog-6.0.0-py3-none-win_amd64.whl", hash = "sha256:cbafb470cf848d93b5d013e2ecb245d4aa1c8fd0504e863ccefa32445359d680", size = 79070, upload-time = "2024-11-01T14:07:10.686Z" }, - { url = "https://files.pythonhosted.org/packages/33/e8/e40370e6d74ddba47f002a32919d91310d6074130fe4e17dabcafc15cbf1/watchdog-6.0.0-py3-none-win_ia64.whl", hash = "sha256:a1914259fa9e1454315171103c6a30961236f508b9b623eae470268bbcc6a22f", size = 79067, upload-time = "2024-11-01T14:07:11.845Z" }, -] - [[package]] name = "watchfiles" version = "1.1.0" diff --git a/vite-app/dist/assets/index-BMc_e8JT.js b/vite-app/dist/assets/index-CmKCiozr.js similarity index 60% rename from vite-app/dist/assets/index-BMc_e8JT.js rename to vite-app/dist/assets/index-CmKCiozr.js index 67e5a177..c0e21f08 100644 --- a/vite-app/dist/assets/index-BMc_e8JT.js +++ b/vite-app/dist/assets/index-CmKCiozr.js @@ -1,4 +1,4 @@ -(function(){const l=document.createElement("link").relList;if(l&&l.supports&&l.supports("modulepreload"))return;for(const c of document.querySelectorAll('link[rel="modulepreload"]'))r(c);new MutationObserver(c=>{for(const f of c)if(f.type==="childList")for(const d of f.addedNodes)d.tagName==="LINK"&&d.rel==="modulepreload"&&r(d)}).observe(document,{childList:!0,subtree:!0});function i(c){const f={};return c.integrity&&(f.integrity=c.integrity),c.referrerPolicy&&(f.referrerPolicy=c.referrerPolicy),c.crossOrigin==="use-credentials"?f.credentials="include":c.crossOrigin==="anonymous"?f.credentials="omit":f.credentials="same-origin",f}function r(c){if(c.ep)return;c.ep=!0;const f=i(c);fetch(c.href,f)}})();function kp(n){return n&&n.__esModule&&Object.prototype.hasOwnProperty.call(n,"default")?n.default:n}var $s={exports:{}},Yu={};/** +(function(){const l=document.createElement("link").relList;if(l&&l.supports&&l.supports("modulepreload"))return;for(const c of document.querySelectorAll('link[rel="modulepreload"]'))r(c);new MutationObserver(c=>{for(const f of c)if(f.type==="childList")for(const d of f.addedNodes)d.tagName==="LINK"&&d.rel==="modulepreload"&&r(d)}).observe(document,{childList:!0,subtree:!0});function i(c){const f={};return c.integrity&&(f.integrity=c.integrity),c.referrerPolicy&&(f.referrerPolicy=c.referrerPolicy),c.crossOrigin==="use-credentials"?f.credentials="include":c.crossOrigin==="anonymous"?f.credentials="omit":f.credentials="same-origin",f}function r(c){if(c.ep)return;c.ep=!0;const f=i(c);fetch(c.href,f)}})();function $p(n){return n&&n.__esModule&&Object.prototype.hasOwnProperty.call(n,"default")?n.default:n}var ks={exports:{}},Yu={};/** * @license React * react-jsx-runtime.production.js * @@ -6,7 +6,7 @@ * * This source code is licensed under the MIT license found in the * LICENSE file in the root directory of this source tree. - */var Dm;function G0(){if(Dm)return Yu;Dm=1;var n=Symbol.for("react.transitional.element"),l=Symbol.for("react.fragment");function i(r,c,f){var d=null;if(f!==void 0&&(d=""+f),c.key!==void 0&&(d=""+c.key),"key"in c){f={};for(var v in c)v!=="key"&&(f[v]=c[v])}else f=c;return c=f.ref,{$$typeof:n,type:r,key:d,ref:c!==void 0?c:null,props:f}}return Yu.Fragment=l,Yu.jsx=i,Yu.jsxs=i,Yu}var Mm;function X0(){return Mm||(Mm=1,$s.exports=G0()),$s.exports}var D=X0(),qs={exports:{}},oe={};/** + */var Mm;function X0(){if(Mm)return Yu;Mm=1;var n=Symbol.for("react.transitional.element"),l=Symbol.for("react.fragment");function i(r,c,f){var d=null;if(f!==void 0&&(d=""+f),c.key!==void 0&&(d=""+c.key),"key"in c){f={};for(var v in c)v!=="key"&&(f[v]=c[v])}else f=c;return c=f.ref,{$$typeof:n,type:r,key:d,ref:c!==void 0?c:null,props:f}}return Yu.Fragment=l,Yu.jsx=i,Yu.jsxs=i,Yu}var Nm;function Q0(){return Nm||(Nm=1,ks.exports=X0()),ks.exports}var D=Q0(),$s={exports:{}},oe={};/** * @license React * react.production.js * @@ -14,7 +14,7 @@ * * This source code is licensed under the MIT license found in the * LICENSE file in the root directory of this source tree. - */var Nm;function Q0(){if(Nm)return oe;Nm=1;var n=Symbol.for("react.transitional.element"),l=Symbol.for("react.portal"),i=Symbol.for("react.fragment"),r=Symbol.for("react.strict_mode"),c=Symbol.for("react.profiler"),f=Symbol.for("react.consumer"),d=Symbol.for("react.context"),v=Symbol.for("react.forward_ref"),m=Symbol.for("react.suspense"),p=Symbol.for("react.memo"),y=Symbol.for("react.lazy"),x=Symbol.iterator;function z(b){return b===null||typeof b!="object"?null:(b=x&&b[x]||b["@@iterator"],typeof b=="function"?b:null)}var B={isMounted:function(){return!1},enqueueForceUpdate:function(){},enqueueReplaceState:function(){},enqueueSetState:function(){}},H=Object.assign,L={};function q(b,Z,X){this.props=b,this.context=Z,this.refs=L,this.updater=X||B}q.prototype.isReactComponent={},q.prototype.setState=function(b,Z){if(typeof b!="object"&&typeof b!="function"&&b!=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,b,Z,"setState")},q.prototype.forceUpdate=function(b){this.updater.enqueueForceUpdate(this,b,"forceUpdate")};function k(){}k.prototype=q.prototype;function F(b,Z,X){this.props=b,this.context=Z,this.refs=L,this.updater=X||B}var G=F.prototype=new k;G.constructor=F,H(G,q.prototype),G.isPureReactComponent=!0;var I=Array.isArray,K={H:null,A:null,T:null,S:null,V:null},me=Object.prototype.hasOwnProperty;function Re(b,Z,X,V,ee,pe){return X=pe.ref,{$$typeof:n,type:b,key:Z,ref:X!==void 0?X:null,props:pe}}function Ye(b,Z){return Re(b.type,Z,void 0,void 0,void 0,b.props)}function ae(b){return typeof b=="object"&&b!==null&&b.$$typeof===n}function $e(b){var Z={"=":"=0",":":"=2"};return"$"+b.replace(/[=:]/g,function(X){return Z[X]})}var Ie=/\/+/g;function Ge(b,Z){return typeof b=="object"&&b!==null&&b.key!=null?$e(""+b.key):Z.toString(36)}function Jt(){}function qn(b){switch(b.status){case"fulfilled":return b.value;case"rejected":throw b.reason;default:switch(typeof b.status=="string"?b.then(Jt,Jt):(b.status="pending",b.then(function(Z){b.status==="pending"&&(b.status="fulfilled",b.value=Z)},function(Z){b.status==="pending"&&(b.status="rejected",b.reason=Z)})),b.status){case"fulfilled":return b.value;case"rejected":throw b.reason}}throw b}function ut(b,Z,X,V,ee){var pe=typeof b;(pe==="undefined"||pe==="boolean")&&(b=null);var re=!1;if(b===null)re=!0;else switch(pe){case"bigint":case"string":case"number":re=!0;break;case"object":switch(b.$$typeof){case n:case l:re=!0;break;case y:return re=b._init,ut(re(b._payload),Z,X,V,ee)}}if(re)return ee=ee(b),re=V===""?"."+Ge(b,0):V,I(ee)?(X="",re!=null&&(X=re.replace(Ie,"$&/")+"/"),ut(ee,Z,X,"",function(Vn){return Vn})):ee!=null&&(ae(ee)&&(ee=Ye(ee,X+(ee.key==null||b&&b.key===ee.key?"":(""+ee.key).replace(Ie,"$&/")+"/")+re)),Z.push(ee)),1;re=0;var St=V===""?".":V+":";if(I(b))for(var Ne=0;Ne>>1,b=M[Ae];if(0>>1;Aec(V,le))eec(pe,V)?(M[Ae]=pe,M[ee]=le,Ae=ee):(M[Ae]=V,M[X]=le,Ae=X);else if(eec(pe,le))M[Ae]=pe,M[ee]=le,Ae=ee;else break e}}return Y}function c(M,Y){var le=M.sortIndex-Y.sortIndex;return le!==0?le:M.id-Y.id}if(n.unstable_now=void 0,typeof performance=="object"&&typeof performance.now=="function"){var f=performance;n.unstable_now=function(){return f.now()}}else{var d=Date,v=d.now();n.unstable_now=function(){return d.now()-v}}var m=[],p=[],y=1,x=null,z=3,B=!1,H=!1,L=!1,q=!1,k=typeof setTimeout=="function"?setTimeout:null,F=typeof clearTimeout=="function"?clearTimeout:null,G=typeof setImmediate<"u"?setImmediate:null;function I(M){for(var Y=i(p);Y!==null;){if(Y.callback===null)r(p);else if(Y.startTime<=M)r(p),Y.sortIndex=Y.expirationTime,l(m,Y);else break;Y=i(p)}}function K(M){if(L=!1,I(M),!H)if(i(m)!==null)H=!0,me||(me=!0,Ge());else{var Y=i(p);Y!==null&&ut(K,Y.startTime-M)}}var me=!1,Re=-1,Ye=5,ae=-1;function $e(){return q?!0:!(n.unstable_now()-aeM&&$e());){var Ae=x.callback;if(typeof Ae=="function"){x.callback=null,z=x.priorityLevel;var b=Ae(x.expirationTime<=M);if(M=n.unstable_now(),typeof b=="function"){x.callback=b,I(M),Y=!0;break t}x===i(m)&&r(m),I(M)}else r(m);x=i(m)}if(x!==null)Y=!0;else{var Z=i(p);Z!==null&&ut(K,Z.startTime-M),Y=!1}}break e}finally{x=null,z=le,B=!1}Y=void 0}}finally{Y?Ge():me=!1}}}var Ge;if(typeof G=="function")Ge=function(){G(Ie)};else if(typeof MessageChannel<"u"){var Jt=new MessageChannel,qn=Jt.port2;Jt.port1.onmessage=Ie,Ge=function(){qn.postMessage(null)}}else Ge=function(){k(Ie,0)};function ut(M,Y){Re=k(function(){M(n.unstable_now())},Y)}n.unstable_IdlePriority=5,n.unstable_ImmediatePriority=1,n.unstable_LowPriority=4,n.unstable_NormalPriority=3,n.unstable_Profiling=null,n.unstable_UserBlockingPriority=2,n.unstable_cancelCallback=function(M){M.callback=null},n.unstable_forceFrameRate=function(M){0>M||125Ae?(M.sortIndex=le,l(p,M),i(m)===null&&M===i(p)&&(L?(F(Re),Re=-1):L=!0,ut(K,le-Ae))):(M.sortIndex=b,l(m,M),H||B||(H=!0,me||(me=!0,Ge()))),M},n.unstable_shouldYield=$e,n.unstable_wrapCallback=function(M){var Y=z;return function(){var le=z;z=Y;try{return M.apply(this,arguments)}finally{z=le}}}}(Gs)),Gs}var Um;function J0(){return Um||(Um=1,Ys.exports=K0()),Ys.exports}var Xs={exports:{}},ft={};/** + */var Um;function J0(){return Um||(Um=1,function(n){function l(M,Y){var le=M.length;M.push(Y);e:for(;0>>1,b=M[Ae];if(0>>1;Aec(V,le))eec(pe,V)?(M[Ae]=pe,M[ee]=le,Ae=ee):(M[Ae]=V,M[X]=le,Ae=X);else if(eec(pe,le))M[Ae]=pe,M[ee]=le,Ae=ee;else break e}}return Y}function c(M,Y){var le=M.sortIndex-Y.sortIndex;return le!==0?le:M.id-Y.id}if(n.unstable_now=void 0,typeof performance=="object"&&typeof performance.now=="function"){var f=performance;n.unstable_now=function(){return f.now()}}else{var d=Date,v=d.now();n.unstable_now=function(){return d.now()-v}}var m=[],p=[],y=1,x=null,z=3,B=!1,H=!1,L=!1,q=!1,k=typeof setTimeout=="function"?setTimeout:null,F=typeof clearTimeout=="function"?clearTimeout:null,G=typeof setImmediate<"u"?setImmediate:null;function I(M){for(var Y=i(p);Y!==null;){if(Y.callback===null)r(p);else if(Y.startTime<=M)r(p),Y.sortIndex=Y.expirationTime,l(m,Y);else break;Y=i(p)}}function K(M){if(L=!1,I(M),!H)if(i(m)!==null)H=!0,me||(me=!0,Ge());else{var Y=i(p);Y!==null&<(K,Y.startTime-M)}}var me=!1,we=-1,Ye=5,ae=-1;function $e(){return q?!0:!(n.unstable_now()-aeM&&$e());){var Ae=x.callback;if(typeof Ae=="function"){x.callback=null,z=x.priorityLevel;var b=Ae(x.expirationTime<=M);if(M=n.unstable_now(),typeof b=="function"){x.callback=b,I(M),Y=!0;break t}x===i(m)&&r(m),I(M)}else r(m);x=i(m)}if(x!==null)Y=!0;else{var Z=i(p);Z!==null&<(K,Z.startTime-M),Y=!1}}break e}finally{x=null,z=le,B=!1}Y=void 0}}finally{Y?Ge():me=!1}}}var Ge;if(typeof G=="function")Ge=function(){G(Fe)};else if(typeof MessageChannel<"u"){var Jt=new MessageChannel,qn=Jt.port2;Jt.port1.onmessage=Fe,Ge=function(){qn.postMessage(null)}}else Ge=function(){k(Fe,0)};function lt(M,Y){we=k(function(){M(n.unstable_now())},Y)}n.unstable_IdlePriority=5,n.unstable_ImmediatePriority=1,n.unstable_LowPriority=4,n.unstable_NormalPriority=3,n.unstable_Profiling=null,n.unstable_UserBlockingPriority=2,n.unstable_cancelCallback=function(M){M.callback=null},n.unstable_forceFrameRate=function(M){0>M||125Ae?(M.sortIndex=le,l(p,M),i(m)===null&&M===i(p)&&(L?(F(we),we=-1):L=!0,lt(K,le-Ae))):(M.sortIndex=b,l(m,M),H||B||(H=!0,me||(me=!0,Ge()))),M},n.unstable_shouldYield=$e,n.unstable_wrapCallback=function(M){var Y=z;return function(){var le=z;z=Y;try{return M.apply(this,arguments)}finally{z=le}}}}(Ys)),Ys}var Zm;function P0(){return Zm||(Zm=1,Vs.exports=J0()),Vs.exports}var Gs={exports:{}},ft={};/** * @license React * react-dom.production.js * @@ -30,7 +30,7 @@ * * This source code is licensed under the MIT license found in the * LICENSE file in the root directory of this source tree. - */var Zm;function P0(){if(Zm)return ft;Zm=1;var n=uo();function l(m){var p="https://react.dev/errors/"+m;if(1"u"||typeof __REACT_DEVTOOLS_GLOBAL_HOOK__.checkDCE!="function"))try{__REACT_DEVTOOLS_GLOBAL_HOOK__.checkDCE(n)}catch(l){console.error(l)}}return n(),Xs.exports=P0(),Xs.exports}/** + */var Bm;function W0(){if(Bm)return ft;Bm=1;var n=uo();function l(m){var p="https://react.dev/errors/"+m;if(1"u"||typeof __REACT_DEVTOOLS_GLOBAL_HOOK__.checkDCE!="function"))try{__REACT_DEVTOOLS_GLOBAL_HOOK__.checkDCE(n)}catch(l){console.error(l)}}return n(),Gs.exports=W0(),Gs.exports}/** * @license React * react-dom-client.production.js * @@ -38,15 +38,15 @@ * * This source code is licensed under the MIT license found in the * LICENSE file in the root directory of this source tree. - */var Lm;function W0(){if(Lm)return Gu;Lm=1;var n=J0(),l=uo(),i=$p();function r(e){var t="https://react.dev/errors/"+e;if(1b||(e.current=Ae[b],Ae[b]=null,b--)}function V(e,t){b++,Ae[b]=e.current,e.current=t}var ee=Z(null),pe=Z(null),re=Z(null),St=Z(null);function Ne(e,t){switch(V(re,t),V(pe,e),V(ee,null),t.nodeType){case 9:case 11:e=(e=t.documentElement)&&(e=e.namespaceURI)?lm(e):0;break;default:if(e=t.tagName,t=t.namespaceURI)t=lm(t),e=um(t,e);else switch(e){case"svg":e=1;break;case"math":e=2;break;default:e=0}}X(ee),V(ee,e)}function Vn(){X(ee),X(pe),X(re)}function To(e){e.memoizedState!==null&&V(St,e);var t=ee.current,a=um(t,e.type);t!==a&&(V(pe,e),V(ee,a))}function pi(e){pe.current===e&&(X(ee),X(pe)),St.current===e&&(X(St),Hu._currentValue=le)}var zo=Object.prototype.hasOwnProperty,Ro=n.unstable_scheduleCallback,wo=n.unstable_cancelCallback,E_=n.unstable_shouldYield,x_=n.unstable_requestPaint,ln=n.unstable_now,A_=n.unstable_getCurrentPriorityLevel,Lf=n.unstable_ImmediatePriority,Hf=n.unstable_UserBlockingPriority,gi=n.unstable_NormalPriority,T_=n.unstable_LowPriority,kf=n.unstable_IdlePriority,z_=n.log,R_=n.unstable_setDisableYieldValue,Ql=null,Ot=null;function Yn(e){if(typeof z_=="function"&&R_(e),Ot&&typeof Ot.setStrictMode=="function")try{Ot.setStrictMode(Ql,e)}catch{}}var Et=Math.clz32?Math.clz32:M_,w_=Math.log,D_=Math.LN2;function M_(e){return e>>>=0,e===0?32:31-(w_(e)/D_|0)|0}var _i=256,yi=4194304;function ya(e){var t=e&42;if(t!==0)return t;switch(e&-e){case 1:return 1;case 2:return 2;case 4:return 4;case 8:return 8;case 16:return 16;case 32:return 32;case 64: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 e&4194048;case 4194304:case 8388608:case 16777216:case 33554432:return e&62914560;case 67108864:return 67108864;case 134217728:return 134217728;case 268435456:return 268435456;case 536870912:return 536870912;case 1073741824:return 0;default:return e}}function bi(e,t,a){var u=e.pendingLanes;if(u===0)return 0;var o=0,s=e.suspendedLanes,h=e.pingedLanes;e=e.warmLanes;var g=u&134217727;return g!==0?(u=g&~s,u!==0?o=ya(u):(h&=g,h!==0?o=ya(h):a||(a=g&~e,a!==0&&(o=ya(a))))):(g=u&~s,g!==0?o=ya(g):h!==0?o=ya(h):a||(a=u&~e,a!==0&&(o=ya(a)))),o===0?0:t!==0&&t!==o&&(t&s)===0&&(s=o&-o,a=t&-t,s>=a||s===32&&(a&4194048)!==0)?t:o}function Kl(e,t){return(e.pendingLanes&~(e.suspendedLanes&~e.pingedLanes)&t)===0}function N_(e,t){switch(e){case 1:case 2:case 4:case 8:case 64:return t+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 t+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 $f(){var e=_i;return _i<<=1,(_i&4194048)===0&&(_i=256),e}function qf(){var e=yi;return yi<<=1,(yi&62914560)===0&&(yi=4194304),e}function Do(e){for(var t=[],a=0;31>a;a++)t.push(e);return t}function Jl(e,t){e.pendingLanes|=t,t!==268435456&&(e.suspendedLanes=0,e.pingedLanes=0,e.warmLanes=0)}function j_(e,t,a,u,o,s){var h=e.pendingLanes;e.pendingLanes=a,e.suspendedLanes=0,e.pingedLanes=0,e.warmLanes=0,e.expiredLanes&=a,e.entangledLanes&=a,e.errorRecoveryDisabledLanes&=a,e.shellSuspendCounter=0;var g=e.entanglements,_=e.expirationTimes,A=e.hiddenUpdates;for(a=h&~a;0b||(e.current=Ae[b],Ae[b]=null,b--)}function V(e,t){b++,Ae[b]=e.current,e.current=t}var ee=Z(null),pe=Z(null),re=Z(null),St=Z(null);function Ne(e,t){switch(V(re,t),V(pe,e),V(ee,null),t.nodeType){case 9:case 11:e=(e=t.documentElement)&&(e=e.namespaceURI)?um(e):0;break;default:if(e=t.tagName,t=t.namespaceURI)t=um(t),e=im(t,e);else switch(e){case"svg":e=1;break;case"math":e=2;break;default:e=0}}X(ee),V(ee,e)}function Vn(){X(ee),X(pe),X(re)}function Ao(e){e.memoizedState!==null&&V(St,e);var t=ee.current,a=im(t,e.type);t!==a&&(V(pe,e),V(ee,a))}function pi(e){pe.current===e&&(X(ee),X(pe)),St.current===e&&(X(St),Hu._currentValue=le)}var To=Object.prototype.hasOwnProperty,zo=n.unstable_scheduleCallback,wo=n.unstable_cancelCallback,x_=n.unstable_shouldYield,A_=n.unstable_requestPaint,ln=n.unstable_now,T_=n.unstable_getCurrentPriorityLevel,Hf=n.unstable_ImmediatePriority,kf=n.unstable_UserBlockingPriority,gi=n.unstable_NormalPriority,z_=n.unstable_LowPriority,$f=n.unstable_IdlePriority,w_=n.log,R_=n.unstable_setDisableYieldValue,Ql=null,Ot=null;function Yn(e){if(typeof w_=="function"&&R_(e),Ot&&typeof Ot.setStrictMode=="function")try{Ot.setStrictMode(Ql,e)}catch{}}var Et=Math.clz32?Math.clz32:N_,D_=Math.log,M_=Math.LN2;function N_(e){return e>>>=0,e===0?32:31-(D_(e)/M_|0)|0}var _i=256,yi=4194304;function ya(e){var t=e&42;if(t!==0)return t;switch(e&-e){case 1:return 1;case 2:return 2;case 4:return 4;case 8:return 8;case 16:return 16;case 32:return 32;case 64: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 e&4194048;case 4194304:case 8388608:case 16777216:case 33554432:return e&62914560;case 67108864:return 67108864;case 134217728:return 134217728;case 268435456:return 268435456;case 536870912:return 536870912;case 1073741824:return 0;default:return e}}function bi(e,t,a){var u=e.pendingLanes;if(u===0)return 0;var o=0,s=e.suspendedLanes,h=e.pingedLanes;e=e.warmLanes;var g=u&134217727;return g!==0?(u=g&~s,u!==0?o=ya(u):(h&=g,h!==0?o=ya(h):a||(a=g&~e,a!==0&&(o=ya(a))))):(g=u&~s,g!==0?o=ya(g):h!==0?o=ya(h):a||(a=u&~e,a!==0&&(o=ya(a)))),o===0?0:t!==0&&t!==o&&(t&s)===0&&(s=o&-o,a=t&-t,s>=a||s===32&&(a&4194048)!==0)?t:o}function Kl(e,t){return(e.pendingLanes&~(e.suspendedLanes&~e.pingedLanes)&t)===0}function j_(e,t){switch(e){case 1:case 2:case 4:case 8:case 64:return t+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 t+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 qf(){var e=_i;return _i<<=1,(_i&4194048)===0&&(_i=256),e}function Vf(){var e=yi;return yi<<=1,(yi&62914560)===0&&(yi=4194304),e}function Ro(e){for(var t=[],a=0;31>a;a++)t.push(e);return t}function Jl(e,t){e.pendingLanes|=t,t!==268435456&&(e.suspendedLanes=0,e.pingedLanes=0,e.warmLanes=0)}function C_(e,t,a,u,o,s){var h=e.pendingLanes;e.pendingLanes=a,e.suspendedLanes=0,e.pingedLanes=0,e.warmLanes=0,e.expiredLanes&=a,e.entangledLanes&=a,e.errorRecoveryDisabledLanes&=a,e.shellSuspendCounter=0;var g=e.entanglements,_=e.expirationTimes,A=e.hiddenUpdates;for(a=h&~a;0)":-1o||_[u]!==A[o]){var N=` -`+_[u].replace(" at new "," at ");return e.displayName&&N.includes("")&&(N=N.replace("",e.displayName)),N}while(1<=u&&0<=o);break}}}finally{Zo=!1,Error.prepareStackTrace=a}return(a=e?e.displayName||e.name:"")?el(a):""}function H_(e){switch(e.tag){case 26:case 27:case 5:return el(e.type);case 16:return el("Lazy");case 13:return el("Suspense");case 19:return el("SuspenseList");case 0:case 15:return Bo(e.type,!1);case 11:return Bo(e.type.render,!1);case 1:return Bo(e.type,!0);case 31:return el("Activity");default:return""}}function Ff(e){try{var t="";do t+=H_(e),e=e.return;while(e);return t}catch(a){return` +`+_[u].replace(" at new "," at ");return e.displayName&&N.includes("")&&(N=N.replace("",e.displayName)),N}while(1<=u&&0<=o);break}}}finally{Uo=!1,Error.prepareStackTrace=a}return(a=e?e.displayName||e.name:"")?el(a):""}function k_(e){switch(e.tag){case 26:case 27:case 5:return el(e.type);case 16:return el("Lazy");case 13:return el("Suspense");case 19:return el("SuspenseList");case 0:case 15:return Zo(e.type,!1);case 11:return Zo(e.type.render,!1);case 1:return Zo(e.type,!0);case 31:return el("Activity");default:return""}}function If(e){try{var t="";do t+=k_(e),e=e.return;while(e);return t}catch(a){return` Error generating stack: `+a.message+` -`+a.stack}}function Ut(e){switch(typeof e){case"bigint":case"boolean":case"number":case"string":case"undefined":return e;case"object":return e;default:return""}}function If(e){var t=e.type;return(e=e.nodeName)&&e.toLowerCase()==="input"&&(t==="checkbox"||t==="radio")}function k_(e){var t=If(e)?"checked":"value",a=Object.getOwnPropertyDescriptor(e.constructor.prototype,t),u=""+e[t];if(!e.hasOwnProperty(t)&&typeof a<"u"&&typeof a.get=="function"&&typeof a.set=="function"){var o=a.get,s=a.set;return Object.defineProperty(e,t,{configurable:!0,get:function(){return o.call(this)},set:function(h){u=""+h,s.call(this,h)}}),Object.defineProperty(e,t,{enumerable:a.enumerable}),{getValue:function(){return u},setValue:function(h){u=""+h},stopTracking:function(){e._valueTracker=null,delete e[t]}}}}function Ei(e){e._valueTracker||(e._valueTracker=k_(e))}function ed(e){if(!e)return!1;var t=e._valueTracker;if(!t)return!0;var a=t.getValue(),u="";return e&&(u=If(e)?e.checked?"true":"false":e.value),e=u,e!==a?(t.setValue(e),!0):!1}function xi(e){if(e=e||(typeof document<"u"?document:void 0),typeof e>"u")return null;try{return e.activeElement||e.body}catch{return e.body}}var $_=/[\n"\\]/g;function Zt(e){return e.replace($_,function(t){return"\\"+t.charCodeAt(0).toString(16)+" "})}function Lo(e,t,a,u,o,s,h,g){e.name="",h!=null&&typeof h!="function"&&typeof h!="symbol"&&typeof h!="boolean"?e.type=h:e.removeAttribute("type"),t!=null?h==="number"?(t===0&&e.value===""||e.value!=t)&&(e.value=""+Ut(t)):e.value!==""+Ut(t)&&(e.value=""+Ut(t)):h!=="submit"&&h!=="reset"||e.removeAttribute("value"),t!=null?Ho(e,h,Ut(t)):a!=null?Ho(e,h,Ut(a)):u!=null&&e.removeAttribute("value"),o==null&&s!=null&&(e.defaultChecked=!!s),o!=null&&(e.checked=o&&typeof o!="function"&&typeof o!="symbol"),g!=null&&typeof g!="function"&&typeof g!="symbol"&&typeof g!="boolean"?e.name=""+Ut(g):e.removeAttribute("name")}function td(e,t,a,u,o,s,h,g){if(s!=null&&typeof s!="function"&&typeof s!="symbol"&&typeof s!="boolean"&&(e.type=s),t!=null||a!=null){if(!(s!=="submit"&&s!=="reset"||t!=null))return;a=a!=null?""+Ut(a):"",t=t!=null?""+Ut(t):a,g||t===e.value||(e.value=t),e.defaultValue=t}u=u??o,u=typeof u!="function"&&typeof u!="symbol"&&!!u,e.checked=g?e.checked:!!u,e.defaultChecked=!!u,h!=null&&typeof h!="function"&&typeof h!="symbol"&&typeof h!="boolean"&&(e.name=h)}function Ho(e,t,a){t==="number"&&xi(e.ownerDocument)===e||e.defaultValue===""+a||(e.defaultValue=""+a)}function tl(e,t,a,u){if(e=e.options,t){t={};for(var o=0;o"u"||typeof window.document>"u"||typeof window.document.createElement>"u"),Yo=!1;if(bn)try{var Il={};Object.defineProperty(Il,"passive",{get:function(){Yo=!0}}),window.addEventListener("test",Il,Il),window.removeEventListener("test",Il,Il)}catch{Yo=!1}var Xn=null,Go=null,Ti=null;function od(){if(Ti)return Ti;var e,t=Go,a=t.length,u,o="value"in Xn?Xn.value:Xn.textContent,s=o.length;for(e=0;e=nu),vd=" ",md=!1;function pd(e,t){switch(e){case"keyup":return my.indexOf(t.keyCode)!==-1;case"keydown":return t.keyCode!==229;case"keypress":case"mousedown":case"focusout":return!0;default:return!1}}function gd(e){return e=e.detail,typeof e=="object"&&"data"in e?e.data:null}var ul=!1;function gy(e,t){switch(e){case"compositionend":return gd(t);case"keypress":return t.which!==32?null:(md=!0,vd);case"textInput":return e=t.data,e===vd&&md?null:e;default:return null}}function _y(e,t){if(ul)return e==="compositionend"||!Po&&pd(e,t)?(e=od(),Ti=Go=Xn=null,ul=!1,e):null;switch(e){case"paste":return null;case"keypress":if(!(t.ctrlKey||t.altKey||t.metaKey)||t.ctrlKey&&t.altKey){if(t.char&&1=t)return{node:a,offset:t-e};e=u}e:{for(;a;){if(a.nextSibling){a=a.nextSibling;break e}a=a.parentNode}a=void 0}a=Ad(a)}}function zd(e,t){return e&&t?e===t?!0:e&&e.nodeType===3?!1:t&&t.nodeType===3?zd(e,t.parentNode):"contains"in e?e.contains(t):e.compareDocumentPosition?!!(e.compareDocumentPosition(t)&16):!1:!1}function Rd(e){e=e!=null&&e.ownerDocument!=null&&e.ownerDocument.defaultView!=null?e.ownerDocument.defaultView:window;for(var t=xi(e.document);t instanceof e.HTMLIFrameElement;){try{var a=typeof t.contentWindow.location.href=="string"}catch{a=!1}if(a)e=t.contentWindow;else break;t=xi(e.document)}return t}function Io(e){var t=e&&e.nodeName&&e.nodeName.toLowerCase();return t&&(t==="input"&&(e.type==="text"||e.type==="search"||e.type==="tel"||e.type==="url"||e.type==="password")||t==="textarea"||e.contentEditable==="true")}var Ty=bn&&"documentMode"in document&&11>=document.documentMode,il=null,ec=null,iu=null,tc=!1;function wd(e,t,a){var u=a.window===a?a.document:a.nodeType===9?a:a.ownerDocument;tc||il==null||il!==xi(u)||(u=il,"selectionStart"in u&&Io(u)?u={start:u.selectionStart,end:u.selectionEnd}:(u=(u.ownerDocument&&u.ownerDocument.defaultView||window).getSelection(),u={anchorNode:u.anchorNode,anchorOffset:u.anchorOffset,focusNode:u.focusNode,focusOffset:u.focusOffset}),iu&&uu(iu,u)||(iu=u,u=pr(ec,"onSelect"),0>=h,o-=h,On=1<<32-Et(t)+o|a<s?s:8;var h=M.T,g={};M.T=g,kc(e,!1,t,a);try{var _=o(),A=M.S;if(A!==null&&A(g,_),_!==null&&typeof _=="object"&&typeof _.then=="function"){var N=Uy(_,u);Su(e,t,N,wt(e))}else Su(e,t,u,wt(e))}catch(U){Su(e,t,{then:function(){},status:"rejected",reason:U},wt())}finally{Y.p=s,M.T=h}}function ky(){}function Lc(e,t,a,u){if(e.tag!==5)throw Error(r(476));var o=Dh(e).queue;wh(e,o,t,le,a===null?ky:function(){return Mh(e),a(u)})}function Dh(e){var t=e.memoizedState;if(t!==null)return t;t={memoizedState:le,baseState:le,baseQueue:null,queue:{pending:null,lanes:0,dispatch:null,lastRenderedReducer:Tn,lastRenderedState:le},next:null};var a={};return t.next={memoizedState:a,baseState:a,baseQueue:null,queue:{pending:null,lanes:0,dispatch:null,lastRenderedReducer:Tn,lastRenderedState:a},next:null},e.memoizedState=t,e=e.alternate,e!==null&&(e.memoizedState=t),t}function Mh(e){var t=Dh(e).next.queue;Su(e,t,{},wt())}function Hc(){return st(Hu)}function Nh(){return Qe().memoizedState}function jh(){return Qe().memoizedState}function $y(e){for(var t=e.return;t!==null;){switch(t.tag){case 24:case 3:var a=wt();e=Jn(a);var u=Pn(t,e,a);u!==null&&(Dt(u,t,a),mu(u,t,a)),t={cache:mc()},e.payload=t;return}t=t.return}}function qy(e,t,a){var u=wt();a={lane:u,revertLane:0,action:a,hasEagerState:!1,eagerState:null,next:null},Pi(e)?Uh(t,a):(a=uc(e,t,a,u),a!==null&&(Dt(a,e,u),Zh(a,t,u)))}function Ch(e,t,a){var u=wt();Su(e,t,a,u)}function Su(e,t,a,u){var o={lane:u,revertLane:0,action:a,hasEagerState:!1,eagerState:null,next:null};if(Pi(e))Uh(t,o);else{var s=e.alternate;if(e.lanes===0&&(s===null||s.lanes===0)&&(s=t.lastRenderedReducer,s!==null))try{var h=t.lastRenderedState,g=s(h,a);if(o.hasEagerState=!0,o.eagerState=g,xt(g,h))return ji(e,t,o,0),we===null&&Ni(),!1}catch{}finally{}if(a=uc(e,t,o,u),a!==null)return Dt(a,e,u),Zh(a,t,u),!0}return!1}function kc(e,t,a,u){if(u={lane:2,revertLane:_s(),action:u,hasEagerState:!1,eagerState:null,next:null},Pi(e)){if(t)throw Error(r(479))}else t=uc(e,a,u,2),t!==null&&Dt(t,e,2)}function Pi(e){var t=e.alternate;return e===ce||t!==null&&t===ce}function Uh(e,t){pl=Yi=!0;var a=e.pending;a===null?t.next=t:(t.next=a.next,a.next=t),e.pending=t}function Zh(e,t,a){if((a&4194048)!==0){var u=t.lanes;u&=e.pendingLanes,a|=u,t.lanes=a,Yf(e,a)}}var Wi={readContext:st,use:Xi,useCallback:qe,useContext:qe,useEffect:qe,useImperativeHandle:qe,useLayoutEffect:qe,useInsertionEffect:qe,useMemo:qe,useReducer:qe,useRef:qe,useState:qe,useDebugValue:qe,useDeferredValue:qe,useTransition:qe,useSyncExternalStore:qe,useId:qe,useHostTransitionStatus:qe,useFormState:qe,useActionState:qe,useOptimistic:qe,useMemoCache:qe,useCacheRefresh:qe},Bh={readContext:st,use:Xi,useCallback:function(e,t){return mt().memoizedState=[e,t===void 0?null:t],e},useContext:st,useEffect:bh,useImperativeHandle:function(e,t,a){a=a!=null?a.concat([e]):null,Ji(4194308,4,xh.bind(null,t,e),a)},useLayoutEffect:function(e,t){return Ji(4194308,4,e,t)},useInsertionEffect:function(e,t){Ji(4,2,e,t)},useMemo:function(e,t){var a=mt();t=t===void 0?null:t;var u=e();if(Na){Yn(!0);try{e()}finally{Yn(!1)}}return a.memoizedState=[u,t],u},useReducer:function(e,t,a){var u=mt();if(a!==void 0){var o=a(t);if(Na){Yn(!0);try{a(t)}finally{Yn(!1)}}}else o=t;return u.memoizedState=u.baseState=o,e={pending:null,lanes:0,dispatch:null,lastRenderedReducer:e,lastRenderedState:o},u.queue=e,e=e.dispatch=qy.bind(null,ce,e),[u.memoizedState,e]},useRef:function(e){var t=mt();return e={current:e},t.memoizedState=e},useState:function(e){e=Cc(e);var t=e.queue,a=Ch.bind(null,ce,t);return t.dispatch=a,[e.memoizedState,a]},useDebugValue:Zc,useDeferredValue:function(e,t){var a=mt();return Bc(a,e,t)},useTransition:function(){var e=Cc(!1);return e=wh.bind(null,ce,e.queue,!0,!1),mt().memoizedState=e,[!1,e]},useSyncExternalStore:function(e,t,a){var u=ce,o=mt();if(ye){if(a===void 0)throw Error(r(407));a=a()}else{if(a=t(),we===null)throw Error(r(349));(he&124)!==0||lh(u,t,a)}o.memoizedState=a;var s={value:a,getSnapshot:t};return o.queue=s,bh(ih.bind(null,u,s,e),[e]),u.flags|=2048,_l(9,Ki(),uh.bind(null,u,s,a,t),null),a},useId:function(){var e=mt(),t=we.identifierPrefix;if(ye){var a=En,u=On;a=(u&~(1<<32-Et(u)-1)).toString(32)+a,t="«"+t+"R"+a,a=Gi++,0ne?(at=W,W=null):at=W.sibling;var ge=T(O,W,E[ne],C);if(ge===null){W===null&&(W=at);break}e&&W&&ge.alternate===null&&t(O,W),S=s(ge,S,ne),se===null?Q=ge:se.sibling=ge,se=ge,W=at}if(ne===E.length)return a(O,W),ye&&Ta(O,ne),Q;if(W===null){for(;nene?(at=W,W=null):at=W.sibling;var ha=T(O,W,ge.value,C);if(ha===null){W===null&&(W=at);break}e&&W&&ha.alternate===null&&t(O,W),S=s(ha,S,ne),se===null?Q=ha:se.sibling=ha,se=ha,W=at}if(ge.done)return a(O,W),ye&&Ta(O,ne),Q;if(W===null){for(;!ge.done;ne++,ge=E.next())ge=U(O,ge.value,C),ge!==null&&(S=s(ge,S,ne),se===null?Q=ge:se.sibling=ge,se=ge);return ye&&Ta(O,ne),Q}for(W=u(W);!ge.done;ne++,ge=E.next())ge=R(W,O,ne,ge.value,C),ge!==null&&(e&&ge.alternate!==null&&W.delete(ge.key===null?ne:ge.key),S=s(ge,S,ne),se===null?Q=ge:se.sibling=ge,se=ge);return e&&W.forEach(function(Y0){return t(O,Y0)}),ye&&Ta(O,ne),Q}function xe(O,S,E,C){if(typeof E=="object"&&E!==null&&E.type===H&&E.key===null&&(E=E.props.children),typeof E=="object"&&E!==null){switch(E.$$typeof){case z:e:{for(var Q=E.key;S!==null;){if(S.key===Q){if(Q=E.type,Q===H){if(S.tag===7){a(O,S.sibling),C=o(S,E.props.children),C.return=O,O=C;break e}}else if(S.elementType===Q||typeof Q=="object"&&Q!==null&&Q.$$typeof===Ye&&Hh(Q)===S.type){a(O,S.sibling),C=o(S,E.props),Eu(C,E),C.return=O,O=C;break e}a(O,S);break}else t(O,S);S=S.sibling}E.type===H?(C=xa(E.props.children,O.mode,C,E.key),C.return=O,O=C):(C=Ui(E.type,E.key,E.props,null,O.mode,C),Eu(C,E),C.return=O,O=C)}return h(O);case B:e:{for(Q=E.key;S!==null;){if(S.key===Q)if(S.tag===4&&S.stateNode.containerInfo===E.containerInfo&&S.stateNode.implementation===E.implementation){a(O,S.sibling),C=o(S,E.children||[]),C.return=O,O=C;break e}else{a(O,S);break}else t(O,S);S=S.sibling}C=oc(E,O.mode,C),C.return=O,O=C}return h(O);case Ye:return Q=E._init,E=Q(E._payload),xe(O,S,E,C)}if(ut(E))return ue(O,S,E,C);if(Ge(E)){if(Q=Ge(E),typeof Q!="function")throw Error(r(150));return E=Q.call(E),te(O,S,E,C)}if(typeof E.then=="function")return xe(O,S,Fi(E),C);if(E.$$typeof===G)return xe(O,S,Hi(O,E),C);Ii(O,E)}return typeof E=="string"&&E!==""||typeof E=="number"||typeof E=="bigint"?(E=""+E,S!==null&&S.tag===6?(a(O,S.sibling),C=o(S,E),C.return=O,O=C):(a(O,S),C=rc(E,O.mode,C),C.return=O,O=C),h(O)):a(O,S)}return function(O,S,E,C){try{Ou=0;var Q=xe(O,S,E,C);return yl=null,Q}catch(W){if(W===hu||W===$i)throw W;var se=At(29,W,null,O.mode);return se.lanes=C,se.return=O,se}finally{}}}var bl=kh(!0),$h=kh(!1),$t=Z(null),rn=null;function Fn(e){var t=e.alternate;V(Pe,Pe.current&1),V($t,e),rn===null&&(t===null||ml.current!==null||t.memoizedState!==null)&&(rn=e)}function qh(e){if(e.tag===22){if(V(Pe,Pe.current),V($t,e),rn===null){var t=e.alternate;t!==null&&t.memoizedState!==null&&(rn=e)}}else In()}function In(){V(Pe,Pe.current),V($t,$t.current)}function zn(e){X($t),rn===e&&(rn=null),X(Pe)}var Pe=Z(0);function er(e){for(var t=e;t!==null;){if(t.tag===13){var a=t.memoizedState;if(a!==null&&(a=a.dehydrated,a===null||a.data==="$?"||Ds(a)))return t}else if(t.tag===19&&t.memoizedProps.revealOrder!==void 0){if((t.flags&128)!==0)return t}else if(t.child!==null){t.child.return=t,t=t.child;continue}if(t===e)break;for(;t.sibling===null;){if(t.return===null||t.return===e)return null;t=t.return}t.sibling.return=t.return,t=t.sibling}return null}function $c(e,t,a,u){t=e.memoizedState,a=a(u,t),a=a==null?t:y({},t,a),e.memoizedState=a,e.lanes===0&&(e.updateQueue.baseState=a)}var qc={enqueueSetState:function(e,t,a){e=e._reactInternals;var u=wt(),o=Jn(u);o.payload=t,a!=null&&(o.callback=a),t=Pn(e,o,u),t!==null&&(Dt(t,e,u),mu(t,e,u))},enqueueReplaceState:function(e,t,a){e=e._reactInternals;var u=wt(),o=Jn(u);o.tag=1,o.payload=t,a!=null&&(o.callback=a),t=Pn(e,o,u),t!==null&&(Dt(t,e,u),mu(t,e,u))},enqueueForceUpdate:function(e,t){e=e._reactInternals;var a=wt(),u=Jn(a);u.tag=2,t!=null&&(u.callback=t),t=Pn(e,u,a),t!==null&&(Dt(t,e,a),mu(t,e,a))}};function Vh(e,t,a,u,o,s,h){return e=e.stateNode,typeof e.shouldComponentUpdate=="function"?e.shouldComponentUpdate(u,s,h):t.prototype&&t.prototype.isPureReactComponent?!uu(a,u)||!uu(o,s):!0}function Yh(e,t,a,u){e=t.state,typeof t.componentWillReceiveProps=="function"&&t.componentWillReceiveProps(a,u),typeof t.UNSAFE_componentWillReceiveProps=="function"&&t.UNSAFE_componentWillReceiveProps(a,u),t.state!==e&&qc.enqueueReplaceState(t,t.state,null)}function ja(e,t){var a=t;if("ref"in t){a={};for(var u in t)u!=="ref"&&(a[u]=t[u])}if(e=e.defaultProps){a===t&&(a=y({},a));for(var o in e)a[o]===void 0&&(a[o]=e[o])}return a}var tr=typeof reportError=="function"?reportError:function(e){if(typeof window=="object"&&typeof window.ErrorEvent=="function"){var t=new window.ErrorEvent("error",{bubbles:!0,cancelable:!0,message:typeof e=="object"&&e!==null&&typeof e.message=="string"?String(e.message):String(e),error:e});if(!window.dispatchEvent(t))return}else if(typeof process=="object"&&typeof process.emit=="function"){process.emit("uncaughtException",e);return}console.error(e)};function Gh(e){tr(e)}function Xh(e){console.error(e)}function Qh(e){tr(e)}function nr(e,t){try{var a=e.onUncaughtError;a(t.value,{componentStack:t.stack})}catch(u){setTimeout(function(){throw u})}}function Kh(e,t,a){try{var u=e.onCaughtError;u(a.value,{componentStack:a.stack,errorBoundary:t.tag===1?t.stateNode:null})}catch(o){setTimeout(function(){throw o})}}function Vc(e,t,a){return a=Jn(a),a.tag=3,a.payload={element:null},a.callback=function(){nr(e,t)},a}function Jh(e){return e=Jn(e),e.tag=3,e}function Ph(e,t,a,u){var o=a.type.getDerivedStateFromError;if(typeof o=="function"){var s=u.value;e.payload=function(){return o(s)},e.callback=function(){Kh(t,a,u)}}var h=a.stateNode;h!==null&&typeof h.componentDidCatch=="function"&&(e.callback=function(){Kh(t,a,u),typeof o!="function"&&(ua===null?ua=new Set([this]):ua.add(this));var g=u.stack;this.componentDidCatch(u.value,{componentStack:g!==null?g:""})})}function Yy(e,t,a,u,o){if(a.flags|=32768,u!==null&&typeof u=="object"&&typeof u.then=="function"){if(t=a.alternate,t!==null&&su(t,a,o,!0),a=$t.current,a!==null){switch(a.tag){case 13:return rn===null?hs():a.alternate===null&&ke===0&&(ke=3),a.flags&=-257,a.flags|=65536,a.lanes=o,u===_c?a.flags|=16384:(t=a.updateQueue,t===null?a.updateQueue=new Set([u]):t.add(u),ms(e,u,o)),!1;case 22:return a.flags|=65536,u===_c?a.flags|=16384:(t=a.updateQueue,t===null?(t={transitions:null,markerInstances:null,retryQueue:new Set([u])},a.updateQueue=t):(a=t.retryQueue,a===null?t.retryQueue=new Set([u]):a.add(u)),ms(e,u,o)),!1}throw Error(r(435,a.tag))}return ms(e,u,o),hs(),!1}if(ye)return t=$t.current,t!==null?((t.flags&65536)===0&&(t.flags|=256),t.flags|=65536,t.lanes=o,u!==fc&&(e=Error(r(422),{cause:u}),cu(Bt(e,a)))):(u!==fc&&(t=Error(r(423),{cause:u}),cu(Bt(t,a))),e=e.current.alternate,e.flags|=65536,o&=-o,e.lanes|=o,u=Bt(u,a),o=Vc(e.stateNode,u,o),Sc(e,o),ke!==4&&(ke=2)),!1;var s=Error(r(520),{cause:u});if(s=Bt(s,a),Du===null?Du=[s]:Du.push(s),ke!==4&&(ke=2),t===null)return!0;u=Bt(u,a),a=t;do{switch(a.tag){case 3:return a.flags|=65536,e=o&-o,a.lanes|=e,e=Vc(a.stateNode,u,e),Sc(a,e),!1;case 1:if(t=a.type,s=a.stateNode,(a.flags&128)===0&&(typeof t.getDerivedStateFromError=="function"||s!==null&&typeof s.componentDidCatch=="function"&&(ua===null||!ua.has(s))))return a.flags|=65536,o&=-o,a.lanes|=o,o=Jh(o),Ph(o,e,a,u),Sc(a,o),!1}a=a.return}while(a!==null);return!1}var Wh=Error(r(461)),tt=!1;function it(e,t,a,u){t.child=e===null?$h(t,null,a,u):bl(t,e.child,a,u)}function Fh(e,t,a,u,o){a=a.render;var s=t.ref;if("ref"in u){var h={};for(var g in u)g!=="ref"&&(h[g]=u[g])}else h=u;return Da(t),u=Tc(e,t,a,h,s,o),g=zc(),e!==null&&!tt?(Rc(e,t,o),Rn(e,t,o)):(ye&&g&&cc(t),t.flags|=1,it(e,t,u,o),t.child)}function Ih(e,t,a,u,o){if(e===null){var s=a.type;return typeof s=="function"&&!ic(s)&&s.defaultProps===void 0&&a.compare===null?(t.tag=15,t.type=s,ev(e,t,s,u,o)):(e=Ui(a.type,null,u,t,t.mode,o),e.ref=t.ref,e.return=t,t.child=e)}if(s=e.child,!Wc(e,o)){var h=s.memoizedProps;if(a=a.compare,a=a!==null?a:uu,a(h,u)&&e.ref===t.ref)return Rn(e,t,o)}return t.flags|=1,e=Sn(s,u),e.ref=t.ref,e.return=t,t.child=e}function ev(e,t,a,u,o){if(e!==null){var s=e.memoizedProps;if(uu(s,u)&&e.ref===t.ref)if(tt=!1,t.pendingProps=u=s,Wc(e,o))(e.flags&131072)!==0&&(tt=!0);else return t.lanes=e.lanes,Rn(e,t,o)}return Yc(e,t,a,u,o)}function tv(e,t,a){var u=t.pendingProps,o=u.children,s=e!==null?e.memoizedState:null;if(u.mode==="hidden"){if((t.flags&128)!==0){if(u=s!==null?s.baseLanes|a:a,e!==null){for(o=t.child=e.child,s=0;o!==null;)s=s|o.lanes|o.childLanes,o=o.sibling;t.childLanes=s&~u}else t.childLanes=0,t.child=null;return nv(e,t,u,a)}if((a&536870912)!==0)t.memoizedState={baseLanes:0,cachePool:null},e!==null&&ki(t,s!==null?s.cachePool:null),s!==null?eh(t,s):Ec(),qh(t);else return t.lanes=t.childLanes=536870912,nv(e,t,s!==null?s.baseLanes|a:a,a)}else s!==null?(ki(t,s.cachePool),eh(t,s),In(),t.memoizedState=null):(e!==null&&ki(t,null),Ec(),In());return it(e,t,o,a),t.child}function nv(e,t,a,u){var o=gc();return o=o===null?null:{parent:Je._currentValue,pool:o},t.memoizedState={baseLanes:a,cachePool:o},e!==null&&ki(t,null),Ec(),qh(t),e!==null&&su(e,t,u,!0),null}function ar(e,t){var a=t.ref;if(a===null)e!==null&&e.ref!==null&&(t.flags|=4194816);else{if(typeof a!="function"&&typeof a!="object")throw Error(r(284));(e===null||e.ref!==a)&&(t.flags|=4194816)}}function Yc(e,t,a,u,o){return Da(t),a=Tc(e,t,a,u,void 0,o),u=zc(),e!==null&&!tt?(Rc(e,t,o),Rn(e,t,o)):(ye&&u&&cc(t),t.flags|=1,it(e,t,a,o),t.child)}function av(e,t,a,u,o,s){return Da(t),t.updateQueue=null,a=nh(t,u,a,o),th(e),u=zc(),e!==null&&!tt?(Rc(e,t,s),Rn(e,t,s)):(ye&&u&&cc(t),t.flags|=1,it(e,t,a,s),t.child)}function lv(e,t,a,u,o){if(Da(t),t.stateNode===null){var s=sl,h=a.contextType;typeof h=="object"&&h!==null&&(s=st(h)),s=new a(u,s),t.memoizedState=s.state!==null&&s.state!==void 0?s.state:null,s.updater=qc,t.stateNode=s,s._reactInternals=t,s=t.stateNode,s.props=u,s.state=t.memoizedState,s.refs={},yc(t),h=a.contextType,s.context=typeof h=="object"&&h!==null?st(h):sl,s.state=t.memoizedState,h=a.getDerivedStateFromProps,typeof h=="function"&&($c(t,a,h,u),s.state=t.memoizedState),typeof a.getDerivedStateFromProps=="function"||typeof s.getSnapshotBeforeUpdate=="function"||typeof s.UNSAFE_componentWillMount!="function"&&typeof s.componentWillMount!="function"||(h=s.state,typeof s.componentWillMount=="function"&&s.componentWillMount(),typeof s.UNSAFE_componentWillMount=="function"&&s.UNSAFE_componentWillMount(),h!==s.state&&qc.enqueueReplaceState(s,s.state,null),gu(t,u,s,o),pu(),s.state=t.memoizedState),typeof s.componentDidMount=="function"&&(t.flags|=4194308),u=!0}else if(e===null){s=t.stateNode;var g=t.memoizedProps,_=ja(a,g);s.props=_;var A=s.context,N=a.contextType;h=sl,typeof N=="object"&&N!==null&&(h=st(N));var U=a.getDerivedStateFromProps;N=typeof U=="function"||typeof s.getSnapshotBeforeUpdate=="function",g=t.pendingProps!==g,N||typeof s.UNSAFE_componentWillReceiveProps!="function"&&typeof s.componentWillReceiveProps!="function"||(g||A!==h)&&Yh(t,s,u,h),Kn=!1;var T=t.memoizedState;s.state=T,gu(t,u,s,o),pu(),A=t.memoizedState,g||T!==A||Kn?(typeof U=="function"&&($c(t,a,U,u),A=t.memoizedState),(_=Kn||Vh(t,a,_,u,T,A,h))?(N||typeof s.UNSAFE_componentWillMount!="function"&&typeof s.componentWillMount!="function"||(typeof s.componentWillMount=="function"&&s.componentWillMount(),typeof s.UNSAFE_componentWillMount=="function"&&s.UNSAFE_componentWillMount()),typeof s.componentDidMount=="function"&&(t.flags|=4194308)):(typeof s.componentDidMount=="function"&&(t.flags|=4194308),t.memoizedProps=u,t.memoizedState=A),s.props=u,s.state=A,s.context=h,u=_):(typeof s.componentDidMount=="function"&&(t.flags|=4194308),u=!1)}else{s=t.stateNode,bc(e,t),h=t.memoizedProps,N=ja(a,h),s.props=N,U=t.pendingProps,T=s.context,A=a.contextType,_=sl,typeof A=="object"&&A!==null&&(_=st(A)),g=a.getDerivedStateFromProps,(A=typeof g=="function"||typeof s.getSnapshotBeforeUpdate=="function")||typeof s.UNSAFE_componentWillReceiveProps!="function"&&typeof s.componentWillReceiveProps!="function"||(h!==U||T!==_)&&Yh(t,s,u,_),Kn=!1,T=t.memoizedState,s.state=T,gu(t,u,s,o),pu();var R=t.memoizedState;h!==U||T!==R||Kn||e!==null&&e.dependencies!==null&&Li(e.dependencies)?(typeof g=="function"&&($c(t,a,g,u),R=t.memoizedState),(N=Kn||Vh(t,a,N,u,T,R,_)||e!==null&&e.dependencies!==null&&Li(e.dependencies))?(A||typeof s.UNSAFE_componentWillUpdate!="function"&&typeof s.componentWillUpdate!="function"||(typeof s.componentWillUpdate=="function"&&s.componentWillUpdate(u,R,_),typeof s.UNSAFE_componentWillUpdate=="function"&&s.UNSAFE_componentWillUpdate(u,R,_)),typeof s.componentDidUpdate=="function"&&(t.flags|=4),typeof s.getSnapshotBeforeUpdate=="function"&&(t.flags|=1024)):(typeof s.componentDidUpdate!="function"||h===e.memoizedProps&&T===e.memoizedState||(t.flags|=4),typeof s.getSnapshotBeforeUpdate!="function"||h===e.memoizedProps&&T===e.memoizedState||(t.flags|=1024),t.memoizedProps=u,t.memoizedState=R),s.props=u,s.state=R,s.context=_,u=N):(typeof s.componentDidUpdate!="function"||h===e.memoizedProps&&T===e.memoizedState||(t.flags|=4),typeof s.getSnapshotBeforeUpdate!="function"||h===e.memoizedProps&&T===e.memoizedState||(t.flags|=1024),u=!1)}return s=u,ar(e,t),u=(t.flags&128)!==0,s||u?(s=t.stateNode,a=u&&typeof a.getDerivedStateFromError!="function"?null:s.render(),t.flags|=1,e!==null&&u?(t.child=bl(t,e.child,null,o),t.child=bl(t,null,a,o)):it(e,t,a,o),t.memoizedState=s.state,e=t.child):e=Rn(e,t,o),e}function uv(e,t,a,u){return ou(),t.flags|=256,it(e,t,a,u),t.child}var Gc={dehydrated:null,treeContext:null,retryLane:0,hydrationErrors:null};function Xc(e){return{baseLanes:e,cachePool:Xd()}}function Qc(e,t,a){return e=e!==null?e.childLanes&~a:0,t&&(e|=qt),e}function iv(e,t,a){var u=t.pendingProps,o=!1,s=(t.flags&128)!==0,h;if((h=s)||(h=e!==null&&e.memoizedState===null?!1:(Pe.current&2)!==0),h&&(o=!0,t.flags&=-129),h=(t.flags&32)!==0,t.flags&=-33,e===null){if(ye){if(o?Fn(t):In(),ye){var g=He,_;if(_=g){e:{for(_=g,g=un;_.nodeType!==8;){if(!g){g=null;break e}if(_=Ft(_.nextSibling),_===null){g=null;break e}}g=_}g!==null?(t.memoizedState={dehydrated:g,treeContext:Aa!==null?{id:On,overflow:En}:null,retryLane:536870912,hydrationErrors:null},_=At(18,null,null,0),_.stateNode=g,_.return=t,t.child=_,dt=t,He=null,_=!0):_=!1}_||Ra(t)}if(g=t.memoizedState,g!==null&&(g=g.dehydrated,g!==null))return Ds(g)?t.lanes=32:t.lanes=536870912,null;zn(t)}return g=u.children,u=u.fallback,o?(In(),o=t.mode,g=lr({mode:"hidden",children:g},o),u=xa(u,o,a,null),g.return=t,u.return=t,g.sibling=u,t.child=g,o=t.child,o.memoizedState=Xc(a),o.childLanes=Qc(e,h,a),t.memoizedState=Gc,u):(Fn(t),Kc(t,g))}if(_=e.memoizedState,_!==null&&(g=_.dehydrated,g!==null)){if(s)t.flags&256?(Fn(t),t.flags&=-257,t=Jc(e,t,a)):t.memoizedState!==null?(In(),t.child=e.child,t.flags|=128,t=null):(In(),o=u.fallback,g=t.mode,u=lr({mode:"visible",children:u.children},g),o=xa(o,g,a,null),o.flags|=2,u.return=t,o.return=t,u.sibling=o,t.child=u,bl(t,e.child,null,a),u=t.child,u.memoizedState=Xc(a),u.childLanes=Qc(e,h,a),t.memoizedState=Gc,t=o);else if(Fn(t),Ds(g)){if(h=g.nextSibling&&g.nextSibling.dataset,h)var A=h.dgst;h=A,u=Error(r(419)),u.stack="",u.digest=h,cu({value:u,source:null,stack:null}),t=Jc(e,t,a)}else if(tt||su(e,t,a,!1),h=(a&e.childLanes)!==0,tt||h){if(h=we,h!==null&&(u=a&-a,u=(u&42)!==0?1:Mo(u),u=(u&(h.suspendedLanes|a))!==0?0:u,u!==0&&u!==_.retryLane))throw _.retryLane=u,cl(e,u),Dt(h,e,u),Wh;g.data==="$?"||hs(),t=Jc(e,t,a)}else g.data==="$?"?(t.flags|=192,t.child=e.child,t=null):(e=_.treeContext,He=Ft(g.nextSibling),dt=t,ye=!0,za=null,un=!1,e!==null&&(Ht[kt++]=On,Ht[kt++]=En,Ht[kt++]=Aa,On=e.id,En=e.overflow,Aa=t),t=Kc(t,u.children),t.flags|=4096);return t}return o?(In(),o=u.fallback,g=t.mode,_=e.child,A=_.sibling,u=Sn(_,{mode:"hidden",children:u.children}),u.subtreeFlags=_.subtreeFlags&65011712,A!==null?o=Sn(A,o):(o=xa(o,g,a,null),o.flags|=2),o.return=t,u.return=t,u.sibling=o,t.child=u,u=o,o=t.child,g=e.child.memoizedState,g===null?g=Xc(a):(_=g.cachePool,_!==null?(A=Je._currentValue,_=_.parent!==A?{parent:A,pool:A}:_):_=Xd(),g={baseLanes:g.baseLanes|a,cachePool:_}),o.memoizedState=g,o.childLanes=Qc(e,h,a),t.memoizedState=Gc,u):(Fn(t),a=e.child,e=a.sibling,a=Sn(a,{mode:"visible",children:u.children}),a.return=t,a.sibling=null,e!==null&&(h=t.deletions,h===null?(t.deletions=[e],t.flags|=16):h.push(e)),t.child=a,t.memoizedState=null,a)}function Kc(e,t){return t=lr({mode:"visible",children:t},e.mode),t.return=e,e.child=t}function lr(e,t){return e=At(22,e,null,t),e.lanes=0,e.stateNode={_visibility:1,_pendingMarkers:null,_retryCache:null,_transitions:null},e}function Jc(e,t,a){return bl(t,e.child,null,a),e=Kc(t,t.pendingProps.children),e.flags|=2,t.memoizedState=null,e}function rv(e,t,a){e.lanes|=t;var u=e.alternate;u!==null&&(u.lanes|=t),hc(e.return,t,a)}function Pc(e,t,a,u,o){var s=e.memoizedState;s===null?e.memoizedState={isBackwards:t,rendering:null,renderingStartTime:0,last:u,tail:a,tailMode:o}:(s.isBackwards=t,s.rendering=null,s.renderingStartTime=0,s.last=u,s.tail=a,s.tailMode=o)}function ov(e,t,a){var u=t.pendingProps,o=u.revealOrder,s=u.tail;if(it(e,t,u.children,a),u=Pe.current,(u&2)!==0)u=u&1|2,t.flags|=128;else{if(e!==null&&(e.flags&128)!==0)e:for(e=t.child;e!==null;){if(e.tag===13)e.memoizedState!==null&&rv(e,a,t);else if(e.tag===19)rv(e,a,t);else if(e.child!==null){e.child.return=e,e=e.child;continue}if(e===t)break e;for(;e.sibling===null;){if(e.return===null||e.return===t)break e;e=e.return}e.sibling.return=e.return,e=e.sibling}u&=1}switch(V(Pe,u),o){case"forwards":for(a=t.child,o=null;a!==null;)e=a.alternate,e!==null&&er(e)===null&&(o=a),a=a.sibling;a=o,a===null?(o=t.child,t.child=null):(o=a.sibling,a.sibling=null),Pc(t,!1,o,a,s);break;case"backwards":for(a=null,o=t.child,t.child=null;o!==null;){if(e=o.alternate,e!==null&&er(e)===null){t.child=o;break}e=o.sibling,o.sibling=a,a=o,o=e}Pc(t,!0,a,null,s);break;case"together":Pc(t,!1,null,null,void 0);break;default:t.memoizedState=null}return t.child}function Rn(e,t,a){if(e!==null&&(t.dependencies=e.dependencies),la|=t.lanes,(a&t.childLanes)===0)if(e!==null){if(su(e,t,a,!1),(a&t.childLanes)===0)return null}else return null;if(e!==null&&t.child!==e.child)throw Error(r(153));if(t.child!==null){for(e=t.child,a=Sn(e,e.pendingProps),t.child=a,a.return=t;e.sibling!==null;)e=e.sibling,a=a.sibling=Sn(e,e.pendingProps),a.return=t;a.sibling=null}return t.child}function Wc(e,t){return(e.lanes&t)!==0?!0:(e=e.dependencies,!!(e!==null&&Li(e)))}function Gy(e,t,a){switch(t.tag){case 3:Ne(t,t.stateNode.containerInfo),Qn(t,Je,e.memoizedState.cache),ou();break;case 27:case 5:To(t);break;case 4:Ne(t,t.stateNode.containerInfo);break;case 10:Qn(t,t.type,t.memoizedProps.value);break;case 13:var u=t.memoizedState;if(u!==null)return u.dehydrated!==null?(Fn(t),t.flags|=128,null):(a&t.child.childLanes)!==0?iv(e,t,a):(Fn(t),e=Rn(e,t,a),e!==null?e.sibling:null);Fn(t);break;case 19:var o=(e.flags&128)!==0;if(u=(a&t.childLanes)!==0,u||(su(e,t,a,!1),u=(a&t.childLanes)!==0),o){if(u)return ov(e,t,a);t.flags|=128}if(o=t.memoizedState,o!==null&&(o.rendering=null,o.tail=null,o.lastEffect=null),V(Pe,Pe.current),u)break;return null;case 22:case 23:return t.lanes=0,tv(e,t,a);case 24:Qn(t,Je,e.memoizedState.cache)}return Rn(e,t,a)}function cv(e,t,a){if(e!==null)if(e.memoizedProps!==t.pendingProps)tt=!0;else{if(!Wc(e,a)&&(t.flags&128)===0)return tt=!1,Gy(e,t,a);tt=(e.flags&131072)!==0}else tt=!1,ye&&(t.flags&1048576)!==0&&Hd(t,Bi,t.index);switch(t.lanes=0,t.tag){case 16:e:{e=t.pendingProps;var u=t.elementType,o=u._init;if(u=o(u._payload),t.type=u,typeof u=="function")ic(u)?(e=ja(u,e),t.tag=1,t=lv(null,t,u,e,a)):(t.tag=0,t=Yc(null,t,u,e,a));else{if(u!=null){if(o=u.$$typeof,o===I){t.tag=11,t=Fh(null,t,u,e,a);break e}else if(o===Re){t.tag=14,t=Ih(null,t,u,e,a);break e}}throw t=qn(u)||u,Error(r(306,t,""))}}return t;case 0:return Yc(e,t,t.type,t.pendingProps,a);case 1:return u=t.type,o=ja(u,t.pendingProps),lv(e,t,u,o,a);case 3:e:{if(Ne(t,t.stateNode.containerInfo),e===null)throw Error(r(387));u=t.pendingProps;var s=t.memoizedState;o=s.element,bc(e,t),gu(t,u,null,a);var h=t.memoizedState;if(u=h.cache,Qn(t,Je,u),u!==s.cache&&vc(t,[Je],a,!0),pu(),u=h.element,s.isDehydrated)if(s={element:u,isDehydrated:!1,cache:h.cache},t.updateQueue.baseState=s,t.memoizedState=s,t.flags&256){t=uv(e,t,u,a);break e}else if(u!==o){o=Bt(Error(r(424)),t),cu(o),t=uv(e,t,u,a);break e}else{switch(e=t.stateNode.containerInfo,e.nodeType){case 9:e=e.body;break;default:e=e.nodeName==="HTML"?e.ownerDocument.body:e}for(He=Ft(e.firstChild),dt=t,ye=!0,za=null,un=!0,a=$h(t,null,u,a),t.child=a;a;)a.flags=a.flags&-3|4096,a=a.sibling}else{if(ou(),u===o){t=Rn(e,t,a);break e}it(e,t,u,a)}t=t.child}return t;case 26:return ar(e,t),e===null?(a=hm(t.type,null,t.pendingProps,null))?t.memoizedState=a:ye||(a=t.type,e=t.pendingProps,u=_r(re.current).createElement(a),u[ct]=t,u[ht]=e,ot(u,a,e),et(u),t.stateNode=u):t.memoizedState=hm(t.type,e.memoizedProps,t.pendingProps,e.memoizedState),null;case 27:return To(t),e===null&&ye&&(u=t.stateNode=sm(t.type,t.pendingProps,re.current),dt=t,un=!0,o=He,oa(t.type)?(Ms=o,He=Ft(u.firstChild)):He=o),it(e,t,t.pendingProps.children,a),ar(e,t),e===null&&(t.flags|=4194304),t.child;case 5:return e===null&&ye&&((o=u=He)&&(u=y0(u,t.type,t.pendingProps,un),u!==null?(t.stateNode=u,dt=t,He=Ft(u.firstChild),un=!1,o=!0):o=!1),o||Ra(t)),To(t),o=t.type,s=t.pendingProps,h=e!==null?e.memoizedProps:null,u=s.children,zs(o,s)?u=null:h!==null&&zs(o,h)&&(t.flags|=32),t.memoizedState!==null&&(o=Tc(e,t,By,null,null,a),Hu._currentValue=o),ar(e,t),it(e,t,u,a),t.child;case 6:return e===null&&ye&&((e=a=He)&&(a=b0(a,t.pendingProps,un),a!==null?(t.stateNode=a,dt=t,He=null,e=!0):e=!1),e||Ra(t)),null;case 13:return iv(e,t,a);case 4:return Ne(t,t.stateNode.containerInfo),u=t.pendingProps,e===null?t.child=bl(t,null,u,a):it(e,t,u,a),t.child;case 11:return Fh(e,t,t.type,t.pendingProps,a);case 7:return it(e,t,t.pendingProps,a),t.child;case 8:return it(e,t,t.pendingProps.children,a),t.child;case 12:return it(e,t,t.pendingProps.children,a),t.child;case 10:return u=t.pendingProps,Qn(t,t.type,u.value),it(e,t,u.children,a),t.child;case 9:return o=t.type._context,u=t.pendingProps.children,Da(t),o=st(o),u=u(o),t.flags|=1,it(e,t,u,a),t.child;case 14:return Ih(e,t,t.type,t.pendingProps,a);case 15:return ev(e,t,t.type,t.pendingProps,a);case 19:return ov(e,t,a);case 31:return u=t.pendingProps,a=t.mode,u={mode:u.mode,children:u.children},e===null?(a=lr(u,a),a.ref=t.ref,t.child=a,a.return=t,t=a):(a=Sn(e.child,u),a.ref=t.ref,t.child=a,a.return=t,t=a),t;case 22:return tv(e,t,a);case 24:return Da(t),u=st(Je),e===null?(o=gc(),o===null&&(o=we,s=mc(),o.pooledCache=s,s.refCount++,s!==null&&(o.pooledCacheLanes|=a),o=s),t.memoizedState={parent:u,cache:o},yc(t),Qn(t,Je,o)):((e.lanes&a)!==0&&(bc(e,t),gu(t,null,null,a),pu()),o=e.memoizedState,s=t.memoizedState,o.parent!==u?(o={parent:u,cache:u},t.memoizedState=o,t.lanes===0&&(t.memoizedState=t.updateQueue.baseState=o),Qn(t,Je,u)):(u=s.cache,Qn(t,Je,u),u!==o.cache&&vc(t,[Je],a,!0))),it(e,t,t.pendingProps.children,a),t.child;case 29:throw t.pendingProps}throw Error(r(156,t.tag))}function wn(e){e.flags|=4}function sv(e,t){if(t.type!=="stylesheet"||(t.state.loading&4)!==0)e.flags&=-16777217;else if(e.flags|=16777216,!_m(t)){if(t=$t.current,t!==null&&((he&4194048)===he?rn!==null:(he&62914560)!==he&&(he&536870912)===0||t!==rn))throw vu=_c,Qd;e.flags|=8192}}function ur(e,t){t!==null&&(e.flags|=4),e.flags&16384&&(t=e.tag!==22?qf():536870912,e.lanes|=t,xl|=t)}function xu(e,t){if(!ye)switch(e.tailMode){case"hidden":t=e.tail;for(var a=null;t!==null;)t.alternate!==null&&(a=t),t=t.sibling;a===null?e.tail=null:a.sibling=null;break;case"collapsed":a=e.tail;for(var u=null;a!==null;)a.alternate!==null&&(u=a),a=a.sibling;u===null?t||e.tail===null?e.tail=null:e.tail.sibling=null:u.sibling=null}}function Ze(e){var t=e.alternate!==null&&e.alternate.child===e.child,a=0,u=0;if(t)for(var o=e.child;o!==null;)a|=o.lanes|o.childLanes,u|=o.subtreeFlags&65011712,u|=o.flags&65011712,o.return=e,o=o.sibling;else for(o=e.child;o!==null;)a|=o.lanes|o.childLanes,u|=o.subtreeFlags,u|=o.flags,o.return=e,o=o.sibling;return e.subtreeFlags|=u,e.childLanes=a,t}function Xy(e,t,a){var u=t.pendingProps;switch(sc(t),t.tag){case 31:case 16:case 15:case 0:case 11:case 7:case 8:case 12:case 9:case 14:return Ze(t),null;case 1:return Ze(t),null;case 3:return a=t.stateNode,u=null,e!==null&&(u=e.memoizedState.cache),t.memoizedState.cache!==u&&(t.flags|=2048),An(Je),Vn(),a.pendingContext&&(a.context=a.pendingContext,a.pendingContext=null),(e===null||e.child===null)&&(ru(t)?wn(t):e===null||e.memoizedState.isDehydrated&&(t.flags&256)===0||(t.flags|=1024,qd())),Ze(t),null;case 26:return a=t.memoizedState,e===null?(wn(t),a!==null?(Ze(t),sv(t,a)):(Ze(t),t.flags&=-16777217)):a?a!==e.memoizedState?(wn(t),Ze(t),sv(t,a)):(Ze(t),t.flags&=-16777217):(e.memoizedProps!==u&&wn(t),Ze(t),t.flags&=-16777217),null;case 27:pi(t),a=re.current;var o=t.type;if(e!==null&&t.stateNode!=null)e.memoizedProps!==u&&wn(t);else{if(!u){if(t.stateNode===null)throw Error(r(166));return Ze(t),null}e=ee.current,ru(t)?kd(t):(e=sm(o,u,a),t.stateNode=e,wn(t))}return Ze(t),null;case 5:if(pi(t),a=t.type,e!==null&&t.stateNode!=null)e.memoizedProps!==u&&wn(t);else{if(!u){if(t.stateNode===null)throw Error(r(166));return Ze(t),null}if(e=ee.current,ru(t))kd(t);else{switch(o=_r(re.current),e){case 1:e=o.createElementNS("http://www.w3.org/2000/svg",a);break;case 2:e=o.createElementNS("http://www.w3.org/1998/Math/MathML",a);break;default:switch(a){case"svg":e=o.createElementNS("http://www.w3.org/2000/svg",a);break;case"math":e=o.createElementNS("http://www.w3.org/1998/Math/MathML",a);break;case"script":e=o.createElement("div"),e.innerHTML=" +
- + \ No newline at end of file diff --git a/vite-app/src/App.tsx b/vite-app/src/App.tsx index f590a84d..c274e420 100644 --- a/vite-app/src/App.tsx +++ b/vite-app/src/App.tsx @@ -41,8 +41,12 @@ const App = observer(() => { const rows: EvaluationRow[] = update.logs.map((log) => { return EvaluationRowSchema.parse(JSON.parse(log)); }); - console.log(rows); + console.log("initialize_logs", rows); state.setDataset(rows); + } else if (update.type === "log") { + const row: EvaluationRow = EvaluationRowSchema.parse(update.row); + console.log("log", row); + state.setDataset([row]); } } catch (error) { console.error("Failed to parse WebSocket message:", error); diff --git a/vite-app/src/types/websocket.ts b/vite-app/src/types/websocket.ts index 087ea1d1..f4b8f247 100644 --- a/vite-app/src/types/websocket.ts +++ b/vite-app/src/types/websocket.ts @@ -1,26 +1,24 @@ // WebSocket message types based on logs_server.py import { z } from 'zod'; +import { EvaluationRowSchema } from './eval-protocol'; // Zod Schemas for runtime validation -// File update message schema -export const FileUpdateMessageSchema = z.object({ - type: z.enum(['file_created', 'file_changed', 'file_deleted']), - path: z.string(), - timestamp: z.number(), - contents: z.string().nullable().optional(), -}); - // Initialize logs message schema export const InitializeLogsMessageSchema = z.object({ type: z.literal('initialize_logs'), logs: z.array(z.string()), }); +export const LogMessageSchema = z.object({ + type: z.literal('log'), + row: EvaluationRowSchema, +}); + // Union schema for all WebSocket server messages export const WebSocketServerMessageSchema = z.discriminatedUnion('type', [ - FileUpdateMessageSchema, InitializeLogsMessageSchema, + LogMessageSchema, ] as const); // Server status response schema @@ -31,14 +29,6 @@ export const ServerStatusResponseSchema = z.object({ watch_paths: z.array(z.string()), }); -// File system event schema -export const FileSystemEventSchema = z.object({ - type: z.enum(['file_created', 'file_changed', 'file_deleted']), - path: z.string(), - timestamp: z.number(), - contents: z.string().nullable().optional(), -}); - // Log entry schema export const LogEntrySchema = z.object({ id: z.string(), @@ -49,9 +39,8 @@ export const LogEntrySchema = z.object({ }); // Type inference from Zod schemas -export type FileUpdateMessage = z.infer; export type InitializeLogsMessage = z.infer; +export type LogMessage = z.infer; export type WebSocketServerMessage = z.infer; export type ServerStatusResponse = z.infer; -export type FileSystemEvent = z.infer; export type LogEntry = z.infer;