diff --git a/CHANGELOG.md b/CHANGELOG.md index 338c0697a..9e98bb3bc 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -8,8 +8,10 @@ All notable changes to Bambuddy will be documented in this file. - **MQTT Smart Plug Support** - Add smart plugs that subscribe to MQTT topics for energy monitoring (Issue #173): - New "MQTT" plug type alongside Tasmota and Home Assistant - Subscribe to any MQTT topic (Zigbee2MQTT, Shelly, Tasmota discovery, etc.) - - Configurable JSON paths for power, energy, and state extraction (e.g., `power_l1`, `data.power`) - - Optional multiplier for unit conversion (mW to W, etc.) + - **Separate topics per data type**: Configure different MQTT topics for power, energy, and state + - Configurable JSON paths for data extraction (e.g., `power_l1`, `data.power`) + - **Separate multipliers**: Individual multiplier for power and energy (e.g., mW→W, Wh→kWh) + - **Custom ON value**: Configure what value means "ON" for state (e.g., "ON", "true", "1") - Monitor-only: displays power/energy data without control capabilities - Reuses existing MQTT broker settings from Settings → Network - Energy data included in statistics and per-print tracking diff --git a/README.md b/README.md index 999972dbb..6d42769e1 100644 --- a/README.md +++ b/README.md @@ -78,7 +78,8 @@ - Per-printer AMS mapping (individual slot configuration for print farms) - Scheduled prints (date/time) - Queue Only mode (stage without auto-start) -- Smart plug integration (Tasmota, Home Assistant) +- Smart plug integration (Tasmota, Home Assistant, MQTT) +- MQTT smart plugs: Subscribe to Zigbee2MQTT, Shelly, or any MQTT topic for energy monitoring - Energy consumption tracking (per-print kWh and cost) - HA energy sensor support (for plugs with separate power/energy sensors) - Auto power-on before print diff --git a/backend/app/api/routes/settings.py b/backend/app/api/routes/settings.py index 798b343b8..67f2fd675 100644 --- a/backend/app/api/routes/settings.py +++ b/backend/app/api/routes/settings.py @@ -360,12 +360,21 @@ async def export_backup( "ha_power_entity": plug.ha_power_entity, "ha_energy_today_entity": plug.ha_energy_today_entity, "ha_energy_total_entity": plug.ha_energy_total_entity, - # MQTT plug fields + # MQTT plug fields (legacy) "mqtt_topic": plug.mqtt_topic, - "mqtt_power_path": plug.mqtt_power_path, - "mqtt_energy_path": plug.mqtt_energy_path, - "mqtt_state_path": plug.mqtt_state_path, "mqtt_multiplier": plug.mqtt_multiplier, + # MQTT power fields + "mqtt_power_topic": plug.mqtt_power_topic, + "mqtt_power_path": plug.mqtt_power_path, + "mqtt_power_multiplier": plug.mqtt_power_multiplier, + # MQTT energy fields + "mqtt_energy_topic": plug.mqtt_energy_topic, + "mqtt_energy_path": plug.mqtt_energy_path, + "mqtt_energy_multiplier": plug.mqtt_energy_multiplier, + # MQTT state fields + "mqtt_state_topic": plug.mqtt_state_topic, + "mqtt_state_path": plug.mqtt_state_path, + "mqtt_state_on_value": plug.mqtt_state_on_value, "printer_serial": printer_id_to_serial.get(plug.printer_id) if plug.printer_id else None, "enabled": plug.enabled, "auto_on": plug.auto_on, @@ -1270,10 +1279,16 @@ async def import_backup( result = await db.execute(select(SmartPlug).where(SmartPlug.ha_entity_id == plug_data["ha_entity_id"])) existing = result.scalar_one_or_none() plug_identifier = plug_data["ha_entity_id"] - elif plug_type == "mqtt" and plug_data.get("mqtt_topic"): - result = await db.execute(select(SmartPlug).where(SmartPlug.mqtt_topic == plug_data["mqtt_topic"])) + elif plug_type == "mqtt" and (plug_data.get("mqtt_power_topic") or plug_data.get("mqtt_topic")): + # Check by mqtt_power_topic first (new format), fall back to mqtt_topic (legacy) + power_topic = plug_data.get("mqtt_power_topic") or plug_data.get("mqtt_topic") + result = await db.execute( + select(SmartPlug).where( + (SmartPlug.mqtt_power_topic == power_topic) | (SmartPlug.mqtt_topic == power_topic) + ) + ) existing = result.scalar_one_or_none() - plug_identifier = plug_data["mqtt_topic"] + plug_identifier = power_topic elif plug_data.get("ip_address"): result = await db.execute(select(SmartPlug).where(SmartPlug.ip_address == plug_data["ip_address"])) existing = result.scalar_one_or_none() @@ -1290,12 +1305,21 @@ async def import_backup( existing.ha_power_entity = plug_data.get("ha_power_entity") existing.ha_energy_today_entity = plug_data.get("ha_energy_today_entity") existing.ha_energy_total_entity = plug_data.get("ha_energy_total_entity") - # MQTT fields + # MQTT fields (legacy) existing.mqtt_topic = plug_data.get("mqtt_topic") - existing.mqtt_power_path = plug_data.get("mqtt_power_path") - existing.mqtt_energy_path = plug_data.get("mqtt_energy_path") - existing.mqtt_state_path = plug_data.get("mqtt_state_path") existing.mqtt_multiplier = plug_data.get("mqtt_multiplier", 1.0) + # MQTT power fields + existing.mqtt_power_topic = plug_data.get("mqtt_power_topic") + existing.mqtt_power_path = plug_data.get("mqtt_power_path") + existing.mqtt_power_multiplier = plug_data.get("mqtt_power_multiplier", 1.0) + # MQTT energy fields + existing.mqtt_energy_topic = plug_data.get("mqtt_energy_topic") + existing.mqtt_energy_path = plug_data.get("mqtt_energy_path") + existing.mqtt_energy_multiplier = plug_data.get("mqtt_energy_multiplier", 1.0) + # MQTT state fields + existing.mqtt_state_topic = plug_data.get("mqtt_state_topic") + existing.mqtt_state_path = plug_data.get("mqtt_state_path") + existing.mqtt_state_on_value = plug_data.get("mqtt_state_on_value") existing.printer_id = printer_id existing.enabled = plug_data.get("enabled", True) existing.auto_on = plug_data.get("auto_on", True) @@ -1325,12 +1349,21 @@ async def import_backup( ha_power_entity=plug_data.get("ha_power_entity"), ha_energy_today_entity=plug_data.get("ha_energy_today_entity"), ha_energy_total_entity=plug_data.get("ha_energy_total_entity"), - # MQTT fields + # MQTT fields (legacy) mqtt_topic=plug_data.get("mqtt_topic"), - mqtt_power_path=plug_data.get("mqtt_power_path"), - mqtt_energy_path=plug_data.get("mqtt_energy_path"), - mqtt_state_path=plug_data.get("mqtt_state_path"), mqtt_multiplier=plug_data.get("mqtt_multiplier", 1.0), + # MQTT power fields + mqtt_power_topic=plug_data.get("mqtt_power_topic"), + mqtt_power_path=plug_data.get("mqtt_power_path"), + mqtt_power_multiplier=plug_data.get("mqtt_power_multiplier", 1.0), + # MQTT energy fields + mqtt_energy_topic=plug_data.get("mqtt_energy_topic"), + mqtt_energy_path=plug_data.get("mqtt_energy_path"), + mqtt_energy_multiplier=plug_data.get("mqtt_energy_multiplier", 1.0), + # MQTT state fields + mqtt_state_topic=plug_data.get("mqtt_state_topic"), + mqtt_state_path=plug_data.get("mqtt_state_path"), + mqtt_state_on_value=plug_data.get("mqtt_state_on_value"), printer_id=printer_id, enabled=plug_data.get("enabled", True), auto_on=plug_data.get("auto_on", True), diff --git a/backend/app/api/routes/smart_plugs.py b/backend/app/api/routes/smart_plugs.py index b782e126c..42c915347 100644 --- a/backend/app/api/routes/smart_plugs.py +++ b/backend/app/api/routes/smart_plugs.py @@ -96,17 +96,44 @@ async def create_smart_plug( await db.commit() await db.refresh(plug) - # Subscribe MQTT plugs to their topic - if plug.plug_type == "mqtt" and plug.mqtt_topic: - mqtt_relay.smart_plug_service.subscribe( - plug_id=plug.id, - topic=plug.mqtt_topic, - power_path=plug.mqtt_power_path, - energy_path=plug.mqtt_energy_path, - state_path=plug.mqtt_state_path, - multiplier=plug.mqtt_multiplier or 1.0, - ) - logger.info(f"Created MQTT plug '{plug.name}' subscribed to {plug.mqtt_topic}") + # Subscribe MQTT plugs to their topics + if plug.plug_type == "mqtt": + # Determine effective topics (new fields take priority, fall back to legacy) + power_topic = plug.mqtt_power_topic or plug.mqtt_topic + energy_topic = plug.mqtt_energy_topic or plug.mqtt_topic + state_topic = plug.mqtt_state_topic or plug.mqtt_topic + + # Only subscribe if at least one data source is configured + if ( + (power_topic and plug.mqtt_power_path) + or (energy_topic and plug.mqtt_energy_path) + or (state_topic and plug.mqtt_state_path) + ): + mqtt_relay.smart_plug_service.subscribe( + plug_id=plug.id, + # Power source + power_topic=power_topic if plug.mqtt_power_path else None, + power_path=plug.mqtt_power_path, + power_multiplier=plug.mqtt_power_multiplier or plug.mqtt_multiplier or 1.0, + # Energy source + energy_topic=energy_topic if plug.mqtt_energy_path else None, + energy_path=plug.mqtt_energy_path, + energy_multiplier=plug.mqtt_energy_multiplier or plug.mqtt_multiplier or 1.0, + # State source + state_topic=state_topic if plug.mqtt_state_path else None, + state_path=plug.mqtt_state_path, + state_on_value=plug.mqtt_state_on_value, + ) + topics = [ + t + for t in [ + power_topic if plug.mqtt_power_path else None, + energy_topic if plug.mqtt_energy_path else None, + state_topic if plug.mqtt_state_path else None, + ] + if t + ] + logger.info(f"Created MQTT plug '{plug.name}' subscribed to {', '.join(set(topics))}") elif plug.plug_type == "homeassistant": logger.info(f"Created Home Assistant plug '{plug.name}' ({plug.ha_entity_id})") else: @@ -338,9 +365,19 @@ async def update_smart_plug( if result.scalar_one_or_none(): raise HTTPException(400, "This printer already has a smart plug assigned") - # Check if MQTT topic is changing - need to resubscribe - old_topic = plug.mqtt_topic + # Track old MQTT settings for comparison old_plug_type = plug.plug_type + old_mqtt_config = { + "power_topic": plug.mqtt_power_topic or plug.mqtt_topic, + "power_path": plug.mqtt_power_path, + "power_multiplier": plug.mqtt_power_multiplier, + "energy_topic": plug.mqtt_energy_topic or plug.mqtt_topic, + "energy_path": plug.mqtt_energy_path, + "energy_multiplier": plug.mqtt_energy_multiplier, + "state_topic": plug.mqtt_state_topic or plug.mqtt_topic, + "state_path": plug.mqtt_state_path, + "state_on_value": plug.mqtt_state_on_value, + } for field, value in update_data.items(): setattr(plug, field, value) @@ -353,20 +390,50 @@ async def update_smart_plug( # Changed away from MQTT - unsubscribe mqtt_relay.smart_plug_service.unsubscribe(plug.id) elif plug.plug_type == "mqtt": - # Is now MQTT - check if topic changed or newly MQTT - if old_plug_type != "mqtt" or old_topic != plug.mqtt_topic: - # Unsubscribe from old topic first + # Check if any MQTT config changed + new_mqtt_config = { + "power_topic": plug.mqtt_power_topic or plug.mqtt_topic, + "power_path": plug.mqtt_power_path, + "power_multiplier": plug.mqtt_power_multiplier, + "energy_topic": plug.mqtt_energy_topic or plug.mqtt_topic, + "energy_path": plug.mqtt_energy_path, + "energy_multiplier": plug.mqtt_energy_multiplier, + "state_topic": plug.mqtt_state_topic or plug.mqtt_topic, + "state_path": plug.mqtt_state_path, + "state_on_value": plug.mqtt_state_on_value, + } + + mqtt_changed = old_plug_type != "mqtt" or old_mqtt_config != new_mqtt_config + + if mqtt_changed: + # Unsubscribe from old topics first if old_plug_type == "mqtt": mqtt_relay.smart_plug_service.unsubscribe(plug.id) - # Subscribe to new topic - if plug.mqtt_topic: + + # Subscribe to new topics + power_topic = plug.mqtt_power_topic or plug.mqtt_topic + energy_topic = plug.mqtt_energy_topic or plug.mqtt_topic + state_topic = plug.mqtt_state_topic or plug.mqtt_topic + + if ( + (power_topic and plug.mqtt_power_path) + or (energy_topic and plug.mqtt_energy_path) + or (state_topic and plug.mqtt_state_path) + ): mqtt_relay.smart_plug_service.subscribe( plug_id=plug.id, - topic=plug.mqtt_topic, + # Power source + power_topic=power_topic if plug.mqtt_power_path else None, power_path=plug.mqtt_power_path, + power_multiplier=plug.mqtt_power_multiplier or plug.mqtt_multiplier or 1.0, + # Energy source + energy_topic=energy_topic if plug.mqtt_energy_path else None, energy_path=plug.mqtt_energy_path, + energy_multiplier=plug.mqtt_energy_multiplier or plug.mqtt_multiplier or 1.0, + # State source + state_topic=state_topic if plug.mqtt_state_path else None, state_path=plug.mqtt_state_path, - multiplier=plug.mqtt_multiplier or 1.0, + state_on_value=plug.mqtt_state_on_value, ) logger.info(f"Updated smart plug '{plug.name}'") diff --git a/backend/app/core/database.py b/backend/app/core/database.py index 683347868..9b3c6c299 100644 --- a/backend/app/core/database.py +++ b/backend/app/core/database.py @@ -760,7 +760,7 @@ async def run_migrations(conn): except Exception: pass - # Migration: Add MQTT smart plug fields + # Migration: Add MQTT smart plug fields (legacy) try: await conn.execute(text("ALTER TABLE smart_plugs ADD COLUMN mqtt_topic VARCHAR(200)")) except Exception: @@ -782,6 +782,45 @@ async def run_migrations(conn): except Exception: pass + # Migration: Add enhanced MQTT smart plug fields (separate topics and multipliers) + try: + await conn.execute(text("ALTER TABLE smart_plugs ADD COLUMN mqtt_power_topic VARCHAR(200)")) + except Exception: + pass + try: + await conn.execute(text("ALTER TABLE smart_plugs ADD COLUMN mqtt_power_multiplier REAL DEFAULT 1.0")) + except Exception: + pass + try: + await conn.execute(text("ALTER TABLE smart_plugs ADD COLUMN mqtt_energy_topic VARCHAR(200)")) + except Exception: + pass + try: + await conn.execute(text("ALTER TABLE smart_plugs ADD COLUMN mqtt_energy_multiplier REAL DEFAULT 1.0")) + except Exception: + pass + try: + await conn.execute(text("ALTER TABLE smart_plugs ADD COLUMN mqtt_state_topic VARCHAR(200)")) + except Exception: + pass + try: + await conn.execute(text("ALTER TABLE smart_plugs ADD COLUMN mqtt_state_on_value VARCHAR(50)")) + except Exception: + pass + + # Migration: Copy existing mqtt_topic to mqtt_power_topic for backward compatibility + try: + await conn.execute( + text(""" + UPDATE smart_plugs + SET mqtt_power_topic = mqtt_topic, + mqtt_power_multiplier = mqtt_multiplier + WHERE mqtt_topic IS NOT NULL AND mqtt_power_topic IS NULL + """) + ) + 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 99d63ed87..3bcc3b1ee 100644 --- a/backend/app/models/smart_plug.py +++ b/backend/app/models/smart_plug.py @@ -25,13 +25,30 @@ class SmartPlug(Base): ha_energy_total_entity: Mapped[str | None] = mapped_column(String(100), nullable=True) # sensor.xxx_total # MQTT plug fields (required when plug_type="mqtt") + # Legacy field - kept for backward compatibility, now use mqtt_power_topic mqtt_topic: Mapped[str | None] = mapped_column( String(200), nullable=True - ) # e.g., "zigbee2mqtt/shelly-working-room" + ) # e.g., "zigbee2mqtt/shelly-working-room" (deprecated, use mqtt_power_topic) + + # Power monitoring + mqtt_power_topic: Mapped[str | None] = mapped_column(String(200), nullable=True) # Topic for power data mqtt_power_path: Mapped[str | None] = mapped_column(String(100), nullable=True) # e.g., "power_l1" or "data.power" + mqtt_power_multiplier: Mapped[float] = mapped_column(Float, default=1.0) # Unit conversion for power + + # Energy monitoring + mqtt_energy_topic: Mapped[str | None] = mapped_column(String(200), nullable=True) # Topic for energy data mqtt_energy_path: Mapped[str | None] = mapped_column(String(100), nullable=True) # e.g., "energy_l1" + mqtt_energy_multiplier: Mapped[float] = mapped_column(Float, default=1.0) # Unit conversion for energy + + # State monitoring + mqtt_state_topic: Mapped[str | None] = mapped_column(String(200), nullable=True) # Topic for state data mqtt_state_path: Mapped[str | None] = mapped_column(String(100), nullable=True) # e.g., "state_l1" for ON/OFF - mqtt_multiplier: Mapped[float] = mapped_column(Float, default=1.0) # Unit conversion (e.g., 0.001 for mW→W) + mqtt_state_on_value: Mapped[str | None] = mapped_column( + String(50), nullable=True + ) # What value means "ON" (e.g., "ON", "true", "1") + + # Legacy multiplier - kept for backward compatibility + mqtt_multiplier: Mapped[float] = mapped_column(Float, default=1.0) # Deprecated, use mqtt_power_multiplier # Link to printer (1:1) printer_id: Mapped[int | None] = mapped_column( diff --git a/backend/app/schemas/smart_plug.py b/backend/app/schemas/smart_plug.py index ed4d2be5c..edb8a1210 100644 --- a/backend/app/schemas/smart_plug.py +++ b/backend/app/schemas/smart_plug.py @@ -21,11 +21,28 @@ class SmartPlugBase(BaseModel): ha_energy_total_entity: str | None = Field(default=None, pattern=r"^sensor\.[a-z0-9_]+$") # MQTT fields (required when plug_type="mqtt") - mqtt_topic: str | None = Field(default=None, max_length=200) + # Legacy field - kept for backward compatibility + mqtt_topic: str | None = Field(default=None, max_length=200) # Deprecated, use mqtt_power_topic + + # Power monitoring + mqtt_power_topic: str | None = Field(default=None, max_length=200) # Topic for power data mqtt_power_path: str | None = Field(default=None, max_length=100) # e.g., "power_l1" or "data.power" + mqtt_power_multiplier: float = Field(default=1.0, ge=0.0001, le=10000) # Unit conversion for power + + # Energy monitoring + mqtt_energy_topic: str | None = Field(default=None, max_length=200) # Topic for energy data mqtt_energy_path: str | None = Field(default=None, max_length=100) # e.g., "energy_l1" + mqtt_energy_multiplier: float = Field(default=1.0, ge=0.0001, le=10000) # Unit conversion for energy + + # State monitoring + mqtt_state_topic: str | None = Field(default=None, max_length=200) # Topic for state data mqtt_state_path: str | None = Field(default=None, max_length=100) # e.g., "state_l1" for ON/OFF - mqtt_multiplier: float = Field(default=1.0, ge=0.0001, le=10000) # Unit conversion (e.g., 0.001 for mW→W) + mqtt_state_on_value: str | None = Field( + default=None, max_length=50 + ) # What value means "ON" (e.g., "ON", "true", "1") + + # Legacy multiplier - kept for backward compatibility + mqtt_multiplier: float = Field(default=1.0, ge=0.0001, le=10000) # Deprecated, use mqtt_power_multiplier printer_id: int | None = None enabled: bool = True @@ -52,10 +69,18 @@ class SmartPlugBase(BaseModel): if self.plug_type == "homeassistant" and not self.ha_entity_id: raise ValueError("ha_entity_id is required for Home Assistant plugs") if self.plug_type == "mqtt": - if not self.mqtt_topic: - raise ValueError("mqtt_topic is required for MQTT plugs") - if not self.mqtt_power_path and not self.mqtt_state_path: - raise ValueError("At least mqtt_power_path or mqtt_state_path is required for MQTT plugs") + # Determine the effective power topic (new field takes priority, fall back to legacy) + power_topic = self.mqtt_power_topic or self.mqtt_topic + has_power = power_topic and self.mqtt_power_path + has_energy = self.mqtt_energy_topic and self.mqtt_energy_path + has_state = self.mqtt_state_topic and self.mqtt_state_path + + # At least one data source must be fully configured + if not has_power and not has_energy and not has_state: + raise ValueError( + "At least one MQTT data source must be configured: " + "power (topic + path), energy (topic + path), or state (topic + path)" + ) return self @@ -72,12 +97,21 @@ class SmartPlugUpdate(BaseModel): ha_power_entity: str | None = None ha_energy_today_entity: str | None = None ha_energy_total_entity: str | None = None - # MQTT fields + # MQTT fields (legacy) mqtt_topic: str | None = None - mqtt_power_path: str | None = None - mqtt_energy_path: str | None = None - mqtt_state_path: str | None = None mqtt_multiplier: float | None = Field(default=None, ge=0.0001, le=10000) + # MQTT power fields + mqtt_power_topic: str | None = None + mqtt_power_path: str | None = None + mqtt_power_multiplier: float | None = Field(default=None, ge=0.0001, le=10000) + # MQTT energy fields + mqtt_energy_topic: str | None = None + mqtt_energy_path: str | None = None + mqtt_energy_multiplier: float | None = Field(default=None, ge=0.0001, le=10000) + # MQTT state fields + mqtt_state_topic: str | None = None + mqtt_state_path: str | None = None + mqtt_state_on_value: str | None = None printer_id: int | None = None enabled: bool | None = None auto_on: bool | None = None diff --git a/backend/app/services/mqtt_smart_plug.py b/backend/app/services/mqtt_smart_plug.py index 35e913153..dfc7fbba0 100644 --- a/backend/app/services/mqtt_smart_plug.py +++ b/backend/app/services/mqtt_smart_plug.py @@ -26,6 +26,16 @@ class SmartPlugMQTTData: last_seen: datetime = field(default_factory=datetime.utcnow) +@dataclass +class MQTTDataSourceConfig: + """Configuration for a single MQTT data source (power, energy, or state).""" + + topic: str + path: str + multiplier: float = 1.0 # For power/energy + on_value: str | None = None # For state (what value means "ON") + + class MQTTSmartPlugService: """Subscribes to MQTT topics for smart plug energy monitoring.""" @@ -36,10 +46,10 @@ class MQTTSmartPlugService: self.client: mqtt.Client | None = None self.connected = False self._lock = threading.Lock() - # topic -> list of plug_ids (multiple plugs can subscribe to same topic with different paths) - self.subscriptions: dict[str, list[int]] = {} - # plug_id -> (topic, power_path, energy_path, state_path, multiplier) - self.plug_configs: dict[int, tuple[str, str | None, str | None, str | None, float]] = {} + # topic -> list of (plug_id, data_type) where data_type is "power", "energy", or "state" + self.subscriptions: dict[str, list[tuple[int, str]]] = {} + # plug_id -> {data_type: MQTTDataSourceConfig} + self.plug_configs: dict[int, dict[str, MQTTDataSourceConfig]] = {} # plug_id -> latest data self.plug_data: dict[int, SmartPlugMQTTData] = {} self._configured = False @@ -205,78 +215,80 @@ class MQTTSmartPlugService: topic = msg.topic with self._lock: - plug_ids = self.subscriptions.get(topic, []) - if not plug_ids: + subscriptions = self.subscriptions.get(topic, []) + if not subscriptions: return - # Parse JSON payload + # Parse JSON payload (or treat as raw value) try: payload = json.loads(msg.payload.decode("utf-8")) - except (json.JSONDecodeError, UnicodeDecodeError) as e: - logger.debug(f"MQTT smart plug: failed to parse message on {topic}: {e}") - return + is_json = True + except (json.JSONDecodeError, UnicodeDecodeError): + # Not JSON - treat the whole payload as a raw value + payload = msg.payload.decode("utf-8").strip() + is_json = False - # Process for each subscribed plug - for plug_id in plug_ids: - config = self.plug_configs.get(plug_id) + # Process for each subscribed (plug_id, data_type) + for plug_id, data_type in subscriptions: + configs = self.plug_configs.get(plug_id, {}) + config = configs.get(data_type) if not config: continue - _, power_path, energy_path, state_path, multiplier = config - - # Extract values - power = None - energy = None - state = None - - if power_path: - raw_power = self._extract_json_path(payload, power_path) - if raw_power is not None: - try: - power = float(raw_power) * multiplier - except (ValueError, TypeError): - pass - - if energy_path: - raw_energy = self._extract_json_path(payload, energy_path) - if raw_energy is not None: - try: - energy = float(raw_energy) * multiplier - except (ValueError, TypeError): - pass - - if state_path: - raw_state = self._extract_json_path(payload, state_path) - if raw_state is not None: - # Normalize state to ON/OFF - state_str = str(raw_state).upper() - if state_str in ("ON", "1", "TRUE"): - state = "ON" - elif state_str in ("OFF", "0", "FALSE"): - state = "OFF" - else: - state = state_str - - # Update plug data - if plug_id in self.plug_data: - data = self.plug_data[plug_id] - if power is not None: - data.power = power - if energy is not None: - data.energy = energy - if state is not None: - data.state = state - data.last_seen = datetime.utcnow() + # Extract value using path (or use raw payload if no path or not JSON) + if is_json and config.path: + raw_value = self._extract_json_path(payload, config.path) + elif is_json and not config.path: + # JSON but no path - use the whole payload (shouldn't happen normally) + raw_value = payload else: - self.plug_data[plug_id] = SmartPlugMQTTData( - plug_id=plug_id, - power=power, - energy=energy, - state=state, - last_seen=datetime.utcnow(), - ) + # Raw value (non-JSON) + raw_value = payload - logger.debug(f"MQTT smart plug {plug_id}: power={power}, energy={energy}, state={state}") + if raw_value is None: + continue + + # Initialize plug data if needed + if plug_id not in self.plug_data: + self.plug_data[plug_id] = SmartPlugMQTTData(plug_id=plug_id) + + data = self.plug_data[plug_id] + data.last_seen = datetime.utcnow() + + # Process based on data type + if data_type == "power": + try: + data.power = float(raw_value) * config.multiplier + logger.debug(f"MQTT smart plug {plug_id}: power={data.power}") + except (ValueError, TypeError): + pass + + elif data_type == "energy": + try: + data.energy = float(raw_value) * config.multiplier + logger.debug(f"MQTT smart plug {plug_id}: energy={data.energy}") + except (ValueError, TypeError): + pass + + elif data_type == "state": + state_str = str(raw_value) + # Check against configured ON value if set + if config.on_value: + # Case-insensitive comparison + if state_str.lower() == config.on_value.lower(): + data.state = "ON" + else: + data.state = "OFF" + else: + # Default behavior: normalize common values + upper_state = state_str.upper() + if upper_state in ("ON", "1", "TRUE"): + data.state = "ON" + elif upper_state in ("OFF", "0", "FALSE"): + data.state = "OFF" + else: + data.state = state_str + logger.debug(f"MQTT smart plug {plug_id}: state={data.state}") def _extract_json_path(self, data: dict, path: str) -> Any: """Extract value using dot notation (e.g., 'power_l1' or 'data.power'). @@ -304,61 +316,131 @@ class MQTTSmartPlugService: with self._lock: for topic in self.subscriptions: - try: - self.client.subscribe(topic, qos=1) - logger.debug(f"MQTT smart plug: resubscribed to {topic}") - except Exception as e: - logger.error(f"MQTT smart plug: failed to resubscribe to {topic}: {e}") + if self.subscriptions[topic]: # Only if there are subscribers + try: + self.client.subscribe(topic, qos=1) + logger.debug(f"MQTT smart plug: resubscribed to {topic}") + except Exception as e: + logger.error(f"MQTT smart plug: failed to resubscribe to {topic}: {e}") def subscribe( self, plug_id: int, - topic: str, + # Power source + power_topic: str | None = None, power_path: str | None = None, + power_multiplier: float = 1.0, + # Energy source + energy_topic: str | None = None, energy_path: str | None = None, + energy_multiplier: float = 1.0, + # State source + state_topic: str | None = None, state_path: str | None = None, + state_on_value: str | None = None, + # Legacy: single topic/path/multiplier (for backward compatibility) + topic: str | None = None, multiplier: float = 1.0, ): - """Subscribe to a topic for a plug.""" + """Subscribe to MQTT topics for a plug. + + Each data type (power, energy, state) can have its own topic. + For backward compatibility, if power_topic is not set but topic is, + topic will be used for all data types that have paths configured. + """ with self._lock: - # Store configuration - self.plug_configs[plug_id] = (topic, power_path, energy_path, state_path, multiplier) + # Initialize config for this plug + self.plug_configs[plug_id] = {} - # Add to subscriptions - if topic not in self.subscriptions: - self.subscriptions[topic] = [] - # Actually subscribe if connected - if self.client and self.connected: - try: - self.client.subscribe(topic, qos=1) - logger.info(f"MQTT smart plug {plug_id}: subscribed to {topic}") - except Exception as e: - logger.error(f"MQTT smart plug: failed to subscribe to {topic}: {e}") + # Determine topics (new fields take priority, fall back to legacy) + effective_power_topic = power_topic or topic + effective_energy_topic = energy_topic or topic + effective_state_topic = state_topic or topic - if plug_id not in self.subscriptions[topic]: - self.subscriptions[topic].append(plug_id) + # Use new multipliers or fall back to legacy + effective_power_mult = power_multiplier if power_multiplier != 1.0 else multiplier + effective_energy_mult = energy_multiplier if energy_multiplier != 1.0 else multiplier + + # Configure power subscription + if effective_power_topic and power_path: + config = MQTTDataSourceConfig( + topic=effective_power_topic, + path=power_path, + multiplier=effective_power_mult, + ) + self.plug_configs[plug_id]["power"] = config + self._add_subscription(plug_id, effective_power_topic, "power") + + # Configure energy subscription + if effective_energy_topic and energy_path: + config = MQTTDataSourceConfig( + topic=effective_energy_topic, + path=energy_path, + multiplier=effective_energy_mult, + ) + self.plug_configs[plug_id]["energy"] = config + self._add_subscription(plug_id, effective_energy_topic, "energy") + + # Configure state subscription + if effective_state_topic and state_path: + config = MQTTDataSourceConfig( + topic=effective_state_topic, + path=state_path, + on_value=state_on_value, + ) + self.plug_configs[plug_id]["state"] = config + self._add_subscription(plug_id, effective_state_topic, "state") # Initialize data entry if plug_id not in self.plug_data: self.plug_data[plug_id] = SmartPlugMQTTData(plug_id=plug_id) + logger.info( + f"MQTT smart plug {plug_id}: configured with " + f"power={effective_power_topic if power_path else None}, " + f"energy={effective_energy_topic if energy_path else None}, " + f"state={effective_state_topic if state_path else None}" + ) + + def _add_subscription(self, plug_id: int, topic: str, data_type: str): + """Add a subscription for a plug/data_type to a topic.""" + if topic not in self.subscriptions: + self.subscriptions[topic] = [] + # Actually subscribe if connected + if self.client and self.connected: + try: + self.client.subscribe(topic, qos=1) + logger.info(f"MQTT smart plug: subscribed to {topic}") + except Exception as e: + logger.error(f"MQTT smart plug: failed to subscribe to {topic}: {e}") + + entry = (plug_id, data_type) + if entry not in self.subscriptions[topic]: + self.subscriptions[topic].append(entry) + def unsubscribe(self, plug_id: int): """Unsubscribe when plug is deleted/updated.""" with self._lock: - # Get the topic for this plug - config = self.plug_configs.pop(plug_id, None) - if not config: - return + # Get all configs for this plug + configs = self.plug_configs.pop(plug_id, {}) + if not configs: + # Still clean up any stray subscriptions + pass - topic = config[0] + # Collect all topics this plug was subscribed to + topics_to_check = set() + for _data_type, config in configs.items(): + topics_to_check.add(config.topic) - # Remove from subscriptions - if topic in self.subscriptions: - if plug_id in self.subscriptions[topic]: - self.subscriptions[topic].remove(plug_id) + # Also scan subscriptions to remove any entries for this plug + for topic in list(self.subscriptions.keys()): + # Remove all entries for this plug_id + self.subscriptions[topic] = [(pid, dtype) for pid, dtype in self.subscriptions[topic] if pid != plug_id] + topics_to_check.add(topic) - # If no more plugs on this topic, unsubscribe - if not self.subscriptions[topic]: + # Unsubscribe from topics with no more subscribers + for topic in topics_to_check: + if topic in self.subscriptions and not self.subscriptions[topic]: del self.subscriptions[topic] if self.client and self.connected: try: diff --git a/backend/tests/conftest.py b/backend/tests/conftest.py index 69b520fd1..1b4bb262b 100644 --- a/backend/tests/conftest.py +++ b/backend/tests/conftest.py @@ -315,9 +315,19 @@ def smart_plug_factory(db_session): defaults["ha_entity_id"] = "switch.test" defaults["ip_address"] = None elif plug_type == "mqtt": + # Legacy fields (for backward compatibility tests) defaults["mqtt_topic"] = kwargs.get("mqtt_topic", "test/topic") - defaults["mqtt_power_path"] = kwargs.get("mqtt_power_path", "power") defaults["mqtt_multiplier"] = kwargs.get("mqtt_multiplier", 1.0) + # New separate topic/path/multiplier fields + defaults["mqtt_power_topic"] = kwargs.get("mqtt_power_topic") + defaults["mqtt_power_path"] = kwargs.get("mqtt_power_path", "power") + defaults["mqtt_power_multiplier"] = kwargs.get("mqtt_power_multiplier", 1.0) + defaults["mqtt_energy_topic"] = kwargs.get("mqtt_energy_topic") + defaults["mqtt_energy_path"] = kwargs.get("mqtt_energy_path") + defaults["mqtt_energy_multiplier"] = kwargs.get("mqtt_energy_multiplier", 1.0) + defaults["mqtt_state_topic"] = kwargs.get("mqtt_state_topic") + defaults["mqtt_state_path"] = kwargs.get("mqtt_state_path") + defaults["mqtt_state_on_value"] = kwargs.get("mqtt_state_on_value") defaults["ip_address"] = None defaults["ha_entity_id"] = None else: diff --git a/backend/tests/integration/test_smart_plugs_api.py b/backend/tests/integration/test_smart_plugs_api.py index 90b7b1e8f..0a6421ae0 100644 --- a/backend/tests/integration/test_smart_plugs_api.py +++ b/backend/tests/integration/test_smart_plugs_api.py @@ -694,3 +694,132 @@ class TestSmartPlugsAPI: result = response.json() assert result["mqtt_topic"] == "new/topic" assert result["mqtt_power_path"] == "new_power" + + # ======================================================================== + # Enhanced MQTT Integration tests (separate topics per data type) + # ======================================================================== + + @pytest.mark.asyncio + @pytest.mark.integration + async def test_create_mqtt_plug_with_separate_topics(self, async_client: AsyncClient, mock_mqtt_smart_plug_service): + """Verify MQTT plug can be created with separate topics for power, energy, and state.""" + data = { + "name": "MQTT Separate Topics", + "plug_type": "mqtt", + "mqtt_power_topic": "zigbee/power", + "mqtt_power_path": "power_l1", + "mqtt_power_multiplier": 0.001, + "mqtt_energy_topic": "zigbee/energy", + "mqtt_energy_path": "energy_total", + "mqtt_energy_multiplier": 1.0, + "mqtt_state_topic": "zigbee/state", + "mqtt_state_path": "state", + "mqtt_state_on_value": "ON", + "enabled": True, + } + + response = await async_client.post("/api/v1/smart-plugs/", json=data) + + assert response.status_code == 200 + result = response.json() + assert result["name"] == "MQTT Separate Topics" + assert result["plug_type"] == "mqtt" + # Power fields + assert result["mqtt_power_topic"] == "zigbee/power" + assert result["mqtt_power_path"] == "power_l1" + assert result["mqtt_power_multiplier"] == 0.001 + # Energy fields + assert result["mqtt_energy_topic"] == "zigbee/energy" + assert result["mqtt_energy_path"] == "energy_total" + assert result["mqtt_energy_multiplier"] == 1.0 + # State fields + assert result["mqtt_state_topic"] == "zigbee/state" + assert result["mqtt_state_path"] == "state" + assert result["mqtt_state_on_value"] == "ON" + + @pytest.mark.asyncio + @pytest.mark.integration + async def test_create_mqtt_plug_energy_only(self, async_client: AsyncClient, mock_mqtt_smart_plug_service): + """Verify MQTT plug can be created with only energy monitoring.""" + data = { + "name": "Energy Only Monitor", + "plug_type": "mqtt", + "mqtt_energy_topic": "sensors/energy", + "mqtt_energy_path": "kwh", + "mqtt_energy_multiplier": 0.001, # Wh to kWh + "enabled": True, + } + + response = await async_client.post("/api/v1/smart-plugs/", json=data) + + assert response.status_code == 200 + result = response.json() + assert result["mqtt_energy_topic"] == "sensors/energy" + assert result["mqtt_energy_path"] == "kwh" + assert result["mqtt_energy_multiplier"] == 0.001 + + @pytest.mark.asyncio + @pytest.mark.integration + async def test_create_mqtt_plug_state_only(self, async_client: AsyncClient, mock_mqtt_smart_plug_service): + """Verify MQTT plug can be created with only state monitoring.""" + data = { + "name": "State Only Monitor", + "plug_type": "mqtt", + "mqtt_state_topic": "switches/outlet", + "mqtt_state_path": "state", + "mqtt_state_on_value": "true", + "enabled": True, + } + + response = await async_client.post("/api/v1/smart-plugs/", json=data) + + assert response.status_code == 200 + result = response.json() + assert result["mqtt_state_topic"] == "switches/outlet" + assert result["mqtt_state_path"] == "state" + assert result["mqtt_state_on_value"] == "true" + + @pytest.mark.asyncio + @pytest.mark.integration + async def test_create_mqtt_plug_no_data_source_fails(self, async_client: AsyncClient): + """Verify creating MQTT plug without any complete data source fails.""" + data = { + "name": "Invalid MQTT Plug", + "plug_type": "mqtt", + # Has topic but no path - not a complete data source + "mqtt_power_topic": "zigbee/power", + "enabled": True, + } + + response = await async_client.post("/api/v1/smart-plugs/", json=data) + + assert response.status_code == 422 # Validation error + + @pytest.mark.asyncio + @pytest.mark.integration + async def test_update_mqtt_plug_separate_multipliers( + self, async_client: AsyncClient, smart_plug_factory, db_session, mock_mqtt_smart_plug_service + ): + """Verify MQTT plug multipliers can be updated separately.""" + plug = await smart_plug_factory( + plug_type="mqtt", + mqtt_power_topic="test/power", + mqtt_power_path="power", + mqtt_power_multiplier=1.0, + mqtt_energy_topic="test/energy", + mqtt_energy_path="energy", + mqtt_energy_multiplier=1.0, + ) + + response = await async_client.patch( + f"/api/v1/smart-plugs/{plug.id}", + json={ + "mqtt_power_multiplier": 0.001, # Change power multiplier only + "mqtt_energy_multiplier": 0.001, # Change energy multiplier only + }, + ) + + assert response.status_code == 200 + result = response.json() + assert result["mqtt_power_multiplier"] == 0.001 + assert result["mqtt_energy_multiplier"] == 0.001 diff --git a/frontend/src/__tests__/components/SmartPlugCard.test.tsx b/frontend/src/__tests__/components/SmartPlugCard.test.tsx index da294867b..5182b08cb 100644 --- a/frontend/src/__tests__/components/SmartPlugCard.test.tsx +++ b/frontend/src/__tests__/components/SmartPlugCard.test.tsx @@ -21,12 +21,24 @@ const createMockPlug = (overrides: Partial = {}): SmartPlug => ({ plug_type: 'tasmota', ip_address: '192.168.1.100', ha_entity_id: null, - // MQTT fields + ha_power_entity: null, + ha_energy_today_entity: null, + ha_energy_total_entity: null, + // MQTT fields (legacy) mqtt_topic: null, - mqtt_power_path: null, - mqtt_energy_path: null, - mqtt_state_path: null, mqtt_multiplier: 1.0, + // MQTT power fields + mqtt_power_topic: null, + mqtt_power_path: null, + mqtt_power_multiplier: 1.0, + // MQTT energy fields + mqtt_energy_topic: null, + mqtt_energy_path: null, + mqtt_energy_multiplier: 1.0, + // MQTT state fields + mqtt_state_topic: null, + mqtt_state_path: null, + mqtt_state_on_value: null, printer_id: 1, enabled: true, auto_on: true, diff --git a/frontend/src/api/client.ts b/frontend/src/api/client.ts index c512fd3d4..bf5299f54 100644 --- a/frontend/src/api/client.ts +++ b/frontend/src/api/client.ts @@ -848,11 +848,21 @@ export interface SmartPlug { ha_energy_today_entity: string | null; ha_energy_total_entity: string | null; // MQTT fields (required when plug_type="mqtt") - mqtt_topic: string | null; // e.g., "zigbee2mqtt/shelly-working-room" + // Legacy field - kept for backward compatibility + mqtt_topic: string | null; // Deprecated, use mqtt_power_topic + mqtt_multiplier: number; // Deprecated, use mqtt_power_multiplier + // Power monitoring + mqtt_power_topic: string | null; // Topic for power data mqtt_power_path: string | null; // e.g., "power_l1" or "data.power" + mqtt_power_multiplier: number; // Unit conversion for power + // Energy monitoring + mqtt_energy_topic: string | null; // Topic for energy data mqtt_energy_path: string | null; // e.g., "energy_l1" + mqtt_energy_multiplier: number; // Unit conversion for energy + // State monitoring + mqtt_state_topic: string | null; // Topic for state data mqtt_state_path: string | null; // e.g., "state_l1" for ON/OFF - mqtt_multiplier: number; // Unit conversion (e.g., 0.001 for mW→W) + mqtt_state_on_value: string | null; // What value means "ON" (e.g., "ON", "true", "1") printer_id: number | null; enabled: boolean; auto_on: boolean; @@ -891,11 +901,21 @@ export interface SmartPlugCreate { ha_energy_today_entity?: string | null; ha_energy_total_entity?: string | null; // MQTT fields (required when plug_type="mqtt") + // Legacy fields - kept for backward compatibility mqtt_topic?: string | null; - mqtt_power_path?: string | null; - mqtt_energy_path?: string | null; - mqtt_state_path?: string | null; mqtt_multiplier?: number; + // Power monitoring + mqtt_power_topic?: string | null; + mqtt_power_path?: string | null; + mqtt_power_multiplier?: number; + // Energy monitoring + mqtt_energy_topic?: string | null; + mqtt_energy_path?: string | null; + mqtt_energy_multiplier?: number; + // State monitoring + mqtt_state_topic?: string | null; + mqtt_state_path?: string | null; + mqtt_state_on_value?: string | null; printer_id?: number | null; enabled?: boolean; auto_on?: boolean; @@ -926,12 +946,21 @@ export interface SmartPlugUpdate { ha_power_entity?: string | null; ha_energy_today_entity?: string | null; ha_energy_total_entity?: string | null; - // MQTT fields + // MQTT fields (legacy) mqtt_topic?: string | null; - mqtt_power_path?: string | null; - mqtt_energy_path?: string | null; - mqtt_state_path?: string | null; mqtt_multiplier?: number; + // MQTT power fields + mqtt_power_topic?: string | null; + mqtt_power_path?: string | null; + mqtt_power_multiplier?: number; + // MQTT energy fields + mqtt_energy_topic?: string | null; + mqtt_energy_path?: string | null; + mqtt_energy_multiplier?: number; + // MQTT state fields + mqtt_state_topic?: string | null; + mqtt_state_path?: string | null; + mqtt_state_on_value?: string | null; printer_id?: number | null; enabled?: boolean; auto_on?: boolean; diff --git a/frontend/src/components/AddSmartPlugModal.tsx b/frontend/src/components/AddSmartPlugModal.tsx index eccde0ac2..f976cab6e 100644 --- a/frontend/src/components/AddSmartPlugModal.tsx +++ b/frontend/src/components/AddSmartPlugModal.tsx @@ -24,12 +24,22 @@ export function AddSmartPlugModal({ plug, onClose }: AddSmartPlugModalProps) { const [password, setPassword] = useState(plug?.password || ''); // Home Assistant fields const [haEntityId, setHaEntityId] = useState(plug?.ha_entity_id || ''); - // MQTT fields - const [mqttTopic, setMqttTopic] = useState(plug?.mqtt_topic || ''); + // MQTT fields - Power + const [mqttPowerTopic, setMqttPowerTopic] = useState(plug?.mqtt_power_topic || plug?.mqtt_topic || ''); const [mqttPowerPath, setMqttPowerPath] = useState(plug?.mqtt_power_path || ''); + const [mqttPowerMultiplier, setMqttPowerMultiplier] = useState( + (plug?.mqtt_power_multiplier ?? plug?.mqtt_multiplier ?? 1).toString() + ); + // MQTT fields - Energy + const [mqttEnergyTopic, setMqttEnergyTopic] = useState(plug?.mqtt_energy_topic || ''); const [mqttEnergyPath, setMqttEnergyPath] = useState(plug?.mqtt_energy_path || ''); + const [mqttEnergyMultiplier, setMqttEnergyMultiplier] = useState( + (plug?.mqtt_energy_multiplier ?? 1).toString() + ); + // MQTT fields - State + const [mqttStateTopic, setMqttStateTopic] = useState(plug?.mqtt_state_topic || ''); const [mqttStatePath, setMqttStatePath] = useState(plug?.mqtt_state_path || ''); - const [mqttMultiplier, setMqttMultiplier] = useState(plug?.mqtt_multiplier?.toString() || '1'); + const [mqttStateOnValue, setMqttStateOnValue] = useState(plug?.mqtt_state_on_value || ''); // HA energy sensor entities (optional) const [haPowerEntity, setHaPowerEntity] = useState(plug?.ha_power_entity || ''); const [haEnergyTodayEntity, setHaEnergyTodayEntity] = useState(plug?.ha_energy_today_entity || ''); @@ -286,12 +296,13 @@ export function AddSmartPlugModal({ plug, onClose }: AddSmartPlugModalProps) { } if (plugType === 'mqtt') { - if (!mqttTopic.trim()) { - setError('MQTT topic is required for MQTT plugs'); - return; - } - if (!mqttPowerPath.trim() && !mqttStatePath.trim()) { - setError('At least power path or state path is required for MQTT plugs'); + // Check that at least one data source is fully configured + const hasPower = mqttPowerTopic.trim() && mqttPowerPath.trim(); + const hasEnergy = mqttEnergyTopic.trim() && mqttEnergyPath.trim(); + const hasState = mqttStateTopic.trim() && mqttStatePath.trim(); + + if (!hasPower && !hasEnergy && !hasState) { + setError('At least one data source must be configured: power (topic + path), energy (topic + path), or state (topic + path)'); return; } } @@ -305,12 +316,18 @@ export function AddSmartPlugModal({ plug, onClose }: AddSmartPlugModalProps) { ha_power_entity: plugType === 'homeassistant' ? (haPowerEntity || null) : null, ha_energy_today_entity: plugType === 'homeassistant' ? (haEnergyTodayEntity || null) : null, ha_energy_total_entity: plugType === 'homeassistant' ? (haEnergyTotalEntity || null) : null, - // MQTT fields - mqtt_topic: plugType === 'mqtt' ? mqttTopic.trim() : null, + // MQTT power fields + mqtt_power_topic: plugType === 'mqtt' ? (mqttPowerTopic.trim() || null) : null, mqtt_power_path: plugType === 'mqtt' ? (mqttPowerPath.trim() || null) : null, + mqtt_power_multiplier: plugType === 'mqtt' ? (parseFloat(mqttPowerMultiplier) || 1) : 1, + // MQTT energy fields + mqtt_energy_topic: plugType === 'mqtt' ? (mqttEnergyTopic.trim() || null) : null, mqtt_energy_path: plugType === 'mqtt' ? (mqttEnergyPath.trim() || null) : null, + mqtt_energy_multiplier: plugType === 'mqtt' ? (parseFloat(mqttEnergyMultiplier) || 1) : 1, + // MQTT state fields + mqtt_state_topic: plugType === 'mqtt' ? (mqttStateTopic.trim() || null) : null, mqtt_state_path: plugType === 'mqtt' ? (mqttStatePath.trim() || null) : null, - mqtt_multiplier: plugType === 'mqtt' ? (parseFloat(mqttMultiplier) || 1) : 1, + mqtt_state_on_value: plugType === 'mqtt' ? (mqttStateOnValue.trim() || null) : null, username: plugType === 'tasmota' ? (username.trim() || null) : null, password: plugType === 'tasmota' ? (password.trim() || null) : null, printer_id: printerId, @@ -918,65 +935,123 @@ export function AddSmartPlugModal({ plug, onClose }: AddSmartPlugModalProps) {

-
- - setMqttTopic(e.target.value)} - placeholder="zigbee2mqtt/shelly-working-room" - className="w-full px-3 py-2 bg-bambu-dark border border-bambu-dark-tertiary rounded-lg text-white placeholder-bambu-gray focus:border-bambu-green focus:outline-none" - /> -

The MQTT topic to subscribe to

+ {/* Power Section */} +
+

Power Monitoring

+
+ + setMqttPowerTopic(e.target.value)} + placeholder="zigbee2mqtt/shelly-working-room" + className="w-full px-3 py-2 bg-bambu-dark-secondary border border-bambu-dark-tertiary rounded-lg text-white placeholder-bambu-gray focus:border-bambu-green focus:outline-none" + /> +
+
+
+ + setMqttPowerPath(e.target.value)} + placeholder="power_l1" + className="w-full px-3 py-2 bg-bambu-dark-secondary border border-bambu-dark-tertiary rounded-lg text-white placeholder-bambu-gray focus:border-bambu-green focus:outline-none" + /> +
+
+ + setMqttPowerMultiplier(e.target.value)} + placeholder="1" + className="w-full px-3 py-2 bg-bambu-dark-secondary border border-bambu-dark-tertiary rounded-lg text-white placeholder-bambu-gray focus:border-bambu-green focus:outline-none" + /> +
+
+

+ Use multiplier 0.001 for mW→W, 1000 for kW→W +

-
- - setMqttPowerPath(e.target.value)} - placeholder="power_l1 or data.power" - className="w-full px-3 py-2 bg-bambu-dark border border-bambu-dark-tertiary rounded-lg text-white placeholder-bambu-gray focus:border-bambu-green focus:outline-none" - /> -

Path to power value in JSON (dot notation)

+ {/* Energy Section */} +
+

Energy Monitoring (optional)

+
+ + setMqttEnergyTopic(e.target.value)} + placeholder="Same as power topic, or different" + className="w-full px-3 py-2 bg-bambu-dark-secondary border border-bambu-dark-tertiary rounded-lg text-white placeholder-bambu-gray focus:border-bambu-green focus:outline-none" + /> +
+
+
+ + setMqttEnergyPath(e.target.value)} + placeholder="energy_l1" + className="w-full px-3 py-2 bg-bambu-dark-secondary border border-bambu-dark-tertiary rounded-lg text-white placeholder-bambu-gray focus:border-bambu-green focus:outline-none" + /> +
+
+ + setMqttEnergyMultiplier(e.target.value)} + placeholder="1" + className="w-full px-3 py-2 bg-bambu-dark-secondary border border-bambu-dark-tertiary rounded-lg text-white placeholder-bambu-gray focus:border-bambu-green focus:outline-none" + /> +
+
+

+ Use multiplier 0.001 for Wh→kWh, 1000 for MWh→kWh +

-
- - setMqttStatePath(e.target.value)} - placeholder="state_l1" - className="w-full px-3 py-2 bg-bambu-dark border border-bambu-dark-tertiary rounded-lg text-white placeholder-bambu-gray focus:border-bambu-green focus:outline-none" - /> -

Path to ON/OFF state (optional)

-
- -
- - setMqttEnergyPath(e.target.value)} - placeholder="energy_l1" - className="w-full px-3 py-2 bg-bambu-dark border border-bambu-dark-tertiary rounded-lg text-white placeholder-bambu-gray focus:border-bambu-green focus:outline-none" - /> -

Path to energy/kWh value (optional)

-
- -
- - setMqttMultiplier(e.target.value)} - placeholder="1" - className="w-full px-3 py-2 bg-bambu-dark border border-bambu-dark-tertiary rounded-lg text-white placeholder-bambu-gray focus:border-bambu-green focus:outline-none" - /> -

- Multiply values by this factor. Use 0.001 to convert mW to W, 1000 for kW to W. + {/* State Section */} +

+

State Monitoring (optional)

+
+ + setMqttStateTopic(e.target.value)} + placeholder="Same as power topic, or different" + className="w-full px-3 py-2 bg-bambu-dark-secondary border border-bambu-dark-tertiary rounded-lg text-white placeholder-bambu-gray focus:border-bambu-green focus:outline-none" + /> +
+
+
+ + setMqttStatePath(e.target.value)} + placeholder="state_l1" + className="w-full px-3 py-2 bg-bambu-dark-secondary border border-bambu-dark-tertiary rounded-lg text-white placeholder-bambu-gray focus:border-bambu-green focus:outline-none" + /> +
+
+ + setMqttStateOnValue(e.target.value)} + placeholder="ON, true, 1" + className="w-full px-3 py-2 bg-bambu-dark-secondary border border-bambu-dark-tertiary rounded-lg text-white placeholder-bambu-gray focus:border-bambu-green focus:outline-none" + /> +
+
+

+ ON value: the exact string that means "ON". Leave empty for auto-detect (ON, true, 1)

diff --git a/static/assets/index-CmjSB2jQ.js b/static/assets/index-Cry_KMOA.js similarity index 81% rename from static/assets/index-CmjSB2jQ.js rename to static/assets/index-Cry_KMOA.js index 853f67890..1082b56e4 100644 --- a/static/assets/index-CmjSB2jQ.js +++ b/static/assets/index-Cry_KMOA.js @@ -1,20 +1,20 @@ -function Pae(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 o of a.addedNodes)o.tagName==="LINK"&&o.rel==="modulepreload"&&r(o)}).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 Jv=typeof globalThis<"u"?globalThis:typeof window<"u"?window:typeof global<"u"?global:typeof self<"u"?self:{};function Fo(t){return t&&t.__esModule&&Object.prototype.hasOwnProperty.call(t,"default")?t.default:t}var _T={exports:{}},Kx={};var wU;function Rae(){if(wU)return Kx;wU=1;var t=Symbol.for("react.transitional.element"),e=Symbol.for("react.fragment");function n(r,i,a){var o=null;if(a!==void 0&&(o=""+a),i.key!==void 0&&(o=""+i.key),"key"in i){a={};for(var l in i)l!=="key"&&(a[l]=i[l])}else a=i;return i=a.ref,{$$typeof:t,type:r,key:o,ref:i!==void 0?i:null,props:a}}return Kx.Fragment=e,Kx.jsx=n,Kx.jsxs=n,Kx}var SU;function Dae(){return SU||(SU=1,_T.exports=Rae()),_T.exports}var s=Dae(),wT={exports:{}},gn={};var NU;function Oae(){if(NU)return gn;NU=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"),o=Symbol.for("react.context"),l=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"),m=Symbol.iterator;function g(B){return B===null||typeof B!="object"?null:(B=m&&B[m]||B["@@iterator"],typeof B=="function"?B:null)}var x={isMounted:function(){return!1},enqueueForceUpdate:function(){},enqueueReplaceState:function(){},enqueueSetState:function(){}},y=Object.assign,b={};function v(B,G,ae){this.props=B,this.context=G,this.refs=b,this.updater=ae||x}v.prototype.isReactComponent={},v.prototype.setState=function(B,G){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,G,"setState")},v.prototype.forceUpdate=function(B){this.updater.enqueueForceUpdate(this,B,"forceUpdate")};function N(){}N.prototype=v.prototype;function S(B,G,ae){this.props=B,this.context=G,this.refs=b,this.updater=ae||x}var A=S.prototype=new N;A.constructor=S,y(A,v.prototype),A.isPureReactComponent=!0;var T=Array.isArray;function C(){}var E={H:null,A:null,T:null,S:null},D=Object.prototype.hasOwnProperty;function k(B,G,ae){var se=ae.ref;return{$$typeof:t,type:B,key:G,ref:se!==void 0?se:null,props:ae}}function R(B,G){return k(B.type,G,B.props)}function F(B){return typeof B=="object"&&B!==null&&B.$$typeof===t}function O(B){var G={"=":"=0",":":"=2"};return"$"+B.replace(/[=:]/g,function(ae){return G[ae]})}var X=/\/+/g;function j(B,G){return typeof B=="object"&&B!==null&&B.key!=null?O(""+B.key):G.toString(36)}function q(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(G){B.status==="pending"&&(B.status="fulfilled",B.value=G)},function(G){B.status==="pending"&&(B.status="rejected",B.reason=G)})),B.status){case"fulfilled":return B.value;case"rejected":throw B.reason}}throw B}function Y(B,G,ae,se,de){var me=typeof B;(me==="undefined"||me==="boolean")&&(B=null);var ue=!1;if(B===null)ue=!0;else switch(me){case"bigint":case"string":case"number":ue=!0;break;case"object":switch(B.$$typeof){case t:case e:ue=!0;break;case d:return ue=B._init,Y(ue(B._payload),G,ae,se,de)}}if(ue)return de=de(B),ue=se===""?"."+j(B,0):se,T(de)?(ae="",ue!=null&&(ae=ue.replace(X,"$&/")+"/"),Y(de,G,ae,"",function(Ne){return Ne})):de!=null&&(F(de)&&(de=R(de,ae+(de.key==null||B&&B.key===de.key?"":(""+de.key).replace(X,"$&/")+"/")+ue)),G.push(de)),1;ue=0;var Ee=se===""?".":se+":";if(T(B))for(var pe=0;pe>>1,ne=Y[ee];if(0>>1;eei(ae,Z))sei(de,ae)?(Y[ee]=de,Y[se]=Z,ee=se):(Y[ee]=ae,Y[G]=Z,ee=G);else if(sei(de,Z))Y[ee]=de,Y[se]=Z,ee=se;else break e}}return W}function i(Y,W){var Z=Y.sortIndex-W.sortIndex;return Z!==0?Z:Y.id-W.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 o=Date,l=o.now();t.unstable_now=function(){return o.now()-l}}var c=[],u=[],d=1,f=null,m=3,g=!1,x=!1,y=!1,b=!1,v=typeof setTimeout=="function"?setTimeout:null,N=typeof clearTimeout=="function"?clearTimeout:null,S=typeof setImmediate<"u"?setImmediate:null;function A(Y){for(var W=n(u);W!==null;){if(W.callback===null)r(u);else if(W.startTime<=Y)r(u),W.sortIndex=W.expirationTime,e(c,W);else break;W=n(u)}}function T(Y){if(y=!1,A(Y),!x)if(n(c)!==null)x=!0,C||(C=!0,O());else{var W=n(u);W!==null&&q(T,W.startTime-Y)}}var C=!1,E=-1,D=5,k=-1;function R(){return b?!0:!(t.unstable_now()-kY&&R());){var ee=f.callback;if(typeof ee=="function"){f.callback=null,m=f.priorityLevel;var ne=ee(f.expirationTime<=Y);if(Y=t.unstable_now(),typeof ne=="function"){f.callback=ne,A(Y),W=!0;break t}f===n(c)&&r(c),A(Y)}else r(c);f=n(c)}if(f!==null)W=!0;else{var B=n(u);B!==null&&q(T,B.startTime-Y),W=!1}}break e}finally{f=null,m=Z,g=!1}W=void 0}}finally{W?O():C=!1}}}var O;if(typeof S=="function")O=function(){S(F)};else if(typeof MessageChannel<"u"){var X=new MessageChannel,j=X.port2;X.port1.onmessage=F,O=function(){j.postMessage(null)}}else O=function(){v(F,0)};function q(Y,W){E=v(function(){Y(t.unstable_now())},W)}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(Y){Y.callback=null},t.unstable_forceFrameRate=function(Y){0>Y||125ee?(Y.sortIndex=Z,e(u,Y),n(c)===null&&Y===n(u)&&(y?(N(E),E=-1):y=!0,q(T,Z-ee))):(Y.sortIndex=ne,e(c,Y),x||g||(x=!0,C||(C=!0,O()))),Y},t.unstable_shouldYield=R,t.unstable_wrapCallback=function(Y){var W=m;return function(){var Z=m;m=W;try{return Y.apply(this,arguments)}finally{m=Z}}}})(MT)),MT}var TU;function Fae(){return TU||(TU=1,NT.exports=Iae()),NT.exports}var ET={exports:{}},Na={};var AU;function Uae(){if(AU)return Na;AU=1;var t=Og();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(),ET.exports=Uae(),ET.exports}var kU;function zae(){if(kU)return Xx;kU=1;var t=Fae(),e=Og(),n=sq();function r(h){var p="https://react.dev/errors/"+h;if(1ne||(h.current=ee[ne],ee[ne]=null,ne--)}function ae(h,p){ne++,ee[ne]=h.current,h.current=p}var se=B(null),de=B(null),me=B(null),ue=B(null);function Ee(h,p){switch(ae(me,p),ae(de,h),ae(se,null),p.nodeType){case 9:case 11:h=(h=p.documentElement)&&(h=h.namespaceURI)?GF(h):0;break;default:if(h=p.tagName,p=p.namespaceURI)p=GF(p),h=qF(p,h);else switch(h){case"svg":h=1;break;case"math":h=2;break;default:h=0}}G(se),ae(se,h)}function pe(){G(se),G(de),G(me)}function Ne(h){h.memoizedState!==null&&ae(ue,h);var p=se.current,_=qF(p,h.type);p!==_&&(ae(de,h),ae(se,_))}function _e(h){de.current===h&&(G(se),G(de)),ue.current===h&&(G(ue),Vx._currentValue=Z)}var oe,Ue;function V(h){if(oe===void 0)try{throw Error()}catch(_){var p=_.stack.trim().match(/\n( *(at )?)/);oe=p&&p[1]||"",Ue=-1<_.stack.indexOf(` +function Pae(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 o of a.addedNodes)o.tagName==="LINK"&&o.rel==="modulepreload"&&r(o)}).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 Jv=typeof globalThis<"u"?globalThis:typeof window<"u"?window:typeof global<"u"?global:typeof self<"u"?self:{};function Fo(t){return t&&t.__esModule&&Object.prototype.hasOwnProperty.call(t,"default")?t.default:t}var _T={exports:{}},Kx={};var wU;function Rae(){if(wU)return Kx;wU=1;var t=Symbol.for("react.transitional.element"),e=Symbol.for("react.fragment");function n(r,i,a){var o=null;if(a!==void 0&&(o=""+a),i.key!==void 0&&(o=""+i.key),"key"in i){a={};for(var l in i)l!=="key"&&(a[l]=i[l])}else a=i;return i=a.ref,{$$typeof:t,type:r,key:o,ref:i!==void 0?i:null,props:a}}return Kx.Fragment=e,Kx.jsx=n,Kx.jsxs=n,Kx}var SU;function Dae(){return SU||(SU=1,_T.exports=Rae()),_T.exports}var s=Dae(),wT={exports:{}},pn={};var NU;function Oae(){if(NU)return pn;NU=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"),o=Symbol.for("react.context"),l=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"),m=Symbol.iterator;function g(z){return z===null||typeof z!="object"?null:(z=m&&z[m]||z["@@iterator"],typeof z=="function"?z:null)}var x={isMounted:function(){return!1},enqueueForceUpdate:function(){},enqueueReplaceState:function(){},enqueueSetState:function(){}},y=Object.assign,b={};function v(z,G,ae){this.props=z,this.context=G,this.refs=b,this.updater=ae||x}v.prototype.isReactComponent={},v.prototype.setState=function(z,G){if(typeof z!="object"&&typeof z!="function"&&z!=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,z,G,"setState")},v.prototype.forceUpdate=function(z){this.updater.enqueueForceUpdate(this,z,"forceUpdate")};function N(){}N.prototype=v.prototype;function S(z,G,ae){this.props=z,this.context=G,this.refs=b,this.updater=ae||x}var A=S.prototype=new N;A.constructor=S,y(A,v.prototype),A.isPureReactComponent=!0;var T=Array.isArray;function C(){}var E={H:null,A:null,T:null,S:null},D=Object.prototype.hasOwnProperty;function k(z,G,ae){var se=ae.ref;return{$$typeof:t,type:z,key:G,ref:se!==void 0?se:null,props:ae}}function R(z,G){return k(z.type,G,z.props)}function F(z){return typeof z=="object"&&z!==null&&z.$$typeof===t}function O(z){var G={"=":"=0",":":"=2"};return"$"+z.replace(/[=:]/g,function(ae){return G[ae]})}var X=/\/+/g;function j(z,G){return typeof z=="object"&&z!==null&&z.key!=null?O(""+z.key):G.toString(36)}function q(z){switch(z.status){case"fulfilled":return z.value;case"rejected":throw z.reason;default:switch(typeof z.status=="string"?z.then(C,C):(z.status="pending",z.then(function(G){z.status==="pending"&&(z.status="fulfilled",z.value=G)},function(G){z.status==="pending"&&(z.status="rejected",z.reason=G)})),z.status){case"fulfilled":return z.value;case"rejected":throw z.reason}}throw z}function Y(z,G,ae,se,de){var me=typeof z;(me==="undefined"||me==="boolean")&&(z=null);var ue=!1;if(z===null)ue=!0;else switch(me){case"bigint":case"string":case"number":ue=!0;break;case"object":switch(z.$$typeof){case t:case e:ue=!0;break;case d:return ue=z._init,Y(ue(z._payload),G,ae,se,de)}}if(ue)return de=de(z),ue=se===""?"."+j(z,0):se,T(de)?(ae="",ue!=null&&(ae=ue.replace(X,"$&/")+"/"),Y(de,G,ae,"",function(Ee){return Ee})):de!=null&&(F(de)&&(de=R(de,ae+(de.key==null||z&&z.key===de.key?"":(""+de.key).replace(X,"$&/")+"/")+ue)),G.push(de)),1;ue=0;var Ae=se===""?".":se+":";if(T(z))for(var pe=0;pe>>1,ne=Y[ee];if(0>>1;eei(ae,Z))sei(de,ae)?(Y[ee]=de,Y[se]=Z,ee=se):(Y[ee]=ae,Y[G]=Z,ee=G);else if(sei(de,Z))Y[ee]=de,Y[se]=Z,ee=se;else break e}}return W}function i(Y,W){var Z=Y.sortIndex-W.sortIndex;return Z!==0?Z:Y.id-W.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 o=Date,l=o.now();t.unstable_now=function(){return o.now()-l}}var c=[],u=[],d=1,f=null,m=3,g=!1,x=!1,y=!1,b=!1,v=typeof setTimeout=="function"?setTimeout:null,N=typeof clearTimeout=="function"?clearTimeout:null,S=typeof setImmediate<"u"?setImmediate:null;function A(Y){for(var W=n(u);W!==null;){if(W.callback===null)r(u);else if(W.startTime<=Y)r(u),W.sortIndex=W.expirationTime,e(c,W);else break;W=n(u)}}function T(Y){if(y=!1,A(Y),!x)if(n(c)!==null)x=!0,C||(C=!0,O());else{var W=n(u);W!==null&&q(T,W.startTime-Y)}}var C=!1,E=-1,D=5,k=-1;function R(){return b?!0:!(t.unstable_now()-kY&&R());){var ee=f.callback;if(typeof ee=="function"){f.callback=null,m=f.priorityLevel;var ne=ee(f.expirationTime<=Y);if(Y=t.unstable_now(),typeof ne=="function"){f.callback=ne,A(Y),W=!0;break t}f===n(c)&&r(c),A(Y)}else r(c);f=n(c)}if(f!==null)W=!0;else{var z=n(u);z!==null&&q(T,z.startTime-Y),W=!1}}break e}finally{f=null,m=Z,g=!1}W=void 0}}finally{W?O():C=!1}}}var O;if(typeof S=="function")O=function(){S(F)};else if(typeof MessageChannel<"u"){var X=new MessageChannel,j=X.port2;X.port1.onmessage=F,O=function(){j.postMessage(null)}}else O=function(){v(F,0)};function q(Y,W){E=v(function(){Y(t.unstable_now())},W)}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(Y){Y.callback=null},t.unstable_forceFrameRate=function(Y){0>Y||125ee?(Y.sortIndex=Z,e(u,Y),n(c)===null&&Y===n(u)&&(y?(N(E),E=-1):y=!0,q(T,Z-ee))):(Y.sortIndex=ne,e(c,Y),x||g||(x=!0,C||(C=!0,O()))),Y},t.unstable_shouldYield=R,t.unstable_wrapCallback=function(Y){var W=m;return function(){var Z=m;m=W;try{return Y.apply(this,arguments)}finally{m=Z}}}})(MT)),MT}var TU;function Fae(){return TU||(TU=1,NT.exports=Iae()),NT.exports}var ET={exports:{}},Na={};var AU;function Uae(){if(AU)return Na;AU=1;var t=Og();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(),ET.exports=Uae(),ET.exports}var kU;function zae(){if(kU)return Xx;kU=1;var t=Fae(),e=Og(),n=sq();function r(h){var p="https://react.dev/errors/"+h;if(1ne||(h.current=ee[ne],ee[ne]=null,ne--)}function ae(h,p){ne++,ee[ne]=h.current,h.current=p}var se=z(null),de=z(null),me=z(null),ue=z(null);function Ae(h,p){switch(ae(me,p),ae(de,h),ae(se,null),p.nodeType){case 9:case 11:h=(h=p.documentElement)&&(h=h.namespaceURI)?GF(h):0;break;default:if(h=p.tagName,p=p.namespaceURI)p=GF(p),h=qF(p,h);else switch(h){case"svg":h=1;break;case"math":h=2;break;default:h=0}}G(se),ae(se,h)}function pe(){G(se),G(de),G(me)}function Ee(h){h.memoizedState!==null&&ae(ue,h);var p=se.current,_=qF(p,h.type);p!==_&&(ae(de,h),ae(se,_))}function _e(h){de.current===h&&(G(se),G(de)),ue.current===h&&(G(ue),Vx._currentValue=Z)}var oe,ze;function V(h){if(oe===void 0)try{throw Error()}catch(_){var p=_.stack.trim().match(/\n( *(at )?)/);oe=p&&p[1]||"",ze=-1<_.stack.indexOf(` at`)?" ()":-1<_.stack.indexOf("@")?"@unknown:0:0":""}return` -`+oe+h+Ue}var $=!1;function K(h,p){if(!h||$)return"";$=!0;var _=Error.prepareStackTrace;Error.prepareStackTrace=void 0;try{var M={DetermineComponentFrameRoot:function(){try{if(p){var _t=function(){throw Error()};if(Object.defineProperty(_t.prototype,"props",{set:function(){throw Error()}}),typeof Reflect=="object"&&Reflect.construct){try{Reflect.construct(_t,[])}catch(dt){var st=dt}Reflect.construct(h,[],_t)}else{try{_t.call()}catch(dt){st=dt}h.call(_t.prototype)}}else{try{throw Error()}catch(dt){st=dt}(_t=h())&&typeof _t.catch=="function"&&_t.catch(function(){})}}catch(dt){if(dt&&st&&typeof dt.stack=="string")return[dt.stack,st.stack]}return[null,null]}};M.DetermineComponentFrameRoot.displayName="DetermineComponentFrameRoot";var P=Object.getOwnPropertyDescriptor(M.DetermineComponentFrameRoot,"name");P&&P.configurable&&Object.defineProperty(M.DetermineComponentFrameRoot,"name",{value:"DetermineComponentFrameRoot"});var L=M.DetermineComponentFrameRoot(),J=L[0],ge=L[1];if(J&&ge){var Le=J.split(` +`+oe+h+ze}var H=!1;function K(h,p){if(!h||H)return"";H=!0;var _=Error.prepareStackTrace;Error.prepareStackTrace=void 0;try{var M={DetermineComponentFrameRoot:function(){try{if(p){var vt=function(){throw Error()};if(Object.defineProperty(vt.prototype,"props",{set:function(){throw Error()}}),typeof Reflect=="object"&&Reflect.construct){try{Reflect.construct(vt,[])}catch(dt){var st=dt}Reflect.construct(h,[],vt)}else{try{vt.call()}catch(dt){st=dt}h.call(vt.prototype)}}else{try{throw Error()}catch(dt){st=dt}(vt=h())&&typeof vt.catch=="function"&&vt.catch(function(){})}}catch(dt){if(dt&&st&&typeof dt.stack=="string")return[dt.stack,st.stack]}return[null,null]}};M.DetermineComponentFrameRoot.displayName="DetermineComponentFrameRoot";var P=Object.getOwnPropertyDescriptor(M.DetermineComponentFrameRoot,"name");P&&P.configurable&&Object.defineProperty(M.DetermineComponentFrameRoot,"name",{value:"DetermineComponentFrameRoot"});var L=M.DetermineComponentFrameRoot(),J=L[0],ge=L[1];if(J&&ge){var Fe=J.split(` `),rt=ge.split(` -`);for(P=M=0;MP||Le[M]!==rt[P]){var xt=` -`+Le[M].replace(" at new "," at ");return h.displayName&&xt.includes("")&&(xt=xt.replace("",h.displayName)),xt}while(1<=M&&0<=P);break}}}finally{$=!1,Error.prepareStackTrace=_}return(_=h?h.displayName||h.name:"")?V(_):""}function U(h,p){switch(h.tag){case 26:case 27:case 5:return V(h.type);case 16:return V("Lazy");case 13:return h.child!==p&&p!==null?V("Suspense Fallback"):V("Suspense");case 19:return V("SuspenseList");case 0:case 15:return K(h.type,!1);case 11:return K(h.type.render,!1);case 1:return K(h.type,!0);case 31:return V("Activity");default:return""}}function Q(h){try{var p="",_=null;do p+=U(h,_),_=h,h=h.return;while(h);return p}catch(M){return` +`);for(P=M=0;MP||Fe[M]!==rt[P]){var xt=` +`+Fe[M].replace(" at new "," at ");return h.displayName&&xt.includes("")&&(xt=xt.replace("",h.displayName)),xt}while(1<=M&&0<=P);break}}}finally{H=!1,Error.prepareStackTrace=_}return(_=h?h.displayName||h.name:"")?V(_):""}function U(h,p){switch(h.tag){case 26:case 27:case 5:return V(h.type);case 16:return V("Lazy");case 13:return h.child!==p&&p!==null?V("Suspense Fallback"):V("Suspense");case 19:return V("SuspenseList");case 0:case 15:return K(h.type,!1);case 11:return K(h.type.render,!1);case 1:return K(h.type,!0);case 31:return V("Activity");default:return""}}function Q(h){try{var p="",_=null;do p+=U(h,_),_=h,h=h.return;while(h);return p}catch(M){return` Error generating stack: `+M.message+` -`+M.stack}}var le=Object.prototype.hasOwnProperty,Ae=t.unstable_scheduleCallback,je=t.unstable_cancelCallback,Ce=t.unstable_shouldYield,z=t.unstable_requestPaint,I=t.unstable_now,H=t.unstable_getCurrentPriorityLevel,te=t.unstable_ImmediatePriority,ye=t.unstable_UserBlockingPriority,xe=t.unstable_NormalPriority,Ze=t.unstable_LowPriority,Be=t.unstable_IdlePriority,et=t.log,Ke=t.unstable_setDisableYieldValue,Ye=null,ce=null;function ie(h){if(typeof et=="function"&&Ke(h),ce&&typeof ce.setStrictMode=="function")try{ce.setStrictMode(Ye,h)}catch{}}var Me=Math.clz32?Math.clz32:re,qe=Math.log,it=Math.LN2;function re(h){return h>>>=0,h===0?32:31-(qe(h)/it|0)|0}var Pe=256,we=262144,Re=4194304;function Ie(h){var p=h&42;if(p!==0)return p;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 Te(h,p,_){var M=h.pendingLanes;if(M===0)return 0;var P=0,L=h.suspendedLanes,J=h.pingedLanes;h=h.warmLanes;var ge=M&134217727;return ge!==0?(M=ge&~L,M!==0?P=Ie(M):(J&=ge,J!==0?P=Ie(J):_||(_=ge&~h,_!==0&&(P=Ie(_))))):(ge=M&~L,ge!==0?P=Ie(ge):J!==0?P=Ie(J):_||(_=M&~h,_!==0&&(P=Ie(_)))),P===0?0:p!==0&&p!==P&&(p&L)===0&&(L=P&-P,_=p&-p,L>=_||L===32&&(_&4194048)!==0)?p:P}function Je(h,p){return(h.pendingLanes&~(h.suspendedLanes&~h.pingedLanes)&p)===0}function bt(h,p){switch(h){case 1:case 2:case 4:case 8:case 64:return p+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 p+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 At(){var h=Re;return Re<<=1,(Re&62914560)===0&&(Re=4194304),h}function Ot(h){for(var p=[],_=0;31>_;_++)p.push(h);return p}function sn(h,p){h.pendingLanes|=p,p!==268435456&&(h.suspendedLanes=0,h.pingedLanes=0,h.warmLanes=0)}function It(h,p,_,M,P,L){var J=h.pendingLanes;h.pendingLanes=_,h.suspendedLanes=0,h.pingedLanes=0,h.warmLanes=0,h.expiredLanes&=_,h.entangledLanes&=_,h.errorRecoveryDisabledLanes&=_,h.shellSuspendCounter=0;var ge=h.entanglements,Le=h.expirationTimes,rt=h.hiddenUpdates;for(_=J&~_;0<_;){var xt=31-Me(_),_t=1<"u")return null;try{return h.activeElement||h.body}catch{return h.body}}var Kt=/[\n"\\]/g;function Xt(h){return h.replace(Kt,function(p){return"\\"+p.charCodeAt(0).toString(16)+" "})}function Bn(h,p,_,M,P,L,J,ge){h.name="",J!=null&&typeof J!="function"&&typeof J!="symbol"&&typeof J!="boolean"?h.type=J:h.removeAttribute("type"),p!=null?J==="number"?(p===0&&h.value===""||h.value!=p)&&(h.value=""+ve(p)):h.value!==""+ve(p)&&(h.value=""+ve(p)):J!=="submit"&&J!=="reset"||h.removeAttribute("value"),p!=null?Er(h,J,ve(p)):_!=null?Er(h,J,ve(_)):M!=null&&h.removeAttribute("value"),P==null&&L!=null&&(h.defaultChecked=!!L),P!=null&&(h.checked=P&&typeof P!="function"&&typeof P!="symbol"),ge!=null&&typeof ge!="function"&&typeof ge!="symbol"&&typeof ge!="boolean"?h.name=""+ve(ge):h.removeAttribute("name")}function ln(h,p,_,M,P,L,J,ge){if(L!=null&&typeof L!="function"&&typeof L!="symbol"&&typeof L!="boolean"&&(h.type=L),p!=null||_!=null){if(!(L!=="submit"&&L!=="reset"||p!=null)){mt(h);return}_=_!=null?""+ve(_):"",p=p!=null?""+ve(p):_,ge||p===h.value||(h.value=p),h.defaultValue=p}M=M??P,M=typeof M!="function"&&typeof M!="symbol"&&!!M,h.checked=ge?h.checked:!!M,h.defaultChecked=!!M,J!=null&&typeof J!="function"&&typeof J!="symbol"&&typeof J!="boolean"&&(h.name=J),mt(h)}function Er(h,p,_){p==="number"&>(h.ownerDocument)===h||h.defaultValue===""+_||(h.defaultValue=""+_)}function Gt(h,p,_,M){if(h=h.options,p){p={};for(var P=0;P<_.length;P++)p["$"+_[P]]=!0;for(_=0;_"u"||typeof window.document>"u"||typeof window.document.createElement>"u"),_M=!1;if(rc)try{var lx={};Object.defineProperty(lx,"passive",{get:function(){_M=!0}}),window.addEventListener("test",lx,lx),window.removeEventListener("test",lx,lx)}catch{_M=!1}var Su=null,wM=null,Gy=null;function qL(){if(Gy)return Gy;var h,p=wM,_=p.length,M,P="value"in Su?Su.value:Su.textContent,L=P.length;for(h=0;h<_&&p[h]===P[h];h++);var J=_-h;for(M=1;M<=J&&p[_-M]===P[L-M];M++);return Gy=P.slice(h,1=dx),ZL=" ",JL=!1;function eI(h,p){switch(h){case"keyup":return eie.indexOf(p.keyCode)!==-1;case"keydown":return p.keyCode!==229;case"keypress":case"mousedown":case"focusout":return!0;default:return!1}}function tI(h){return h=h.detail,typeof h=="object"&&"data"in h?h.data:null}var cm=!1;function nie(h,p){switch(h){case"compositionend":return tI(p);case"keypress":return p.which!==32?null:(JL=!0,ZL);case"textInput":return h=p.data,h===ZL&&JL?null:h;default:return null}}function rie(h,p){if(cm)return h==="compositionend"||!TM&&eI(h,p)?(h=qL(),Gy=wM=Su=null,cm=!1,h):null;switch(h){case"paste":return null;case"keypress":if(!(p.ctrlKey||p.altKey||p.metaKey)||p.ctrlKey&&p.altKey){if(p.char&&1=p)return{node:_,offset:p-h};h=M}e:{for(;_;){if(_.nextSibling){_=_.nextSibling;break e}_=_.parentNode}_=void 0}_=cI(_)}}function dI(h,p){return h&&p?h===p?!0:h&&h.nodeType===3?!1:p&&p.nodeType===3?dI(h,p.parentNode):"contains"in h?h.contains(p):h.compareDocumentPosition?!!(h.compareDocumentPosition(p)&16):!1:!1}function hI(h){h=h!=null&&h.ownerDocument!=null&&h.ownerDocument.defaultView!=null?h.ownerDocument.defaultView:window;for(var p=gt(h.document);p instanceof h.HTMLIFrameElement;){try{var _=typeof p.contentWindow.location.href=="string"}catch{_=!1}if(_)h=p.contentWindow;else break;p=gt(h.document)}return p}function kM(h){var p=h&&h.nodeName&&h.nodeName.toLowerCase();return p&&(p==="input"&&(h.type==="text"||h.type==="search"||h.type==="tel"||h.type==="url"||h.type==="password")||p==="textarea"||h.contentEditable==="true")}var die=rc&&"documentMode"in document&&11>=document.documentMode,um=null,jM=null,px=null,PM=!1;function fI(h,p,_){var M=_.window===_?_.document:_.nodeType===9?_:_.ownerDocument;PM||um==null||um!==gt(M)||(M=um,"selectionStart"in M&&kM(M)?M={start:M.selectionStart,end:M.selectionEnd}:(M=(M.ownerDocument&&M.ownerDocument.defaultView||window).getSelection(),M={anchorNode:M.anchorNode,anchorOffset:M.anchorOffset,focusNode:M.focusNode,focusOffset:M.focusOffset}),px&&mx(px,M)||(px=M,M=Fv(jM,"onSelect"),0>=J,P-=J,xl=1<<32-Me(p)+P|_<yn?(Dn=Yt,Yt=null):Dn=Yt.sibling;var Gn=st(Qe,Yt,nt[yn],vt);if(Gn===null){Yt===null&&(Yt=Dn);break}h&&Yt&&Gn.alternate===null&&p(Qe,Yt),Ge=L(Gn,Ge,yn),Vn===null?Zt=Gn:Vn.sibling=Gn,Vn=Gn,Yt=Dn}if(yn===nt.length)return _(Qe,Yt),Ln&&ac(Qe,yn),Zt;if(Yt===null){for(;ynyn?(Dn=Yt,Yt=null):Dn=Yt.sibling;var Gu=st(Qe,Yt,Gn.value,vt);if(Gu===null){Yt===null&&(Yt=Dn);break}h&&Yt&&Gu.alternate===null&&p(Qe,Yt),Ge=L(Gu,Ge,yn),Vn===null?Zt=Gu:Vn.sibling=Gu,Vn=Gu,Yt=Dn}if(Gn.done)return _(Qe,Yt),Ln&&ac(Qe,yn),Zt;if(Yt===null){for(;!Gn.done;yn++,Gn=nt.next())Gn=_t(Qe,Gn.value,vt),Gn!==null&&(Ge=L(Gn,Ge,yn),Vn===null?Zt=Gn:Vn.sibling=Gn,Vn=Gn);return Ln&&ac(Qe,yn),Zt}for(Yt=M(Yt);!Gn.done;yn++,Gn=nt.next())Gn=dt(Yt,Qe,yn,Gn.value,vt),Gn!==null&&(h&&Gn.alternate!==null&&Yt.delete(Gn.key===null?yn:Gn.key),Ge=L(Gn,Ge,yn),Vn===null?Zt=Gn:Vn.sibling=Gn,Vn=Gn);return h&&Yt.forEach(function(jae){return p(Qe,jae)}),Ln&&ac(Qe,yn),Zt}function lr(Qe,Ge,nt,vt){if(typeof nt=="object"&&nt!==null&&nt.type===y&&nt.key===null&&(nt=nt.props.children),typeof nt=="object"&&nt!==null){switch(nt.$$typeof){case g:e:{for(var Zt=nt.key;Ge!==null;){if(Ge.key===Zt){if(Zt=nt.type,Zt===y){if(Ge.tag===7){_(Qe,Ge.sibling),vt=P(Ge,nt.props.children),vt.return=Qe,Qe=vt;break e}}else if(Ge.elementType===Zt||typeof Zt=="object"&&Zt!==null&&Zt.$$typeof===D&&wh(Zt)===Ge.type){_(Qe,Ge.sibling),vt=P(Ge,nt.props),_x(vt,nt),vt.return=Qe,Qe=vt;break e}_(Qe,Ge);break}else p(Qe,Ge);Ge=Ge.sibling}nt.type===y?(vt=xh(nt.props.children,Qe.mode,vt,nt.key),vt.return=Qe,Qe=vt):(vt=tv(nt.type,nt.key,nt.props,null,Qe.mode,vt),_x(vt,nt),vt.return=Qe,Qe=vt)}return J(Qe);case x:e:{for(Zt=nt.key;Ge!==null;){if(Ge.key===Zt)if(Ge.tag===4&&Ge.stateNode.containerInfo===nt.containerInfo&&Ge.stateNode.implementation===nt.implementation){_(Qe,Ge.sibling),vt=P(Ge,nt.children||[]),vt.return=Qe,Qe=vt;break e}else{_(Qe,Ge);break}else p(Qe,Ge);Ge=Ge.sibling}vt=UM(nt,Qe.mode,vt),vt.return=Qe,Qe=vt}return J(Qe);case D:return nt=wh(nt),lr(Qe,Ge,nt,vt)}if(q(nt))return qt(Qe,Ge,nt,vt);if(O(nt)){if(Zt=O(nt),typeof Zt!="function")throw Error(r(150));return nt=Zt.call(nt),rn(Qe,Ge,nt,vt)}if(typeof nt.then=="function")return lr(Qe,Ge,lv(nt),vt);if(nt.$$typeof===S)return lr(Qe,Ge,iv(Qe,nt),vt);cv(Qe,nt)}return typeof nt=="string"&&nt!==""||typeof nt=="number"||typeof nt=="bigint"?(nt=""+nt,Ge!==null&&Ge.tag===6?(_(Qe,Ge.sibling),vt=P(Ge,nt),vt.return=Qe,Qe=vt):(_(Qe,Ge),vt=FM(nt,Qe.mode,vt),vt.return=Qe,Qe=vt),J(Qe)):_(Qe,Ge)}return function(Qe,Ge,nt,vt){try{vx=0;var Zt=lr(Qe,Ge,nt,vt);return _m=null,Zt}catch(Yt){if(Yt===vm||Yt===sv)throw Yt;var Vn=Cs(29,Yt,null,Qe.mode);return Vn.lanes=vt,Vn.return=Qe,Vn}finally{}}}var Nh=LI(!0),II=LI(!1),Au=!1;function QM(h){h.updateQueue={baseState:h.memoizedState,firstBaseUpdate:null,lastBaseUpdate:null,shared:{pending:null,lanes:0,hiddenCallbacks:null},callbacks:null}}function ZM(h,p){h=h.updateQueue,p.updateQueue===h&&(p.updateQueue={baseState:h.baseState,firstBaseUpdate:h.firstBaseUpdate,lastBaseUpdate:h.lastBaseUpdate,shared:h.shared,callbacks:null})}function Cu(h){return{lane:h,tag:0,payload:null,callback:null,next:null}}function ku(h,p,_){var M=h.updateQueue;if(M===null)return null;if(M=M.shared,(Xn&2)!==0){var P=M.pending;return P===null?p.next=p:(p.next=P.next,P.next=p),M.pending=p,p=ev(h),vI(h,null,_),p}return Jy(h,M,p,_),ev(h)}function wx(h,p,_){if(p=p.updateQueue,p!==null&&(p=p.shared,(_&4194048)!==0)){var M=p.lanes;M&=h.pendingLanes,_|=M,p.lanes=_,on(h,_)}}function JM(h,p){var _=h.updateQueue,M=h.alternate;if(M!==null&&(M=M.updateQueue,_===M)){var P=null,L=null;if(_=_.firstBaseUpdate,_!==null){do{var J={lane:_.lane,tag:_.tag,payload:_.payload,callback:null,next:null};L===null?P=L=J:L=L.next=J,_=_.next}while(_!==null);L===null?P=L=p:L=L.next=p}else P=L=p;_={baseState:M.baseState,firstBaseUpdate:P,lastBaseUpdate:L,shared:M.shared,callbacks:M.callbacks},h.updateQueue=_;return}h=_.lastBaseUpdate,h===null?_.firstBaseUpdate=p:h.next=p,_.lastBaseUpdate=p}var eE=!1;function Sx(){if(eE){var h=ym;if(h!==null)throw h}}function Nx(h,p,_,M){eE=!1;var P=h.updateQueue;Au=!1;var L=P.firstBaseUpdate,J=P.lastBaseUpdate,ge=P.shared.pending;if(ge!==null){P.shared.pending=null;var Le=ge,rt=Le.next;Le.next=null,J===null?L=rt:J.next=rt,J=Le;var xt=h.alternate;xt!==null&&(xt=xt.updateQueue,ge=xt.lastBaseUpdate,ge!==J&&(ge===null?xt.firstBaseUpdate=rt:ge.next=rt,xt.lastBaseUpdate=Le))}if(L!==null){var _t=P.baseState;J=0,xt=rt=Le=null,ge=L;do{var st=ge.lane&-536870913,dt=st!==ge.lane;if(dt?(Rn&st)===st:(M&st)===st){st!==0&&st===bm&&(eE=!0),xt!==null&&(xt=xt.next={lane:0,tag:ge.tag,payload:ge.payload,callback:null,next:null});e:{var qt=h,rn=ge;st=p;var lr=_;switch(rn.tag){case 1:if(qt=rn.payload,typeof qt=="function"){_t=qt.call(lr,_t,st);break e}_t=qt;break e;case 3:qt.flags=qt.flags&-65537|128;case 0:if(qt=rn.payload,st=typeof qt=="function"?qt.call(lr,_t,st):qt,st==null)break e;_t=f({},_t,st);break e;case 2:Au=!0}}st=ge.callback,st!==null&&(h.flags|=64,dt&&(h.flags|=8192),dt=P.callbacks,dt===null?P.callbacks=[st]:dt.push(st))}else dt={lane:st,tag:ge.tag,payload:ge.payload,callback:ge.callback,next:null},xt===null?(rt=xt=dt,Le=_t):xt=xt.next=dt,J|=st;if(ge=ge.next,ge===null){if(ge=P.shared.pending,ge===null)break;dt=ge,ge=dt.next,dt.next=null,P.lastBaseUpdate=dt,P.shared.pending=null}}while(!0);xt===null&&(Le=_t),P.baseState=Le,P.firstBaseUpdate=rt,P.lastBaseUpdate=xt,L===null&&(P.shared.lanes=0),Ou|=J,h.lanes=J,h.memoizedState=_t}}function FI(h,p){if(typeof h!="function")throw Error(r(191,h));h.call(p)}function UI(h,p){var _=h.callbacks;if(_!==null)for(h.callbacks=null,h=0;h<_.length;h++)FI(_[h],p)}var wm=B(null),uv=B(0);function zI(h,p){h=gc,ae(uv,h),ae(wm,p),gc=h|p.baseLanes}function tE(){ae(uv,gc),ae(wm,wm.current)}function nE(){gc=uv.current,G(wm),G(uv)}var ks=B(null),ao=null;function ju(h){var p=h.alternate;ae(oi,oi.current&1),ae(ks,h),ao===null&&(p===null||wm.current!==null||p.memoizedState!==null)&&(ao=h)}function rE(h){ae(oi,oi.current),ae(ks,h),ao===null&&(ao=h)}function BI(h){h.tag===22?(ae(oi,oi.current),ae(ks,h),ao===null&&(ao=h)):Pu()}function Pu(){ae(oi,oi.current),ae(ks,ks.current)}function js(h){G(ks),ao===h&&(ao=null),G(oi)}var oi=B(0);function dv(h){for(var p=h;p!==null;){if(p.tag===13){var _=p.memoizedState;if(_!==null&&(_=_.dehydrated,_===null||cT(_)||uT(_)))return p}else if(p.tag===19&&(p.memoizedProps.revealOrder==="forwards"||p.memoizedProps.revealOrder==="backwards"||p.memoizedProps.revealOrder==="unstable_legacy-backwards"||p.memoizedProps.revealOrder==="together")){if((p.flags&128)!==0)return p}else if(p.child!==null){p.child.return=p,p=p.child;continue}if(p===h)break;for(;p.sibling===null;){if(p.return===null||p.return===h)return null;p=p.return}p.sibling.return=p.return,p=p.sibling}return null}var lc=0,bn=null,sr=null,pi=null,hv=!1,Sm=!1,Mh=!1,fv=0,Mx=0,Nm=null,_ie=0;function Qr(){throw Error(r(321))}function iE(h,p){if(p===null)return!1;for(var _=0;_L?L:8;var J=Y.T,ge={};Y.T=ge,yE(h,!1,p,_);try{var Le=P(),rt=Y.S;if(rt!==null&&rt(ge,Le),Le!==null&&typeof Le=="object"&&typeof Le.then=="function"){var xt=vie(Le,M);Tx(h,p,xt,Ds(h))}else Tx(h,p,M,Ds(h))}catch(_t){Tx(h,p,{then:function(){},status:"rejected",reason:_t},Ds())}finally{W.p=L,J!==null&&ge.types!==null&&(J.types=ge.types),Y.T=J}}function Eie(){}function xE(h,p,_,M){if(h.tag!==5)throw Error(r(476));var P=x5(h).queue;g5(h,P,p,Z,_===null?Eie:function(){return b5(h),_(M)})}function x5(h){var p=h.memoizedState;if(p!==null)return p;p={memoizedState:Z,baseState:Z,baseQueue:null,queue:{pending:null,lanes:0,dispatch:null,lastRenderedReducer:cc,lastRenderedState:Z},next:null};var _={};return p.next={memoizedState:_,baseState:_,baseQueue:null,queue:{pending:null,lanes:0,dispatch:null,lastRenderedReducer:cc,lastRenderedState:_},next:null},h.memoizedState=p,h=h.alternate,h!==null&&(h.memoizedState=p),p}function b5(h){var p=x5(h);p.next===null&&(p=h.alternate.memoizedState),Tx(h,p.next.queue,{},Ds())}function bE(){return la(Vx)}function y5(){return li().memoizedState}function v5(){return li().memoizedState}function Tie(h){for(var p=h.return;p!==null;){switch(p.tag){case 24:case 3:var _=Ds();h=Cu(_);var M=ku(p,h,_);M!==null&&(us(M,p,_),wx(M,p,_)),p={cache:WM()},h.payload=p;return}p=p.return}}function Aie(h,p,_){var M=Ds();_={lane:M,revertLane:0,gesture:null,action:_,hasEagerState:!1,eagerState:null,next:null},yv(h)?w5(p,_):(_=LM(h,p,_,M),_!==null&&(us(_,h,M),S5(_,p,M)))}function _5(h,p,_){var M=Ds();Tx(h,p,_,M)}function Tx(h,p,_,M){var P={lane:M,revertLane:0,gesture:null,action:_,hasEagerState:!1,eagerState:null,next:null};if(yv(h))w5(p,P);else{var L=h.alternate;if(h.lanes===0&&(L===null||L.lanes===0)&&(L=p.lastRenderedReducer,L!==null))try{var J=p.lastRenderedState,ge=L(J,_);if(P.hasEagerState=!0,P.eagerState=ge,As(ge,J))return Jy(h,p,P,0),vr===null&&Zy(),!1}catch{}finally{}if(_=LM(h,p,P,M),_!==null)return us(_,h,M),S5(_,p,M),!0}return!1}function yE(h,p,_,M){if(M={lane:2,revertLane:QE(),gesture:null,action:M,hasEagerState:!1,eagerState:null,next:null},yv(h)){if(p)throw Error(r(479))}else p=LM(h,_,M,2),p!==null&&us(p,h,2)}function yv(h){var p=h.alternate;return h===bn||p!==null&&p===bn}function w5(h,p){Sm=hv=!0;var _=h.pending;_===null?p.next=p:(p.next=_.next,_.next=p),h.pending=p}function S5(h,p,_){if((_&4194048)!==0){var M=p.lanes;M&=h.pendingLanes,_|=M,p.lanes=_,on(h,_)}}var Ax={readContext:la,use:pv,useCallback:Qr,useContext:Qr,useEffect:Qr,useImperativeHandle:Qr,useLayoutEffect:Qr,useInsertionEffect:Qr,useMemo:Qr,useReducer:Qr,useRef:Qr,useState:Qr,useDebugValue:Qr,useDeferredValue:Qr,useTransition:Qr,useSyncExternalStore:Qr,useId:Qr,useHostTransitionStatus:Qr,useFormState:Qr,useActionState:Qr,useOptimistic:Qr,useMemoCache:Qr,useCacheRefresh:Qr};Ax.useEffectEvent=Qr;var N5={readContext:la,use:pv,useCallback:function(h,p){return za().memoizedState=[h,p===void 0?null:p],h},useContext:la,useEffect:o5,useImperativeHandle:function(h,p,_){_=_!=null?_.concat([h]):null,xv(4194308,4,d5.bind(null,p,h),_)},useLayoutEffect:function(h,p){return xv(4194308,4,h,p)},useInsertionEffect:function(h,p){xv(4,2,h,p)},useMemo:function(h,p){var _=za();p=p===void 0?null:p;var M=h();if(Mh){ie(!0);try{h()}finally{ie(!1)}}return _.memoizedState=[M,p],M},useReducer:function(h,p,_){var M=za();if(_!==void 0){var P=_(p);if(Mh){ie(!0);try{_(p)}finally{ie(!1)}}}else P=p;return M.memoizedState=M.baseState=P,h={pending:null,lanes:0,dispatch:null,lastRenderedReducer:h,lastRenderedState:P},M.queue=h,h=h.dispatch=Aie.bind(null,bn,h),[M.memoizedState,h]},useRef:function(h){var p=za();return h={current:h},p.memoizedState=h},useState:function(h){h=hE(h);var p=h.queue,_=_5.bind(null,bn,p);return p.dispatch=_,[h.memoizedState,_]},useDebugValue:pE,useDeferredValue:function(h,p){var _=za();return gE(_,h,p)},useTransition:function(){var h=hE(!1);return h=g5.bind(null,bn,h.queue,!0,!1),za().memoizedState=h,[!1,h]},useSyncExternalStore:function(h,p,_){var M=bn,P=za();if(Ln){if(_===void 0)throw Error(r(407));_=_()}else{if(_=p(),vr===null)throw Error(r(349));(Rn&127)!==0||GI(M,p,_)}P.memoizedState=_;var L={value:_,getSnapshot:p};return P.queue=L,o5(WI.bind(null,M,L,h),[h]),M.flags|=2048,Mm(9,{destroy:void 0},qI.bind(null,M,L,_,p),null),_},useId:function(){var h=za(),p=vr.identifierPrefix;if(Ln){var _=bl,M=xl;_=(M&~(1<<32-Me(M)-1)).toString(32)+_,p="_"+p+"R_"+_,_=fv++,0<_&&(p+="H"+_.toString(32)),p+="_"}else _=_ie++,p="_"+p+"r_"+_.toString(32)+"_";return h.memoizedState=p},useHostTransitionStatus:bE,useFormState:n5,useActionState:n5,useOptimistic:function(h){var p=za();p.memoizedState=p.baseState=h;var _={pending:null,lanes:0,dispatch:null,lastRenderedReducer:null,lastRenderedState:null};return p.queue=_,p=yE.bind(null,bn,!0,_),_.dispatch=p,[h,p]},useMemoCache:cE,useCacheRefresh:function(){return za().memoizedState=Tie.bind(null,bn)},useEffectEvent:function(h){var p=za(),_={impl:h};return p.memoizedState=_,function(){if((Xn&2)!==0)throw Error(r(440));return _.impl.apply(void 0,arguments)}}},vE={readContext:la,use:pv,useCallback:f5,useContext:la,useEffect:mE,useImperativeHandle:h5,useInsertionEffect:c5,useLayoutEffect:u5,useMemo:m5,useReducer:gv,useRef:s5,useState:function(){return gv(cc)},useDebugValue:pE,useDeferredValue:function(h,p){var _=li();return p5(_,sr.memoizedState,h,p)},useTransition:function(){var h=gv(cc)[0],p=li().memoizedState;return[typeof h=="boolean"?h:Ex(h),p]},useSyncExternalStore:VI,useId:y5,useHostTransitionStatus:bE,useFormState:r5,useActionState:r5,useOptimistic:function(h,p){var _=li();return YI(_,sr,h,p)},useMemoCache:cE,useCacheRefresh:v5};vE.useEffectEvent=l5;var M5={readContext:la,use:pv,useCallback:f5,useContext:la,useEffect:mE,useImperativeHandle:h5,useInsertionEffect:c5,useLayoutEffect:u5,useMemo:m5,useReducer:dE,useRef:s5,useState:function(){return dE(cc)},useDebugValue:pE,useDeferredValue:function(h,p){var _=li();return sr===null?gE(_,h,p):p5(_,sr.memoizedState,h,p)},useTransition:function(){var h=dE(cc)[0],p=li().memoizedState;return[typeof h=="boolean"?h:Ex(h),p]},useSyncExternalStore:VI,useId:y5,useHostTransitionStatus:bE,useFormState:a5,useActionState:a5,useOptimistic:function(h,p){var _=li();return sr!==null?YI(_,sr,h,p):(_.baseState=h,[h,_.queue.dispatch])},useMemoCache:cE,useCacheRefresh:v5};M5.useEffectEvent=l5;function _E(h,p,_,M){p=h.memoizedState,_=_(M,p),_=_==null?p:f({},p,_),h.memoizedState=_,h.lanes===0&&(h.updateQueue.baseState=_)}var wE={enqueueSetState:function(h,p,_){h=h._reactInternals;var M=Ds(),P=Cu(M);P.payload=p,_!=null&&(P.callback=_),p=ku(h,P,M),p!==null&&(us(p,h,M),wx(p,h,M))},enqueueReplaceState:function(h,p,_){h=h._reactInternals;var M=Ds(),P=Cu(M);P.tag=1,P.payload=p,_!=null&&(P.callback=_),p=ku(h,P,M),p!==null&&(us(p,h,M),wx(p,h,M))},enqueueForceUpdate:function(h,p){h=h._reactInternals;var _=Ds(),M=Cu(_);M.tag=2,p!=null&&(M.callback=p),p=ku(h,M,_),p!==null&&(us(p,h,_),wx(p,h,_))}};function E5(h,p,_,M,P,L,J){return h=h.stateNode,typeof h.shouldComponentUpdate=="function"?h.shouldComponentUpdate(M,L,J):p.prototype&&p.prototype.isPureReactComponent?!mx(_,M)||!mx(P,L):!0}function T5(h,p,_,M){h=p.state,typeof p.componentWillReceiveProps=="function"&&p.componentWillReceiveProps(_,M),typeof p.UNSAFE_componentWillReceiveProps=="function"&&p.UNSAFE_componentWillReceiveProps(_,M),p.state!==h&&wE.enqueueReplaceState(p,p.state,null)}function Eh(h,p){var _=p;if("ref"in p){_={};for(var M in p)M!=="ref"&&(_[M]=p[M])}if(h=h.defaultProps){_===p&&(_=f({},_));for(var P in h)_[P]===void 0&&(_[P]=h[P])}return _}function A5(h){Qy(h)}function C5(h){console.error(h)}function k5(h){Qy(h)}function vv(h,p){try{var _=h.onUncaughtError;_(p.value,{componentStack:p.stack})}catch(M){setTimeout(function(){throw M})}}function j5(h,p,_){try{var M=h.onCaughtError;M(_.value,{componentStack:_.stack,errorBoundary:p.tag===1?p.stateNode:null})}catch(P){setTimeout(function(){throw P})}}function SE(h,p,_){return _=Cu(_),_.tag=3,_.payload={element:null},_.callback=function(){vv(h,p)},_}function P5(h){return h=Cu(h),h.tag=3,h}function R5(h,p,_,M){var P=_.type.getDerivedStateFromError;if(typeof P=="function"){var L=M.value;h.payload=function(){return P(L)},h.callback=function(){j5(p,_,M)}}var J=_.stateNode;J!==null&&typeof J.componentDidCatch=="function"&&(h.callback=function(){j5(p,_,M),typeof P!="function"&&(Lu===null?Lu=new Set([this]):Lu.add(this));var ge=M.stack;this.componentDidCatch(M.value,{componentStack:ge!==null?ge:""})})}function Cie(h,p,_,M,P){if(_.flags|=32768,M!==null&&typeof M=="object"&&typeof M.then=="function"){if(p=_.alternate,p!==null&&xm(p,_,P,!0),_=ks.current,_!==null){switch(_.tag){case 31:case 13:return ao===null?Pv():_.alternate===null&&Zr===0&&(Zr=3),_.flags&=-257,_.flags|=65536,_.lanes=P,M===ov?_.flags|=16384:(p=_.updateQueue,p===null?_.updateQueue=new Set([M]):p.add(M),KE(h,M,P)),!1;case 22:return _.flags|=65536,M===ov?_.flags|=16384:(p=_.updateQueue,p===null?(p={transitions:null,markerInstances:null,retryQueue:new Set([M])},_.updateQueue=p):(_=p.retryQueue,_===null?p.retryQueue=new Set([M]):_.add(M)),KE(h,M,P)),!1}throw Error(r(435,_.tag))}return KE(h,M,P),Pv(),!1}if(Ln)return p=ks.current,p!==null?((p.flags&65536)===0&&(p.flags|=256),p.flags|=65536,p.lanes=P,M!==$M&&(h=Error(r(422),{cause:M}),xx(to(h,_)))):(M!==$M&&(p=Error(r(423),{cause:M}),xx(to(p,_))),h=h.current.alternate,h.flags|=65536,P&=-P,h.lanes|=P,M=to(M,_),P=SE(h.stateNode,M,P),JM(h,P),Zr!==4&&(Zr=2)),!1;var L=Error(r(520),{cause:M});if(L=to(L,_),Lx===null?Lx=[L]:Lx.push(L),Zr!==4&&(Zr=2),p===null)return!0;M=to(M,_),_=p;do{switch(_.tag){case 3:return _.flags|=65536,h=P&-P,_.lanes|=h,h=SE(_.stateNode,M,h),JM(_,h),!1;case 1:if(p=_.type,L=_.stateNode,(_.flags&128)===0&&(typeof p.getDerivedStateFromError=="function"||L!==null&&typeof L.componentDidCatch=="function"&&(Lu===null||!Lu.has(L))))return _.flags|=65536,P&=-P,_.lanes|=P,P=P5(P),R5(P,h,_,M),JM(_,P),!1}_=_.return}while(_!==null);return!1}var NE=Error(r(461)),gi=!1;function ca(h,p,_,M){p.child=h===null?II(p,null,_,M):Nh(p,h.child,_,M)}function D5(h,p,_,M,P){_=_.render;var L=p.ref;if("ref"in M){var J={};for(var ge in M)ge!=="ref"&&(J[ge]=M[ge])}else J=M;return vh(p),M=aE(h,p,_,J,L,P),ge=sE(),h!==null&&!gi?(oE(h,p,P),uc(h,p,P)):(Ln&&ge&&zM(p),p.flags|=1,ca(h,p,M,P),p.child)}function O5(h,p,_,M,P){if(h===null){var L=_.type;return typeof L=="function"&&!IM(L)&&L.defaultProps===void 0&&_.compare===null?(p.tag=15,p.type=L,L5(h,p,L,M,P)):(h=tv(_.type,null,M,p,p.mode,P),h.ref=p.ref,h.return=p,p.child=h)}if(L=h.child,!PE(h,P)){var J=L.memoizedProps;if(_=_.compare,_=_!==null?_:mx,_(J,M)&&h.ref===p.ref)return uc(h,p,P)}return p.flags|=1,h=ic(L,M),h.ref=p.ref,h.return=p,p.child=h}function L5(h,p,_,M,P){if(h!==null){var L=h.memoizedProps;if(mx(L,M)&&h.ref===p.ref)if(gi=!1,p.pendingProps=M=L,PE(h,P))(h.flags&131072)!==0&&(gi=!0);else return p.lanes=h.lanes,uc(h,p,P)}return ME(h,p,_,M,P)}function I5(h,p,_,M){var P=M.children,L=h!==null?h.memoizedState:null;if(h===null&&p.stateNode===null&&(p.stateNode={_visibility:1,_pendingMarkers:null,_retryCache:null,_transitions:null}),M.mode==="hidden"){if((p.flags&128)!==0){if(L=L!==null?L.baseLanes|_:_,h!==null){for(M=p.child=h.child,P=0;M!==null;)P=P|M.lanes|M.childLanes,M=M.sibling;M=P&~L}else M=0,p.child=null;return F5(h,p,L,_,M)}if((_&536870912)!==0)p.memoizedState={baseLanes:0,cachePool:null},h!==null&&av(p,L!==null?L.cachePool:null),L!==null?zI(p,L):tE(),BI(p);else return M=p.lanes=536870912,F5(h,p,L!==null?L.baseLanes|_:_,_,M)}else L!==null?(av(p,L.cachePool),zI(p,L),Pu(),p.memoizedState=null):(h!==null&&av(p,null),tE(),Pu());return ca(h,p,P,_),p.child}function Cx(h,p){return h!==null&&h.tag===22||p.stateNode!==null||(p.stateNode={_visibility:1,_pendingMarkers:null,_retryCache:null,_transitions:null}),p.sibling}function F5(h,p,_,M,P){var L=XM();return L=L===null?null:{parent:mi._currentValue,pool:L},p.memoizedState={baseLanes:_,cachePool:L},h!==null&&av(p,null),tE(),BI(p),h!==null&&xm(h,p,M,!0),p.childLanes=P,null}function _v(h,p){return p=Sv({mode:p.mode,children:p.children},h.mode),p.ref=h.ref,h.child=p,p.return=h,p}function U5(h,p,_){return Nh(p,h.child,null,_),h=_v(p,p.pendingProps),h.flags|=2,js(p),p.memoizedState=null,h}function kie(h,p,_){var M=p.pendingProps,P=(p.flags&128)!==0;if(p.flags&=-129,h===null){if(Ln){if(M.mode==="hidden")return h=_v(p,M),p.lanes=536870912,Cx(null,h);if(rE(p),(h=kr)?(h=QF(h,io),h=h!==null&&h.data==="&"?h:null,h!==null&&(p.memoizedState={dehydrated:h,treeContext:Nu!==null?{id:xl,overflow:bl}:null,retryLane:536870912,hydrationErrors:null},_=wI(h),_.return=p,p.child=_,oa=p,kr=null)):h=null,h===null)throw Eu(p);return p.lanes=536870912,null}return _v(p,M)}var L=h.memoizedState;if(L!==null){var J=L.dehydrated;if(rE(p),P)if(p.flags&256)p.flags&=-257,p=U5(h,p,_);else if(p.memoizedState!==null)p.child=h.child,p.flags|=128,p=null;else throw Error(r(558));else if(gi||xm(h,p,_,!1),P=(_&h.childLanes)!==0,gi||P){if(M=vr,M!==null&&(J=zn(M,_),J!==0&&J!==L.retryLane))throw L.retryLane=J,gh(h,J),us(M,h,J),NE;Pv(),p=U5(h,p,_)}else h=L.treeContext,kr=so(J.nextSibling),oa=p,Ln=!0,Mu=null,io=!1,h!==null&&MI(p,h),p=_v(p,M),p.flags|=4096;return p}return h=ic(h.child,{mode:M.mode,children:M.children}),h.ref=p.ref,p.child=h,h.return=p,h}function wv(h,p){var _=p.ref;if(_===null)h!==null&&h.ref!==null&&(p.flags|=4194816);else{if(typeof _!="function"&&typeof _!="object")throw Error(r(284));(h===null||h.ref!==_)&&(p.flags|=4194816)}}function ME(h,p,_,M,P){return vh(p),_=aE(h,p,_,M,void 0,P),M=sE(),h!==null&&!gi?(oE(h,p,P),uc(h,p,P)):(Ln&&M&&zM(p),p.flags|=1,ca(h,p,_,P),p.child)}function z5(h,p,_,M,P,L){return vh(p),p.updateQueue=null,_=HI(p,M,_,P),$I(h),M=sE(),h!==null&&!gi?(oE(h,p,L),uc(h,p,L)):(Ln&&M&&zM(p),p.flags|=1,ca(h,p,_,L),p.child)}function B5(h,p,_,M,P){if(vh(p),p.stateNode===null){var L=fm,J=_.contextType;typeof J=="object"&&J!==null&&(L=la(J)),L=new _(M,L),p.memoizedState=L.state!==null&&L.state!==void 0?L.state:null,L.updater=wE,p.stateNode=L,L._reactInternals=p,L=p.stateNode,L.props=M,L.state=p.memoizedState,L.refs={},QM(p),J=_.contextType,L.context=typeof J=="object"&&J!==null?la(J):fm,L.state=p.memoizedState,J=_.getDerivedStateFromProps,typeof J=="function"&&(_E(p,_,J,M),L.state=p.memoizedState),typeof _.getDerivedStateFromProps=="function"||typeof L.getSnapshotBeforeUpdate=="function"||typeof L.UNSAFE_componentWillMount!="function"&&typeof L.componentWillMount!="function"||(J=L.state,typeof L.componentWillMount=="function"&&L.componentWillMount(),typeof L.UNSAFE_componentWillMount=="function"&&L.UNSAFE_componentWillMount(),J!==L.state&&wE.enqueueReplaceState(L,L.state,null),Nx(p,M,L,P),Sx(),L.state=p.memoizedState),typeof L.componentDidMount=="function"&&(p.flags|=4194308),M=!0}else if(h===null){L=p.stateNode;var ge=p.memoizedProps,Le=Eh(_,ge);L.props=Le;var rt=L.context,xt=_.contextType;J=fm,typeof xt=="object"&&xt!==null&&(J=la(xt));var _t=_.getDerivedStateFromProps;xt=typeof _t=="function"||typeof L.getSnapshotBeforeUpdate=="function",ge=p.pendingProps!==ge,xt||typeof L.UNSAFE_componentWillReceiveProps!="function"&&typeof L.componentWillReceiveProps!="function"||(ge||rt!==J)&&T5(p,L,M,J),Au=!1;var st=p.memoizedState;L.state=st,Nx(p,M,L,P),Sx(),rt=p.memoizedState,ge||st!==rt||Au?(typeof _t=="function"&&(_E(p,_,_t,M),rt=p.memoizedState),(Le=Au||E5(p,_,Le,M,st,rt,J))?(xt||typeof L.UNSAFE_componentWillMount!="function"&&typeof L.componentWillMount!="function"||(typeof L.componentWillMount=="function"&&L.componentWillMount(),typeof L.UNSAFE_componentWillMount=="function"&&L.UNSAFE_componentWillMount()),typeof L.componentDidMount=="function"&&(p.flags|=4194308)):(typeof L.componentDidMount=="function"&&(p.flags|=4194308),p.memoizedProps=M,p.memoizedState=rt),L.props=M,L.state=rt,L.context=J,M=Le):(typeof L.componentDidMount=="function"&&(p.flags|=4194308),M=!1)}else{L=p.stateNode,ZM(h,p),J=p.memoizedProps,xt=Eh(_,J),L.props=xt,_t=p.pendingProps,st=L.context,rt=_.contextType,Le=fm,typeof rt=="object"&&rt!==null&&(Le=la(rt)),ge=_.getDerivedStateFromProps,(rt=typeof ge=="function"||typeof L.getSnapshotBeforeUpdate=="function")||typeof L.UNSAFE_componentWillReceiveProps!="function"&&typeof L.componentWillReceiveProps!="function"||(J!==_t||st!==Le)&&T5(p,L,M,Le),Au=!1,st=p.memoizedState,L.state=st,Nx(p,M,L,P),Sx();var dt=p.memoizedState;J!==_t||st!==dt||Au||h!==null&&h.dependencies!==null&&rv(h.dependencies)?(typeof ge=="function"&&(_E(p,_,ge,M),dt=p.memoizedState),(xt=Au||E5(p,_,xt,M,st,dt,Le)||h!==null&&h.dependencies!==null&&rv(h.dependencies))?(rt||typeof L.UNSAFE_componentWillUpdate!="function"&&typeof L.componentWillUpdate!="function"||(typeof L.componentWillUpdate=="function"&&L.componentWillUpdate(M,dt,Le),typeof L.UNSAFE_componentWillUpdate=="function"&&L.UNSAFE_componentWillUpdate(M,dt,Le)),typeof L.componentDidUpdate=="function"&&(p.flags|=4),typeof L.getSnapshotBeforeUpdate=="function"&&(p.flags|=1024)):(typeof L.componentDidUpdate!="function"||J===h.memoizedProps&&st===h.memoizedState||(p.flags|=4),typeof L.getSnapshotBeforeUpdate!="function"||J===h.memoizedProps&&st===h.memoizedState||(p.flags|=1024),p.memoizedProps=M,p.memoizedState=dt),L.props=M,L.state=dt,L.context=Le,M=xt):(typeof L.componentDidUpdate!="function"||J===h.memoizedProps&&st===h.memoizedState||(p.flags|=4),typeof L.getSnapshotBeforeUpdate!="function"||J===h.memoizedProps&&st===h.memoizedState||(p.flags|=1024),M=!1)}return L=M,wv(h,p),M=(p.flags&128)!==0,L||M?(L=p.stateNode,_=M&&typeof _.getDerivedStateFromError!="function"?null:L.render(),p.flags|=1,h!==null&&M?(p.child=Nh(p,h.child,null,P),p.child=Nh(p,null,_,P)):ca(h,p,_,P),p.memoizedState=L.state,h=p.child):h=uc(h,p,P),h}function $5(h,p,_,M){return bh(),p.flags|=256,ca(h,p,_,M),p.child}var EE={dehydrated:null,treeContext:null,retryLane:0,hydrationErrors:null};function TE(h){return{baseLanes:h,cachePool:jI()}}function AE(h,p,_){return h=h!==null?h.childLanes&~_:0,p&&(h|=Rs),h}function H5(h,p,_){var M=p.pendingProps,P=!1,L=(p.flags&128)!==0,J;if((J=L)||(J=h!==null&&h.memoizedState===null?!1:(oi.current&2)!==0),J&&(P=!0,p.flags&=-129),J=(p.flags&32)!==0,p.flags&=-33,h===null){if(Ln){if(P?ju(p):Pu(),(h=kr)?(h=QF(h,io),h=h!==null&&h.data!=="&"?h:null,h!==null&&(p.memoizedState={dehydrated:h,treeContext:Nu!==null?{id:xl,overflow:bl}:null,retryLane:536870912,hydrationErrors:null},_=wI(h),_.return=p,p.child=_,oa=p,kr=null)):h=null,h===null)throw Eu(p);return uT(h)?p.lanes=32:p.lanes=536870912,null}var ge=M.children;return M=M.fallback,P?(Pu(),P=p.mode,ge=Sv({mode:"hidden",children:ge},P),M=xh(M,P,_,null),ge.return=p,M.return=p,ge.sibling=M,p.child=ge,M=p.child,M.memoizedState=TE(_),M.childLanes=AE(h,J,_),p.memoizedState=EE,Cx(null,M)):(ju(p),CE(p,ge))}var Le=h.memoizedState;if(Le!==null&&(ge=Le.dehydrated,ge!==null)){if(L)p.flags&256?(ju(p),p.flags&=-257,p=kE(h,p,_)):p.memoizedState!==null?(Pu(),p.child=h.child,p.flags|=128,p=null):(Pu(),ge=M.fallback,P=p.mode,M=Sv({mode:"visible",children:M.children},P),ge=xh(ge,P,_,null),ge.flags|=2,M.return=p,ge.return=p,M.sibling=ge,p.child=M,Nh(p,h.child,null,_),M=p.child,M.memoizedState=TE(_),M.childLanes=AE(h,J,_),p.memoizedState=EE,p=Cx(null,M));else if(ju(p),uT(ge)){if(J=ge.nextSibling&&ge.nextSibling.dataset,J)var rt=J.dgst;J=rt,M=Error(r(419)),M.stack="",M.digest=J,xx({value:M,source:null,stack:null}),p=kE(h,p,_)}else if(gi||xm(h,p,_,!1),J=(_&h.childLanes)!==0,gi||J){if(J=vr,J!==null&&(M=zn(J,_),M!==0&&M!==Le.retryLane))throw Le.retryLane=M,gh(h,M),us(J,h,M),NE;cT(ge)||Pv(),p=kE(h,p,_)}else cT(ge)?(p.flags|=192,p.child=h.child,p=null):(h=Le.treeContext,kr=so(ge.nextSibling),oa=p,Ln=!0,Mu=null,io=!1,h!==null&&MI(p,h),p=CE(p,M.children),p.flags|=4096);return p}return P?(Pu(),ge=M.fallback,P=p.mode,Le=h.child,rt=Le.sibling,M=ic(Le,{mode:"hidden",children:M.children}),M.subtreeFlags=Le.subtreeFlags&65011712,rt!==null?ge=ic(rt,ge):(ge=xh(ge,P,_,null),ge.flags|=2),ge.return=p,M.return=p,M.sibling=ge,p.child=M,Cx(null,M),M=p.child,ge=h.child.memoizedState,ge===null?ge=TE(_):(P=ge.cachePool,P!==null?(Le=mi._currentValue,P=P.parent!==Le?{parent:Le,pool:Le}:P):P=jI(),ge={baseLanes:ge.baseLanes|_,cachePool:P}),M.memoizedState=ge,M.childLanes=AE(h,J,_),p.memoizedState=EE,Cx(h.child,M)):(ju(p),_=h.child,h=_.sibling,_=ic(_,{mode:"visible",children:M.children}),_.return=p,_.sibling=null,h!==null&&(J=p.deletions,J===null?(p.deletions=[h],p.flags|=16):J.push(h)),p.child=_,p.memoizedState=null,_)}function CE(h,p){return p=Sv({mode:"visible",children:p},h.mode),p.return=h,h.child=p}function Sv(h,p){return h=Cs(22,h,null,p),h.lanes=0,h}function kE(h,p,_){return Nh(p,h.child,null,_),h=CE(p,p.pendingProps.children),h.flags|=2,p.memoizedState=null,h}function V5(h,p,_){h.lanes|=p;var M=h.alternate;M!==null&&(M.lanes|=p),GM(h.return,p,_)}function jE(h,p,_,M,P,L){var J=h.memoizedState;J===null?h.memoizedState={isBackwards:p,rendering:null,renderingStartTime:0,last:M,tail:_,tailMode:P,treeForkCount:L}:(J.isBackwards=p,J.rendering=null,J.renderingStartTime=0,J.last=M,J.tail=_,J.tailMode=P,J.treeForkCount=L)}function G5(h,p,_){var M=p.pendingProps,P=M.revealOrder,L=M.tail;M=M.children;var J=oi.current,ge=(J&2)!==0;if(ge?(J=J&1|2,p.flags|=128):J&=1,ae(oi,J),ca(h,p,M,_),M=Ln?gx:0,!ge&&h!==null&&(h.flags&128)!==0)e:for(h=p.child;h!==null;){if(h.tag===13)h.memoizedState!==null&&V5(h,_,p);else if(h.tag===19)V5(h,_,p);else if(h.child!==null){h.child.return=h,h=h.child;continue}if(h===p)break e;for(;h.sibling===null;){if(h.return===null||h.return===p)break e;h=h.return}h.sibling.return=h.return,h=h.sibling}switch(P){case"forwards":for(_=p.child,P=null;_!==null;)h=_.alternate,h!==null&&dv(h)===null&&(P=_),_=_.sibling;_=P,_===null?(P=p.child,p.child=null):(P=_.sibling,_.sibling=null),jE(p,!1,P,_,L,M);break;case"backwards":case"unstable_legacy-backwards":for(_=null,P=p.child,p.child=null;P!==null;){if(h=P.alternate,h!==null&&dv(h)===null){p.child=P;break}h=P.sibling,P.sibling=_,_=P,P=h}jE(p,!0,_,null,L,M);break;case"together":jE(p,!1,null,null,void 0,M);break;default:p.memoizedState=null}return p.child}function uc(h,p,_){if(h!==null&&(p.dependencies=h.dependencies),Ou|=p.lanes,(_&p.childLanes)===0)if(h!==null){if(xm(h,p,_,!1),(_&p.childLanes)===0)return null}else return null;if(h!==null&&p.child!==h.child)throw Error(r(153));if(p.child!==null){for(h=p.child,_=ic(h,h.pendingProps),p.child=_,_.return=p;h.sibling!==null;)h=h.sibling,_=_.sibling=ic(h,h.pendingProps),_.return=p;_.sibling=null}return p.child}function PE(h,p){return(h.lanes&p)!==0?!0:(h=h.dependencies,!!(h!==null&&rv(h)))}function jie(h,p,_){switch(p.tag){case 3:Ee(p,p.stateNode.containerInfo),Tu(p,mi,h.memoizedState.cache),bh();break;case 27:case 5:Ne(p);break;case 4:Ee(p,p.stateNode.containerInfo);break;case 10:Tu(p,p.type,p.memoizedProps.value);break;case 31:if(p.memoizedState!==null)return p.flags|=128,rE(p),null;break;case 13:var M=p.memoizedState;if(M!==null)return M.dehydrated!==null?(ju(p),p.flags|=128,null):(_&p.child.childLanes)!==0?H5(h,p,_):(ju(p),h=uc(h,p,_),h!==null?h.sibling:null);ju(p);break;case 19:var P=(h.flags&128)!==0;if(M=(_&p.childLanes)!==0,M||(xm(h,p,_,!1),M=(_&p.childLanes)!==0),P){if(M)return G5(h,p,_);p.flags|=128}if(P=p.memoizedState,P!==null&&(P.rendering=null,P.tail=null,P.lastEffect=null),ae(oi,oi.current),M)break;return null;case 22:return p.lanes=0,I5(h,p,_,p.pendingProps);case 24:Tu(p,mi,h.memoizedState.cache)}return uc(h,p,_)}function q5(h,p,_){if(h!==null)if(h.memoizedProps!==p.pendingProps)gi=!0;else{if(!PE(h,_)&&(p.flags&128)===0)return gi=!1,jie(h,p,_);gi=(h.flags&131072)!==0}else gi=!1,Ln&&(p.flags&1048576)!==0&&NI(p,gx,p.index);switch(p.lanes=0,p.tag){case 16:e:{var M=p.pendingProps;if(h=wh(p.elementType),p.type=h,typeof h=="function")IM(h)?(M=Eh(h,M),p.tag=1,p=B5(null,p,h,M,_)):(p.tag=0,p=ME(null,p,h,M,_));else{if(h!=null){var P=h.$$typeof;if(P===A){p.tag=11,p=D5(null,p,h,M,_);break e}else if(P===E){p.tag=14,p=O5(null,p,h,M,_);break e}}throw p=j(h)||h,Error(r(306,p,""))}}return p;case 0:return ME(h,p,p.type,p.pendingProps,_);case 1:return M=p.type,P=Eh(M,p.pendingProps),B5(h,p,M,P,_);case 3:e:{if(Ee(p,p.stateNode.containerInfo),h===null)throw Error(r(387));M=p.pendingProps;var L=p.memoizedState;P=L.element,ZM(h,p),Nx(p,M,null,_);var J=p.memoizedState;if(M=J.cache,Tu(p,mi,M),M!==L.cache&&qM(p,[mi],_,!0),Sx(),M=J.element,L.isDehydrated)if(L={element:M,isDehydrated:!1,cache:J.cache},p.updateQueue.baseState=L,p.memoizedState=L,p.flags&256){p=$5(h,p,M,_);break e}else if(M!==P){P=to(Error(r(424)),p),xx(P),p=$5(h,p,M,_);break e}else{switch(h=p.stateNode.containerInfo,h.nodeType){case 9:h=h.body;break;default:h=h.nodeName==="HTML"?h.ownerDocument.body:h}for(kr=so(h.firstChild),oa=p,Ln=!0,Mu=null,io=!0,_=II(p,null,M,_),p.child=_;_;)_.flags=_.flags&-3|4096,_=_.sibling}else{if(bh(),M===P){p=uc(h,p,_);break e}ca(h,p,M,_)}p=p.child}return p;case 26:return wv(h,p),h===null?(_=rU(p.type,null,p.pendingProps,null))?p.memoizedState=_:Ln||(_=p.type,h=p.pendingProps,M=Uv(me.current).createElement(_),M[Vt]=p,M[br]=h,ua(M,_,h),$e(M),p.stateNode=M):p.memoizedState=rU(p.type,h.memoizedProps,p.pendingProps,h.memoizedState),null;case 27:return Ne(p),h===null&&Ln&&(M=p.stateNode=eU(p.type,p.pendingProps,me.current),oa=p,io=!0,P=kr,zu(p.type)?(dT=P,kr=so(M.firstChild)):kr=P),ca(h,p,p.pendingProps.children,_),wv(h,p),h===null&&(p.flags|=4194304),p.child;case 5:return h===null&&Ln&&((P=M=kr)&&(M=oae(M,p.type,p.pendingProps,io),M!==null?(p.stateNode=M,oa=p,kr=so(M.firstChild),io=!1,P=!0):P=!1),P||Eu(p)),Ne(p),P=p.type,L=p.pendingProps,J=h!==null?h.memoizedProps:null,M=L.children,sT(P,L)?M=null:J!==null&&sT(P,J)&&(p.flags|=32),p.memoizedState!==null&&(P=aE(h,p,wie,null,null,_),Vx._currentValue=P),wv(h,p),ca(h,p,M,_),p.child;case 6:return h===null&&Ln&&((h=_=kr)&&(_=lae(_,p.pendingProps,io),_!==null?(p.stateNode=_,oa=p,kr=null,h=!0):h=!1),h||Eu(p)),null;case 13:return H5(h,p,_);case 4:return Ee(p,p.stateNode.containerInfo),M=p.pendingProps,h===null?p.child=Nh(p,null,M,_):ca(h,p,M,_),p.child;case 11:return D5(h,p,p.type,p.pendingProps,_);case 7:return ca(h,p,p.pendingProps,_),p.child;case 8:return ca(h,p,p.pendingProps.children,_),p.child;case 12:return ca(h,p,p.pendingProps.children,_),p.child;case 10:return M=p.pendingProps,Tu(p,p.type,M.value),ca(h,p,M.children,_),p.child;case 9:return P=p.type._context,M=p.pendingProps.children,vh(p),P=la(P),M=M(P),p.flags|=1,ca(h,p,M,_),p.child;case 14:return O5(h,p,p.type,p.pendingProps,_);case 15:return L5(h,p,p.type,p.pendingProps,_);case 19:return G5(h,p,_);case 31:return kie(h,p,_);case 22:return I5(h,p,_,p.pendingProps);case 24:return vh(p),M=la(mi),h===null?(P=XM(),P===null&&(P=vr,L=WM(),P.pooledCache=L,L.refCount++,L!==null&&(P.pooledCacheLanes|=_),P=L),p.memoizedState={parent:M,cache:P},QM(p),Tu(p,mi,P)):((h.lanes&_)!==0&&(ZM(h,p),Nx(p,null,null,_),Sx()),P=h.memoizedState,L=p.memoizedState,P.parent!==M?(P={parent:M,cache:M},p.memoizedState=P,p.lanes===0&&(p.memoizedState=p.updateQueue.baseState=P),Tu(p,mi,M)):(M=L.cache,Tu(p,mi,M),M!==P.cache&&qM(p,[mi],_,!0))),ca(h,p,p.pendingProps.children,_),p.child;case 29:throw p.pendingProps}throw Error(r(156,p.tag))}function dc(h){h.flags|=4}function RE(h,p,_,M,P){if((p=(h.mode&32)!==0)&&(p=!1),p){if(h.flags|=16777216,(P&335544128)===P)if(h.stateNode.complete)h.flags|=8192;else if(yF())h.flags|=8192;else throw Sh=ov,YM}else h.flags&=-16777217}function W5(h,p){if(p.type!=="stylesheet"||(p.state.loading&4)!==0)h.flags&=-16777217;else if(h.flags|=16777216,!lU(p))if(yF())h.flags|=8192;else throw Sh=ov,YM}function Nv(h,p){p!==null&&(h.flags|=4),h.flags&16384&&(p=h.tag!==22?At():536870912,h.lanes|=p,Cm|=p)}function kx(h,p){if(!Ln)switch(h.tailMode){case"hidden":p=h.tail;for(var _=null;p!==null;)p.alternate!==null&&(_=p),p=p.sibling;_===null?h.tail=null:_.sibling=null;break;case"collapsed":_=h.tail;for(var M=null;_!==null;)_.alternate!==null&&(M=_),_=_.sibling;M===null?p||h.tail===null?h.tail=null:h.tail.sibling=null:M.sibling=null}}function jr(h){var p=h.alternate!==null&&h.alternate.child===h.child,_=0,M=0;if(p)for(var P=h.child;P!==null;)_|=P.lanes|P.childLanes,M|=P.subtreeFlags&65011712,M|=P.flags&65011712,P.return=h,P=P.sibling;else for(P=h.child;P!==null;)_|=P.lanes|P.childLanes,M|=P.subtreeFlags,M|=P.flags,P.return=h,P=P.sibling;return h.subtreeFlags|=M,h.childLanes=_,p}function Pie(h,p,_){var M=p.pendingProps;switch(BM(p),p.tag){case 16:case 15:case 0:case 11:case 7:case 8:case 12:case 9:case 14:return jr(p),null;case 1:return jr(p),null;case 3:return _=p.stateNode,M=null,h!==null&&(M=h.memoizedState.cache),p.memoizedState.cache!==M&&(p.flags|=2048),oc(mi),pe(),_.pendingContext&&(_.context=_.pendingContext,_.pendingContext=null),(h===null||h.child===null)&&(gm(p)?dc(p):h===null||h.memoizedState.isDehydrated&&(p.flags&256)===0||(p.flags|=1024,HM())),jr(p),null;case 26:var P=p.type,L=p.memoizedState;return h===null?(dc(p),L!==null?(jr(p),W5(p,L)):(jr(p),RE(p,P,null,M,_))):L?L!==h.memoizedState?(dc(p),jr(p),W5(p,L)):(jr(p),p.flags&=-16777217):(h=h.memoizedProps,h!==M&&dc(p),jr(p),RE(p,P,h,M,_)),null;case 27:if(_e(p),_=me.current,P=p.type,h!==null&&p.stateNode!=null)h.memoizedProps!==M&&dc(p);else{if(!M){if(p.stateNode===null)throw Error(r(166));return jr(p),null}h=se.current,gm(p)?EI(p):(h=eU(P,M,_),p.stateNode=h,dc(p))}return jr(p),null;case 5:if(_e(p),P=p.type,h!==null&&p.stateNode!=null)h.memoizedProps!==M&&dc(p);else{if(!M){if(p.stateNode===null)throw Error(r(166));return jr(p),null}if(L=se.current,gm(p))EI(p);else{var J=Uv(me.current);switch(L){case 1:L=J.createElementNS("http://www.w3.org/2000/svg",P);break;case 2:L=J.createElementNS("http://www.w3.org/1998/Math/MathML",P);break;default:switch(P){case"svg":L=J.createElementNS("http://www.w3.org/2000/svg",P);break;case"math":L=J.createElementNS("http://www.w3.org/1998/Math/MathML",P);break;case"script":L=J.createElement("div"),L.innerHTML=" +