diff --git a/CHANGELOG.md b/CHANGELOG.md index 4963d3ff9..9ea661ee0 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,8 @@ All notable changes to Bambuddy will be documented in this file. ## [0.1.6b] - 2025-12-28 ### Added +- **Tasmota device discovery** - Automatically discover Tasmota smart plugs on your network. Click "Discover Tasmota Devices" in the Add Smart Plug modal to scan your local subnet. Supports devices with and without authentication. +- **Switchbar for quick smart plug access** - New sidebar widget for controlling smart plugs without leaving the current page. Enable "Show in Switchbar" for any plug to add it to the quick access panel. Shows real-time status, power consumption, and on/off controls. - **Timelapse editor** - Edit timelapse videos with trim, speed adjustment (0.25x-4x), and music overlay. Uses FFmpeg for server-side processing with browser-based preview. - **AMS filament preview** - Reprint modal shows filament comparison between what the print requires and what's currently loaded in the AMS. Compares both type and color with visual indicators (green=match, yellow=color mismatch, orange=type mismatch). - **File type badge** - Archive cards now show GCODE (green) or SOURCE (orange) badge to indicate whether the file is a sliced print-ready file or source-only. diff --git a/backend/app/api/routes/settings.py b/backend/app/api/routes/settings.py index 2f29a323f..4c62fced9 100644 --- a/backend/app/api/routes/settings.py +++ b/backend/app/api/routes/settings.py @@ -270,6 +270,7 @@ async def export_backup( "schedule_enabled": plug.schedule_enabled, "schedule_on_time": plug.schedule_on_time, "schedule_off_time": plug.schedule_off_time, + "show_in_switchbar": plug.show_in_switchbar, } ) backup["included"].append("smart_plugs") @@ -739,6 +740,7 @@ async def import_backup( existing.schedule_enabled = plug_data.get("schedule_enabled", False) existing.schedule_on_time = plug_data.get("schedule_on_time") existing.schedule_off_time = plug_data.get("schedule_off_time") + existing.show_in_switchbar = plug_data.get("show_in_switchbar", False) restored["smart_plugs"] += 1 else: skipped["smart_plugs"] += 1 @@ -762,6 +764,7 @@ async def import_backup( schedule_enabled=plug_data.get("schedule_enabled", False), schedule_on_time=plug_data.get("schedule_on_time"), schedule_off_time=plug_data.get("schedule_off_time"), + show_in_switchbar=plug_data.get("show_in_switchbar", False), ) db.add(plug) restored["smart_plugs"] += 1 diff --git a/backend/app/api/routes/smart_plugs.py b/backend/app/api/routes/smart_plugs.py index 5648f8a86..5102d80d7 100644 --- a/backend/app/api/routes/smart_plugs.py +++ b/backend/app/api/routes/smart_plugs.py @@ -3,25 +3,27 @@ import logging from datetime import datetime, timedelta -from fastapi import APIRouter, Depends, HTTPException, Query -from sqlalchemy.ext.asyncio import AsyncSession +from fastapi import APIRouter, Body, Depends, HTTPException +from pydantic import BaseModel from sqlalchemy import select +from sqlalchemy.ext.asyncio import AsyncSession from backend.app.core.database import get_db -from backend.app.models.smart_plug import SmartPlug from backend.app.models.printer import Printer +from backend.app.models.smart_plug import SmartPlug from backend.app.schemas.smart_plug import ( - SmartPlugCreate, - SmartPlugUpdate, - SmartPlugResponse, SmartPlugControl, + SmartPlugCreate, + SmartPlugEnergy, + SmartPlugResponse, SmartPlugStatus, SmartPlugTestConnection, - SmartPlugEnergy, + SmartPlugUpdate, ) -from backend.app.services.tasmota import tasmota_service -from backend.app.services.printer_manager import printer_manager +from backend.app.services.discovery import tasmota_scanner from backend.app.services.notification_service import notification_service +from backend.app.services.printer_manager import printer_manager +from backend.app.services.tasmota import tasmota_service logger = logging.getLogger(__name__) @@ -43,16 +45,12 @@ async def create_smart_plug( """Create a new smart plug.""" # Validate printer_id if provided if data.printer_id: - result = await db.execute( - select(Printer).where(Printer.id == data.printer_id) - ) + result = await db.execute(select(Printer).where(Printer.id == data.printer_id)) if not result.scalar_one_or_none(): raise HTTPException(400, "Printer not found") # Check if printer already has a plug assigned - result = await db.execute( - select(SmartPlug).where(SmartPlug.printer_id == data.printer_id) - ) + result = await db.execute(select(SmartPlug).where(SmartPlug.printer_id == data.printer_id)) if result.scalar_one_or_none(): raise HTTPException(400, "This printer already has a smart plug assigned") @@ -68,15 +66,131 @@ async def create_smart_plug( @router.get("/by-printer/{printer_id}", response_model=SmartPlugResponse | None) async def get_smart_plug_by_printer(printer_id: int, db: AsyncSession = Depends(get_db)): """Get the smart plug assigned to a printer.""" - result = await db.execute( - select(SmartPlug).where(SmartPlug.printer_id == printer_id) - ) + result = await db.execute(select(SmartPlug).where(SmartPlug.printer_id == printer_id)) plug = result.scalar_one_or_none() if not plug: return None return plug +# Tasmota Discovery Endpoints +# NOTE: These must be defined BEFORE /{plug_id} routes to avoid path conflicts + + +class TasmotaScanRequest(BaseModel): + """Request to scan for Tasmota devices.""" + + from_ip: str | None = None # Starting IP (auto-detected if not provided) + to_ip: str | None = None # Ending IP (auto-detected if not provided) + timeout: float = 1.0 # Connection timeout per host + + +def get_local_network_range() -> tuple[str, str]: + """Auto-detect local network and return IP range to scan.""" + import socket + + try: + # Get local IP by connecting to a public DNS (doesn't actually send data) + s = socket.socket(socket.AF_INET, socket.SOCK_DGRAM) + s.connect(("8.8.8.8", 80)) + local_ip = s.getsockname()[0] + s.close() + + # Parse IP and create range (assume /24 subnet) + parts = local_ip.split(".") + base = ".".join(parts[:3]) + from_ip = f"{base}.1" + to_ip = f"{base}.254" + + logger.info(f"Auto-detected network: {from_ip} - {to_ip} (local IP: {local_ip})") + return from_ip, to_ip + + except Exception as e: + logger.error(f"Failed to detect local network: {e}") + # Fallback to common home network + return "192.168.1.1", "192.168.1.254" + + +class TasmotaScanStatus(BaseModel): + """Tasmota scan status response.""" + + running: bool + scanned: int + total: int + + +class DiscoveredTasmotaDevice(BaseModel): + """Discovered Tasmota device.""" + + ip_address: str + name: str + module: int | None = None + state: str | None = None + discovered_at: str | None = None + + +@router.post("/discover/scan", response_model=TasmotaScanStatus) +async def start_tasmota_scan(request: TasmotaScanRequest | None = Body(default=None)): + """Start an IP range scan for Tasmota devices. + + Auto-detects local network if no IP range provided. + """ + import asyncio + + # Auto-detect network + from_ip, to_ip = get_local_network_range() + timeout = request.timeout if request else 1.0 + + # Start scan in background + asyncio.create_task(tasmota_scanner.scan_range(from_ip, to_ip, timeout)) + + # Return immediate status + scanned, total = tasmota_scanner.progress + return TasmotaScanStatus( + running=tasmota_scanner.is_running, + scanned=scanned, + total=total, + ) + + +@router.get("/discover/status", response_model=TasmotaScanStatus) +async def get_tasmota_scan_status(): + """Get the current Tasmota scan status.""" + scanned, total = tasmota_scanner.progress + return TasmotaScanStatus( + running=tasmota_scanner.is_running, + scanned=scanned, + total=total, + ) + + +@router.post("/discover/stop", response_model=TasmotaScanStatus) +async def stop_tasmota_scan(): + """Stop the current Tasmota scan.""" + tasmota_scanner.stop() + scanned, total = tasmota_scanner.progress + return TasmotaScanStatus( + running=tasmota_scanner.is_running, + scanned=scanned, + total=total, + ) + + +@router.get("/discover/devices", response_model=list[DiscoveredTasmotaDevice]) +async def get_discovered_tasmota_devices(): + """Get list of discovered Tasmota devices.""" + return [ + DiscoveredTasmotaDevice( + ip_address=d["ip_address"], + name=d["name"], + module=d.get("module"), + state=d.get("state"), + discovered_at=d.get("discovered_at"), + ) + for d in tasmota_scanner.discovered_devices + ] + + @router.get("/{plug_id}", response_model=SmartPlugResponse) async def get_smart_plug(plug_id: int, db: AsyncSession = Depends(get_db)): """Get a specific smart plug.""" @@ -106,9 +220,7 @@ async def update_smart_plug( new_printer_id = update_data["printer_id"] # Check printer exists - result = await db.execute( - select(Printer).where(Printer.id == new_printer_id) - ) + result = await db.execute(select(Printer).where(Printer.id == new_printer_id)) if not result.scalar_one_or_none(): raise HTTPException(400, "Printer not found") diff --git a/backend/app/core/database.py b/backend/app/core/database.py index d1a3c74a0..1f12918c7 100644 --- a/backend/app/core/database.py +++ b/backend/app/core/database.py @@ -359,6 +359,12 @@ async def run_migrations(conn): except Exception: pass + # Migration: Add show_in_switchbar column to smart_plugs + try: + await conn.execute(text("ALTER TABLE smart_plugs ADD COLUMN show_in_switchbar BOOLEAN DEFAULT 0")) + except Exception: + pass + async def seed_notification_templates(): """Seed default notification templates if they don't exist.""" diff --git a/backend/app/models/smart_plug.py b/backend/app/models/smart_plug.py index 04a78401d..005cecf73 100644 --- a/backend/app/models/smart_plug.py +++ b/backend/app/models/smart_plug.py @@ -1,5 +1,6 @@ from datetime import datetime -from sqlalchemy import String, Boolean, Integer, Float, DateTime, ForeignKey, func + +from sqlalchemy import Boolean, DateTime, Float, ForeignKey, Integer, String, func from sqlalchemy.orm import Mapped, mapped_column, relationship from backend.app.core.database import Base @@ -44,18 +45,21 @@ class SmartPlug(Base): schedule_on_time: Mapped[str | None] = mapped_column(String(5), nullable=True) # "HH:MM" format schedule_off_time: Mapped[str | None] = mapped_column(String(5), nullable=True) # "HH:MM" format + # Switchbar visibility + show_in_switchbar: Mapped[bool] = mapped_column(Boolean, default=False) + # Status tracking last_state: Mapped[str | None] = mapped_column(String(10), nullable=True) # "ON"/"OFF" last_checked: Mapped[datetime | None] = mapped_column(DateTime, nullable=True) auto_off_executed: Mapped[bool] = mapped_column(Boolean, default=False) # True when auto-off was triggered auto_off_pending: Mapped[bool] = mapped_column(Boolean, default=False) # True when waiting for cooldown - auto_off_pending_since: Mapped[datetime | None] = mapped_column(DateTime, nullable=True) # When auto-off was scheduled + auto_off_pending_since: Mapped[datetime | None] = mapped_column( + DateTime, nullable=True + ) # When auto-off was scheduled # Timestamps created_at: Mapped[datetime] = mapped_column(DateTime, server_default=func.now()) - updated_at: Mapped[datetime] = mapped_column( - DateTime, server_default=func.now(), onupdate=func.now() - ) + updated_at: Mapped[datetime] = mapped_column(DateTime, server_default=func.now(), onupdate=func.now()) # Relationship printer: Mapped["Printer"] = relationship(back_populates="smart_plug") diff --git a/backend/app/schemas/smart_plug.py b/backend/app/schemas/smart_plug.py index dc1396b2e..3d1ef45ed 100644 --- a/backend/app/schemas/smart_plug.py +++ b/backend/app/schemas/smart_plug.py @@ -1,5 +1,6 @@ from datetime import datetime from typing import Literal + from pydantic import BaseModel, Field @@ -23,6 +24,8 @@ class SmartPlugBase(BaseModel): schedule_enabled: bool = False schedule_on_time: str | None = Field(default=None, pattern=r"^([01]\d|2[0-3]):[0-5]\d$") # HH:MM format schedule_off_time: str | None = Field(default=None, pattern=r"^([01]\d|2[0-3]):[0-5]\d$") # HH:MM format + # Switchbar visibility + show_in_switchbar: bool = False class SmartPlugCreate(SmartPlugBase): @@ -49,6 +52,8 @@ class SmartPlugUpdate(BaseModel): schedule_enabled: bool | None = None schedule_on_time: str | None = Field(default=None, pattern=r"^([01]\d|2[0-3]):[0-5]\d$") schedule_off_time: str | None = Field(default=None, pattern=r"^([01]\d|2[0-3]):[0-5]\d$") + # Switchbar visibility + show_in_switchbar: bool | None = None class SmartPlugResponse(SmartPlugBase): @@ -70,6 +75,7 @@ class SmartPlugControl(BaseModel): class SmartPlugEnergy(BaseModel): """Energy monitoring data from a smart plug.""" + power: float | None = None # Current watts voltage: float | None = None # Volts current: float | None = None # Amps diff --git a/backend/app/services/discovery.py b/backend/app/services/discovery.py index 3142f7058..c6cc18a47 100644 --- a/backend/app/services/discovery.py +++ b/backend/app/services/discovery.py @@ -479,6 +479,200 @@ class SubnetScanner: self._running = False +class TasmotaScanner: + """Scanner for discovering Tasmota devices by probing IP addresses.""" + + HTTP_PORT = 80 + + def __init__(self): + self._discovered: dict[str, dict] = {} + self._running = False + self._scanned = 0 + self._total = 0 + + @property + def is_running(self) -> bool: + return self._running + + @property + def discovered_devices(self) -> list[dict]: + return list(self._discovered.values()) + + @property + def progress(self) -> tuple[int, int]: + """Return (scanned, total) counts.""" + return self._scanned, self._total + + async def scan_range(self, from_ip: str, to_ip: str, timeout: float = 1.0) -> list[dict]: + """Scan an IP range for Tasmota devices. + + Args: + from_ip: Starting IP address (e.g., "192.168.1.1") + to_ip: Ending IP address (e.g., "192.168.1.254") + timeout: Connection timeout per host in seconds + + Returns: + List of discovered Tasmota devices + """ + if self._running: + return [] + + self._running = True + self._discovered.clear() + self._scanned = 0 + + try: + start = ipaddress.ip_address(from_ip) + end = ipaddress.ip_address(to_ip) + + # Generate list of IPs in range + hosts = [] + current = start + while current <= end: + hosts.append(str(current)) + current = ipaddress.ip_address(int(current) + 1) + + self._total = len(hosts) + + if self._total > 1024: + logger.warning(f"IP range has {self._total} hosts, limiting to 1024") + self._total = 1024 + hosts = hosts[:1024] + + logger.info(f"Starting Tasmota scan from {from_ip} to {to_ip} ({self._total} hosts)") + + # Scan in batches to avoid overwhelming the network + batch_size = 50 + for i in range(0, len(hosts), batch_size): + if not self._running: + logger.info("Tasmota scan stopped by user") + break + + batch = hosts[i : i + batch_size] + tasks = [self._probe_host(ip) for ip in batch] + try: + await asyncio.gather(*tasks, return_exceptions=True) + except Exception as e: + logger.warning(f"Batch {i//batch_size} error: {e}") + self._scanned = min(i + batch_size, len(hosts)) + + logger.info(f"Tasmota scan complete. Found {len(self._discovered)} devices.") + return self.discovered_devices + + except ValueError as e: + logger.error(f"Invalid IP address format: {e}") + return [] + finally: + self._running = False + + async def _probe_host(self, ip: str): + """Probe a single host for Tasmota HTTP API.""" + try: + # Hard timeout of 5 seconds max per host + await asyncio.wait_for(self._do_probe(ip), timeout=5.0) + except TimeoutError: + pass + except Exception: + pass + + async def _do_probe(self, ip: str): + """Actually probe the host.""" + import httpx + + try: + # Reasonable timeouts for network scanning + client_timeout = httpx.Timeout(3.0, connect=1.0) + async with httpx.AsyncClient(timeout=client_timeout, follow_redirects=False) as client: + # First try simple Power command - most reliable indicator of Tasmota + power_url = f"http://{ip}/cm?cmnd=Power" + try: + power_response = await client.get(power_url) + if power_response.status_code == 401: + # Device requires auth - still a Tasmota device! + logger.info(f"Discovered Tasmota at {ip} (requires auth - 401)") + device = { + "ip_address": ip, + "name": f"Tasmota ({ip})", + "module": None, + "state": "UNKNOWN", + "discovered_at": datetime.now().isoformat(), + } + self._discovered[ip] = device + return + + if power_response.status_code != 200: + return + + power_data = power_response.json() + + # Check for Tasmota auth warning (returns 200 with WARNING) + if "WARNING" in power_data: + logger.info(f"Discovered Tasmota at {ip} (requires auth)") + device = { + "ip_address": ip, + "name": f"Tasmota ({ip})", + "module": None, + "state": "UNKNOWN", + "discovered_at": datetime.now().isoformat(), + } + self._discovered[ip] = device + return + + # Check if response looks like Tasmota (has POWER or POWER1 key) + power_state = power_data.get("POWER") or power_data.get("POWER1") + if power_state is None: + return + + except Exception as e: + logger.debug(f"Error probing {ip}: {e}") + return + + # It's a Tasmota device! Now get more info + device_name = f"Tasmota ({ip})" + module = None + + # Try to get device name from Status 0 + try: + status_url = f"http://{ip}/cm?cmnd=Status%200" + status_response = await client.get(status_url) + if status_response.status_code == 200: + status_data = status_response.json() + if "Status" in status_data: + status = status_data["Status"] + device_name = status.get("DeviceName") or device_name + if not device_name or device_name == f"Tasmota ({ip})": + # Try FriendlyName + friendly = status.get("FriendlyName") + if friendly and isinstance(friendly, list) and friendly[0]: + device_name = friendly[0] + module = status.get("Module") + except Exception: + pass + + device = { + "ip_address": ip, + "name": device_name, + "module": module, + "state": power_state, + "discovered_at": datetime.now().isoformat(), + } + + self._discovered[ip] = device + logger.info(f"Discovered Tasmota device: {device_name} at {ip}") + + except httpx.TimeoutException: + pass + except httpx.ConnectError: + pass + except Exception: + pass + + def stop(self): + """Stop the current scan.""" + self._running = False + + # Global instances discovery_service = PrinterDiscoveryService() subnet_scanner = SubnetScanner() +tasmota_scanner = TasmotaScanner() diff --git a/backend/tests/integration/test_smart_plugs_api.py b/backend/tests/integration/test_smart_plugs_api.py index 5b3e21ccf..39f18706b 100644 --- a/backend/tests/integration/test_smart_plugs_api.py +++ b/backend/tests/integration/test_smart_plugs_api.py @@ -25,11 +25,9 @@ class TestSmartPlugsAPI: @pytest.mark.asyncio @pytest.mark.integration - async def test_list_smart_plugs_with_data( - self, async_client: AsyncClient, smart_plug_factory, db_session - ): + async def test_list_smart_plugs_with_data(self, async_client: AsyncClient, smart_plug_factory, db_session): """Verify list returns existing plugs.""" - plug = await smart_plug_factory(name="Test Plug 1") + await smart_plug_factory(name="Test Plug 1") response = await async_client.get("/api/v1/smart-plugs/") @@ -64,9 +62,7 @@ class TestSmartPlugsAPI: @pytest.mark.asyncio @pytest.mark.integration - async def test_create_smart_plug_with_printer( - self, async_client: AsyncClient, printer_factory, db_session - ): + async def test_create_smart_plug_with_printer(self, async_client: AsyncClient, printer_factory, db_session): """Verify smart plug can be linked to a printer.""" printer = await printer_factory(name="Test Printer") @@ -84,9 +80,7 @@ class TestSmartPlugsAPI: @pytest.mark.asyncio @pytest.mark.integration - async def test_create_plug_with_invalid_printer_id( - self, async_client: AsyncClient - ): + async def test_create_plug_with_invalid_printer_id(self, async_client: AsyncClient): """Verify creating plug with non-existent printer fails.""" data = { "name": "Test Plug", @@ -105,9 +99,7 @@ class TestSmartPlugsAPI: @pytest.mark.asyncio @pytest.mark.integration - async def test_get_smart_plug( - self, async_client: AsyncClient, smart_plug_factory, db_session - ): + async def test_get_smart_plug(self, async_client: AsyncClient, smart_plug_factory, db_session): """Verify single plug can be retrieved.""" plug = await smart_plug_factory(name="Get Test Plug") @@ -132,9 +124,7 @@ class TestSmartPlugsAPI: @pytest.mark.asyncio @pytest.mark.integration - async def test_update_auto_off_toggle( - self, async_client: AsyncClient, smart_plug_factory, db_session - ): + async def test_update_auto_off_toggle(self, async_client: AsyncClient, smart_plug_factory, db_session): """CRITICAL: Verify auto_off toggle persists correctly. This tests the regression scenario where toggling auto_off @@ -149,10 +139,7 @@ class TestSmartPlugsAPI: assert response.json()["auto_off"] is True # Toggle auto_off to False - response = await async_client.patch( - f"/api/v1/smart-plugs/{plug.id}", - json={"auto_off": False} - ) + response = await async_client.patch(f"/api/v1/smart-plugs/{plug.id}", json={"auto_off": False}) assert response.status_code == 200 assert response.json()["auto_off"] is False @@ -163,16 +150,11 @@ class TestSmartPlugsAPI: @pytest.mark.asyncio @pytest.mark.integration - async def test_update_auto_on_toggle( - self, async_client: AsyncClient, smart_plug_factory, db_session - ): + async def test_update_auto_on_toggle(self, async_client: AsyncClient, smart_plug_factory, db_session): """Verify auto_on toggle persists correctly.""" plug = await smart_plug_factory(auto_on=True) - response = await async_client.patch( - f"/api/v1/smart-plugs/{plug.id}", - json={"auto_on": False} - ) + response = await async_client.patch(f"/api/v1/smart-plugs/{plug.id}", json={"auto_on": False}) assert response.status_code == 200 assert response.json()["auto_on"] is False @@ -183,31 +165,23 @@ class TestSmartPlugsAPI: @pytest.mark.asyncio @pytest.mark.integration - async def test_update_enabled_toggle( - self, async_client: AsyncClient, smart_plug_factory, db_session - ): + async def test_update_enabled_toggle(self, async_client: AsyncClient, smart_plug_factory, db_session): """Verify enabled toggle persists correctly.""" plug = await smart_plug_factory(enabled=True) - response = await async_client.patch( - f"/api/v1/smart-plugs/{plug.id}", - json={"enabled": False} - ) + response = await async_client.patch(f"/api/v1/smart-plugs/{plug.id}", json={"enabled": False}) assert response.status_code == 200 assert response.json()["enabled"] is False @pytest.mark.asyncio @pytest.mark.integration - async def test_update_off_delay_mode( - self, async_client: AsyncClient, smart_plug_factory, db_session - ): + async def test_update_off_delay_mode(self, async_client: AsyncClient, smart_plug_factory, db_session): """Verify off_delay_mode can be changed.""" plug = await smart_plug_factory(off_delay_mode="time") response = await async_client.patch( - f"/api/v1/smart-plugs/{plug.id}", - json={"off_delay_mode": "temperature", "off_temp_threshold": 50} + f"/api/v1/smart-plugs/{plug.id}", json={"off_delay_mode": "temperature", "off_temp_threshold": 50} ) assert response.status_code == 200 @@ -217,9 +191,7 @@ class TestSmartPlugsAPI: @pytest.mark.asyncio @pytest.mark.integration - async def test_update_schedule_settings( - self, async_client: AsyncClient, smart_plug_factory, db_session - ): + async def test_update_schedule_settings(self, async_client: AsyncClient, smart_plug_factory, db_session): """Verify schedule settings can be updated.""" plug = await smart_plug_factory(schedule_enabled=False) @@ -229,7 +201,7 @@ class TestSmartPlugsAPI: "schedule_enabled": True, "schedule_on_time": "08:00", "schedule_off_time": "22:00", - } + }, ) assert response.status_code == 200 @@ -240,9 +212,7 @@ class TestSmartPlugsAPI: @pytest.mark.asyncio @pytest.mark.integration - async def test_update_multiple_fields( - self, async_client: AsyncClient, smart_plug_factory, db_session - ): + async def test_update_multiple_fields(self, async_client: AsyncClient, smart_plug_factory, db_session): """Verify multiple fields can be updated at once.""" plug = await smart_plug_factory( name="Old Name", @@ -256,7 +226,7 @@ class TestSmartPlugsAPI: "name": "New Name", "auto_on": False, "auto_off": False, - } + }, ) assert response.status_code == 200 @@ -277,10 +247,7 @@ class TestSmartPlugsAPI: """Verify smart plug can be turned on.""" plug = await smart_plug_factory() - response = await async_client.post( - f"/api/v1/smart-plugs/{plug.id}/control", - json={"action": "on"} - ) + response = await async_client.post(f"/api/v1/smart-plugs/{plug.id}/control", json={"action": "on"}) assert response.status_code == 200 result = response.json() @@ -295,10 +262,7 @@ class TestSmartPlugsAPI: """Verify smart plug can be turned off.""" plug = await smart_plug_factory() - response = await async_client.post( - f"/api/v1/smart-plugs/{plug.id}/control", - json={"action": "off"} - ) + response = await async_client.post(f"/api/v1/smart-plugs/{plug.id}/control", json={"action": "off"}) assert response.status_code == 200 result = response.json() @@ -313,10 +277,7 @@ class TestSmartPlugsAPI: """Verify smart plug can be toggled.""" plug = await smart_plug_factory() - response = await async_client.post( - f"/api/v1/smart-plugs/{plug.id}/control", - json={"action": "toggle"} - ) + response = await async_client.post(f"/api/v1/smart-plugs/{plug.id}/control", json={"action": "toggle"}) assert response.status_code == 200 result = response.json() @@ -325,16 +286,11 @@ class TestSmartPlugsAPI: @pytest.mark.asyncio @pytest.mark.integration - async def test_control_invalid_action( - self, async_client: AsyncClient, smart_plug_factory, db_session - ): + async def test_control_invalid_action(self, async_client: AsyncClient, smart_plug_factory, db_session): """Verify invalid action returns error.""" plug = await smart_plug_factory() - response = await async_client.post( - f"/api/v1/smart-plugs/{plug.id}/control", - json={"action": "invalid"} - ) + response = await async_client.post(f"/api/v1/smart-plugs/{plug.id}/control", json={"action": "invalid"}) # FastAPI returns 422 for pydantic validation errors assert response.status_code == 422 @@ -364,9 +320,7 @@ class TestSmartPlugsAPI: @pytest.mark.asyncio @pytest.mark.integration - async def test_delete_smart_plug( - self, async_client: AsyncClient, smart_plug_factory, db_session - ): + async def test_delete_smart_plug(self, async_client: AsyncClient, smart_plug_factory, db_session): """Verify smart plug can be deleted.""" plug = await smart_plug_factory() plug_id = plug.id @@ -386,3 +340,70 @@ class TestSmartPlugsAPI: response = await async_client.delete("/api/v1/smart-plugs/9999") assert response.status_code == 404 + + # ======================================================================== + # Switchbar visibility + # ======================================================================== + + @pytest.mark.asyncio + @pytest.mark.integration + async def test_update_show_in_switchbar(self, async_client: AsyncClient, smart_plug_factory, db_session): + """Verify show_in_switchbar toggle persists correctly.""" + plug = await smart_plug_factory(show_in_switchbar=False) + + response = await async_client.patch(f"/api/v1/smart-plugs/{plug.id}", json={"show_in_switchbar": True}) + + assert response.status_code == 200 + assert response.json()["show_in_switchbar"] is True + + # Verify persistence + response = await async_client.get(f"/api/v1/smart-plugs/{plug.id}") + assert response.json()["show_in_switchbar"] is True + + # ======================================================================== + # Tasmota Discovery endpoints + # ======================================================================== + + @pytest.mark.asyncio + @pytest.mark.integration + async def test_tasmota_discovery_scan(self, async_client: AsyncClient): + """Verify Tasmota discovery scan can be started.""" + response = await async_client.post("/api/v1/smart-plugs/discover/scan") + + assert response.status_code == 200 + data = response.json() + assert "running" in data + assert "scanned" in data + assert "total" in data + + @pytest.mark.asyncio + @pytest.mark.integration + async def test_tasmota_discovery_status(self, async_client: AsyncClient): + """Verify Tasmota discovery status endpoint works.""" + response = await async_client.get("/api/v1/smart-plugs/discover/status") + + assert response.status_code == 200 + data = response.json() + assert "running" in data + assert "scanned" in data + assert "total" in data + + @pytest.mark.asyncio + @pytest.mark.integration + async def test_tasmota_discovery_devices(self, async_client: AsyncClient): + """Verify Tasmota discovered devices endpoint works.""" + response = await async_client.get("/api/v1/smart-plugs/discover/devices") + + assert response.status_code == 200 + data = response.json() + assert isinstance(data, list) + + @pytest.mark.asyncio + @pytest.mark.integration + async def test_tasmota_discovery_stop(self, async_client: AsyncClient): + """Verify Tasmota discovery can be stopped.""" + response = await async_client.post("/api/v1/smart-plugs/discover/stop") + + assert response.status_code == 200 + data = response.json() + assert "running" in data diff --git a/frontend/src/api/client.ts b/frontend/src/api/client.ts index 77b7dbddf..1b2b7c77b 100644 --- a/frontend/src/api/client.ts +++ b/frontend/src/api/client.ts @@ -14,7 +14,11 @@ async function request( if (!response.ok) { const error = await response.json().catch(() => ({})); - throw new Error(error.detail || `HTTP ${response.status}`); + const detail = error.detail; + const message = typeof detail === 'string' + ? detail + : (detail ? JSON.stringify(detail) : `HTTP ${response.status}`); + throw new Error(message); } return response.json(); @@ -659,6 +663,8 @@ export interface SmartPlug { schedule_enabled: boolean; schedule_on_time: string | null; schedule_off_time: string | null; + // Switchbar visibility + show_in_switchbar: boolean; // Status last_state: string | null; last_checked: string | null; @@ -687,6 +693,8 @@ export interface SmartPlugCreate { schedule_enabled?: boolean; schedule_on_time?: string | null; schedule_off_time?: string | null; + // Switchbar visibility + show_in_switchbar?: boolean; } export interface SmartPlugUpdate { @@ -709,6 +717,8 @@ export interface SmartPlugUpdate { schedule_enabled?: boolean; schedule_on_time?: string | null; schedule_off_time?: string | null; + // Switchbar visibility + show_in_switchbar?: boolean; } export interface SmartPlugEnergy { @@ -736,6 +746,21 @@ export interface SmartPlugTestResult { device_name: string | null; } +// Tasmota Discovery types +export interface TasmotaScanStatus { + running: boolean; + scanned: number; + total: number; +} + +export interface DiscoveredTasmotaDevice { + ip_address: string; + name: string; + module: number | null; + state: string | null; + discovered_at: string | null; +} + // Print Queue types export interface PrintQueueItem { id: number; @@ -1757,6 +1782,18 @@ export const api = { body: JSON.stringify({ ip_address, username, password }), }), + // Tasmota Discovery (auto-detects network) + startTasmotaScan: () => + fetch(`${API_BASE}/smart-plugs/discover/scan`, { method: 'POST' }) + .then(res => res.ok ? res.json() : res.json().then(e => { throw new Error(e.detail || `HTTP ${res.status}`); })), + getTasmotaScanStatus: () => + request('/smart-plugs/discover/status'), + stopTasmotaScan: () => + fetch(`${API_BASE}/smart-plugs/discover/stop`, { method: 'POST' }) + .then(res => res.ok ? res.json() : res.json().then(e => { throw new Error(e.detail || `HTTP ${res.status}`); })), + getDiscoveredTasmotaDevices: () => + request('/smart-plugs/discover/devices'), + // Print Queue getQueue: (printerId?: number, status?: string) => { const params = new URLSearchParams(); diff --git a/frontend/src/components/AddSmartPlugModal.tsx b/frontend/src/components/AddSmartPlugModal.tsx index 0ba245669..c5952dbb8 100644 --- a/frontend/src/components/AddSmartPlugModal.tsx +++ b/frontend/src/components/AddSmartPlugModal.tsx @@ -1,8 +1,8 @@ -import { useState, useEffect } from 'react'; +import { useState, useEffect, useRef } from 'react'; import { useMutation, useQueryClient, useQuery } from '@tanstack/react-query'; -import { X, Save, Loader2, Wifi, WifiOff, CheckCircle, Bell, Clock } from 'lucide-react'; +import { X, Save, Loader2, Wifi, WifiOff, CheckCircle, Bell, Clock, LayoutGrid, Search, Plug, Power } from 'lucide-react'; import { api } from '../api/client'; -import type { SmartPlug, SmartPlugCreate, SmartPlugUpdate } from '../api/client'; +import type { SmartPlug, SmartPlugCreate, SmartPlugUpdate, DiscoveredTasmotaDevice } from '../api/client'; import { Button } from './Button'; interface AddSmartPlugModalProps { @@ -32,6 +32,15 @@ export function AddSmartPlugModal({ plug, onClose }: AddSmartPlugModalProps) { const [scheduleOnTime, setScheduleOnTime] = useState(plug?.schedule_on_time || ''); const [scheduleOffTime, setScheduleOffTime] = useState(plug?.schedule_off_time || ''); + // Switchbar visibility + const [showInSwitchbar, setShowInSwitchbar] = useState(plug?.show_in_switchbar || false); + + // Discovery state + const [isScanning, setIsScanning] = useState(false); + const [scanProgress, setScanProgress] = useState({ scanned: 0, total: 0 }); + const [discoveredDevices, setDiscoveredDevices] = useState([]); + const scanPollRef = useRef(null); + // Fetch printers for linking const { data: printers } = useQuery({ queryKey: ['printers'], @@ -44,15 +53,82 @@ export function AddSmartPlugModal({ plug, onClose }: AddSmartPlugModalProps) { queryFn: api.getSmartPlugs, }); - // Close on Escape key + // Close on Escape key and cleanup scan polling useEffect(() => { const handleKeyDown = (e: KeyboardEvent) => { if (e.key === 'Escape') onClose(); }; window.addEventListener('keydown', handleKeyDown); - return () => window.removeEventListener('keydown', handleKeyDown); + return () => { + window.removeEventListener('keydown', handleKeyDown); + if (scanPollRef.current) { + clearInterval(scanPollRef.current); + } + }; }, [onClose]); + // Start scanning for Tasmota devices (auto-detects network) + const startScan = async () => { + setIsScanning(true); + setDiscoveredDevices([]); + setScanProgress({ scanned: 0, total: 0 }); + setError(null); + + try { + await api.startTasmotaScan(); + + // Poll function to fetch status and devices + const pollStatus = async () => { + try { + const status = await api.getTasmotaScanStatus(); + setScanProgress({ scanned: status.scanned, total: status.total }); + + const devices = await api.getDiscoveredTasmotaDevices(); + setDiscoveredDevices(devices); + + if (!status.running) { + setIsScanning(false); + if (scanPollRef.current) { + clearInterval(scanPollRef.current); + scanPollRef.current = null; + } + } + } catch (e) { + console.error('Polling error:', e); + } + }; + + // Poll immediately, then every 500ms + await pollStatus(); + scanPollRef.current = setInterval(pollStatus, 500); + } catch (err) { + setIsScanning(false); + const errorMsg = err instanceof Error ? err.message : (typeof err === 'string' ? err : JSON.stringify(err)); + setError(errorMsg || 'Failed to start scan'); + } + }; + + // Stop scanning + const stopScan = async () => { + try { + await api.stopTasmotaScan(); + } catch { + // Ignore stop errors + } + setIsScanning(false); + if (scanPollRef.current) { + clearInterval(scanPollRef.current); + scanPollRef.current = null; + } + }; + + // Select a discovered device + const selectDevice = (device: DiscoveredTasmotaDevice) => { + setIpAddress(device.ip_address); + setName(device.name); + setTestResult(null); + }; + // Test connection mutation const testMutation = useMutation({ mutationFn: () => api.testSmartPlugConnection(ipAddress, username || null, password || null), @@ -127,6 +203,8 @@ export function AddSmartPlugModal({ plug, onClose }: AddSmartPlugModalProps) { schedule_enabled: scheduleEnabled, schedule_on_time: scheduleOnTime || null, schedule_off_time: scheduleOffTime || null, + // Switchbar + show_in_switchbar: showInSwitchbar, }; if (isEditing) { @@ -168,6 +246,79 @@ export function AddSmartPlugModal({ plug, onClose }: AddSmartPlugModalProps) { )} + {/* Discovery Section - only show when not editing */} + {!isEditing && ( +
+ {/* Scan button - auto-detects network */} + {isScanning ? ( + + ) : ( + + )} + + {/* Progress bar */} + {isScanning && scanProgress.total > 0 && ( +
+
+ Scanning network... + {scanProgress.scanned} / {scanProgress.total} +
+
+
+
+
+ )} + + {/* Discovered devices */} + {discoveredDevices.length > 0 && ( +
+

Found {discoveredDevices.length} device(s) - click to select:

+
+ {discoveredDevices.map((device) => ( + + ))} +
+
+ )} + + {!isScanning && discoveredDevices.length === 0 && scanProgress.total > 0 && ( +

+ No Tasmota devices found on your network +

+ )} +
+ )} + {/* IP Address */}
@@ -382,6 +533,28 @@ export function AddSmartPlugModal({ plug, onClose }: AddSmartPlugModalProps) { )}
+ {/* Switchbar Visibility */} +
+
+
+ +
+ Show in Switchbar +

Quick access from sidebar

+
+
+ +
+
+ {/* Actions */}
+ {hasSwitchbarPlugs && ( +
+ + {showSwitchbar && ( + setShowSwitchbar(false)} /> + )} +
+ )} @@ -504,6 +531,22 @@ export function Layout() { )} + {hasSwitchbarPlugs && ( +
+ + {showSwitchbar && ( + setShowSwitchbar(false)} /> + )} +
+ )} diff --git a/frontend/src/components/SmartPlugCard.tsx b/frontend/src/components/SmartPlugCard.tsx index 9903f72f6..da1a99502 100644 --- a/frontend/src/components/SmartPlugCard.tsx +++ b/frontend/src/components/SmartPlugCard.tsx @@ -1,6 +1,6 @@ import { useState } from 'react'; import { useMutation, useQueryClient, useQuery } from '@tanstack/react-query'; -import { Plug, Power, PowerOff, Loader2, Trash2, Settings2, Thermometer, Clock, Wifi, WifiOff, Edit2, Bell, Calendar } from 'lucide-react'; +import { Plug, Power, PowerOff, Loader2, Trash2, Settings2, Thermometer, Clock, Wifi, WifiOff, Edit2, Bell, Calendar, LayoutGrid } from 'lucide-react'; import { api } from '../api/client'; import type { SmartPlug, SmartPlugUpdate } from '../api/client'; import { Card, CardContent } from './Card'; @@ -169,6 +169,26 @@ export function SmartPlugCard({ plug, onEdit }: SmartPlugCardProps) { {/* Expanded Settings */} {isExpanded && (
+ {/* Show in Switchbar Toggle */} +
+
+ +
+

Show in Switchbar

+

Quick access from sidebar

+
+
+ +
+ {/* Enabled Toggle */}
diff --git a/frontend/src/components/SwitchbarPopover.tsx b/frontend/src/components/SwitchbarPopover.tsx new file mode 100644 index 000000000..0ac7071ee --- /dev/null +++ b/frontend/src/components/SwitchbarPopover.tsx @@ -0,0 +1,142 @@ +import { useQuery, useMutation, useQueryClient } from '@tanstack/react-query'; +import { Plug, Power, PowerOff, Loader2, Wifi, WifiOff, Zap } from 'lucide-react'; +import { api } from '../api/client'; +import type { SmartPlug } from '../api/client'; + +interface SwitchbarPopoverProps { + onClose: () => void; +} + +function SwitchItem({ plug }: { plug: SmartPlug }) { + const queryClient = useQueryClient(); + + // Fetch current status + const { data: status, isLoading: statusLoading } = useQuery({ + queryKey: ['smart-plug-status', plug.id], + queryFn: () => api.getSmartPlugStatus(plug.id), + refetchInterval: 10000, // Refresh every 10 seconds when popover is open + }); + + // Control mutation + const controlMutation = useMutation({ + mutationFn: (action: 'on' | 'off') => api.controlSmartPlug(plug.id, action), + onSuccess: () => { + queryClient.invalidateQueries({ queryKey: ['smart-plug-status', plug.id] }); + }, + }); + + const isOn = status?.state === 'ON'; + const isReachable = status?.reachable ?? false; + const isPending = controlMutation.isPending; + + return ( +
+
+
+ +
+
+

{plug.name}

+
+ {statusLoading ? ( + + ) : isReachable ? ( + <> + + {status?.state || 'Unknown'} + {status?.energy?.power !== null && status?.energy?.power !== undefined && ( + <> + | + + {Math.round(status.energy.power)}W + + )} + + ) : ( + <> + + Offline + + )} +
+
+
+ +
+ + +
+
+ ); +} + +export function SwitchbarPopover({ onClose }: SwitchbarPopoverProps) { + // Fetch all smart plugs + const { data: plugs, isLoading } = useQuery({ + queryKey: ['smart-plugs'], + queryFn: api.getSmartPlugs, + }); + + // Filter to only show plugs with show_in_switchbar enabled + const switchbarPlugs = plugs?.filter(p => p.show_in_switchbar) || []; + + return ( +
+ {/* Header */} +
+

+ + Smart Switches +

+
+ + {/* Content */} +
+ {isLoading ? ( +
+ +
+ ) : switchbarPlugs.length === 0 ? ( +
+ +

No switches in switchbar

+

+ Enable "Show in Switchbar" in Settings > Smart Plugs +

+
+ ) : ( +
+ {switchbarPlugs.map(plug => ( + + ))} +
+ )} +
+
+ ); +} diff --git a/static/assets/index-BGnYjiV-.js b/static/assets/index-BGnYjiV-.js new file mode 100644 index 000000000..3590233cb --- /dev/null +++ b/static/assets/index-BGnYjiV-.js @@ -0,0 +1,8619 @@ +function Ire(t,e){for(var n=0;nr[i]})}}}return Object.freeze(Object.defineProperty(t,Symbol.toStringTag,{value:"Module"}))}(function(){const e=document.createElement("link").relList;if(e&&e.supports&&e.supports("modulepreload"))return;for(const i of document.querySelectorAll('link[rel="modulepreload"]'))r(i);new MutationObserver(i=>{for(const a of i)if(a.type==="childList")for(const s of a.addedNodes)s.tagName==="LINK"&&s.rel==="modulepreload"&&r(s)}).observe(document,{childList:!0,subtree:!0});function n(i){const a={};return i.integrity&&(a.integrity=i.integrity),i.referrerPolicy&&(a.referrerPolicy=i.referrerPolicy),i.crossOrigin==="use-credentials"?a.credentials="include":i.crossOrigin==="anonymous"?a.credentials="omit":a.credentials="same-origin",a}function r(i){if(i.ep)return;i.ep=!0;const a=n(i);fetch(i.href,a)}})();var bv=typeof globalThis<"u"?globalThis:typeof window<"u"?window:typeof global<"u"?global:typeof self<"u"?self:{};function po(t){return t&&t.__esModule&&Object.prototype.hasOwnProperty.call(t,"default")?t.default:t}var NT={exports:{}},cx={};var fF;function Fre(){if(fF)return cx;fF=1;var t=Symbol.for("react.transitional.element"),e=Symbol.for("react.fragment");function n(r,i,a){var s=null;if(a!==void 0&&(s=""+a),i.key!==void 0&&(s=""+i.key),"key"in i){a={};for(var o in i)o!=="key"&&(a[o]=i[o])}else a=i;return i=a.ref,{$$typeof:t,type:r,key:s,ref:i!==void 0?i:null,props:a}}return cx.Fragment=e,cx.jsx=n,cx.jsxs=n,cx}var mF;function Ure(){return mF||(mF=1,NT.exports=Fre()),NT.exports}var l=Ure(),CT={exports:{}},en={};var pF;function zre(){if(pF)return en;pF=1;var t=Symbol.for("react.transitional.element"),e=Symbol.for("react.portal"),n=Symbol.for("react.fragment"),r=Symbol.for("react.strict_mode"),i=Symbol.for("react.profiler"),a=Symbol.for("react.consumer"),s=Symbol.for("react.context"),o=Symbol.for("react.forward_ref"),c=Symbol.for("react.suspense"),u=Symbol.for("react.memo"),d=Symbol.for("react.lazy"),f=Symbol.for("react.activity"),g=Symbol.iterator;function p(B){return B===null||typeof B!="object"?null:(B=g&&B[g]||B["@@iterator"],typeof B=="function"?B:null)}var x={isMounted:function(){return!1},enqueueForceUpdate:function(){},enqueueReplaceState:function(){},enqueueSetState:function(){}},v=Object.assign,b={};function y(B,$,J){this.props=B,this.context=$,this.refs=b,this.updater=J||x}y.prototype.isReactComponent={},y.prototype.setState=function(B,$){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,$,"setState")},y.prototype.forceUpdate=function(B){this.updater.enqueueForceUpdate(this,B,"forceUpdate")};function E(){}E.prototype=y.prototype;function w(B,$,J){this.props=B,this.context=$,this.refs=b,this.updater=J||x}var A=w.prototype=new E;A.constructor=w,v(A,y.prototype),A.isPureReactComponent=!0;var T=Array.isArray;function C(){}var N={H:null,A:null,T:null,S:null},I=Object.prototype.hasOwnProperty;function k(B,$,J){var ee=J.ref;return{$$typeof:t,type:B,key:$,ref:ee!==void 0?ee:null,props:J}}function R(B,$){return k(B.type,$,B.props)}function F(B){return typeof B=="object"&&B!==null&&B.$$typeof===t}function L(B){var $={"=":"=0",":":"=2"};return"$"+B.replace(/[=:]/g,function(J){return $[J]})}var q=/\/+/g;function j(B,$){return typeof B=="object"&&B!==null&&B.key!=null?L(""+B.key):$.toString(36)}function W(B){switch(B.status){case"fulfilled":return B.value;case"rejected":throw B.reason;default:switch(typeof B.status=="string"?B.then(C,C):(B.status="pending",B.then(function($){B.status==="pending"&&(B.status="fulfilled",B.value=$)},function($){B.status==="pending"&&(B.status="rejected",B.reason=$)})),B.status){case"fulfilled":return B.value;case"rejected":throw B.reason}}throw B}function K(B,$,J,ee,he){var ue=typeof B;(ue==="undefined"||ue==="boolean")&&(B=null);var ge=!1;if(B===null)ge=!0;else switch(ue){case"bigint":case"string":case"number":ge=!0;break;case"object":switch(B.$$typeof){case t:case e:ge=!0;break;case d:return ge=B._init,K(ge(B._payload),$,J,ee,he)}}if(ge)return he=he(B),ge=ee===""?"."+j(B,0):ee,T(he)?(J="",ge!=null&&(J=ge.replace(q,"$&/")+"/"),K(he,$,J,"",function(_e){return _e})):he!=null&&(F(he)&&(he=R(he,J+(he.key==null||B&&B.key===he.key?"":(""+he.key).replace(q,"$&/")+"/")+ge)),$.push(he)),1;ge=0;var Ae=ee===""?".":ee+":";if(T(B))for(var ie=0;ie>>1,te=K[Y];if(0>>1;Yi(J,Q))eei(he,J)?(K[Y]=he,K[ee]=Q,Y=ee):(K[Y]=J,K[$]=Q,Y=$);else if(eei(he,Q))K[Y]=he,K[ee]=Q,Y=ee;else break e}}return G}function i(K,G){var Q=K.sortIndex-G.sortIndex;return Q!==0?Q:K.id-G.id}if(t.unstable_now=void 0,typeof performance=="object"&&typeof performance.now=="function"){var a=performance;t.unstable_now=function(){return a.now()}}else{var s=Date,o=s.now();t.unstable_now=function(){return s.now()-o}}var c=[],u=[],d=1,f=null,g=3,p=!1,x=!1,v=!1,b=!1,y=typeof setTimeout=="function"?setTimeout:null,E=typeof clearTimeout=="function"?clearTimeout:null,w=typeof setImmediate<"u"?setImmediate:null;function A(K){for(var G=n(u);G!==null;){if(G.callback===null)r(u);else if(G.startTime<=K)r(u),G.sortIndex=G.expirationTime,e(c,G);else break;G=n(u)}}function T(K){if(v=!1,A(K),!x)if(n(c)!==null)x=!0,C||(C=!0,L());else{var G=n(u);G!==null&&W(T,G.startTime-K)}}var C=!1,N=-1,I=5,k=-1;function R(){return b?!0:!(t.unstable_now()-kK&&R());){var Y=f.callback;if(typeof Y=="function"){f.callback=null,g=f.priorityLevel;var te=Y(f.expirationTime<=K);if(K=t.unstable_now(),typeof te=="function"){f.callback=te,A(K),G=!0;break t}f===n(c)&&r(c),A(K)}else r(c);f=n(c)}if(f!==null)G=!0;else{var B=n(u);B!==null&&W(T,B.startTime-K),G=!1}}break e}finally{f=null,g=Q,p=!1}G=void 0}}finally{G?L():C=!1}}}var L;if(typeof w=="function")L=function(){w(F)};else if(typeof MessageChannel<"u"){var q=new MessageChannel,j=q.port2;q.port1.onmessage=F,L=function(){j.postMessage(null)}}else L=function(){y(F,0)};function W(K,G){N=y(function(){K(t.unstable_now())},G)}t.unstable_IdlePriority=5,t.unstable_ImmediatePriority=1,t.unstable_LowPriority=4,t.unstable_NormalPriority=3,t.unstable_Profiling=null,t.unstable_UserBlockingPriority=2,t.unstable_cancelCallback=function(K){K.callback=null},t.unstable_forceFrameRate=function(K){0>K||125Y?(K.sortIndex=Q,e(u,K),n(c)===null&&K===n(u)&&(v?(E(N),N=-1):v=!0,W(T,Q-Y))):(K.sortIndex=te,e(c,K),x||p||(x=!0,C||(C=!0,L()))),K},t.unstable_shouldYield=R,t.unstable_wrapCallback=function(K){var G=g;return function(){var Q=g;g=G;try{return K.apply(this,arguments)}finally{g=Q}}}})(jT)),jT}var bF;function $re(){return bF||(bF=1,PT.exports=Hre()),PT.exports}var RT={exports:{}},ia={};var yF;function Vre(){if(yF)return ia;yF=1;var t=Yp();function e(c){var u="https://react.dev/errors/"+c;if(1"u"||typeof __REACT_DEVTOOLS_GLOBAL_HOOK__.checkDCE!="function"))try{__REACT_DEVTOOLS_GLOBAL_HOOK__.checkDCE(t)}catch(e){console.error(e)}}return t(),RT.exports=Vre(),RT.exports}var _F;function Gre(){if(_F)return ux;_F=1;var t=$re(),e=Yp(),n=O9();function r(h){var m="https://react.dev/errors/"+h;if(1te||(h.current=Y[te],Y[te]=null,te--)}function J(h,m){te++,Y[te]=h.current,h.current=m}var ee=B(null),he=B(null),ue=B(null),ge=B(null);function Ae(h,m){switch(J(ue,m),J(he,h),J(ee,null),m.nodeType){case 9:case 11:h=(h=m.documentElement)&&(h=h.namespaceURI)?L5(h):0;break;default:if(h=m.tagName,m=m.namespaceURI)m=L5(m),h=I5(m,h);else switch(h){case"svg":h=1;break;case"math":h=2;break;default:h=0}}$(ee),J(ee,h)}function ie(){$(ee),$(he),$(ue)}function _e(h){h.memoizedState!==null&&J(ge,h);var m=ee.current,_=I5(m,h.type);m!==_&&(J(he,h),J(ee,_))}function Oe(h){he.current===h&&($(ee),$(he)),ge.current===h&&($(ge),ax._currentValue=Q)}var le,Le;function z(h){if(le===void 0)try{throw Error()}catch(_){var m=_.stack.trim().match(/\n( *(at )?)/);le=m&&m[1]||"",Le=-1<_.stack.indexOf(` + at`)?" ()":-1<_.stack.indexOf("@")?"@unknown:0:0":""}return` +`+le+h+Le}var H=!1;function ne(h,m){if(!h||H)return"";H=!0;var _=Error.prepareStackTrace;Error.prepareStackTrace=void 0;try{var S={DetermineComponentFrameRoot:function(){try{if(m){var ht=function(){throw Error()};if(Object.defineProperty(ht.prototype,"props",{set:function(){throw Error()}}),typeof Reflect=="object"&&Reflect.construct){try{Reflect.construct(ht,[])}catch(st){var Je=st}Reflect.construct(h,[],ht)}else{try{ht.call()}catch(st){Je=st}h.call(ht.prototype)}}else{try{throw Error()}catch(st){Je=st}(ht=h())&&typeof ht.catch=="function"&&ht.catch(function(){})}}catch(st){if(st&&Je&&typeof st.stack=="string")return[st.stack,Je.stack]}return[null,null]}};S.DetermineComponentFrameRoot.displayName="DetermineComponentFrameRoot";var P=Object.getOwnPropertyDescriptor(S.DetermineComponentFrameRoot,"name");P&&P.configurable&&Object.defineProperty(S.DetermineComponentFrameRoot,"name",{value:"DetermineComponentFrameRoot"});var D=S.DetermineComponentFrameRoot(),X=D[0],ce=D[1];if(X&&ce){var Ce=X.split(` +`),Ze=ce.split(` +`);for(P=S=0;SP||Ce[S]!==Ze[P]){var ct=` +`+Ce[S].replace(" at new "," at ");return h.displayName&&ct.includes("")&&(ct=ct.replace("",h.displayName)),ct}while(1<=S&&0<=P);break}}}finally{H=!1,Error.prepareStackTrace=_}return(_=h?h.displayName||h.name:"")?z(_):""}function V(h,m){switch(h.tag){case 26:case 27:case 5:return z(h.type);case 16:return z("Lazy");case 13:return h.child!==m&&m!==null?z("Suspense Fallback"):z("Suspense");case 19:return z("SuspenseList");case 0:case 15:return ne(h.type,!1);case 11:return ne(h.type.render,!1);case 1:return ne(h.type,!0);case 31:return z("Activity");default:return""}}function Z(h){try{var m="",_=null;do m+=V(h,_),_=h,h=h.return;while(h);return m}catch(S){return` +Error generating stack: `+S.message+` +`+S.stack}}var se=Object.prototype.hasOwnProperty,be=t.unstable_scheduleCallback,Te=t.unstable_cancelCallback,Me=t.unstable_shouldYield,U=t.unstable_requestPaint,O=t.unstable_now,re=t.unstable_getCurrentPriorityLevel,ve=t.unstable_ImmediatePriority,Se=t.unstable_UserBlockingPriority,ye=t.unstable_NormalPriority,rt=t.unstable_LowPriority,He=t.unstable_IdlePriority,fe=t.log,qe=t.unstable_setDisableYieldValue,Ue=null,oe=null;function tt(h){if(typeof fe=="function"&&qe(h),oe&&typeof oe.setStrictMode=="function")try{oe.setStrictMode(Ue,h)}catch{}}var $e=Math.clz32?Math.clz32:me,Ye=Math.log,ut=Math.LN2;function me(h){return h>>>=0,h===0?32:31-(Ye(h)/ut|0)|0}var Qe=256,xe=262144,ze=4194304;function Pe(h){var m=h&42;if(m!==0)return m;switch(h&-h){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:return h&261888;case 262144:case 524288:case 1048576:case 2097152:return h&3932160;case 4194304:case 8388608:case 16777216:case 33554432:return h&62914560;case 67108864:return 67108864;case 134217728:return 134217728;case 268435456:return 268435456;case 536870912:return 536870912;case 1073741824:return 0;default:return h}}function we(h,m,_){var S=h.pendingLanes;if(S===0)return 0;var P=0,D=h.suspendedLanes,X=h.pingedLanes;h=h.warmLanes;var ce=S&134217727;return ce!==0?(S=ce&~D,S!==0?P=Pe(S):(X&=ce,X!==0?P=Pe(X):_||(_=ce&~h,_!==0&&(P=Pe(_))))):(ce=S&~D,ce!==0?P=Pe(ce):X!==0?P=Pe(X):_||(_=S&~h,_!==0&&(P=Pe(_)))),P===0?0:m!==0&&m!==P&&(m&D)===0&&(D=P&-P,_=m&-m,D>=_||D===32&&(_&4194048)!==0)?m:P}function de(h,m){return(h.pendingLanes&~(h.suspendedLanes&~h.pingedLanes)&m)===0}function ot(h,m){switch(h){case 1:case 2:case 4:case 8:case 64:return m+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 m+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 yt(){var h=ze;return ze<<=1,(ze&62914560)===0&&(ze=4194304),h}function Et(h){for(var m=[],_=0;31>_;_++)m.push(h);return m}function Qt(h,m){h.pendingLanes|=m,m!==268435456&&(h.suspendedLanes=0,h.pingedLanes=0,h.warmLanes=0)}function Ot(h,m,_,S,P,D){var X=h.pendingLanes;h.pendingLanes=_,h.suspendedLanes=0,h.pingedLanes=0,h.warmLanes=0,h.expiredLanes&=_,h.entangledLanes&=_,h.errorRecoveryDisabledLanes&=_,h.shellSuspendCounter=0;var ce=h.entanglements,Ce=h.expirationTimes,Ze=h.hiddenUpdates;for(_=X&~_;0<_;){var ct=31-$e(_),ht=1<"u")return null;try{return h.activeElement||h.body}catch{return h.body}}var Fr=/[\n"\\]/g;function Dn(h){return h.replace(Fr,function(m){return"\\"+m.charCodeAt(0).toString(16)+" "})}function bo(h,m,_,S,P,D,X,ce){h.name="",X!=null&&typeof X!="function"&&typeof X!="symbol"&&typeof X!="boolean"?h.type=X:h.removeAttribute("type"),m!=null?X==="number"?(m===0&&h.value===""||h.value!=m)&&(h.value=""+hn(m)):h.value!==""+hn(m)&&(h.value=""+hn(m)):X!=="submit"&&X!=="reset"||h.removeAttribute("value"),m!=null?Ti(h,X,hn(m)):_!=null?Ti(h,X,hn(_)):S!=null&&h.removeAttribute("value"),P==null&&D!=null&&(h.defaultChecked=!!D),P!=null&&(h.checked=P&&typeof P!="function"&&typeof P!="symbol"),ce!=null&&typeof ce!="function"&&typeof ce!="symbol"&&typeof ce!="boolean"?h.name=""+hn(ce):h.removeAttribute("name")}function In(h,m,_,S,P,D,X,ce){if(D!=null&&typeof D!="function"&&typeof D!="symbol"&&typeof D!="boolean"&&(h.type=D),m!=null||_!=null){if(!(D!=="submit"&&D!=="reset"||m!=null)){Jt(h);return}_=_!=null?""+hn(_):"",m=m!=null?""+hn(m):_,ce||m===h.value||(h.value=m),h.defaultValue=m}S=S??P,S=typeof S!="function"&&typeof S!="symbol"&&!!S,h.checked=ce?h.checked:!!S,h.defaultChecked=!!S,X!=null&&typeof X!="function"&&typeof X!="symbol"&&typeof X!="boolean"&&(h.name=X),Jt(h)}function Ti(h,m,_){m==="number"&&Nr(h.ownerDocument)===h||h.defaultValue===""+_||(h.defaultValue=""+_)}function li(h,m,_,S){if(h=h.options,m){m={};for(var P=0;P<_.length;P++)m["$"+_[P]]=!0;for(_=0;_"u"||typeof window.document>"u"||typeof window.document.createElement>"u"),NE=!1;if(Dl)try{var Sg={};Object.defineProperty(Sg,"passive",{get:function(){NE=!0}}),window.addEventListener("test",Sg,Sg),window.removeEventListener("test",Sg,Sg)}catch{NE=!1}var tu=null,CE=null,uy=null;function IL(){if(uy)return uy;var h,m=CE,_=m.length,S,P="value"in tu?tu.value:tu.textContent,D=P.length;for(h=0;h<_&&m[h]===P[h];h++);var X=_-h;for(S=1;S<=X&&m[_-S]===P[D-S];S++);return uy=P.slice(h,1=Tg),$L=" ",VL=!1;function GL(h,m){switch(h){case"keyup":return ane.indexOf(m.keyCode)!==-1;case"keydown":return m.keyCode!==229;case"keypress":case"mousedown":case"focusout":return!0;default:return!1}}function WL(h){return h=h.detail,typeof h=="object"&&"data"in h?h.data:null}var Tf=!1;function one(h,m){switch(h){case"compositionend":return WL(m);case"keypress":return m.which!==32?null:(VL=!0,$L);case"textInput":return h=m.data,h===$L&&VL?null:h;default:return null}}function lne(h,m){if(Tf)return h==="compositionend"||!DE&&GL(h,m)?(h=IL(),uy=CE=tu=null,Tf=!1,h):null;switch(h){case"paste":return null;case"keypress":if(!(m.ctrlKey||m.altKey||m.metaKey)||m.ctrlKey&&m.altKey){if(m.char&&1=m)return{node:_,offset:m-h};h=S}e:{for(;_;){if(_.nextSibling){_=_.nextSibling;break e}_=_.parentNode}_=void 0}_=e4(_)}}function n4(h,m){return h&&m?h===m?!0:h&&h.nodeType===3?!1:m&&m.nodeType===3?n4(h,m.parentNode):"contains"in h?h.contains(m):h.compareDocumentPosition?!!(h.compareDocumentPosition(m)&16):!1:!1}function r4(h){h=h!=null&&h.ownerDocument!=null&&h.ownerDocument.defaultView!=null?h.ownerDocument.defaultView:window;for(var m=Nr(h.document);m instanceof h.HTMLIFrameElement;){try{var _=typeof m.contentWindow.location.href=="string"}catch{_=!1}if(_)h=m.contentWindow;else break;m=Nr(h.document)}return m}function IE(h){var m=h&&h.nodeName&&h.nodeName.toLowerCase();return m&&(m==="input"&&(h.type==="text"||h.type==="search"||h.type==="tel"||h.type==="url"||h.type==="password")||m==="textarea"||h.contentEditable==="true")}var gne=Dl&&"documentMode"in document&&11>=document.documentMode,Af=null,FE=null,kg=null,UE=!1;function i4(h,m,_){var S=_.window===_?_.document:_.nodeType===9?_:_.ownerDocument;UE||Af==null||Af!==Nr(S)||(S=Af,"selectionStart"in S&&IE(S)?S={start:S.selectionStart,end:S.selectionEnd}:(S=(S.ownerDocument&&S.ownerDocument.defaultView||window).getSelection(),S={anchorNode:S.anchorNode,anchorOffset:S.anchorOffset,focusNode:S.focusNode,focusOffset:S.focusOffset}),kg&&Cg(kg,S)||(kg=S,S=rv(FE,"onSelect"),0>=X,P-=X,qo=1<<32-$e(m)+P|_<rn?(vn=It,It=null):vn=It.sibling;var Nn=Je(Be,It,Xe[rn],dt);if(Nn===null){It===null&&(It=vn);break}h&&It&&Nn.alternate===null&&m(Be,It),Re=D(Nn,Re,rn),An===null?zt=Nn:An.sibling=Nn,An=Nn,It=vn}if(rn===Xe.length)return _(Be,It),Sn&&Ll(Be,rn),zt;if(It===null){for(;rnrn?(vn=It,It=null):vn=It.sibling;var Su=Je(Be,It,Nn.value,dt);if(Su===null){It===null&&(It=vn);break}h&&It&&Su.alternate===null&&m(Be,It),Re=D(Su,Re,rn),An===null?zt=Su:An.sibling=Su,An=Su,It=vn}if(Nn.done)return _(Be,It),Sn&&Ll(Be,rn),zt;if(It===null){for(;!Nn.done;rn++,Nn=Xe.next())Nn=ht(Be,Nn.value,dt),Nn!==null&&(Re=D(Nn,Re,rn),An===null?zt=Nn:An.sibling=Nn,An=Nn);return Sn&&Ll(Be,rn),zt}for(It=S(It);!Nn.done;rn++,Nn=Xe.next())Nn=st(It,Be,rn,Nn.value,dt),Nn!==null&&(h&&Nn.alternate!==null&&It.delete(Nn.key===null?rn:Nn.key),Re=D(Nn,Re,rn),An===null?zt=Nn:An.sibling=Nn,An=Nn);return h&&It.forEach(function(Lre){return m(Be,Lre)}),Sn&&Ll(Be,rn),zt}function Wn(Be,Re,Xe,dt){if(typeof Xe=="object"&&Xe!==null&&Xe.type===v&&Xe.key===null&&(Xe=Xe.props.children),typeof Xe=="object"&&Xe!==null){switch(Xe.$$typeof){case p:e:{for(var zt=Xe.key;Re!==null;){if(Re.key===zt){if(zt=Xe.type,zt===v){if(Re.tag===7){_(Be,Re.sibling),dt=P(Re,Xe.props.children),dt.return=Be,Be=dt;break e}}else if(Re.elementType===zt||typeof zt=="object"&&zt!==null&&zt.$$typeof===I&&Gd(zt)===Re.type){_(Be,Re.sibling),dt=P(Re,Xe.props),Lg(dt,Xe),dt.return=Be,Be=dt;break e}_(Be,Re);break}else m(Be,Re);Re=Re.sibling}Xe.type===v?(dt=zd(Xe.props.children,Be.mode,dt,Xe.key),dt.return=Be,Be=dt):(dt=vy(Xe.type,Xe.key,Xe.props,null,Be.mode,dt),Lg(dt,Xe),dt.return=Be,Be=dt)}return X(Be);case x:e:{for(zt=Xe.key;Re!==null;){if(Re.key===zt)if(Re.tag===4&&Re.stateNode.containerInfo===Xe.containerInfo&&Re.stateNode.implementation===Xe.implementation){_(Be,Re.sibling),dt=P(Re,Xe.children||[]),dt.return=Be,Be=dt;break e}else{_(Be,Re);break}else m(Be,Re);Re=Re.sibling}dt=WE(Xe,Be.mode,dt),dt.return=Be,Be=dt}return X(Be);case I:return Xe=Gd(Xe),Wn(Be,Re,Xe,dt)}if(W(Xe))return Dt(Be,Re,Xe,dt);if(L(Xe)){if(zt=L(Xe),typeof zt!="function")throw Error(r(150));return Xe=zt.call(Xe),Wt(Be,Re,Xe,dt)}if(typeof Xe.then=="function")return Wn(Be,Re,Ay(Xe),dt);if(Xe.$$typeof===w)return Wn(Be,Re,Sy(Be,Xe),dt);Ny(Be,Xe)}return typeof Xe=="string"&&Xe!==""||typeof Xe=="number"||typeof Xe=="bigint"?(Xe=""+Xe,Re!==null&&Re.tag===6?(_(Be,Re.sibling),dt=P(Re,Xe),dt.return=Be,Be=dt):(_(Be,Re),dt=GE(Xe,Be.mode,dt),dt.return=Be,Be=dt),X(Be)):_(Be,Re)}return function(Be,Re,Xe,dt){try{Og=0;var zt=Wn(Be,Re,Xe,dt);return Ff=null,zt}catch(It){if(It===If||It===Ey)throw It;var An=cs(29,It,null,Be.mode);return An.lanes=dt,An.return=Be,An}finally{}}}var qd=A4(!0),N4=A4(!1),su=!1;function i2(h){h.updateQueue={baseState:h.memoizedState,firstBaseUpdate:null,lastBaseUpdate:null,shared:{pending:null,lanes:0,hiddenCallbacks:null},callbacks:null}}function a2(h,m){h=h.updateQueue,m.updateQueue===h&&(m.updateQueue={baseState:h.baseState,firstBaseUpdate:h.firstBaseUpdate,lastBaseUpdate:h.lastBaseUpdate,shared:h.shared,callbacks:null})}function ou(h){return{lane:h,tag:0,payload:null,callback:null,next:null}}function lu(h,m,_){var S=h.updateQueue;if(S===null)return null;if(S=S.shared,(jn&2)!==0){var P=S.pending;return P===null?m.next=m:(m.next=P.next,P.next=m),S.pending=m,m=yy(h),d4(h,null,_),m}return by(h,S,m,_),yy(h)}function Ig(h,m,_){if(m=m.updateQueue,m!==null&&(m=m.shared,(_&4194048)!==0)){var S=m.lanes;S&=h.pendingLanes,_|=S,m.lanes=_,Ln(h,_)}}function s2(h,m){var _=h.updateQueue,S=h.alternate;if(S!==null&&(S=S.updateQueue,_===S)){var P=null,D=null;if(_=_.firstBaseUpdate,_!==null){do{var X={lane:_.lane,tag:_.tag,payload:_.payload,callback:null,next:null};D===null?P=D=X:D=D.next=X,_=_.next}while(_!==null);D===null?P=D=m:D=D.next=m}else P=D=m;_={baseState:S.baseState,firstBaseUpdate:P,lastBaseUpdate:D,shared:S.shared,callbacks:S.callbacks},h.updateQueue=_;return}h=_.lastBaseUpdate,h===null?_.firstBaseUpdate=m:h.next=m,_.lastBaseUpdate=m}var o2=!1;function Fg(){if(o2){var h=Lf;if(h!==null)throw h}}function Ug(h,m,_,S){o2=!1;var P=h.updateQueue;su=!1;var D=P.firstBaseUpdate,X=P.lastBaseUpdate,ce=P.shared.pending;if(ce!==null){P.shared.pending=null;var Ce=ce,Ze=Ce.next;Ce.next=null,X===null?D=Ze:X.next=Ze,X=Ce;var ct=h.alternate;ct!==null&&(ct=ct.updateQueue,ce=ct.lastBaseUpdate,ce!==X&&(ce===null?ct.firstBaseUpdate=Ze:ce.next=Ze,ct.lastBaseUpdate=Ce))}if(D!==null){var ht=P.baseState;X=0,ct=Ze=Ce=null,ce=D;do{var Je=ce.lane&-536870913,st=Je!==ce.lane;if(st?(yn&Je)===Je:(S&Je)===Je){Je!==0&&Je===Of&&(o2=!0),ct!==null&&(ct=ct.next={lane:0,tag:ce.tag,payload:ce.payload,callback:null,next:null});e:{var Dt=h,Wt=ce;Je=m;var Wn=_;switch(Wt.tag){case 1:if(Dt=Wt.payload,typeof Dt=="function"){ht=Dt.call(Wn,ht,Je);break e}ht=Dt;break e;case 3:Dt.flags=Dt.flags&-65537|128;case 0:if(Dt=Wt.payload,Je=typeof Dt=="function"?Dt.call(Wn,ht,Je):Dt,Je==null)break e;ht=f({},ht,Je);break e;case 2:su=!0}}Je=ce.callback,Je!==null&&(h.flags|=64,st&&(h.flags|=8192),st=P.callbacks,st===null?P.callbacks=[Je]:st.push(Je))}else st={lane:Je,tag:ce.tag,payload:ce.payload,callback:ce.callback,next:null},ct===null?(Ze=ct=st,Ce=ht):ct=ct.next=st,X|=Je;if(ce=ce.next,ce===null){if(ce=P.shared.pending,ce===null)break;st=ce,ce=st.next,st.next=null,P.lastBaseUpdate=st,P.shared.pending=null}}while(!0);ct===null&&(Ce=ht),P.baseState=Ce,P.firstBaseUpdate=Ze,P.lastBaseUpdate=ct,D===null&&(P.shared.lanes=0),fu|=X,h.lanes=X,h.memoizedState=ht}}function C4(h,m){if(typeof h!="function")throw Error(r(191,h));h.call(m)}function k4(h,m){var _=h.callbacks;if(_!==null)for(h.callbacks=null,h=0;h<_.length;h++)C4(_[h],m)}var Uf=B(null),Cy=B(0);function P4(h,m){h=ql,J(Cy,h),J(Uf,m),ql=h|m.baseLanes}function l2(){J(Cy,ql),J(Uf,Uf.current)}function c2(){ql=Cy.current,$(Uf),$(Cy)}var us=B(null),Is=null;function cu(h){var m=h.alternate;J(Ur,Ur.current&1),J(us,h),Is===null&&(m===null||Uf.current!==null||m.memoizedState!==null)&&(Is=h)}function u2(h){J(Ur,Ur.current),J(us,h),Is===null&&(Is=h)}function j4(h){h.tag===22?(J(Ur,Ur.current),J(us,h),Is===null&&(Is=h)):uu()}function uu(){J(Ur,Ur.current),J(us,us.current)}function ds(h){$(us),Is===h&&(Is=null),$(Ur)}var Ur=B(0);function ky(h){for(var m=h;m!==null;){if(m.tag===13){var _=m.memoizedState;if(_!==null&&(_=_.dehydrated,_===null||gT(_)||xT(_)))return m}else if(m.tag===19&&(m.memoizedProps.revealOrder==="forwards"||m.memoizedProps.revealOrder==="backwards"||m.memoizedProps.revealOrder==="unstable_legacy-backwards"||m.memoizedProps.revealOrder==="together")){if((m.flags&128)!==0)return m}else if(m.child!==null){m.child.return=m,m=m.child;continue}if(m===h)break;for(;m.sibling===null;){if(m.return===null||m.return===h)return null;m=m.return}m.sibling.return=m.return,m=m.sibling}return null}var Ul=0,nn=null,Vn=null,Kr=null,Py=!1,zf=!1,Kd=!1,jy=0,zg=0,Bf=null,Tne=0;function Cr(){throw Error(r(321))}function d2(h,m){if(m===null)return!1;for(var _=0;_D?D:8;var X=K.T,ce={};K.T=ce,T2(h,!1,m,_);try{var Ce=P(),Ze=K.S;if(Ze!==null&&Ze(ce,Ce),Ce!==null&&typeof Ce=="object"&&typeof Ce.then=="function"){var ct=Ene(Ce,S);Hg(h,m,ct,ms(h))}else Hg(h,m,S,ms(h))}catch(ht){Hg(h,m,{then:function(){},status:"rejected",reason:ht},ms())}finally{G.p=D,X!==null&&ce.types!==null&&(X.types=ce.types),K.T=X}}function Pne(){}function M2(h,m,_,S){if(h.tag!==5)throw Error(r(476));var P=lI(h).queue;oI(h,P,m,Q,_===null?Pne:function(){return cI(h),_(S)})}function lI(h){var m=h.memoizedState;if(m!==null)return m;m={memoizedState:Q,baseState:Q,baseQueue:null,queue:{pending:null,lanes:0,dispatch:null,lastRenderedReducer:zl,lastRenderedState:Q},next:null};var _={};return m.next={memoizedState:_,baseState:_,baseQueue:null,queue:{pending:null,lanes:0,dispatch:null,lastRenderedReducer:zl,lastRenderedState:_},next:null},h.memoizedState=m,h=h.alternate,h!==null&&(h.memoizedState=m),m}function cI(h){var m=lI(h);m.next===null&&(m=h.alternate.memoizedState),Hg(h,m.next.queue,{},ms())}function E2(){return $i(ax)}function uI(){return zr().memoizedState}function dI(){return zr().memoizedState}function jne(h){for(var m=h.return;m!==null;){switch(m.tag){case 24:case 3:var _=ms();h=ou(_);var S=lu(m,h,_);S!==null&&(Ha(S,m,_),Ig(S,m,_)),m={cache:e2()},h.payload=m;return}m=m.return}}function Rne(h,m,_){var S=ms();_={lane:S,revertLane:0,gesture:null,action:_,hasEagerState:!1,eagerState:null,next:null},Fy(h)?fI(m,_):(_=$E(h,m,_,S),_!==null&&(Ha(_,h,S),mI(_,m,S)))}function hI(h,m,_){var S=ms();Hg(h,m,_,S)}function Hg(h,m,_,S){var P={lane:S,revertLane:0,gesture:null,action:_,hasEagerState:!1,eagerState:null,next:null};if(Fy(h))fI(m,P);else{var D=h.alternate;if(h.lanes===0&&(D===null||D.lanes===0)&&(D=m.lastRenderedReducer,D!==null))try{var X=m.lastRenderedState,ce=D(X,_);if(P.hasEagerState=!0,P.eagerState=ce,ls(ce,X))return by(h,m,P,0),tr===null&&xy(),!1}catch{}finally{}if(_=$E(h,m,P,S),_!==null)return Ha(_,h,S),mI(_,m,S),!0}return!1}function T2(h,m,_,S){if(S={lane:2,revertLane:iT(),gesture:null,action:S,hasEagerState:!1,eagerState:null,next:null},Fy(h)){if(m)throw Error(r(479))}else m=$E(h,_,S,2),m!==null&&Ha(m,h,2)}function Fy(h){var m=h.alternate;return h===nn||m!==null&&m===nn}function fI(h,m){zf=Py=!0;var _=h.pending;_===null?m.next=m:(m.next=_.next,_.next=m),h.pending=m}function mI(h,m,_){if((_&4194048)!==0){var S=m.lanes;S&=h.pendingLanes,_|=S,m.lanes=_,Ln(h,_)}}var $g={readContext:$i,use:Dy,useCallback:Cr,useContext:Cr,useEffect:Cr,useImperativeHandle:Cr,useLayoutEffect:Cr,useInsertionEffect:Cr,useMemo:Cr,useReducer:Cr,useRef:Cr,useState:Cr,useDebugValue:Cr,useDeferredValue:Cr,useTransition:Cr,useSyncExternalStore:Cr,useId:Cr,useHostTransitionStatus:Cr,useFormState:Cr,useActionState:Cr,useOptimistic:Cr,useMemoCache:Cr,useCacheRefresh:Cr};$g.useEffectEvent=Cr;var pI={readContext:$i,use:Dy,useCallback:function(h,m){return ya().memoizedState=[h,m===void 0?null:m],h},useContext:$i,useEffect:Q4,useImperativeHandle:function(h,m,_){_=_!=null?_.concat([h]):null,Ly(4194308,4,nI.bind(null,m,h),_)},useLayoutEffect:function(h,m){return Ly(4194308,4,h,m)},useInsertionEffect:function(h,m){Ly(4,2,h,m)},useMemo:function(h,m){var _=ya();m=m===void 0?null:m;var S=h();if(Kd){tt(!0);try{h()}finally{tt(!1)}}return _.memoizedState=[S,m],S},useReducer:function(h,m,_){var S=ya();if(_!==void 0){var P=_(m);if(Kd){tt(!0);try{_(m)}finally{tt(!1)}}}else P=m;return S.memoizedState=S.baseState=P,h={pending:null,lanes:0,dispatch:null,lastRenderedReducer:h,lastRenderedState:P},S.queue=h,h=h.dispatch=Rne.bind(null,nn,h),[S.memoizedState,h]},useRef:function(h){var m=ya();return h={current:h},m.memoizedState=h},useState:function(h){h=y2(h);var m=h.queue,_=hI.bind(null,nn,m);return m.dispatch=_,[h.memoizedState,_]},useDebugValue:w2,useDeferredValue:function(h,m){var _=ya();return S2(_,h,m)},useTransition:function(){var h=y2(!1);return h=oI.bind(null,nn,h.queue,!0,!1),ya().memoizedState=h,[!1,h]},useSyncExternalStore:function(h,m,_){var S=nn,P=ya();if(Sn){if(_===void 0)throw Error(r(407));_=_()}else{if(_=m(),tr===null)throw Error(r(349));(yn&127)!==0||L4(S,m,_)}P.memoizedState=_;var D={value:_,getSnapshot:m};return P.queue=D,Q4(F4.bind(null,S,D,h),[h]),S.flags|=2048,Hf(9,{destroy:void 0},I4.bind(null,S,D,_,m),null),_},useId:function(){var h=ya(),m=tr.identifierPrefix;if(Sn){var _=Ko,S=qo;_=(S&~(1<<32-$e(S)-1)).toString(32)+_,m="_"+m+"R_"+_,_=jy++,0<_&&(m+="H"+_.toString(32)),m+="_"}else _=Tne++,m="_"+m+"r_"+_.toString(32)+"_";return h.memoizedState=m},useHostTransitionStatus:E2,useFormState:q4,useActionState:q4,useOptimistic:function(h){var m=ya();m.memoizedState=m.baseState=h;var _={pending:null,lanes:0,dispatch:null,lastRenderedReducer:null,lastRenderedState:null};return m.queue=_,m=T2.bind(null,nn,!0,_),_.dispatch=m,[h,m]},useMemoCache:g2,useCacheRefresh:function(){return ya().memoizedState=jne.bind(null,nn)},useEffectEvent:function(h){var m=ya(),_={impl:h};return m.memoizedState=_,function(){if((jn&2)!==0)throw Error(r(440));return _.impl.apply(void 0,arguments)}}},A2={readContext:$i,use:Dy,useCallback:iI,useContext:$i,useEffect:_2,useImperativeHandle:rI,useInsertionEffect:eI,useLayoutEffect:tI,useMemo:aI,useReducer:Oy,useRef:Z4,useState:function(){return Oy(zl)},useDebugValue:w2,useDeferredValue:function(h,m){var _=zr();return sI(_,Vn.memoizedState,h,m)},useTransition:function(){var h=Oy(zl)[0],m=zr().memoizedState;return[typeof h=="boolean"?h:Bg(h),m]},useSyncExternalStore:O4,useId:uI,useHostTransitionStatus:E2,useFormState:K4,useActionState:K4,useOptimistic:function(h,m){var _=zr();return B4(_,Vn,h,m)},useMemoCache:g2,useCacheRefresh:dI};A2.useEffectEvent=J4;var gI={readContext:$i,use:Dy,useCallback:iI,useContext:$i,useEffect:_2,useImperativeHandle:rI,useInsertionEffect:eI,useLayoutEffect:tI,useMemo:aI,useReducer:b2,useRef:Z4,useState:function(){return b2(zl)},useDebugValue:w2,useDeferredValue:function(h,m){var _=zr();return Vn===null?S2(_,h,m):sI(_,Vn.memoizedState,h,m)},useTransition:function(){var h=b2(zl)[0],m=zr().memoizedState;return[typeof h=="boolean"?h:Bg(h),m]},useSyncExternalStore:O4,useId:uI,useHostTransitionStatus:E2,useFormState:Y4,useActionState:Y4,useOptimistic:function(h,m){var _=zr();return Vn!==null?B4(_,Vn,h,m):(_.baseState=h,[h,_.queue.dispatch])},useMemoCache:g2,useCacheRefresh:dI};gI.useEffectEvent=J4;function N2(h,m,_,S){m=h.memoizedState,_=_(S,m),_=_==null?m:f({},m,_),h.memoizedState=_,h.lanes===0&&(h.updateQueue.baseState=_)}var C2={enqueueSetState:function(h,m,_){h=h._reactInternals;var S=ms(),P=ou(S);P.payload=m,_!=null&&(P.callback=_),m=lu(h,P,S),m!==null&&(Ha(m,h,S),Ig(m,h,S))},enqueueReplaceState:function(h,m,_){h=h._reactInternals;var S=ms(),P=ou(S);P.tag=1,P.payload=m,_!=null&&(P.callback=_),m=lu(h,P,S),m!==null&&(Ha(m,h,S),Ig(m,h,S))},enqueueForceUpdate:function(h,m){h=h._reactInternals;var _=ms(),S=ou(_);S.tag=2,m!=null&&(S.callback=m),m=lu(h,S,_),m!==null&&(Ha(m,h,_),Ig(m,h,_))}};function xI(h,m,_,S,P,D,X){return h=h.stateNode,typeof h.shouldComponentUpdate=="function"?h.shouldComponentUpdate(S,D,X):m.prototype&&m.prototype.isPureReactComponent?!Cg(_,S)||!Cg(P,D):!0}function bI(h,m,_,S){h=m.state,typeof m.componentWillReceiveProps=="function"&&m.componentWillReceiveProps(_,S),typeof m.UNSAFE_componentWillReceiveProps=="function"&&m.UNSAFE_componentWillReceiveProps(_,S),m.state!==h&&C2.enqueueReplaceState(m,m.state,null)}function Xd(h,m){var _=m;if("ref"in m){_={};for(var S in m)S!=="ref"&&(_[S]=m[S])}if(h=h.defaultProps){_===m&&(_=f({},_));for(var P in h)_[P]===void 0&&(_[P]=h[P])}return _}function yI(h){gy(h)}function vI(h){console.error(h)}function _I(h){gy(h)}function Uy(h,m){try{var _=h.onUncaughtError;_(m.value,{componentStack:m.stack})}catch(S){setTimeout(function(){throw S})}}function wI(h,m,_){try{var S=h.onCaughtError;S(_.value,{componentStack:_.stack,errorBoundary:m.tag===1?m.stateNode:null})}catch(P){setTimeout(function(){throw P})}}function k2(h,m,_){return _=ou(_),_.tag=3,_.payload={element:null},_.callback=function(){Uy(h,m)},_}function SI(h){return h=ou(h),h.tag=3,h}function MI(h,m,_,S){var P=_.type.getDerivedStateFromError;if(typeof P=="function"){var D=S.value;h.payload=function(){return P(D)},h.callback=function(){wI(m,_,S)}}var X=_.stateNode;X!==null&&typeof X.componentDidCatch=="function"&&(h.callback=function(){wI(m,_,S),typeof P!="function"&&(mu===null?mu=new Set([this]):mu.add(this));var ce=S.stack;this.componentDidCatch(S.value,{componentStack:ce!==null?ce:""})})}function Dne(h,m,_,S,P){if(_.flags|=32768,S!==null&&typeof S=="object"&&typeof S.then=="function"){if(m=_.alternate,m!==null&&Df(m,_,P,!0),_=us.current,_!==null){switch(_.tag){case 31:case 13:return Is===null?Zy():_.alternate===null&&kr===0&&(kr=3),_.flags&=-257,_.flags|=65536,_.lanes=P,S===Ty?_.flags|=16384:(m=_.updateQueue,m===null?_.updateQueue=new Set([S]):m.add(S),tT(h,S,P)),!1;case 22:return _.flags|=65536,S===Ty?_.flags|=16384:(m=_.updateQueue,m===null?(m={transitions:null,markerInstances:null,retryQueue:new Set([S])},_.updateQueue=m):(_=m.retryQueue,_===null?m.retryQueue=new Set([S]):_.add(S)),tT(h,S,P)),!1}throw Error(r(435,_.tag))}return tT(h,S,P),Zy(),!1}if(Sn)return m=us.current,m!==null?((m.flags&65536)===0&&(m.flags|=256),m.flags|=65536,m.lanes=P,S!==XE&&(h=Error(r(422),{cause:S}),jg(Rs(h,_)))):(S!==XE&&(m=Error(r(423),{cause:S}),jg(Rs(m,_))),h=h.current.alternate,h.flags|=65536,P&=-P,h.lanes|=P,S=Rs(S,_),P=k2(h.stateNode,S,P),s2(h,P),kr!==4&&(kr=2)),!1;var D=Error(r(520),{cause:S});if(D=Rs(D,_),Zg===null?Zg=[D]:Zg.push(D),kr!==4&&(kr=2),m===null)return!0;S=Rs(S,_),_=m;do{switch(_.tag){case 3:return _.flags|=65536,h=P&-P,_.lanes|=h,h=k2(_.stateNode,S,h),s2(_,h),!1;case 1:if(m=_.type,D=_.stateNode,(_.flags&128)===0&&(typeof m.getDerivedStateFromError=="function"||D!==null&&typeof D.componentDidCatch=="function"&&(mu===null||!mu.has(D))))return _.flags|=65536,P&=-P,_.lanes|=P,P=SI(P),MI(P,h,_,S),s2(_,P),!1}_=_.return}while(_!==null);return!1}var P2=Error(r(461)),Xr=!1;function Vi(h,m,_,S){m.child=h===null?N4(m,null,_,S):qd(m,h.child,_,S)}function EI(h,m,_,S,P){_=_.render;var D=m.ref;if("ref"in S){var X={};for(var ce in S)ce!=="ref"&&(X[ce]=S[ce])}else X=S;return $d(m),S=h2(h,m,_,X,D,P),ce=f2(),h!==null&&!Xr?(m2(h,m,P),Bl(h,m,P)):(Sn&&ce&&qE(m),m.flags|=1,Vi(h,m,S,P),m.child)}function TI(h,m,_,S,P){if(h===null){var D=_.type;return typeof D=="function"&&!VE(D)&&D.defaultProps===void 0&&_.compare===null?(m.tag=15,m.type=D,AI(h,m,D,S,P)):(h=vy(_.type,null,S,m,m.mode,P),h.ref=m.ref,h.return=m,m.child=h)}if(D=h.child,!U2(h,P)){var X=D.memoizedProps;if(_=_.compare,_=_!==null?_:Cg,_(X,S)&&h.ref===m.ref)return Bl(h,m,P)}return m.flags|=1,h=Ol(D,S),h.ref=m.ref,h.return=m,m.child=h}function AI(h,m,_,S,P){if(h!==null){var D=h.memoizedProps;if(Cg(D,S)&&h.ref===m.ref)if(Xr=!1,m.pendingProps=S=D,U2(h,P))(h.flags&131072)!==0&&(Xr=!0);else return m.lanes=h.lanes,Bl(h,m,P)}return j2(h,m,_,S,P)}function NI(h,m,_,S){var P=S.children,D=h!==null?h.memoizedState:null;if(h===null&&m.stateNode===null&&(m.stateNode={_visibility:1,_pendingMarkers:null,_retryCache:null,_transitions:null}),S.mode==="hidden"){if((m.flags&128)!==0){if(D=D!==null?D.baseLanes|_:_,h!==null){for(S=m.child=h.child,P=0;S!==null;)P=P|S.lanes|S.childLanes,S=S.sibling;S=P&~D}else S=0,m.child=null;return CI(h,m,D,_,S)}if((_&536870912)!==0)m.memoizedState={baseLanes:0,cachePool:null},h!==null&&My(m,D!==null?D.cachePool:null),D!==null?P4(m,D):l2(),j4(m);else return S=m.lanes=536870912,CI(h,m,D!==null?D.baseLanes|_:_,_,S)}else D!==null?(My(m,D.cachePool),P4(m,D),uu(),m.memoizedState=null):(h!==null&&My(m,null),l2(),uu());return Vi(h,m,P,_),m.child}function Vg(h,m){return h!==null&&h.tag===22||m.stateNode!==null||(m.stateNode={_visibility:1,_pendingMarkers:null,_retryCache:null,_transitions:null}),m.sibling}function CI(h,m,_,S,P){var D=n2();return D=D===null?null:{parent:qr._currentValue,pool:D},m.memoizedState={baseLanes:_,cachePool:D},h!==null&&My(m,null),l2(),j4(m),h!==null&&Df(h,m,S,!0),m.childLanes=P,null}function zy(h,m){return m=Hy({mode:m.mode,children:m.children},h.mode),m.ref=h.ref,h.child=m,m.return=h,m}function kI(h,m,_){return qd(m,h.child,null,_),h=zy(m,m.pendingProps),h.flags|=2,ds(m),m.memoizedState=null,h}function One(h,m,_){var S=m.pendingProps,P=(m.flags&128)!==0;if(m.flags&=-129,h===null){if(Sn){if(S.mode==="hidden")return h=zy(m,S),m.lanes=536870912,Vg(null,h);if(u2(m),(h=cr)?(h=H5(h,Ls),h=h!==null&&h.data==="&"?h:null,h!==null&&(m.memoizedState={dehydrated:h,treeContext:nu!==null?{id:qo,overflow:Ko}:null,retryLane:536870912,hydrationErrors:null},_=f4(h),_.return=m,m.child=_,Hi=m,cr=null)):h=null,h===null)throw iu(m);return m.lanes=536870912,null}return zy(m,S)}var D=h.memoizedState;if(D!==null){var X=D.dehydrated;if(u2(m),P)if(m.flags&256)m.flags&=-257,m=kI(h,m,_);else if(m.memoizedState!==null)m.child=h.child,m.flags|=128,m=null;else throw Error(r(558));else if(Xr||Df(h,m,_,!1),P=(_&h.childLanes)!==0,Xr||P){if(S=tr,S!==null&&(X=Mi(S,_),X!==0&&X!==D.retryLane))throw D.retryLane=X,Ud(h,X),Ha(S,h,X),P2;Zy(),m=kI(h,m,_)}else h=D.treeContext,cr=Fs(X.nextSibling),Hi=m,Sn=!0,ru=null,Ls=!1,h!==null&&g4(m,h),m=zy(m,S),m.flags|=4096;return m}return h=Ol(h.child,{mode:S.mode,children:S.children}),h.ref=m.ref,m.child=h,h.return=m,h}function By(h,m){var _=m.ref;if(_===null)h!==null&&h.ref!==null&&(m.flags|=4194816);else{if(typeof _!="function"&&typeof _!="object")throw Error(r(284));(h===null||h.ref!==_)&&(m.flags|=4194816)}}function j2(h,m,_,S,P){return $d(m),_=h2(h,m,_,S,void 0,P),S=f2(),h!==null&&!Xr?(m2(h,m,P),Bl(h,m,P)):(Sn&&S&&qE(m),m.flags|=1,Vi(h,m,_,P),m.child)}function PI(h,m,_,S,P,D){return $d(m),m.updateQueue=null,_=D4(m,S,_,P),R4(h),S=f2(),h!==null&&!Xr?(m2(h,m,D),Bl(h,m,D)):(Sn&&S&&qE(m),m.flags|=1,Vi(h,m,_,D),m.child)}function jI(h,m,_,S,P){if($d(m),m.stateNode===null){var D=kf,X=_.contextType;typeof X=="object"&&X!==null&&(D=$i(X)),D=new _(S,D),m.memoizedState=D.state!==null&&D.state!==void 0?D.state:null,D.updater=C2,m.stateNode=D,D._reactInternals=m,D=m.stateNode,D.props=S,D.state=m.memoizedState,D.refs={},i2(m),X=_.contextType,D.context=typeof X=="object"&&X!==null?$i(X):kf,D.state=m.memoizedState,X=_.getDerivedStateFromProps,typeof X=="function"&&(N2(m,_,X,S),D.state=m.memoizedState),typeof _.getDerivedStateFromProps=="function"||typeof D.getSnapshotBeforeUpdate=="function"||typeof D.UNSAFE_componentWillMount!="function"&&typeof D.componentWillMount!="function"||(X=D.state,typeof D.componentWillMount=="function"&&D.componentWillMount(),typeof D.UNSAFE_componentWillMount=="function"&&D.UNSAFE_componentWillMount(),X!==D.state&&C2.enqueueReplaceState(D,D.state,null),Ug(m,S,D,P),Fg(),D.state=m.memoizedState),typeof D.componentDidMount=="function"&&(m.flags|=4194308),S=!0}else if(h===null){D=m.stateNode;var ce=m.memoizedProps,Ce=Xd(_,ce);D.props=Ce;var Ze=D.context,ct=_.contextType;X=kf,typeof ct=="object"&&ct!==null&&(X=$i(ct));var ht=_.getDerivedStateFromProps;ct=typeof ht=="function"||typeof D.getSnapshotBeforeUpdate=="function",ce=m.pendingProps!==ce,ct||typeof D.UNSAFE_componentWillReceiveProps!="function"&&typeof D.componentWillReceiveProps!="function"||(ce||Ze!==X)&&bI(m,D,S,X),su=!1;var Je=m.memoizedState;D.state=Je,Ug(m,S,D,P),Fg(),Ze=m.memoizedState,ce||Je!==Ze||su?(typeof ht=="function"&&(N2(m,_,ht,S),Ze=m.memoizedState),(Ce=su||xI(m,_,Ce,S,Je,Ze,X))?(ct||typeof D.UNSAFE_componentWillMount!="function"&&typeof D.componentWillMount!="function"||(typeof D.componentWillMount=="function"&&D.componentWillMount(),typeof D.UNSAFE_componentWillMount=="function"&&D.UNSAFE_componentWillMount()),typeof D.componentDidMount=="function"&&(m.flags|=4194308)):(typeof D.componentDidMount=="function"&&(m.flags|=4194308),m.memoizedProps=S,m.memoizedState=Ze),D.props=S,D.state=Ze,D.context=X,S=Ce):(typeof D.componentDidMount=="function"&&(m.flags|=4194308),S=!1)}else{D=m.stateNode,a2(h,m),X=m.memoizedProps,ct=Xd(_,X),D.props=ct,ht=m.pendingProps,Je=D.context,Ze=_.contextType,Ce=kf,typeof Ze=="object"&&Ze!==null&&(Ce=$i(Ze)),ce=_.getDerivedStateFromProps,(Ze=typeof ce=="function"||typeof D.getSnapshotBeforeUpdate=="function")||typeof D.UNSAFE_componentWillReceiveProps!="function"&&typeof D.componentWillReceiveProps!="function"||(X!==ht||Je!==Ce)&&bI(m,D,S,Ce),su=!1,Je=m.memoizedState,D.state=Je,Ug(m,S,D,P),Fg();var st=m.memoizedState;X!==ht||Je!==st||su||h!==null&&h.dependencies!==null&&wy(h.dependencies)?(typeof ce=="function"&&(N2(m,_,ce,S),st=m.memoizedState),(ct=su||xI(m,_,ct,S,Je,st,Ce)||h!==null&&h.dependencies!==null&&wy(h.dependencies))?(Ze||typeof D.UNSAFE_componentWillUpdate!="function"&&typeof D.componentWillUpdate!="function"||(typeof D.componentWillUpdate=="function"&&D.componentWillUpdate(S,st,Ce),typeof D.UNSAFE_componentWillUpdate=="function"&&D.UNSAFE_componentWillUpdate(S,st,Ce)),typeof D.componentDidUpdate=="function"&&(m.flags|=4),typeof D.getSnapshotBeforeUpdate=="function"&&(m.flags|=1024)):(typeof D.componentDidUpdate!="function"||X===h.memoizedProps&&Je===h.memoizedState||(m.flags|=4),typeof D.getSnapshotBeforeUpdate!="function"||X===h.memoizedProps&&Je===h.memoizedState||(m.flags|=1024),m.memoizedProps=S,m.memoizedState=st),D.props=S,D.state=st,D.context=Ce,S=ct):(typeof D.componentDidUpdate!="function"||X===h.memoizedProps&&Je===h.memoizedState||(m.flags|=4),typeof D.getSnapshotBeforeUpdate!="function"||X===h.memoizedProps&&Je===h.memoizedState||(m.flags|=1024),S=!1)}return D=S,By(h,m),S=(m.flags&128)!==0,D||S?(D=m.stateNode,_=S&&typeof _.getDerivedStateFromError!="function"?null:D.render(),m.flags|=1,h!==null&&S?(m.child=qd(m,h.child,null,P),m.child=qd(m,null,_,P)):Vi(h,m,_,P),m.memoizedState=D.state,h=m.child):h=Bl(h,m,P),h}function RI(h,m,_,S){return Bd(),m.flags|=256,Vi(h,m,_,S),m.child}var R2={dehydrated:null,treeContext:null,retryLane:0,hydrationErrors:null};function D2(h){return{baseLanes:h,cachePool:w4()}}function O2(h,m,_){return h=h!==null?h.childLanes&~_:0,m&&(h|=fs),h}function DI(h,m,_){var S=m.pendingProps,P=!1,D=(m.flags&128)!==0,X;if((X=D)||(X=h!==null&&h.memoizedState===null?!1:(Ur.current&2)!==0),X&&(P=!0,m.flags&=-129),X=(m.flags&32)!==0,m.flags&=-33,h===null){if(Sn){if(P?cu(m):uu(),(h=cr)?(h=H5(h,Ls),h=h!==null&&h.data!=="&"?h:null,h!==null&&(m.memoizedState={dehydrated:h,treeContext:nu!==null?{id:qo,overflow:Ko}:null,retryLane:536870912,hydrationErrors:null},_=f4(h),_.return=m,m.child=_,Hi=m,cr=null)):h=null,h===null)throw iu(m);return xT(h)?m.lanes=32:m.lanes=536870912,null}var ce=S.children;return S=S.fallback,P?(uu(),P=m.mode,ce=Hy({mode:"hidden",children:ce},P),S=zd(S,P,_,null),ce.return=m,S.return=m,ce.sibling=S,m.child=ce,S=m.child,S.memoizedState=D2(_),S.childLanes=O2(h,X,_),m.memoizedState=R2,Vg(null,S)):(cu(m),L2(m,ce))}var Ce=h.memoizedState;if(Ce!==null&&(ce=Ce.dehydrated,ce!==null)){if(D)m.flags&256?(cu(m),m.flags&=-257,m=I2(h,m,_)):m.memoizedState!==null?(uu(),m.child=h.child,m.flags|=128,m=null):(uu(),ce=S.fallback,P=m.mode,S=Hy({mode:"visible",children:S.children},P),ce=zd(ce,P,_,null),ce.flags|=2,S.return=m,ce.return=m,S.sibling=ce,m.child=S,qd(m,h.child,null,_),S=m.child,S.memoizedState=D2(_),S.childLanes=O2(h,X,_),m.memoizedState=R2,m=Vg(null,S));else if(cu(m),xT(ce)){if(X=ce.nextSibling&&ce.nextSibling.dataset,X)var Ze=X.dgst;X=Ze,S=Error(r(419)),S.stack="",S.digest=X,jg({value:S,source:null,stack:null}),m=I2(h,m,_)}else if(Xr||Df(h,m,_,!1),X=(_&h.childLanes)!==0,Xr||X){if(X=tr,X!==null&&(S=Mi(X,_),S!==0&&S!==Ce.retryLane))throw Ce.retryLane=S,Ud(h,S),Ha(X,h,S),P2;gT(ce)||Zy(),m=I2(h,m,_)}else gT(ce)?(m.flags|=192,m.child=h.child,m=null):(h=Ce.treeContext,cr=Fs(ce.nextSibling),Hi=m,Sn=!0,ru=null,Ls=!1,h!==null&&g4(m,h),m=L2(m,S.children),m.flags|=4096);return m}return P?(uu(),ce=S.fallback,P=m.mode,Ce=h.child,Ze=Ce.sibling,S=Ol(Ce,{mode:"hidden",children:S.children}),S.subtreeFlags=Ce.subtreeFlags&65011712,Ze!==null?ce=Ol(Ze,ce):(ce=zd(ce,P,_,null),ce.flags|=2),ce.return=m,S.return=m,S.sibling=ce,m.child=S,Vg(null,S),S=m.child,ce=h.child.memoizedState,ce===null?ce=D2(_):(P=ce.cachePool,P!==null?(Ce=qr._currentValue,P=P.parent!==Ce?{parent:Ce,pool:Ce}:P):P=w4(),ce={baseLanes:ce.baseLanes|_,cachePool:P}),S.memoizedState=ce,S.childLanes=O2(h,X,_),m.memoizedState=R2,Vg(h.child,S)):(cu(m),_=h.child,h=_.sibling,_=Ol(_,{mode:"visible",children:S.children}),_.return=m,_.sibling=null,h!==null&&(X=m.deletions,X===null?(m.deletions=[h],m.flags|=16):X.push(h)),m.child=_,m.memoizedState=null,_)}function L2(h,m){return m=Hy({mode:"visible",children:m},h.mode),m.return=h,h.child=m}function Hy(h,m){return h=cs(22,h,null,m),h.lanes=0,h}function I2(h,m,_){return qd(m,h.child,null,_),h=L2(m,m.pendingProps.children),h.flags|=2,m.memoizedState=null,h}function OI(h,m,_){h.lanes|=m;var S=h.alternate;S!==null&&(S.lanes|=m),QE(h.return,m,_)}function F2(h,m,_,S,P,D){var X=h.memoizedState;X===null?h.memoizedState={isBackwards:m,rendering:null,renderingStartTime:0,last:S,tail:_,tailMode:P,treeForkCount:D}:(X.isBackwards=m,X.rendering=null,X.renderingStartTime=0,X.last=S,X.tail=_,X.tailMode=P,X.treeForkCount=D)}function LI(h,m,_){var S=m.pendingProps,P=S.revealOrder,D=S.tail;S=S.children;var X=Ur.current,ce=(X&2)!==0;if(ce?(X=X&1|2,m.flags|=128):X&=1,J(Ur,X),Vi(h,m,S,_),S=Sn?Pg:0,!ce&&h!==null&&(h.flags&128)!==0)e:for(h=m.child;h!==null;){if(h.tag===13)h.memoizedState!==null&&OI(h,_,m);else if(h.tag===19)OI(h,_,m);else if(h.child!==null){h.child.return=h,h=h.child;continue}if(h===m)break e;for(;h.sibling===null;){if(h.return===null||h.return===m)break e;h=h.return}h.sibling.return=h.return,h=h.sibling}switch(P){case"forwards":for(_=m.child,P=null;_!==null;)h=_.alternate,h!==null&&ky(h)===null&&(P=_),_=_.sibling;_=P,_===null?(P=m.child,m.child=null):(P=_.sibling,_.sibling=null),F2(m,!1,P,_,D,S);break;case"backwards":case"unstable_legacy-backwards":for(_=null,P=m.child,m.child=null;P!==null;){if(h=P.alternate,h!==null&&ky(h)===null){m.child=P;break}h=P.sibling,P.sibling=_,_=P,P=h}F2(m,!0,_,null,D,S);break;case"together":F2(m,!1,null,null,void 0,S);break;default:m.memoizedState=null}return m.child}function Bl(h,m,_){if(h!==null&&(m.dependencies=h.dependencies),fu|=m.lanes,(_&m.childLanes)===0)if(h!==null){if(Df(h,m,_,!1),(_&m.childLanes)===0)return null}else return null;if(h!==null&&m.child!==h.child)throw Error(r(153));if(m.child!==null){for(h=m.child,_=Ol(h,h.pendingProps),m.child=_,_.return=m;h.sibling!==null;)h=h.sibling,_=_.sibling=Ol(h,h.pendingProps),_.return=m;_.sibling=null}return m.child}function U2(h,m){return(h.lanes&m)!==0?!0:(h=h.dependencies,!!(h!==null&&wy(h)))}function Lne(h,m,_){switch(m.tag){case 3:Ae(m,m.stateNode.containerInfo),au(m,qr,h.memoizedState.cache),Bd();break;case 27:case 5:_e(m);break;case 4:Ae(m,m.stateNode.containerInfo);break;case 10:au(m,m.type,m.memoizedProps.value);break;case 31:if(m.memoizedState!==null)return m.flags|=128,u2(m),null;break;case 13:var S=m.memoizedState;if(S!==null)return S.dehydrated!==null?(cu(m),m.flags|=128,null):(_&m.child.childLanes)!==0?DI(h,m,_):(cu(m),h=Bl(h,m,_),h!==null?h.sibling:null);cu(m);break;case 19:var P=(h.flags&128)!==0;if(S=(_&m.childLanes)!==0,S||(Df(h,m,_,!1),S=(_&m.childLanes)!==0),P){if(S)return LI(h,m,_);m.flags|=128}if(P=m.memoizedState,P!==null&&(P.rendering=null,P.tail=null,P.lastEffect=null),J(Ur,Ur.current),S)break;return null;case 22:return m.lanes=0,NI(h,m,_,m.pendingProps);case 24:au(m,qr,h.memoizedState.cache)}return Bl(h,m,_)}function II(h,m,_){if(h!==null)if(h.memoizedProps!==m.pendingProps)Xr=!0;else{if(!U2(h,_)&&(m.flags&128)===0)return Xr=!1,Lne(h,m,_);Xr=(h.flags&131072)!==0}else Xr=!1,Sn&&(m.flags&1048576)!==0&&p4(m,Pg,m.index);switch(m.lanes=0,m.tag){case 16:e:{var S=m.pendingProps;if(h=Gd(m.elementType),m.type=h,typeof h=="function")VE(h)?(S=Xd(h,S),m.tag=1,m=jI(null,m,h,S,_)):(m.tag=0,m=j2(null,m,h,S,_));else{if(h!=null){var P=h.$$typeof;if(P===A){m.tag=11,m=EI(null,m,h,S,_);break e}else if(P===N){m.tag=14,m=TI(null,m,h,S,_);break e}}throw m=j(h)||h,Error(r(306,m,""))}}return m;case 0:return j2(h,m,m.type,m.pendingProps,_);case 1:return S=m.type,P=Xd(S,m.pendingProps),jI(h,m,S,P,_);case 3:e:{if(Ae(m,m.stateNode.containerInfo),h===null)throw Error(r(387));S=m.pendingProps;var D=m.memoizedState;P=D.element,a2(h,m),Ug(m,S,null,_);var X=m.memoizedState;if(S=X.cache,au(m,qr,S),S!==D.cache&&JE(m,[qr],_,!0),Fg(),S=X.element,D.isDehydrated)if(D={element:S,isDehydrated:!1,cache:X.cache},m.updateQueue.baseState=D,m.memoizedState=D,m.flags&256){m=RI(h,m,S,_);break e}else if(S!==P){P=Rs(Error(r(424)),m),jg(P),m=RI(h,m,S,_);break e}else{switch(h=m.stateNode.containerInfo,h.nodeType){case 9:h=h.body;break;default:h=h.nodeName==="HTML"?h.ownerDocument.body:h}for(cr=Fs(h.firstChild),Hi=m,Sn=!0,ru=null,Ls=!0,_=N4(m,null,S,_),m.child=_;_;)_.flags=_.flags&-3|4096,_=_.sibling}else{if(Bd(),S===P){m=Bl(h,m,_);break e}Vi(h,m,S,_)}m=m.child}return m;case 26:return By(h,m),h===null?(_=K5(m.type,null,m.pendingProps,null))?m.memoizedState=_:Sn||(_=m.type,h=m.pendingProps,S=iv(ue.current).createElement(_),S[vr]=m,S[Ir]=h,Gi(S,_,h),Ie(S),m.stateNode=S):m.memoizedState=K5(m.type,h.memoizedProps,m.pendingProps,h.memoizedState),null;case 27:return _e(m),h===null&&Sn&&(S=m.stateNode=G5(m.type,m.pendingProps,ue.current),Hi=m,Ls=!0,P=cr,bu(m.type)?(bT=P,cr=Fs(S.firstChild)):cr=P),Vi(h,m,m.pendingProps.children,_),By(h,m),h===null&&(m.flags|=4194304),m.child;case 5:return h===null&&Sn&&((P=S=cr)&&(S=hre(S,m.type,m.pendingProps,Ls),S!==null?(m.stateNode=S,Hi=m,cr=Fs(S.firstChild),Ls=!1,P=!0):P=!1),P||iu(m)),_e(m),P=m.type,D=m.pendingProps,X=h!==null?h.memoizedProps:null,S=D.children,fT(P,D)?S=null:X!==null&&fT(P,X)&&(m.flags|=32),m.memoizedState!==null&&(P=h2(h,m,Ane,null,null,_),ax._currentValue=P),By(h,m),Vi(h,m,S,_),m.child;case 6:return h===null&&Sn&&((h=_=cr)&&(_=fre(_,m.pendingProps,Ls),_!==null?(m.stateNode=_,Hi=m,cr=null,h=!0):h=!1),h||iu(m)),null;case 13:return DI(h,m,_);case 4:return Ae(m,m.stateNode.containerInfo),S=m.pendingProps,h===null?m.child=qd(m,null,S,_):Vi(h,m,S,_),m.child;case 11:return EI(h,m,m.type,m.pendingProps,_);case 7:return Vi(h,m,m.pendingProps,_),m.child;case 8:return Vi(h,m,m.pendingProps.children,_),m.child;case 12:return Vi(h,m,m.pendingProps.children,_),m.child;case 10:return S=m.pendingProps,au(m,m.type,S.value),Vi(h,m,S.children,_),m.child;case 9:return P=m.type._context,S=m.pendingProps.children,$d(m),P=$i(P),S=S(P),m.flags|=1,Vi(h,m,S,_),m.child;case 14:return TI(h,m,m.type,m.pendingProps,_);case 15:return AI(h,m,m.type,m.pendingProps,_);case 19:return LI(h,m,_);case 31:return One(h,m,_);case 22:return NI(h,m,_,m.pendingProps);case 24:return $d(m),S=$i(qr),h===null?(P=n2(),P===null&&(P=tr,D=e2(),P.pooledCache=D,D.refCount++,D!==null&&(P.pooledCacheLanes|=_),P=D),m.memoizedState={parent:S,cache:P},i2(m),au(m,qr,P)):((h.lanes&_)!==0&&(a2(h,m),Ug(m,null,null,_),Fg()),P=h.memoizedState,D=m.memoizedState,P.parent!==S?(P={parent:S,cache:S},m.memoizedState=P,m.lanes===0&&(m.memoizedState=m.updateQueue.baseState=P),au(m,qr,S)):(S=D.cache,au(m,qr,S),S!==P.cache&&JE(m,[qr],_,!0))),Vi(h,m,m.pendingProps.children,_),m.child;case 29:throw m.pendingProps}throw Error(r(156,m.tag))}function Hl(h){h.flags|=4}function z2(h,m,_,S,P){if((m=(h.mode&32)!==0)&&(m=!1),m){if(h.flags|=16777216,(P&335544128)===P)if(h.stateNode.complete)h.flags|=8192;else if(u5())h.flags|=8192;else throw Wd=Ty,r2}else h.flags&=-16777217}function FI(h,m){if(m.type!=="stylesheet"||(m.state.loading&4)!==0)h.flags&=-16777217;else if(h.flags|=16777216,!J5(m))if(u5())h.flags|=8192;else throw Wd=Ty,r2}function $y(h,m){m!==null&&(h.flags|=4),h.flags&16384&&(m=h.tag!==22?yt():536870912,h.lanes|=m,Wf|=m)}function Gg(h,m){if(!Sn)switch(h.tailMode){case"hidden":m=h.tail;for(var _=null;m!==null;)m.alternate!==null&&(_=m),m=m.sibling;_===null?h.tail=null:_.sibling=null;break;case"collapsed":_=h.tail;for(var S=null;_!==null;)_.alternate!==null&&(S=_),_=_.sibling;S===null?m||h.tail===null?h.tail=null:h.tail.sibling=null:S.sibling=null}}function ur(h){var m=h.alternate!==null&&h.alternate.child===h.child,_=0,S=0;if(m)for(var P=h.child;P!==null;)_|=P.lanes|P.childLanes,S|=P.subtreeFlags&65011712,S|=P.flags&65011712,P.return=h,P=P.sibling;else for(P=h.child;P!==null;)_|=P.lanes|P.childLanes,S|=P.subtreeFlags,S|=P.flags,P.return=h,P=P.sibling;return h.subtreeFlags|=S,h.childLanes=_,m}function Ine(h,m,_){var S=m.pendingProps;switch(KE(m),m.tag){case 16:case 15:case 0:case 11:case 7:case 8:case 12:case 9:case 14:return ur(m),null;case 1:return ur(m),null;case 3:return _=m.stateNode,S=null,h!==null&&(S=h.memoizedState.cache),m.memoizedState.cache!==S&&(m.flags|=2048),Fl(qr),ie(),_.pendingContext&&(_.context=_.pendingContext,_.pendingContext=null),(h===null||h.child===null)&&(Rf(m)?Hl(m):h===null||h.memoizedState.isDehydrated&&(m.flags&256)===0||(m.flags|=1024,YE())),ur(m),null;case 26:var P=m.type,D=m.memoizedState;return h===null?(Hl(m),D!==null?(ur(m),FI(m,D)):(ur(m),z2(m,P,null,S,_))):D?D!==h.memoizedState?(Hl(m),ur(m),FI(m,D)):(ur(m),m.flags&=-16777217):(h=h.memoizedProps,h!==S&&Hl(m),ur(m),z2(m,P,h,S,_)),null;case 27:if(Oe(m),_=ue.current,P=m.type,h!==null&&m.stateNode!=null)h.memoizedProps!==S&&Hl(m);else{if(!S){if(m.stateNode===null)throw Error(r(166));return ur(m),null}h=ee.current,Rf(m)?x4(m):(h=G5(P,S,_),m.stateNode=h,Hl(m))}return ur(m),null;case 5:if(Oe(m),P=m.type,h!==null&&m.stateNode!=null)h.memoizedProps!==S&&Hl(m);else{if(!S){if(m.stateNode===null)throw Error(r(166));return ur(m),null}if(D=ee.current,Rf(m))x4(m);else{var X=iv(ue.current);switch(D){case 1:D=X.createElementNS("http://www.w3.org/2000/svg",P);break;case 2:D=X.createElementNS("http://www.w3.org/1998/Math/MathML",P);break;default:switch(P){case"svg":D=X.createElementNS("http://www.w3.org/2000/svg",P);break;case"math":D=X.createElementNS("http://www.w3.org/1998/Math/MathML",P);break;case"script":D=X.createElement("div"),D.innerHTML=" - + +