diff --git a/.gitignore b/.gitignore index d6c64ad59..adbd2bbaa 100644 --- a/.gitignore +++ b/.gitignore @@ -28,6 +28,8 @@ npm-debug.log* # Database *.db *.db-journal +*.db-wal +*.db-shm # Archive files (user data) archive/ diff --git a/CHANGELOG.md b/CHANGELOG.md index 662701bc1..ff0a1f951 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -8,8 +8,15 @@ All notable changes to Bambuddy will be documented in this file. - **Spool Inventory — AMS Slot Assignment** — Assign inventory spools to AMS slots for filament tracking. Hover over any non-Bambu-Lab AMS slot to assign or unassign spools. The assign modal filters out Bambu Lab spools (tracked via RFID) and spools already assigned to other slots. Bambu Lab spool slots automatically hide assign/unassign UI since they are managed by the AMS. When a Bambu Lab spool is inserted into a slot with a manual assignment, the assignment is automatically unlinked. - **Spool Inventory — Remaining Weight Editing** — Edit the remaining filament weight when adding or editing a spool. The new "Remaining Weight" field in the Additional section shows current weight (label weight minus consumed) with a max reference. Edits are stored as `weight_used` internally. - **Spool Inventory — 3MF-Based Usage Tracking for Non-BL Spools** — Non-Bambu-Lab spools (no RFID) cannot use AMS remain% for usage tracking. Now falls back to per-filament weight estimates from the archived 3MF file (`used_g` per filament slot). For completed prints, uses the full slicer estimate. For failed or aborted prints, scales by print progress percentage. Bambu Lab spools continue using AMS remain% delta tracking as before. +- **Nozzle-Aware AMS Filament Mapping for Dual-Nozzle Printers** ([#318](https://github.com/maziggy/bambuddy/issues/318)) — On dual-nozzle printers (H2D, H2D Pro), each AMS unit is physically connected to either the left or right nozzle. Bambuddy now reads nozzle assignments from the 3MF file (`filament_nozzle_map` + `physical_extruder_map` in `project_settings.config`) and constrains filament matching to only AMS trays connected to the correct nozzle via `ams_extruder_map`. Applies to the print scheduler, reprint modal, queue modal, and multi-printer selection. Falls back gracefully to unfiltered matching when no trays exist on the target nozzle. The filament mapping UI shows L/R nozzle badges for dual-nozzle prints. Translated in all 4 locales (en, de, ja, it). ### Fixed +- **AMS Slot Configuration Overwritten on Startup** — Bambuddy was resetting AMS slot filament presets on every startup and reconnection. The `on_ams_change` callback unconditionally unlinked Bambu Lab spool assignments on each MQTT push-all response, then re-assigned them by sending `ams_filament_setting` without a `setting_id`, which cleared the printer's filament preset. Now compares spool RFID identifiers (`tray_uuid` / `tag_uid`) before unlinking — if the same spool is still in the slot, the assignment is preserved and no `ams_filament_setting` command is sent. +- **Bambu Lab Spool Detection False Positives** — The `is_bambu_lab_spool()` function (backend) and `isBambuLabSpool()` (frontend) incorrectly identified third-party spools as Bambu Lab spools when they used Bambu generic filament presets (e.g., "Generic PLA"). The `tray_info_idx` field (e.g., "GFA00") identifies the filament *type*, not the spool manufacturer — third-party spools using Bambu presets also have GF-prefixed values. Removed `tray_info_idx` from detection logic; now uses only hardware RFID identifiers (`tray_uuid` and `tag_uid`) which are physically embedded in genuine Bambu Lab spools. +- **FTP Disconnect Raises EOFError When Server Dies** — `BambuFTPClient.disconnect()` only caught `OSError` and `ftplib.Error`, but `quit()` raises `EOFError` when the server has closed the connection mid-session. `EOFError` is not a subclass of either, so it propagated to callers. Now caught alongside the other exception types for clean best-effort disconnect. + +### Improved +- **SQLite WAL Mode for Database Reliability** — Database now uses Write-Ahead Logging (WAL) mode with a 5-second busy timeout, reducing "database is locked" errors under concurrent access. WAL mode allows simultaneous reads during writes, improving responsiveness for multi-printer setups. Automatically enabled on startup. - **External Camera Not Used for Snapshot + Stream Dropping** ([#325](https://github.com/maziggy/bambuddy/issues/325)) — The snapshot endpoint (`/camera/snapshot`) always used the internal printer camera even when an external camera was configured. Now checks for external camera first, matching the existing stream endpoint behavior. Also fixed external MJPEG and RTSP streams silently dropping every ~60 seconds due to missing reconnect logic — the underlying stream generators exit on read timeout, and the caller now retries up to 3 times with a 2-second delay instead of ending the stream. - **H2C Nozzle Rack Text Unreadable on Light Filament Colors** ([#300](https://github.com/maziggy/bambuddy/issues/300)) — Nozzle rack slots use the loaded filament color as background, but white/light filaments made the white "0.4" text nearly invisible. Now uses a luminance check to switch to dark text on light backgrounds. - **File Downloads Show Generic Filenames** ([#334](https://github.com/maziggy/bambuddy/issues/334)) — Downloaded files with special characters in their names (spaces, umlauts, parentheses) were saved as generic `file_1`, `file_2` instead of the original filename. The `Content-Disposition` header parser now handles RFC 5987 percent-encoded filenames (`filename*=utf-8''...`) used by FastAPI for non-ASCII characters. Fix applied to all download endpoints (library files, archives, source files, F3D files, project exports, support bundles, printer files). diff --git a/README.md b/README.md index de9befc33..34729319c 100644 --- a/README.md +++ b/README.md @@ -72,7 +72,7 @@ Perfect for remote print farms, traveling makers, or accessing your home printer - Duplicate detection & full-text search - Photo attachments & failure analysis - Timelapse editor (trim, speed, music) -- Re-print to any connected printer with AMS mapping (auto-match or manual slot selection, multi-plate support) +- Re-print to any connected printer with AMS mapping (auto-match or manual slot selection, multi-plate support, nozzle-aware matching for dual-nozzle H2D/H2D Pro) - Plate thumbnail browsing for multi-plate archives (hover to navigate between plates) - Archive comparison (side-by-side diff) - Tag management (rename/delete across all archives) @@ -200,7 +200,7 @@ Perfect for remote print farms, traveling makers, or accessing your home printer -**Plus:** Configurable slicer (Bambu Studio / OrcaSlicer) • Customizable themes (style, background, accent) • Mobile responsive • Keyboard shortcuts • Multi-language (EN/DE) • Auto updates • Database backup/restore • System info dashboard +**Plus:** Configurable slicer (Bambu Studio / OrcaSlicer) • Customizable themes (style, background, accent) • Mobile responsive • Keyboard shortcuts • Multi-language (EN/DE/JA/IT) • Auto updates • Database backup/restore • System info dashboard --- diff --git a/backend/app/api/routes/archives.py b/backend/app/api/routes/archives.py index aba710cd4..c38687ace 100644 --- a/backend/app/api/routes/archives.py +++ b/backend/app/api/routes/archives.py @@ -21,6 +21,7 @@ from backend.app.models.filament import Filament from backend.app.models.user import User from backend.app.schemas.archive import ArchiveResponse, ArchiveStats, ArchiveUpdate, ReprintRequest from backend.app.services.archive import ArchiveService +from backend.app.utils.threemf_tools import extract_nozzle_mapping_from_3mf logger = logging.getLogger(__name__) @@ -2669,6 +2670,12 @@ async def get_filament_requirements( # Sort by slot ID filaments.sort(key=lambda x: x["slot_id"]) + # Enrich with nozzle mapping for dual-nozzle printers + nozzle_mapping = extract_nozzle_mapping_from_3mf(zf) + if nozzle_mapping: + for filament in filaments: + filament["nozzle_id"] = nozzle_mapping.get(filament["slot_id"]) + except Exception as e: logger.warning("Failed to parse filament requirements from archive %s: %s", archive_id, e) diff --git a/backend/app/api/routes/library.py b/backend/app/api/routes/library.py index f337bd142..6b65d2514 100644 --- a/backend/app/api/routes/library.py +++ b/backend/app/api/routes/library.py @@ -56,6 +56,7 @@ from backend.app.schemas.library import ( ) from backend.app.services.archive import ArchiveService, ThreeMFParser from backend.app.services.stl_thumbnail import generate_stl_thumbnail +from backend.app.utils.threemf_tools import extract_nozzle_mapping_from_3mf logger = logging.getLogger(__name__) @@ -1711,6 +1712,12 @@ async def get_library_file_filament_requirements( # Sort by slot ID filaments.sort(key=lambda x: x["slot_id"]) + # Enrich with nozzle mapping for dual-nozzle printers + nozzle_mapping = extract_nozzle_mapping_from_3mf(zf) + if nozzle_mapping: + for filament in filaments: + filament["nozzle_id"] = nozzle_mapping.get(filament["slot_id"]) + except Exception as e: logger.warning("Failed to parse filament requirements from library file %s: %s", file_id, e) diff --git a/backend/app/core/database.py b/backend/app/core/database.py index 065796e3d..89d750ff3 100644 --- a/backend/app/core/database.py +++ b/backend/app/core/database.py @@ -1,14 +1,30 @@ +from sqlalchemy import event from sqlalchemy.exc import OperationalError from sqlalchemy.ext.asyncio import AsyncSession, async_sessionmaker, create_async_engine from sqlalchemy.orm import DeclarativeBase from backend.app.core.config import settings + +def _set_sqlite_pragmas(dbapi_conn, connection_record): + """Set SQLite pragmas on each new connection for concurrency and performance.""" + cursor = dbapi_conn.cursor() + # WAL mode allows concurrent readers + one writer (vs default DELETE mode which locks entirely) + cursor.execute("PRAGMA journal_mode = WAL") + # Wait up to 5 seconds when the database is locked instead of failing immediately + cursor.execute("PRAGMA busy_timeout = 5000") + cursor.execute("PRAGMA synchronous = NORMAL") + cursor.close() + + engine = create_async_engine( settings.database_url, echo=settings.debug, ) +# Register the pragma listener on the underlying sync engine +event.listen(engine.sync_engine, "connect", _set_sqlite_pragmas) + async_session = async_sessionmaker( engine, class_=AsyncSession, @@ -29,6 +45,7 @@ async def reinitialize_database(): settings.database_url, echo=settings.debug, ) + event.listen(engine.sync_engine, "connect", _set_sqlite_pragmas) async_session = async_sessionmaker( engine, class_=AsyncSession, diff --git a/backend/app/main.py b/backend/app/main.py index d55686522..e1ba442cf 100644 --- a/backend/app/main.py +++ b/backend/app/main.py @@ -551,13 +551,45 @@ async def on_ams_change(printer_id: int, ams_data: list): ) stale.append(assignment) # Slot empty elif _is_bambu_uuid(current_tray.get("tray_uuid", "")): - # A Bambu Lab spool was inserted — always unlink manual assignments + # A Bambu Lab spool is in this slot — check if it's the same spool + # that's currently assigned. If yes, keep the assignment (avoids + # unnecessary unlink/re-assign/ams_filament_setting cycle that clears + # the printer's filament preset on every startup). + tray_uuid = current_tray.get("tray_uuid", "") + tag_uid = current_tray.get("tag_uid", "") + spool = assignment.spool + spool_matches = False + if spool: + if (spool.tray_uuid and spool.tray_uuid.upper() == tray_uuid.upper()) or ( + spool.tag_uid + and tag_uid + and tag_uid != "0000000000000000" + and spool.tag_uid.upper() == tag_uid.upper() + ): + spool_matches = True + if spool_matches: + # Same BL spool still in slot — keep assignment, update fingerprint if needed + cur_color = current_tray.get("tray_color", "") + cur_type = current_tray.get("tray_type", "") + fp_color = assignment.fingerprint_color or "" + fp_type = assignment.fingerprint_type or "" + if cur_color.upper() != fp_color.upper() or cur_type.upper() != fp_type.upper(): + assignment.fingerprint_color = cur_color + assignment.fingerprint_type = cur_type + logger.debug( + "Auto-unlink: spool %d AMS%d-T%d — same BL spool, updated fingerprint", + assignment.spool_id, + assignment.ams_id, + assignment.tray_id, + ) + continue + # Different BL spool or unrecognized — unlink so auto-assign can match logger.info( - "Auto-unlink: spool %d AMS%d-T%d — Bambu Lab spool detected (uuid=%s)", + "Auto-unlink: spool %d AMS%d-T%d — different Bambu Lab spool detected (uuid=%s)", assignment.spool_id, assignment.ams_id, assignment.tray_id, - current_tray.get("tray_uuid", ""), + tray_uuid, ) stale.append(assignment) else: diff --git a/backend/app/services/bambu_ftp.py b/backend/app/services/bambu_ftp.py index 4030a2082..5dfe559f9 100644 --- a/backend/app/services/bambu_ftp.py +++ b/backend/app/services/bambu_ftp.py @@ -181,7 +181,7 @@ class BambuFTPClient: if self._ftp: try: self._ftp.quit() - except (OSError, ftplib.Error): + except (OSError, ftplib.Error, EOFError): pass # Best-effort FTP cleanup; connection may already be closed self._ftp = None diff --git a/backend/app/services/print_scheduler.py b/backend/app/services/print_scheduler.py index edc60c7e4..7c28ebdcb 100644 --- a/backend/app/services/print_scheduler.py +++ b/backend/app/services/print_scheduler.py @@ -23,6 +23,7 @@ from backend.app.services.notification_service import notification_service from backend.app.services.printer_manager import printer_manager from backend.app.services.smart_plug_manager import smart_plug_manager from backend.app.utils.printer_models import normalize_printer_model +from backend.app.utils.threemf_tools import extract_nozzle_mapping_from_3mf logger = logging.getLogger(__name__) @@ -477,6 +478,12 @@ class PrintScheduler: pass # Skip filament entry with unparseable usage data filaments.sort(key=lambda x: x["slot_id"]) + + # Enrich with nozzle mapping for dual-nozzle printers + nozzle_mapping = extract_nozzle_mapping_from_3mf(zf) + if nozzle_mapping: + for filament in filaments: + filament["nozzle_id"] = nozzle_mapping.get(filament["slot_id"]) except Exception as e: logger.warning("Failed to parse filament requirements: %s", e) return None @@ -494,6 +501,9 @@ class PrintScheduler: """ filaments = [] + # Get ams_extruder_map for dual-nozzle printers (H2D, H2D Pro) + ams_extruder_map = status.raw_data.get("ams_extruder_map", {}) + # Parse AMS units from raw_data ams_data = status.raw_data.get("ams", []) for ams_unit in ams_data: @@ -524,6 +534,7 @@ class PrintScheduler: "is_ht": is_ht, "is_external": False, "global_tray_id": global_tray_id, + "extruder_id": ams_extruder_map.get(str(ams_id)), } ) @@ -541,6 +552,7 @@ class PrintScheduler: "is_ht": False, "is_external": True, "global_tray_id": 254, + "extruder_id": 0 if ams_extruder_map else None, } ) @@ -616,6 +628,13 @@ class PrintScheduler: # Get available trays (not already used) available = [f for f in loaded if f["global_tray_id"] not in used_tray_ids] + # Nozzle-aware filtering: restrict to trays on the correct nozzle + req_nozzle_id = req.get("nozzle_id") + if req_nozzle_id is not None: + nozzle_filtered = [f for f in available if f.get("extruder_id") == req_nozzle_id] + if nozzle_filtered: + available = nozzle_filtered + # Check if tray_info_idx is unique among available trays if req_tray_info_idx: idx_matches = [f for f in available if f.get("tray_info_idx") == req_tray_info_idx] diff --git a/backend/app/services/spoolman.py b/backend/app/services/spoolman.py index b7f54a8bf..b47ce0aba 100644 --- a/backend/app/services/spoolman.py +++ b/backend/app/services/spoolman.py @@ -693,31 +693,22 @@ class SpoolmanClient: def is_bambu_lab_spool(self, tray_uuid: str, tag_uid: str = "", tray_info_idx: str = "") -> bool: """Check if a tray has a valid Bambu Lab spool. - Bambu Lab spools can be identified by: + Bambu Lab spools are identified by hardware RFID identifiers only: 1. tray_uuid: 32-character hex string (preferred, consistent across printers) 2. tag_uid: 16-character hex string (RFID tag, varies between readers) - 3. tray_info_idx: Bambu filament preset ID like "GFA00" (most reliable) - Non-Bambu Lab spools (SpoolEase, third-party) won't have these identifiers. + Note: tray_info_idx (e.g. "GFA00") is NOT a reliable indicator — third-party + spools using Bambu generic presets also have GF-prefixed tray_info_idx values. + The tray_info_idx parameter is kept for API compatibility but ignored. Args: tray_uuid: The tray UUID to check (32 hex chars) tag_uid: The RFID tag UID to check as fallback (16 hex chars) - tray_info_idx: Bambu filament preset ID like "GFA00", "GFB00" + tray_info_idx: Ignored (kept for API compatibility) Returns: - True if the spool has valid Bambu Lab identifiers, False otherwise. + True if the spool has valid Bambu Lab RFID identifiers, False otherwise. """ - # Check tray_info_idx first - Bambu filament preset IDs like "GFA00", "GFB00", etc. - # This is the most reliable indicator as it's set when the spool is recognized - if tray_info_idx: - idx = tray_info_idx.strip() - # Bambu Lab preset IDs start with "GF" followed by letter and digits - # e.g., GFA00, GFB00, GFL00, GFN00, GFG00, GFS00, GFU00 - if idx and len(idx) >= 3 and idx.startswith("GF"): - logger.debug("Identified Bambu Lab spool via tray_info_idx: %s", idx) - return True - # Check tray_uuid (preferred - consistent across printer models) if tray_uuid: uuid = tray_uuid.strip() diff --git a/backend/app/utils/threemf_tools.py b/backend/app/utils/threemf_tools.py index e2c6f019b..c06c7a896 100644 --- a/backend/app/utils/threemf_tools.py +++ b/backend/app/utils/threemf_tools.py @@ -264,6 +264,62 @@ def extract_filament_properties_from_3mf(file_path: Path) -> dict[int, dict]: return properties +def extract_nozzle_mapping_from_3mf(zf: zipfile.ZipFile) -> dict[int, int] | None: + """Extract per-slot nozzle/extruder mapping from a 3MF file's project settings. + + On dual-nozzle printers (H2D, H2D Pro), each filament slot is assigned to a + specific nozzle. This reads the slicer's nozzle assignment from + Metadata/project_settings.config. + + Translation chain: + filament_nozzle_map[slot_id - 1] -> slicer extruder index + physical_extruder_map[slicer_ext] -> MQTT extruder ID (0=right, 1=left) + + Args: + zf: An open ZipFile of the 3MF archive + + Returns: + Dictionary mapping {slot_id: extruder_id} for dual-nozzle files, + or None if single-nozzle, missing data, or parse error. + """ + try: + if "Metadata/project_settings.config" not in zf.namelist(): + return None + + content = zf.read("Metadata/project_settings.config").decode() + data = json.loads(content) + + filament_nozzle_map = data.get("filament_nozzle_map") + physical_extruder_map = data.get("physical_extruder_map") + + if not filament_nozzle_map or not physical_extruder_map: + return None + + # Build slot_id (1-based) -> extruder_id mapping + nozzle_mapping: dict[int, int] = {} + for i, slicer_ext_str in enumerate(filament_nozzle_map): + slot_id = i + 1 + try: + slicer_ext = int(slicer_ext_str) + if slicer_ext < len(physical_extruder_map): + extruder_id = int(physical_extruder_map[slicer_ext]) + nozzle_mapping[slot_id] = extruder_id + except (ValueError, TypeError, IndexError): + pass # Skip slots with unparseable nozzle mapping + + if not nozzle_mapping: + return None + + # If all slots map to the same extruder, this is a single-nozzle printer + unique_extruders = set(nozzle_mapping.values()) + if len(unique_extruders) <= 1: + return None + + return nozzle_mapping + except Exception: + return None + + def extract_filament_usage_from_3mf(file_path: Path) -> list[dict]: """Extract per-filament total usage from 3MF slice_info.config. diff --git a/backend/tests/unit/services/test_bambu_ftp.py b/backend/tests/unit/services/test_bambu_ftp.py index b7b448705..796b62a45 100644 --- a/backend/tests/unit/services/test_bambu_ftp.py +++ b/backend/tests/unit/services/test_bambu_ftp.py @@ -123,11 +123,10 @@ class TestDisconnectServerGone: """Test disconnect behavior when the server has stopped.""" def test_disconnect_after_server_gone(self, ftp_certs, tmp_path): - """Disconnect after server has stopped raises EOFError. + """Disconnect after server has stopped does not raise. - Note: The current disconnect() catches (OSError, ftplib.Error) but - EOFError is neither. This documents actual behavior — a future fix - could add EOFError to the except clause. + disconnect() catches OSError, ftplib.Error, and EOFError so that + best-effort cleanup never propagates exceptions to the caller. """ from backend.tests.unit.services.mock_ftp_server import ( MockBambuFTPServer, @@ -145,8 +144,9 @@ class TestDisconnectServerGone: client.connect() server.stop() - with pytest.raises(EOFError): - client.disconnect() + # Should not raise — disconnect() catches all connection errors + client.disconnect() + assert client._ftp is None # --------------------------------------------------------------------------- diff --git a/backend/tests/unit/services/test_spoolman_service.py b/backend/tests/unit/services/test_spoolman_service.py index a092d927a..8697a88c2 100644 --- a/backend/tests/unit/services/test_spoolman_service.py +++ b/backend/tests/unit/services/test_spoolman_service.py @@ -2,6 +2,7 @@ These tests specifically target the sync_ams_tray method's disable_weight_sync functionality that controls whether remaining_weight is updated. +Also includes tests for is_bambu_lab_spool RFID detection. """ from unittest.mock import AsyncMock, Mock, patch @@ -11,6 +12,71 @@ import pytest from backend.app.services.spoolman import AMSTray, SpoolmanClient +class TestIsBambuLabSpool: + """Tests for is_bambu_lab_spool — detects BL spools via RFID hardware identifiers only.""" + + @pytest.fixture + def client(self): + return SpoolmanClient("http://localhost:7912") + + def test_valid_tray_uuid_returns_true(self, client): + """A non-zero 32-char hex tray_uuid identifies a BL spool.""" + assert client.is_bambu_lab_spool("A1B2C3D4E5F6A1B2C3D4E5F6A1B2C3D4") is True + + def test_valid_tag_uid_returns_true(self, client): + """A non-zero 16-char hex tag_uid identifies a BL spool (fallback).""" + assert client.is_bambu_lab_spool("", tag_uid="A1B2C3D4E5F6A1B2") is True + + def test_zero_tray_uuid_returns_false(self, client): + """All-zero tray_uuid means no RFID tag read.""" + assert client.is_bambu_lab_spool("00000000000000000000000000000000") is False + + def test_zero_tag_uid_returns_false(self, client): + """All-zero tag_uid means no RFID tag read.""" + assert client.is_bambu_lab_spool("", tag_uid="0000000000000000") is False + + def test_empty_identifiers_returns_false(self, client): + """No identifiers means no BL spool.""" + assert client.is_bambu_lab_spool("") is False + assert client.is_bambu_lab_spool("", tag_uid="") is False + + def test_tray_info_idx_ignored(self, client): + """tray_info_idx is NOT a reliable BL indicator — third-party spools + using Bambu generic presets also have GF-prefixed tray_info_idx values.""" + # Third-party spool with Bambu preset but no RFID identifiers + assert client.is_bambu_lab_spool("", tray_info_idx="GFA00") is False + assert client.is_bambu_lab_spool("", tray_info_idx="GFB00") is False + assert client.is_bambu_lab_spool("", tray_info_idx="GFSA02_04") is False + + def test_tray_info_idx_with_valid_uuid_returns_true(self, client): + """BL spool with both RFID UUID and preset ID — detected by UUID.""" + assert ( + client.is_bambu_lab_spool( + "A1B2C3D4E5F6A1B2C3D4E5F6A1B2C3D4", + tray_info_idx="GFA00", + ) + is True + ) + + def test_tray_uuid_preferred_over_tag_uid(self, client): + """tray_uuid is checked before tag_uid (both valid).""" + assert ( + client.is_bambu_lab_spool( + "A1B2C3D4E5F6A1B2C3D4E5F6A1B2C3D4", + tag_uid="A1B2C3D4E5F6A1B2", + ) + is True + ) + + def test_short_tray_uuid_returns_false(self, client): + """UUID must be exactly 32 hex chars.""" + assert client.is_bambu_lab_spool("A1B2C3D4") is False + + def test_non_hex_tray_uuid_returns_false(self, client): + """UUID must be valid hex.""" + assert client.is_bambu_lab_spool("ZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZ") is False + + class TestSpoolmanClient: """Tests for SpoolmanClient class.""" diff --git a/backend/tests/unit/test_scheduler_ams_mapping.py b/backend/tests/unit/test_scheduler_ams_mapping.py index 04b7b3d7a..47fff82f7 100644 --- a/backend/tests/unit/test_scheduler_ams_mapping.py +++ b/backend/tests/unit/test_scheduler_ams_mapping.py @@ -1,8 +1,13 @@ """Tests for the AMS mapping computation in the print scheduler.""" +import io +import json +import zipfile + import pytest from backend.app.services.print_scheduler import PrintScheduler +from backend.app.utils.threemf_tools import extract_nozzle_mapping_from_3mf class TestSchedulerAmsMappingHelpers: @@ -467,3 +472,186 @@ class TestBuildLoadedFilamentsTrayInfoIdx: assert len(result) == 1 assert result[0]["tray_info_idx"] == "P4d64437" assert result[0]["is_external"] is True + + +def _make_3mf_zip(project_settings: dict | None = None) -> zipfile.ZipFile: + """Create an in-memory ZipFile mimicking a 3MF with project_settings.config.""" + buf = io.BytesIO() + with zipfile.ZipFile(buf, "w") as zf: + if project_settings is not None: + zf.writestr("Metadata/project_settings.config", json.dumps(project_settings)) + buf.seek(0) + return zipfile.ZipFile(buf, "r") + + +class TestExtractNozzleMappingFrom3mf: + """Test the extract_nozzle_mapping_from_3mf utility.""" + + def test_dual_nozzle_mapping(self): + """Should return slot->extruder mapping for dual-nozzle files.""" + zf = _make_3mf_zip( + { + "filament_nozzle_map": ["0", "1", "0"], + "physical_extruder_map": ["0", "1"], + } + ) + result = extract_nozzle_mapping_from_3mf(zf) + assert result == {1: 0, 2: 1, 3: 0} + zf.close() + + def test_single_nozzle_returns_none(self): + """All slots on same extruder should return None (single-nozzle).""" + zf = _make_3mf_zip( + { + "filament_nozzle_map": ["0", "0", "0"], + "physical_extruder_map": ["0"], + } + ) + result = extract_nozzle_mapping_from_3mf(zf) + assert result is None + zf.close() + + def test_missing_project_settings_returns_none(self): + """Missing project_settings.config should return None.""" + zf = _make_3mf_zip(None) + result = extract_nozzle_mapping_from_3mf(zf) + assert result is None + zf.close() + + def test_missing_fields_returns_none(self): + """Missing filament_nozzle_map or physical_extruder_map should return None.""" + zf = _make_3mf_zip({"some_other_key": "value"}) + result = extract_nozzle_mapping_from_3mf(zf) + assert result is None + zf.close() + + def test_physical_extruder_map_remapping(self): + """Should apply physical_extruder_map to remap slicer extruder to MQTT extruder.""" + # Slicer ext 0 -> MQTT ext 1, slicer ext 1 -> MQTT ext 0 + zf = _make_3mf_zip( + { + "filament_nozzle_map": ["0", "1"], + "physical_extruder_map": ["1", "0"], + } + ) + result = extract_nozzle_mapping_from_3mf(zf) + assert result == {1: 1, 2: 0} + zf.close() + + +class TestNozzleAwareMapping: + """Test nozzle-aware filament matching in the print scheduler.""" + + @pytest.fixture + def scheduler(self): + return PrintScheduler() + + def test_dual_nozzle_matching(self, scheduler): + """Filaments assigned to different nozzles should match to correct AMS units.""" + required = [ + {"slot_id": 1, "type": "PLA", "color": "#FF0000", "nozzle_id": 0}, # Right nozzle + {"slot_id": 2, "type": "PLA", "color": "#00FF00", "nozzle_id": 1}, # Left nozzle + ] + loaded = [ + {"type": "PLA", "color": "#00FF00", "global_tray_id": 0, "extruder_id": 0}, # AMS0 on right + {"type": "PLA", "color": "#FF0000", "global_tray_id": 4, "extruder_id": 1}, # AMS1 on left + ] + # Without nozzle filtering, slot 1 (red, right) would match tray 4 (red, left) by color. + # With nozzle filtering, slot 1 (right nozzle) can only use tray 0 (right extruder), + # and slot 2 (left nozzle) can only use tray 4 (left extruder). + result = scheduler._match_filaments_to_slots(required, loaded) + assert result == [0, 4] + + def test_nozzle_fallback_when_no_match(self, scheduler): + """Should fall back to unfiltered list when nozzle-filtered list is empty.""" + required = [ + {"slot_id": 1, "type": "PLA", "color": "#FF0000", "nozzle_id": 0}, # Right nozzle + ] + loaded = [ + # Only a tray on the left nozzle, none on right + {"type": "PLA", "color": "#FF0000", "global_tray_id": 4, "extruder_id": 1}, + ] + # No trays on extruder 0, so fallback to unfiltered -> should still match + result = scheduler._match_filaments_to_slots(required, loaded) + assert result == [4] + + def test_no_nozzle_id_skips_filtering(self, scheduler): + """When nozzle_id is None, no nozzle filtering should be applied.""" + required = [ + {"slot_id": 1, "type": "PLA", "color": "#FF0000"}, # No nozzle_id + ] + loaded = [ + {"type": "PLA", "color": "#FF0000", "global_tray_id": 0, "extruder_id": 0}, + {"type": "PLA", "color": "#FF0000", "global_tray_id": 4, "extruder_id": 1}, + ] + # Should match first available (tray 0) regardless of extruder + result = scheduler._match_filaments_to_slots(required, loaded) + assert result == [0] + + def test_extruder_id_in_loaded_filaments(self, scheduler): + """_build_loaded_filaments should include extruder_id from ams_extruder_map.""" + + class MockStatus: + raw_data = { + "ams": [ + {"id": 0, "tray": [{"id": 0, "tray_type": "PLA", "tray_color": "FF0000"}]}, + {"id": 1, "tray": [{"id": 0, "tray_type": "PLA", "tray_color": "00FF00"}]}, + ], + "ams_extruder_map": {"0": 0, "1": 1}, + } + + result = scheduler._build_loaded_filaments(MockStatus()) + assert len(result) == 2 + assert result[0]["extruder_id"] == 0 + assert result[1]["extruder_id"] == 1 + + def test_extruder_id_none_without_map(self, scheduler): + """extruder_id should be None when ams_extruder_map is absent.""" + + class MockStatus: + raw_data = { + "ams": [ + {"id": 0, "tray": [{"id": 0, "tray_type": "PLA", "tray_color": "FF0000"}]}, + ] + } + + result = scheduler._build_loaded_filaments(MockStatus()) + assert len(result) == 1 + assert result[0]["extruder_id"] is None + + def test_external_spool_extruder_id(self, scheduler): + """External spool should have extruder_id=0 when ams_extruder_map exists.""" + + class MockStatus: + raw_data = { + "vt_tray": {"tray_type": "TPU", "tray_color": "0000FF"}, + "ams_extruder_map": {"0": 0}, + } + + result = scheduler._build_loaded_filaments(MockStatus()) + assert len(result) == 1 + assert result[0]["extruder_id"] == 0 + assert result[0]["is_external"] is True + + def test_external_spool_no_extruder_map(self, scheduler): + """External spool extruder_id should be None without ams_extruder_map.""" + + class MockStatus: + raw_data = {"vt_tray": {"tray_type": "TPU", "tray_color": "0000FF"}} + + result = scheduler._build_loaded_filaments(MockStatus()) + assert len(result) == 1 + assert result[0]["extruder_id"] is None + + def test_dual_nozzle_with_tray_info_idx(self, scheduler): + """Nozzle filtering should work together with tray_info_idx matching.""" + required = [ + {"slot_id": 1, "type": "PLA", "color": "#000000", "tray_info_idx": "GFA00", "nozzle_id": 0}, + {"slot_id": 2, "type": "PLA", "color": "#000000", "tray_info_idx": "GFA01", "nozzle_id": 1}, + ] + loaded = [ + {"type": "PLA", "color": "#000000", "global_tray_id": 0, "tray_info_idx": "GFA00", "extruder_id": 0}, + {"type": "PLA", "color": "#000000", "global_tray_id": 4, "tray_info_idx": "GFA01", "extruder_id": 1}, + ] + result = scheduler._match_filaments_to_slots(required, loaded) + assert result == [0, 4] diff --git a/frontend/src/__tests__/hooks/useFilamentMapping.test.ts b/frontend/src/__tests__/hooks/useFilamentMapping.test.ts index 06c2a847a..68e997593 100644 --- a/frontend/src/__tests__/hooks/useFilamentMapping.test.ts +++ b/frontend/src/__tests__/hooks/useFilamentMapping.test.ts @@ -347,3 +347,182 @@ describe('computeAmsMapping', () => { expect(result).toEqual([254]); // External spool global ID }); }); + +describe('buildLoadedFilaments - nozzle awareness', () => { + it('sets extruderId from ams_extruder_map', () => { + const status = createPrinterStatus([ + { + id: 0, + tray: [{ id: 0, tray_type: 'PLA', tray_color: 'FF0000' }], + }, + { + id: 1, + tray: [{ id: 0, tray_type: 'PETG', tray_color: '00FF00' }], + }, + ]); + (status as any).ams_extruder_map = { '0': 1, '1': 0 }; + + const result = buildLoadedFilaments(status); + + expect(result[0].extruderId).toBe(1); // AMS 0 → left nozzle + expect(result[1].extruderId).toBe(0); // AMS 1 → right nozzle + }); + + it('leaves extruderId undefined when no ams_extruder_map', () => { + const status = createPrinterStatus([ + { + id: 0, + tray: [{ id: 0, tray_type: 'PLA', tray_color: 'FF0000' }], + }, + ]); + + const result = buildLoadedFilaments(status); + + expect(result[0].extruderId).toBeUndefined(); + }); +}); + +describe('computeAmsMapping - nozzle filtering', () => { + it('filters candidates by nozzle_id when set', () => { + // Filament requires left nozzle (extruder 1), only AMS 0 is on left + const reqs = { + filaments: [ + { slot_id: 1, type: 'PLA', color: '#FF0000', used_grams: 10, nozzle_id: 1 }, + ], + }; + const status = createPrinterStatus([ + { + id: 0, // Left nozzle + tray: [{ id: 0, tray_type: 'PLA', tray_color: 'FF0000' }], + }, + { + id: 1, // Right nozzle + tray: [{ id: 0, tray_type: 'PLA', tray_color: 'FF0000' }], + }, + ]); + (status as any).ams_extruder_map = { '0': 1, '1': 0 }; + + const result = computeAmsMapping(reqs, status); + + expect(result).toEqual([0]); // AMS 0, tray 0 (on left nozzle) + }); + + it('filters to right nozzle when nozzle_id=0', () => { + const reqs = { + filaments: [ + { slot_id: 1, type: 'PLA', color: '#FF0000', used_grams: 10, nozzle_id: 0 }, + ], + }; + const status = createPrinterStatus([ + { + id: 0, // Left nozzle + tray: [{ id: 0, tray_type: 'PLA', tray_color: 'FF0000' }], + }, + { + id: 1, // Right nozzle + tray: [{ id: 0, tray_type: 'PLA', tray_color: 'FF0000' }], + }, + ]); + (status as any).ams_extruder_map = { '0': 1, '1': 0 }; + + const result = computeAmsMapping(reqs, status); + + expect(result).toEqual([4]); // AMS 1, tray 0 (global ID = 1*4+0 = 4, on right nozzle) + }); + + it('falls back to all trays when target nozzle has no trays at all', () => { + // Requires nozzle_id=1 (left), but no AMS units are on left nozzle + const reqs = { + filaments: [ + { slot_id: 1, type: 'PLA', color: '#FF0000', used_grams: 10, nozzle_id: 1 }, + ], + }; + const status = createPrinterStatus([ + { + id: 0, // Right nozzle only + tray: [{ id: 0, tray_type: 'PLA', tray_color: 'FF0000' }], + }, + ]); + (status as any).ams_extruder_map = { '0': 0 }; // AMS 0 → right nozzle, none on left + + const result = computeAmsMapping(reqs, status); + + expect(result).toEqual([0]); // Falls back to unfiltered (right nozzle PLA) + }); + + it('stays restricted when target nozzle has trays but wrong type', () => { + // Left nozzle has PETG, right has PLA — but requires PLA on left + const reqs = { + filaments: [ + { slot_id: 1, type: 'PLA', color: '#FF0000', used_grams: 10, nozzle_id: 1 }, + ], + }; + const status = createPrinterStatus([ + { + id: 0, // Left nozzle - only PETG + tray: [{ id: 0, tray_type: 'PETG', tray_color: '00FF00' }], + }, + { + id: 1, // Right nozzle - has PLA + tray: [{ id: 0, tray_type: 'PLA', tray_color: 'FF0000' }], + }, + ]); + (status as any).ams_extruder_map = { '0': 1, '1': 0 }; + + const result = computeAmsMapping(reqs, status); + + expect(result).toEqual([-1]); // No PLA on left nozzle, stays restricted + }); + + it('skips nozzle filtering when nozzle_id is undefined', () => { + const reqs = { + filaments: [ + { slot_id: 1, type: 'PLA', color: '#FF0000', used_grams: 10 }, // No nozzle_id + ], + }; + const status = createPrinterStatus([ + { + id: 0, + tray: [{ id: 0, tray_type: 'PETG', tray_color: '00FF00' }], + }, + { + id: 1, + tray: [{ id: 0, tray_type: 'PLA', tray_color: 'FF0000' }], + }, + ]); + (status as any).ams_extruder_map = { '0': 1, '1': 0 }; + + const result = computeAmsMapping(reqs, status); + + expect(result).toEqual([4]); // Picks best match regardless of nozzle + }); + + it('handles dual-nozzle multi-slot mapping', () => { + // Two filaments: one for left, one for right + const reqs = { + filaments: [ + { slot_id: 1, type: 'PLA', color: '#FF0000', used_grams: 10, nozzle_id: 1 }, // Left + { slot_id: 2, type: 'PETG', color: '#00FF00', used_grams: 10, nozzle_id: 0 }, // Right + ], + }; + const status = createPrinterStatus([ + { + id: 0, // Left nozzle + tray: [ + { id: 0, tray_type: 'PLA', tray_color: 'FF0000' }, + ], + }, + { + id: 1, // Right nozzle + tray: [ + { id: 0, tray_type: 'PETG', tray_color: '00FF00' }, + ], + }, + ]); + (status as any).ams_extruder_map = { '0': 1, '1': 0 }; + + const result = computeAmsMapping(reqs, status); + + expect(result).toEqual([0, 4]); // Left gets AMS0-T0, Right gets AMS1-T0 + }); +}); diff --git a/frontend/src/components/PrintModal/FilamentMapping.tsx b/frontend/src/components/PrintModal/FilamentMapping.tsx index 7ae5ce97c..99085f502 100644 --- a/frontend/src/components/PrintModal/FilamentMapping.tsx +++ b/frontend/src/components/PrintModal/FilamentMapping.tsx @@ -1,4 +1,5 @@ import { useState } from 'react'; +import { useTranslation } from 'react-i18next'; import { useQuery, useQueryClient } from '@tanstack/react-query'; import { Circle, Check, AlertTriangle, RefreshCw, ChevronDown, ChevronUp } from 'lucide-react'; import { api } from '../../api/client'; @@ -17,6 +18,7 @@ export function FilamentMapping({ onManualMappingChange, defaultExpanded = false, }: FilamentMappingProps & { defaultExpanded?: boolean }) { + const { t } = useTranslation(); const queryClient = useQueryClient(); const [isRefreshing, setIsRefreshing] = useState(false); const [isExpanded, setIsExpanded] = useState(defaultExpanded); @@ -32,6 +34,7 @@ export function FilamentMapping({ useFilamentMapping(filamentReqs, printerStatus, manualMappings); const hasFilamentReqs = filamentReqs?.filaments && filamentReqs.filaments.length > 0; + const isDualNozzle = filamentReqs?.filaments?.some((f) => f.nozzle_id != null) ?? false; // Don't render if no filament requirements if (!hasFilamentReqs) { @@ -126,8 +129,16 @@ export function FilamentMapping({ - {/* Required type + grams */} - + {/* Required type + grams + nozzle badge */} + + {isDualNozzle && item.nozzle_id != null && ( + + {item.nozzle_id === 1 ? t('printModal.leftNozzle') : t('printModal.rightNozzle')} + + )} {item.type} ({item.used_grams}g) {/* Arrow */} diff --git a/frontend/src/components/PrintModal/types.ts b/frontend/src/components/PrintModal/types.ts index daa3f3a99..38241cab7 100644 --- a/frontend/src/components/PrintModal/types.ts +++ b/frontend/src/components/PrintModal/types.ts @@ -158,6 +158,7 @@ export interface FilamentReqsData { color: string; used_grams: number; used_meters: number; + nozzle_id?: number; }>; } diff --git a/frontend/src/hooks/useFilamentMapping.ts b/frontend/src/hooks/useFilamentMapping.ts index e430149ba..bb79c80c3 100644 --- a/frontend/src/hooks/useFilamentMapping.ts +++ b/frontend/src/hooks/useFilamentMapping.ts @@ -15,6 +15,8 @@ import type { PrinterStatus } from '../api/client'; */ export function buildLoadedFilaments(printerStatus: PrinterStatus | undefined): LoadedFilament[] { const filaments: LoadedFilament[] = []; + const amsExtruderMap = printerStatus?.ams_extruder_map; + const hasDualNozzle = amsExtruderMap && Object.keys(amsExtruderMap).length > 0; // Add filaments from all AMS units (regular and HT) printerStatus?.ams?.forEach((amsUnit) => { @@ -33,6 +35,7 @@ export function buildLoadedFilaments(printerStatus: PrinterStatus | undefined): label: formatSlotLabel(amsUnit.id, tray.id, isHt, false), globalTrayId: getGlobalTrayId(amsUnit.id, tray.id, false), trayInfoIdx: tray.tray_info_idx || '', + extruderId: amsExtruderMap?.[String(amsUnit.id)], }); } }); @@ -52,6 +55,7 @@ export function buildLoadedFilaments(printerStatus: PrinterStatus | undefined): label: 'External', globalTrayId: 254, trayInfoIdx: printerStatus.vt_tray.tray_info_idx || '', + extruderId: hasDualNozzle ? 0 : undefined, }); } @@ -90,7 +94,15 @@ export function computeAmsMapping( const reqTrayInfoIdx = req.tray_info_idx || ''; // Get available trays (not already used) - const available = loadedFilaments.filter((f) => !usedTrayIds.has(f.globalTrayId)); + let available = loadedFilaments.filter((f) => !usedTrayIds.has(f.globalTrayId)); + + // Nozzle-aware filtering: restrict to trays on the correct nozzle + if (req.nozzle_id != null) { + const nozzleFiltered = available.filter((f) => f.extruderId === req.nozzle_id); + if (nozzleFiltered.length > 0) { + available = nozzleFiltered; + } + } let idxMatch: LoadedFilament | undefined; let exactMatch: LoadedFilament | undefined; @@ -191,6 +203,8 @@ export interface LoadedFilament { globalTrayId: number; /** Unique spool identifier (e.g., "GFA00", "P4d64437") */ trayInfoIdx?: string; + /** Extruder ID for dual-nozzle printers (0=right, 1=left) */ + extruderId?: number; } /** @@ -203,6 +217,8 @@ export interface FilamentRequirement { used_grams: number; /** Unique spool identifier from slicing (e.g., "GFA00", "P4d64437") */ tray_info_idx?: string; + /** Target nozzle for dual-nozzle printers (0=right, 1=left) */ + nozzle_id?: number; } /** @@ -247,48 +263,7 @@ export function useLoadedFilaments( printerStatus: PrinterStatus | undefined ): LoadedFilament[] { return useMemo(() => { - const filaments: LoadedFilament[] = []; - - // Add filaments from all AMS units (regular and HT) - printerStatus?.ams?.forEach((amsUnit) => { - const isHt = amsUnit.tray.length === 1; // AMS-HT has single tray - amsUnit.tray.forEach((tray) => { - if (tray.tray_type) { - const color = normalizeColor(tray.tray_color); - filaments.push({ - type: tray.tray_type, - color, - colorName: getColorName(color), - amsId: amsUnit.id, - trayId: tray.id, - isHt, - isExternal: false, - label: formatSlotLabel(amsUnit.id, tray.id, isHt, false), - globalTrayId: getGlobalTrayId(amsUnit.id, tray.id, false), - trayInfoIdx: tray.tray_info_idx || '', - }); - } - }); - }); - - // Add external spool if loaded - if (printerStatus?.vt_tray?.tray_type) { - const color = normalizeColor(printerStatus.vt_tray.tray_color); - filaments.push({ - type: printerStatus.vt_tray.tray_type, - color, - colorName: getColorName(color), - amsId: -1, - trayId: 0, - isHt: false, - isExternal: true, - label: 'External', - globalTrayId: 254, - trayInfoIdx: printerStatus.vt_tray.tray_info_idx || '', - }); - } - - return filaments; + return buildLoadedFilaments(printerStatus); }, [printerStatus]); } @@ -355,7 +330,15 @@ export function useFilamentMapping( const reqTrayInfoIdx = req.tray_info_idx || ''; // Get available trays (not already used) - const available = loadedFilaments.filter((f) => !usedTrayIds.has(f.globalTrayId)); + let available = loadedFilaments.filter((f) => !usedTrayIds.has(f.globalTrayId)); + + // Nozzle-aware filtering: restrict to trays on the correct nozzle + if (req.nozzle_id != null) { + const nozzleFiltered = available.filter((f) => f.extruderId === req.nozzle_id); + if (nozzleFiltered.length > 0) { + available = nozzleFiltered; + } + } let idxMatch: LoadedFilament | undefined; let exactMatch: LoadedFilament | undefined; diff --git a/frontend/src/hooks/useMultiPrinterFilamentMapping.ts b/frontend/src/hooks/useMultiPrinterFilamentMapping.ts index b8e62d45b..0a9e30a50 100644 --- a/frontend/src/hooks/useMultiPrinterFilamentMapping.ts +++ b/frontend/src/hooks/useMultiPrinterFilamentMapping.ts @@ -124,26 +124,32 @@ function computeMatchDetails( } } - // Auto-match - const exactMatch = loadedFilaments.find( + // Auto-match with nozzle-aware filtering + let candidates = loadedFilaments.filter((f) => !usedTrayIds.has(f.globalTrayId)); + if (req.nozzle_id != null) { + const nozzleFiltered = candidates.filter((f) => f.extruderId === req.nozzle_id); + if (nozzleFiltered.length > 0) { + candidates = nozzleFiltered; + } + } + + const exactMatch = candidates.find( (f) => - !usedTrayIds.has(f.globalTrayId) && f.type?.toUpperCase() === req.type?.toUpperCase() && normalizeColorForCompare(f.color) === normalizeColorForCompare(req.color) ); const similarMatch = exactMatch ? undefined - : loadedFilaments.find( + : candidates.find( (f) => - !usedTrayIds.has(f.globalTrayId) && f.type?.toUpperCase() === req.type?.toUpperCase() && colorsAreSimilar(f.color, req.color) ); const typeOnlyMatch = exactMatch || similarMatch ? undefined - : loadedFilaments.find( - (f) => !usedTrayIds.has(f.globalTrayId) && f.type?.toUpperCase() === req.type?.toUpperCase() + : candidates.find( + (f) => f.type?.toUpperCase() === req.type?.toUpperCase() ); const loaded = exactMatch ?? similarMatch ?? typeOnlyMatch; @@ -196,26 +202,32 @@ function computeMappingWithOverrides( continue; } - // Auto-match - const exactMatch = loadedFilaments.find( + // Auto-match with nozzle-aware filtering + let candidates = loadedFilaments.filter((f) => !usedTrayIds.has(f.globalTrayId)); + if (req.nozzle_id != null) { + const nozzleFiltered = candidates.filter((f) => f.extruderId === req.nozzle_id); + if (nozzleFiltered.length > 0) { + candidates = nozzleFiltered; + } + } + + const exactMatch = candidates.find( (f) => - !usedTrayIds.has(f.globalTrayId) && f.type?.toUpperCase() === req.type?.toUpperCase() && normalizeColorForCompare(f.color) === normalizeColorForCompare(req.color) ); const similarMatch = exactMatch ? undefined - : loadedFilaments.find( + : candidates.find( (f) => - !usedTrayIds.has(f.globalTrayId) && f.type?.toUpperCase() === req.type?.toUpperCase() && colorsAreSimilar(f.color, req.color) ); const typeOnlyMatch = exactMatch || similarMatch ? undefined - : loadedFilaments.find( - (f) => !usedTrayIds.has(f.globalTrayId) && f.type?.toUpperCase() === req.type?.toUpperCase() + : candidates.find( + (f) => f.type?.toUpperCase() === req.type?.toUpperCase() ); const loaded = exactMatch ?? similarMatch ?? typeOnlyMatch; diff --git a/frontend/src/i18n/locales/de.ts b/frontend/src/i18n/locales/de.ts index 8dfc1e160..35d91080a 100644 --- a/frontend/src/i18n/locales/de.ts +++ b/frontend/src/i18n/locales/de.ts @@ -2632,6 +2632,10 @@ export default { sameTypeDifferentColor: 'Gleicher Typ, andere Farbe', filamentTypeNotLoaded: 'Filamenttyp nicht geladen', openCalendar: 'Kalender öffnen', + leftNozzle: 'L', + rightNozzle: 'R', + leftNozzleTooltip: 'Linke Düse', + rightNozzleTooltip: 'Rechte Düse', }, // Backup diff --git a/frontend/src/i18n/locales/en.ts b/frontend/src/i18n/locales/en.ts index df586256f..4c1fabd7e 100644 --- a/frontend/src/i18n/locales/en.ts +++ b/frontend/src/i18n/locales/en.ts @@ -2636,6 +2636,10 @@ export default { sameTypeDifferentColor: 'Same type, different color', filamentTypeNotLoaded: 'Filament type not loaded', openCalendar: 'Open calendar', + leftNozzle: 'L', + rightNozzle: 'R', + leftNozzleTooltip: 'Left nozzle', + rightNozzleTooltip: 'Right nozzle', }, // Backup diff --git a/frontend/src/i18n/locales/it.ts b/frontend/src/i18n/locales/it.ts index 7078d974e..600132d0d 100644 --- a/frontend/src/i18n/locales/it.ts +++ b/frontend/src/i18n/locales/it.ts @@ -2306,6 +2306,10 @@ export default { noPrintersAvailable: 'Nessuna stampante disponibile', printerBusy: 'Stampante occupata', printerOffline: 'Stampante offline', + leftNozzle: 'L', + rightNozzle: 'R', + leftNozzleTooltip: 'Ugello sinistro', + rightNozzleTooltip: 'Ugello destro', }, // Backup diff --git a/frontend/src/i18n/locales/ja.ts b/frontend/src/i18n/locales/ja.ts index 656427701..066e71a9e 100644 --- a/frontend/src/i18n/locales/ja.ts +++ b/frontend/src/i18n/locales/ja.ts @@ -2552,6 +2552,10 @@ export default { printerBusy: 'プリンターは使用中です', printerOffline: 'プリンターはオフラインです', cancel: 'キャンセル', + leftNozzle: 'L', + rightNozzle: 'R', + leftNozzleTooltip: '左ノズル', + rightNozzleTooltip: '右ノズル', }, backup: { restoreBackup: 'バックアップの復元', diff --git a/frontend/src/pages/PrintersPage.tsx b/frontend/src/pages/PrintersPage.tsx index 92ea82746..57c739560 100644 --- a/frontend/src/pages/PrintersPage.tsx +++ b/frontend/src/pages/PrintersPage.tsx @@ -1137,21 +1137,17 @@ function getWifiStrength(rssi: number): { labelKey: string; color: string; bars: } /** - * Check if a tray contains a Bambu Lab spool. - * Uses same logic as backend: tray_info_idx (GF*), tray_uuid, or tag_uid. + * Check if a tray contains a Bambu Lab spool (RFID-tagged). + * Only checks hardware identifiers (tray_uuid, tag_uid) — NOT tray_info_idx, + * which is a filament profile/preset ID that third-party spools also get when + * the user selects a generic Bambu preset (e.g. "GFA00" for Generic PLA). */ function isBambuLabSpool(tray: { tray_uuid?: string | null; tag_uid?: string | null; - tray_info_idx?: string | null; } | null | undefined): boolean { if (!tray) return false; - // Check tray_info_idx first (most reliable - Bambu preset IDs start with "GF") - if (tray.tray_info_idx && tray.tray_info_idx.startsWith('GF')) { - return true; - } - // Check tray_uuid (32 hex chars, non-zero) if (tray.tray_uuid && tray.tray_uuid !== '00000000000000000000000000000000') { return true; diff --git a/static/assets/index-CYXpThrD.js b/static/assets/index-C7KLL0cs.js similarity index 78% rename from static/assets/index-CYXpThrD.js rename to static/assets/index-C7KLL0cs.js index b3ed329cb..bd3a8e9d8 100644 --- a/static/assets/index-CYXpThrD.js +++ b/static/assets/index-C7KLL0cs.js @@ -1,18 +1,18 @@ -function gse(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 s of i)if(s.type==="childList")for(const o of s.addedNodes)o.tagName==="LINK"&&o.rel==="modulepreload"&&r(o)}).observe(document,{childList:!0,subtree:!0});function n(i){const s={};return i.integrity&&(s.integrity=i.integrity),i.referrerPolicy&&(s.referrerPolicy=i.referrerPolicy),i.crossOrigin==="use-credentials"?s.credentials="include":i.crossOrigin==="anonymous"?s.credentials="omit":s.credentials="same-origin",s}function r(i){if(i.ep)return;i.ep=!0;const s=n(i);fetch(i.href,s)}})();var qw=typeof globalThis<"u"?globalThis:typeof window<"u"?window:typeof global<"u"?global:typeof self<"u"?self:{};function wl(t){return t&&t.__esModule&&Object.prototype.hasOwnProperty.call(t,"default")?t.default:t}var cE={exports:{}},Ux={};var gU;function bse(){if(gU)return Ux;gU=1;var t=Symbol.for("react.transitional.element"),e=Symbol.for("react.fragment");function n(r,i,s){var o=null;if(s!==void 0&&(o=""+s),i.key!==void 0&&(o=""+i.key),"key"in i){s={};for(var l in i)l!=="key"&&(s[l]=i[l])}else s=i;return i=s.ref,{$$typeof:t,type:r,key:o,ref:i!==void 0?i:null,props:s}}return Ux.Fragment=e,Ux.jsx=n,Ux.jsxs=n,Ux}var bU;function xse(){return bU||(bU=1,cE.exports=bse()),cE.exports}var a=xse(),uE={exports:{}},Nn={};var xU;function yse(){if(xU)return Nn;xU=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"),s=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"),h=Symbol.for("react.activity"),p=Symbol.iterator;function m(V){return V===null||typeof V!="object"?null:(V=p&&V[p]||V["@@iterator"],typeof V=="function"?V:null)}var x={isMounted:function(){return!1},enqueueForceUpdate:function(){},enqueueReplaceState:function(){},enqueueSetState:function(){}},y=Object.assign,b={};function v(V,W,ie){this.props=V,this.context=W,this.refs=b,this.updater=ie||x}v.prototype.isReactComponent={},v.prototype.setState=function(V,W){if(typeof V!="object"&&typeof V!="function"&&V!=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,V,W,"setState")},v.prototype.forceUpdate=function(V){this.updater.enqueueForceUpdate(this,V,"forceUpdate")};function S(){}S.prototype=v.prototype;function N(V,W,ie){this.props=V,this.context=W,this.refs=b,this.updater=ie||x}var E=N.prototype=new S;E.constructor=N,y(E,v.prototype),E.isPureReactComponent=!0;var k=Array.isArray;function A(){}var M={H:null,A:null,T:null,S:null},P=Object.prototype.hasOwnProperty;function T(V,W,ie){var J=ie.ref;return{$$typeof:t,type:V,key:W,ref:J!==void 0?J:null,props:ie}}function j(V,W){return T(V.type,W,V.props)}function z(V){return typeof V=="object"&&V!==null&&V.$$typeof===t}function F(V){var W={"=":"=0",":":"=2"};return"$"+V.replace(/[=:]/g,function(ie){return W[ie]})}var $=/\/+/g;function D(V,W){return typeof V=="object"&&V!==null&&V.key!=null?F(""+V.key):W.toString(36)}function H(V){switch(V.status){case"fulfilled":return V.value;case"rejected":throw V.reason;default:switch(typeof V.status=="string"?V.then(A,A):(V.status="pending",V.then(function(W){V.status==="pending"&&(V.status="fulfilled",V.value=W)},function(W){V.status==="pending"&&(V.status="rejected",V.reason=W)})),V.status){case"fulfilled":return V.value;case"rejected":throw V.reason}}throw V}function q(V,W,ie,J,pe){var fe=typeof V;(fe==="undefined"||fe==="boolean")&&(V=null);var ve=!1;if(V===null)ve=!0;else switch(fe){case"bigint":case"string":case"number":ve=!0;break;case"object":switch(V.$$typeof){case t:case e:ve=!0;break;case d:return ve=V._init,q(ve(V._payload),W,ie,J,pe)}}if(ve)return pe=pe(V),ve=J===""?"."+D(V,0):J,k(pe)?(ie="",ve!=null&&(ie=ve.replace($,"$&/")+"/"),q(pe,W,ie,"",function(ce){return ce})):pe!=null&&(z(pe)&&(pe=j(pe,ie+(pe.key==null||V&&V.key===pe.key?"":(""+pe.key).replace($,"$&/")+"/")+ve)),W.push(pe)),1;ve=0;var Le=J===""?".":J+":";if(k(V))for(var de=0;de>>1,se=q[Z];if(0>>1;Zi(ie,K))Ji(pe,ie)?(q[Z]=pe,q[J]=K,Z=J):(q[Z]=ie,q[W]=K,Z=W);else if(Ji(pe,K))q[Z]=pe,q[J]=K,Z=J;else break e}}return I}function i(q,I){var K=q.sortIndex-I.sortIndex;return K!==0?K:q.id-I.id}if(t.unstable_now=void 0,typeof performance=="object"&&typeof performance.now=="function"){var s=performance;t.unstable_now=function(){return s.now()}}else{var o=Date,l=o.now();t.unstable_now=function(){return o.now()-l}}var c=[],u=[],d=1,h=null,p=3,m=!1,x=!1,y=!1,b=!1,v=typeof setTimeout=="function"?setTimeout:null,S=typeof clearTimeout=="function"?clearTimeout:null,N=typeof setImmediate<"u"?setImmediate:null;function E(q){for(var I=n(u);I!==null;){if(I.callback===null)r(u);else if(I.startTime<=q)r(u),I.sortIndex=I.expirationTime,e(c,I);else break;I=n(u)}}function k(q){if(y=!1,E(q),!x)if(n(c)!==null)x=!0,A||(A=!0,F());else{var I=n(u);I!==null&&H(k,I.startTime-q)}}var A=!1,M=-1,P=5,T=-1;function j(){return b?!0:!(t.unstable_now()-Tq&&j());){var Z=h.callback;if(typeof Z=="function"){h.callback=null,p=h.priorityLevel;var se=Z(h.expirationTime<=q);if(q=t.unstable_now(),typeof se=="function"){h.callback=se,E(q),I=!0;break t}h===n(c)&&r(c),E(q)}else r(c);h=n(c)}if(h!==null)I=!0;else{var V=n(u);V!==null&&H(k,V.startTime-q),I=!1}}break e}finally{h=null,p=K,m=!1}I=void 0}}finally{I?F():A=!1}}}var F;if(typeof N=="function")F=function(){N(z)};else if(typeof MessageChannel<"u"){var $=new MessageChannel,D=$.port2;$.port1.onmessage=z,F=function(){D.postMessage(null)}}else F=function(){v(z,0)};function H(q,I){M=v(function(){q(t.unstable_now())},I)}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(q){q.callback=null},t.unstable_forceFrameRate=function(q){0>q||125Z?(q.sortIndex=K,e(u,q),n(c)===null&&q===n(u)&&(y?(S(M),M=-1):y=!0,H(k,K-Z))):(q.sortIndex=se,e(c,q),x||m||(x=!0,A||(A=!0,F()))),q},t.unstable_shouldYield=j,t.unstable_wrapCallback=function(q){var I=p;return function(){var K=p;p=I;try{return q.apply(this,arguments)}finally{p=K}}}})(fE)),fE}var wU;function _se(){return wU||(wU=1,hE.exports=wse()),hE.exports}var mE={exports:{}},ns={};var _U;function Sse(){if(_U)return ns;_U=1;var t=Eb();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(),mE.exports=Sse(),mE.exports}var NU;function Nse(){if(NU)return Bx;NU=1;var t=_se(),e=Eb(),n=nW();function r(f){var g="https://react.dev/errors/"+f;if(1se||(f.current=Z[se],Z[se]=null,se--)}function ie(f,g){se++,Z[se]=f.current,f.current=g}var J=V(null),pe=V(null),fe=V(null),ve=V(null);function Le(f,g){switch(ie(fe,g),ie(pe,f),ie(J,null),g.nodeType){case 9:case 11:f=(f=g.documentElement)&&(f=f.namespaceURI)?zz(f):0;break;default:if(f=g.tagName,g=g.namespaceURI)g=zz(g),f=Uz(g,f);else switch(f){case"svg":f=1;break;case"math":f=2;break;default:f=0}}W(J),ie(J,f)}function de(){W(J),W(pe),W(fe)}function ce(f){f.memoizedState!==null&&ie(ve,f);var g=J.current,_=Uz(g,f.type);g!==_&&(ie(pe,f),ie(J,_))}function Ae(f){pe.current===f&&(W(J),W(pe)),ve.current===f&&(W(ve),Ox._currentValue=K)}var re,Ie;function G(f){if(re===void 0)try{throw Error()}catch(_){var g=_.stack.trim().match(/\n( *(at )?)/);re=g&&g[1]||"",Ie=-1<_.stack.indexOf(` +function mse(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 s of i)if(s.type==="childList")for(const o of s.addedNodes)o.tagName==="LINK"&&o.rel==="modulepreload"&&r(o)}).observe(document,{childList:!0,subtree:!0});function n(i){const s={};return i.integrity&&(s.integrity=i.integrity),i.referrerPolicy&&(s.referrerPolicy=i.referrerPolicy),i.crossOrigin==="use-credentials"?s.credentials="include":i.crossOrigin==="anonymous"?s.credentials="omit":s.credentials="same-origin",s}function r(i){if(i.ep)return;i.ep=!0;const s=n(i);fetch(i.href,s)}})();var Gw=typeof globalThis<"u"?globalThis:typeof window<"u"?window:typeof global<"u"?global:typeof self<"u"?self:{};function wl(t){return t&&t.__esModule&&Object.prototype.hasOwnProperty.call(t,"default")?t.default:t}var cE={exports:{}},zx={};var pU;function pse(){if(pU)return zx;pU=1;var t=Symbol.for("react.transitional.element"),e=Symbol.for("react.fragment");function n(r,i,s){var o=null;if(s!==void 0&&(o=""+s),i.key!==void 0&&(o=""+i.key),"key"in i){s={};for(var l in i)l!=="key"&&(s[l]=i[l])}else s=i;return i=s.ref,{$$typeof:t,type:r,key:o,ref:i!==void 0?i:null,props:s}}return zx.Fragment=e,zx.jsx=n,zx.jsxs=n,zx}var gU;function gse(){return gU||(gU=1,cE.exports=pse()),cE.exports}var a=gse(),uE={exports:{}},Nn={};var bU;function bse(){if(bU)return Nn;bU=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"),s=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"),h=Symbol.for("react.activity"),p=Symbol.iterator;function m(V){return V===null||typeof V!="object"?null:(V=p&&V[p]||V["@@iterator"],typeof V=="function"?V:null)}var x={isMounted:function(){return!1},enqueueForceUpdate:function(){},enqueueReplaceState:function(){},enqueueSetState:function(){}},y=Object.assign,b={};function v(V,W,ie){this.props=V,this.context=W,this.refs=b,this.updater=ie||x}v.prototype.isReactComponent={},v.prototype.setState=function(V,W){if(typeof V!="object"&&typeof V!="function"&&V!=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,V,W,"setState")},v.prototype.forceUpdate=function(V){this.updater.enqueueForceUpdate(this,V,"forceUpdate")};function S(){}S.prototype=v.prototype;function k(V,W,ie){this.props=V,this.context=W,this.refs=b,this.updater=ie||x}var E=k.prototype=new S;E.constructor=k,y(E,v.prototype),E.isPureReactComponent=!0;var N=Array.isArray;function A(){}var M={H:null,A:null,T:null,S:null},P=Object.prototype.hasOwnProperty;function T(V,W,ie){var J=ie.ref;return{$$typeof:t,type:V,key:W,ref:J!==void 0?J:null,props:ie}}function j(V,W){return T(V.type,W,V.props)}function z(V){return typeof V=="object"&&V!==null&&V.$$typeof===t}function F(V){var W={"=":"=0",":":"=2"};return"$"+V.replace(/[=:]/g,function(ie){return W[ie]})}var $=/\/+/g;function D(V,W){return typeof V=="object"&&V!==null&&V.key!=null?F(""+V.key):W.toString(36)}function H(V){switch(V.status){case"fulfilled":return V.value;case"rejected":throw V.reason;default:switch(typeof V.status=="string"?V.then(A,A):(V.status="pending",V.then(function(W){V.status==="pending"&&(V.status="fulfilled",V.value=W)},function(W){V.status==="pending"&&(V.status="rejected",V.reason=W)})),V.status){case"fulfilled":return V.value;case"rejected":throw V.reason}}throw V}function q(V,W,ie,J,pe){var fe=typeof V;(fe==="undefined"||fe==="boolean")&&(V=null);var ve=!1;if(V===null)ve=!0;else switch(fe){case"bigint":case"string":case"number":ve=!0;break;case"object":switch(V.$$typeof){case t:case e:ve=!0;break;case d:return ve=V._init,q(ve(V._payload),W,ie,J,pe)}}if(ve)return pe=pe(V),ve=J===""?"."+D(V,0):J,N(pe)?(ie="",ve!=null&&(ie=ve.replace($,"$&/")+"/"),q(pe,W,ie,"",function(ce){return ce})):pe!=null&&(z(pe)&&(pe=j(pe,ie+(pe.key==null||V&&V.key===pe.key?"":(""+pe.key).replace($,"$&/")+"/")+ve)),W.push(pe)),1;ve=0;var Le=J===""?".":J+":";if(N(V))for(var de=0;de>>1,se=q[Z];if(0>>1;Zi(ie,K))Ji(pe,ie)?(q[Z]=pe,q[J]=K,Z=J):(q[Z]=ie,q[W]=K,Z=W);else if(Ji(pe,K))q[Z]=pe,q[J]=K,Z=J;else break e}}return I}function i(q,I){var K=q.sortIndex-I.sortIndex;return K!==0?K:q.id-I.id}if(t.unstable_now=void 0,typeof performance=="object"&&typeof performance.now=="function"){var s=performance;t.unstable_now=function(){return s.now()}}else{var o=Date,l=o.now();t.unstable_now=function(){return o.now()-l}}var c=[],u=[],d=1,h=null,p=3,m=!1,x=!1,y=!1,b=!1,v=typeof setTimeout=="function"?setTimeout:null,S=typeof clearTimeout=="function"?clearTimeout:null,k=typeof setImmediate<"u"?setImmediate:null;function E(q){for(var I=n(u);I!==null;){if(I.callback===null)r(u);else if(I.startTime<=q)r(u),I.sortIndex=I.expirationTime,e(c,I);else break;I=n(u)}}function N(q){if(y=!1,E(q),!x)if(n(c)!==null)x=!0,A||(A=!0,F());else{var I=n(u);I!==null&&H(N,I.startTime-q)}}var A=!1,M=-1,P=5,T=-1;function j(){return b?!0:!(t.unstable_now()-Tq&&j());){var Z=h.callback;if(typeof Z=="function"){h.callback=null,p=h.priorityLevel;var se=Z(h.expirationTime<=q);if(q=t.unstable_now(),typeof se=="function"){h.callback=se,E(q),I=!0;break t}h===n(c)&&r(c),E(q)}else r(c);h=n(c)}if(h!==null)I=!0;else{var V=n(u);V!==null&&H(N,V.startTime-q),I=!1}}break e}finally{h=null,p=K,m=!1}I=void 0}}finally{I?F():A=!1}}}var F;if(typeof k=="function")F=function(){k(z)};else if(typeof MessageChannel<"u"){var $=new MessageChannel,D=$.port2;$.port1.onmessage=z,F=function(){D.postMessage(null)}}else F=function(){v(z,0)};function H(q,I){M=v(function(){q(t.unstable_now())},I)}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(q){q.callback=null},t.unstable_forceFrameRate=function(q){0>q||125Z?(q.sortIndex=K,e(u,q),n(c)===null&&q===n(u)&&(y?(S(M),M=-1):y=!0,H(N,K-Z))):(q.sortIndex=se,e(c,q),x||m||(x=!0,A||(A=!0,F()))),q},t.unstable_shouldYield=j,t.unstable_wrapCallback=function(q){var I=p;return function(){var K=p;p=I;try{return q.apply(this,arguments)}finally{p=K}}}})(fE)),fE}var vU;function vse(){return vU||(vU=1,hE.exports=yse()),hE.exports}var mE={exports:{}},ns={};var wU;function wse(){if(wU)return ns;wU=1;var t=Cb();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(),mE.exports=wse(),mE.exports}var SU;function _se(){if(SU)return Ux;SU=1;var t=vse(),e=Cb(),n=nW();function r(f){var g="https://react.dev/errors/"+f;if(1se||(f.current=Z[se],Z[se]=null,se--)}function ie(f,g){se++,Z[se]=f.current,f.current=g}var J=V(null),pe=V(null),fe=V(null),ve=V(null);function Le(f,g){switch(ie(fe,g),ie(pe,f),ie(J,null),g.nodeType){case 9:case 11:f=(f=g.documentElement)&&(f=f.namespaceURI)?Iz(f):0;break;default:if(f=g.tagName,g=g.namespaceURI)g=Iz(g),f=zz(g,f);else switch(f){case"svg":f=1;break;case"math":f=2;break;default:f=0}}W(J),ie(J,f)}function de(){W(J),W(pe),W(fe)}function ce(f){f.memoizedState!==null&&ie(ve,f);var g=J.current,_=zz(g,f.type);g!==_&&(ie(pe,f),ie(J,_))}function Ae(f){pe.current===f&&(W(J),W(pe)),ve.current===f&&(W(ve),Lx._currentValue=K)}var re,Ie;function G(f){if(re===void 0)try{throw Error()}catch(_){var g=_.stack.trim().match(/\n( *(at )?)/);re=g&&g[1]||"",Ie=-1<_.stack.indexOf(` at`)?" ()":-1<_.stack.indexOf("@")?"@unknown:0:0":""}return` `+re+f+Ie}var Y=!1;function ue(f,g){if(!f||Y)return"";Y=!0;var _=Error.prepareStackTrace;Error.prepareStackTrace=void 0;try{var C={DetermineComponentFrameRoot:function(){try{if(g){var kt=function(){throw Error()};if(Object.defineProperty(kt.prototype,"props",{set:function(){throw Error()}}),typeof Reflect=="object"&&Reflect.construct){try{Reflect.construct(kt,[])}catch(xt){var ft=xt}Reflect.construct(f,[],kt)}else{try{kt.call()}catch(xt){ft=xt}f.call(kt.prototype)}}else{try{throw Error()}catch(xt){ft=xt}(kt=f())&&typeof kt.catch=="function"&&kt.catch(function(){})}}catch(xt){if(xt&&ft&&typeof xt.stack=="string")return[xt.stack,ft.stack]}return[null,null]}};C.DetermineComponentFrameRoot.displayName="DetermineComponentFrameRoot";var R=Object.getOwnPropertyDescriptor(C.DetermineComponentFrameRoot,"name");R&&R.configurable&&Object.defineProperty(C.DetermineComponentFrameRoot,"name",{value:"DetermineComponentFrameRoot"});var O=C.DetermineComponentFrameRoot(),ne=O[0],Se=O[1];if(ne&&Se){var Be=ne.split(` `),ut=Se.split(` `);for(R=C=0;CR||Be[C]!==ut[R]){var vt=` `+Be[C].replace(" at new "," at ");return f.displayName&&vt.includes("")&&(vt=vt.replace("",f.displayName)),vt}while(1<=C&&0<=R);break}}}finally{Y=!1,Error.prepareStackTrace=_}return(_=f?f.displayName||f.name:"")?G(_):""}function B(f,g){switch(f.tag){case 26:case 27:case 5:return G(f.type);case 16:return G("Lazy");case 13:return f.child!==g&&g!==null?G("Suspense Fallback"):G("Suspense");case 19:return G("SuspenseList");case 0:case 15:return ue(f.type,!1);case 11:return ue(f.type.render,!1);case 1:return ue(f.type,!0);case 31:return G("Activity");default:return""}}function ee(f){try{var g="",_=null;do g+=B(f,_),_=f,f=f.return;while(f);return g}catch(C){return` Error generating stack: `+C.message+` -`+C.stack}}var Q=Object.prototype.hasOwnProperty,le=t.unstable_scheduleCallback,he=t.unstable_cancelCallback,te=t.unstable_shouldYield,U=t.unstable_requestPaint,L=t.unstable_now,X=t.unstable_getCurrentPriorityLevel,ge=t.unstable_ImmediatePriority,_e=t.unstable_UserBlockingPriority,Ee=t.unstable_NormalPriority,$e=t.unstable_LowPriority,Ce=t.unstable_IdlePriority,He=t.log,Qe=t.unstable_setDisableYieldValue,xe=null,ye=null;function we(f){if(typeof He=="function"&&Qe(f),ye&&typeof ye.setStrictMode=="function")try{ye.setStrictMode(xe,f)}catch{}}var Ue=Math.clz32?Math.clz32:ke,Pe=Math.log,Ve=Math.LN2;function ke(f){return f>>>=0,f===0?32:31-(Pe(f)/Ve|0)|0}var Ze=256,je=262144,Xe=4194304;function Re(f){var g=f&42;if(g!==0)return g;switch(f&-f){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 f&261888;case 262144:case 524288:case 1048576:case 2097152:return f&3932160;case 4194304:case 8388608:case 16777216:case 33554432:return f&62914560;case 67108864:return 67108864;case 134217728:return 134217728;case 268435456:return 268435456;case 536870912:return 536870912;case 1073741824:return 0;default:return f}}function Oe(f,g,_){var C=f.pendingLanes;if(C===0)return 0;var R=0,O=f.suspendedLanes,ne=f.pingedLanes;f=f.warmLanes;var Se=C&134217727;return Se!==0?(C=Se&~O,C!==0?R=Re(C):(ne&=Se,ne!==0?R=Re(ne):_||(_=Se&~f,_!==0&&(R=Re(_))))):(Se=C&~O,Se!==0?R=Re(Se):ne!==0?R=Re(ne):_||(_=C&~f,_!==0&&(R=Re(_)))),R===0?0:g!==0&&g!==R&&(g&O)===0&&(O=R&-R,_=g&-g,O>=_||O===32&&(_&4194048)!==0)?g:R}function Ne(f,g){return(f.pendingLanes&~(f.suspendedLanes&~f.pingedLanes)&g)===0}function Je(f,g){switch(f){case 1:case 2:case 4:case 8:case 64:return g+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 g+5e3;case 4194304:case 8388608:case 16777216:case 33554432:return-1;case 67108864:case 134217728:case 268435456:case 536870912:case 1073741824:return-1;default:return-1}}function yt(){var f=Xe;return Xe<<=1,(Xe&62914560)===0&&(Xe=4194304),f}function Et(f){for(var g=[],_=0;31>_;_++)g.push(f);return g}function an(f,g){f.pendingLanes|=g,g!==268435456&&(f.suspendedLanes=0,f.pingedLanes=0,f.warmLanes=0)}function jt(f,g,_,C,R,O){var ne=f.pendingLanes;f.pendingLanes=_,f.suspendedLanes=0,f.pingedLanes=0,f.warmLanes=0,f.expiredLanes&=_,f.entangledLanes&=_,f.errorRecoveryDisabledLanes&=_,f.shellSuspendCounter=0;var Se=f.entanglements,Be=f.expirationTimes,ut=f.hiddenUpdates;for(_=ne&~_;0<_;){var vt=31-Ue(_),kt=1<"u")return null;try{return f.activeElement||f.body}catch{return f.body}}var Vr=/[\n"\\]/g;function Tn(f){return f.replace(Vr,function(g){return"\\"+g.charCodeAt(0).toString(16)+" "})}function _n(f,g,_,C,R,O,ne,Se){f.name="",ne!=null&&typeof ne!="function"&&typeof ne!="symbol"&&typeof ne!="boolean"?f.type=ne:f.removeAttribute("type"),g!=null?ne==="number"?(g===0&&f.value===""||f.value!=g)&&(f.value=""+Cn(g)):f.value!==""+Cn(g)&&(f.value=""+Cn(g)):ne!=="submit"&&ne!=="reset"||f.removeAttribute("value"),g!=null?mi(f,ne,Cn(g)):_!=null?mi(f,ne,Cn(_)):C!=null&&f.removeAttribute("value"),R==null&&O!=null&&(f.defaultChecked=!!O),R!=null&&(f.checked=R&&typeof R!="function"&&typeof R!="symbol"),Se!=null&&typeof Se!="function"&&typeof Se!="symbol"&&typeof Se!="boolean"?f.name=""+Cn(Se):f.removeAttribute("name")}function $n(f,g,_,C,R,O,ne,Se){if(O!=null&&typeof O!="function"&&typeof O!="symbol"&&typeof O!="boolean"&&(f.type=O),g!=null||_!=null){if(!(O!=="submit"&&O!=="reset"||g!=null)){mn(f);return}_=_!=null?""+Cn(_):"",g=g!=null?""+Cn(g):_,Se||g===f.value||(f.value=g),f.defaultValue=g}C=C??R,C=typeof C!="function"&&typeof C!="symbol"&&!!C,f.checked=Se?f.checked:!!C,f.defaultChecked=!!C,ne!=null&&typeof ne!="function"&&typeof ne!="symbol"&&typeof ne!="boolean"&&(f.name=ne),mn(f)}function mi(f,g,_){g==="number"&&Hr(f.ownerDocument)===f||f.defaultValue===""+_||(f.defaultValue=""+_)}function hr(f,g,_,C){if(f=f.options,g){g={};for(var R=0;R<_.length;R++)g["$"+_[R]]=!0;for(_=0;_"u"||typeof window.document>"u"||typeof window.document.createElement>"u"),kl=!1;if(Sa)try{var Ml={};Object.defineProperty(Ml,"passive",{get:function(){kl=!0}}),window.addEventListener("test",Ml,Ml),window.removeEventListener("test",Ml,Ml)}catch{kl=!1}var Ia=null,Si=null,rc=null;function Gr(){if(rc)return rc;var f,g=Si,_=g.length,C,R="value"in Ia?Ia.value:Ia.textContent,O=R.length;for(f=0;f<_&&g[f]===R[f];f++);var ne=_-f;for(C=1;C<=ne&&g[_-C]===R[O-C];C++);return rc=R.slice(f,1=rx),qF=" ",WF=!1;function KF(f,g){switch(f){case"keyup":return Iie.indexOf(g.keyCode)!==-1;case"keydown":return g.keyCode!==229;case"keypress":case"mousedown":case"focusout":return!0;default:return!1}}function XF(f){return f=f.detail,typeof f=="object"&&"data"in f?f.data:null}var Zm=!1;function Uie(f,g){switch(f){case"compositionend":return XF(g);case"keypress":return g.which!==32?null:(WF=!0,qF);case"textInput":return f=g.data,f===qF&&WF?null:f;default:return null}}function Bie(f,g){if(Zm)return f==="compositionend"||!pC&&KF(f,g)?(f=Gr(),rc=Si=Ia=null,Zm=!1,f):null;switch(f){case"paste":return null;case"keypress":if(!(g.ctrlKey||g.altKey||g.metaKey)||g.ctrlKey&&g.altKey){if(g.char&&1=g)return{node:_,offset:g-f};f=C}e:{for(;_;){if(_.nextSibling){_=_.nextSibling;break e}_=_.parentNode}_=void 0}_=rI(_)}}function aI(f,g){return f&&g?f===g?!0:f&&f.nodeType===3?!1:g&&g.nodeType===3?aI(f,g.parentNode):"contains"in f?f.contains(g):f.compareDocumentPosition?!!(f.compareDocumentPosition(g)&16):!1:!1}function sI(f){f=f!=null&&f.ownerDocument!=null&&f.ownerDocument.defaultView!=null?f.ownerDocument.defaultView:window;for(var g=Hr(f.document);g instanceof f.HTMLIFrameElement;){try{var _=typeof g.contentWindow.location.href=="string"}catch{_=!1}if(_)f=g.contentWindow;else break;g=Hr(f.document)}return g}function xC(f){var g=f&&f.nodeName&&f.nodeName.toLowerCase();return g&&(g==="input"&&(f.type==="text"||f.type==="search"||f.type==="tel"||f.type==="url"||f.type==="password")||g==="textarea"||f.contentEditable==="true")}var Xie=Sa&&"documentMode"in document&&11>=document.documentMode,Jm=null,yC=null,ox=null,vC=!1;function oI(f,g,_){var C=_.window===_?_.document:_.nodeType===9?_:_.ownerDocument;vC||Jm==null||Jm!==Hr(C)||(C=Jm,"selectionStart"in C&&xC(C)?C={start:C.selectionStart,end:C.selectionEnd}:(C=(C.ownerDocument&&C.ownerDocument.defaultView||window).getSelection(),C={anchorNode:C.anchorNode,anchorOffset:C.anchorOffset,focusNode:C.focusNode,focusOffset:C.focusOffset}),ox&&sx(ox,C)||(ox=C,C=jw(yC,"onSelect"),0>=ne,R-=ne,ac=1<<32-Ue(g)+R|_<An?(Qn=Yt,Yt=null):Qn=Yt.sibling;var lr=ft(tt,Yt,ct[An],St);if(lr===null){Yt===null&&(Yt=Qn);break}f&&Yt&&lr.alternate===null&&g(tt,Yt),qe=O(lr,qe,An),or===null?nn=lr:or.sibling=lr,or=lr,Yt=Qn}if(An===ct.length)return _(tt,Yt),Jn&&qc(tt,An),nn;if(Yt===null){for(;AnAn?(Qn=Yt,Yt=null):Qn=Yt.sibling;var Id=ft(tt,Yt,lr.value,St);if(Id===null){Yt===null&&(Yt=Qn);break}f&&Yt&&Id.alternate===null&&g(tt,Yt),qe=O(Id,qe,An),or===null?nn=Id:or.sibling=Id,or=Id,Yt=Qn}if(lr.done)return _(tt,Yt),Jn&&qc(tt,An),nn;if(Yt===null){for(;!lr.done;An++,lr=ct.next())lr=kt(tt,lr.value,St),lr!==null&&(qe=O(lr,qe,An),or===null?nn=lr:or.sibling=lr,or=lr);return Jn&&qc(tt,An),nn}for(Yt=C(Yt);!lr.done;An++,lr=ct.next())lr=xt(Yt,tt,An,lr.value,St),lr!==null&&(f&&lr.alternate!==null&&Yt.delete(lr.key===null?An:lr.key),qe=O(lr,qe,An),or===null?nn=lr:or.sibling=lr,or=lr);return f&&Yt.forEach(function(pse){return g(tt,pse)}),Jn&&qc(tt,An),nn}function Cr(tt,qe,ct,St){if(typeof ct=="object"&&ct!==null&&ct.type===y&&ct.key===null&&(ct=ct.props.children),typeof ct=="object"&&ct!==null){switch(ct.$$typeof){case m:e:{for(var nn=ct.key;qe!==null;){if(qe.key===nn){if(nn=ct.type,nn===y){if(qe.tag===7){_(tt,qe.sibling),St=R(qe,ct.props.children),St.return=tt,tt=St;break e}}else if(qe.elementType===nn||typeof nn=="object"&&nn!==null&&nn.$$typeof===P&&gf(nn)===qe.type){_(tt,qe.sibling),St=R(qe,ct.props),fx(St,ct),St.return=tt,tt=St;break e}_(tt,qe);break}else g(tt,qe);qe=qe.sibling}ct.type===y?(St=df(ct.props.children,tt.mode,St,ct.key),St.return=tt,tt=St):(St=Kv(ct.type,ct.key,ct.props,null,tt.mode,St),fx(St,ct),St.return=tt,tt=St)}return ne(tt);case x:e:{for(nn=ct.key;qe!==null;){if(qe.key===nn)if(qe.tag===4&&qe.stateNode.containerInfo===ct.containerInfo&&qe.stateNode.implementation===ct.implementation){_(tt,qe.sibling),St=R(qe,ct.children||[]),St.return=tt,tt=St;break e}else{_(tt,qe);break}else g(tt,qe);qe=qe.sibling}St=CC(ct,tt.mode,St),St.return=tt,tt=St}return ne(tt);case P:return ct=gf(ct),Cr(tt,qe,ct,St)}if(H(ct))return Wt(tt,qe,ct,St);if(F(ct)){if(nn=F(ct),typeof nn!="function")throw Error(r(150));return ct=nn.call(ct),dn(tt,qe,ct,St)}if(typeof ct.then=="function")return Cr(tt,qe,tw(ct),St);if(ct.$$typeof===N)return Cr(tt,qe,Qv(tt,ct),St);nw(tt,ct)}return typeof ct=="string"&&ct!==""||typeof ct=="number"||typeof ct=="bigint"?(ct=""+ct,qe!==null&&qe.tag===6?(_(tt,qe.sibling),St=R(qe,ct),St.return=tt,tt=St):(_(tt,qe),St=MC(ct,tt.mode,St),St.return=tt,tt=St),ne(tt)):_(tt,qe)}return function(tt,qe,ct,St){try{hx=0;var nn=Cr(tt,qe,ct,St);return up=null,nn}catch(Yt){if(Yt===cp||Yt===Jv)throw Yt;var or=lo(29,Yt,null,tt.mode);return or.lanes=St,or.return=tt,or}finally{}}}var xf=TI(!0),jI=TI(!1),wd=!1;function zC(f){f.updateQueue={baseState:f.memoizedState,firstBaseUpdate:null,lastBaseUpdate:null,shared:{pending:null,lanes:0,hiddenCallbacks:null},callbacks:null}}function UC(f,g){f=f.updateQueue,g.updateQueue===f&&(g.updateQueue={baseState:f.baseState,firstBaseUpdate:f.firstBaseUpdate,lastBaseUpdate:f.lastBaseUpdate,shared:f.shared,callbacks:null})}function _d(f){return{lane:f,tag:0,payload:null,callback:null,next:null}}function Sd(f,g,_){var C=f.updateQueue;if(C===null)return null;if(C=C.shared,(fr&2)!==0){var R=C.pending;return R===null?g.next=g:(g.next=R.next,R.next=g),C.pending=g,g=Wv(f),mI(f,null,_),g}return qv(f,C,g,_),Wv(f)}function mx(f,g,_){if(g=g.updateQueue,g!==null&&(g=g.shared,(_&4194048)!==0)){var C=g.lanes;C&=f.pendingLanes,_|=C,g.lanes=_,gt(f,_)}}function BC(f,g){var _=f.updateQueue,C=f.alternate;if(C!==null&&(C=C.updateQueue,_===C)){var R=null,O=null;if(_=_.firstBaseUpdate,_!==null){do{var ne={lane:_.lane,tag:_.tag,payload:_.payload,callback:null,next:null};O===null?R=O=ne:O=O.next=ne,_=_.next}while(_!==null);O===null?R=O=g:O=O.next=g}else R=O=g;_={baseState:C.baseState,firstBaseUpdate:R,lastBaseUpdate:O,shared:C.shared,callbacks:C.callbacks},f.updateQueue=_;return}f=_.lastBaseUpdate,f===null?_.firstBaseUpdate=g:f.next=g,_.lastBaseUpdate=g}var $C=!1;function px(){if($C){var f=lp;if(f!==null)throw f}}function gx(f,g,_,C){$C=!1;var R=f.updateQueue;wd=!1;var O=R.firstBaseUpdate,ne=R.lastBaseUpdate,Se=R.shared.pending;if(Se!==null){R.shared.pending=null;var Be=Se,ut=Be.next;Be.next=null,ne===null?O=ut:ne.next=ut,ne=Be;var vt=f.alternate;vt!==null&&(vt=vt.updateQueue,Se=vt.lastBaseUpdate,Se!==ne&&(Se===null?vt.firstBaseUpdate=ut:Se.next=ut,vt.lastBaseUpdate=Be))}if(O!==null){var kt=R.baseState;ne=0,vt=ut=Be=null,Se=O;do{var ft=Se.lane&-536870913,xt=ft!==Se.lane;if(xt?(Yn&ft)===ft:(C&ft)===ft){ft!==0&&ft===op&&($C=!0),vt!==null&&(vt=vt.next={lane:0,tag:Se.tag,payload:Se.payload,callback:null,next:null});e:{var Wt=f,dn=Se;ft=g;var Cr=_;switch(dn.tag){case 1:if(Wt=dn.payload,typeof Wt=="function"){kt=Wt.call(Cr,kt,ft);break e}kt=Wt;break e;case 3:Wt.flags=Wt.flags&-65537|128;case 0:if(Wt=dn.payload,ft=typeof Wt=="function"?Wt.call(Cr,kt,ft):Wt,ft==null)break e;kt=h({},kt,ft);break e;case 2:wd=!0}}ft=Se.callback,ft!==null&&(f.flags|=64,xt&&(f.flags|=8192),xt=R.callbacks,xt===null?R.callbacks=[ft]:xt.push(ft))}else xt={lane:ft,tag:Se.tag,payload:Se.payload,callback:Se.callback,next:null},vt===null?(ut=vt=xt,Be=kt):vt=vt.next=xt,ne|=ft;if(Se=Se.next,Se===null){if(Se=R.shared.pending,Se===null)break;xt=Se,Se=xt.next,xt.next=null,R.lastBaseUpdate=xt,R.shared.pending=null}}while(!0);vt===null&&(Be=kt),R.baseState=Be,R.firstBaseUpdate=ut,R.lastBaseUpdate=vt,O===null&&(R.shared.lanes=0),Ed|=ne,f.lanes=ne,f.memoizedState=kt}}function PI(f,g){if(typeof f!="function")throw Error(r(191,f));f.call(g)}function DI(f,g){var _=f.callbacks;if(_!==null)for(f.callbacks=null,f=0;f<_.length;f++)PI(_[f],g)}var dp=V(null),rw=V(0);function RI(f,g){f=ru,ie(rw,f),ie(dp,g),ru=f|g.baseLanes}function HC(){ie(rw,ru),ie(dp,dp.current)}function VC(){ru=rw.current,W(dp),W(rw)}var co=V(null),Ho=null;function Nd(f){var g=f.alternate;ie(Li,Li.current&1),ie(co,f),Ho===null&&(g===null||dp.current!==null||g.memoizedState!==null)&&(Ho=f)}function GC(f){ie(Li,Li.current),ie(co,f),Ho===null&&(Ho=f)}function LI(f){f.tag===22?(ie(Li,Li.current),ie(co,f),Ho===null&&(Ho=f)):kd()}function kd(){ie(Li,Li.current),ie(co,co.current)}function uo(f){W(co),Ho===f&&(Ho=null),W(Li)}var Li=V(0);function iw(f){for(var g=f;g!==null;){if(g.tag===13){var _=g.memoizedState;if(_!==null&&(_=_.dehydrated,_===null||Q2(_)||Z2(_)))return g}else if(g.tag===19&&(g.memoizedProps.revealOrder==="forwards"||g.memoizedProps.revealOrder==="backwards"||g.memoizedProps.revealOrder==="unstable_legacy-backwards"||g.memoizedProps.revealOrder==="together")){if((g.flags&128)!==0)return g}else if(g.child!==null){g.child.return=g,g=g.child;continue}if(g===f)break;for(;g.sibling===null;){if(g.return===null||g.return===f)return null;g=g.return}g.sibling.return=g.return,g=g.sibling}return null}var Xc=0,En=null,kr=null,Vi=null,aw=!1,hp=!1,yf=!1,sw=0,bx=0,fp=null,aae=0;function ki(){throw Error(r(321))}function qC(f,g){if(g===null)return!1;for(var _=0;_O?O:8;var ne=q.T,Se={};q.T=Se,o2(f,!1,g,_);try{var Be=R(),ut=q.S;if(ut!==null&&ut(Se,Be),Be!==null&&typeof Be=="object"&&typeof Be.then=="function"){var vt=iae(Be,C);yx(f,g,vt,mo(f))}else yx(f,g,C,mo(f))}catch(kt){yx(f,g,{then:function(){},status:"rejected",reason:kt},mo())}finally{I.p=O,ne!==null&&Se.types!==null&&(ne.types=Se.types),q.T=ne}}function uae(){}function a2(f,g,_,C){if(f.tag!==5)throw Error(r(476));var R=d5(f).queue;u5(f,R,g,K,_===null?uae:function(){return h5(f),_(C)})}function d5(f){var g=f.memoizedState;if(g!==null)return g;g={memoizedState:K,baseState:K,baseQueue:null,queue:{pending:null,lanes:0,dispatch:null,lastRenderedReducer:Yc,lastRenderedState:K},next:null};var _={};return g.next={memoizedState:_,baseState:_,baseQueue:null,queue:{pending:null,lanes:0,dispatch:null,lastRenderedReducer:Yc,lastRenderedState:_},next:null},f.memoizedState=g,f=f.alternate,f!==null&&(f.memoizedState=g),g}function h5(f){var g=d5(f);g.next===null&&(g=f.alternate.memoizedState),yx(f,g.next.queue,{},mo())}function s2(){return Ua(Ox)}function f5(){return Oi().memoizedState}function m5(){return Oi().memoizedState}function dae(f){for(var g=f.return;g!==null;){switch(g.tag){case 24:case 3:var _=mo();f=_d(_);var C=Sd(g,f,_);C!==null&&($s(C,g,_),mx(C,g,_)),g={cache:LC()},f.payload=g;return}g=g.return}}function hae(f,g,_){var C=mo();_={lane:C,revertLane:0,gesture:null,action:_,hasEagerState:!1,eagerState:null,next:null},hw(f)?g5(g,_):(_=NC(f,g,_,C),_!==null&&($s(_,f,C),b5(_,g,C)))}function p5(f,g,_){var C=mo();yx(f,g,_,C)}function yx(f,g,_,C){var R={lane:C,revertLane:0,gesture:null,action:_,hasEagerState:!1,eagerState:null,next:null};if(hw(f))g5(g,R);else{var O=f.alternate;if(f.lanes===0&&(O===null||O.lanes===0)&&(O=g.lastRenderedReducer,O!==null))try{var ne=g.lastRenderedState,Se=O(ne,_);if(R.hasEagerState=!0,R.eagerState=Se,oo(Se,ne))return qv(f,g,R,0),Fr===null&&Gv(),!1}catch{}finally{}if(_=NC(f,g,R,C),_!==null)return $s(_,f,C),b5(_,g,C),!0}return!1}function o2(f,g,_,C){if(C={lane:2,revertLane:z2(),gesture:null,action:C,hasEagerState:!1,eagerState:null,next:null},hw(f)){if(g)throw Error(r(479))}else g=NC(f,_,C,2),g!==null&&$s(g,f,2)}function hw(f){var g=f.alternate;return f===En||g!==null&&g===En}function g5(f,g){hp=aw=!0;var _=f.pending;_===null?g.next=g:(g.next=_.next,_.next=g),f.pending=g}function b5(f,g,_){if((_&4194048)!==0){var C=g.lanes;C&=f.pendingLanes,_|=C,g.lanes=_,gt(f,_)}}var vx={readContext:Ua,use:lw,useCallback:ki,useContext:ki,useEffect:ki,useImperativeHandle:ki,useLayoutEffect:ki,useInsertionEffect:ki,useMemo:ki,useReducer:ki,useRef:ki,useState:ki,useDebugValue:ki,useDeferredValue:ki,useTransition:ki,useSyncExternalStore:ki,useId:ki,useHostTransitionStatus:ki,useFormState:ki,useActionState:ki,useOptimistic:ki,useMemoCache:ki,useCacheRefresh:ki};vx.useEffectEvent=ki;var x5={readContext:Ua,use:lw,useCallback:function(f,g){return ws().memoizedState=[f,g===void 0?null:g],f},useContext:Ua,useEffect:t5,useImperativeHandle:function(f,g,_){_=_!=null?_.concat([f]):null,uw(4194308,4,a5.bind(null,g,f),_)},useLayoutEffect:function(f,g){return uw(4194308,4,f,g)},useInsertionEffect:function(f,g){uw(4,2,f,g)},useMemo:function(f,g){var _=ws();g=g===void 0?null:g;var C=f();if(yf){we(!0);try{f()}finally{we(!1)}}return _.memoizedState=[C,g],C},useReducer:function(f,g,_){var C=ws();if(_!==void 0){var R=_(g);if(yf){we(!0);try{_(g)}finally{we(!1)}}}else R=g;return C.memoizedState=C.baseState=R,f={pending:null,lanes:0,dispatch:null,lastRenderedReducer:f,lastRenderedState:R},C.queue=f,f=f.dispatch=hae.bind(null,En,f),[C.memoizedState,f]},useRef:function(f){var g=ws();return f={current:f},g.memoizedState=f},useState:function(f){f=e2(f);var g=f.queue,_=p5.bind(null,En,g);return g.dispatch=_,[f.memoizedState,_]},useDebugValue:r2,useDeferredValue:function(f,g){var _=ws();return i2(_,f,g)},useTransition:function(){var f=e2(!1);return f=u5.bind(null,En,f.queue,!0,!1),ws().memoizedState=f,[!1,f]},useSyncExternalStore:function(f,g,_){var C=En,R=ws();if(Jn){if(_===void 0)throw Error(r(407));_=_()}else{if(_=g(),Fr===null)throw Error(r(349));(Yn&127)!==0||zI(C,g,_)}R.memoizedState=_;var O={value:_,getSnapshot:g};return R.queue=O,t5(BI.bind(null,C,O,f),[f]),C.flags|=2048,mp(9,{destroy:void 0},UI.bind(null,C,O,_,g),null),_},useId:function(){var f=ws(),g=Fr.identifierPrefix;if(Jn){var _=sc,C=ac;_=(C&~(1<<32-Ue(C)-1)).toString(32)+_,g="_"+g+"R_"+_,_=sw++,0<_&&(g+="H"+_.toString(32)),g+="_"}else _=aae++,g="_"+g+"r_"+_.toString(32)+"_";return f.memoizedState=g},useHostTransitionStatus:s2,useFormState:YI,useActionState:YI,useOptimistic:function(f){var g=ws();g.memoizedState=g.baseState=f;var _={pending:null,lanes:0,dispatch:null,lastRenderedReducer:null,lastRenderedState:null};return g.queue=_,g=o2.bind(null,En,!0,_),_.dispatch=g,[f,g]},useMemoCache:QC,useCacheRefresh:function(){return ws().memoizedState=dae.bind(null,En)},useEffectEvent:function(f){var g=ws(),_={impl:f};return g.memoizedState=_,function(){if((fr&2)!==0)throw Error(r(440));return _.impl.apply(void 0,arguments)}}},l2={readContext:Ua,use:lw,useCallback:o5,useContext:Ua,useEffect:n2,useImperativeHandle:s5,useInsertionEffect:r5,useLayoutEffect:i5,useMemo:l5,useReducer:cw,useRef:e5,useState:function(){return cw(Yc)},useDebugValue:r2,useDeferredValue:function(f,g){var _=Oi();return c5(_,kr.memoizedState,f,g)},useTransition:function(){var f=cw(Yc)[0],g=Oi().memoizedState;return[typeof f=="boolean"?f:xx(f),g]},useSyncExternalStore:II,useId:f5,useHostTransitionStatus:s2,useFormState:QI,useActionState:QI,useOptimistic:function(f,g){var _=Oi();return VI(_,kr,f,g)},useMemoCache:QC,useCacheRefresh:m5};l2.useEffectEvent=n5;var y5={readContext:Ua,use:lw,useCallback:o5,useContext:Ua,useEffect:n2,useImperativeHandle:s5,useInsertionEffect:r5,useLayoutEffect:i5,useMemo:l5,useReducer:JC,useRef:e5,useState:function(){return JC(Yc)},useDebugValue:r2,useDeferredValue:function(f,g){var _=Oi();return kr===null?i2(_,f,g):c5(_,kr.memoizedState,f,g)},useTransition:function(){var f=JC(Yc)[0],g=Oi().memoizedState;return[typeof f=="boolean"?f:xx(f),g]},useSyncExternalStore:II,useId:f5,useHostTransitionStatus:s2,useFormState:JI,useActionState:JI,useOptimistic:function(f,g){var _=Oi();return kr!==null?VI(_,kr,f,g):(_.baseState=f,[f,_.queue.dispatch])},useMemoCache:QC,useCacheRefresh:m5};y5.useEffectEvent=n5;function c2(f,g,_,C){g=f.memoizedState,_=_(C,g),_=_==null?g:h({},g,_),f.memoizedState=_,f.lanes===0&&(f.updateQueue.baseState=_)}var u2={enqueueSetState:function(f,g,_){f=f._reactInternals;var C=mo(),R=_d(C);R.payload=g,_!=null&&(R.callback=_),g=Sd(f,R,C),g!==null&&($s(g,f,C),mx(g,f,C))},enqueueReplaceState:function(f,g,_){f=f._reactInternals;var C=mo(),R=_d(C);R.tag=1,R.payload=g,_!=null&&(R.callback=_),g=Sd(f,R,C),g!==null&&($s(g,f,C),mx(g,f,C))},enqueueForceUpdate:function(f,g){f=f._reactInternals;var _=mo(),C=_d(_);C.tag=2,g!=null&&(C.callback=g),g=Sd(f,C,_),g!==null&&($s(g,f,_),mx(g,f,_))}};function v5(f,g,_,C,R,O,ne){return f=f.stateNode,typeof f.shouldComponentUpdate=="function"?f.shouldComponentUpdate(C,O,ne):g.prototype&&g.prototype.isPureReactComponent?!sx(_,C)||!sx(R,O):!0}function w5(f,g,_,C){f=g.state,typeof g.componentWillReceiveProps=="function"&&g.componentWillReceiveProps(_,C),typeof g.UNSAFE_componentWillReceiveProps=="function"&&g.UNSAFE_componentWillReceiveProps(_,C),g.state!==f&&u2.enqueueReplaceState(g,g.state,null)}function vf(f,g){var _=g;if("ref"in g){_={};for(var C in g)C!=="ref"&&(_[C]=g[C])}if(f=f.defaultProps){_===g&&(_=h({},_));for(var R in f)_[R]===void 0&&(_[R]=f[R])}return _}function _5(f){Vv(f)}function S5(f){console.error(f)}function N5(f){Vv(f)}function fw(f,g){try{var _=f.onUncaughtError;_(g.value,{componentStack:g.stack})}catch(C){setTimeout(function(){throw C})}}function k5(f,g,_){try{var C=f.onCaughtError;C(_.value,{componentStack:_.stack,errorBoundary:g.tag===1?g.stateNode:null})}catch(R){setTimeout(function(){throw R})}}function d2(f,g,_){return _=_d(_),_.tag=3,_.payload={element:null},_.callback=function(){fw(f,g)},_}function M5(f){return f=_d(f),f.tag=3,f}function C5(f,g,_,C){var R=_.type.getDerivedStateFromError;if(typeof R=="function"){var O=C.value;f.payload=function(){return R(O)},f.callback=function(){k5(g,_,C)}}var ne=_.stateNode;ne!==null&&typeof ne.componentDidCatch=="function"&&(f.callback=function(){k5(g,_,C),typeof R!="function"&&(Ad===null?Ad=new Set([this]):Ad.add(this));var Se=C.stack;this.componentDidCatch(C.value,{componentStack:Se!==null?Se:""})})}function fae(f,g,_,C,R){if(_.flags|=32768,C!==null&&typeof C=="object"&&typeof C.then=="function"){if(g=_.alternate,g!==null&&sp(g,_,R,!0),_=co.current,_!==null){switch(_.tag){case 31:case 13:return Ho===null?kw():_.alternate===null&&Mi===0&&(Mi=3),_.flags&=-257,_.flags|=65536,_.lanes=R,C===ew?_.flags|=16384:(g=_.updateQueue,g===null?_.updateQueue=new Set([C]):g.add(C),O2(f,C,R)),!1;case 22:return _.flags|=65536,C===ew?_.flags|=16384:(g=_.updateQueue,g===null?(g={transitions:null,markerInstances:null,retryQueue:new Set([C])},_.updateQueue=g):(_=g.retryQueue,_===null?g.retryQueue=new Set([C]):_.add(C)),O2(f,C,R)),!1}throw Error(r(435,_.tag))}return O2(f,C,R),kw(),!1}if(Jn)return g=co.current,g!==null?((g.flags&65536)===0&&(g.flags|=256),g.flags|=65536,g.lanes=R,C!==TC&&(f=Error(r(422),{cause:C}),cx(zo(f,_)))):(C!==TC&&(g=Error(r(423),{cause:C}),cx(zo(g,_))),f=f.current.alternate,f.flags|=65536,R&=-R,f.lanes|=R,C=zo(C,_),R=d2(f.stateNode,C,R),BC(f,R),Mi!==4&&(Mi=2)),!1;var O=Error(r(520),{cause:C});if(O=zo(O,_),Ex===null?Ex=[O]:Ex.push(O),Mi!==4&&(Mi=2),g===null)return!0;C=zo(C,_),_=g;do{switch(_.tag){case 3:return _.flags|=65536,f=R&-R,_.lanes|=f,f=d2(_.stateNode,C,f),BC(_,f),!1;case 1:if(g=_.type,O=_.stateNode,(_.flags&128)===0&&(typeof g.getDerivedStateFromError=="function"||O!==null&&typeof O.componentDidCatch=="function"&&(Ad===null||!Ad.has(O))))return _.flags|=65536,R&=-R,_.lanes|=R,R=M5(R),C5(R,f,_,C),BC(_,R),!1}_=_.return}while(_!==null);return!1}var h2=Error(r(461)),Gi=!1;function Ba(f,g,_,C){g.child=f===null?jI(g,null,_,C):xf(g,f.child,_,C)}function E5(f,g,_,C,R){_=_.render;var O=g.ref;if("ref"in C){var ne={};for(var Se in C)Se!=="ref"&&(ne[Se]=C[Se])}else ne=C;return mf(g),C=WC(f,g,_,ne,O,R),Se=KC(),f!==null&&!Gi?(XC(f,g,R),Qc(f,g,R)):(Jn&&Se&&EC(g),g.flags|=1,Ba(f,g,C,R),g.child)}function A5(f,g,_,C,R){if(f===null){var O=_.type;return typeof O=="function"&&!kC(O)&&O.defaultProps===void 0&&_.compare===null?(g.tag=15,g.type=O,T5(f,g,O,C,R)):(f=Kv(_.type,null,C,g,g.mode,R),f.ref=g.ref,f.return=g,g.child=f)}if(O=f.child,!v2(f,R)){var ne=O.memoizedProps;if(_=_.compare,_=_!==null?_:sx,_(ne,C)&&f.ref===g.ref)return Qc(f,g,R)}return g.flags|=1,f=Gc(O,C),f.ref=g.ref,f.return=g,g.child=f}function T5(f,g,_,C,R){if(f!==null){var O=f.memoizedProps;if(sx(O,C)&&f.ref===g.ref)if(Gi=!1,g.pendingProps=C=O,v2(f,R))(f.flags&131072)!==0&&(Gi=!0);else return g.lanes=f.lanes,Qc(f,g,R)}return f2(f,g,_,C,R)}function j5(f,g,_,C){var R=C.children,O=f!==null?f.memoizedState:null;if(f===null&&g.stateNode===null&&(g.stateNode={_visibility:1,_pendingMarkers:null,_retryCache:null,_transitions:null}),C.mode==="hidden"){if((g.flags&128)!==0){if(O=O!==null?O.baseLanes|_:_,f!==null){for(C=g.child=f.child,R=0;C!==null;)R=R|C.lanes|C.childLanes,C=C.sibling;C=R&~O}else C=0,g.child=null;return P5(f,g,O,_,C)}if((_&536870912)!==0)g.memoizedState={baseLanes:0,cachePool:null},f!==null&&Zv(g,O!==null?O.cachePool:null),O!==null?RI(g,O):HC(),LI(g);else return C=g.lanes=536870912,P5(f,g,O!==null?O.baseLanes|_:_,_,C)}else O!==null?(Zv(g,O.cachePool),RI(g,O),kd(),g.memoizedState=null):(f!==null&&Zv(g,null),HC(),kd());return Ba(f,g,R,_),g.child}function wx(f,g){return f!==null&&f.tag===22||g.stateNode!==null||(g.stateNode={_visibility:1,_pendingMarkers:null,_retryCache:null,_transitions:null}),g.sibling}function P5(f,g,_,C,R){var O=FC();return O=O===null?null:{parent:Hi._currentValue,pool:O},g.memoizedState={baseLanes:_,cachePool:O},f!==null&&Zv(g,null),HC(),LI(g),f!==null&&sp(f,g,C,!0),g.childLanes=R,null}function mw(f,g){return g=gw({mode:g.mode,children:g.children},f.mode),g.ref=f.ref,f.child=g,g.return=f,g}function D5(f,g,_){return xf(g,f.child,null,_),f=mw(g,g.pendingProps),f.flags|=2,uo(g),g.memoizedState=null,f}function mae(f,g,_){var C=g.pendingProps,R=(g.flags&128)!==0;if(g.flags&=-129,f===null){if(Jn){if(C.mode==="hidden")return f=mw(g,C),g.lanes=536870912,wx(null,f);if(GC(g),(f=Zr)?(f=Gz(f,$o),f=f!==null&&f.data==="&"?f:null,f!==null&&(g.memoizedState={dehydrated:f,treeContext:bd!==null?{id:ac,overflow:sc}:null,retryLane:536870912,hydrationErrors:null},_=gI(f),_.return=g,g.child=_,za=g,Zr=null)):f=null,f===null)throw yd(g);return g.lanes=536870912,null}return mw(g,C)}var O=f.memoizedState;if(O!==null){var ne=O.dehydrated;if(GC(g),R)if(g.flags&256)g.flags&=-257,g=D5(f,g,_);else if(g.memoizedState!==null)g.child=f.child,g.flags|=128,g=null;else throw Error(r(558));else if(Gi||sp(f,g,_,!1),R=(_&f.childLanes)!==0,Gi||R){if(C=Fr,C!==null&&(ne=Qt(C,_),ne!==0&&ne!==O.retryLane))throw O.retryLane=ne,uf(f,ne),$s(C,f,ne),h2;kw(),g=D5(f,g,_)}else f=O.treeContext,Zr=Vo(ne.nextSibling),za=g,Jn=!0,xd=null,$o=!1,f!==null&&yI(g,f),g=mw(g,C),g.flags|=4096;return g}return f=Gc(f.child,{mode:C.mode,children:C.children}),f.ref=g.ref,g.child=f,f.return=g,f}function pw(f,g){var _=g.ref;if(_===null)f!==null&&f.ref!==null&&(g.flags|=4194816);else{if(typeof _!="function"&&typeof _!="object")throw Error(r(284));(f===null||f.ref!==_)&&(g.flags|=4194816)}}function f2(f,g,_,C,R){return mf(g),_=WC(f,g,_,C,void 0,R),C=KC(),f!==null&&!Gi?(XC(f,g,R),Qc(f,g,R)):(Jn&&C&&EC(g),g.flags|=1,Ba(f,g,_,R),g.child)}function R5(f,g,_,C,R,O){return mf(g),g.updateQueue=null,_=FI(g,C,_,R),OI(f),C=KC(),f!==null&&!Gi?(XC(f,g,O),Qc(f,g,O)):(Jn&&C&&EC(g),g.flags|=1,Ba(f,g,_,O),g.child)}function L5(f,g,_,C,R){if(mf(g),g.stateNode===null){var O=np,ne=_.contextType;typeof ne=="object"&&ne!==null&&(O=Ua(ne)),O=new _(C,O),g.memoizedState=O.state!==null&&O.state!==void 0?O.state:null,O.updater=u2,g.stateNode=O,O._reactInternals=g,O=g.stateNode,O.props=C,O.state=g.memoizedState,O.refs={},zC(g),ne=_.contextType,O.context=typeof ne=="object"&&ne!==null?Ua(ne):np,O.state=g.memoizedState,ne=_.getDerivedStateFromProps,typeof ne=="function"&&(c2(g,_,ne,C),O.state=g.memoizedState),typeof _.getDerivedStateFromProps=="function"||typeof O.getSnapshotBeforeUpdate=="function"||typeof O.UNSAFE_componentWillMount!="function"&&typeof O.componentWillMount!="function"||(ne=O.state,typeof O.componentWillMount=="function"&&O.componentWillMount(),typeof O.UNSAFE_componentWillMount=="function"&&O.UNSAFE_componentWillMount(),ne!==O.state&&u2.enqueueReplaceState(O,O.state,null),gx(g,C,O,R),px(),O.state=g.memoizedState),typeof O.componentDidMount=="function"&&(g.flags|=4194308),C=!0}else if(f===null){O=g.stateNode;var Se=g.memoizedProps,Be=vf(_,Se);O.props=Be;var ut=O.context,vt=_.contextType;ne=np,typeof vt=="object"&&vt!==null&&(ne=Ua(vt));var kt=_.getDerivedStateFromProps;vt=typeof kt=="function"||typeof O.getSnapshotBeforeUpdate=="function",Se=g.pendingProps!==Se,vt||typeof O.UNSAFE_componentWillReceiveProps!="function"&&typeof O.componentWillReceiveProps!="function"||(Se||ut!==ne)&&w5(g,O,C,ne),wd=!1;var ft=g.memoizedState;O.state=ft,gx(g,C,O,R),px(),ut=g.memoizedState,Se||ft!==ut||wd?(typeof kt=="function"&&(c2(g,_,kt,C),ut=g.memoizedState),(Be=wd||v5(g,_,Be,C,ft,ut,ne))?(vt||typeof O.UNSAFE_componentWillMount!="function"&&typeof O.componentWillMount!="function"||(typeof O.componentWillMount=="function"&&O.componentWillMount(),typeof O.UNSAFE_componentWillMount=="function"&&O.UNSAFE_componentWillMount()),typeof O.componentDidMount=="function"&&(g.flags|=4194308)):(typeof O.componentDidMount=="function"&&(g.flags|=4194308),g.memoizedProps=C,g.memoizedState=ut),O.props=C,O.state=ut,O.context=ne,C=Be):(typeof O.componentDidMount=="function"&&(g.flags|=4194308),C=!1)}else{O=g.stateNode,UC(f,g),ne=g.memoizedProps,vt=vf(_,ne),O.props=vt,kt=g.pendingProps,ft=O.context,ut=_.contextType,Be=np,typeof ut=="object"&&ut!==null&&(Be=Ua(ut)),Se=_.getDerivedStateFromProps,(ut=typeof Se=="function"||typeof O.getSnapshotBeforeUpdate=="function")||typeof O.UNSAFE_componentWillReceiveProps!="function"&&typeof O.componentWillReceiveProps!="function"||(ne!==kt||ft!==Be)&&w5(g,O,C,Be),wd=!1,ft=g.memoizedState,O.state=ft,gx(g,C,O,R),px();var xt=g.memoizedState;ne!==kt||ft!==xt||wd||f!==null&&f.dependencies!==null&&Yv(f.dependencies)?(typeof Se=="function"&&(c2(g,_,Se,C),xt=g.memoizedState),(vt=wd||v5(g,_,vt,C,ft,xt,Be)||f!==null&&f.dependencies!==null&&Yv(f.dependencies))?(ut||typeof O.UNSAFE_componentWillUpdate!="function"&&typeof O.componentWillUpdate!="function"||(typeof O.componentWillUpdate=="function"&&O.componentWillUpdate(C,xt,Be),typeof O.UNSAFE_componentWillUpdate=="function"&&O.UNSAFE_componentWillUpdate(C,xt,Be)),typeof O.componentDidUpdate=="function"&&(g.flags|=4),typeof O.getSnapshotBeforeUpdate=="function"&&(g.flags|=1024)):(typeof O.componentDidUpdate!="function"||ne===f.memoizedProps&&ft===f.memoizedState||(g.flags|=4),typeof O.getSnapshotBeforeUpdate!="function"||ne===f.memoizedProps&&ft===f.memoizedState||(g.flags|=1024),g.memoizedProps=C,g.memoizedState=xt),O.props=C,O.state=xt,O.context=Be,C=vt):(typeof O.componentDidUpdate!="function"||ne===f.memoizedProps&&ft===f.memoizedState||(g.flags|=4),typeof O.getSnapshotBeforeUpdate!="function"||ne===f.memoizedProps&&ft===f.memoizedState||(g.flags|=1024),C=!1)}return O=C,pw(f,g),C=(g.flags&128)!==0,O||C?(O=g.stateNode,_=C&&typeof _.getDerivedStateFromError!="function"?null:O.render(),g.flags|=1,f!==null&&C?(g.child=xf(g,f.child,null,R),g.child=xf(g,null,_,R)):Ba(f,g,_,R),g.memoizedState=O.state,f=g.child):f=Qc(f,g,R),f}function O5(f,g,_,C){return hf(),g.flags|=256,Ba(f,g,_,C),g.child}var m2={dehydrated:null,treeContext:null,retryLane:0,hydrationErrors:null};function p2(f){return{baseLanes:f,cachePool:kI()}}function g2(f,g,_){return f=f!==null?f.childLanes&~_:0,g&&(f|=fo),f}function F5(f,g,_){var C=g.pendingProps,R=!1,O=(g.flags&128)!==0,ne;if((ne=O)||(ne=f!==null&&f.memoizedState===null?!1:(Li.current&2)!==0),ne&&(R=!0,g.flags&=-129),ne=(g.flags&32)!==0,g.flags&=-33,f===null){if(Jn){if(R?Nd(g):kd(),(f=Zr)?(f=Gz(f,$o),f=f!==null&&f.data!=="&"?f:null,f!==null&&(g.memoizedState={dehydrated:f,treeContext:bd!==null?{id:ac,overflow:sc}:null,retryLane:536870912,hydrationErrors:null},_=gI(f),_.return=g,g.child=_,za=g,Zr=null)):f=null,f===null)throw yd(g);return Z2(f)?g.lanes=32:g.lanes=536870912,null}var Se=C.children;return C=C.fallback,R?(kd(),R=g.mode,Se=gw({mode:"hidden",children:Se},R),C=df(C,R,_,null),Se.return=g,C.return=g,Se.sibling=C,g.child=Se,C=g.child,C.memoizedState=p2(_),C.childLanes=g2(f,ne,_),g.memoizedState=m2,wx(null,C)):(Nd(g),b2(g,Se))}var Be=f.memoizedState;if(Be!==null&&(Se=Be.dehydrated,Se!==null)){if(O)g.flags&256?(Nd(g),g.flags&=-257,g=x2(f,g,_)):g.memoizedState!==null?(kd(),g.child=f.child,g.flags|=128,g=null):(kd(),Se=C.fallback,R=g.mode,C=gw({mode:"visible",children:C.children},R),Se=df(Se,R,_,null),Se.flags|=2,C.return=g,Se.return=g,C.sibling=Se,g.child=C,xf(g,f.child,null,_),C=g.child,C.memoizedState=p2(_),C.childLanes=g2(f,ne,_),g.memoizedState=m2,g=wx(null,C));else if(Nd(g),Z2(Se)){if(ne=Se.nextSibling&&Se.nextSibling.dataset,ne)var ut=ne.dgst;ne=ut,C=Error(r(419)),C.stack="",C.digest=ne,cx({value:C,source:null,stack:null}),g=x2(f,g,_)}else if(Gi||sp(f,g,_,!1),ne=(_&f.childLanes)!==0,Gi||ne){if(ne=Fr,ne!==null&&(C=Qt(ne,_),C!==0&&C!==Be.retryLane))throw Be.retryLane=C,uf(f,C),$s(ne,f,C),h2;Q2(Se)||kw(),g=x2(f,g,_)}else Q2(Se)?(g.flags|=192,g.child=f.child,g=null):(f=Be.treeContext,Zr=Vo(Se.nextSibling),za=g,Jn=!0,xd=null,$o=!1,f!==null&&yI(g,f),g=b2(g,C.children),g.flags|=4096);return g}return R?(kd(),Se=C.fallback,R=g.mode,Be=f.child,ut=Be.sibling,C=Gc(Be,{mode:"hidden",children:C.children}),C.subtreeFlags=Be.subtreeFlags&65011712,ut!==null?Se=Gc(ut,Se):(Se=df(Se,R,_,null),Se.flags|=2),Se.return=g,C.return=g,C.sibling=Se,g.child=C,wx(null,C),C=g.child,Se=f.child.memoizedState,Se===null?Se=p2(_):(R=Se.cachePool,R!==null?(Be=Hi._currentValue,R=R.parent!==Be?{parent:Be,pool:Be}:R):R=kI(),Se={baseLanes:Se.baseLanes|_,cachePool:R}),C.memoizedState=Se,C.childLanes=g2(f,ne,_),g.memoizedState=m2,wx(f.child,C)):(Nd(g),_=f.child,f=_.sibling,_=Gc(_,{mode:"visible",children:C.children}),_.return=g,_.sibling=null,f!==null&&(ne=g.deletions,ne===null?(g.deletions=[f],g.flags|=16):ne.push(f)),g.child=_,g.memoizedState=null,_)}function b2(f,g){return g=gw({mode:"visible",children:g},f.mode),g.return=f,f.child=g}function gw(f,g){return f=lo(22,f,null,g),f.lanes=0,f}function x2(f,g,_){return xf(g,f.child,null,_),f=b2(g,g.pendingProps.children),f.flags|=2,g.memoizedState=null,f}function I5(f,g,_){f.lanes|=g;var C=f.alternate;C!==null&&(C.lanes|=g),DC(f.return,g,_)}function y2(f,g,_,C,R,O){var ne=f.memoizedState;ne===null?f.memoizedState={isBackwards:g,rendering:null,renderingStartTime:0,last:C,tail:_,tailMode:R,treeForkCount:O}:(ne.isBackwards=g,ne.rendering=null,ne.renderingStartTime=0,ne.last=C,ne.tail=_,ne.tailMode=R,ne.treeForkCount=O)}function z5(f,g,_){var C=g.pendingProps,R=C.revealOrder,O=C.tail;C=C.children;var ne=Li.current,Se=(ne&2)!==0;if(Se?(ne=ne&1|2,g.flags|=128):ne&=1,ie(Li,ne),Ba(f,g,C,_),C=Jn?lx:0,!Se&&f!==null&&(f.flags&128)!==0)e:for(f=g.child;f!==null;){if(f.tag===13)f.memoizedState!==null&&I5(f,_,g);else if(f.tag===19)I5(f,_,g);else if(f.child!==null){f.child.return=f,f=f.child;continue}if(f===g)break e;for(;f.sibling===null;){if(f.return===null||f.return===g)break e;f=f.return}f.sibling.return=f.return,f=f.sibling}switch(R){case"forwards":for(_=g.child,R=null;_!==null;)f=_.alternate,f!==null&&iw(f)===null&&(R=_),_=_.sibling;_=R,_===null?(R=g.child,g.child=null):(R=_.sibling,_.sibling=null),y2(g,!1,R,_,O,C);break;case"backwards":case"unstable_legacy-backwards":for(_=null,R=g.child,g.child=null;R!==null;){if(f=R.alternate,f!==null&&iw(f)===null){g.child=R;break}f=R.sibling,R.sibling=_,_=R,R=f}y2(g,!0,_,null,O,C);break;case"together":y2(g,!1,null,null,void 0,C);break;default:g.memoizedState=null}return g.child}function Qc(f,g,_){if(f!==null&&(g.dependencies=f.dependencies),Ed|=g.lanes,(_&g.childLanes)===0)if(f!==null){if(sp(f,g,_,!1),(_&g.childLanes)===0)return null}else return null;if(f!==null&&g.child!==f.child)throw Error(r(153));if(g.child!==null){for(f=g.child,_=Gc(f,f.pendingProps),g.child=_,_.return=g;f.sibling!==null;)f=f.sibling,_=_.sibling=Gc(f,f.pendingProps),_.return=g;_.sibling=null}return g.child}function v2(f,g){return(f.lanes&g)!==0?!0:(f=f.dependencies,!!(f!==null&&Yv(f)))}function pae(f,g,_){switch(g.tag){case 3:Le(g,g.stateNode.containerInfo),vd(g,Hi,f.memoizedState.cache),hf();break;case 27:case 5:ce(g);break;case 4:Le(g,g.stateNode.containerInfo);break;case 10:vd(g,g.type,g.memoizedProps.value);break;case 31:if(g.memoizedState!==null)return g.flags|=128,GC(g),null;break;case 13:var C=g.memoizedState;if(C!==null)return C.dehydrated!==null?(Nd(g),g.flags|=128,null):(_&g.child.childLanes)!==0?F5(f,g,_):(Nd(g),f=Qc(f,g,_),f!==null?f.sibling:null);Nd(g);break;case 19:var R=(f.flags&128)!==0;if(C=(_&g.childLanes)!==0,C||(sp(f,g,_,!1),C=(_&g.childLanes)!==0),R){if(C)return z5(f,g,_);g.flags|=128}if(R=g.memoizedState,R!==null&&(R.rendering=null,R.tail=null,R.lastEffect=null),ie(Li,Li.current),C)break;return null;case 22:return g.lanes=0,j5(f,g,_,g.pendingProps);case 24:vd(g,Hi,f.memoizedState.cache)}return Qc(f,g,_)}function U5(f,g,_){if(f!==null)if(f.memoizedProps!==g.pendingProps)Gi=!0;else{if(!v2(f,_)&&(g.flags&128)===0)return Gi=!1,pae(f,g,_);Gi=(f.flags&131072)!==0}else Gi=!1,Jn&&(g.flags&1048576)!==0&&xI(g,lx,g.index);switch(g.lanes=0,g.tag){case 16:e:{var C=g.pendingProps;if(f=gf(g.elementType),g.type=f,typeof f=="function")kC(f)?(C=vf(f,C),g.tag=1,g=L5(null,g,f,C,_)):(g.tag=0,g=f2(null,g,f,C,_));else{if(f!=null){var R=f.$$typeof;if(R===E){g.tag=11,g=E5(null,g,f,C,_);break e}else if(R===M){g.tag=14,g=A5(null,g,f,C,_);break e}}throw g=D(f)||f,Error(r(306,g,""))}}return g;case 0:return f2(f,g,g.type,g.pendingProps,_);case 1:return C=g.type,R=vf(C,g.pendingProps),L5(f,g,C,R,_);case 3:e:{if(Le(g,g.stateNode.containerInfo),f===null)throw Error(r(387));C=g.pendingProps;var O=g.memoizedState;R=O.element,UC(f,g),gx(g,C,null,_);var ne=g.memoizedState;if(C=ne.cache,vd(g,Hi,C),C!==O.cache&&RC(g,[Hi],_,!0),px(),C=ne.element,O.isDehydrated)if(O={element:C,isDehydrated:!1,cache:ne.cache},g.updateQueue.baseState=O,g.memoizedState=O,g.flags&256){g=O5(f,g,C,_);break e}else if(C!==R){R=zo(Error(r(424)),g),cx(R),g=O5(f,g,C,_);break e}else{switch(f=g.stateNode.containerInfo,f.nodeType){case 9:f=f.body;break;default:f=f.nodeName==="HTML"?f.ownerDocument.body:f}for(Zr=Vo(f.firstChild),za=g,Jn=!0,xd=null,$o=!0,_=jI(g,null,C,_),g.child=_;_;)_.flags=_.flags&-3|4096,_=_.sibling}else{if(hf(),C===R){g=Qc(f,g,_);break e}Ba(f,g,C,_)}g=g.child}return g;case 26:return pw(f,g),f===null?(_=Qz(g.type,null,g.pendingProps,null))?g.memoizedState=_:Jn||(_=g.type,f=g.pendingProps,C=Pw(fe.current).createElement(_),C[Xt]=g,C[sr]=f,$a(C,_,f),Ge(C),g.stateNode=C):g.memoizedState=Qz(g.type,f.memoizedProps,g.pendingProps,f.memoizedState),null;case 27:return ce(g),f===null&&Jn&&(C=g.stateNode=Kz(g.type,g.pendingProps,fe.current),za=g,$o=!0,R=Zr,Dd(g.type)?(J2=R,Zr=Vo(C.firstChild)):Zr=R),Ba(f,g,g.pendingProps.children,_),pw(f,g),f===null&&(g.flags|=4194304),g.child;case 5:return f===null&&Jn&&((R=C=Zr)&&(C=Gae(C,g.type,g.pendingProps,$o),C!==null?(g.stateNode=C,za=g,Zr=Vo(C.firstChild),$o=!1,R=!0):R=!1),R||yd(g)),ce(g),R=g.type,O=g.pendingProps,ne=f!==null?f.memoizedProps:null,C=O.children,K2(R,O)?C=null:ne!==null&&K2(R,ne)&&(g.flags|=32),g.memoizedState!==null&&(R=WC(f,g,sae,null,null,_),Ox._currentValue=R),pw(f,g),Ba(f,g,C,_),g.child;case 6:return f===null&&Jn&&((f=_=Zr)&&(_=qae(_,g.pendingProps,$o),_!==null?(g.stateNode=_,za=g,Zr=null,f=!0):f=!1),f||yd(g)),null;case 13:return F5(f,g,_);case 4:return Le(g,g.stateNode.containerInfo),C=g.pendingProps,f===null?g.child=xf(g,null,C,_):Ba(f,g,C,_),g.child;case 11:return E5(f,g,g.type,g.pendingProps,_);case 7:return Ba(f,g,g.pendingProps,_),g.child;case 8:return Ba(f,g,g.pendingProps.children,_),g.child;case 12:return Ba(f,g,g.pendingProps.children,_),g.child;case 10:return C=g.pendingProps,vd(g,g.type,C.value),Ba(f,g,C.children,_),g.child;case 9:return R=g.type._context,C=g.pendingProps.children,mf(g),R=Ua(R),C=C(R),g.flags|=1,Ba(f,g,C,_),g.child;case 14:return A5(f,g,g.type,g.pendingProps,_);case 15:return T5(f,g,g.type,g.pendingProps,_);case 19:return z5(f,g,_);case 31:return mae(f,g,_);case 22:return j5(f,g,_,g.pendingProps);case 24:return mf(g),C=Ua(Hi),f===null?(R=FC(),R===null&&(R=Fr,O=LC(),R.pooledCache=O,O.refCount++,O!==null&&(R.pooledCacheLanes|=_),R=O),g.memoizedState={parent:C,cache:R},zC(g),vd(g,Hi,R)):((f.lanes&_)!==0&&(UC(f,g),gx(g,null,null,_),px()),R=f.memoizedState,O=g.memoizedState,R.parent!==C?(R={parent:C,cache:C},g.memoizedState=R,g.lanes===0&&(g.memoizedState=g.updateQueue.baseState=R),vd(g,Hi,C)):(C=O.cache,vd(g,Hi,C),C!==R.cache&&RC(g,[Hi],_,!0))),Ba(f,g,g.pendingProps.children,_),g.child;case 29:throw g.pendingProps}throw Error(r(156,g.tag))}function Zc(f){f.flags|=4}function w2(f,g,_,C,R){if((g=(f.mode&32)!==0)&&(g=!1),g){if(f.flags|=16777216,(R&335544128)===R)if(f.stateNode.complete)f.flags|=8192;else if(fz())f.flags|=8192;else throw bf=ew,IC}else f.flags&=-16777217}function B5(f,g){if(g.type!=="stylesheet"||(g.state.loading&4)!==0)f.flags&=-16777217;else if(f.flags|=16777216,!nU(g))if(fz())f.flags|=8192;else throw bf=ew,IC}function bw(f,g){g!==null&&(f.flags|=4),f.flags&16384&&(g=f.tag!==22?yt():536870912,f.lanes|=g,xp|=g)}function _x(f,g){if(!Jn)switch(f.tailMode){case"hidden":g=f.tail;for(var _=null;g!==null;)g.alternate!==null&&(_=g),g=g.sibling;_===null?f.tail=null:_.sibling=null;break;case"collapsed":_=f.tail;for(var C=null;_!==null;)_.alternate!==null&&(C=_),_=_.sibling;C===null?g||f.tail===null?f.tail=null:f.tail.sibling=null:C.sibling=null}}function Jr(f){var g=f.alternate!==null&&f.alternate.child===f.child,_=0,C=0;if(g)for(var R=f.child;R!==null;)_|=R.lanes|R.childLanes,C|=R.subtreeFlags&65011712,C|=R.flags&65011712,R.return=f,R=R.sibling;else for(R=f.child;R!==null;)_|=R.lanes|R.childLanes,C|=R.subtreeFlags,C|=R.flags,R.return=f,R=R.sibling;return f.subtreeFlags|=C,f.childLanes=_,g}function gae(f,g,_){var C=g.pendingProps;switch(AC(g),g.tag){case 16:case 15:case 0:case 11:case 7:case 8:case 12:case 9:case 14:return Jr(g),null;case 1:return Jr(g),null;case 3:return _=g.stateNode,C=null,f!==null&&(C=f.memoizedState.cache),g.memoizedState.cache!==C&&(g.flags|=2048),Kc(Hi),de(),_.pendingContext&&(_.context=_.pendingContext,_.pendingContext=null),(f===null||f.child===null)&&(ap(g)?Zc(g):f===null||f.memoizedState.isDehydrated&&(g.flags&256)===0||(g.flags|=1024,jC())),Jr(g),null;case 26:var R=g.type,O=g.memoizedState;return f===null?(Zc(g),O!==null?(Jr(g),B5(g,O)):(Jr(g),w2(g,R,null,C,_))):O?O!==f.memoizedState?(Zc(g),Jr(g),B5(g,O)):(Jr(g),g.flags&=-16777217):(f=f.memoizedProps,f!==C&&Zc(g),Jr(g),w2(g,R,f,C,_)),null;case 27:if(Ae(g),_=fe.current,R=g.type,f!==null&&g.stateNode!=null)f.memoizedProps!==C&&Zc(g);else{if(!C){if(g.stateNode===null)throw Error(r(166));return Jr(g),null}f=J.current,ap(g)?vI(g):(f=Kz(R,C,_),g.stateNode=f,Zc(g))}return Jr(g),null;case 5:if(Ae(g),R=g.type,f!==null&&g.stateNode!=null)f.memoizedProps!==C&&Zc(g);else{if(!C){if(g.stateNode===null)throw Error(r(166));return Jr(g),null}if(O=J.current,ap(g))vI(g);else{var ne=Pw(fe.current);switch(O){case 1:O=ne.createElementNS("http://www.w3.org/2000/svg",R);break;case 2:O=ne.createElementNS("http://www.w3.org/1998/Math/MathML",R);break;default:switch(R){case"svg":O=ne.createElementNS("http://www.w3.org/2000/svg",R);break;case"math":O=ne.createElementNS("http://www.w3.org/1998/Math/MathML",R);break;case"script":O=ne.createElement("div"),O.innerHTML=" +