diff --git a/backend/app/api/routes/archives.py b/backend/app/api/routes/archives.py index 9e103a0ee..8ff97ff22 100644 --- a/backend/app/api/routes/archives.py +++ b/backend/app/api/routes/archives.py @@ -3129,12 +3129,21 @@ async def _try_preview_slice_filaments( plate_id: int, file_path: Path, request_id: str | None = None, + bundle_id: str | None = None, + printer_name: str | None = None, + process_name: str | None = None, + filament_names: list[str] | None = None, ) -> list[dict] | None: """Run a preview slice via the user's configured sidecar so the filament list endpoint can return real per-plate filaments for unsliced project files. Returns ``None`` on any failure — the caller falls back to the painted-face heuristic. ``request_id`` flows through to the sidecar - for live progress on the SliceModal's inline spinner + toast.""" + for live progress on the SliceModal's inline spinner + toast. + + Bundle context (id + preset names) is forwarded to the preview helper + so the preview can mirror the real-print profile triplet when supplied + — see ``slice_preview.get_preview_filaments`` for the full contract. + """ from backend.app.api.routes.settings import get_setting from backend.app.services.slice_preview import get_preview_filaments @@ -3162,6 +3171,10 @@ async def _try_preview_slice_filaments( file_name=file_path.name, api_url=api_url, request_id=request_id, + bundle_id=bundle_id, + printer_name=printer_name, + process_name=process_name, + filament_names=filament_names, ) @@ -3170,6 +3183,10 @@ async def get_filament_requirements( archive_id: int, plate_id: int | None = None, request_id: str | None = None, + bundle_id: str | None = None, + printer_name: str | None = None, + process_name: str | None = None, + filament_names: str | None = None, db: AsyncSession = Depends(get_db), _: User | None = RequirePermissionIfAuthEnabled(Permission.ARCHIVES_READ), ): @@ -3181,6 +3198,12 @@ async def get_filament_requirements( Args: archive_id: The archive ID plate_id: Optional plate index to filter filaments for (for multi-plate files) + bundle_id / printer_name / process_name / filament_names: Optional + bundle context. When all four are supplied, the preview slice + (run for unsliced project files) uses ``slice_with_bundle`` + against the named preset triplet instead of the embedded- + settings fallback. ``filament_names`` is comma- or semicolon- + separated. """ import defusedxml.ElementTree as ET @@ -3284,6 +3307,11 @@ async def get_filament_requirements( project_filaments = extract_project_filaments_from_3mf(zf) used_slot_ids: set[int] = set() if project_filaments and plate_id is not None: + parsed_filament_names: list[str] | None = None + if filament_names: + parsed_filament_names = [ + n.strip() for n in filament_names.replace(";", ",").split(",") if n.strip() + ] or None preview = await _try_preview_slice_filaments( db, kind="archive", @@ -3291,6 +3319,10 @@ async def get_filament_requirements( plate_id=plate_id, file_path=file_path, request_id=request_id, + bundle_id=bundle_id, + printer_name=printer_name, + process_name=process_name, + filament_names=parsed_filament_names, ) if preview is not None: used_slot_ids = {f["slot_id"] for f in preview} diff --git a/backend/app/api/routes/library.py b/backend/app/api/routes/library.py index 23efbe1be..9d2306cb3 100644 --- a/backend/app/api/routes/library.py +++ b/backend/app/api/routes/library.py @@ -2363,6 +2363,10 @@ async def _try_preview_slice_filaments( plate_id: int, file_path: Path, request_id: str | None = None, + bundle_id: str | None = None, + printer_name: str | None = None, + process_name: str | None = None, + filament_names: list[str] | None = None, ) -> list[dict] | None: """Run a preview slice via the user's configured sidecar. Same shape as the matching helper in archives.py — see that module for rationale. @@ -2370,6 +2374,13 @@ async def _try_preview_slice_filaments( ``request_id``: when supplied, forwarded to the sidecar so the SliceModal's inline spinner + toast can poll the matching progress endpoint and show "Generating G-code (45%)" for the preview as well. + + ``bundle_id`` / ``printer_name`` / ``process_name`` / ``filament_names``: + when all are supplied, the preview uses ``slice_with_bundle`` against + the named bundle's preset triplet so the preview's gram numbers reflect + the same profiles the real print will use. Partial context falls back + to the embedded-settings path so a half-completed Bundle-tier selection + in the modal doesn't error out. """ from backend.app.api.routes.settings import get_setting from backend.app.services.slice_preview import get_preview_filaments @@ -2398,6 +2409,10 @@ async def _try_preview_slice_filaments( file_name=file_path.name, api_url=api_url, request_id=request_id, + bundle_id=bundle_id, + printer_name=printer_name, + process_name=process_name, + filament_names=filament_names, ) @@ -2406,6 +2421,10 @@ async def get_library_file_filament_requirements( file_id: int, plate_id: int | None = None, request_id: str | None = None, + bundle_id: str | None = None, + printer_name: str | None = None, + process_name: str | None = None, + filament_names: str | None = None, db: AsyncSession = Depends(get_db), _: User | None = Depends(require_permission_if_auth_enabled(Permission.LIBRARY_READ)), ): @@ -2417,6 +2436,12 @@ async def get_library_file_filament_requirements( Args: file_id: The library file ID plate_id: Optional plate index to get filaments for a specific plate + bundle_id / printer_name / process_name / filament_names: Optional + bundle context. When all four are supplied, the preview slice + (run for unsliced project files) uses ``slice_with_bundle`` + against the named preset triplet instead of the embedded- + settings fallback. ``filament_names`` is comma- or semicolon- + separated to mirror the slice route's multi-color form. """ import defusedxml.ElementTree as ET @@ -2536,6 +2561,14 @@ async def get_library_file_filament_requirements( project_filaments = extract_project_filaments_from_3mf(zf) used_slot_ids: set[int] = set() if project_filaments and plate_id is not None: + # Bundle context flows through optional query params so + # callers without a Bundle-tier selection (the common + # case) hit the same path as before. + parsed_filament_names: list[str] | None = None + if filament_names: + parsed_filament_names = [ + n.strip() for n in filament_names.replace(";", ",").split(",") if n.strip() + ] or None preview = await _try_preview_slice_filaments( db, kind="library_file", @@ -2543,6 +2576,10 @@ async def get_library_file_filament_requirements( plate_id=plate_id, file_path=file_path, request_id=request_id, + bundle_id=bundle_id, + printer_name=printer_name, + process_name=process_name, + filament_names=parsed_filament_names, ) if preview is not None: used_slot_ids = {f["slot_id"] for f in preview} diff --git a/backend/app/services/slice_preview.py b/backend/app/services/slice_preview.py index db46bbc2c..867d37d70 100644 --- a/backend/app/services/slice_preview.py +++ b/backend/app/services/slice_preview.py @@ -6,14 +6,26 @@ the ``/filament-requirements`` endpoint can read it directly. For unsliced project files it doesn't exist yet — only the slicer can produce it, since Bambu Studio applies its own pruning to painted-face data at slice time. -This module wraps the sidecar's ``slice_without_profiles`` call so the -endpoint can run a preview slice with the project's embedded settings, -parse the result's slice_info, and return the actual filament list. Results -are cached by ``(kind, source_id, plate_id, content_hash)`` so repeat -opens of the modal on the same plate are instant; LRU eviction keeps the -cache bounded. Hash invalidation handles in-place file replacement; no TTL -is used because preview-slice output is deterministic for a given file -content. +This module wraps the sidecar's slice call so the endpoint can run a preview +slice, parse the result's slice_info, and return the actual filament list. +Two slice modes are supported: + + * "embedded settings" mode (default) — calls ``slice_without_profiles`` so + the slicer falls back on the file's own ``Metadata/project_settings.config``. + Used when the SliceModal opens before the user has picked a profile + triplet and we just want the slot-mapping (which is a model property, + independent of process settings). + + * "bundle" mode — when the caller passes a bundle id + per-category preset + names, calls ``slice_with_bundle`` so the preview reflects the same + triplet the real print will use. More accurate gram numbers; same slot + mapping. Used after the SliceModal's Bundle tier resolves. + +Results are cached by ``(kind, source_id, plate_id, content_hash, bundle_key)`` +so different bundle picks on the same file don't collide and repeat opens +on the same plate + same bundle are instant. LRU eviction keeps the cache +bounded. Hash invalidation handles in-place file replacement; no TTL is +used because preview-slice output is deterministic for a given input. """ from __future__ import annotations @@ -35,22 +47,46 @@ from backend.app.services.slicer_api import ( logger = logging.getLogger(__name__) _PREVIEW_CACHE_MAX = 256 +# Cache key includes a bundle-context fingerprint (or "" when no bundle was +# supplied) so a "preview without profiles" result and a "preview with +# bundle X" result for the same file/plate occupy distinct entries instead +# of clobbering each other. +_PreviewCacheKey = tuple[str, int, int, str, str] # Cache values: list[dict] on success, [] on parsed-but-empty (slicer # returned a 3MF without filament data for this plate — caching the negative # avoids burning 30s+ per modal open on a known-bad input). -_preview_cache: OrderedDict[tuple[str, int, int, str], list[dict]] = OrderedDict() -# Per-key locks prevent N concurrent modal opens on the same (file, plate) -# from launching N redundant preview slices — only the first one runs, the -# rest wait and read from the cache. Locks are evicted alongside cache -# entries to keep the dict bounded; we do NOT cache transient sidecar +_preview_cache: OrderedDict[_PreviewCacheKey, list[dict]] = OrderedDict() +# Per-key locks prevent N concurrent modal opens on the same (file, plate, +# bundle) from launching N redundant preview slices — only the first one +# runs, the rest wait and read from the cache. Locks are evicted alongside +# cache entries to keep the dict bounded; we do NOT cache transient sidecar # failures (network errors etc.) so those retry naturally on next request. -_preview_locks: dict[tuple[str, int, int, str], asyncio.Lock] = {} +_preview_locks: dict[_PreviewCacheKey, asyncio.Lock] = {} def _content_hash(file_bytes: bytes) -> str: return hashlib.sha256(file_bytes).hexdigest()[:16] +def _bundle_context_fingerprint( + bundle_id: str | None, + printer_name: str | None, + process_name: str | None, + filament_names: list[str] | None, +) -> str: + """Derive a stable cache-key fragment for the bundle context. Empty + string when no bundle is supplied — preserves cache compatibility with + the no-bundle ("embedded settings") path so existing entries remain + valid. SHA-256 prefix keeps the key short while collision-resistant + enough for a 256-entry LRU. + """ + if not (bundle_id and printer_name and process_name and filament_names): + return "" + parts = [bundle_id, printer_name, process_name, *filament_names] + raw = "\x1f".join(parts).encode("utf-8") + return hashlib.sha256(raw).hexdigest()[:12] + + async def get_preview_filaments( *, kind: str, @@ -60,16 +96,33 @@ async def get_preview_filaments( file_name: str, api_url: str, request_id: str | None = None, + bundle_id: str | None = None, + printer_name: str | None = None, + process_name: str | None = None, + filament_names: list[str] | None = None, ) -> list[dict] | None: - """Run a preview slice for ``plate_id`` using the file's embedded settings, - parse the resulting slice_info, and return the per-plate filament list. + """Run a preview slice for ``plate_id``, parse the resulting slice_info, + and return the per-plate filament list. + + By default uses the file's embedded settings (``slice_without_profiles``). + When all four ``bundle_*`` params are provided, uses ``slice_with_bundle`` + so the preview matches the profile triplet the real print will use — + same slot mapping, more-accurate gram numbers. Partial bundle context + (e.g. id without preset names) falls back to the embedded path rather + than failing, so an in-progress modal selection doesn't surface errors. Returns ``None`` when the preview slice fails — the caller should fall back to whatever heuristic it has (typically the project_filaments + painted-face approach in ``threemf_tools``). """ h = _content_hash(file_bytes) - key = (kind, source_id, plate_id, h) + bundle_fp = _bundle_context_fingerprint( + bundle_id, + printer_name, + process_name, + filament_names, + ) + key: _PreviewCacheKey = (kind, source_id, plate_id, h, bundle_fp) cached = _preview_cache.get(key) if cached is not None: _preview_cache.move_to_end(key) @@ -86,19 +139,38 @@ async def get_preview_filaments( try: async with SlicerApiService(base_url=api_url) as svc: - result = await svc.slice_without_profiles( - model_bytes=file_bytes, - model_filename=file_name, - plate=plate_id, - export_3mf=True, - request_id=request_id, - ) + if bundle_fp: + # All four bundle params present (guaranteed non-None by + # _bundle_context_fingerprint returning non-empty); + # the type-checker can't see that, so assert for narrowing. + assert bundle_id and printer_name and process_name + assert filament_names is not None + result = await svc.slice_with_bundle( + model_bytes=file_bytes, + model_filename=file_name, + bundle_id=bundle_id, + printer_name=printer_name, + process_name=process_name, + filament_names=filament_names, + plate=plate_id, + export_3mf=True, + request_id=request_id, + ) + else: + result = await svc.slice_without_profiles( + model_bytes=file_bytes, + model_filename=file_name, + plate=plate_id, + export_3mf=True, + request_id=request_id, + ) except SlicerApiError as e: logger.warning( - "Preview slice failed for %s/%s plate %s: %s", + "Preview slice failed for %s/%s plate %s (bundle=%s): %s", kind, source_id, plate_id, + bundle_id or "-", e, ) return None diff --git a/backend/tests/unit/services/test_slice_preview.py b/backend/tests/unit/services/test_slice_preview.py index 6988d5afc..1467f81c7 100644 --- a/backend/tests/unit/services/test_slice_preview.py +++ b/backend/tests/unit/services/test_slice_preview.py @@ -72,7 +72,18 @@ class _StubService: return False async def slice_without_profiles(self, **kw): - self.calls.append(kw) + self.calls.append({"method": "slice_without_profiles", **kw}) + if self.raise_exc is not None: + raise self.raise_exc + return SliceResult( + content=self.response_bytes or b"", + print_time_seconds=0, + filament_used_g=0.0, + filament_used_mm=0.0, + ) + + async def slice_with_bundle(self, **kw): + self.calls.append({"method": "slice_with_bundle", **kw}) if self.raise_exc is not None: raise self.raise_exc return SliceResult( @@ -254,3 +265,174 @@ class TestGetPreviewFilaments: assert len(slice_preview._preview_cache) == _PREVIEW_CACHE_MAX # Lock dict is also pruned (no leak): same size as cache. assert len(slice_preview._preview_locks) == _PREVIEW_CACHE_MAX + + +# --------------------------------------------------------------------------- +# Bundle-aware preview path — when bundle context is supplied, the preview +# routes through `slice_with_bundle` so its gram numbers reflect the same +# triplet the real print will use. Cache must distinguish between bundle +# picks so a fresh selection doesn't re-serve a prior preview's output. +# --------------------------------------------------------------------------- + + +class TestBundleAwarePreview: + @pytest.mark.asyncio + async def test_full_bundle_context_uses_slice_with_bundle(self): + body = _make_sliced_3mf(plate_id=1, filaments=[{"id": "1", "type": "PLA", "color": "#000"}]) + stub = _StubService(response_bytes=body) + with patch.object(slice_preview, "SlicerApiService", lambda **kw: stub): + result = await get_preview_filaments( + kind="library_file", + source_id=42, + plate_id=1, + file_bytes=b"abc", + file_name="x.3mf", + api_url="http://sidecar", + bundle_id="abc123", + printer_name="# Bambu Lab H2D 0.4 nozzle", + process_name="# 0.20mm Standard @BBL H2D", + filament_names=["# Bambu PLA Basic @BBL H2D"], + ) + assert result is not None + assert result[0]["slot_id"] == 1 + # The bundle path engaged — slice_with_bundle was called, not the + # embedded-settings fallback. + assert len(stub.calls) == 1 + assert stub.calls[0]["method"] == "slice_with_bundle" + assert stub.calls[0]["bundle_id"] == "abc123" + assert stub.calls[0]["filament_names"] == ["# Bambu PLA Basic @BBL H2D"] + + @pytest.mark.asyncio + async def test_partial_bundle_context_falls_back_to_embedded(self): + # Modal-in-progress case: user picked a bundle id but hasn't yet + # picked the filament. Falling back to embedded settings keeps + # the preview's slot mapping fresh while gram numbers will firm + # up once the selection completes. + body = _make_sliced_3mf(plate_id=1, filaments=[{"id": "1", "type": "PLA", "color": "#000"}]) + stub = _StubService(response_bytes=body) + with patch.object(slice_preview, "SlicerApiService", lambda **kw: stub): + await get_preview_filaments( + kind="library_file", + source_id=42, + plate_id=1, + file_bytes=b"abc", + file_name="x.3mf", + api_url="http://sidecar", + bundle_id="abc123", + printer_name="# Bambu Lab H2D 0.4 nozzle", + process_name="# 0.20mm Standard @BBL H2D", + # filament_names missing + ) + assert len(stub.calls) == 1 + assert stub.calls[0]["method"] == "slice_without_profiles" + + @pytest.mark.asyncio + async def test_empty_filament_names_list_falls_back(self): + # Empty list (vs None) is treated as "incomplete context" since + # passing `[]` to slice_with_bundle would yield no + # --load-filaments arg and confuse the CLI. + body = _make_sliced_3mf(plate_id=1, filaments=[{"id": "1", "type": "PLA", "color": "#000"}]) + stub = _StubService(response_bytes=body) + with patch.object(slice_preview, "SlicerApiService", lambda **kw: stub): + await get_preview_filaments( + kind="library_file", + source_id=42, + plate_id=1, + file_bytes=b"abc", + file_name="x.3mf", + api_url="http://sidecar", + bundle_id="abc123", + printer_name="P", + process_name="Q", + filament_names=[], + ) + assert stub.calls[0]["method"] == "slice_without_profiles" + + @pytest.mark.asyncio + async def test_cache_separates_bundle_picks(self): + # Same file/plate, two different bundle picks → two distinct cache + # entries → two slices run. Without the bundle-fingerprint cache key, + # the second call would erroneously serve the first's output. + body = _make_sliced_3mf(plate_id=1, filaments=[{"id": "1", "type": "PLA", "color": "#000"}]) + stub = _StubService(response_bytes=body) + with patch.object(slice_preview, "SlicerApiService", lambda **kw: stub): + await get_preview_filaments( + kind="library_file", + source_id=42, + plate_id=1, + file_bytes=b"abc", + file_name="x.3mf", + api_url="http://sidecar", + bundle_id="bundleA", + printer_name="P", + process_name="Q", + filament_names=["F"], + ) + await get_preview_filaments( + kind="library_file", + source_id=42, + plate_id=1, + file_bytes=b"abc", + file_name="x.3mf", + api_url="http://sidecar", + bundle_id="bundleB", + printer_name="P", + process_name="Q", + filament_names=["F"], + ) + assert len(stub.calls) == 2 + assert stub.calls[0]["bundle_id"] == "bundleA" + assert stub.calls[1]["bundle_id"] == "bundleB" + + @pytest.mark.asyncio + async def test_cache_separates_bundle_vs_embedded(self): + # Same file/plate, one call without bundle and one with bundle → + # both must run. The embedded-settings cache entry must NOT be + # served as the bundle-picked result (gram numbers would be wrong). + body = _make_sliced_3mf(plate_id=1, filaments=[{"id": "1", "type": "PLA", "color": "#000"}]) + stub = _StubService(response_bytes=body) + with patch.object(slice_preview, "SlicerApiService", lambda **kw: stub): + await get_preview_filaments( + kind="library_file", + source_id=42, + plate_id=1, + file_bytes=b"abc", + file_name="x.3mf", + api_url="http://sidecar", + ) + await get_preview_filaments( + kind="library_file", + source_id=42, + plate_id=1, + file_bytes=b"abc", + file_name="x.3mf", + api_url="http://sidecar", + bundle_id="bundleA", + printer_name="P", + process_name="Q", + filament_names=["F"], + ) + methods = [c["method"] for c in stub.calls] + assert methods == ["slice_without_profiles", "slice_with_bundle"] + + @pytest.mark.asyncio + async def test_bundle_repeat_call_hits_cache(self): + # Sanity check that the new cache key is otherwise stable: same + # bundle pick on the same file → cache hit on second call. + body = _make_sliced_3mf(plate_id=1, filaments=[{"id": "1", "type": "PLA", "color": "#000"}]) + stub = _StubService(response_bytes=body) + with patch.object(slice_preview, "SlicerApiService", lambda **kw: stub): + for _ in range(2): + await get_preview_filaments( + kind="library_file", + source_id=42, + plate_id=1, + file_bytes=b"abc", + file_name="x.3mf", + api_url="http://sidecar", + bundle_id="bundleA", + printer_name="P", + process_name="Q", + filament_names=["F"], + ) + assert len(stub.calls) == 1 diff --git a/frontend/src/__tests__/components/SlicerBundlesPanel.test.tsx b/frontend/src/__tests__/components/SlicerBundlesPanel.test.tsx new file mode 100644 index 000000000..6efe1bdd5 --- /dev/null +++ b/frontend/src/__tests__/components/SlicerBundlesPanel.test.tsx @@ -0,0 +1,214 @@ +/** + * Tests for the SlicerBundlesPanel — Settings panel for managing + * BambuStudio Printer Preset Bundles (.bbscfg) on the slicer sidecar. + * + * Coverage: + * - Empty state when the sidecar has no bundles imported yet. + * - List rendering with summary line (process / filament counts). + * - Upload happy path → success toast + list invalidation. + * - Upload error → error toast. + * - Delete with confirmation → success toast + list invalidation. + * - Delete error → error toast. + */ + +import { describe, it, expect, vi, beforeEach } from 'vitest'; +import { screen, fireEvent, waitFor } from '@testing-library/react'; +import { render } from '../utils'; +import { api } from '../../api/client'; +import { SlicerBundlesPanel } from '../../components/SlicerBundlesPanel'; + +vi.mock('../../api/client', async () => { + const actual: typeof import('../../api/client') = await vi.importActual( + '../../api/client', + ); + return { + ...actual, + api: { + ...actual.api, + listSlicerBundles: vi.fn(), + importSlicerBundle: vi.fn(), + deleteSlicerBundle: vi.fn(), + }, + getAuthToken: vi.fn(() => null), + }; +}); + +const SAMPLE_BUNDLE = { + id: 'abc123def456abcd', + printer_preset_name: '# Bambu Lab H2D 0.4 nozzle', + printer: ['# Bambu Lab H2D 0.4 nozzle'], + process: [ + '# 0.20mm Standard @BBL H2D', + '# 0.16mm Standard @BBL H2D', + ], + filament: [ + '# Bambu PLA Basic @BBL H2D', + '# Bambu PETG HF @BBL H2D 0.4 nozzle', + '# Bambu ABS @BBL H2D', + ], + version: '02.06.00.50', +}; + +beforeEach(() => { + vi.clearAllMocks(); +}); + +describe('SlicerBundlesPanel — empty state', () => { + it('renders the empty-state message when no bundles exist', async () => { + vi.mocked(api.listSlicerBundles).mockResolvedValueOnce([]); + + render(); + + await waitFor(() => + expect(api.listSlicerBundles).toHaveBeenCalled(), + ); + expect( + await screen.findByText(/no bundles imported yet/i), + ).toBeInTheDocument(); + }); +}); + +describe('SlicerBundlesPanel — list rendering', () => { + it('renders bundle name + summary (process and filament counts)', async () => { + vi.mocked(api.listSlicerBundles).mockResolvedValueOnce([SAMPLE_BUNDLE]); + + render(); + + expect( + await screen.findByText('# Bambu Lab H2D 0.4 nozzle'), + ).toBeInTheDocument(); + // Summary should reflect 2 process + 3 filament from the fixture. + expect( + await screen.findByText(/2 process · 3 filament/i), + ).toBeInTheDocument(); + // Version suffix appended after the summary. + expect(screen.getByText(/v02\.06\.00\.50/)).toBeInTheDocument(); + }); +}); + +describe('SlicerBundlesPanel — upload flow', () => { + it('imports a selected file and refreshes the list on success', async () => { + // First listing call returns empty so the test can detect the post-import + // re-fetch (second call) returning the new bundle. + vi.mocked(api.listSlicerBundles) + .mockResolvedValueOnce([]) + .mockResolvedValueOnce([SAMPLE_BUNDLE]); + vi.mocked(api.importSlicerBundle).mockResolvedValueOnce(SAMPLE_BUNDLE); + + const { container } = render(); + + // The file input is hidden (display: none for styling); grab it directly. + const fileInput = container.querySelector( + 'input[type="file"]', + ) as HTMLInputElement; + expect(fileInput).toBeTruthy(); + + const file = new File([new Uint8Array([0x50, 0x4b, 0x03, 0x04])], 'H2D.bbscfg', { + type: 'application/zip', + }); + + fireEvent.change(fileInput, { target: { files: [file] } }); + + await waitFor(() => + expect(api.importSlicerBundle).toHaveBeenCalledWith(file), + ); + // After the success, the list call should fire a second time (cache + // invalidation by react-query). + await waitFor(() => + expect(api.listSlicerBundles).toHaveBeenCalledTimes(2), + ); + // The newly imported bundle should now be visible in the list. + expect( + await screen.findByText('# Bambu Lab H2D 0.4 nozzle'), + ).toBeInTheDocument(); + }); + + it('shows an error and does not refresh on upload failure', async () => { + vi.mocked(api.listSlicerBundles).mockResolvedValueOnce([]); + vi.mocked(api.importSlicerBundle).mockRejectedValueOnce( + new Error('Bundle is missing bundle_structure.json'), + ); + + const { container } = render(); + + await waitFor(() => expect(api.listSlicerBundles).toHaveBeenCalled()); + + const fileInput = container.querySelector( + 'input[type="file"]', + ) as HTMLInputElement; + const file = new File([new Uint8Array([0])], 'bad.bbscfg', { + type: 'application/zip', + }); + fireEvent.change(fileInput, { target: { files: [file] } }); + + await waitFor(() => + expect(api.importSlicerBundle).toHaveBeenCalled(), + ); + // Listing should NOT be re-called on failure — only the initial load. + expect(api.listSlicerBundles).toHaveBeenCalledTimes(1); + // Empty state still showing. + expect( + screen.getByText(/no bundles imported yet/i), + ).toBeInTheDocument(); + }); +}); + +describe('SlicerBundlesPanel — delete flow', () => { + it('deletes a bundle after confirmation and refreshes the list', async () => { + vi.mocked(api.listSlicerBundles) + .mockResolvedValueOnce([SAMPLE_BUNDLE]) + .mockResolvedValueOnce([]); + vi.mocked(api.deleteSlicerBundle).mockResolvedValueOnce(undefined); + + render(); + + // Wait for the bundle to render. + await screen.findByText('# Bambu Lab H2D 0.4 nozzle'); + + // Click the trash button (aria-label="Delete"). + fireEvent.click(screen.getByRole('button', { name: /delete/i })); + + // ConfirmModal should appear with the bundle name in the message. + const confirmMessage = await screen.findByText( + /Slice requests that reference "# Bambu Lab H2D 0.4 nozzle" will fail/i, + ); + expect(confirmMessage).toBeInTheDocument(); + + // The modal renders its own "Delete" button — there are now two buttons + // matching /delete/i. Click the one inside the dialog (last in document + // order, since the modal portal renders after the panel). + const deleteButtons = screen.getAllByRole('button', { name: /delete/i }); + fireEvent.click(deleteButtons[deleteButtons.length - 1]); + + await waitFor(() => + expect(api.deleteSlicerBundle).toHaveBeenCalledWith( + 'abc123def456abcd', + ), + ); + // Cache invalidation should re-fire the list query. + await waitFor(() => + expect(api.listSlicerBundles).toHaveBeenCalledTimes(2), + ); + }); + + it('keeps the bundle in the list when the user cancels the delete dialog', async () => { + vi.mocked(api.listSlicerBundles).mockResolvedValueOnce([SAMPLE_BUNDLE]); + + render(); + + await screen.findByText('# Bambu Lab H2D 0.4 nozzle'); + fireEvent.click(screen.getByRole('button', { name: /delete/i })); + + // Cancel by clicking the "Cancel" button on the ConfirmModal. + const cancelButton = await screen.findByRole('button', { name: /cancel/i }); + fireEvent.click(cancelButton); + + // Delete API never called, list never re-fetched. + expect(api.deleteSlicerBundle).not.toHaveBeenCalled(); + expect(api.listSlicerBundles).toHaveBeenCalledTimes(1); + // Bundle still rendered. + expect( + screen.getByText('# Bambu Lab H2D 0.4 nozzle'), + ).toBeInTheDocument(); + }); +}); diff --git a/frontend/src/api/client.ts b/frontend/src/api/client.ts index e0d504b15..deaa49b58 100644 --- a/frontend/src/api/client.ts +++ b/frontend/src/api/client.ts @@ -1171,6 +1171,20 @@ export interface SliceRequest { export_3mf?: boolean; } +// GET /api/v1/slicer/bundles — Printer Preset Bundles imported from +// BambuStudio's "File → Export → Export Preset Bundle" dialog. Each bundle +// is a .bbscfg zip the user uploads once per printer, after which the +// SliceModal can pick its inner presets by name (no re-upload per slice). +// Backend: backend/app/api/routes/slicer_presets.py — bundle endpoints. +export interface SlicerBundle { + id: string; + printer_preset_name: string; + printer: string[]; + process: string[]; + filament: string[]; + version: string | null; +} + // GET /api/v1/slicer/presets — unified listing across cloud / local / standard. export type SlicerCloudStatus = 'ok' | 'not_authenticated' | 'expired' | 'unreachable'; export interface UnifiedPreset { @@ -3776,10 +3790,31 @@ export const api = { }, getArchivePlates: (archiveId: number) => request(`/archives/${archiveId}/plates`), - getArchiveFilamentRequirements: (archiveId: number, plateId?: number, requestId?: string) => { + getArchiveFilamentRequirements: ( + archiveId: number, + plateId?: number, + requestId?: string, + // Optional bundle context: when supplied, the backend's preview slice + // (run for unsliced project files) uses slice_with_bundle so gram + // numbers reflect the same triplet the real print will use. All four + // fields must be set for the bundle path to engage; partial context + // falls back to the embedded-settings preview without erroring. + bundle?: { + bundle_id: string; + printer_name: string; + process_name: string; + filament_names: string[]; + }, + ) => { const qs = new URLSearchParams(); if (plateId !== undefined) qs.set('plate_id', String(plateId)); if (requestId) qs.set('request_id', requestId); + if (bundle) { + qs.set('bundle_id', bundle.bundle_id); + qs.set('printer_name', bundle.printer_name); + qs.set('process_name', bundle.process_name); + qs.set('filament_names', bundle.filament_names.join(';')); + } return request<{ archive_id: number; filename: string; @@ -5141,10 +5176,28 @@ export const api = { }), getLibraryFilePlates: (fileId: number) => request(`/library/files/${fileId}/plates`), - getLibraryFileFilamentRequirements: (fileId: number, plateId?: number, requestId?: string) => { + getLibraryFileFilamentRequirements: ( + fileId: number, + plateId?: number, + requestId?: string, + // Optional bundle context — see getArchiveFilamentRequirements above + // for the contract. Same shape so callers can share a builder helper. + bundle?: { + bundle_id: string; + printer_name: string; + process_name: string; + filament_names: string[]; + }, + ) => { const qs = new URLSearchParams(); if (plateId !== undefined) qs.set('plate_id', String(plateId)); if (requestId) qs.set('request_id', requestId); + if (bundle) { + qs.set('bundle_id', bundle.bundle_id); + qs.set('printer_name', bundle.printer_name); + qs.set('process_name', bundle.process_name); + qs.set('filament_names', bundle.filament_names.join(';')); + } return request<{ file_id: number; filename: string; @@ -5269,6 +5322,35 @@ export const api = { getSlicerPresets: () => request('/slicer/presets'), + // Slicer Bundles (.bbscfg) — Printer Preset Bundles imported from BambuStudio. + // Settings → Slicer Bundles uploads/lists/deletes; the SliceModal picks + // presets by name from a chosen bundle (separate follow-up). + listSlicerBundles: () => + request('/slicer/bundles'), + importSlicerBundle: (file: File) => { + // The /slicer/bundles upload accepts multipart with field name "file" + // (matches the FastAPI route's UploadFile parameter). Bypass `request` + // because it always JSON-stringifies the body — multipart needs the + // browser to set the boundary in the Content-Type header. + const fd = new FormData(); + fd.append('file', file); + return fetch(`${API_BASE}/slicer/bundles`, { + method: 'POST', + headers: authToken ? { 'Authorization': `Bearer ${authToken}` } : {}, + body: fd, + }).then(async (res) => { + if (!res.ok) { + const err = await res.json().catch(() => ({})); + throw new Error(err.detail || `HTTP ${res.status}`); + } + return res.json() as Promise; + }); + }, + deleteSlicerBundle: (bundleId: string) => + request(`/slicer/bundles/${encodeURIComponent(bundleId)}`, { + method: 'DELETE', + }), + // Local Presets (OrcaSlicer imports) getLocalPresets: () => request('/local-presets/'), diff --git a/frontend/src/components/SlicerBundlesPanel.tsx b/frontend/src/components/SlicerBundlesPanel.tsx new file mode 100644 index 000000000..1114f8f53 --- /dev/null +++ b/frontend/src/components/SlicerBundlesPanel.tsx @@ -0,0 +1,198 @@ +import { useRef, useState } from 'react'; +import { useTranslation } from 'react-i18next'; +import { useMutation, useQuery, useQueryClient } from '@tanstack/react-query'; +import { Loader2, Package, Trash2, Upload } from 'lucide-react'; +import { api, type SlicerBundle } from '../api/client'; +import { Card, CardContent, CardHeader } from './Card'; +import { Button } from './Button'; +import { ConfirmModal } from './ConfirmModal'; +import { useToast } from '../contexts/ToastContext'; + +// Settings panel for managing BambuStudio "Printer Preset Bundles" +// (.bbscfg) on the slicer sidecar. Sits below the slicer-API URL panel +// in SettingsPage and is hidden when use_slicer_api is off — without a +// configured sidecar there's nowhere to upload bundles to. +// +// Backend wiring: backend/app/api/routes/slicer_presets.py exposes +// /api/v1/slicer/bundles (POST/GET/DELETE). The list call returns [] +// when no sidecar is configured, so an empty render is the natural +// "first-run" state for users who haven't enabled the sidecar yet. +export function SlicerBundlesPanel() { + const { t } = useTranslation(); + const queryClient = useQueryClient(); + const { showToast } = useToast(); + const fileInputRef = useRef(null); + const [pendingDelete, setPendingDelete] = useState(null); + + const { data: bundles, isLoading } = useQuery({ + queryKey: ['slicer-bundles'], + queryFn: api.listSlicerBundles, + }); + + const importMutation = useMutation({ + mutationFn: (file: File) => api.importSlicerBundle(file), + onSuccess: (bundle) => { + queryClient.invalidateQueries({ queryKey: ['slicer-bundles'] }); + showToast( + t('settings.slicerBundles.uploadSuccess', { + defaultValue: 'Imported {{name}}', + name: bundle.printer_preset_name, + }), + 'success', + ); + // Reset the file input so the same file can be re-selected after a + // failed retry. (Without this, a second click on the same file + // doesn't trigger onChange and looks like the panel is broken.) + if (fileInputRef.current) fileInputRef.current.value = ''; + }, + onError: (err: Error) => { + showToast( + t('settings.slicerBundles.uploadError', { + defaultValue: 'Bundle upload failed: {{message}}', + message: err.message, + }), + 'error', + ); + if (fileInputRef.current) fileInputRef.current.value = ''; + }, + }); + + const deleteMutation = useMutation({ + mutationFn: (bundleId: string) => api.deleteSlicerBundle(bundleId), + onSuccess: () => { + queryClient.invalidateQueries({ queryKey: ['slicer-bundles'] }); + setPendingDelete(null); + showToast( + t('settings.slicerBundles.deleteSuccess', { + defaultValue: 'Bundle removed', + }), + 'success', + ); + }, + onError: (err: Error) => { + showToast( + t('settings.slicerBundles.deleteError', { + defaultValue: 'Bundle delete failed: {{message}}', + message: err.message, + }), + 'error', + ); + setPendingDelete(null); + }, + }); + + const handleFileChange = (e: React.ChangeEvent) => { + const file = e.target.files?.[0]; + if (!file) return; + importMutation.mutate(file); + }; + + return ( + + +

+ + {t('settings.slicerBundles.title', { defaultValue: 'Slicer Bundles' })} +

+
+ +

+ {t('settings.slicerBundles.description', { + defaultValue: + 'Import a Printer Preset Bundle (.bbscfg) exported from BambuStudio (File → Export → Export Preset Bundle → "Printer preset bundle"). Once imported, slice requests can pick presets from the bundle by name without re-uploading the JSON profile triplet.', + })} +

+ +
+ + +
+ + {isLoading ? ( +
+ + {t('settings.slicerBundles.loading', { defaultValue: 'Loading bundles…' })} +
+ ) : bundles && bundles.length > 0 ? ( +
    + {bundles.map((b) => ( +
  • +
    +

    {b.printer_preset_name}

    +

    + {t('settings.slicerBundles.summary', { + defaultValue: + '{{processCount}} process · {{filamentCount}} filament presets', + processCount: b.process.length, + filamentCount: b.filament.length, + })} + {b.version && ` · v${b.version}`} +

    +
    + +
  • + ))} +
+ ) : ( +

+ {t('settings.slicerBundles.empty', { + defaultValue: 'No bundles imported yet.', + })} +

+ )} +
+ + {pendingDelete && ( + deleteMutation.mutate(pendingDelete.id)} + onCancel={() => setPendingDelete(null)} + /> + )} +
+ ); +} diff --git a/frontend/src/i18n/locales/de.ts b/frontend/src/i18n/locales/de.ts index c69168aaf..e27380fc5 100644 --- a/frontend/src/i18n/locales/de.ts +++ b/frontend/src/i18n/locales/de.ts @@ -1852,6 +1852,22 @@ export default { orcaslicerApiUrl: 'OrcaSlicer Sidecar-URL', bambuStudioApiUrl: 'Bambu Studio Sidecar-URL', slicerApiUrlDescription: 'URL des Slicer-API-Sidecar-Containers. Leer lassen, um die SLICER_API_URL- bzw. BAMBU_STUDIO_API_URL-Umgebungsvariablen zu nutzen.', + slicerBundles: { + title: 'Slicer-Bundles', + description: 'Importiere ein Drucker-Voreinstellungspaket (.bbscfg), das aus BambuStudio exportiert wurde (Datei → Exportieren → Voreinstellungspaket exportieren → "Drucker-Voreinstellungspaket"). Nach dem Import können Slice-Anfragen Voreinstellungen aus dem Bundle per Name auswählen, ohne das JSON-Profil-Trio erneut hochzuladen.', + uploadButton: 'Bundle hochladen', + uploading: 'Hochladen…', + loading: 'Bundles werden geladen…', + empty: 'Noch keine Bundles importiert.', + summary: '{{processCount}} Prozess · {{filamentCount}} Filament-Voreinstellungen', + delete: 'Löschen', + uploadSuccess: '{{name}} importiert', + uploadError: 'Bundle-Upload fehlgeschlagen: {{message}}', + deleteSuccess: 'Bundle entfernt', + deleteError: 'Löschen des Bundles fehlgeschlagen: {{message}}', + confirmDeleteTitle: 'Dieses Bundle entfernen?', + confirmDeleteMessage: 'Slice-Anfragen, die "{{name}}" referenzieren, schlagen fehl, bis das Bundle erneut importiert wird.', + }, externalCameras: 'Externe Kameras', costTracking: 'Kostenverfolgung', printsOnly: 'Nur Drucke', diff --git a/frontend/src/i18n/locales/en.ts b/frontend/src/i18n/locales/en.ts index 3b0bd5cfa..aaf319f21 100644 --- a/frontend/src/i18n/locales/en.ts +++ b/frontend/src/i18n/locales/en.ts @@ -1855,6 +1855,22 @@ export default { orcaslicerApiUrl: 'OrcaSlicer sidecar URL', bambuStudioApiUrl: 'Bambu Studio sidecar URL', slicerApiUrlDescription: 'URL of the slicer-API sidecar container. Leave blank to use the SLICER_API_URL / BAMBU_STUDIO_API_URL env var defaults.', + slicerBundles: { + title: 'Slicer Bundles', + description: 'Import a Printer Preset Bundle (.bbscfg) exported from BambuStudio (File → Export → Export Preset Bundle → "Printer preset bundle"). Once imported, slice requests can pick presets from the bundle by name without re-uploading the JSON profile triplet.', + uploadButton: 'Upload bundle', + uploading: 'Uploading…', + loading: 'Loading bundles…', + empty: 'No bundles imported yet.', + summary: '{{processCount}} process · {{filamentCount}} filament presets', + delete: 'Delete', + uploadSuccess: 'Imported {{name}}', + uploadError: 'Bundle upload failed: {{message}}', + deleteSuccess: 'Bundle removed', + deleteError: 'Bundle delete failed: {{message}}', + confirmDeleteTitle: 'Remove this bundle?', + confirmDeleteMessage: 'Slice requests that reference "{{name}}" will fail until the bundle is re-imported.', + }, externalCameras: 'External Cameras', costTracking: 'Cost Tracking', printsOnly: 'Prints Only', diff --git a/frontend/src/i18n/locales/fr.ts b/frontend/src/i18n/locales/fr.ts index c4235a9a0..0decc8e5e 100644 --- a/frontend/src/i18n/locales/fr.ts +++ b/frontend/src/i18n/locales/fr.ts @@ -1809,6 +1809,22 @@ export default { orcaslicerApiUrl: 'OrcaSlicer sidecar URL', bambuStudioApiUrl: 'Bambu Studio sidecar URL', slicerApiUrlDescription: 'URL of the slicer-API sidecar container. Leave blank to use the SLICER_API_URL / BAMBU_STUDIO_API_URL env var defaults.', + slicerBundles: { + title: 'Slicer Bundles', + description: 'Import a Printer Preset Bundle (.bbscfg) exported from BambuStudio (File → Export → Export Preset Bundle → "Printer preset bundle"). Once imported, slice requests can pick presets from the bundle by name without re-uploading the JSON profile triplet.', + uploadButton: 'Upload bundle', + uploading: 'Uploading…', + loading: 'Loading bundles…', + empty: 'No bundles imported yet.', + summary: '{{processCount}} process · {{filamentCount}} filament presets', + delete: 'Delete', + uploadSuccess: 'Imported {{name}}', + uploadError: 'Bundle upload failed: {{message}}', + deleteSuccess: 'Bundle removed', + deleteError: 'Bundle delete failed: {{message}}', + confirmDeleteTitle: 'Remove this bundle?', + confirmDeleteMessage: 'Slice requests that reference "{{name}}" will fail until the bundle is re-imported.', + }, externalCameras: 'Caméras externes', costTracking: 'Suivi des coûts', printsOnly: 'Impressions uniquement', diff --git a/frontend/src/i18n/locales/it.ts b/frontend/src/i18n/locales/it.ts index a813dc399..f4c3a9c87 100644 --- a/frontend/src/i18n/locales/it.ts +++ b/frontend/src/i18n/locales/it.ts @@ -1809,6 +1809,22 @@ export default { orcaslicerApiUrl: 'OrcaSlicer sidecar URL', bambuStudioApiUrl: 'Bambu Studio sidecar URL', slicerApiUrlDescription: 'URL of the slicer-API sidecar container. Leave blank to use the SLICER_API_URL / BAMBU_STUDIO_API_URL env var defaults.', + slicerBundles: { + title: 'Slicer Bundles', + description: 'Import a Printer Preset Bundle (.bbscfg) exported from BambuStudio (File → Export → Export Preset Bundle → "Printer preset bundle"). Once imported, slice requests can pick presets from the bundle by name without re-uploading the JSON profile triplet.', + uploadButton: 'Upload bundle', + uploading: 'Uploading…', + loading: 'Loading bundles…', + empty: 'No bundles imported yet.', + summary: '{{processCount}} process · {{filamentCount}} filament presets', + delete: 'Delete', + uploadSuccess: 'Imported {{name}}', + uploadError: 'Bundle upload failed: {{message}}', + deleteSuccess: 'Bundle removed', + deleteError: 'Bundle delete failed: {{message}}', + confirmDeleteTitle: 'Remove this bundle?', + confirmDeleteMessage: 'Slice requests that reference "{{name}}" will fail until the bundle is re-imported.', + }, externalCameras: 'Camere esterne', costTracking: 'Tracciamento costi', printsOnly: 'Solo stampe', diff --git a/frontend/src/i18n/locales/ja.ts b/frontend/src/i18n/locales/ja.ts index 2b012a2aa..8c86b3346 100644 --- a/frontend/src/i18n/locales/ja.ts +++ b/frontend/src/i18n/locales/ja.ts @@ -1851,6 +1851,22 @@ export default { orcaslicerApiUrl: 'OrcaSlicer sidecar URL', bambuStudioApiUrl: 'Bambu Studio sidecar URL', slicerApiUrlDescription: 'URL of the slicer-API sidecar container. Leave blank to use the SLICER_API_URL / BAMBU_STUDIO_API_URL env var defaults.', + slicerBundles: { + title: 'Slicer Bundles', + description: 'Import a Printer Preset Bundle (.bbscfg) exported from BambuStudio (File → Export → Export Preset Bundle → "Printer preset bundle"). Once imported, slice requests can pick presets from the bundle by name without re-uploading the JSON profile triplet.', + uploadButton: 'Upload bundle', + uploading: 'Uploading…', + loading: 'Loading bundles…', + empty: 'No bundles imported yet.', + summary: '{{processCount}} process · {{filamentCount}} filament presets', + delete: 'Delete', + uploadSuccess: 'Imported {{name}}', + uploadError: 'Bundle upload failed: {{message}}', + deleteSuccess: 'Bundle removed', + deleteError: 'Bundle delete failed: {{message}}', + confirmDeleteTitle: 'Remove this bundle?', + confirmDeleteMessage: 'Slice requests that reference "{{name}}" will fail until the bundle is re-imported.', + }, externalCameras: '外部カメラ', costTracking: 'コスト追跡', printsOnly: '印刷のみ', diff --git a/frontend/src/i18n/locales/pt-BR.ts b/frontend/src/i18n/locales/pt-BR.ts index 89d688469..f8796ea2c 100644 --- a/frontend/src/i18n/locales/pt-BR.ts +++ b/frontend/src/i18n/locales/pt-BR.ts @@ -1809,6 +1809,22 @@ export default { orcaslicerApiUrl: 'OrcaSlicer sidecar URL', bambuStudioApiUrl: 'Bambu Studio sidecar URL', slicerApiUrlDescription: 'URL of the slicer-API sidecar container. Leave blank to use the SLICER_API_URL / BAMBU_STUDIO_API_URL env var defaults.', + slicerBundles: { + title: 'Slicer Bundles', + description: 'Import a Printer Preset Bundle (.bbscfg) exported from BambuStudio (File → Export → Export Preset Bundle → "Printer preset bundle"). Once imported, slice requests can pick presets from the bundle by name without re-uploading the JSON profile triplet.', + uploadButton: 'Upload bundle', + uploading: 'Uploading…', + loading: 'Loading bundles…', + empty: 'No bundles imported yet.', + summary: '{{processCount}} process · {{filamentCount}} filament presets', + delete: 'Delete', + uploadSuccess: 'Imported {{name}}', + uploadError: 'Bundle upload failed: {{message}}', + deleteSuccess: 'Bundle removed', + deleteError: 'Bundle delete failed: {{message}}', + confirmDeleteTitle: 'Remove this bundle?', + confirmDeleteMessage: 'Slice requests that reference "{{name}}" will fail until the bundle is re-imported.', + }, externalCameras: 'Câmeras Externas', costTracking: 'Rastreamento de Custos', printsOnly: 'Apenas Impressões', diff --git a/frontend/src/i18n/locales/zh-CN.ts b/frontend/src/i18n/locales/zh-CN.ts index a1649be26..f9fc9e482 100644 --- a/frontend/src/i18n/locales/zh-CN.ts +++ b/frontend/src/i18n/locales/zh-CN.ts @@ -1853,6 +1853,22 @@ export default { orcaslicerApiUrl: 'OrcaSlicer sidecar URL', bambuStudioApiUrl: 'Bambu Studio sidecar URL', slicerApiUrlDescription: 'URL of the slicer-API sidecar container. Leave blank to use the SLICER_API_URL / BAMBU_STUDIO_API_URL env var defaults.', + slicerBundles: { + title: 'Slicer Bundles', + description: 'Import a Printer Preset Bundle (.bbscfg) exported from BambuStudio (File → Export → Export Preset Bundle → "Printer preset bundle"). Once imported, slice requests can pick presets from the bundle by name without re-uploading the JSON profile triplet.', + uploadButton: 'Upload bundle', + uploading: 'Uploading…', + loading: 'Loading bundles…', + empty: 'No bundles imported yet.', + summary: '{{processCount}} process · {{filamentCount}} filament presets', + delete: 'Delete', + uploadSuccess: 'Imported {{name}}', + uploadError: 'Bundle upload failed: {{message}}', + deleteSuccess: 'Bundle removed', + deleteError: 'Bundle delete failed: {{message}}', + confirmDeleteTitle: 'Remove this bundle?', + confirmDeleteMessage: 'Slice requests that reference "{{name}}" will fail until the bundle is re-imported.', + }, externalCameras: '外部摄像头', costTracking: '成本追踪', printsOnly: '仅打印', diff --git a/frontend/src/i18n/locales/zh-TW.ts b/frontend/src/i18n/locales/zh-TW.ts index a90f54e01..65067a53e 100644 --- a/frontend/src/i18n/locales/zh-TW.ts +++ b/frontend/src/i18n/locales/zh-TW.ts @@ -1853,6 +1853,22 @@ export default { orcaslicerApiUrl: 'OrcaSlicer sidecar URL', bambuStudioApiUrl: 'Bambu Studio sidecar URL', slicerApiUrlDescription: 'URL of the slicer-API sidecar container. Leave blank to use the SLICER_API_URL / BAMBU_STUDIO_API_URL env var defaults.', + slicerBundles: { + title: 'Slicer Bundles', + description: 'Import a Printer Preset Bundle (.bbscfg) exported from BambuStudio (File → Export → Export Preset Bundle → "Printer preset bundle"). Once imported, slice requests can pick presets from the bundle by name without re-uploading the JSON profile triplet.', + uploadButton: 'Upload bundle', + uploading: 'Uploading…', + loading: 'Loading bundles…', + empty: 'No bundles imported yet.', + summary: '{{processCount}} process · {{filamentCount}} filament presets', + delete: 'Delete', + uploadSuccess: 'Imported {{name}}', + uploadError: 'Bundle upload failed: {{message}}', + deleteSuccess: 'Bundle removed', + deleteError: 'Bundle delete failed: {{message}}', + confirmDeleteTitle: 'Remove this bundle?', + confirmDeleteMessage: 'Slice requests that reference "{{name}}" will fail until the bundle is re-imported.', + }, externalCameras: '外部攝影機', costTracking: '成本追蹤', printsOnly: '僅列印', diff --git a/frontend/src/pages/SettingsPage.tsx b/frontend/src/pages/SettingsPage.tsx index 7f0f02cc8..212f09214 100644 --- a/frontend/src/pages/SettingsPage.tsx +++ b/frontend/src/pages/SettingsPage.tsx @@ -8,6 +8,7 @@ import { formatDateOnly } from '../utils/date'; import { getCurrencySymbol, SUPPORTED_CURRENCIES } from '../utils/currency'; import type { APIKey, AppSettings, AppSettingsUpdate, SmartPlug, SmartPlugStatus, NotificationProvider, NotificationTemplate, UpdateStatus, GitHubBackupStatus, CloudAuthStatus, UserCreate, UserUpdate, UserResponse, StorageUsageResponse } from '../api/client'; import { Card, CardContent, CardDensityProvider, CardHeader } from '../components/Card'; +import { SlicerBundlesPanel } from '../components/SlicerBundlesPanel'; import { CameraTokensSection } from './CameraTokensPage'; import { Collapsible } from '../components/Collapsible'; import { Button } from '../components/Button'; @@ -4262,6 +4263,12 @@ export function SettingsPage() { + {/* Slicer Preset Bundles — only meaningful when the sidecar is in use, + since uploads / lists round-trip through it. Hide it entirely when + use_slicer_api is off so the Settings page doesn't show a panel that + can't do anything. */} + {(localSettings.use_slicer_api ?? false) && } + {/* Auto-Drying */} diff --git a/static/assets/index-DA6QDhrM.js b/static/assets/index-B2mJSviN.js similarity index 53% rename from static/assets/index-DA6QDhrM.js rename to static/assets/index-B2mJSviN.js index c96689edf..8a2e6337b 100644 --- a/static/assets/index-DA6QDhrM.js +++ b/static/assets/index-B2mJSviN.js @@ -1,42 +1,42 @@ -function cue(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 nk=typeof globalThis<"u"?globalThis:typeof window<"u"?window:typeof global<"u"?global:typeof self<"u"?self:{};function hc(t){return t&&t.__esModule&&Object.prototype.hasOwnProperty.call(t,"default")?t.default:t}var TM={exports:{}},Fv={};var Y8;function due(){if(Y8)return Fv;Y8=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 Fv.Fragment=e,Fv.jsx=n,Fv.jsxs=n,Fv}var Q8;function uue(){return Q8||(Q8=1,TM.exports=due()),TM.exports}var a=uue(),AM={exports:{}},Gn={};var J8;function mue(){if(J8)return Gn;J8=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"),d=Symbol.for("react.memo"),u=Symbol.for("react.lazy"),m=Symbol.for("react.activity"),p=Symbol.iterator;function f(K){return K===null||typeof K!="object"?null:(K=p&&K[p]||K["@@iterator"],typeof K=="function"?K:null)}var x={isMounted:function(){return!1},enqueueForceUpdate:function(){},enqueueReplaceState:function(){},enqueueSetState:function(){}},v=Object.assign,g={};function b(K,ie,pe){this.props=K,this.context=ie,this.refs=g,this.updater=pe||x}b.prototype.isReactComponent={},b.prototype.setState=function(K,ie){if(typeof K!="object"&&typeof K!="function"&&K!=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,K,ie,"setState")},b.prototype.forceUpdate=function(K){this.updater.enqueueForceUpdate(this,K,"forceUpdate")};function S(){}S.prototype=b.prototype;function C(K,ie,pe){this.props=K,this.context=ie,this.refs=g,this.updater=pe||x}var P=C.prototype=new S;P.constructor=C,v(P,b.prototype),P.isPureReactComponent=!0;var N=Array.isArray;function A(){}var T={H:null,A:null,T:null,S:null},E=Object.prototype.hasOwnProperty;function k(K,ie,pe){var xe=pe.ref;return{$$typeof:t,type:K,key:ie,ref:xe!==void 0?xe:null,props:pe}}function M(K,ie){return k(K.type,ie,K.props)}function H(K){return typeof K=="object"&&K!==null&&K.$$typeof===t}function O(K){var ie={"=":"=0",":":"=2"};return"$"+K.replace(/[=:]/g,function(pe){return ie[pe]})}var X=/\/+/g;function L(K,ie){return typeof K=="object"&&K!==null&&K.key!=null?O(""+K.key):ie.toString(36)}function ee(K){switch(K.status){case"fulfilled":return K.value;case"rejected":throw K.reason;default:switch(typeof K.status=="string"?K.then(A,A):(K.status="pending",K.then(function(ie){K.status==="pending"&&(K.status="fulfilled",K.value=ie)},function(ie){K.status==="pending"&&(K.status="rejected",K.reason=ie)})),K.status){case"fulfilled":return K.value;case"rejected":throw K.reason}}throw K}function re(K,ie,pe,xe,fe){var B=typeof K;(B==="undefined"||B==="boolean")&&(K=null);var Y=!1;if(K===null)Y=!0;else switch(B){case"bigint":case"string":case"number":Y=!0;break;case"object":switch(K.$$typeof){case t:case e:Y=!0;break;case u:return Y=K._init,re(Y(K._payload),ie,pe,xe,fe)}}if(Y)return fe=fe(K),Y=xe===""?"."+L(K,0):xe,N(fe)?(pe="",Y!=null&&(pe=Y.replace(X,"$&/")+"/"),re(fe,ie,pe,"",function(U){return U})):fe!=null&&(H(fe)&&(fe=M(fe,pe+(fe.key==null||K&&K.key===fe.key?"":(""+fe.key).replace(X,"$&/")+"/")+Y)),ie.push(fe)),1;Y=0;var F=xe===""?".":xe+":";if(N(K))for(var D=0;D>>1,ce=re[se];if(0>>1;sei(pe,ue))xei(fe,pe)?(re[se]=fe,re[xe]=ue,se=xe):(re[se]=pe,re[ie]=ue,se=ie);else if(xei(fe,ue))re[se]=fe,re[xe]=ue,se=xe;else break e}}return J}function i(re,J){var ue=re.sortIndex-J.sortIndex;return ue!==0?ue:re.id-J.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=[],d=[],u=1,m=null,p=3,f=!1,x=!1,v=!1,g=!1,b=typeof setTimeout=="function"?setTimeout:null,S=typeof clearTimeout=="function"?clearTimeout:null,C=typeof setImmediate<"u"?setImmediate:null;function P(re){for(var J=n(d);J!==null;){if(J.callback===null)r(d);else if(J.startTime<=re)r(d),J.sortIndex=J.expirationTime,e(c,J);else break;J=n(d)}}function N(re){if(v=!1,P(re),!x)if(n(c)!==null)x=!0,A||(A=!0,O());else{var J=n(d);J!==null&&ee(N,J.startTime-re)}}var A=!1,T=-1,E=5,k=-1;function M(){return g?!0:!(t.unstable_now()-kre&&M());){var se=m.callback;if(typeof se=="function"){m.callback=null,p=m.priorityLevel;var ce=se(m.expirationTime<=re);if(re=t.unstable_now(),typeof ce=="function"){m.callback=ce,P(re),J=!0;break t}m===n(c)&&r(c),P(re)}else r(c);m=n(c)}if(m!==null)J=!0;else{var K=n(d);K!==null&&ee(N,K.startTime-re),J=!1}}break e}finally{m=null,p=ue,f=!1}J=void 0}}finally{J?O():A=!1}}}var O;if(typeof C=="function")O=function(){C(H)};else if(typeof MessageChannel<"u"){var X=new MessageChannel,L=X.port2;X.port1.onmessage=H,O=function(){L.postMessage(null)}}else O=function(){b(H,0)};function ee(re,J){T=b(function(){re(t.unstable_now())},J)}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(re){re.callback=null},t.unstable_forceFrameRate=function(re){0>re||125se?(re.sortIndex=ue,e(d,re),n(c)===null&&re===n(d)&&(v?(S(T),T=-1):v=!0,ee(N,ue-se))):(re.sortIndex=ce,e(c,re),x||f||(x=!0,A||(A=!0,O()))),re},t.unstable_shouldYield=M,t.unstable_wrapCallback=function(re){var J=p;return function(){var ue=p;p=J;try{return re.apply(this,arguments)}finally{p=ue}}}})(EM)),EM}var tH;function fue(){return tH||(tH=1,MM.exports=pue()),MM.exports}var DM={exports:{}},Fs={};var nH;function gue(){if(nH)return Fs;nH=1;var t=qg();function e(c){var d="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(),DM.exports=gue(),DM.exports}var aH;function bue(){if(aH)return Rv;aH=1;var t=fue(),e=qg(),n=hQ();function r(h){var y="https://react.dev/errors/"+h;if(1ce||(h.current=se[ce],se[ce]=null,ce--)}function pe(h,y){ce++,se[ce]=h.current,h.current=y}var xe=K(null),fe=K(null),B=K(null),Y=K(null);function F(h,y){switch(pe(B,y),pe(fe,h),pe(xe,null),y.nodeType){case 9:case 11:h=(h=y.documentElement)&&(h=h.namespaceURI)?y8(h):0;break;default:if(h=y.tagName,y=y.namespaceURI)y=y8(y),h=v8(y,h);else switch(h){case"svg":h=1;break;case"math":h=2;break;default:h=0}}ie(xe),pe(xe,h)}function D(){ie(xe),ie(fe),ie(B)}function U(h){h.memoizedState!==null&&pe(Y,h);var y=xe.current,_=v8(y,h.type);y!==_&&(pe(fe,h),pe(xe,_))}function Q(h){fe.current===h&&(ie(xe),ie(fe)),Y.current===h&&(ie(Y),jv._currentValue=ue)}var I,de;function z(h){if(I===void 0)try{throw Error()}catch(_){var y=_.stack.trim().match(/\n( *(at )?)/);I=y&&y[1]||"",de=-1<_.stack.indexOf(` +function xue(t,e){for(var n=0;na[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"]'))a(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"&&a(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 a(i){if(i.ep)return;i.ep=!0;const s=n(i);fetch(i.href,s)}})();var lk=typeof globalThis<"u"?globalThis:typeof window<"u"?window:typeof global<"u"?global:typeof self<"u"?self:{};function fc(t){return t&&t.__esModule&&Object.prototype.hasOwnProperty.call(t,"default")?t.default:t}var OM={exports:{}},Iv={};var sH;function yue(){if(sH)return Iv;sH=1;var t=Symbol.for("react.transitional.element"),e=Symbol.for("react.fragment");function n(a,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:a,key:o,ref:i!==void 0?i:null,props:s}}return Iv.Fragment=e,Iv.jsx=n,Iv.jsxs=n,Iv}var oH;function vue(){return oH||(oH=1,OM.exports=yue()),OM.exports}var r=vue(),zM={exports:{}},Gn={};var lH;function wue(){if(lH)return Gn;lH=1;var t=Symbol.for("react.transitional.element"),e=Symbol.for("react.portal"),n=Symbol.for("react.fragment"),a=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"),d=Symbol.for("react.memo"),u=Symbol.for("react.lazy"),m=Symbol.for("react.activity"),h=Symbol.iterator;function f(K){return K===null||typeof K!="object"?null:(K=h&&K[h]||K["@@iterator"],typeof K=="function"?K:null)}var x={isMounted:function(){return!1},enqueueForceUpdate:function(){},enqueueReplaceState:function(){},enqueueSetState:function(){}},v=Object.assign,g={};function b(K,se,pe){this.props=K,this.context=se,this.refs=g,this.updater=pe||x}b.prototype.isReactComponent={},b.prototype.setState=function(K,se){if(typeof K!="object"&&typeof K!="function"&&K!=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,K,se,"setState")},b.prototype.forceUpdate=function(K){this.updater.enqueueForceUpdate(this,K,"forceUpdate")};function S(){}S.prototype=b.prototype;function C(K,se,pe){this.props=K,this.context=se,this.refs=g,this.updater=pe||x}var P=C.prototype=new S;P.constructor=C,v(P,b.prototype),P.isPureReactComponent=!0;var N=Array.isArray;function T(){}var A={H:null,A:null,T:null,S:null},E=Object.prototype.hasOwnProperty;function k(K,se,pe){var xe=pe.ref;return{$$typeof:t,type:K,key:se,ref:xe!==void 0?xe:null,props:pe}}function j(K,se){return k(K.type,se,K.props)}function B(K){return typeof K=="object"&&K!==null&&K.$$typeof===t}function O(K){var se={"=":"=0",":":"=2"};return"$"+K.replace(/[=:]/g,function(pe){return se[pe]})}var G=/\/+/g;function L(K,se){return typeof K=="object"&&K!==null&&K.key!=null?O(""+K.key):se.toString(36)}function ee(K){switch(K.status){case"fulfilled":return K.value;case"rejected":throw K.reason;default:switch(typeof K.status=="string"?K.then(T,T):(K.status="pending",K.then(function(se){K.status==="pending"&&(K.status="fulfilled",K.value=se)},function(se){K.status==="pending"&&(K.status="rejected",K.reason=se)})),K.status){case"fulfilled":return K.value;case"rejected":throw K.reason}}throw K}function ne(K,se,pe,xe,fe){var H=typeof K;(H==="undefined"||H==="boolean")&&(K=null);var Y=!1;if(K===null)Y=!0;else switch(H){case"bigint":case"string":case"number":Y=!0;break;case"object":switch(K.$$typeof){case t:case e:Y=!0;break;case u:return Y=K._init,ne(Y(K._payload),se,pe,xe,fe)}}if(Y)return fe=fe(K),Y=xe===""?"."+L(K,0):xe,N(fe)?(pe="",Y!=null&&(pe=Y.replace(G,"$&/")+"/"),ne(fe,se,pe,"",function(U){return U})):fe!=null&&(B(fe)&&(fe=j(fe,pe+(fe.key==null||K&&K.key===fe.key?"":(""+fe.key).replace(G,"$&/")+"/")+Y)),se.push(fe)),1;Y=0;var F=xe===""?".":xe+":";if(N(K))for(var D=0;D>>1,ce=ne[ae];if(0>>1;aei(pe,oe))xei(fe,pe)?(ne[ae]=fe,ne[xe]=oe,ae=xe):(ne[ae]=pe,ne[se]=oe,ae=se);else if(xei(fe,oe))ne[ae]=fe,ne[xe]=oe,ae=xe;else break e}}return J}function i(ne,J){var oe=ne.sortIndex-J.sortIndex;return oe!==0?oe:ne.id-J.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=[],d=[],u=1,m=null,h=3,f=!1,x=!1,v=!1,g=!1,b=typeof setTimeout=="function"?setTimeout:null,S=typeof clearTimeout=="function"?clearTimeout:null,C=typeof setImmediate<"u"?setImmediate:null;function P(ne){for(var J=n(d);J!==null;){if(J.callback===null)a(d);else if(J.startTime<=ne)a(d),J.sortIndex=J.expirationTime,e(c,J);else break;J=n(d)}}function N(ne){if(v=!1,P(ne),!x)if(n(c)!==null)x=!0,T||(T=!0,O());else{var J=n(d);J!==null&&ee(N,J.startTime-ne)}}var T=!1,A=-1,E=5,k=-1;function j(){return g?!0:!(t.unstable_now()-kne&&j());){var ae=m.callback;if(typeof ae=="function"){m.callback=null,h=m.priorityLevel;var ce=ae(m.expirationTime<=ne);if(ne=t.unstable_now(),typeof ce=="function"){m.callback=ce,P(ne),J=!0;break t}m===n(c)&&a(c),P(ne)}else a(c);m=n(c)}if(m!==null)J=!0;else{var K=n(d);K!==null&&ee(N,K.startTime-ne),J=!1}}break e}finally{m=null,h=oe,f=!1}J=void 0}}finally{J?O():T=!1}}}var O;if(typeof C=="function")O=function(){C(B)};else if(typeof MessageChannel<"u"){var G=new MessageChannel,L=G.port2;G.port1.onmessage=B,O=function(){L.postMessage(null)}}else O=function(){b(B,0)};function ee(ne,J){A=b(function(){ne(t.unstable_now())},J)}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(ne){ne.callback=null},t.unstable_forceFrameRate=function(ne){0>ne||125ae?(ne.sortIndex=oe,e(d,ne),n(c)===null&&ne===n(d)&&(v?(S(A),A=-1):v=!0,ee(N,oe-ae))):(ne.sortIndex=ce,e(c,ne),x||f||(x=!0,T||(T=!0,O()))),ne},t.unstable_shouldYield=j,t.unstable_wrapCallback=function(ne){var J=h;return function(){var oe=h;h=J;try{return ne.apply(this,arguments)}finally{h=oe}}}})(HM)),HM}var uH;function kue(){return uH||(uH=1,BM.exports=_ue()),BM.exports}var qM={exports:{}},Fs={};var mH;function Nue(){if(mH)return Fs;mH=1;var t=Vg();function e(c){var d="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(),qM.exports=Nue(),qM.exports}var pH;function Cue(){if(pH)return Ov;pH=1;var t=kue(),e=Vg(),n=kY();function a(p){var y="https://react.dev/errors/"+p;if(1ce||(p.current=ae[ce],ae[ce]=null,ce--)}function pe(p,y){ce++,ae[ce]=p.current,p.current=y}var xe=K(null),fe=K(null),H=K(null),Y=K(null);function F(p,y){switch(pe(H,y),pe(fe,p),pe(xe,null),y.nodeType){case 9:case 11:p=(p=y.documentElement)&&(p=p.namespaceURI)?A8(p):0;break;default:if(p=y.tagName,y=y.namespaceURI)y=A8(y),p=j8(y,p);else switch(p){case"svg":p=1;break;case"math":p=2;break;default:p=0}}se(xe),pe(xe,p)}function D(){se(xe),se(fe),se(H)}function U(p){p.memoizedState!==null&&pe(Y,p);var y=xe.current,_=j8(y,p.type);y!==_&&(pe(fe,p),pe(xe,_))}function Q(p){fe.current===p&&(se(xe),se(fe)),Y.current===p&&(se(Y),Dv._currentValue=oe)}var I,ue;function z(p){if(I===void 0)try{throw Error()}catch(_){var y=_.stack.trim().match(/\n( *(at )?)/);I=y&&y[1]||"",ue=-1<_.stack.indexOf(` at`)?" ()":-1<_.stack.indexOf("@")?"@unknown:0:0":""}return` -`+I+h+de}var G=!1;function $(h,y){if(!h||G)return"";G=!0;var _=Error.prepareStackTrace;Error.prepareStackTrace=void 0;try{var j={DetermineComponentFrameRoot:function(){try{if(y){var Bt=function(){throw Error()};if(Object.defineProperty(Bt.prototype,"props",{set:function(){throw Error()}}),typeof Reflect=="object"&&Reflect.construct){try{Reflect.construct(Bt,[])}catch(Dt){var Ct=Dt}Reflect.construct(h,[],Bt)}else{try{Bt.call()}catch(Dt){Ct=Dt}h.call(Bt.prototype)}}else{try{throw Error()}catch(Dt){Ct=Dt}(Bt=h())&&typeof Bt.catch=="function"&&Bt.catch(function(){})}}catch(Dt){if(Dt&&Ct&&typeof Dt.stack=="string")return[Dt.stack,Ct.stack]}return[null,null]}};j.DetermineComponentFrameRoot.displayName="DetermineComponentFrameRoot";var V=Object.getOwnPropertyDescriptor(j.DetermineComponentFrameRoot,"name");V&&V.configurable&&Object.defineProperty(j.DetermineComponentFrameRoot,"name",{value:"DetermineComponentFrameRoot"});var Z=j.DetermineComponentFrameRoot(),ke=Z[0],qe=Z[1];if(ke&&qe){var ct=ke.split(` -`),wt=qe.split(` -`);for(V=j=0;jV||ct[j]!==wt[V]){var Lt=` -`+ct[j].replace(" at new "," at ");return h.displayName&&Lt.includes("")&&(Lt=Lt.replace("",h.displayName)),Lt}while(1<=j&&0<=V);break}}}finally{G=!1,Error.prepareStackTrace=_}return(_=h?h.displayName||h.name:"")?z(_):""}function W(h,y){switch(h.tag){case 26:case 27:case 5:return z(h.type);case 16:return z("Lazy");case 13:return h.child!==y&&y!==null?z("Suspense Fallback"):z("Suspense");case 19:return z("SuspenseList");case 0:case 15:return $(h.type,!1);case 11:return $(h.type.render,!1);case 1:return $(h.type,!0);case 31:return z("Activity");default:return""}}function ne(h){try{var y="",_=null;do y+=W(h,_),_=h,h=h.return;while(h);return y}catch(j){return` -Error generating stack: `+j.message+` -`+j.stack}}var ae=Object.prototype.hasOwnProperty,be=t.unstable_scheduleCallback,he=t.unstable_cancelCallback,me=t.unstable_shouldYield,q=t.unstable_requestPaint,R=t.unstable_now,te=t.unstable_getCurrentPriorityLevel,ge=t.unstable_ImmediatePriority,_e=t.unstable_UserBlockingPriority,Ce=t.unstable_NormalPriority,Ae=t.unstable_LowPriority,ye=t.unstable_IdlePriority,Re=t.log,Ie=t.unstable_setDisableYieldValue,Ne=null,ze=null;function je(h){if(typeof Re=="function"&&Ie(h),ze&&typeof ze.setStrictMode=="function")try{ze.setStrictMode(Ne,h)}catch{}}var Ue=Math.clz32?Math.clz32:De,We=Math.log,tt=Math.LN2;function De(h){return h>>>=0,h===0?32:31-(We(h)/tt|0)|0}var nt=256,Pe=262144,Ke=4194304;function Se(h){var y=h&42;if(y!==0)return y;switch(h&-h){case 1:return 1;case 2:return 2;case 4:return 4;case 8:return 8;case 16:return 16;case 32:return 32;case 64:return 64;case 128:return 128;case 256:case 512:case 1024:case 2048:case 4096:case 8192:case 16384:case 32768:case 65536:case 131072:return h&261888;case 262144:case 524288:case 1048576:case 2097152:return h&3932160;case 4194304:case 8388608:case 16777216:case 33554432:return h&62914560;case 67108864:return 67108864;case 134217728:return 134217728;case 268435456:return 268435456;case 536870912:return 536870912;case 1073741824:return 0;default:return h}}function le(h,y,_){var j=h.pendingLanes;if(j===0)return 0;var V=0,Z=h.suspendedLanes,ke=h.pingedLanes;h=h.warmLanes;var qe=j&134217727;return qe!==0?(j=qe&~Z,j!==0?V=Se(j):(ke&=qe,ke!==0?V=Se(ke):_||(_=qe&~h,_!==0&&(V=Se(_))))):(qe=j&~Z,qe!==0?V=Se(qe):ke!==0?V=Se(ke):_||(_=j&~h,_!==0&&(V=Se(_)))),V===0?0:y!==0&&y!==V&&(y&Z)===0&&(Z=V&-V,_=y&-y,Z>=_||Z===32&&(_&4194048)!==0)?y:V}function we(h,y){return(h.pendingLanes&~(h.suspendedLanes&~h.pingedLanes)&y)===0}function He(h,y){switch(h){case 1:case 2:case 4:case 8:case 64:return y+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 y+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 st(){var h=Ke;return Ke<<=1,(Ke&62914560)===0&&(Ke=4194304),h}function ft(h){for(var y=[],_=0;31>_;_++)y.push(h);return y}function _t(h,y){h.pendingLanes|=y,y!==268435456&&(h.suspendedLanes=0,h.pingedLanes=0,h.warmLanes=0)}function Ve(h,y,_,j,V,Z){var ke=h.pendingLanes;h.pendingLanes=_,h.suspendedLanes=0,h.pingedLanes=0,h.warmLanes=0,h.expiredLanes&=_,h.entangledLanes&=_,h.errorRecoveryDisabledLanes&=_,h.shellSuspendCounter=0;var qe=h.entanglements,ct=h.expirationTimes,wt=h.hiddenUpdates;for(_=ke&~_;0<_;){var Lt=31-Ue(_),Bt=1<"u")return null;try{return h.activeElement||h.body}catch{return h.body}}var Cr=/[\n"\\]/g;function On(h){return h.replace(Cr,function(y){return"\\"+y.charCodeAt(0).toString(16)+" "})}function Fa(h,y,_,j,V,Z,ke,qe){h.name="",ke!=null&&typeof ke!="function"&&typeof ke!="symbol"&&typeof ke!="boolean"?h.type=ke:h.removeAttribute("type"),y!=null?ke==="number"?(y===0&&h.value===""||h.value!=y)&&(h.value=""+sn(y)):h.value!==""+sn(y)&&(h.value=""+sn(y)):ke!=="submit"&&ke!=="reset"||h.removeAttribute("value"),y!=null?Or(h,ke,sn(y)):_!=null?Or(h,ke,sn(_)):j!=null&&h.removeAttribute("value"),V==null&&Z!=null&&(h.defaultChecked=!!Z),V!=null&&(h.checked=V&&typeof V!="function"&&typeof V!="symbol"),qe!=null&&typeof qe!="function"&&typeof qe!="symbol"&&typeof qe!="boolean"?h.name=""+sn(qe):h.removeAttribute("name")}function qn(h,y,_,j,V,Z,ke,qe){if(Z!=null&&typeof Z!="function"&&typeof Z!="symbol"&&typeof Z!="boolean"&&(h.type=Z),y!=null||_!=null){if(!(Z!=="submit"&&Z!=="reset"||y!=null)){Nn(h);return}_=_!=null?""+sn(_):"",y=y!=null?""+sn(y):_,qe||y===h.value||(h.value=y),h.defaultValue=y}j=j??V,j=typeof j!="function"&&typeof j!="symbol"&&!!j,h.checked=qe?h.checked:!!j,h.defaultChecked=!!j,ke!=null&&typeof ke!="function"&&typeof ke!="symbol"&&typeof ke!="boolean"&&(h.name=ke),Nn(h)}function Or(h,y,_){y==="number"&&gr(h.ownerDocument)===h||h.defaultValue===""+_||(h.defaultValue=""+_)}function Gr(h,y,_,j){if(h=h.options,y){y={};for(var V=0;V<_.length;V++)y["$"+_[V]]=!0;for(_=0;_"u"||typeof window.document>"u"||typeof window.document.createElement>"u"),Ii=!1;if(qo)try{var vl={};Object.defineProperty(vl,"passive",{get:function(){Ii=!0}}),window.addEventListener("test",vl,vl),window.removeEventListener("test",vl,vl)}catch{Ii=!1}var yo=null,ha=null,bc=null;function Bm(){if(bc)return bc;var h,y=ha,_=y.length,j,V="value"in yo?yo.value:yo.textContent,Z=V.length;for(h=0;h<_&&y[h]===V[h];h++);var ke=_-h;for(j=1;j<=ke&&y[_-j]===V[Z-j];j++);return bc=V.slice(h,1=uu),bt=" ",Rt=!1;function ot(h,y){switch(h){case"keyup":return Wm.indexOf(y.keyCode)!==-1;case"keydown":return y.keyCode!==229;case"keypress":case"mousedown":case"focusout":return!0;default:return!1}}function on(h){return h=h.detail,typeof h=="object"&&"data"in h?h.data:null}var En=!1;function fn(h,y){switch(h){case"compositionend":return on(y);case"keypress":return y.which!==32?null:(Rt=!0,bt);case"textInput":return h=y.data,h===bt&&Rt?null:h;default:return null}}function Rr(h,y){if(En)return h==="compositionend"||!du&&ot(h,y)?(h=Bm(),bc=ha=yo=null,En=!1,h):null;switch(h){case"paste":return null;case"keypress":if(!(y.ctrlKey||y.altKey||y.metaKey)||y.ctrlKey&&y.altKey){if(y.char&&1=y)return{node:_,offset:y-h};h=j}e:{for(;_;){if(_.nextSibling){_=_.nextSibling;break e}_=_.parentNode}_=void 0}_=LA(_)}}function Jp(h,y){return h&&y?h===y?!0:h&&h.nodeType===3?!1:y&&y.nodeType===3?Jp(h,y.parentNode):"contains"in h?h.contains(y):h.compareDocumentPosition?!!(h.compareDocumentPosition(y)&16):!1:!1}function ev(h){h=h!=null&&h.ownerDocument!=null&&h.ownerDocument.defaultView!=null?h.ownerDocument.defaultView:window;for(var y=gr(h.document);y instanceof h.HTMLIFrameElement;){try{var _=typeof y.contentWindow.location.href=="string"}catch{_=!1}if(_)h=y.contentWindow;else break;y=gr(h.document)}return y}function tb(h){var y=h&&h.nodeName&&h.nodeName.toLowerCase();return y&&(y==="input"&&(h.type==="text"||h.type==="search"||h.type==="tel"||h.type==="url"||h.type==="password")||y==="textarea"||h.contentEditable==="true")}var IA=qo&&"documentMode"in document&&11>=document.documentMode,nb=null,OA=null,tv=null,zA=!1;function B5(h,y,_){var j=_.window===_?_.document:_.nodeType===9?_:_.ownerDocument;zA||nb==null||nb!==gr(j)||(j=nb,"selectionStart"in j&&tb(j)?j={start:j.selectionStart,end:j.selectionEnd}:(j=(j.ownerDocument&&j.ownerDocument.defaultView||window).getSelection(),j={anchorNode:j.anchorNode,anchorOffset:j.anchorOffset,focusNode:j.focusNode,focusOffset:j.focusOffset}),tv&&Sn(tv,j)||(tv=j,j=B_(OA,"onSelect"),0>=ke,V-=ke,dd=1<<32-Ue(y)+V|_<Yn?(xr=_n,_n=null):xr=_n.sibling;var jr=Ct(gt,_n,vt[Yn],zt);if(jr===null){_n===null&&(_n=xr);break}h&&_n&&jr.alternate===null&&y(gt,_n),ht=Z(jr,ht,Yn),Ar===null?jn=jr:Ar.sibling=jr,Ar=jr,_n=xr}if(Yn===vt.length)return _(gt,_n),wr&&pu(gt,Yn),jn;if(_n===null){for(;YnYn?(xr=_n,_n=null):xr=_n.sibling;var fh=Ct(gt,_n,jr.value,zt);if(fh===null){_n===null&&(_n=xr);break}h&&_n&&fh.alternate===null&&y(gt,_n),ht=Z(fh,ht,Yn),Ar===null?jn=fh:Ar.sibling=fh,Ar=fh,_n=xr}if(jr.done)return _(gt,_n),wr&&pu(gt,Yn),jn;if(_n===null){for(;!jr.done;Yn++,jr=vt.next())jr=Bt(gt,jr.value,zt),jr!==null&&(ht=Z(jr,ht,Yn),Ar===null?jn=jr:Ar.sibling=jr,Ar=jr);return wr&&pu(gt,Yn),jn}for(_n=j(_n);!jr.done;Yn++,jr=vt.next())jr=Dt(_n,gt,Yn,jr.value,zt),jr!==null&&(h&&jr.alternate!==null&&_n.delete(jr.key===null?Yn:jr.key),ht=Z(jr,ht,Yn),Ar===null?jn=jr:Ar.sibling=jr,Ar=jr);return h&&_n.forEach(function(lue){return y(gt,lue)}),wr&&pu(gt,Yn),jn}function Xr(gt,ht,vt,zt){if(typeof vt=="object"&&vt!==null&&vt.type===v&&vt.key===null&&(vt=vt.props.children),typeof vt=="object"&&vt!==null){switch(vt.$$typeof){case f:e:{for(var jn=vt.key;ht!==null;){if(ht.key===jn){if(jn=vt.type,jn===v){if(ht.tag===7){_(gt,ht.sibling),zt=V(ht,vt.props.children),zt.return=gt,gt=zt;break e}}else if(ht.elementType===jn||typeof jn=="object"&&jn!==null&&jn.$$typeof===E&&lf(jn)===ht.type){_(gt,ht.sibling),zt=V(ht,vt.props),ov(zt,vt),zt.return=gt,gt=zt;break e}_(gt,ht);break}else y(gt,ht);ht=ht.sibling}vt.type===v?(zt=nf(vt.props.children,gt.mode,zt,vt.key),zt.return=gt,gt=zt):(zt=a_(vt.type,vt.key,vt.props,null,gt.mode,zt),ov(zt,vt),zt.return=gt,gt=zt)}return ke(gt);case x:e:{for(jn=vt.key;ht!==null;){if(ht.key===jn)if(ht.tag===4&&ht.stateNode.containerInfo===vt.containerInfo&&ht.stateNode.implementation===vt.implementation){_(gt,ht.sibling),zt=V(ht,vt.children||[]),zt.return=gt,gt=zt;break e}else{_(gt,ht);break}else y(gt,ht);ht=ht.sibling}zt=GA(vt,gt.mode,zt),zt.return=gt,gt=zt}return ke(gt);case E:return vt=lf(vt),Xr(gt,ht,vt,zt)}if(ee(vt))return gn(gt,ht,vt,zt);if(O(vt)){if(jn=O(vt),typeof jn!="function")throw Error(r(150));return vt=jn.call(vt),In(gt,ht,vt,zt)}if(typeof vt.then=="function")return Xr(gt,ht,u_(vt),zt);if(vt.$$typeof===C)return Xr(gt,ht,o_(gt,vt),zt);m_(gt,vt)}return typeof vt=="string"&&vt!==""||typeof vt=="number"||typeof vt=="bigint"?(vt=""+vt,ht!==null&&ht.tag===6?(_(gt,ht.sibling),zt=V(ht,vt),zt.return=gt,gt=zt):(_(gt,ht),zt=VA(vt,gt.mode,zt),zt.return=gt,gt=zt),ke(gt)):_(gt,ht)}return function(gt,ht,vt,zt){try{sv=0;var jn=Xr(gt,ht,vt,zt);return hb=null,jn}catch(_n){if(_n===mb||_n===c_)throw _n;var Ar=Wo(29,_n,null,gt.mode);return Ar.lanes=zt,Ar.return=gt,Ar}}}var df=dU(!0),uU=dU(!1),Jm=!1;function aj(h){h.updateQueue={baseState:h.memoizedState,firstBaseUpdate:null,lastBaseUpdate:null,shared:{pending:null,lanes:0,hiddenCallbacks:null},callbacks:null}}function ij(h,y){h=h.updateQueue,y.updateQueue===h&&(y.updateQueue={baseState:h.baseState,firstBaseUpdate:h.firstBaseUpdate,lastBaseUpdate:h.lastBaseUpdate,shared:h.shared,callbacks:null})}function Zm(h){return{lane:h,tag:0,payload:null,callback:null,next:null}}function eh(h,y,_){var j=h.updateQueue;if(j===null)return null;if(j=j.shared,(Fr&2)!==0){var V=j.pending;return V===null?y.next=y:(y.next=V.next,V.next=y),j.pending=y,y=r_(h),K5(h,null,_),y}return n_(h,j,y,_),r_(h)}function lv(h,y,_){if(y=y.updateQueue,y!==null&&(y=y.shared,(_&4194048)!==0)){var j=y.lanes;j&=h.pendingLanes,_|=j,y.lanes=_,Pt(h,_)}}function sj(h,y){var _=h.updateQueue,j=h.alternate;if(j!==null&&(j=j.updateQueue,_===j)){var V=null,Z=null;if(_=_.firstBaseUpdate,_!==null){do{var ke={lane:_.lane,tag:_.tag,payload:_.payload,callback:null,next:null};Z===null?V=Z=ke:Z=Z.next=ke,_=_.next}while(_!==null);Z===null?V=Z=y:Z=Z.next=y}else V=Z=y;_={baseState:j.baseState,firstBaseUpdate:V,lastBaseUpdate:Z,shared:j.shared,callbacks:j.callbacks},h.updateQueue=_;return}h=_.lastBaseUpdate,h===null?_.firstBaseUpdate=y:h.next=y,_.lastBaseUpdate=y}var oj=!1;function cv(){if(oj){var h=ub;if(h!==null)throw h}}function dv(h,y,_,j){oj=!1;var V=h.updateQueue;Jm=!1;var Z=V.firstBaseUpdate,ke=V.lastBaseUpdate,qe=V.shared.pending;if(qe!==null){V.shared.pending=null;var ct=qe,wt=ct.next;ct.next=null,ke===null?Z=wt:ke.next=wt,ke=ct;var Lt=h.alternate;Lt!==null&&(Lt=Lt.updateQueue,qe=Lt.lastBaseUpdate,qe!==ke&&(qe===null?Lt.firstBaseUpdate=wt:qe.next=wt,Lt.lastBaseUpdate=ct))}if(Z!==null){var Bt=V.baseState;ke=0,Lt=wt=ct=null,qe=Z;do{var Ct=qe.lane&-536870913,Dt=Ct!==qe.lane;if(Dt?(br&Ct)===Ct:(j&Ct)===Ct){Ct!==0&&Ct===db&&(oj=!0),Lt!==null&&(Lt=Lt.next={lane:0,tag:qe.tag,payload:qe.payload,callback:null,next:null});e:{var gn=h,In=qe;Ct=y;var Xr=_;switch(In.tag){case 1:if(gn=In.payload,typeof gn=="function"){Bt=gn.call(Xr,Bt,Ct);break e}Bt=gn;break e;case 3:gn.flags=gn.flags&-65537|128;case 0:if(gn=In.payload,Ct=typeof gn=="function"?gn.call(Xr,Bt,Ct):gn,Ct==null)break e;Bt=m({},Bt,Ct);break e;case 2:Jm=!0}}Ct=qe.callback,Ct!==null&&(h.flags|=64,Dt&&(h.flags|=8192),Dt=V.callbacks,Dt===null?V.callbacks=[Ct]:Dt.push(Ct))}else Dt={lane:Ct,tag:qe.tag,payload:qe.payload,callback:qe.callback,next:null},Lt===null?(wt=Lt=Dt,ct=Bt):Lt=Lt.next=Dt,ke|=Ct;if(qe=qe.next,qe===null){if(qe=V.shared.pending,qe===null)break;Dt=qe,qe=Dt.next,Dt.next=null,V.lastBaseUpdate=Dt,V.shared.pending=null}}while(!0);Lt===null&&(ct=Bt),V.baseState=ct,V.firstBaseUpdate=wt,V.lastBaseUpdate=Lt,Z===null&&(V.shared.lanes=0),ih|=ke,h.lanes=ke,h.memoizedState=Bt}}function mU(h,y){if(typeof h!="function")throw Error(r(191,h));h.call(y)}function hU(h,y){var _=h.callbacks;if(_!==null)for(h.callbacks=null,h=0;h<_.length;h++)mU(_[h],y)}var pb=K(null),h_=K(0);function pU(h,y){h=Nu,pe(h_,h),pe(pb,y),Nu=h|y.baseLanes}function lj(){pe(h_,Nu),pe(pb,pb.current)}function cj(){Nu=h_.current,ie(pb),ie(h_)}var Ko=K(null),jl=null;function th(h){var y=h.alternate;pe(ti,ti.current&1),pe(Ko,h),jl===null&&(y===null||pb.current!==null||y.memoizedState!==null)&&(jl=h)}function dj(h){pe(ti,ti.current),pe(Ko,h),jl===null&&(jl=h)}function fU(h){h.tag===22?(pe(ti,ti.current),pe(Ko,h),jl===null&&(jl=h)):nh()}function nh(){pe(ti,ti.current),pe(Ko,Ko.current)}function Xo(h){ie(Ko),jl===h&&(jl=null),ie(ti)}var ti=K(0);function p_(h){for(var y=h;y!==null;){if(y.tag===13){var _=y.memoizedState;if(_!==null&&(_=_.dehydrated,_===null||gM(_)||bM(_)))return y}else if(y.tag===19&&(y.memoizedProps.revealOrder==="forwards"||y.memoizedProps.revealOrder==="backwards"||y.memoizedProps.revealOrder==="unstable_legacy-backwards"||y.memoizedProps.revealOrder==="together")){if((y.flags&128)!==0)return y}else if(y.child!==null){y.child.return=y,y=y.child;continue}if(y===h)break;for(;y.sibling===null;){if(y.return===null||y.return===h)return null;y=y.return}y.sibling.return=y.return,y=y.sibling}return null}var bu=0,Kn=null,Wr=null,li=null,f_=!1,fb=!1,uf=!1,g_=0,uv=0,gb=null,Jce=0;function Ha(){throw Error(r(321))}function uj(h,y){if(y===null)return!1;for(var _=0;_Z?Z:8;var ke=re.T,qe={};re.T=qe,Cj(h,!1,y,_);try{var ct=V(),wt=re.S;if(wt!==null&&wt(qe,ct),ct!==null&&typeof ct=="object"&&typeof ct.then=="function"){var Lt=Qce(ct,j);hv(h,y,Lt,Jo(h))}else hv(h,y,j,Jo(h))}catch(Bt){hv(h,y,{then:function(){},status:"rejected",reason:Bt},Jo())}finally{J.p=Z,ke!==null&&qe.types!==null&&(ke.types=qe.types),re.T=ke}}function rde(){}function kj(h,y,_,j){if(h.tag!==5)throw Error(r(476));var V=VU(h).queue;$U(h,V,y,ue,_===null?rde:function(){return GU(h),_(j)})}function VU(h){var y=h.memoizedState;if(y!==null)return y;y={memoizedState:ue,baseState:ue,baseQueue:null,queue:{pending:null,lanes:0,dispatch:null,lastRenderedReducer:xu,lastRenderedState:ue},next:null};var _={};return y.next={memoizedState:_,baseState:_,baseQueue:null,queue:{pending:null,lanes:0,dispatch:null,lastRenderedReducer:xu,lastRenderedState:_},next:null},h.memoizedState=y,h=h.alternate,h!==null&&(h.memoizedState=y),y}function GU(h){var y=VU(h);y.next===null&&(y=h.alternate.memoizedState),hv(h,y.next.queue,{},Jo())}function Nj(){return ds(jv)}function WU(){return ni().memoizedState}function KU(){return ni().memoizedState}function ade(h){for(var y=h.return;y!==null;){switch(y.tag){case 24:case 3:var _=Jo();h=Zm(_);var j=eh(y,h,_);j!==null&&(Po(j,y,_),lv(j,y,_)),y={cache:ej()},h.payload=y;return}y=y.return}}function ide(h,y,_){var j=Jo();_={lane:j,revertLane:0,gesture:null,action:_,hasEagerState:!1,eagerState:null,next:null},S_(h)?YU(y,_):(_=qA(h,y,_,j),_!==null&&(Po(_,h,j),QU(_,y,j)))}function XU(h,y,_){var j=Jo();hv(h,y,_,j)}function hv(h,y,_,j){var V={lane:j,revertLane:0,gesture:null,action:_,hasEagerState:!1,eagerState:null,next:null};if(S_(h))YU(y,V);else{var Z=h.alternate;if(h.lanes===0&&(Z===null||Z.lanes===0)&&(Z=y.lastRenderedReducer,Z!==null))try{var ke=y.lastRenderedState,qe=Z(ke,_);if(V.hasEagerState=!0,V.eagerState=qe,Si(qe,ke))return n_(h,y,V,0),oa===null&&t_(),!1}catch{}if(_=qA(h,y,V,j),_!==null)return Po(_,h,j),QU(_,y,j),!0}return!1}function Cj(h,y,_,j){if(j={lane:2,revertLane:aM(),gesture:null,action:j,hasEagerState:!1,eagerState:null,next:null},S_(h)){if(y)throw Error(r(479))}else y=qA(h,_,j,2),y!==null&&Po(y,h,2)}function S_(h){var y=h.alternate;return h===Kn||y!==null&&y===Kn}function YU(h,y){fb=f_=!0;var _=h.pending;_===null?y.next=y:(y.next=_.next,_.next=y),h.pending=y}function QU(h,y,_){if((_&4194048)!==0){var j=y.lanes;j&=h.pendingLanes,_|=j,y.lanes=_,Pt(h,_)}}var pv={readContext:ds,use:x_,useCallback:Ha,useContext:Ha,useEffect:Ha,useImperativeHandle:Ha,useLayoutEffect:Ha,useInsertionEffect:Ha,useMemo:Ha,useReducer:Ha,useRef:Ha,useState:Ha,useDebugValue:Ha,useDeferredValue:Ha,useTransition:Ha,useSyncExternalStore:Ha,useId:Ha,useHostTransitionStatus:Ha,useFormState:Ha,useActionState:Ha,useOptimistic:Ha,useMemoCache:Ha,useCacheRefresh:Ha};pv.useEffectEvent=Ha;var JU={readContext:ds,use:x_,useCallback:function(h,y){return Js().memoizedState=[h,y===void 0?null:y],h},useContext:ds,useEffect:RU,useImperativeHandle:function(h,y,_){_=_!=null?_.concat([h]):null,v_(4194308,4,zU.bind(null,y,h),_)},useLayoutEffect:function(h,y){return v_(4194308,4,h,y)},useInsertionEffect:function(h,y){v_(4,2,h,y)},useMemo:function(h,y){var _=Js();y=y===void 0?null:y;var j=h();if(uf){je(!0);try{h()}finally{je(!1)}}return _.memoizedState=[j,y],j},useReducer:function(h,y,_){var j=Js();if(_!==void 0){var V=_(y);if(uf){je(!0);try{_(y)}finally{je(!1)}}}else V=y;return j.memoizedState=j.baseState=V,h={pending:null,lanes:0,dispatch:null,lastRenderedReducer:h,lastRenderedState:V},j.queue=h,h=h.dispatch=ide.bind(null,Kn,h),[j.memoizedState,h]},useRef:function(h){var y=Js();return h={current:h},y.memoizedState=h},useState:function(h){h=yj(h);var y=h.queue,_=XU.bind(null,Kn,y);return y.dispatch=_,[h.memoizedState,_]},useDebugValue:Sj,useDeferredValue:function(h,y){var _=Js();return _j(_,h,y)},useTransition:function(){var h=yj(!1);return h=$U.bind(null,Kn,h.queue,!0,!1),Js().memoizedState=h,[!1,h]},useSyncExternalStore:function(h,y,_){var j=Kn,V=Js();if(wr){if(_===void 0)throw Error(r(407));_=_()}else{if(_=y(),oa===null)throw Error(r(349));(br&127)!==0||yU(j,y,_)}V.memoizedState=_;var Z={value:_,getSnapshot:y};return V.queue=Z,RU(wU.bind(null,j,Z,h),[h]),j.flags|=2048,bb(9,{destroy:void 0},vU.bind(null,j,Z,_,y),null),_},useId:function(){var h=Js(),y=oa.identifierPrefix;if(wr){var _=ud,j=dd;_=(j&~(1<<32-Ue(j)-1)).toString(32)+_,y="_"+y+"R_"+_,_=g_++,0<_&&(y+="H"+_.toString(32)),y+="_"}else _=Jce++,y="_"+y+"r_"+_.toString(32)+"_";return h.memoizedState=y},useHostTransitionStatus:Nj,useFormState:jU,useActionState:jU,useOptimistic:function(h){var y=Js();y.memoizedState=y.baseState=h;var _={pending:null,lanes:0,dispatch:null,lastRenderedReducer:null,lastRenderedState:null};return y.queue=_,y=Cj.bind(null,Kn,!0,_),_.dispatch=y,[h,y]},useMemoCache:gj,useCacheRefresh:function(){return Js().memoizedState=ade.bind(null,Kn)},useEffectEvent:function(h){var y=Js(),_={impl:h};return y.memoizedState=_,function(){if((Fr&2)!==0)throw Error(r(440));return _.impl.apply(void 0,arguments)}}},Pj={readContext:ds,use:x_,useCallback:BU,useContext:ds,useEffect:wj,useImperativeHandle:UU,useInsertionEffect:IU,useLayoutEffect:OU,useMemo:HU,useReducer:y_,useRef:FU,useState:function(){return y_(xu)},useDebugValue:Sj,useDeferredValue:function(h,y){var _=ni();return qU(_,Wr.memoizedState,h,y)},useTransition:function(){var h=y_(xu)[0],y=ni().memoizedState;return[typeof h=="boolean"?h:mv(h),y]},useSyncExternalStore:xU,useId:WU,useHostTransitionStatus:Nj,useFormState:MU,useActionState:MU,useOptimistic:function(h,y){var _=ni();return kU(_,Wr,h,y)},useMemoCache:gj,useCacheRefresh:KU};Pj.useEffectEvent=LU;var ZU={readContext:ds,use:x_,useCallback:BU,useContext:ds,useEffect:wj,useImperativeHandle:UU,useInsertionEffect:IU,useLayoutEffect:OU,useMemo:HU,useReducer:xj,useRef:FU,useState:function(){return xj(xu)},useDebugValue:Sj,useDeferredValue:function(h,y){var _=ni();return Wr===null?_j(_,h,y):qU(_,Wr.memoizedState,h,y)},useTransition:function(){var h=xj(xu)[0],y=ni().memoizedState;return[typeof h=="boolean"?h:mv(h),y]},useSyncExternalStore:xU,useId:WU,useHostTransitionStatus:Nj,useFormState:DU,useActionState:DU,useOptimistic:function(h,y){var _=ni();return Wr!==null?kU(_,Wr,h,y):(_.baseState=h,[h,_.queue.dispatch])},useMemoCache:gj,useCacheRefresh:KU};ZU.useEffectEvent=LU;function Tj(h,y,_,j){y=h.memoizedState,_=_(j,y),_=_==null?y:m({},y,_),h.memoizedState=_,h.lanes===0&&(h.updateQueue.baseState=_)}var Aj={enqueueSetState:function(h,y,_){h=h._reactInternals;var j=Jo(),V=Zm(j);V.payload=y,_!=null&&(V.callback=_),y=eh(h,V,j),y!==null&&(Po(y,h,j),lv(y,h,j))},enqueueReplaceState:function(h,y,_){h=h._reactInternals;var j=Jo(),V=Zm(j);V.tag=1,V.payload=y,_!=null&&(V.callback=_),y=eh(h,V,j),y!==null&&(Po(y,h,j),lv(y,h,j))},enqueueForceUpdate:function(h,y){h=h._reactInternals;var _=Jo(),j=Zm(_);j.tag=2,y!=null&&(j.callback=y),y=eh(h,j,_),y!==null&&(Po(y,h,_),lv(y,h,_))}};function eB(h,y,_,j,V,Z,ke){return h=h.stateNode,typeof h.shouldComponentUpdate=="function"?h.shouldComponentUpdate(j,Z,ke):y.prototype&&y.prototype.isPureReactComponent?!Sn(_,j)||!Sn(V,Z):!0}function tB(h,y,_,j){h=y.state,typeof y.componentWillReceiveProps=="function"&&y.componentWillReceiveProps(_,j),typeof y.UNSAFE_componentWillReceiveProps=="function"&&y.UNSAFE_componentWillReceiveProps(_,j),y.state!==h&&Aj.enqueueReplaceState(y,y.state,null)}function mf(h,y){var _=y;if("ref"in y){_={};for(var j in y)j!=="ref"&&(_[j]=y[j])}if(h=h.defaultProps){_===y&&(_=m({},_));for(var V in h)_[V]===void 0&&(_[V]=h[V])}return _}function nB(h){e_(h)}function rB(h){console.error(h)}function aB(h){e_(h)}function __(h,y){try{var _=h.onUncaughtError;_(y.value,{componentStack:y.stack})}catch(j){setTimeout(function(){throw j})}}function iB(h,y,_){try{var j=h.onCaughtError;j(_.value,{componentStack:_.stack,errorBoundary:y.tag===1?y.stateNode:null})}catch(V){setTimeout(function(){throw V})}}function jj(h,y,_){return _=Zm(_),_.tag=3,_.payload={element:null},_.callback=function(){__(h,y)},_}function sB(h){return h=Zm(h),h.tag=3,h}function oB(h,y,_,j){var V=_.type.getDerivedStateFromError;if(typeof V=="function"){var Z=j.value;h.payload=function(){return V(Z)},h.callback=function(){iB(y,_,j)}}var ke=_.stateNode;ke!==null&&typeof ke.componentDidCatch=="function"&&(h.callback=function(){iB(y,_,j),typeof V!="function"&&(sh===null?sh=new Set([this]):sh.add(this));var qe=j.stack;this.componentDidCatch(j.value,{componentStack:qe!==null?qe:""})})}function sde(h,y,_,j,V){if(_.flags|=32768,j!==null&&typeof j=="object"&&typeof j.then=="function"){if(y=_.alternate,y!==null&&cb(y,_,V,!0),_=Ko.current,_!==null){switch(_.tag){case 31:case 13:return jl===null?R_():_.alternate===null&&qa===0&&(qa=3),_.flags&=-257,_.flags|=65536,_.lanes=V,j===d_?_.flags|=16384:(y=_.updateQueue,y===null?_.updateQueue=new Set([j]):y.add(j),tM(h,j,V)),!1;case 22:return _.flags|=65536,j===d_?_.flags|=16384:(y=_.updateQueue,y===null?(y={transitions:null,markerInstances:null,retryQueue:new Set([j])},_.updateQueue=y):(_=y.retryQueue,_===null?y.retryQueue=new Set([j]):_.add(j)),tM(h,j,V)),!1}throw Error(r(435,_.tag))}return tM(h,j,V),R_(),!1}if(wr)return y=Ko.current,y!==null?((y.flags&65536)===0&&(y.flags|=256),y.flags|=65536,y.lanes=V,j!==XA&&(h=Error(r(422),{cause:j}),rv(Cl(h,_)))):(j!==XA&&(y=Error(r(423),{cause:j}),rv(Cl(y,_))),h=h.current.alternate,h.flags|=65536,V&=-V,h.lanes|=V,j=Cl(j,_),V=jj(h.stateNode,j,V),sj(h,V),qa!==4&&(qa=2)),!1;var Z=Error(r(520),{cause:j});if(Z=Cl(Z,_),Sv===null?Sv=[Z]:Sv.push(Z),qa!==4&&(qa=2),y===null)return!0;j=Cl(j,_),_=y;do{switch(_.tag){case 3:return _.flags|=65536,h=V&-V,_.lanes|=h,h=jj(_.stateNode,j,h),sj(_,h),!1;case 1:if(y=_.type,Z=_.stateNode,(_.flags&128)===0&&(typeof y.getDerivedStateFromError=="function"||Z!==null&&typeof Z.componentDidCatch=="function"&&(sh===null||!sh.has(Z))))return _.flags|=65536,V&=-V,_.lanes|=V,V=sB(V),oB(V,h,_,j),sj(_,V),!1}_=_.return}while(_!==null);return!1}var Mj=Error(r(461)),ci=!1;function us(h,y,_,j){y.child=h===null?uU(y,null,_,j):df(y,h.child,_,j)}function lB(h,y,_,j,V){_=_.render;var Z=y.ref;if("ref"in j){var ke={};for(var qe in j)qe!=="ref"&&(ke[qe]=j[qe])}else ke=j;return sf(y),j=mj(h,y,_,ke,Z,V),qe=hj(),h!==null&&!ci?(pj(h,y,V),yu(h,y,V)):(wr&&qe&&WA(y),y.flags|=1,us(h,y,j,V),y.child)}function cB(h,y,_,j,V){if(h===null){var Z=_.type;return typeof Z=="function"&&!$A(Z)&&Z.defaultProps===void 0&&_.compare===null?(y.tag=15,y.type=Z,dB(h,y,Z,j,V)):(h=a_(_.type,null,j,y,y.mode,V),h.ref=y.ref,h.return=y,y.child=h)}if(Z=h.child,!zj(h,V)){var ke=Z.memoizedProps;if(_=_.compare,_=_!==null?_:Sn,_(ke,j)&&h.ref===y.ref)return yu(h,y,V)}return y.flags|=1,h=hu(Z,j),h.ref=y.ref,h.return=y,y.child=h}function dB(h,y,_,j,V){if(h!==null){var Z=h.memoizedProps;if(Sn(Z,j)&&h.ref===y.ref)if(ci=!1,y.pendingProps=j=Z,zj(h,V))(h.flags&131072)!==0&&(ci=!0);else return y.lanes=h.lanes,yu(h,y,V)}return Ej(h,y,_,j,V)}function uB(h,y,_,j){var V=j.children,Z=h!==null?h.memoizedState:null;if(h===null&&y.stateNode===null&&(y.stateNode={_visibility:1,_pendingMarkers:null,_retryCache:null,_transitions:null}),j.mode==="hidden"){if((y.flags&128)!==0){if(Z=Z!==null?Z.baseLanes|_:_,h!==null){for(j=y.child=h.child,V=0;j!==null;)V=V|j.lanes|j.childLanes,j=j.sibling;j=V&~Z}else j=0,y.child=null;return mB(h,y,Z,_,j)}if((_&536870912)!==0)y.memoizedState={baseLanes:0,cachePool:null},h!==null&&l_(y,Z!==null?Z.cachePool:null),Z!==null?pU(y,Z):lj(),fU(y);else return j=y.lanes=536870912,mB(h,y,Z!==null?Z.baseLanes|_:_,_,j)}else Z!==null?(l_(y,Z.cachePool),pU(y,Z),nh(),y.memoizedState=null):(h!==null&&l_(y,null),lj(),nh());return us(h,y,V,_),y.child}function fv(h,y){return h!==null&&h.tag===22||y.stateNode!==null||(y.stateNode={_visibility:1,_pendingMarkers:null,_retryCache:null,_transitions:null}),y.sibling}function mB(h,y,_,j,V){var Z=nj();return Z=Z===null?null:{parent:oi._currentValue,pool:Z},y.memoizedState={baseLanes:_,cachePool:Z},h!==null&&l_(y,null),lj(),fU(y),h!==null&&cb(h,y,j,!0),y.childLanes=V,null}function k_(h,y){return y=C_({mode:y.mode,children:y.children},h.mode),y.ref=h.ref,h.child=y,y.return=h,y}function hB(h,y,_){return df(y,h.child,null,_),h=k_(y,y.pendingProps),h.flags|=2,Xo(y),y.memoizedState=null,h}function ode(h,y,_){var j=y.pendingProps,V=(y.flags&128)!==0;if(y.flags&=-129,h===null){if(wr){if(j.mode==="hidden")return h=k_(y,j),y.lanes=536870912,fv(null,h);if(dj(y),(h=ga)?(h=N8(h,Al),h=h!==null&&h.data==="&"?h:null,h!==null&&(y.memoizedState={dehydrated:h,treeContext:Km!==null?{id:dd,overflow:ud}:null,retryLane:536870912,hydrationErrors:null},_=Y5(h),_.return=y,y.child=_,cs=y,ga=null)):h=null,h===null)throw Ym(y);return y.lanes=536870912,null}return k_(y,j)}var Z=h.memoizedState;if(Z!==null){var ke=Z.dehydrated;if(dj(y),V)if(y.flags&256)y.flags&=-257,y=hB(h,y,_);else if(y.memoizedState!==null)y.child=h.child,y.flags|=128,y=null;else throw Error(r(558));else if(ci||cb(h,y,_,!1),V=(_&h.childLanes)!==0,ci||V){if(j=oa,j!==null&&(ke=qt(j,_),ke!==0&&ke!==Z.retryLane))throw Z.retryLane=ke,tf(h,ke),Po(j,h,ke),Mj;R_(),y=hB(h,y,_)}else h=Z.treeContext,ga=Ml(ke.nextSibling),cs=y,wr=!0,Xm=null,Al=!1,h!==null&&Z5(y,h),y=k_(y,j),y.flags|=4096;return y}return h=hu(h.child,{mode:j.mode,children:j.children}),h.ref=y.ref,y.child=h,h.return=y,h}function N_(h,y){var _=y.ref;if(_===null)h!==null&&h.ref!==null&&(y.flags|=4194816);else{if(typeof _!="function"&&typeof _!="object")throw Error(r(284));(h===null||h.ref!==_)&&(y.flags|=4194816)}}function Ej(h,y,_,j,V){return sf(y),_=mj(h,y,_,j,void 0,V),j=hj(),h!==null&&!ci?(pj(h,y,V),yu(h,y,V)):(wr&&j&&WA(y),y.flags|=1,us(h,y,_,V),y.child)}function pB(h,y,_,j,V,Z){return sf(y),y.updateQueue=null,_=bU(y,j,_,V),gU(h),j=hj(),h!==null&&!ci?(pj(h,y,Z),yu(h,y,Z)):(wr&&j&&WA(y),y.flags|=1,us(h,y,_,Z),y.child)}function fB(h,y,_,j,V){if(sf(y),y.stateNode===null){var Z=ib,ke=_.contextType;typeof ke=="object"&&ke!==null&&(Z=ds(ke)),Z=new _(j,Z),y.memoizedState=Z.state!==null&&Z.state!==void 0?Z.state:null,Z.updater=Aj,y.stateNode=Z,Z._reactInternals=y,Z=y.stateNode,Z.props=j,Z.state=y.memoizedState,Z.refs={},aj(y),ke=_.contextType,Z.context=typeof ke=="object"&&ke!==null?ds(ke):ib,Z.state=y.memoizedState,ke=_.getDerivedStateFromProps,typeof ke=="function"&&(Tj(y,_,ke,j),Z.state=y.memoizedState),typeof _.getDerivedStateFromProps=="function"||typeof Z.getSnapshotBeforeUpdate=="function"||typeof Z.UNSAFE_componentWillMount!="function"&&typeof Z.componentWillMount!="function"||(ke=Z.state,typeof Z.componentWillMount=="function"&&Z.componentWillMount(),typeof Z.UNSAFE_componentWillMount=="function"&&Z.UNSAFE_componentWillMount(),ke!==Z.state&&Aj.enqueueReplaceState(Z,Z.state,null),dv(y,j,Z,V),cv(),Z.state=y.memoizedState),typeof Z.componentDidMount=="function"&&(y.flags|=4194308),j=!0}else if(h===null){Z=y.stateNode;var qe=y.memoizedProps,ct=mf(_,qe);Z.props=ct;var wt=Z.context,Lt=_.contextType;ke=ib,typeof Lt=="object"&&Lt!==null&&(ke=ds(Lt));var Bt=_.getDerivedStateFromProps;Lt=typeof Bt=="function"||typeof Z.getSnapshotBeforeUpdate=="function",qe=y.pendingProps!==qe,Lt||typeof Z.UNSAFE_componentWillReceiveProps!="function"&&typeof Z.componentWillReceiveProps!="function"||(qe||wt!==ke)&&tB(y,Z,j,ke),Jm=!1;var Ct=y.memoizedState;Z.state=Ct,dv(y,j,Z,V),cv(),wt=y.memoizedState,qe||Ct!==wt||Jm?(typeof Bt=="function"&&(Tj(y,_,Bt,j),wt=y.memoizedState),(ct=Jm||eB(y,_,ct,j,Ct,wt,ke))?(Lt||typeof Z.UNSAFE_componentWillMount!="function"&&typeof Z.componentWillMount!="function"||(typeof Z.componentWillMount=="function"&&Z.componentWillMount(),typeof Z.UNSAFE_componentWillMount=="function"&&Z.UNSAFE_componentWillMount()),typeof Z.componentDidMount=="function"&&(y.flags|=4194308)):(typeof Z.componentDidMount=="function"&&(y.flags|=4194308),y.memoizedProps=j,y.memoizedState=wt),Z.props=j,Z.state=wt,Z.context=ke,j=ct):(typeof Z.componentDidMount=="function"&&(y.flags|=4194308),j=!1)}else{Z=y.stateNode,ij(h,y),ke=y.memoizedProps,Lt=mf(_,ke),Z.props=Lt,Bt=y.pendingProps,Ct=Z.context,wt=_.contextType,ct=ib,typeof wt=="object"&&wt!==null&&(ct=ds(wt)),qe=_.getDerivedStateFromProps,(wt=typeof qe=="function"||typeof Z.getSnapshotBeforeUpdate=="function")||typeof Z.UNSAFE_componentWillReceiveProps!="function"&&typeof Z.componentWillReceiveProps!="function"||(ke!==Bt||Ct!==ct)&&tB(y,Z,j,ct),Jm=!1,Ct=y.memoizedState,Z.state=Ct,dv(y,j,Z,V),cv();var Dt=y.memoizedState;ke!==Bt||Ct!==Dt||Jm||h!==null&&h.dependencies!==null&&s_(h.dependencies)?(typeof qe=="function"&&(Tj(y,_,qe,j),Dt=y.memoizedState),(Lt=Jm||eB(y,_,Lt,j,Ct,Dt,ct)||h!==null&&h.dependencies!==null&&s_(h.dependencies))?(wt||typeof Z.UNSAFE_componentWillUpdate!="function"&&typeof Z.componentWillUpdate!="function"||(typeof Z.componentWillUpdate=="function"&&Z.componentWillUpdate(j,Dt,ct),typeof Z.UNSAFE_componentWillUpdate=="function"&&Z.UNSAFE_componentWillUpdate(j,Dt,ct)),typeof Z.componentDidUpdate=="function"&&(y.flags|=4),typeof Z.getSnapshotBeforeUpdate=="function"&&(y.flags|=1024)):(typeof Z.componentDidUpdate!="function"||ke===h.memoizedProps&&Ct===h.memoizedState||(y.flags|=4),typeof Z.getSnapshotBeforeUpdate!="function"||ke===h.memoizedProps&&Ct===h.memoizedState||(y.flags|=1024),y.memoizedProps=j,y.memoizedState=Dt),Z.props=j,Z.state=Dt,Z.context=ct,j=Lt):(typeof Z.componentDidUpdate!="function"||ke===h.memoizedProps&&Ct===h.memoizedState||(y.flags|=4),typeof Z.getSnapshotBeforeUpdate!="function"||ke===h.memoizedProps&&Ct===h.memoizedState||(y.flags|=1024),j=!1)}return Z=j,N_(h,y),j=(y.flags&128)!==0,Z||j?(Z=y.stateNode,_=j&&typeof _.getDerivedStateFromError!="function"?null:Z.render(),y.flags|=1,h!==null&&j?(y.child=df(y,h.child,null,V),y.child=df(y,null,_,V)):us(h,y,_,V),y.memoizedState=Z.state,h=y.child):h=yu(h,y,V),h}function gB(h,y,_,j){return rf(),y.flags|=256,us(h,y,_,j),y.child}var Dj={dehydrated:null,treeContext:null,retryLane:0,hydrationErrors:null};function Fj(h){return{baseLanes:h,cachePool:iU()}}function Rj(h,y,_){return h=h!==null?h.childLanes&~_:0,y&&(h|=Qo),h}function bB(h,y,_){var j=y.pendingProps,V=!1,Z=(y.flags&128)!==0,ke;if((ke=Z)||(ke=h!==null&&h.memoizedState===null?!1:(ti.current&2)!==0),ke&&(V=!0,y.flags&=-129),ke=(y.flags&32)!==0,y.flags&=-33,h===null){if(wr){if(V?th(y):nh(),(h=ga)?(h=N8(h,Al),h=h!==null&&h.data!=="&"?h:null,h!==null&&(y.memoizedState={dehydrated:h,treeContext:Km!==null?{id:dd,overflow:ud}:null,retryLane:536870912,hydrationErrors:null},_=Y5(h),_.return=y,y.child=_,cs=y,ga=null)):h=null,h===null)throw Ym(y);return bM(h)?y.lanes=32:y.lanes=536870912,null}var qe=j.children;return j=j.fallback,V?(nh(),V=y.mode,qe=C_({mode:"hidden",children:qe},V),j=nf(j,V,_,null),qe.return=y,j.return=y,qe.sibling=j,y.child=qe,j=y.child,j.memoizedState=Fj(_),j.childLanes=Rj(h,ke,_),y.memoizedState=Dj,fv(null,j)):(th(y),Lj(y,qe))}var ct=h.memoizedState;if(ct!==null&&(qe=ct.dehydrated,qe!==null)){if(Z)y.flags&256?(th(y),y.flags&=-257,y=Ij(h,y,_)):y.memoizedState!==null?(nh(),y.child=h.child,y.flags|=128,y=null):(nh(),qe=j.fallback,V=y.mode,j=C_({mode:"visible",children:j.children},V),qe=nf(qe,V,_,null),qe.flags|=2,j.return=y,qe.return=y,j.sibling=qe,y.child=j,df(y,h.child,null,_),j=y.child,j.memoizedState=Fj(_),j.childLanes=Rj(h,ke,_),y.memoizedState=Dj,y=fv(null,j));else if(th(y),bM(qe)){if(ke=qe.nextSibling&&qe.nextSibling.dataset,ke)var wt=ke.dgst;ke=wt,j=Error(r(419)),j.stack="",j.digest=ke,rv({value:j,source:null,stack:null}),y=Ij(h,y,_)}else if(ci||cb(h,y,_,!1),ke=(_&h.childLanes)!==0,ci||ke){if(ke=oa,ke!==null&&(j=qt(ke,_),j!==0&&j!==ct.retryLane))throw ct.retryLane=j,tf(h,j),Po(ke,h,j),Mj;gM(qe)||R_(),y=Ij(h,y,_)}else gM(qe)?(y.flags|=192,y.child=h.child,y=null):(h=ct.treeContext,ga=Ml(qe.nextSibling),cs=y,wr=!0,Xm=null,Al=!1,h!==null&&Z5(y,h),y=Lj(y,j.children),y.flags|=4096);return y}return V?(nh(),qe=j.fallback,V=y.mode,ct=h.child,wt=ct.sibling,j=hu(ct,{mode:"hidden",children:j.children}),j.subtreeFlags=ct.subtreeFlags&65011712,wt!==null?qe=hu(wt,qe):(qe=nf(qe,V,_,null),qe.flags|=2),qe.return=y,j.return=y,j.sibling=qe,y.child=j,fv(null,j),j=y.child,qe=h.child.memoizedState,qe===null?qe=Fj(_):(V=qe.cachePool,V!==null?(ct=oi._currentValue,V=V.parent!==ct?{parent:ct,pool:ct}:V):V=iU(),qe={baseLanes:qe.baseLanes|_,cachePool:V}),j.memoizedState=qe,j.childLanes=Rj(h,ke,_),y.memoizedState=Dj,fv(h.child,j)):(th(y),_=h.child,h=_.sibling,_=hu(_,{mode:"visible",children:j.children}),_.return=y,_.sibling=null,h!==null&&(ke=y.deletions,ke===null?(y.deletions=[h],y.flags|=16):ke.push(h)),y.child=_,y.memoizedState=null,_)}function Lj(h,y){return y=C_({mode:"visible",children:y},h.mode),y.return=h,h.child=y}function C_(h,y){return h=Wo(22,h,null,y),h.lanes=0,h}function Ij(h,y,_){return df(y,h.child,null,_),h=Lj(y,y.pendingProps.children),h.flags|=2,y.memoizedState=null,h}function xB(h,y,_){h.lanes|=y;var j=h.alternate;j!==null&&(j.lanes|=y),JA(h.return,y,_)}function Oj(h,y,_,j,V,Z){var ke=h.memoizedState;ke===null?h.memoizedState={isBackwards:y,rendering:null,renderingStartTime:0,last:j,tail:_,tailMode:V,treeForkCount:Z}:(ke.isBackwards=y,ke.rendering=null,ke.renderingStartTime=0,ke.last=j,ke.tail=_,ke.tailMode=V,ke.treeForkCount=Z)}function yB(h,y,_){var j=y.pendingProps,V=j.revealOrder,Z=j.tail;j=j.children;var ke=ti.current,qe=(ke&2)!==0;if(qe?(ke=ke&1|2,y.flags|=128):ke&=1,pe(ti,ke),us(h,y,j,_),j=wr?nv:0,!qe&&h!==null&&(h.flags&128)!==0)e:for(h=y.child;h!==null;){if(h.tag===13)h.memoizedState!==null&&xB(h,_,y);else if(h.tag===19)xB(h,_,y);else if(h.child!==null){h.child.return=h,h=h.child;continue}if(h===y)break e;for(;h.sibling===null;){if(h.return===null||h.return===y)break e;h=h.return}h.sibling.return=h.return,h=h.sibling}switch(V){case"forwards":for(_=y.child,V=null;_!==null;)h=_.alternate,h!==null&&p_(h)===null&&(V=_),_=_.sibling;_=V,_===null?(V=y.child,y.child=null):(V=_.sibling,_.sibling=null),Oj(y,!1,V,_,Z,j);break;case"backwards":case"unstable_legacy-backwards":for(_=null,V=y.child,y.child=null;V!==null;){if(h=V.alternate,h!==null&&p_(h)===null){y.child=V;break}h=V.sibling,V.sibling=_,_=V,V=h}Oj(y,!0,_,null,Z,j);break;case"together":Oj(y,!1,null,null,void 0,j);break;default:y.memoizedState=null}return y.child}function yu(h,y,_){if(h!==null&&(y.dependencies=h.dependencies),ih|=y.lanes,(_&y.childLanes)===0)if(h!==null){if(cb(h,y,_,!1),(_&y.childLanes)===0)return null}else return null;if(h!==null&&y.child!==h.child)throw Error(r(153));if(y.child!==null){for(h=y.child,_=hu(h,h.pendingProps),y.child=_,_.return=y;h.sibling!==null;)h=h.sibling,_=_.sibling=hu(h,h.pendingProps),_.return=y;_.sibling=null}return y.child}function zj(h,y){return(h.lanes&y)!==0?!0:(h=h.dependencies,!!(h!==null&&s_(h)))}function lde(h,y,_){switch(y.tag){case 3:F(y,y.stateNode.containerInfo),Qm(y,oi,h.memoizedState.cache),rf();break;case 27:case 5:U(y);break;case 4:F(y,y.stateNode.containerInfo);break;case 10:Qm(y,y.type,y.memoizedProps.value);break;case 31:if(y.memoizedState!==null)return y.flags|=128,dj(y),null;break;case 13:var j=y.memoizedState;if(j!==null)return j.dehydrated!==null?(th(y),y.flags|=128,null):(_&y.child.childLanes)!==0?bB(h,y,_):(th(y),h=yu(h,y,_),h!==null?h.sibling:null);th(y);break;case 19:var V=(h.flags&128)!==0;if(j=(_&y.childLanes)!==0,j||(cb(h,y,_,!1),j=(_&y.childLanes)!==0),V){if(j)return yB(h,y,_);y.flags|=128}if(V=y.memoizedState,V!==null&&(V.rendering=null,V.tail=null,V.lastEffect=null),pe(ti,ti.current),j)break;return null;case 22:return y.lanes=0,uB(h,y,_,y.pendingProps);case 24:Qm(y,oi,h.memoizedState.cache)}return yu(h,y,_)}function vB(h,y,_){if(h!==null)if(h.memoizedProps!==y.pendingProps)ci=!0;else{if(!zj(h,_)&&(y.flags&128)===0)return ci=!1,lde(h,y,_);ci=(h.flags&131072)!==0}else ci=!1,wr&&(y.flags&1048576)!==0&&J5(y,nv,y.index);switch(y.lanes=0,y.tag){case 16:e:{var j=y.pendingProps;if(h=lf(y.elementType),y.type=h,typeof h=="function")$A(h)?(j=mf(h,j),y.tag=1,y=fB(null,y,h,j,_)):(y.tag=0,y=Ej(null,y,h,j,_));else{if(h!=null){var V=h.$$typeof;if(V===P){y.tag=11,y=lB(null,y,h,j,_);break e}else if(V===T){y.tag=14,y=cB(null,y,h,j,_);break e}}throw y=L(h)||h,Error(r(306,y,""))}}return y;case 0:return Ej(h,y,y.type,y.pendingProps,_);case 1:return j=y.type,V=mf(j,y.pendingProps),fB(h,y,j,V,_);case 3:e:{if(F(y,y.stateNode.containerInfo),h===null)throw Error(r(387));j=y.pendingProps;var Z=y.memoizedState;V=Z.element,ij(h,y),dv(y,j,null,_);var ke=y.memoizedState;if(j=ke.cache,Qm(y,oi,j),j!==Z.cache&&ZA(y,[oi],_,!0),cv(),j=ke.element,Z.isDehydrated)if(Z={element:j,isDehydrated:!1,cache:ke.cache},y.updateQueue.baseState=Z,y.memoizedState=Z,y.flags&256){y=gB(h,y,j,_);break e}else if(j!==V){V=Cl(Error(r(424)),y),rv(V),y=gB(h,y,j,_);break e}else for(h=y.stateNode.containerInfo,h.nodeType===9?h=h.body:h=h.nodeName==="HTML"?h.ownerDocument.body:h,ga=Ml(h.firstChild),cs=y,wr=!0,Xm=null,Al=!0,_=uU(y,null,j,_),y.child=_;_;)_.flags=_.flags&-3|4096,_=_.sibling;else{if(rf(),j===V){y=yu(h,y,_);break e}us(h,y,j,_)}y=y.child}return y;case 26:return N_(h,y),h===null?(_=M8(y.type,null,y.pendingProps,null))?y.memoizedState=_:wr||(_=y.type,h=y.pendingProps,j=H_(B.current).createElement(_),j[At]=y,j[Kt]=h,ms(j,_,h),at(j),y.stateNode=j):y.memoizedState=M8(y.type,h.memoizedProps,y.pendingProps,h.memoizedState),null;case 27:return U(y),h===null&&wr&&(j=y.stateNode=T8(y.type,y.pendingProps,B.current),cs=y,Al=!0,V=ga,dh(y.type)?(xM=V,ga=Ml(j.firstChild)):ga=V),us(h,y,y.pendingProps.children,_),N_(h,y),h===null&&(y.flags|=4194304),y.child;case 5:return h===null&&wr&&((V=j=ga)&&(j=Ode(j,y.type,y.pendingProps,Al),j!==null?(y.stateNode=j,cs=y,ga=Ml(j.firstChild),Al=!1,V=!0):V=!1),V||Ym(y)),U(y),V=y.type,Z=y.pendingProps,ke=h!==null?h.memoizedProps:null,j=Z.children,hM(V,Z)?j=null:ke!==null&&hM(V,ke)&&(y.flags|=32),y.memoizedState!==null&&(V=mj(h,y,Zce,null,null,_),jv._currentValue=V),N_(h,y),us(h,y,j,_),y.child;case 6:return h===null&&wr&&((h=_=ga)&&(_=zde(_,y.pendingProps,Al),_!==null?(y.stateNode=_,cs=y,ga=null,h=!0):h=!1),h||Ym(y)),null;case 13:return bB(h,y,_);case 4:return F(y,y.stateNode.containerInfo),j=y.pendingProps,h===null?y.child=df(y,null,j,_):us(h,y,j,_),y.child;case 11:return lB(h,y,y.type,y.pendingProps,_);case 7:return us(h,y,y.pendingProps,_),y.child;case 8:return us(h,y,y.pendingProps.children,_),y.child;case 12:return us(h,y,y.pendingProps.children,_),y.child;case 10:return j=y.pendingProps,Qm(y,y.type,j.value),us(h,y,j.children,_),y.child;case 9:return V=y.type._context,j=y.pendingProps.children,sf(y),V=ds(V),j=j(V),y.flags|=1,us(h,y,j,_),y.child;case 14:return cB(h,y,y.type,y.pendingProps,_);case 15:return dB(h,y,y.type,y.pendingProps,_);case 19:return yB(h,y,_);case 31:return ode(h,y,_);case 22:return uB(h,y,_,y.pendingProps);case 24:return sf(y),j=ds(oi),h===null?(V=nj(),V===null&&(V=oa,Z=ej(),V.pooledCache=Z,Z.refCount++,Z!==null&&(V.pooledCacheLanes|=_),V=Z),y.memoizedState={parent:j,cache:V},aj(y),Qm(y,oi,V)):((h.lanes&_)!==0&&(ij(h,y),dv(y,null,null,_),cv()),V=h.memoizedState,Z=y.memoizedState,V.parent!==j?(V={parent:j,cache:j},y.memoizedState=V,y.lanes===0&&(y.memoizedState=y.updateQueue.baseState=V),Qm(y,oi,j)):(j=Z.cache,Qm(y,oi,j),j!==V.cache&&ZA(y,[oi],_,!0))),us(h,y,y.pendingProps.children,_),y.child;case 29:throw y.pendingProps}throw Error(r(156,y.tag))}function vu(h){h.flags|=4}function Uj(h,y,_,j,V){if((y=(h.mode&32)!==0)&&(y=!1),y){if(h.flags|=16777216,(V&335544128)===V)if(h.stateNode.complete)h.flags|=8192;else if(WB())h.flags|=8192;else throw cf=d_,rj}else h.flags&=-16777217}function wB(h,y){if(y.type!=="stylesheet"||(y.state.loading&4)!==0)h.flags&=-16777217;else if(h.flags|=16777216,!L8(y))if(WB())h.flags|=8192;else throw cf=d_,rj}function P_(h,y){y!==null&&(h.flags|=4),h.flags&16384&&(y=h.tag!==22?st():536870912,h.lanes|=y,wb|=y)}function gv(h,y){if(!wr)switch(h.tailMode){case"hidden":y=h.tail;for(var _=null;y!==null;)y.alternate!==null&&(_=y),y=y.sibling;_===null?h.tail=null:_.sibling=null;break;case"collapsed":_=h.tail;for(var j=null;_!==null;)_.alternate!==null&&(j=_),_=_.sibling;j===null?y||h.tail===null?h.tail=null:h.tail.sibling=null:j.sibling=null}}function ba(h){var y=h.alternate!==null&&h.alternate.child===h.child,_=0,j=0;if(y)for(var V=h.child;V!==null;)_|=V.lanes|V.childLanes,j|=V.subtreeFlags&65011712,j|=V.flags&65011712,V.return=h,V=V.sibling;else for(V=h.child;V!==null;)_|=V.lanes|V.childLanes,j|=V.subtreeFlags,j|=V.flags,V.return=h,V=V.sibling;return h.subtreeFlags|=j,h.childLanes=_,y}function cde(h,y,_){var j=y.pendingProps;switch(KA(y),y.tag){case 16:case 15:case 0:case 11:case 7:case 8:case 12:case 9:case 14:return ba(y),null;case 1:return ba(y),null;case 3:return _=y.stateNode,j=null,h!==null&&(j=h.memoizedState.cache),y.memoizedState.cache!==j&&(y.flags|=2048),gu(oi),D(),_.pendingContext&&(_.context=_.pendingContext,_.pendingContext=null),(h===null||h.child===null)&&(lb(y)?vu(y):h===null||h.memoizedState.isDehydrated&&(y.flags&256)===0||(y.flags|=1024,YA())),ba(y),null;case 26:var V=y.type,Z=y.memoizedState;return h===null?(vu(y),Z!==null?(ba(y),wB(y,Z)):(ba(y),Uj(y,V,null,j,_))):Z?Z!==h.memoizedState?(vu(y),ba(y),wB(y,Z)):(ba(y),y.flags&=-16777217):(h=h.memoizedProps,h!==j&&vu(y),ba(y),Uj(y,V,h,j,_)),null;case 27:if(Q(y),_=B.current,V=y.type,h!==null&&y.stateNode!=null)h.memoizedProps!==j&&vu(y);else{if(!j){if(y.stateNode===null)throw Error(r(166));return ba(y),null}h=xe.current,lb(y)?eU(y):(h=T8(V,j,_),y.stateNode=h,vu(y))}return ba(y),null;case 5:if(Q(y),V=y.type,h!==null&&y.stateNode!=null)h.memoizedProps!==j&&vu(y);else{if(!j){if(y.stateNode===null)throw Error(r(166));return ba(y),null}if(Z=xe.current,lb(y))eU(y);else{var ke=H_(B.current);switch(Z){case 1:Z=ke.createElementNS("http://www.w3.org/2000/svg",V);break;case 2:Z=ke.createElementNS("http://www.w3.org/1998/Math/MathML",V);break;default:switch(V){case"svg":Z=ke.createElementNS("http://www.w3.org/2000/svg",V);break;case"math":Z=ke.createElementNS("http://www.w3.org/1998/Math/MathML",V);break;case"script":Z=ke.createElement("div"),Z.innerHTML=" - + +