From 589159b3137d831bd8d96e889c642ca5c93d3980 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E6=88=91=E6=98=AF=E5=B0=8F=E4=B8=80=E7=81=B0?= Date: Tue, 31 Mar 2026 23:39:18 +0800 Subject: [PATCH 1/3] =?UTF-8?q?feat:=20AVI=20=E5=BD=95=E5=88=B6=E4=B8=8E?= =?UTF-8?q?=E5=AE=A2=E6=88=B7=E7=AB=AF=E8=BD=AC=E7=A0=81=E6=B5=81=E7=A8=8B?= =?UTF-8?q?=20/=20AVI=20recording=20with=20client-side=20transcoding?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 调试控制台录制改为低帧率 AVI,减轻开发板写盘与编码负载 / Record low-FPS AVI in debug console to reduce board load - 分析服务新增 replace_video API,支持客户端转码后替换并删除原始 AVI、同步侧车信息 / Add replace_video API to swap transcoded MP4, delete original AVI, and update sidecar - 分析控制台实现 AVI->MP4 客户端转码、导入流程可视化及进度提示 / Implement client-side AVI->MP4 transcoding with visual import flow and progress - 优化素材与通知中文件名显示,避免过长溢出并保留后缀可见性 / Improve filename display in lists and toasts, preserving extensions without overflow Made-with: Cursor --- ogscope/config.py | 24 + ogscope/web/api/analysis/routes.py | 17 + ogscope/web/api/analysis/services.py | 533 +++++++++++++----- ogscope/web/api/debug/services.py | 288 +++++----- ogscope/web/api/models/schemas.py | 19 + tests/unit/test_analysis_api.py | 179 ++++++ web/analysis-ui/package-lock.json | 32 ++ web/analysis-ui/package.json | 2 + web/analysis-ui/src/App.tsx | 258 ++++++++- web/analysis-ui/src/api.ts | 66 ++- web/analysis-ui/src/utils/transcode.ts | 92 +++ .../analysis-lab/assets/index-B7lY1o6d.js | 125 ---- .../analysis-lab/assets/index-BTbVhXi4.js | 125 ++++ .../analysis-lab/assets/index-CiLgcict.css | 1 + .../analysis-lab/assets/index-ClgTRc4R.css | 1 - .../analysis-lab/assets/worker-BAOIWoxA.js | 1 + web/static/analysis-lab/index.html | 4 +- web/static/css/debug.css | 3 + web/static/i18n/analysis.en.json | 28 +- web/static/i18n/analysis.zh.json | 28 +- web/static/i18n/debug.en.json | 1 + web/static/i18n/debug.zh.json | 1 + web/static/js/debug.js | 44 +- web/templates/debug.html | 3 + 24 files changed, 1440 insertions(+), 435 deletions(-) create mode 100644 web/analysis-ui/src/utils/transcode.ts delete mode 100644 web/static/analysis-lab/assets/index-B7lY1o6d.js create mode 100644 web/static/analysis-lab/assets/index-BTbVhXi4.js create mode 100644 web/static/analysis-lab/assets/index-CiLgcict.css delete mode 100644 web/static/analysis-lab/assets/index-ClgTRc4R.css create mode 100644 web/static/analysis-lab/assets/worker-BAOIWoxA.js diff --git a/ogscope/config.py b/ogscope/config.py index eac98eb..031da9f 100644 --- a/ogscope/config.py +++ b/ogscope/config.py @@ -141,6 +141,30 @@ class Settings(BaseSettings): default=2 / 3, description="星空分析目标帧率(约 1.5 秒 1 帧),仅用于前端节流 / Target star-analysis FPS for UI throttle (~1.5s per frame)", ) + star_analysis_min_interval_ms: int = Field( + default=2000, + ge=500, + le=30000, + description="实时解算最小间隔(毫秒)/ Minimum interval for realtime solving in ms", + ) + star_analysis_max_interval_ms: int = Field( + default=12000, + ge=1000, + le=60000, + description="实时解算最大间隔(毫秒)/ Maximum interval for realtime solving in ms", + ) + star_analysis_request_timeout_ms: int = Field( + default=4500, + ge=500, + le=120000, + description="实时解算请求外层硬超时(毫秒)/ Outer hard timeout for realtime solve request in ms", + ) + star_analysis_slow_threshold_ms: int = Field( + default=3000, + ge=200, + le=120000, + description="实时解算慢请求阈值(毫秒)/ Slow realtime solve threshold in ms", + ) model_config = SettingsConfigDict( env_file=".env", diff --git a/ogscope/web/api/analysis/routes.py b/ogscope/web/api/analysis/routes.py index 35383e8..b1557ba 100644 --- a/ogscope/web/api/analysis/routes.py +++ b/ogscope/web/api/analysis/routes.py @@ -10,6 +10,7 @@ from ogscope.web.api.analysis.services import analysis_service from ogscope.web.api.models.schemas import ( + AnalysisReplaceVideoRequest, AnalysisBatchSolveRequest, AnalysisExperimentCreate, AnalysisExtractPreviewRequest, @@ -128,6 +129,19 @@ async def import_upload_from_debug(body: ImportFromDebugRequest): raise HTTPException(status_code=400, detail=str(exc)) from exc +@router.post("/analysis/uploads/replace_video") +async def replace_transcoded_video(body: AnalysisReplaceVideoRequest): + """客户端转码后替换视频并清理原文件 / Replace uploaded video after client transcode.""" + try: + return analysis_service.replace_transcoded_video(body) + except FileNotFoundError as exc: + raise HTTPException(status_code=404, detail=str(exc)) from exc + except ValueError as exc: + raise HTTPException(status_code=400, detail=str(exc)) from exc + except Exception as exc: # noqa: BLE001 + raise HTTPException(status_code=400, detail=str(exc)) from exc + + @router.post("/analysis/jobs") async def create_analysis_job(payload: AnalysisJobCreateRequest): """创建任务 / Create analysis job""" @@ -287,8 +301,10 @@ async def solve_uploaded_frame( raise ValueError("payload 必须为 JSON 对象 / payload must be a JSON object") topn = obj.get("overlay_topn_count") enable_polar = obj.get("enable_polar_guide") + solve_interval_ms = obj.get("solve_interval_ms") obj.pop("overlay_topn_count", None) obj.pop("enable_polar_guide", None) + obj.pop("solve_interval_ms", None) # 前端调试元数据,不参与 Pydantic 模型 / Client metadata not in schema obj.pop("time_sec", None) obj.pop("frame_width", None) @@ -300,6 +316,7 @@ async def solve_uploaded_frame( solve_params=data, overlay_topn_count=topn, enable_polar_guide=enable_polar, + solve_interval_ms=solve_interval_ms, ) except HTTPException: raise diff --git a/ogscope/web/api/analysis/services.py b/ogscope/web/api/analysis/services.py index 3613cf2..0f687ce 100644 --- a/ogscope/web/api/analysis/services.py +++ b/ogscope/web/api/analysis/services.py @@ -33,6 +33,7 @@ AnalysisExperimentCreate, AnalysisExtractPreviewRequest, AnalysisPresetCreate, + AnalysisReplaceVideoRequest, AnalysisSolveImageRequest, AnalysisSolveVideoFrameRequest, CentroidParamsPayload, @@ -137,6 +138,15 @@ def to_dict(self) -> dict[str, Any]: } +@dataclass(slots=True) +class RealtimeSolveGateState: + """实时解算门禁状态 / Gate state for realtime solving.""" + + in_flight: bool = False + last_started_mono: float = 0.0 + last_finished_mono: float = 0.0 + + class AnalysisService: """分析服务 / Analysis service""" @@ -165,6 +175,66 @@ def __init__(self) -> None: self._lab = AnalysisLabStore(settings) self._overlay_topn_default = 3 self._polar_guide_default = True + self._realtime_gate_lock = asyncio.Lock() + self._realtime_gate_states: dict[str, RealtimeSolveGateState] = { + "camera": RealtimeSolveGateState(), + "file": RealtimeSolveGateState(), + } + + async def _try_enter_realtime_gate( + self, source: str, interval_ms: int + ) -> dict[str, Any] | None: + """尝试进入实时解算门禁;返回 skip 响应或 None / Try entering gate; return skip response or None.""" + now = time.monotonic() + interval = max(0.0, float(interval_ms) / 1000.0) + async with self._realtime_gate_lock: + state = self._realtime_gate_states.setdefault(source, RealtimeSolveGateState()) + if state.in_flight: + return { + "success": True, + "gate_status": "SKIPPED_BUSY", + "gate_reason": "previous request still running", + "next_allowed_in_ms": 0, + } + if state.last_finished_mono > 0: + elapsed = now - state.last_finished_mono + if elapsed < interval: + wait_ms = max(0, int((interval - elapsed) * 1000.0)) + return { + "success": True, + "gate_status": "SKIPPED_INTERVAL", + "gate_reason": "minimum interval not reached", + "next_allowed_in_ms": wait_ms, + } + state.in_flight = True + state.last_started_mono = now + return None + + async def _leave_realtime_gate(self, source: str) -> None: + """释放实时解算门禁 / Leave realtime solve gate.""" + now = time.monotonic() + async with self._realtime_gate_lock: + state = self._realtime_gate_states.setdefault(source, RealtimeSolveGateState()) + state.in_flight = False + state.last_finished_mono = now + + def is_realtime_source_busy(self, source: str) -> bool: + """判断实时解算源是否繁忙 / Check whether realtime source is busy.""" + state = self._realtime_gate_states.get(source) + return bool(state and state.in_flight) + + def _resolve_realtime_interval_ms(self, requested_ms: int | None) -> tuple[int, int]: + """解析实时解算间隔并按系统上下限裁剪 / Resolve realtime interval with server bounds.""" + if requested_ms is None: + return 0, 0 + settings = get_settings() + min_interval_ms = int(settings.star_analysis_min_interval_ms) + max_interval_ms = int(settings.star_analysis_max_interval_ms) + requested_interval_ms = int(requested_ms) + effective_interval_ms = min( + max(requested_interval_ms, min_interval_ms), max_interval_ms + ) + return requested_interval_ms, effective_interval_ms def _build_topn_labels( self, @@ -705,6 +775,80 @@ def import_from_debug_capture(self, filename: str) -> dict[str, Any]: "size": dst.stat().st_size, } + def replace_transcoded_video( + self, body: AnalysisReplaceVideoRequest + ) -> dict[str, Any]: + """转码后替换素材视频并同步侧车 / Replace original video with transcoded output.""" + old_name = Path(body.old_filename.strip()).name + new_name = Path(body.new_filename.strip()).name + if not old_name or not new_name: + raise ValueError("文件名无效 / Invalid filename") + if old_name == new_name: + raise ValueError("新旧文件名不能相同 / old_filename and new_filename must differ") + old_path = self.resolve_upload_path(old_name) + new_path = self.resolve_upload_path(new_name) + if not old_path.is_file(): + raise FileNotFoundError("原始文件不存在 / Original file not found") + if not new_path.is_file(): + raise FileNotFoundError("转码文件不存在 / Transcoded file not found") + if old_path.suffix.lower() != ".avi": + raise ValueError("仅支持替换 AVI 原始文件 / only AVI source can be replaced") + if new_path.suffix.lower() != ".mp4": + raise ValueError("新文件必须为 MP4 / new file must be MP4") + + old_sidecar = self.upload_root / f"{old_path.stem}.txt" + new_sidecar = self.upload_root / f"{new_path.stem}.txt" + sidecar_payload: dict[str, Any] = {} + if old_sidecar.is_file(): + try: + raw = json.loads(old_sidecar.read_text(encoding="utf-8")) + if isinstance(raw, dict): + sidecar_payload = raw + except (json.JSONDecodeError, OSError): + sidecar_payload = {} + sidecar_payload.setdefault("kind", "video") + sidecar_payload["media_file"] = new_name + sidecar_payload["size"] = int(new_path.stat().st_size) + extra = sidecar_payload.get("extra") + if not isinstance(extra, dict): + extra = {} + extra["codec_fourcc"] = str(body.codec_fourcc or "mp4v") + extra["container"] = str(body.container or "MP4") + if body.duration_s is not None: + extra["duration_s"] = float(body.duration_s) + if body.nominal_fps is not None: + extra["nominal_fps"] = float(body.nominal_fps) + sidecar_payload["extra"] = extra + new_sidecar.write_text( + json.dumps(sidecar_payload, ensure_ascii=False, indent=2), encoding="utf-8" + ) + + old_path.unlink() + if old_sidecar.is_file() and old_sidecar != new_sidecar: + old_sidecar.unlink() + + manifest = self._lab.load_manifest() + entries = manifest.setdefault("entries", {}) + old_ent = entries.get(old_name, {}) + new_ent = entries.setdefault(new_name, {}) + if isinstance(old_ent, dict): + for key in ("source", "last_solve"): + if key in old_ent and key not in new_ent: + new_ent[key] = old_ent[key] + new_ent["source"] = new_ent.get("source", "analysis_upload") + new_ent["updated_at"] = datetime.now(timezone.utc).isoformat() + if old_name in entries: + del entries[old_name] + self._lab.save_manifest(manifest) + return { + "success": True, + "old_filename": old_name, + "filename": new_name, + "deleted_old": True, + "sidecar": new_sidecar.name, + "size": int(new_path.stat().st_size), + } + def list_presets(self, scope: str) -> dict[str, Any]: """列出官方或用户预设 / List official or user presets.""" if scope not in {"official", "user"}: @@ -766,68 +910,112 @@ async def solve_uploaded_frame( solve_params: AnalysisSolveImageRequest, overlay_topn_count: int | None = None, enable_polar_guide: bool | None = None, + solve_interval_ms: int | None = None, ) -> dict[str, Any]: """解析上传的单帧图像并解算 / Solve a single uploaded frame (multipart).""" - if not image_bytes: - raise ValueError("空图像数据 / Empty image payload") - buf = np.frombuffer(image_bytes, dtype=np.uint8) - frame = cv2.imdecode(buf, cv2.IMREAD_COLOR) - if frame is None: - raise ValueError("无法解码图像 / Cannot decode image") - - centroid_params, max_stars, timeout_ms, effective_profile = ( - self._resolve_solve_profile( - solve_params.solve_profile, - solve_params.centroid, - solve_params.solve_timeout_ms, - ) - ) - loop = asyncio.get_running_loop() - - def _run() -> dict[str, Any]: - return self._solve_bgr_to_row( - frame, - solve_params.hint_ra_deg, - solve_params.hint_dec_deg, - solve_params.fov_estimate, - solve_params.fov_max_error, - timeout_ms, - centroid_params, - solve_params.max_image_side, - max_stars, - bool(solve_params.large_scale_bg_subtract), - ) - - row = await loop.run_in_executor(self._solver_executor, _run) - # 统一 overlay_ext 结构,便于前端复用渲染逻辑 - topn = ( - int(overlay_topn_count) - if overlay_topn_count is not None - else self._overlay_topn_default + settings = get_settings() + requested_interval_ms, effective_interval_ms = ( + self._resolve_realtime_interval_ms(solve_interval_ms) ) - enable_polar = ( - bool(enable_polar_guide) - if enable_polar_guide is not None - else self._polar_guide_default + gate_skip = await self._try_enter_realtime_gate( + "file_upload", effective_interval_ms ) - overlay_ext: dict[str, Any] = {} + if gate_skip is not None: + gate_skip["requested_interval_ms"] = requested_interval_ms + gate_skip["effective_interval_ms"] = effective_interval_ms + return gate_skip + t_total = time.perf_counter() try: - overlay_ext["labels_topn"] = self._build_topn_labels( - row, topn_count=topn + if not image_bytes: + raise ValueError("空图像数据 / Empty image payload") + buf = np.frombuffer(image_bytes, dtype=np.uint8) + frame = cv2.imdecode(buf, cv2.IMREAD_COLOR) + if frame is None: + raise ValueError("无法解码图像 / Cannot decode image") + centroid_params, max_stars, timeout_ms, effective_profile = ( + self._resolve_solve_profile( + solve_params.solve_profile, + solve_params.centroid, + solve_params.solve_timeout_ms, + ) ) - except Exception: - overlay_ext["labels_topn"] = [] - if enable_polar: + loop = asyncio.get_running_loop() + + def _run() -> dict[str, Any]: + return self._solve_bgr_to_row( + frame, + solve_params.hint_ra_deg, + solve_params.hint_dec_deg, + solve_params.fov_estimate, + solve_params.fov_max_error, + timeout_ms, + centroid_params, + solve_params.max_image_side, + max_stars, + bool(solve_params.large_scale_bg_subtract), + ) + + hard_timeout_sec = max( + 0.2, float(settings.star_analysis_request_timeout_ms) / 1000.0 + ) + row = await asyncio.wait_for( + loop.run_in_executor(self._solver_executor, _run), + timeout=hard_timeout_sec, + ) + # 统一 overlay_ext 结构,便于前端复用渲染逻辑 + topn = ( + int(overlay_topn_count) + if overlay_topn_count is not None + else self._overlay_topn_default + ) + enable_polar = ( + bool(enable_polar_guide) + if enable_polar_guide is not None + else self._polar_guide_default + ) + overlay_ext: dict[str, Any] = {} try: - overlay_ext["polar_guide"] = self._build_polar_guide(row) + overlay_ext["labels_topn"] = self._build_topn_labels( + row, topn_count=topn + ) except Exception: - overlay_ext["polar_guide"] = None - row["overlay_ext"] = overlay_ext - row["solve_profile"] = effective_profile - detail_level = getattr(solve_params, "detail_level", None) or "summary" - if detail_level != "full": - row.pop("tetra", None) - return {"success": True, "result": row} + overlay_ext["labels_topn"] = [] + if enable_polar: + try: + overlay_ext["polar_guide"] = self._build_polar_guide(row) + except Exception: + overlay_ext["polar_guide"] = None + row["overlay_ext"] = overlay_ext + row["solve_profile"] = effective_profile + row["t_backend_total_ms"] = round((time.perf_counter() - t_total) * 1000.0, 3) + detail_level = getattr(solve_params, "detail_level", None) or "summary" + if detail_level != "full": + row.pop("tetra", None) + return { + "success": True, + "result": row, + "gate_status": "SOLVED", + "requested_interval_ms": requested_interval_ms, + "effective_interval_ms": effective_interval_ms, + } + except asyncio.TimeoutError: + return { + "success": True, + "result": { + "status": "TIMEOUT_RELEASED", + "status_code": None, + "solve_source": "full", + "ra_deg": 0.0, + "dec_deg": 0.0, + "detected_stars": 0, + }, + "gate_status": "TIMEOUT_RELEASED", + "gate_reason": "outer request timeout", + "requested_interval_ms": requested_interval_ms, + "effective_interval_ms": effective_interval_ms, + } + finally: + await self._leave_realtime_gate("file_upload") def delete_experiment(self, experiment_id: str) -> None: """删除一条实验记录 / Delete one experiment record.""" @@ -1029,98 +1217,171 @@ async def solve_video_frame( self, body: AnalysisSolveVideoFrameRequest ) -> dict[str, Any]: """相机或视频文件单帧解算 / Single-frame solve from camera or video file.""" + if body.source == "camera": + try: + from ogscope.web.api.debug.services import is_recording_active + + if is_recording_active(): + return { + "success": True, + "input_name": body.input_name or "", + "gate_status": "SKIPPED_BUSY", + "gate_reason": "recording is active", + "next_allowed_in_ms": 0, + } + except Exception: + pass + settings = get_settings() + requested_interval_ms, effective_interval_ms = ( + self._resolve_realtime_interval_ms(body.solve_interval_ms) + ) + gate_source_key = ( + body.source + if body.source == "camera" + else f"file:{(body.input_name or '').strip()}" + ) + gate_skip = await self._try_enter_realtime_gate( + gate_source_key, effective_interval_ms + ) + if gate_skip is not None: + gate_skip["requested_interval_ms"] = requested_interval_ms + gate_skip["effective_interval_ms"] = effective_interval_ms + gate_skip["input_name"] = body.input_name or "" + return gate_skip + t_total = time.perf_counter() t_open_decode_ms = None frame = None frame_id = None frame_ts = None - if body.source == "camera": - from ogscope.web.camera_shared import get_camera_manager + elapsed_ms = 0.0 + try: + if body.source == "camera": + from ogscope.web.camera_shared import get_camera_manager - t_decode = time.perf_counter() - frame, frame_id, frame_ts = await get_camera_manager().get_raw_frame() - t_open_decode_ms = (time.perf_counter() - t_decode) * 1000.0 - else: - if not body.input_name: - raise ValueError( - "需要 input_name / input_name required for file source" + t_decode = time.perf_counter() + frame, frame_id, frame_ts = await get_camera_manager().get_raw_frame() + t_open_decode_ms = (time.perf_counter() - t_decode) * 1000.0 + else: + if not body.input_name: + raise ValueError( + "需要 input_name / input_name required for file source" + ) + path = self._resolve_frame_source_path(body.input_name) + t_decode = time.perf_counter() + cap = cv2.VideoCapture(str(path)) + if not cap.isOpened(): + raise ValueError("无法打开视频 / Cannot open video") + try: + if body.time_sec is not None: + cap.set(cv2.CAP_PROP_POS_MSEC, float(body.time_sec) * 1000.0) + else: + cap.set(cv2.CAP_PROP_POS_FRAMES, float(body.frame_index)) + ok, frame = cap.read() + if not ok or frame is None: + raise ValueError("无法读取视频帧 / Cannot read video frame") + finally: + cap.release() + t_open_decode_ms = (time.perf_counter() - t_decode) * 1000.0 + centroid_params, max_stars, timeout_ms, effective_profile = ( + self._resolve_solve_profile( + body.solve_profile, body.centroid, body.solve_timeout_ms ) - path = self._resolve_frame_source_path(body.input_name) - t_decode = time.perf_counter() - cap = cv2.VideoCapture(str(path)) - if not cap.isOpened(): - raise ValueError("无法打开视频 / Cannot open video") - try: - if body.time_sec is not None: - cap.set(cv2.CAP_PROP_POS_MSEC, float(body.time_sec) * 1000.0) - else: - cap.set(cv2.CAP_PROP_POS_FRAMES, float(body.frame_index)) - ok, frame = cap.read() - if not ok or frame is None: - raise ValueError("无法读取视频帧 / Cannot read video frame") - finally: - cap.release() - t_open_decode_ms = (time.perf_counter() - t_decode) * 1000.0 - centroid_params, max_stars, timeout_ms, effective_profile = ( - self._resolve_solve_profile( - body.solve_profile, body.centroid, body.solve_timeout_ms ) - ) - loop = asyncio.get_running_loop() + loop = asyncio.get_running_loop() - def _run() -> dict[str, Any]: - return self._solve_bgr_to_row( - frame, - body.hint_ra_deg, - body.hint_dec_deg, - body.fov_estimate, - body.fov_max_error, - timeout_ms, - centroid_params, - body.max_image_side, - max_stars, - bool(body.large_scale_bg_subtract), - ) + def _run() -> dict[str, Any]: + return self._solve_bgr_to_row( + frame, + body.hint_ra_deg, + body.hint_dec_deg, + body.fov_estimate, + body.fov_max_error, + timeout_ms, + centroid_params, + body.max_image_side, + max_stars, + bool(body.large_scale_bg_subtract), + ) - row = await loop.run_in_executor(self._solver_executor, _run) - # 二次分析与极轴引导(失败降级,不影响基础解算) - topn = ( - int(body.overlay_topn_count) - if getattr(body, "overlay_topn_count", None) is not None - else self._overlay_topn_default - ) - enable_polar = ( - bool(body.enable_polar_guide) - if getattr(body, "enable_polar_guide", None) is not None - else self._polar_guide_default - ) - overlay_ext: dict[str, Any] = {} - try: - overlay_ext["labels_topn"] = self._build_topn_labels(row, topn_count=topn) - except Exception: - overlay_ext["labels_topn"] = [] - if enable_polar: + hard_timeout_sec = max( + 0.2, float(settings.star_analysis_request_timeout_ms) / 1000.0 + ) + row = await asyncio.wait_for( + loop.run_in_executor(self._solver_executor, _run), + timeout=hard_timeout_sec, + ) + # 二次分析与极轴引导(失败降级,不影响基础解算) + topn = ( + int(body.overlay_topn_count) + if getattr(body, "overlay_topn_count", None) is not None + else self._overlay_topn_default + ) + enable_polar = ( + bool(body.enable_polar_guide) + if getattr(body, "enable_polar_guide", None) is not None + else self._polar_guide_default + ) + overlay_ext: dict[str, Any] = {} try: - overlay_ext["polar_guide"] = self._build_polar_guide(row) + overlay_ext["labels_topn"] = self._build_topn_labels( + row, topn_count=topn + ) except Exception: - overlay_ext["polar_guide"] = None - row["overlay_ext"] = overlay_ext - - if t_open_decode_ms is not None: - row["t_open_decode_ms"] = round(t_open_decode_ms, 3) - row["t_backend_total_ms"] = round((time.perf_counter() - t_total) * 1000.0, 3) - row["solve_profile"] = effective_profile - # 默认精简 raw,大字段仅在 detail_level==full 时返回 / Drop heavy raw unless client asks for full detail. - detail_level = getattr(body, "detail_level", None) or "summary" - if detail_level != "full": - row.pop("tetra", None) - return { - "success": True, - "input_name": body.input_name or "", - "result": row, - "frame_id": frame_id, - "frame_ts": frame_ts, - } + overlay_ext["labels_topn"] = [] + if enable_polar: + try: + overlay_ext["polar_guide"] = self._build_polar_guide(row) + except Exception: + overlay_ext["polar_guide"] = None + row["overlay_ext"] = overlay_ext + if t_open_decode_ms is not None: + row["t_open_decode_ms"] = round(t_open_decode_ms, 3) + elapsed_ms = (time.perf_counter() - t_total) * 1000.0 + row["t_backend_total_ms"] = round(elapsed_ms, 3) + row["solve_profile"] = effective_profile + # 默认精简 raw,大字段仅在 detail_level==full 时返回 / Drop heavy raw unless client asks for full detail. + detail_level = getattr(body, "detail_level", None) or "summary" + if detail_level != "full": + row.pop("tetra", None) + return { + "success": True, + "input_name": body.input_name or "", + "result": row, + "frame_id": frame_id, + "frame_ts": frame_ts, + "gate_status": "SOLVED", + "gate_reason": ( + "slow request" + if elapsed_ms >= float(settings.star_analysis_slow_threshold_ms) + else None + ), + "requested_interval_ms": requested_interval_ms, + "effective_interval_ms": effective_interval_ms, + "next_allowed_in_ms": effective_interval_ms, + } + except asyncio.TimeoutError: + return { + "success": True, + "input_name": body.input_name or "", + "result": { + "status": "TIMEOUT_RELEASED", + "status_code": None, + "solve_source": "full", + "ra_deg": 0.0, + "dec_deg": 0.0, + "detected_stars": 0, + }, + "frame_id": frame_id, + "frame_ts": frame_ts, + "gate_status": "TIMEOUT_RELEASED", + "gate_reason": "outer request timeout", + "requested_interval_ms": requested_interval_ms, + "effective_interval_ms": effective_interval_ms, + "next_allowed_in_ms": effective_interval_ms, + } + finally: + await self._leave_realtime_gate(gate_source_key) def lab_public_settings(self) -> dict[str, Any]: """分析台默认参数(供前端)/ Public defaults for analysis UI.""" @@ -1128,6 +1389,10 @@ def lab_public_settings(self) -> dict[str, Any]: return { "solver_timeout_ms": s.solver_timeout_ms, "star_analysis_target_fps": s.star_analysis_target_fps, + "star_analysis_min_interval_ms": s.star_analysis_min_interval_ms, + "star_analysis_max_interval_ms": s.star_analysis_max_interval_ms, + "star_analysis_request_timeout_ms": s.star_analysis_request_timeout_ms, + "star_analysis_slow_threshold_ms": s.star_analysis_slow_threshold_ms, "camera_width": s.camera_width, "camera_height": s.camera_height, "camera_fps": s.camera_fps, diff --git a/ogscope/web/api/debug/services.py b/ogscope/web/api/debug/services.py index 45da7c1..248af6f 100644 --- a/ogscope/web/api/debug/services.py +++ b/ogscope/web/api/debug/services.py @@ -22,13 +22,14 @@ camera_instance = None is_recording = False recording_task = None +recording_state_lock: Optional[asyncio.Lock] = None # 录制会话元数据(用于停止时写入侧车) / Recording session metadata (for sidecar on stop) recording_stem: Optional[str] = None recording_t0_mono: Optional[float] = None recording_fps_value: float = 15.0 recording_media_filename: Optional[str] = None -recording_codec_fourcc: str = "mp4v" -recording_container: str = "MP4" +recording_codec_fourcc: str = "MJPG" +recording_container: str = "AVI" # 预览帧缓存与抓取任务 / Preview frame buffering and grabbing tasks latest_preview_jpeg: Optional[bytes] = None @@ -52,6 +53,19 @@ _camera_ensure_lock = asyncio.Lock() +def _get_recording_state_lock() -> asyncio.Lock: + """懒加载录制状态锁 / Lazy-init lock for recording state.""" + global recording_state_lock + if recording_state_lock is None: + recording_state_lock = asyncio.Lock() + return recording_state_lock + + +def is_recording_active() -> bool: + """是否正在录制 / Whether recording is active.""" + return bool(is_recording) + + def i18n_payload( message_key: str, message: str, message_params: Optional[dict[str, Any]] = None ) -> dict[str, Any]: @@ -469,94 +483,91 @@ async def start_recording(): global is_recording, recording_task, recording_stem, recording_t0_mono, recording_fps_value global recording_media_filename, recording_codec_fourcc, recording_container - if is_recording: - raise Exception("已在录制中") - - camera = get_camera_instance() - if not camera or not camera.is_capturing: - raise Exception("相机未运行") + async with _get_recording_state_lock(): + if is_recording: + raise Exception("已在录制中") + try: + from ogscope.web.api.analysis.services import analysis_service - try: - import time + if analysis_service.is_realtime_source_busy("camera"): + raise Exception("画面解析进行中,无法开始录制") + except ImportError: + pass - import cv2 + camera = get_camera_instance() + if not camera or not camera.is_capturing: + raise Exception("相机未运行") - camera_info = camera.get_camera_info() - stem = generate_capture_stem("VID", camera_info) - video_path = DEBUG_CAPTURES_DIR / f"{stem}.mp4" - - # 优先使用 MP4 编码,按候选顺序探测可用编码器 / Prefer MP4 codecs and probe available codecs in order. - # 浏览器兼容优先级:H264/avc1 通常优于 mp4v - # Browser compatibility priority: H264/avc1 are generally more compatible than mp4v. - codec_candidates = [ - ("avc1", "MP4"), - ("H264", "MP4"), - ("mp4v", "MP4"), - ] - width = int(camera_info.get("output_width", camera_info.get("width", 1920))) - height = int( - camera_info.get("output_height", camera_info.get("height", 1080)) - ) - fps = float(camera_info.get("fps", 15)) - recording_fps_value = fps - - video_writer = None - chosen_codec = None - chosen_container = None - for codec_tag, container in codec_candidates: - fourcc = cv2.VideoWriter_fourcc(*codec_tag) - candidate_writer = cv2.VideoWriter( - str(video_path), fourcc, fps, (width, height) + try: + import cv2 + + camera_info = camera.get_camera_info() + stem = generate_capture_stem("VID", camera_info) + video_path = DEBUG_CAPTURES_DIR / f"{stem}.avi" + + # 优先使用 AVI 友好编码,按候选顺序探测可用编码器 / Prefer AVI-friendly codecs. + codec_candidates = [ + ("MJPG", "AVI"), + ("XVID", "AVI"), + ("DIVX", "AVI"), + ] + width = int( + camera_info.get("output_width", camera_info.get("width", 1920)) ) - if candidate_writer.isOpened(): - video_writer = candidate_writer - chosen_codec = codec_tag - chosen_container = container - break - candidate_writer.release() - - if video_writer is None or not video_writer.isOpened(): - raise Exception("视频写入器创建失败(MP4编码器不可用)") - if str(chosen_codec or "").lower() == "mp4v": - logging.getLogger(__name__).warning( - "录制回退到 mp4v,某些浏览器可能无法预览该 MP4 文件" + height = int( + camera_info.get("output_height", camera_info.get("height", 1080)) ) - - recording_stem = stem - recording_t0_mono = time.monotonic() - recording_media_filename = f"{stem}.mp4" - recording_codec_fourcc = str(chosen_codec or "mp4v") - recording_container = str(chosen_container or "MP4") - is_recording = True - - # 启动录制任务 / Start recording task - async def record_video(): - nonlocal video_writer - try: - while is_recording: - # 采集单帧放到线程,避免阻塞事件循环影响“停止录制”响应 / Offload frame capture to a thread to keep stop-recording responsive. - image = await asyncio.to_thread(camera.capture_image) - if image is not None: - # OpenCV 期望 BGR / OpenCV expects BGR - try: - import cv2 - - bgr = cv2.cvtColor(image, cv2.COLOR_RGB2BGR) - except Exception: - bgr = image - video_writer.write(bgr) - await asyncio.sleep(1 / max(fps, 1)) - finally: - video_writer.release() - - recording_task = asyncio.create_task(record_video()) - - return {"success": True, "filename": f"{stem}.mp4", "path": str(video_path)} - - except ImportError: - raise Exception("OpenCV未安装") - except Exception as e: - raise Exception(f"录制启动失败: {str(e)}") + # 将录制写盘帧率限制在 1-3 FPS,进一步降低开发板负载 / Clamp record-write FPS to 1-3. + source_fps = float(camera_info.get("fps", 15)) + fps = max(1.0, min(3.0, source_fps)) + recording_fps_value = fps + + video_writer = None + chosen_codec = None + chosen_container = None + for codec_tag, container in codec_candidates: + fourcc = cv2.VideoWriter_fourcc(*codec_tag) + candidate_writer = cv2.VideoWriter( + str(video_path), fourcc, fps, (width, height) + ) + if candidate_writer.isOpened(): + video_writer = candidate_writer + chosen_codec = codec_tag + chosen_container = container + break + candidate_writer.release() + + if video_writer is None or not video_writer.isOpened(): + raise Exception("视频写入器创建失败(AVI编码器不可用)") + + recording_stem = stem + recording_t0_mono = time.monotonic() + recording_media_filename = f"{stem}.avi" + recording_codec_fourcc = str(chosen_codec or "MJPG") + recording_container = str(chosen_container or "AVI") + is_recording = True + + async def record_video(): + nonlocal video_writer + try: + while is_recording: + image = await asyncio.to_thread(camera.capture_image) + if image is not None: + try: + bgr = cv2.cvtColor(image, cv2.COLOR_RGB2BGR) + except Exception: + bgr = image + video_writer.write(bgr) + await asyncio.sleep(1 / max(fps, 1)) + finally: + video_writer.release() + + recording_task = asyncio.create_task(record_video()) + return {"success": True, "filename": f"{stem}.avi", "path": str(video_path)} + except ImportError: + raise Exception("OpenCV未安装") + except Exception as e: + raise Exception(f"录制启动失败: {str(e)}") @staticmethod async def stop_recording(): @@ -564,67 +575,64 @@ async def stop_recording(): global is_recording, recording_task, recording_stem, recording_t0_mono, recording_fps_value global recording_media_filename, recording_codec_fourcc, recording_container - if not is_recording: - raise Exception("未在录制中") + async with _get_recording_state_lock(): + if not is_recording: + raise Exception("未在录制中") - import time + stem = recording_stem + t0 = recording_t0_mono + nominal_fps = recording_fps_value + media_filename = recording_media_filename + codec_fourcc = recording_codec_fourcc + container = recording_container - stem = recording_stem - t0 = recording_t0_mono - nominal_fps = recording_fps_value - media_filename = recording_media_filename - codec_fourcc = recording_codec_fourcc - container = recording_container + is_recording = False - is_recording = False - - if recording_task: - try: - # 最多等待短时间优雅结束,避免停止录制长时间卡住 / Wait briefly for graceful stop to avoid long stop-recording stalls. - await asyncio.wait_for(recording_task, timeout=2.0) - except asyncio.TimeoutError: - # 超时后主动取消任务,确保接口快速返回 / Cancel on timeout so API can return quickly. - recording_task.cancel() + if recording_task: try: - await asyncio.wait_for(recording_task, timeout=1.0) + # 最多等待短时间优雅结束,避免停止录制长时间卡住 / Wait briefly for graceful stop. + await asyncio.wait_for(recording_task, timeout=2.0) + except asyncio.TimeoutError: + recording_task.cancel() + try: + await asyncio.wait_for(recording_task, timeout=1.0) + except asyncio.CancelledError: + pass + except Exception as e: + logging.getLogger(__name__).warning( + "停止录制时等待录制任务取消异常: %s", e + ) except asyncio.CancelledError: pass - except Exception as e: - logging.getLogger(__name__).warning( - "停止录制时等待录制任务取消异常: %s", e - ) - except asyncio.CancelledError: - pass - recording_task = None - - # 写入录制参数侧车(与视频同名 .txt)/ Write recording sidecar (.txt) next to video file - if stem: - media_filename = media_filename or f"{stem}.mp4" - video_path = DEBUG_CAPTURES_DIR / media_filename - duration_s = 0.0 - if t0 is not None: - duration_s = max(0.0, time.monotonic() - t0) - file_size = int(video_path.stat().st_size) if video_path.exists() else 0 - camera = get_camera_instance() - camera_info = camera.get_camera_info() if camera else {} - save_capture_sidecar( - stem, - camera_info, - kind="video", - media_filename=media_filename, - file_size=file_size, - extra={ - "duration_s": round(duration_s, 3), - "nominal_fps": nominal_fps, - "codec_fourcc": codec_fourcc, - "container": container, - }, - ) - recording_stem = None - recording_t0_mono = None - recording_media_filename = None - recording_codec_fourcc = "mp4v" - recording_container = "MP4" + recording_task = None + + if stem: + media_filename = media_filename or f"{stem}.avi" + video_path = DEBUG_CAPTURES_DIR / media_filename + duration_s = 0.0 + if t0 is not None: + duration_s = max(0.0, time.monotonic() - t0) + file_size = int(video_path.stat().st_size) if video_path.exists() else 0 + camera = get_camera_instance() + camera_info = camera.get_camera_info() if camera else {} + save_capture_sidecar( + stem, + camera_info, + kind="video", + media_filename=media_filename, + file_size=file_size, + extra={ + "duration_s": round(duration_s, 3), + "nominal_fps": nominal_fps, + "codec_fourcc": codec_fourcc, + "container": container, + }, + ) + recording_stem = None + recording_t0_mono = None + recording_media_filename = None + recording_codec_fourcc = "MJPG" + recording_container = "AVI" return { "success": True, diff --git a/ogscope/web/api/models/schemas.py b/ogscope/web/api/models/schemas.py index a92f9f5..e19c646 100644 --- a/ogscope/web/api/models/schemas.py +++ b/ogscope/web/api/models/schemas.py @@ -247,6 +247,12 @@ class AnalysisSolveVideoFrameRequest(BaseModel): input_name: Optional[str] = None frame_index: int = 0 time_sec: Optional[float] = None + solve_interval_ms: Optional[int] = Field( + default=None, + ge=200, + le=60000, + description="期望解算间隔(毫秒);后端会按系统上下限裁剪 / Desired solve interval in ms (server-clamped)", + ) # 解算参数 / Solve parameters hint_ra_deg: Optional[float] = None hint_dec_deg: Optional[float] = None @@ -276,3 +282,16 @@ class ImportFromDebugRequest(BaseModel): model_config = ConfigDict(extra="forbid") filename: str + + +class AnalysisReplaceVideoRequest(BaseModel): + """转码后替换素材视频 / Replace original video after client transcode.""" + + model_config = ConfigDict(extra="forbid") + + old_filename: str + new_filename: str + duration_s: Optional[float] = None + nominal_fps: Optional[float] = None + codec_fourcc: Optional[str] = None + container: Optional[str] = None diff --git a/tests/unit/test_analysis_api.py b/tests/unit/test_analysis_api.py index f3a93be..741250e 100644 --- a/tests/unit/test_analysis_api.py +++ b/tests/unit/test_analysis_api.py @@ -3,6 +3,7 @@ """ import json +import time from pathlib import Path import cv2 @@ -374,3 +375,181 @@ def test_analysis_solve_frame_upload_endpoint( assert "status" in row ext = row.get("overlay_ext") or {} assert "labels_topn" in ext + + +@pytest.mark.unit +def test_analysis_realtime_gate_skips_by_interval( + client, temp_analysis_dir, mock_plate_solve, tmp_path: Path +): + """连续请求在最小间隔内会被跳过 / Consecutive calls are skipped by interval gate.""" + video_path = tmp_path / "gate_interval.mp4" + _build_test_video(video_path) + with video_path.open("rb") as f: + up = client.post( + "/api/analysis/upload", + files={"file": ("gate_interval.mp4", f, "video/mp4")}, + ) + assert up.status_code == 200 + + first = client.post( + "/api/analysis/solve/frame", + json={ + "source": "file", + "input_name": "gate_interval.mp4", + "time_sec": 0.1, + "solve_interval_ms": 4000, + }, + ) + assert first.status_code == 200 + assert first.json().get("gate_status") == "SOLVED" + + second = client.post( + "/api/analysis/solve/frame", + json={ + "source": "file", + "input_name": "gate_interval.mp4", + "time_sec": 0.1, + "solve_interval_ms": 4000, + }, + ) + assert second.status_code == 200 + data2 = second.json() + assert data2.get("gate_status") == "SKIPPED_INTERVAL" + assert isinstance(data2.get("next_allowed_in_ms"), int) + + +@pytest.mark.unit +def test_analysis_realtime_timeout_releases_gate( + client, temp_analysis_dir, mock_plate_solve, monkeypatch, tmp_path: Path +): + """超时后门禁释放,后续请求可恢复 / Timeout releases gate for following requests.""" + from ogscope.config import get_settings + from ogscope.web.api.analysis.services import analysis_service + + image_path = tmp_path / "gate_timeout.jpg" + _build_star_image(image_path) + settings = get_settings() + monkeypatch.setattr(settings, "star_analysis_request_timeout_ms", 80, raising=False) + monkeypatch.setattr(settings, "star_analysis_min_interval_ms", 50, raising=False) + monkeypatch.setattr(settings, "star_analysis_max_interval_ms", 20000, raising=False) + gate = analysis_service._realtime_gate_states.get("file_upload") + if gate is not None: + gate.in_flight = False + gate.last_finished_mono = 0.0 + + def _slow(*_args, **_kwargs): + time.sleep(0.2) + return { + "frame_index": 0, + "status": "MATCH_FOUND", + "ra_deg": 1.0, + "dec_deg": 2.0, + "solve_overlay": {}, + } + + monkeypatch.setattr(analysis_service, "_solve_bgr_to_row", _slow) + with image_path.open("rb") as f: + resp = client.post( + "/api/analysis/solve/frame_upload", + files={"file": ("frame.jpg", f, "image/jpeg")}, + data={"payload": json.dumps({"solve_interval_ms": 50})}, + ) + assert resp.status_code == 200 + data = resp.json() + assert data.get("gate_status") == "TIMEOUT_RELEASED" + + def _fast(*_args, **_kwargs): + return { + "frame_index": 0, + "status": "MATCH_FOUND", + "ra_deg": 1.0, + "dec_deg": 2.0, + "solve_overlay": {}, + } + + monkeypatch.setattr(analysis_service, "_solve_bgr_to_row", _fast) + time.sleep(0.06) + with image_path.open("rb") as f: + resp2 = client.post( + "/api/analysis/solve/frame_upload", + files={"file": ("frame.jpg", f, "image/jpeg")}, + data={"payload": json.dumps({"solve_interval_ms": 50})}, + ) + assert resp2.status_code == 200 + assert resp2.json().get("gate_status") == "SOLVED" + + +@pytest.mark.unit +def test_analysis_camera_solve_skipped_when_recording_active( + client, temp_analysis_dir, mock_plate_solve, monkeypatch +): + """录制进行中时拒绝实时相机解算 / Reject camera solve when recording is active.""" + monkeypatch.setattr( + "ogscope.web.api.debug.services.is_recording_active", lambda: True + ) + resp = client.post( + "/api/analysis/solve/frame", + json={"source": "camera"}, + ) + assert resp.status_code == 200 + data = resp.json() + assert data.get("gate_status") == "SKIPPED_BUSY" + assert "recording" in str(data.get("gate_reason", "")) + + +@pytest.mark.unit +def test_analysis_replace_transcoded_video_updates_sidecar( + client, temp_analysis_dir, tmp_path: Path +): + """转码替换后删除旧 AVI 并更新侧车 / Replace transcoded video deletes old AVI and updates sidecar.""" + avi_path = tmp_path / "raw.avi" + avi_path.write_bytes(b"AVI") + mp4_path = tmp_path / "out.mp4" + mp4_path.write_bytes(b"MP4") + with avi_path.open("rb") as f: + up_avi = client.post( + "/api/analysis/upload", + files={"file": ("raw.avi", f, "video/x-msvideo")}, + ) + assert up_avi.status_code == 200 + with mp4_path.open("rb") as f: + up_mp4 = client.post( + "/api/analysis/upload", + files={"file": ("out.mp4", f, "video/mp4")}, + ) + assert up_mp4.status_code == 200 + + side = temp_analysis_dir / "uploads" / "raw.txt" + side.write_text( + json.dumps( + { + "kind": "video", + "media_file": "raw.avi", + "extra": {"codec_fourcc": "MJPG", "container": "AVI"}, + } + ), + encoding="utf-8", + ) + + resp = client.post( + "/api/analysis/uploads/replace_video", + json={ + "old_filename": "raw.avi", + "new_filename": "out.mp4", + "duration_s": 3.2, + "nominal_fps": 2.0, + "codec_fourcc": "libx264", + "container": "MP4", + }, + ) + assert resp.status_code == 200 + data = resp.json() + assert data.get("success") is True + assert not (temp_analysis_dir / "uploads" / "raw.avi").exists() + assert (temp_analysis_dir / "uploads" / "out.mp4").is_file() + new_side = temp_analysis_dir / "uploads" / "out.txt" + assert new_side.is_file() + side_obj = json.loads(new_side.read_text(encoding="utf-8")) + assert side_obj.get("media_file") == "out.mp4" + extra = side_obj.get("extra") or {} + assert extra.get("container") == "MP4" diff --git a/web/analysis-ui/package-lock.json b/web/analysis-ui/package-lock.json index 617326b..cbbba70 100644 --- a/web/analysis-ui/package-lock.json +++ b/web/analysis-ui/package-lock.json @@ -8,6 +8,8 @@ "name": "ogscope-analysis-lab", "version": "0.1.0", "dependencies": { + "@ffmpeg/ffmpeg": "^0.12.15", + "@ffmpeg/util": "^0.12.2", "lucide-react": "^0.460.0", "react": "^18.3.1", "react-dom": "^18.3.1" @@ -709,6 +711,36 @@ "node": ">=12" } }, + "node_modules/@ffmpeg/ffmpeg": { + "version": "0.12.15", + "resolved": "https://registry.npmmirror.com/@ffmpeg/ffmpeg/-/ffmpeg-0.12.15.tgz", + "integrity": "sha512-1C8Obr4GsN3xw+/1Ww6PFM84wSQAGsdoTuTWPOj2OizsRDLT4CXTaVjPhkw6ARyDus1B9X/L2LiXHqYYsGnRFw==", + "license": "MIT", + "dependencies": { + "@ffmpeg/types": "^0.12.4" + }, + "engines": { + "node": ">=18.x" + } + }, + "node_modules/@ffmpeg/types": { + "version": "0.12.4", + "resolved": "https://registry.npmmirror.com/@ffmpeg/types/-/types-0.12.4.tgz", + "integrity": "sha512-k9vJQNBGTxE5AhYDtOYR5rO5fKsspbg51gbcwtbkw2lCdoIILzklulcjJfIDwrtn7XhDeF2M+THwJ2FGrLeV6A==", + "license": "MIT", + "engines": { + "node": ">=16.x" + } + }, + "node_modules/@ffmpeg/util": { + "version": "0.12.2", + "resolved": "https://registry.npmmirror.com/@ffmpeg/util/-/util-0.12.2.tgz", + "integrity": "sha512-ouyoW+4JB7WxjeZ2y6KpRvB+dLp7Cp4ro8z0HIVpZVCM7AwFlHa0c4R8Y/a4M3wMqATpYKhC7lSFHQ0T11MEDw==", + "license": "MIT", + "engines": { + "node": ">=18.x" + } + }, "node_modules/@jridgewell/gen-mapping": { "version": "0.3.13", "resolved": "https://registry.npmmirror.com/@jridgewell/gen-mapping/-/gen-mapping-0.3.13.tgz", diff --git a/web/analysis-ui/package.json b/web/analysis-ui/package.json index d34edcb..b611089 100644 --- a/web/analysis-ui/package.json +++ b/web/analysis-ui/package.json @@ -9,6 +9,8 @@ "preview": "vite preview" }, "dependencies": { + "@ffmpeg/ffmpeg": "^0.12.15", + "@ffmpeg/util": "^0.12.2", "lucide-react": "^0.460.0", "react": "^18.3.1", "react-dom": "^18.3.1" diff --git a/web/analysis-ui/src/App.tsx b/web/analysis-ui/src/App.tsx index 03c4865..9b0c5f7 100644 --- a/web/analysis-ui/src/App.tsx +++ b/web/analysis-ui/src/App.tsx @@ -31,6 +31,7 @@ import { fetchUploadFileInfo, fetchUploads, importFromDebug, + replaceTranscodedVideo, saveExperiment, saveUserPreset, solveBatch, @@ -49,6 +50,7 @@ import { drawSolveOverlayVideo, type SolveOverlay, } from "./drawOverlay"; +import { transcodeAviToMp4 } from "./utils/transcode"; import { useI18n } from "./i18n/I18nProvider"; import { buildMetaCaptionRows } from "./utils/metaCaption"; import { formatDateTime, formatFileSize } from "./utils/format"; @@ -113,6 +115,11 @@ function isVideoAsset(name: string): boolean { return /\.(mp4|mov|webm|mkv|avi)$/i.test(name); } +function compactFilename(name: string, head = 16, tail = 14): string { + if (name.length <= head + tail + 1) return name; + return `${name.slice(0, head)}...${name.slice(-tail)}`; +} + export default function App() { const { t, locale, setLocale } = useI18n(); const [view, setView] = useState("lab_image"); @@ -169,6 +176,17 @@ export default function App() { const [lastRoundTripMs, setLastRoundTripMs] = useState(null); /** 最近一次解算输入来源 / Last solve input source */ const [lastSolveSource, setLastSolveSource] = useState<"file" | "camera" | null>(null); + /** 实时解算调度提示 / Realtime solve gate hint */ + const [lastGateHint, setLastGateHint] = useState(null); + /** 用户期望解算间隔(毫秒)/ User desired realtime solve interval in ms */ + const [desiredSolveIntervalMs, setDesiredSolveIntervalMs] = useState(null); + const [transcodeBusy, setTranscodeBusy] = useState(false); + const [transcodeProgress, setTranscodeProgress] = useState(null); + const [transcodeHint, setTranscodeHint] = useState(null); + const [debugImportBusy, setDebugImportBusy] = useState(false); + const [debugImportProgress, setDebugImportProgress] = useState(null); + const [debugImportStep, setDebugImportStep] = useState(null); + const [debugImportMessage, setDebugImportMessage] = useState(null); const imgRef = useRef(null); const videoRef = useRef(null); @@ -182,11 +200,17 @@ export default function App() { const [sysOverview, setSysOverview] = useState(null); const [labSettings, setLabSettings] = useState(null); - const starAnalysisIntervalMs = useMemo(() => { + const intervalMinMs = labSettings?.star_analysis_min_interval_ms ?? 2000; + const intervalMaxMs = labSettings?.star_analysis_max_interval_ms ?? 12000; + const defaultIntervalMs = useMemo(() => { const fps = labSettings?.star_analysis_target_fps ?? 2 / 3; const clampedFps = Math.min(Math.max(fps, 0.2), 5.0); return Math.round(1000 / clampedFps); }, [labSettings]); + const starAnalysisIntervalMs = useMemo(() => { + const raw = desiredSolveIntervalMs ?? defaultIntervalMs; + return Math.min(Math.max(raw, intervalMinMs), intervalMaxMs); + }, [defaultIntervalMs, desiredSolveIntervalMs, intervalMaxMs, intervalMinMs]); const loadLists = useCallback(async () => { const [u, o, usr] = await Promise.all([ @@ -207,7 +231,15 @@ export default function App() { useEffect(() => { fetchLabSettings() - .then((s) => setLabSettings(s)) + .then((s) => { + setLabSettings(s); + const initialMs = Math.round(1000 / Math.min(Math.max(s.star_analysis_target_fps, 0.2), 5.0)); + const bounded = Math.min( + Math.max(initialMs, s.star_analysis_min_interval_ms), + s.star_analysis_max_interval_ms, + ); + setDesiredSolveIntervalMs(bounded); + }) .catch(() => setLabSettings(null)); }, []); @@ -478,9 +510,15 @@ export default function App() { source: "camera", overlay_topn_count: 3, enable_polar_guide: true, + solve_interval_ms: starAnalysisIntervalMs, solve_timeout_ms: Math.min((labSettings?.solver_timeout_ms ?? 1500) * 0.6, 1200), ...params, }); + if (out.gate_status && out.gate_status !== "SOLVED") { + setLastGateHint(`${out.gate_status}${out.gate_reason ? `: ${out.gate_reason}` : ""}`); + } else { + setLastGateHint(null); + } setLastResult(out as Record); setBatchPack(null); setLastSolveSource("camera"); @@ -533,10 +571,18 @@ export default function App() { }); const out = await solveFrameFromBlob(frameBlob, { ...params, + solve_interval_ms: starAnalysisIntervalMs, solve_timeout_ms: Math.min((labSettings?.solver_timeout_ms ?? 1500) * 0.6, 1200), overlay_topn_count: 3, enable_polar_guide: true, }); + const gateStatus = (out as { gate_status?: string | null }).gate_status; + const gateReason = (out as { gate_reason?: string | null }).gate_reason; + if (gateStatus && gateStatus !== "SOLVED") { + setLastGateHint(`${gateStatus}${gateReason ? `: ${gateReason}` : ""}`); + } else { + setLastGateHint(null); + } setLastResult(out as Record); setBatchPack(null); setLastSolveSource("file"); @@ -561,10 +607,115 @@ export default function App() { const canSolveVideoFile = useMemo(() => { if (view !== "lab_video" || videoPreviewMode !== "file") return false; if (!selected) return false; + if (/\.avi$/i.test(selected)) return false; if (videoPreviewError) return false; return videoNatural.w > 1 && videoNatural.h > 1; }, [view, videoPreviewMode, selected, videoPreviewError, videoNatural.w, videoNatural.h]); + const isSelectedAvi = useMemo(() => !!selected && /\.avi$/i.test(selected), [selected]); + + const onTranscodeAndUploadAvi = async () => { + if (!selected || !isSelectedAvi || transcodeBusy) return; + setErr(null); + setTranscodeBusy(true); + setTranscodeProgress(0); + setTranscodeHint(t("lab.transcode.loading")); + try { + const srcUrl = uploadFileUrl(selected); + const res = await fetch(srcUrl, { cache: "no-store" }); + if (!res.ok) throw new Error(t("lab.transcode.fetchFailed")); + const blob = await res.blob(); + const aviFile = new File([blob], selected, { type: "video/x-msvideo" }); + const out = await transcodeAviToMp4(aviFile, (ratio, msg) => { + setTranscodeProgress(Math.max(0, Math.min(1, ratio))); + if (msg === "loading_ffmpeg") setTranscodeHint(t("lab.transcode.loading")); + else if (msg === "transcoding") setTranscodeHint(t("lab.transcode.running")); + else if (msg === "packing_output") setTranscodeHint(t("lab.transcode.packaging")); + }); + setTranscodeHint(t("lab.transcode.uploading")); + const upload = await uploadFile(out.file, "analysis_transcoded"); + await replaceTranscodedVideo({ + old_filename: selected, + new_filename: upload.filename, + duration_s: out.duration_s, + nominal_fps: null, + codec_fourcc: "libx264", + container: "MP4", + }); + await loadLists(); + setSelected(upload.filename); + setTranscodeProgress(1); + setTranscodeHint(t("lab.transcode.done")); + } catch (e) { + setErr(String(e)); + setTranscodeHint(t("lab.transcode.failed")); + } finally { + setTranscodeBusy(false); + } + }; + + const transcodePoolAviAndReplace = async (aviFilename: string) => { + const srcUrl = uploadFileUrl(aviFilename); + const res = await fetch(srcUrl, { cache: "no-store" }); + if (!res.ok) throw new Error(t("lab.transcode.fetchFailed")); + const blob = await res.blob(); + const aviFile = new File([blob], aviFilename, { type: "video/x-msvideo" }); + const out = await transcodeAviToMp4(aviFile, (ratio, msg) => { + const base = 0.2; + const span = 0.6; + setDebugImportProgress(base + Math.max(0, Math.min(1, ratio)) * span); + if (msg === "loading_ffmpeg") setDebugImportStep(t("sidebar.flowLoadingTranscoder")); + else if (msg === "transcoding") setDebugImportStep(t("sidebar.flowTranscoding")); + else if (msg === "packing_output") setDebugImportStep(t("sidebar.flowPackaging")); + }); + setDebugImportStep(t("sidebar.flowUploadingMp4")); + setDebugImportProgress(0.86); + const upload = await uploadFile(out.file, "debug_console_transcoded"); + setDebugImportStep(t("sidebar.flowReplacing")); + setDebugImportProgress(0.94); + await replaceTranscodedVideo({ + old_filename: aviFilename, + new_filename: upload.filename, + duration_s: out.duration_s, + nominal_fps: null, + codec_fourcc: "libx264", + container: "MP4", + }); + return upload.filename; + }; + + const runDebugImportFlow = async () => { + if (!debugPick || debugImportBusy) return; + const isAvi = /\.avi$/i.test(debugPick); + setErr(null); + setDebugImportBusy(true); + setDebugImportProgress(0.02); + setDebugImportMessage(null); + try { + setDebugImportStep(t("sidebar.flowImportingDebug")); + const imported = await importFromDebug(debugPick); + setDebugImportProgress(isAvi ? 0.18 : 1); + let target = imported.filename; + if (isAvi) { + target = await transcodePoolAviAndReplace(imported.filename); + } else { + setDebugImportStep(t("sidebar.flowNoTranscode")); + } + await loadLists(); + setSelected(target); + setView(isVideoAsset(target) ? "lab_video" : "lab_image"); + setDebugImportProgress(1); + setDebugImportStep(t("sidebar.flowDone")); + setDebugImportMessage(t("sidebar.flowDoneMsg", { name: compactFilename(target) })); + } catch (e) { + setErr(String(e)); + setDebugImportStep(t("sidebar.flowFailed")); + setDebugImportMessage(String(e)); + } finally { + setDebugImportBusy(false); + } + }; + const stopFileSolveLoop = () => { setFileSolveRunning(false); if (fileSolveTimerRef.current != null) { @@ -875,7 +1026,7 @@ export default function App() { setView(isVideoAsset(u.filename) ? "lab_video" : "lab_image"); }} > - {u.filename} + {compactFilename(u.filename)} {isVideoAsset(u.filename) ? t("sidebar.assetTypeVideo") : t("sidebar.assetTypeImage")} @@ -903,26 +1054,38 @@ export default function App() { + {debugImportProgress != null && ( +
+
+ + {debugImportStep || t("sidebar.flowPreparing")} + + + {Math.round((debugImportProgress || 0) * 100)}% + +
+
+
+
+ {debugImportMessage && ( +
+ {debugImportMessage} +
+ )} +
+ )} {debugFilesForView.length === 0 ? (

{t("sidebar.debugEmpty")}

) : ( @@ -952,7 +1115,7 @@ export default function App() { )}
- {f.name} + {compactFilename(f.name)}
{f.type === "video" || isVideoAsset(f.name) @@ -998,6 +1161,11 @@ export default function App() { {err}
)} + {lastGateHint && ( +
+ {lastGateHint} +
+ )} {view === "lab_video" && (

{t("lab.videoLiveIntro")}

@@ -1148,6 +1316,35 @@ export default function App() { {videoPreviewError}
)} + {isSelectedAvi && ( +
+
{t("lab.transcode.title")}
+
{t("lab.transcode.desc")}
+ {transcodeProgress != null && ( +
+
+ {transcodeHint || t("lab.transcode.running")} +
+
+
+
+
+ )} +
+ +
+
+ )}
) : ( @@ -1461,7 +1658,9 @@ export default function App() {
{t("lab.file")}:{" "} - {selected} + + {compactFilename(selected, 22, 18)} + {uploads.find((u) => u.filename === selected)?.source && ( @@ -1812,6 +2011,23 @@ export default function App() { value={params.solve_timeout_ms ?? ""} onChange={(v) => setParams((p) => ({ ...p, solve_timeout_ms: v }))} /> + + setDesiredSolveIntervalMs( + v == null ? defaultIntervalMs : Math.round(v), + ) + } + /> +

+ {t("params.solveIntervalBound", { + min: intervalMinMs, + max: intervalMaxMs, + effective: starAnalysisIntervalMs, + })} +

{ + const r = await fetch(`${API}/analysis/uploads/replace_video`, { + method: "POST", + headers: { "Content-Type": "application/json" }, + body: JSON.stringify(payload), + }); + const data = await parseJson(r); + if (!r.ok) throw new Error(String((data as { detail?: string }).detail || r.status)); + return data as { success: boolean; filename: string }; +} + export async function solveImage( input_name: string, params: SolveParams @@ -252,6 +270,10 @@ export async function fetchUploadExperimentCount( export type LabPublicSettings = { solver_timeout_ms: number; star_analysis_target_fps: number; + star_analysis_min_interval_ms: number; + star_analysis_max_interval_ms: number; + star_analysis_request_timeout_ms: number; + star_analysis_slow_threshold_ms: number; camera_width: number; camera_height: number; camera_fps: number; @@ -273,11 +295,22 @@ export async function solveVideoFrame(payload: { input_name?: string | null; frame_index?: number; time_sec?: number | null; + solve_interval_ms?: number | null; /** 自动标注 Top-N 星点(省略则用后端默认) / Auto-label top-N stars (server default if omitted) */ overlay_topn_count?: number | null; /** 是否启用极轴引导信息(省略则用后端默认) / Whether to enable polar guide info (server default if omitted) */ enable_polar_guide?: boolean | null; -} & SolveParams): Promise<{ success: boolean; result?: Record; frame_id?: number | null; frame_ts?: string | null }> { +} & SolveParams): Promise<{ + success: boolean; + result?: Record; + frame_id?: number | null; + frame_ts?: string | null; + gate_status?: string | null; + gate_reason?: string | null; + next_allowed_in_ms?: number | null; + requested_interval_ms?: number | null; + effective_interval_ms?: number | null; +}> { const r = await fetch(`${API}/analysis/solve/frame`, { method: "POST", headers: { "Content-Type": "application/json" }, @@ -285,7 +318,17 @@ export async function solveVideoFrame(payload: { }); const data = await parseJson(r); if (!r.ok) throw new Error(String((data as { detail?: string }).detail || r.status)); - return data as { success: boolean; result?: Record; frame_id?: number | null; frame_ts?: string | null }; + return data as { + success: boolean; + result?: Record; + frame_id?: number | null; + frame_ts?: string | null; + gate_status?: string | null; + gate_reason?: string | null; + next_allowed_in_ms?: number | null; + requested_interval_ms?: number | null; + effective_interval_ms?: number | null; + }; } export async function solveFrameFromBlob( @@ -293,8 +336,16 @@ export async function solveFrameFromBlob( payload: SolveParams & { overlay_topn_count?: number | null; enable_polar_guide?: boolean | null; + solve_interval_ms?: number | null; }, -): Promise<{ success: boolean; result?: Record }> { +): Promise<{ + success: boolean; + result?: Record; + gate_status?: string | null; + gate_reason?: string | null; + requested_interval_ms?: number | null; + effective_interval_ms?: number | null; +}> { const fd = new FormData(); fd.append("file", frameBlob, "frame.jpg"); fd.append("payload", JSON.stringify(payload)); @@ -304,7 +355,14 @@ export async function solveFrameFromBlob( }); const data = await parseJson(r); if (!r.ok) throw new Error(String((data as { detail?: string }).detail || r.status)); - return data as { success: boolean; result?: Record }; + return data as { + success: boolean; + result?: Record; + gate_status?: string | null; + gate_reason?: string | null; + requested_interval_ms?: number | null; + effective_interval_ms?: number | null; + }; } export function experimentAssetUrl(experimentId: string): string { diff --git a/web/analysis-ui/src/utils/transcode.ts b/web/analysis-ui/src/utils/transcode.ts new file mode 100644 index 0000000..0b163af --- /dev/null +++ b/web/analysis-ui/src/utils/transcode.ts @@ -0,0 +1,92 @@ +import { FFmpeg } from "@ffmpeg/ffmpeg"; +import { fetchFile, toBlobURL } from "@ffmpeg/util"; + +type TranscodeProgress = (ratio: number, message?: string) => void; + +let ffmpegSingleton: FFmpeg | null = null; +let ffmpegLoading: Promise | null = null; + +async function ensureFfmpeg(onProgress?: TranscodeProgress): Promise { + if (ffmpegSingleton) return ffmpegSingleton; + if (ffmpegLoading) return ffmpegLoading; + ffmpegLoading = (async () => { + const ffmpeg = new FFmpeg(); + ffmpeg.on("progress", ({ progress }) => { + if (onProgress) onProgress(progress, "transcoding"); + }); + const base = "https://unpkg.com/@ffmpeg/core@0.12.6/dist/esm"; + const coreURL = await toBlobURL(`${base}/ffmpeg-core.js`, "text/javascript"); + const wasmURL = await toBlobURL( + `${base}/ffmpeg-core.wasm`, + "application/wasm", + ); + if (onProgress) onProgress(0.01, "loading_ffmpeg"); + await ffmpeg.load({ coreURL, wasmURL }); + ffmpegSingleton = ffmpeg; + return ffmpeg; + })(); + try { + return await ffmpegLoading; + } finally { + ffmpegLoading = null; + } +} + +async function probeDurationSeconds(file: File): Promise { + const url = URL.createObjectURL(file); + try { + const duration = await new Promise((resolve) => { + const v = document.createElement("video"); + v.preload = "metadata"; + v.onloadedmetadata = () => { + const d = Number(v.duration); + resolve(Number.isFinite(d) && d > 0 ? d : null); + }; + v.onerror = () => resolve(null); + v.src = url; + }); + return duration; + } finally { + URL.revokeObjectURL(url); + } +} + +export async function transcodeAviToMp4( + input: File, + onProgress?: TranscodeProgress, +): Promise<{ file: File; duration_s: number | null }> { + const ffmpeg = await ensureFfmpeg(onProgress); + const srcName = "input.avi"; + const outName = "output.mp4"; + if (onProgress) onProgress(0.03, "writing_input"); + await ffmpeg.writeFile(srcName, await fetchFile(input)); + // 低复杂度参数,优先速度与兼容性 / Favor speed and compatibility. + await ffmpeg.exec([ + "-i", + srcName, + "-c:v", + "libx264", + "-preset", + "veryfast", + "-crf", + "24", + "-pix_fmt", + "yuv420p", + "-movflags", + "+faststart", + "-an", + outName, + ]); + const output = await ffmpeg.readFile(outName); + if (onProgress) onProgress(0.98, "packing_output"); + const blob = new Blob([output], { type: "video/mp4" }); + const outFile = new File([blob], input.name.replace(/\.avi$/i, ".mp4"), { + type: "video/mp4", + }); + // 清理虚拟文件,避免 wasm 内存堆积 / Cleanup in-memory FS. + await ffmpeg.deleteFile(srcName); + await ffmpeg.deleteFile(outName); + const duration_s = await probeDurationSeconds(outFile); + if (onProgress) onProgress(1, "done"); + return { file: outFile, duration_s }; +} diff --git a/web/static/analysis-lab/assets/index-B7lY1o6d.js b/web/static/analysis-lab/assets/index-B7lY1o6d.js deleted file mode 100644 index a045142..0000000 --- a/web/static/analysis-lab/assets/index-B7lY1o6d.js +++ /dev/null @@ -1,125 +0,0 @@ -(function(){const t=document.createElement("link").relList;if(t&&t.supports&&t.supports("modulepreload"))return;for(const l of document.querySelectorAll('link[rel="modulepreload"]'))r(l);new MutationObserver(l=>{for(const o of l)if(o.type==="childList")for(const a of o.addedNodes)a.tagName==="LINK"&&a.rel==="modulepreload"&&r(a)}).observe(document,{childList:!0,subtree:!0});function n(l){const o={};return l.integrity&&(o.integrity=l.integrity),l.referrerPolicy&&(o.referrerPolicy=l.referrerPolicy),l.crossOrigin==="use-credentials"?o.credentials="include":l.crossOrigin==="anonymous"?o.credentials="omit":o.credentials="same-origin",o}function r(l){if(l.ep)return;l.ep=!0;const o=n(l);fetch(l.href,o)}})();function Pf(e){return e&&e.__esModule&&Object.prototype.hasOwnProperty.call(e,"default")?e.default:e}var Ku={exports:{}},Jl={},Gu={exports:{}},F={};/** - * @license React - * react.production.min.js - * - * Copyright (c) Facebook, Inc. and its affiliates. - * - * This source code is licensed under the MIT license found in the - * LICENSE file in the root directory of this source tree. - */var zr=Symbol.for("react.element"),Mf=Symbol.for("react.portal"),zf=Symbol.for("react.fragment"),Tf=Symbol.for("react.strict_mode"),Rf=Symbol.for("react.profiler"),Lf=Symbol.for("react.provider"),Ff=Symbol.for("react.context"),Of=Symbol.for("react.forward_ref"),If=Symbol.for("react.suspense"),Df=Symbol.for("react.memo"),Uf=Symbol.for("react.lazy"),Ei=Symbol.iterator;function $f(e){return e===null||typeof e!="object"?null:(e=Ei&&e[Ei]||e["@@iterator"],typeof e=="function"?e:null)}var Yu={isMounted:function(){return!1},enqueueForceUpdate:function(){},enqueueReplaceState:function(){},enqueueSetState:function(){}},Xu=Object.assign,Ju={};function Dn(e,t,n){this.props=e,this.context=t,this.refs=Ju,this.updater=n||Yu}Dn.prototype.isReactComponent={};Dn.prototype.setState=function(e,t){if(typeof e!="object"&&typeof e!="function"&&e!=null)throw Error("setState(...): takes an object of state variables to update or a function which returns an object of state variables.");this.updater.enqueueSetState(this,e,t,"setState")};Dn.prototype.forceUpdate=function(e){this.updater.enqueueForceUpdate(this,e,"forceUpdate")};function Zu(){}Zu.prototype=Dn.prototype;function ss(e,t,n){this.props=e,this.context=t,this.refs=Ju,this.updater=n||Yu}var is=ss.prototype=new Zu;is.constructor=ss;Xu(is,Dn.prototype);is.isPureReactComponent=!0;var bi=Array.isArray,qu=Object.prototype.hasOwnProperty,us={current:null},ec={key:!0,ref:!0,__self:!0,__source:!0};function tc(e,t,n){var r,l={},o=null,a=null;if(t!=null)for(r in t.ref!==void 0&&(a=t.ref),t.key!==void 0&&(o=""+t.key),t)qu.call(t,r)&&!ec.hasOwnProperty(r)&&(l[r]=t[r]);var i=arguments.length-2;if(i===1)l.children=n;else if(1>>1,Z=b[H];if(0>>1;H<_e;){var Ke=2*(H+1)-1,rt=b[Ke],Ce=Ke+1,lt=b[Ce];if(0>l(rt,R))Cel(lt,rt)?(b[H]=lt,b[Ce]=R,H=Ce):(b[H]=rt,b[Ke]=R,H=Ke);else if(Cel(lt,R))b[H]=lt,b[Ce]=R,H=Ce;else break e}}return T}function l(b,T){var R=b.sortIndex-T.sortIndex;return R!==0?R:b.id-T.id}if(typeof performance=="object"&&typeof performance.now=="function"){var o=performance;e.unstable_now=function(){return o.now()}}else{var a=Date,i=a.now();e.unstable_now=function(){return a.now()-i}}var u=[],f=[],v=1,h=null,y=3,k=!1,j=!1,_=!1,$=typeof setTimeout=="function"?setTimeout:null,p=typeof clearTimeout=="function"?clearTimeout:null,d=typeof setImmediate<"u"?setImmediate:null;typeof navigator<"u"&&navigator.scheduling!==void 0&&navigator.scheduling.isInputPending!==void 0&&navigator.scheduling.isInputPending.bind(navigator.scheduling);function m(b){for(var T=n(f);T!==null;){if(T.callback===null)r(f);else if(T.startTime<=b)r(f),T.sortIndex=T.expirationTime,t(u,T);else break;T=n(f)}}function g(b){if(_=!1,m(b),!j)if(n(u)!==null)j=!0,rn(N);else{var T=n(f);T!==null&&Ut(g,T.startTime-b)}}function N(b,T){j=!1,_&&(_=!1,p(z),z=-1),k=!0;var R=y;try{for(m(T),h=n(u);h!==null&&(!(h.expirationTime>T)||b&&!le());){var H=h.callback;if(typeof H=="function"){h.callback=null,y=h.priorityLevel;var Z=H(h.expirationTime<=T);T=e.unstable_now(),typeof Z=="function"?h.callback=Z:h===n(u)&&r(u),m(T)}else r(u);h=n(u)}if(h!==null)var _e=!0;else{var Ke=n(f);Ke!==null&&Ut(g,Ke.startTime-T),_e=!1}return _e}finally{h=null,y=R,k=!1}}var M=!1,E=null,z=-1,U=5,L=-1;function le(){return!(e.unstable_now()-Lb||125H?(b.sortIndex=R,t(f,b),n(u)===null&&b===n(f)&&(_?(p(z),z=-1):_=!0,Ut(g,R-H))):(b.sortIndex=Z,t(u,b),j||k||(j=!0,rn(N))),b},e.unstable_shouldYield=le,e.unstable_wrapCallback=function(b){var T=y;return function(){var R=y;y=T;try{return b.apply(this,arguments)}finally{y=R}}}})(ac);oc.exports=ac;var Zf=oc.exports;/** - * @license React - * react-dom.production.min.js - * - * Copyright (c) Facebook, Inc. and its affiliates. - * - * This source code is licensed under the MIT license found in the - * LICENSE file in the root directory of this source tree. - */var qf=w,Me=Zf;function S(e){for(var t="https://reactjs.org/docs/error-decoder.html?invariant="+e,n=1;n"u"||typeof window.document>"u"||typeof window.document.createElement>"u"),ma=Object.prototype.hasOwnProperty,ep=/^[:A-Z_a-z\u00C0-\u00D6\u00D8-\u00F6\u00F8-\u02FF\u0370-\u037D\u037F-\u1FFF\u200C-\u200D\u2070-\u218F\u2C00-\u2FEF\u3001-\uD7FF\uF900-\uFDCF\uFDF0-\uFFFD][:A-Z_a-z\u00C0-\u00D6\u00D8-\u00F6\u00F8-\u02FF\u0370-\u037D\u037F-\u1FFF\u200C-\u200D\u2070-\u218F\u2C00-\u2FEF\u3001-\uD7FF\uF900-\uFDCF\uFDF0-\uFFFD\-.0-9\u00B7\u0300-\u036F\u203F-\u2040]*$/,Mi={},zi={};function tp(e){return ma.call(zi,e)?!0:ma.call(Mi,e)?!1:ep.test(e)?zi[e]=!0:(Mi[e]=!0,!1)}function np(e,t,n,r){if(n!==null&&n.type===0)return!1;switch(typeof t){case"function":case"symbol":return!0;case"boolean":return r?!1:n!==null?!n.acceptsBooleans:(e=e.toLowerCase().slice(0,5),e!=="data-"&&e!=="aria-");default:return!1}}function rp(e,t,n,r){if(t===null||typeof t>"u"||np(e,t,n,r))return!0;if(r)return!1;if(n!==null)switch(n.type){case 3:return!t;case 4:return t===!1;case 5:return isNaN(t);case 6:return isNaN(t)||1>t}return!1}function ge(e,t,n,r,l,o,a){this.acceptsBooleans=t===2||t===3||t===4,this.attributeName=r,this.attributeNamespace=l,this.mustUseProperty=n,this.propertyName=e,this.type=t,this.sanitizeURL=o,this.removeEmptyString=a}var ue={};"children dangerouslySetInnerHTML defaultValue defaultChecked innerHTML suppressContentEditableWarning suppressHydrationWarning style".split(" ").forEach(function(e){ue[e]=new ge(e,0,!1,e,null,!1,!1)});[["acceptCharset","accept-charset"],["className","class"],["htmlFor","for"],["httpEquiv","http-equiv"]].forEach(function(e){var t=e[0];ue[t]=new ge(t,1,!1,e[1],null,!1,!1)});["contentEditable","draggable","spellCheck","value"].forEach(function(e){ue[e]=new ge(e,2,!1,e.toLowerCase(),null,!1,!1)});["autoReverse","externalResourcesRequired","focusable","preserveAlpha"].forEach(function(e){ue[e]=new ge(e,2,!1,e,null,!1,!1)});"allowFullScreen async autoFocus autoPlay controls default defer disabled disablePictureInPicture disableRemotePlayback formNoValidate hidden loop noModule noValidate open playsInline readOnly required reversed scoped seamless itemScope".split(" ").forEach(function(e){ue[e]=new ge(e,3,!1,e.toLowerCase(),null,!1,!1)});["checked","multiple","muted","selected"].forEach(function(e){ue[e]=new ge(e,3,!0,e,null,!1,!1)});["capture","download"].forEach(function(e){ue[e]=new ge(e,4,!1,e,null,!1,!1)});["cols","rows","size","span"].forEach(function(e){ue[e]=new ge(e,6,!1,e,null,!1,!1)});["rowSpan","start"].forEach(function(e){ue[e]=new ge(e,5,!1,e.toLowerCase(),null,!1,!1)});var ds=/[\-:]([a-z])/g;function fs(e){return e[1].toUpperCase()}"accent-height alignment-baseline arabic-form baseline-shift cap-height clip-path clip-rule color-interpolation color-interpolation-filters color-profile color-rendering dominant-baseline enable-background fill-opacity fill-rule flood-color flood-opacity font-family font-size font-size-adjust font-stretch font-style font-variant font-weight glyph-name glyph-orientation-horizontal glyph-orientation-vertical horiz-adv-x horiz-origin-x image-rendering letter-spacing lighting-color marker-end marker-mid marker-start overline-position overline-thickness paint-order panose-1 pointer-events rendering-intent shape-rendering stop-color stop-opacity strikethrough-position strikethrough-thickness stroke-dasharray stroke-dashoffset stroke-linecap stroke-linejoin stroke-miterlimit stroke-opacity stroke-width text-anchor text-decoration text-rendering underline-position underline-thickness unicode-bidi unicode-range units-per-em v-alphabetic v-hanging v-ideographic v-mathematical vector-effect vert-adv-y vert-origin-x vert-origin-y word-spacing writing-mode xmlns:xlink x-height".split(" ").forEach(function(e){var t=e.replace(ds,fs);ue[t]=new ge(t,1,!1,e,null,!1,!1)});"xlink:actuate xlink:arcrole xlink:role xlink:show xlink:title xlink:type".split(" ").forEach(function(e){var t=e.replace(ds,fs);ue[t]=new ge(t,1,!1,e,"http://www.w3.org/1999/xlink",!1,!1)});["xml:base","xml:lang","xml:space"].forEach(function(e){var t=e.replace(ds,fs);ue[t]=new ge(t,1,!1,e,"http://www.w3.org/XML/1998/namespace",!1,!1)});["tabIndex","crossOrigin"].forEach(function(e){ue[e]=new ge(e,1,!1,e.toLowerCase(),null,!1,!1)});ue.xlinkHref=new ge("xlinkHref",1,!1,"xlink:href","http://www.w3.org/1999/xlink",!0,!1);["src","href","action","formAction"].forEach(function(e){ue[e]=new ge(e,1,!1,e.toLowerCase(),null,!0,!0)});function ps(e,t,n,r){var l=ue.hasOwnProperty(t)?ue[t]:null;(l!==null?l.type!==0:r||!(2i||l[a]!==o[i]){var u=` -`+l[a].replace(" at new "," at ");return e.displayName&&u.includes("")&&(u=u.replace("",e.displayName)),u}while(1<=a&&0<=i);break}}}finally{Oo=!1,Error.prepareStackTrace=n}return(e=e?e.displayName||e.name:"")?tr(e):""}function lp(e){switch(e.tag){case 5:return tr(e.type);case 16:return tr("Lazy");case 13:return tr("Suspense");case 19:return tr("SuspenseList");case 0:case 2:case 15:return e=Io(e.type,!1),e;case 11:return e=Io(e.type.render,!1),e;case 1:return e=Io(e.type,!0),e;default:return""}}function ga(e){if(e==null)return null;if(typeof e=="function")return e.displayName||e.name||null;if(typeof e=="string")return e;switch(e){case pn:return"Fragment";case fn:return"Portal";case ha:return"Profiler";case ms:return"StrictMode";case va:return"Suspense";case ya:return"SuspenseList"}if(typeof e=="object")switch(e.$$typeof){case uc:return(e.displayName||"Context")+".Consumer";case ic:return(e._context.displayName||"Context")+".Provider";case hs:var t=e.render;return e=e.displayName,e||(e=t.displayName||t.name||"",e=e!==""?"ForwardRef("+e+")":"ForwardRef"),e;case vs:return t=e.displayName||null,t!==null?t:ga(e.type)||"Memo";case wt:t=e._payload,e=e._init;try{return ga(e(t))}catch{}}return null}function op(e){var t=e.type;switch(e.tag){case 24:return"Cache";case 9:return(t.displayName||"Context")+".Consumer";case 10:return(t._context.displayName||"Context")+".Provider";case 18:return"DehydratedFragment";case 11:return e=t.render,e=e.displayName||e.name||"",t.displayName||(e!==""?"ForwardRef("+e+")":"ForwardRef");case 7:return"Fragment";case 5:return t;case 4:return"Portal";case 3:return"Root";case 6:return"Text";case 16:return ga(t);case 8:return t===ms?"StrictMode":"Mode";case 22:return"Offscreen";case 12:return"Profiler";case 21:return"Scope";case 13:return"Suspense";case 19:return"SuspenseList";case 25:return"TracingMarker";case 1:case 0:case 17:case 2:case 14:case 15:if(typeof t=="function")return t.displayName||t.name||null;if(typeof t=="string")return t}return null}function Lt(e){switch(typeof e){case"boolean":case"number":case"string":case"undefined":return e;case"object":return e;default:return""}}function dc(e){var t=e.type;return(e=e.nodeName)&&e.toLowerCase()==="input"&&(t==="checkbox"||t==="radio")}function ap(e){var t=dc(e)?"checked":"value",n=Object.getOwnPropertyDescriptor(e.constructor.prototype,t),r=""+e[t];if(!e.hasOwnProperty(t)&&typeof n<"u"&&typeof n.get=="function"&&typeof n.set=="function"){var l=n.get,o=n.set;return Object.defineProperty(e,t,{configurable:!0,get:function(){return l.call(this)},set:function(a){r=""+a,o.call(this,a)}}),Object.defineProperty(e,t,{enumerable:n.enumerable}),{getValue:function(){return r},setValue:function(a){r=""+a},stopTracking:function(){e._valueTracker=null,delete e[t]}}}}function Jr(e){e._valueTracker||(e._valueTracker=ap(e))}function fc(e){if(!e)return!1;var t=e._valueTracker;if(!t)return!0;var n=t.getValue(),r="";return e&&(r=dc(e)?e.checked?"true":"false":e.value),e=r,e!==n?(t.setValue(e),!0):!1}function Cl(e){if(e=e||(typeof document<"u"?document:void 0),typeof e>"u")return null;try{return e.activeElement||e.body}catch{return e.body}}function xa(e,t){var n=t.checked;return Y({},t,{defaultChecked:void 0,defaultValue:void 0,value:void 0,checked:n??e._wrapperState.initialChecked})}function Ri(e,t){var n=t.defaultValue==null?"":t.defaultValue,r=t.checked!=null?t.checked:t.defaultChecked;n=Lt(t.value!=null?t.value:n),e._wrapperState={initialChecked:r,initialValue:n,controlled:t.type==="checkbox"||t.type==="radio"?t.checked!=null:t.value!=null}}function pc(e,t){t=t.checked,t!=null&&ps(e,"checked",t,!1)}function wa(e,t){pc(e,t);var n=Lt(t.value),r=t.type;if(n!=null)r==="number"?(n===0&&e.value===""||e.value!=n)&&(e.value=""+n):e.value!==""+n&&(e.value=""+n);else if(r==="submit"||r==="reset"){e.removeAttribute("value");return}t.hasOwnProperty("value")?Sa(e,t.type,n):t.hasOwnProperty("defaultValue")&&Sa(e,t.type,Lt(t.defaultValue)),t.checked==null&&t.defaultChecked!=null&&(e.defaultChecked=!!t.defaultChecked)}function Li(e,t,n){if(t.hasOwnProperty("value")||t.hasOwnProperty("defaultValue")){var r=t.type;if(!(r!=="submit"&&r!=="reset"||t.value!==void 0&&t.value!==null))return;t=""+e._wrapperState.initialValue,n||t===e.value||(e.value=t),e.defaultValue=t}n=e.name,n!==""&&(e.name=""),e.defaultChecked=!!e._wrapperState.initialChecked,n!==""&&(e.name=n)}function Sa(e,t,n){(t!=="number"||Cl(e.ownerDocument)!==e)&&(n==null?e.defaultValue=""+e._wrapperState.initialValue:e.defaultValue!==""+n&&(e.defaultValue=""+n))}var nr=Array.isArray;function jn(e,t,n,r){if(e=e.options,t){t={};for(var l=0;l"+t.valueOf().toString()+"",t=Zr.firstChild;e.firstChild;)e.removeChild(e.firstChild);for(;t.firstChild;)e.appendChild(t.firstChild)}});function hr(e,t){if(t){var n=e.firstChild;if(n&&n===e.lastChild&&n.nodeType===3){n.nodeValue=t;return}}e.textContent=t}var or={animationIterationCount:!0,aspectRatio:!0,borderImageOutset:!0,borderImageSlice:!0,borderImageWidth:!0,boxFlex:!0,boxFlexGroup:!0,boxOrdinalGroup:!0,columnCount:!0,columns:!0,flex:!0,flexGrow:!0,flexPositive:!0,flexShrink:!0,flexNegative:!0,flexOrder:!0,gridArea:!0,gridRow:!0,gridRowEnd:!0,gridRowSpan:!0,gridRowStart:!0,gridColumn:!0,gridColumnEnd:!0,gridColumnSpan:!0,gridColumnStart:!0,fontWeight:!0,lineClamp:!0,lineHeight:!0,opacity:!0,order:!0,orphans:!0,tabSize:!0,widows:!0,zIndex:!0,zoom:!0,fillOpacity:!0,floodOpacity:!0,stopOpacity:!0,strokeDasharray:!0,strokeDashoffset:!0,strokeMiterlimit:!0,strokeOpacity:!0,strokeWidth:!0},sp=["Webkit","ms","Moz","O"];Object.keys(or).forEach(function(e){sp.forEach(function(t){t=t+e.charAt(0).toUpperCase()+e.substring(1),or[t]=or[e]})});function yc(e,t,n){return t==null||typeof t=="boolean"||t===""?"":n||typeof t!="number"||t===0||or.hasOwnProperty(e)&&or[e]?(""+t).trim():t+"px"}function gc(e,t){e=e.style;for(var n in t)if(t.hasOwnProperty(n)){var r=n.indexOf("--")===0,l=yc(n,t[n],r);n==="float"&&(n="cssFloat"),r?e.setProperty(n,l):e[n]=l}}var ip=Y({menuitem:!0},{area:!0,base:!0,br:!0,col:!0,embed:!0,hr:!0,img:!0,input:!0,keygen:!0,link:!0,meta:!0,param:!0,source:!0,track:!0,wbr:!0});function ja(e,t){if(t){if(ip[e]&&(t.children!=null||t.dangerouslySetInnerHTML!=null))throw Error(S(137,e));if(t.dangerouslySetInnerHTML!=null){if(t.children!=null)throw Error(S(60));if(typeof t.dangerouslySetInnerHTML!="object"||!("__html"in t.dangerouslySetInnerHTML))throw Error(S(61))}if(t.style!=null&&typeof t.style!="object")throw Error(S(62))}}function _a(e,t){if(e.indexOf("-")===-1)return typeof t.is=="string";switch(e){case"annotation-xml":case"color-profile":case"font-face":case"font-face-src":case"font-face-uri":case"font-face-format":case"font-face-name":case"missing-glyph":return!1;default:return!0}}var Ca=null;function ys(e){return e=e.target||e.srcElement||window,e.correspondingUseElement&&(e=e.correspondingUseElement),e.nodeType===3?e.parentNode:e}var Ea=null,_n=null,Cn=null;function Ii(e){if(e=Lr(e)){if(typeof Ea!="function")throw Error(S(280));var t=e.stateNode;t&&(t=no(t),Ea(e.stateNode,e.type,t))}}function xc(e){_n?Cn?Cn.push(e):Cn=[e]:_n=e}function wc(){if(_n){var e=_n,t=Cn;if(Cn=_n=null,Ii(e),t)for(e=0;e>>=0,e===0?32:31-(xp(e)/wp|0)|0}var qr=64,el=4194304;function rr(e){switch(e&-e){case 1:return 1;case 2:return 2;case 4:return 4;case 8:return 8;case 16:return 16;case 32:return 32;case 64: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 e&4194240;case 4194304:case 8388608:case 16777216:case 33554432:case 67108864:return e&130023424;case 134217728:return 134217728;case 268435456:return 268435456;case 536870912:return 536870912;case 1073741824:return 1073741824;default:return e}}function Ml(e,t){var n=e.pendingLanes;if(n===0)return 0;var r=0,l=e.suspendedLanes,o=e.pingedLanes,a=n&268435455;if(a!==0){var i=a&~l;i!==0?r=rr(i):(o&=a,o!==0&&(r=rr(o)))}else a=n&~l,a!==0?r=rr(a):o!==0&&(r=rr(o));if(r===0)return 0;if(t!==0&&t!==r&&!(t&l)&&(l=r&-r,o=t&-t,l>=o||l===16&&(o&4194240)!==0))return t;if(r&4&&(r|=n&16),t=e.entangledLanes,t!==0)for(e=e.entanglements,t&=r;0n;n++)t.push(e);return t}function Tr(e,t,n){e.pendingLanes|=t,t!==536870912&&(e.suspendedLanes=0,e.pingedLanes=0),e=e.eventTimes,t=31-Ve(t),e[t]=n}function jp(e,t){var n=e.pendingLanes&~t;e.pendingLanes=t,e.suspendedLanes=0,e.pingedLanes=0,e.expiredLanes&=t,e.mutableReadLanes&=t,e.entangledLanes&=t,t=e.entanglements;var r=e.eventTimes;for(e=e.expirationTimes;0=sr),Qi=" ",Ki=!1;function $c(e,t){switch(e){case"keyup":return Zp.indexOf(t.keyCode)!==-1;case"keydown":return t.keyCode!==229;case"keypress":case"mousedown":case"focusout":return!0;default:return!1}}function Hc(e){return e=e.detail,typeof e=="object"&&"data"in e?e.data:null}var mn=!1;function em(e,t){switch(e){case"compositionend":return Hc(t);case"keypress":return t.which!==32?null:(Ki=!0,Qi);case"textInput":return e=t.data,e===Qi&&Ki?null:e;default:return null}}function tm(e,t){if(mn)return e==="compositionend"||!_s&&$c(e,t)?(e=Dc(),vl=ks=jt=null,mn=!1,e):null;switch(e){case"paste":return null;case"keypress":if(!(t.ctrlKey||t.altKey||t.metaKey)||t.ctrlKey&&t.altKey){if(t.char&&1=t)return{node:n,offset:t-e};e=r}e:{for(;n;){if(n.nextSibling){n=n.nextSibling;break e}n=n.parentNode}n=void 0}n=Ji(n)}}function Wc(e,t){return e&&t?e===t?!0:e&&e.nodeType===3?!1:t&&t.nodeType===3?Wc(e,t.parentNode):"contains"in e?e.contains(t):e.compareDocumentPosition?!!(e.compareDocumentPosition(t)&16):!1:!1}function Qc(){for(var e=window,t=Cl();t instanceof e.HTMLIFrameElement;){try{var n=typeof t.contentWindow.location.href=="string"}catch{n=!1}if(n)e=t.contentWindow;else break;t=Cl(e.document)}return t}function Cs(e){var t=e&&e.nodeName&&e.nodeName.toLowerCase();return t&&(t==="input"&&(e.type==="text"||e.type==="search"||e.type==="tel"||e.type==="url"||e.type==="password")||t==="textarea"||e.contentEditable==="true")}function cm(e){var t=Qc(),n=e.focusedElem,r=e.selectionRange;if(t!==n&&n&&n.ownerDocument&&Wc(n.ownerDocument.documentElement,n)){if(r!==null&&Cs(n)){if(t=r.start,e=r.end,e===void 0&&(e=t),"selectionStart"in n)n.selectionStart=t,n.selectionEnd=Math.min(e,n.value.length);else if(e=(t=n.ownerDocument||document)&&t.defaultView||window,e.getSelection){e=e.getSelection();var l=n.textContent.length,o=Math.min(r.start,l);r=r.end===void 0?o:Math.min(r.end,l),!e.extend&&o>r&&(l=r,r=o,o=l),l=Zi(n,o);var a=Zi(n,r);l&&a&&(e.rangeCount!==1||e.anchorNode!==l.node||e.anchorOffset!==l.offset||e.focusNode!==a.node||e.focusOffset!==a.offset)&&(t=t.createRange(),t.setStart(l.node,l.offset),e.removeAllRanges(),o>r?(e.addRange(t),e.extend(a.node,a.offset)):(t.setEnd(a.node,a.offset),e.addRange(t)))}}for(t=[],e=n;e=e.parentNode;)e.nodeType===1&&t.push({element:e,left:e.scrollLeft,top:e.scrollTop});for(typeof n.focus=="function"&&n.focus(),n=0;n=document.documentMode,hn=null,Ra=null,ur=null,La=!1;function qi(e,t,n){var r=n.window===n?n.document:n.nodeType===9?n:n.ownerDocument;La||hn==null||hn!==Cl(r)||(r=hn,"selectionStart"in r&&Cs(r)?r={start:r.selectionStart,end:r.selectionEnd}:(r=(r.ownerDocument&&r.ownerDocument.defaultView||window).getSelection(),r={anchorNode:r.anchorNode,anchorOffset:r.anchorOffset,focusNode:r.focusNode,focusOffset:r.focusOffset}),ur&&Sr(ur,r)||(ur=r,r=Rl(Ra,"onSelect"),0gn||(e.current=$a[gn],$a[gn]=null,gn--)}function A(e,t){gn++,$a[gn]=e.current,e.current=t}var Ft={},pe=It(Ft),ke=It(!1),Yt=Ft;function Tn(e,t){var n=e.type.contextTypes;if(!n)return Ft;var r=e.stateNode;if(r&&r.__reactInternalMemoizedUnmaskedChildContext===t)return r.__reactInternalMemoizedMaskedChildContext;var l={},o;for(o in n)l[o]=t[o];return r&&(e=e.stateNode,e.__reactInternalMemoizedUnmaskedChildContext=t,e.__reactInternalMemoizedMaskedChildContext=l),l}function Ne(e){return e=e.childContextTypes,e!=null}function Fl(){V(ke),V(pe)}function au(e,t,n){if(pe.current!==Ft)throw Error(S(168));A(pe,t),A(ke,n)}function td(e,t,n){var r=e.stateNode;if(t=t.childContextTypes,typeof r.getChildContext!="function")return n;r=r.getChildContext();for(var l in r)if(!(l in t))throw Error(S(108,op(e)||"Unknown",l));return Y({},n,r)}function Ol(e){return e=(e=e.stateNode)&&e.__reactInternalMemoizedMergedChildContext||Ft,Yt=pe.current,A(pe,e),A(ke,ke.current),!0}function su(e,t,n){var r=e.stateNode;if(!r)throw Error(S(169));n?(e=td(e,t,Yt),r.__reactInternalMemoizedMergedChildContext=e,V(ke),V(pe),A(pe,e)):V(ke),A(ke,n)}var ut=null,ro=!1,Jo=!1;function nd(e){ut===null?ut=[e]:ut.push(e)}function km(e){ro=!0,nd(e)}function Dt(){if(!Jo&&ut!==null){Jo=!0;var e=0,t=D;try{var n=ut;for(D=1;e>=a,l-=a,ct=1<<32-Ve(t)+l|n<z?(U=E,E=null):U=E.sibling;var L=y(p,E,m[z],g);if(L===null){E===null&&(E=U);break}e&&E&&L.alternate===null&&t(p,E),d=o(L,d,z),M===null?N=L:M.sibling=L,M=L,E=U}if(z===m.length)return n(p,E),W&&At(p,z),N;if(E===null){for(;zz?(U=E,E=null):U=E.sibling;var le=y(p,E,L.value,g);if(le===null){E===null&&(E=U);break}e&&E&&le.alternate===null&&t(p,E),d=o(le,d,z),M===null?N=le:M.sibling=le,M=le,E=U}if(L.done)return n(p,E),W&&At(p,z),N;if(E===null){for(;!L.done;z++,L=m.next())L=h(p,L.value,g),L!==null&&(d=o(L,d,z),M===null?N=L:M.sibling=L,M=L);return W&&At(p,z),N}for(E=r(p,E);!L.done;z++,L=m.next())L=k(E,p,z,L.value,g),L!==null&&(e&&L.alternate!==null&&E.delete(L.key===null?z:L.key),d=o(L,d,z),M===null?N=L:M.sibling=L,M=L);return e&&E.forEach(function(nt){return t(p,nt)}),W&&At(p,z),N}function $(p,d,m,g){if(typeof m=="object"&&m!==null&&m.type===pn&&m.key===null&&(m=m.props.children),typeof m=="object"&&m!==null){switch(m.$$typeof){case Xr:e:{for(var N=m.key,M=d;M!==null;){if(M.key===N){if(N=m.type,N===pn){if(M.tag===7){n(p,M.sibling),d=l(M,m.props.children),d.return=p,p=d;break e}}else if(M.elementType===N||typeof N=="object"&&N!==null&&N.$$typeof===wt&&cu(N)===M.type){n(p,M.sibling),d=l(M,m.props),d.ref=Jn(p,M,m),d.return=p,p=d;break e}n(p,M);break}else t(p,M);M=M.sibling}m.type===pn?(d=Gt(m.props.children,p.mode,g,m.key),d.return=p,p=d):(g=jl(m.type,m.key,m.props,null,p.mode,g),g.ref=Jn(p,d,m),g.return=p,p=g)}return a(p);case fn:e:{for(M=m.key;d!==null;){if(d.key===M)if(d.tag===4&&d.stateNode.containerInfo===m.containerInfo&&d.stateNode.implementation===m.implementation){n(p,d.sibling),d=l(d,m.children||[]),d.return=p,p=d;break e}else{n(p,d);break}else t(p,d);d=d.sibling}d=oa(m,p.mode,g),d.return=p,p=d}return a(p);case wt:return M=m._init,$(p,d,M(m._payload),g)}if(nr(m))return j(p,d,m,g);if(Qn(m))return _(p,d,m,g);sl(p,m)}return typeof m=="string"&&m!==""||typeof m=="number"?(m=""+m,d!==null&&d.tag===6?(n(p,d.sibling),d=l(d,m),d.return=p,p=d):(n(p,d),d=la(m,p.mode,g),d.return=p,p=d),a(p)):n(p,d)}return $}var Ln=ad(!0),sd=ad(!1),Ul=It(null),$l=null,Sn=null,Ms=null;function zs(){Ms=Sn=$l=null}function Ts(e){var t=Ul.current;V(Ul),e._currentValue=t}function Ba(e,t,n){for(;e!==null;){var r=e.alternate;if((e.childLanes&t)!==t?(e.childLanes|=t,r!==null&&(r.childLanes|=t)):r!==null&&(r.childLanes&t)!==t&&(r.childLanes|=t),e===n)break;e=e.return}}function bn(e,t){$l=e,Ms=Sn=null,e=e.dependencies,e!==null&&e.firstContext!==null&&(e.lanes&t&&(Se=!0),e.firstContext=null)}function De(e){var t=e._currentValue;if(Ms!==e)if(e={context:e,memoizedValue:t,next:null},Sn===null){if($l===null)throw Error(S(308));Sn=e,$l.dependencies={lanes:0,firstContext:e}}else Sn=Sn.next=e;return t}var Wt=null;function Rs(e){Wt===null?Wt=[e]:Wt.push(e)}function id(e,t,n,r){var l=t.interleaved;return l===null?(n.next=n,Rs(t)):(n.next=l.next,l.next=n),t.interleaved=n,ht(e,r)}function ht(e,t){e.lanes|=t;var n=e.alternate;for(n!==null&&(n.lanes|=t),n=e,e=e.return;e!==null;)e.childLanes|=t,n=e.alternate,n!==null&&(n.childLanes|=t),n=e,e=e.return;return n.tag===3?n.stateNode:null}var St=!1;function Ls(e){e.updateQueue={baseState:e.memoizedState,firstBaseUpdate:null,lastBaseUpdate:null,shared:{pending:null,interleaved:null,lanes:0},effects:null}}function ud(e,t){e=e.updateQueue,t.updateQueue===e&&(t.updateQueue={baseState:e.baseState,firstBaseUpdate:e.firstBaseUpdate,lastBaseUpdate:e.lastBaseUpdate,shared:e.shared,effects:e.effects})}function ft(e,t){return{eventTime:e,lane:t,tag:0,payload:null,callback:null,next:null}}function Mt(e,t,n){var r=e.updateQueue;if(r===null)return null;if(r=r.shared,O&2){var l=r.pending;return l===null?t.next=t:(t.next=l.next,l.next=t),r.pending=t,ht(e,n)}return l=r.interleaved,l===null?(t.next=t,Rs(r)):(t.next=l.next,l.next=t),r.interleaved=t,ht(e,n)}function gl(e,t,n){if(t=t.updateQueue,t!==null&&(t=t.shared,(n&4194240)!==0)){var r=t.lanes;r&=e.pendingLanes,n|=r,t.lanes=n,xs(e,n)}}function du(e,t){var n=e.updateQueue,r=e.alternate;if(r!==null&&(r=r.updateQueue,n===r)){var l=null,o=null;if(n=n.firstBaseUpdate,n!==null){do{var a={eventTime:n.eventTime,lane:n.lane,tag:n.tag,payload:n.payload,callback:n.callback,next:null};o===null?l=o=a:o=o.next=a,n=n.next}while(n!==null);o===null?l=o=t:o=o.next=t}else l=o=t;n={baseState:r.baseState,firstBaseUpdate:l,lastBaseUpdate:o,shared:r.shared,effects:r.effects},e.updateQueue=n;return}e=n.lastBaseUpdate,e===null?n.firstBaseUpdate=t:e.next=t,n.lastBaseUpdate=t}function Hl(e,t,n,r){var l=e.updateQueue;St=!1;var o=l.firstBaseUpdate,a=l.lastBaseUpdate,i=l.shared.pending;if(i!==null){l.shared.pending=null;var u=i,f=u.next;u.next=null,a===null?o=f:a.next=f,a=u;var v=e.alternate;v!==null&&(v=v.updateQueue,i=v.lastBaseUpdate,i!==a&&(i===null?v.firstBaseUpdate=f:i.next=f,v.lastBaseUpdate=u))}if(o!==null){var h=l.baseState;a=0,v=f=u=null,i=o;do{var y=i.lane,k=i.eventTime;if((r&y)===y){v!==null&&(v=v.next={eventTime:k,lane:0,tag:i.tag,payload:i.payload,callback:i.callback,next:null});e:{var j=e,_=i;switch(y=t,k=n,_.tag){case 1:if(j=_.payload,typeof j=="function"){h=j.call(k,h,y);break e}h=j;break e;case 3:j.flags=j.flags&-65537|128;case 0:if(j=_.payload,y=typeof j=="function"?j.call(k,h,y):j,y==null)break e;h=Y({},h,y);break e;case 2:St=!0}}i.callback!==null&&i.lane!==0&&(e.flags|=64,y=l.effects,y===null?l.effects=[i]:y.push(i))}else k={eventTime:k,lane:y,tag:i.tag,payload:i.payload,callback:i.callback,next:null},v===null?(f=v=k,u=h):v=v.next=k,a|=y;if(i=i.next,i===null){if(i=l.shared.pending,i===null)break;y=i,i=y.next,y.next=null,l.lastBaseUpdate=y,l.shared.pending=null}}while(!0);if(v===null&&(u=h),l.baseState=u,l.firstBaseUpdate=f,l.lastBaseUpdate=v,t=l.shared.interleaved,t!==null){l=t;do a|=l.lane,l=l.next;while(l!==t)}else o===null&&(l.shared.lanes=0);Zt|=a,e.lanes=a,e.memoizedState=h}}function fu(e,t,n){if(e=t.effects,t.effects=null,e!==null)for(t=0;tn?n:4,e(!0);var r=qo.transition;qo.transition={};try{e(!1),t()}finally{D=n,qo.transition=r}}function Cd(){return Ue().memoizedState}function Cm(e,t,n){var r=Tt(e);if(n={lane:r,action:n,hasEagerState:!1,eagerState:null,next:null},Ed(e))bd(t,n);else if(n=id(e,t,n,r),n!==null){var l=ve();We(n,e,r,l),Pd(n,t,r)}}function Em(e,t,n){var r=Tt(e),l={lane:r,action:n,hasEagerState:!1,eagerState:null,next:null};if(Ed(e))bd(t,l);else{var o=e.alternate;if(e.lanes===0&&(o===null||o.lanes===0)&&(o=t.lastRenderedReducer,o!==null))try{var a=t.lastRenderedState,i=o(a,n);if(l.hasEagerState=!0,l.eagerState=i,Qe(i,a)){var u=t.interleaved;u===null?(l.next=l,Rs(t)):(l.next=u.next,u.next=l),t.interleaved=l;return}}catch{}finally{}n=id(e,t,l,r),n!==null&&(l=ve(),We(n,e,r,l),Pd(n,t,r))}}function Ed(e){var t=e.alternate;return e===G||t!==null&&t===G}function bd(e,t){cr=Bl=!0;var n=e.pending;n===null?t.next=t:(t.next=n.next,n.next=t),e.pending=t}function Pd(e,t,n){if(n&4194240){var r=t.lanes;r&=e.pendingLanes,n|=r,t.lanes=n,xs(e,n)}}var Vl={readContext:De,useCallback:ce,useContext:ce,useEffect:ce,useImperativeHandle:ce,useInsertionEffect:ce,useLayoutEffect:ce,useMemo:ce,useReducer:ce,useRef:ce,useState:ce,useDebugValue:ce,useDeferredValue:ce,useTransition:ce,useMutableSource:ce,useSyncExternalStore:ce,useId:ce,unstable_isNewReconciler:!1},bm={readContext:De,useCallback:function(e,t){return Je().memoizedState=[e,t===void 0?null:t],e},useContext:De,useEffect:mu,useImperativeHandle:function(e,t,n){return n=n!=null?n.concat([e]):null,wl(4194308,4,Sd.bind(null,t,e),n)},useLayoutEffect:function(e,t){return wl(4194308,4,e,t)},useInsertionEffect:function(e,t){return wl(4,2,e,t)},useMemo:function(e,t){var n=Je();return t=t===void 0?null:t,e=e(),n.memoizedState=[e,t],e},useReducer:function(e,t,n){var r=Je();return t=n!==void 0?n(t):t,r.memoizedState=r.baseState=t,e={pending:null,interleaved:null,lanes:0,dispatch:null,lastRenderedReducer:e,lastRenderedState:t},r.queue=e,e=e.dispatch=Cm.bind(null,G,e),[r.memoizedState,e]},useRef:function(e){var t=Je();return e={current:e},t.memoizedState=e},useState:pu,useDebugValue:As,useDeferredValue:function(e){return Je().memoizedState=e},useTransition:function(){var e=pu(!1),t=e[0];return e=_m.bind(null,e[1]),Je().memoizedState=e,[t,e]},useMutableSource:function(){},useSyncExternalStore:function(e,t,n){var r=G,l=Je();if(W){if(n===void 0)throw Error(S(407));n=n()}else{if(n=t(),ae===null)throw Error(S(349));Jt&30||pd(r,t,n)}l.memoizedState=n;var o={value:n,getSnapshot:t};return l.queue=o,mu(hd.bind(null,r,o,e),[e]),r.flags|=2048,Pr(9,md.bind(null,r,o,n,t),void 0,null),n},useId:function(){var e=Je(),t=ae.identifierPrefix;if(W){var n=dt,r=ct;n=(r&~(1<<32-Ve(r)-1)).toString(32)+n,t=":"+t+"R"+n,n=Er++,0<\/script>",e=e.removeChild(e.firstChild)):typeof r.is=="string"?e=a.createElement(n,{is:r.is}):(e=a.createElement(n),n==="select"&&(a=e,r.multiple?a.multiple=!0:r.size&&(a.size=r.size))):e=a.createElementNS(e,n),e[Ze]=t,e[jr]=r,Ud(e,t,!1,!1),t.stateNode=e;e:{switch(a=_a(n,r),n){case"dialog":B("cancel",e),B("close",e),l=r;break;case"iframe":case"object":case"embed":B("load",e),l=r;break;case"video":case"audio":for(l=0;lIn&&(t.flags|=128,r=!0,Zn(o,!1),t.lanes=4194304)}else{if(!r)if(e=Al(a),e!==null){if(t.flags|=128,r=!0,n=e.updateQueue,n!==null&&(t.updateQueue=n,t.flags|=4),Zn(o,!0),o.tail===null&&o.tailMode==="hidden"&&!a.alternate&&!W)return de(t),null}else 2*ee()-o.renderingStartTime>In&&n!==1073741824&&(t.flags|=128,r=!0,Zn(o,!1),t.lanes=4194304);o.isBackwards?(a.sibling=t.child,t.child=a):(n=o.last,n!==null?n.sibling=a:t.child=a,o.last=a)}return o.tail!==null?(t=o.tail,o.rendering=t,o.tail=t.sibling,o.renderingStartTime=ee(),t.sibling=null,n=K.current,A(K,r?n&1|2:n&1),t):(de(t),null);case 22:case 23:return Gs(),r=t.memoizedState!==null,e!==null&&e.memoizedState!==null!==r&&(t.flags|=8192),r&&t.mode&1?Ee&1073741824&&(de(t),t.subtreeFlags&6&&(t.flags|=8192)):de(t),null;case 24:return null;case 25:return null}throw Error(S(156,t.tag))}function Om(e,t){switch(bs(t),t.tag){case 1:return Ne(t.type)&&Fl(),e=t.flags,e&65536?(t.flags=e&-65537|128,t):null;case 3:return Fn(),V(ke),V(pe),Is(),e=t.flags,e&65536&&!(e&128)?(t.flags=e&-65537|128,t):null;case 5:return Os(t),null;case 13:if(V(K),e=t.memoizedState,e!==null&&e.dehydrated!==null){if(t.alternate===null)throw Error(S(340));Rn()}return e=t.flags,e&65536?(t.flags=e&-65537|128,t):null;case 19:return V(K),null;case 4:return Fn(),null;case 10:return Ts(t.type._context),null;case 22:case 23:return Gs(),null;case 24:return null;default:return null}}var ul=!1,fe=!1,Im=typeof WeakSet=="function"?WeakSet:Set,P=null;function kn(e,t){var n=e.ref;if(n!==null)if(typeof n=="function")try{n(null)}catch(r){J(e,t,r)}else n.current=null}function Za(e,t,n){try{n()}catch(r){J(e,t,r)}}var _u=!1;function Dm(e,t){if(Fa=zl,e=Qc(),Cs(e)){if("selectionStart"in e)var n={start:e.selectionStart,end:e.selectionEnd};else e:{n=(n=e.ownerDocument)&&n.defaultView||window;var r=n.getSelection&&n.getSelection();if(r&&r.rangeCount!==0){n=r.anchorNode;var l=r.anchorOffset,o=r.focusNode;r=r.focusOffset;try{n.nodeType,o.nodeType}catch{n=null;break e}var a=0,i=-1,u=-1,f=0,v=0,h=e,y=null;t:for(;;){for(var k;h!==n||l!==0&&h.nodeType!==3||(i=a+l),h!==o||r!==0&&h.nodeType!==3||(u=a+r),h.nodeType===3&&(a+=h.nodeValue.length),(k=h.firstChild)!==null;)y=h,h=k;for(;;){if(h===e)break t;if(y===n&&++f===l&&(i=a),y===o&&++v===r&&(u=a),(k=h.nextSibling)!==null)break;h=y,y=h.parentNode}h=k}n=i===-1||u===-1?null:{start:i,end:u}}else n=null}n=n||{start:0,end:0}}else n=null;for(Oa={focusedElem:e,selectionRange:n},zl=!1,P=t;P!==null;)if(t=P,e=t.child,(t.subtreeFlags&1028)!==0&&e!==null)e.return=t,P=e;else for(;P!==null;){t=P;try{var j=t.alternate;if(t.flags&1024)switch(t.tag){case 0:case 11:case 15:break;case 1:if(j!==null){var _=j.memoizedProps,$=j.memoizedState,p=t.stateNode,d=p.getSnapshotBeforeUpdate(t.elementType===t.type?_:He(t.type,_),$);p.__reactInternalSnapshotBeforeUpdate=d}break;case 3:var m=t.stateNode.containerInfo;m.nodeType===1?m.textContent="":m.nodeType===9&&m.documentElement&&m.removeChild(m.documentElement);break;case 5:case 6:case 4:case 17:break;default:throw Error(S(163))}}catch(g){J(t,t.return,g)}if(e=t.sibling,e!==null){e.return=t.return,P=e;break}P=t.return}return j=_u,_u=!1,j}function dr(e,t,n){var r=t.updateQueue;if(r=r!==null?r.lastEffect:null,r!==null){var l=r=r.next;do{if((l.tag&e)===e){var o=l.destroy;l.destroy=void 0,o!==void 0&&Za(t,n,o)}l=l.next}while(l!==r)}}function ao(e,t){if(t=t.updateQueue,t=t!==null?t.lastEffect:null,t!==null){var n=t=t.next;do{if((n.tag&e)===e){var r=n.create;n.destroy=r()}n=n.next}while(n!==t)}}function qa(e){var t=e.ref;if(t!==null){var n=e.stateNode;switch(e.tag){case 5:e=n;break;default:e=n}typeof t=="function"?t(e):t.current=e}}function Ad(e){var t=e.alternate;t!==null&&(e.alternate=null,Ad(t)),e.child=null,e.deletions=null,e.sibling=null,e.tag===5&&(t=e.stateNode,t!==null&&(delete t[Ze],delete t[jr],delete t[Ua],delete t[wm],delete t[Sm])),e.stateNode=null,e.return=null,e.dependencies=null,e.memoizedProps=null,e.memoizedState=null,e.pendingProps=null,e.stateNode=null,e.updateQueue=null}function Bd(e){return e.tag===5||e.tag===3||e.tag===4}function Cu(e){e:for(;;){for(;e.sibling===null;){if(e.return===null||Bd(e.return))return null;e=e.return}for(e.sibling.return=e.return,e=e.sibling;e.tag!==5&&e.tag!==6&&e.tag!==18;){if(e.flags&2||e.child===null||e.tag===4)continue e;e.child.return=e,e=e.child}if(!(e.flags&2))return e.stateNode}}function es(e,t,n){var r=e.tag;if(r===5||r===6)e=e.stateNode,t?n.nodeType===8?n.parentNode.insertBefore(e,t):n.insertBefore(e,t):(n.nodeType===8?(t=n.parentNode,t.insertBefore(e,n)):(t=n,t.appendChild(e)),n=n._reactRootContainer,n!=null||t.onclick!==null||(t.onclick=Ll));else if(r!==4&&(e=e.child,e!==null))for(es(e,t,n),e=e.sibling;e!==null;)es(e,t,n),e=e.sibling}function ts(e,t,n){var r=e.tag;if(r===5||r===6)e=e.stateNode,t?n.insertBefore(e,t):n.appendChild(e);else if(r!==4&&(e=e.child,e!==null))for(ts(e,t,n),e=e.sibling;e!==null;)ts(e,t,n),e=e.sibling}var se=null,Ae=!1;function xt(e,t,n){for(n=n.child;n!==null;)Vd(e,t,n),n=n.sibling}function Vd(e,t,n){if(qe&&typeof qe.onCommitFiberUnmount=="function")try{qe.onCommitFiberUnmount(Zl,n)}catch{}switch(n.tag){case 5:fe||kn(n,t);case 6:var r=se,l=Ae;se=null,xt(e,t,n),se=r,Ae=l,se!==null&&(Ae?(e=se,n=n.stateNode,e.nodeType===8?e.parentNode.removeChild(n):e.removeChild(n)):se.removeChild(n.stateNode));break;case 18:se!==null&&(Ae?(e=se,n=n.stateNode,e.nodeType===8?Xo(e.parentNode,n):e.nodeType===1&&Xo(e,n),xr(e)):Xo(se,n.stateNode));break;case 4:r=se,l=Ae,se=n.stateNode.containerInfo,Ae=!0,xt(e,t,n),se=r,Ae=l;break;case 0:case 11:case 14:case 15:if(!fe&&(r=n.updateQueue,r!==null&&(r=r.lastEffect,r!==null))){l=r=r.next;do{var o=l,a=o.destroy;o=o.tag,a!==void 0&&(o&2||o&4)&&Za(n,t,a),l=l.next}while(l!==r)}xt(e,t,n);break;case 1:if(!fe&&(kn(n,t),r=n.stateNode,typeof r.componentWillUnmount=="function"))try{r.props=n.memoizedProps,r.state=n.memoizedState,r.componentWillUnmount()}catch(i){J(n,t,i)}xt(e,t,n);break;case 21:xt(e,t,n);break;case 22:n.mode&1?(fe=(r=fe)||n.memoizedState!==null,xt(e,t,n),fe=r):xt(e,t,n);break;default:xt(e,t,n)}}function Eu(e){var t=e.updateQueue;if(t!==null){e.updateQueue=null;var n=e.stateNode;n===null&&(n=e.stateNode=new Im),t.forEach(function(r){var l=Km.bind(null,e,r);n.has(r)||(n.add(r),r.then(l,l))})}}function $e(e,t){var n=t.deletions;if(n!==null)for(var r=0;rl&&(l=a),r&=~o}if(r=l,r=ee()-r,r=(120>r?120:480>r?480:1080>r?1080:1920>r?1920:3e3>r?3e3:4320>r?4320:1960*$m(r/1960))-r,10e?16:e,_t===null)var r=!1;else{if(e=_t,_t=null,Kl=0,O&6)throw Error(S(331));var l=O;for(O|=4,P=e.current;P!==null;){var o=P,a=o.child;if(P.flags&16){var i=o.deletions;if(i!==null){for(var u=0;uee()-Qs?Kt(e,0):Ws|=n),je(e,t)}function Zd(e,t){t===0&&(e.mode&1?(t=el,el<<=1,!(el&130023424)&&(el=4194304)):t=1);var n=ve();e=ht(e,t),e!==null&&(Tr(e,t,n),je(e,n))}function Qm(e){var t=e.memoizedState,n=0;t!==null&&(n=t.retryLane),Zd(e,n)}function Km(e,t){var n=0;switch(e.tag){case 13:var r=e.stateNode,l=e.memoizedState;l!==null&&(n=l.retryLane);break;case 19:r=e.stateNode;break;default:throw Error(S(314))}r!==null&&r.delete(t),Zd(e,n)}var qd;qd=function(e,t,n){if(e!==null)if(e.memoizedProps!==t.pendingProps||ke.current)Se=!0;else{if(!(e.lanes&n)&&!(t.flags&128))return Se=!1,Lm(e,t,n);Se=!!(e.flags&131072)}else Se=!1,W&&t.flags&1048576&&rd(t,Dl,t.index);switch(t.lanes=0,t.tag){case 2:var r=t.type;Sl(e,t),e=t.pendingProps;var l=Tn(t,pe.current);bn(t,n),l=Us(null,t,r,e,l,n);var o=$s();return t.flags|=1,typeof l=="object"&&l!==null&&typeof l.render=="function"&&l.$$typeof===void 0?(t.tag=1,t.memoizedState=null,t.updateQueue=null,Ne(r)?(o=!0,Ol(t)):o=!1,t.memoizedState=l.state!==null&&l.state!==void 0?l.state:null,Ls(t),l.updater=oo,t.stateNode=l,l._reactInternals=t,Wa(t,r,e,n),t=Ga(null,t,r,!0,o,n)):(t.tag=0,W&&o&&Es(t),he(null,t,l,n),t=t.child),t;case 16:r=t.elementType;e:{switch(Sl(e,t),e=t.pendingProps,l=r._init,r=l(r._payload),t.type=r,l=t.tag=Ym(r),e=He(r,e),l){case 0:t=Ka(null,t,r,e,n);break e;case 1:t=ku(null,t,r,e,n);break e;case 11:t=wu(null,t,r,e,n);break e;case 14:t=Su(null,t,r,He(r.type,e),n);break e}throw Error(S(306,r,""))}return t;case 0:return r=t.type,l=t.pendingProps,l=t.elementType===r?l:He(r,l),Ka(e,t,r,l,n);case 1:return r=t.type,l=t.pendingProps,l=t.elementType===r?l:He(r,l),ku(e,t,r,l,n);case 3:e:{if(Od(t),e===null)throw Error(S(387));r=t.pendingProps,o=t.memoizedState,l=o.element,ud(e,t),Hl(t,r,null,n);var a=t.memoizedState;if(r=a.element,o.isDehydrated)if(o={element:r,isDehydrated:!1,cache:a.cache,pendingSuspenseBoundaries:a.pendingSuspenseBoundaries,transitions:a.transitions},t.updateQueue.baseState=o,t.memoizedState=o,t.flags&256){l=On(Error(S(423)),t),t=Nu(e,t,r,n,l);break e}else if(r!==l){l=On(Error(S(424)),t),t=Nu(e,t,r,n,l);break e}else for(be=Pt(t.stateNode.containerInfo.firstChild),Pe=t,W=!0,Be=null,n=sd(t,null,r,n),t.child=n;n;)n.flags=n.flags&-3|4096,n=n.sibling;else{if(Rn(),r===l){t=vt(e,t,n);break e}he(e,t,r,n)}t=t.child}return t;case 5:return cd(t),e===null&&Aa(t),r=t.type,l=t.pendingProps,o=e!==null?e.memoizedProps:null,a=l.children,Ia(r,l)?a=null:o!==null&&Ia(r,o)&&(t.flags|=32),Fd(e,t),he(e,t,a,n),t.child;case 6:return e===null&&Aa(t),null;case 13:return Id(e,t,n);case 4:return Fs(t,t.stateNode.containerInfo),r=t.pendingProps,e===null?t.child=Ln(t,null,r,n):he(e,t,r,n),t.child;case 11:return r=t.type,l=t.pendingProps,l=t.elementType===r?l:He(r,l),wu(e,t,r,l,n);case 7:return he(e,t,t.pendingProps,n),t.child;case 8:return he(e,t,t.pendingProps.children,n),t.child;case 12:return he(e,t,t.pendingProps.children,n),t.child;case 10:e:{if(r=t.type._context,l=t.pendingProps,o=t.memoizedProps,a=l.value,A(Ul,r._currentValue),r._currentValue=a,o!==null)if(Qe(o.value,a)){if(o.children===l.children&&!ke.current){t=vt(e,t,n);break e}}else for(o=t.child,o!==null&&(o.return=t);o!==null;){var i=o.dependencies;if(i!==null){a=o.child;for(var u=i.firstContext;u!==null;){if(u.context===r){if(o.tag===1){u=ft(-1,n&-n),u.tag=2;var f=o.updateQueue;if(f!==null){f=f.shared;var v=f.pending;v===null?u.next=u:(u.next=v.next,v.next=u),f.pending=u}}o.lanes|=n,u=o.alternate,u!==null&&(u.lanes|=n),Ba(o.return,n,t),i.lanes|=n;break}u=u.next}}else if(o.tag===10)a=o.type===t.type?null:o.child;else if(o.tag===18){if(a=o.return,a===null)throw Error(S(341));a.lanes|=n,i=a.alternate,i!==null&&(i.lanes|=n),Ba(a,n,t),a=o.sibling}else a=o.child;if(a!==null)a.return=o;else for(a=o;a!==null;){if(a===t){a=null;break}if(o=a.sibling,o!==null){o.return=a.return,a=o;break}a=a.return}o=a}he(e,t,l.children,n),t=t.child}return t;case 9:return l=t.type,r=t.pendingProps.children,bn(t,n),l=De(l),r=r(l),t.flags|=1,he(e,t,r,n),t.child;case 14:return r=t.type,l=He(r,t.pendingProps),l=He(r.type,l),Su(e,t,r,l,n);case 15:return Rd(e,t,t.type,t.pendingProps,n);case 17:return r=t.type,l=t.pendingProps,l=t.elementType===r?l:He(r,l),Sl(e,t),t.tag=1,Ne(r)?(e=!0,Ol(t)):e=!1,bn(t,n),Md(t,r,l),Wa(t,r,l,n),Ga(null,t,r,!0,e,n);case 19:return Dd(e,t,n);case 22:return Ld(e,t,n)}throw Error(S(156,t.tag))};function ef(e,t){return Ec(e,t)}function Gm(e,t,n,r){this.tag=e,this.key=n,this.sibling=this.child=this.return=this.stateNode=this.type=this.elementType=null,this.index=0,this.ref=null,this.pendingProps=t,this.dependencies=this.memoizedState=this.updateQueue=this.memoizedProps=null,this.mode=r,this.subtreeFlags=this.flags=0,this.deletions=null,this.childLanes=this.lanes=0,this.alternate=null}function Oe(e,t,n,r){return new Gm(e,t,n,r)}function Xs(e){return e=e.prototype,!(!e||!e.isReactComponent)}function Ym(e){if(typeof e=="function")return Xs(e)?1:0;if(e!=null){if(e=e.$$typeof,e===hs)return 11;if(e===vs)return 14}return 2}function Rt(e,t){var n=e.alternate;return n===null?(n=Oe(e.tag,t,e.key,e.mode),n.elementType=e.elementType,n.type=e.type,n.stateNode=e.stateNode,n.alternate=e,e.alternate=n):(n.pendingProps=t,n.type=e.type,n.flags=0,n.subtreeFlags=0,n.deletions=null),n.flags=e.flags&14680064,n.childLanes=e.childLanes,n.lanes=e.lanes,n.child=e.child,n.memoizedProps=e.memoizedProps,n.memoizedState=e.memoizedState,n.updateQueue=e.updateQueue,t=e.dependencies,n.dependencies=t===null?null:{lanes:t.lanes,firstContext:t.firstContext},n.sibling=e.sibling,n.index=e.index,n.ref=e.ref,n}function jl(e,t,n,r,l,o){var a=2;if(r=e,typeof e=="function")Xs(e)&&(a=1);else if(typeof e=="string")a=5;else e:switch(e){case pn:return Gt(n.children,l,o,t);case ms:a=8,l|=8;break;case ha:return e=Oe(12,n,t,l|2),e.elementType=ha,e.lanes=o,e;case va:return e=Oe(13,n,t,l),e.elementType=va,e.lanes=o,e;case ya:return e=Oe(19,n,t,l),e.elementType=ya,e.lanes=o,e;case cc:return io(n,l,o,t);default:if(typeof e=="object"&&e!==null)switch(e.$$typeof){case ic:a=10;break e;case uc:a=9;break e;case hs:a=11;break e;case vs:a=14;break e;case wt:a=16,r=null;break e}throw Error(S(130,e==null?e:typeof e,""))}return t=Oe(a,n,t,l),t.elementType=e,t.type=r,t.lanes=o,t}function Gt(e,t,n,r){return e=Oe(7,e,r,t),e.lanes=n,e}function io(e,t,n,r){return e=Oe(22,e,r,t),e.elementType=cc,e.lanes=n,e.stateNode={isHidden:!1},e}function la(e,t,n){return e=Oe(6,e,null,t),e.lanes=n,e}function oa(e,t,n){return t=Oe(4,e.children!==null?e.children:[],e.key,t),t.lanes=n,t.stateNode={containerInfo:e.containerInfo,pendingChildren:null,implementation:e.implementation},t}function Xm(e,t,n,r,l){this.tag=t,this.containerInfo=e,this.finishedWork=this.pingCache=this.current=this.pendingChildren=null,this.timeoutHandle=-1,this.callbackNode=this.pendingContext=this.context=null,this.callbackPriority=0,this.eventTimes=Uo(0),this.expirationTimes=Uo(-1),this.entangledLanes=this.finishedLanes=this.mutableReadLanes=this.expiredLanes=this.pingedLanes=this.suspendedLanes=this.pendingLanes=0,this.entanglements=Uo(0),this.identifierPrefix=r,this.onRecoverableError=l,this.mutableSourceEagerHydrationData=null}function Js(e,t,n,r,l,o,a,i,u){return e=new Xm(e,t,n,i,u),t===1?(t=1,o===!0&&(t|=8)):t=0,o=Oe(3,null,null,t),e.current=o,o.stateNode=e,o.memoizedState={element:r,isDehydrated:n,cache:null,transitions:null,pendingSuspenseBoundaries:null},Ls(o),e}function Jm(e,t,n){var r=3"u"||typeof __REACT_DEVTOOLS_GLOBAL_HOOK__.checkDCE!="function"))try{__REACT_DEVTOOLS_GLOBAL_HOOK__.checkDCE(lf)}catch(e){console.error(e)}}lf(),lc.exports=ze;var nh=lc.exports,Fu=nh;pa.createRoot=Fu.createRoot,pa.hydrateRoot=Fu.hydrateRoot;/** - * @license lucide-react v0.460.0 - ISC - * - * This source code is licensed under the ISC license. - * See the LICENSE file in the root directory of this source tree. - */const rh=e=>e.replace(/([a-z0-9])([A-Z])/g,"$1-$2").toLowerCase(),of=(...e)=>e.filter((t,n,r)=>!!t&&t.trim()!==""&&r.indexOf(t)===n).join(" ").trim();/** - * @license lucide-react v0.460.0 - ISC - * - * This source code is licensed under the ISC license. - * See the LICENSE file in the root directory of this source tree. - */var lh={xmlns:"http://www.w3.org/2000/svg",width:24,height:24,viewBox:"0 0 24 24",fill:"none",stroke:"currentColor",strokeWidth:2,strokeLinecap:"round",strokeLinejoin:"round"};/** - * @license lucide-react v0.460.0 - ISC - * - * This source code is licensed under the ISC license. - * See the LICENSE file in the root directory of this source tree. - */const oh=w.forwardRef(({color:e="currentColor",size:t=24,strokeWidth:n=2,absoluteStrokeWidth:r,className:l="",children:o,iconNode:a,...i},u)=>w.createElement("svg",{ref:u,...lh,width:t,height:t,stroke:e,strokeWidth:r?Number(n)*24/Number(t):n,className:of("lucide",l),...i},[...a.map(([f,v])=>w.createElement(f,v)),...Array.isArray(o)?o:[o]]));/** - * @license lucide-react v0.460.0 - ISC - * - * This source code is licensed under the ISC license. - * See the LICENSE file in the root directory of this source tree. - */const Re=(e,t)=>{const n=w.forwardRef(({className:r,...l},o)=>w.createElement(oh,{ref:o,iconNode:t,className:of(`lucide-${rh(e)}`,r),...l}));return n.displayName=`${e}`,n};/** - * @license lucide-react v0.460.0 - ISC - * - * This source code is licensed under the ISC license. - * See the LICENSE file in the root directory of this source tree. - */const er=Re("ChevronDown",[["path",{d:"m6 9 6 6 6-6",key:"qrunsl"}]]);/** - * @license lucide-react v0.460.0 - ISC - * - * This source code is licensed under the ISC license. - * See the LICENSE file in the root directory of this source tree. - */const ah=Re("Database",[["ellipse",{cx:"12",cy:"5",rx:"9",ry:"3",key:"msslwz"}],["path",{d:"M3 5V19A9 3 0 0 0 21 19V5",key:"1wlel7"}],["path",{d:"M3 12A9 3 0 0 0 21 12",key:"mv7ke4"}]]);/** - * @license lucide-react v0.460.0 - ISC - * - * This source code is licensed under the ISC license. - * See the LICENSE file in the root directory of this source tree. - */const sh=Re("FlaskConical",[["path",{d:"M10 2v7.527a2 2 0 0 1-.211.896L4.72 20.55a1 1 0 0 0 .9 1.45h12.76a1 1 0 0 0 .9-1.45l-5.069-10.127A2 2 0 0 1 14 9.527V2",key:"pzvekw"}],["path",{d:"M8.5 2h7",key:"csnxdl"}],["path",{d:"M7 16h10",key:"wp8him"}]]);/** - * @license lucide-react v0.460.0 - ISC - * - * This source code is licensed under the ISC license. - * See the LICENSE file in the root directory of this source tree. - */const Ou=Re("Grid3x3",[["rect",{width:"18",height:"18",x:"3",y:"3",rx:"2",key:"afitv7"}],["path",{d:"M3 9h18",key:"1pudct"}],["path",{d:"M3 15h18",key:"5xshup"}],["path",{d:"M9 3v18",key:"fh3hqa"}],["path",{d:"M15 3v18",key:"14nvp0"}]]);/** - * @license lucide-react v0.460.0 - ISC - * - * This source code is licensed under the ISC license. - * See the LICENSE file in the root directory of this source tree. - */const ih=Re("History",[["path",{d:"M3 12a9 9 0 1 0 9-9 9.75 9.75 0 0 0-6.74 2.74L3 8",key:"1357e3"}],["path",{d:"M3 3v5h5",key:"1xhq8a"}],["path",{d:"M12 7v5l4 2",key:"1fdv2h"}]]);/** - * @license lucide-react v0.460.0 - ISC - * - * This source code is licensed under the ISC license. - * See the LICENSE file in the root directory of this source tree. - */const uh=Re("House",[["path",{d:"M15 21v-8a1 1 0 0 0-1-1h-4a1 1 0 0 0-1 1v8",key:"5wwlr5"}],["path",{d:"M3 10a2 2 0 0 1 .709-1.528l7-5.999a2 2 0 0 1 2.582 0l7 5.999A2 2 0 0 1 21 10v9a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2z",key:"1d0kgt"}]]);/** - * @license lucide-react v0.460.0 - ISC - * - * This source code is licensed under the ISC license. - * See the LICENSE file in the root directory of this source tree. - */const aa=Re("LoaderCircle",[["path",{d:"M21 12a9 9 0 1 1-6.219-8.56",key:"13zald"}]]);/** - * @license lucide-react v0.460.0 - ISC - * - * This source code is licensed under the ISC license. - * See the LICENSE file in the root directory of this source tree. - */const ch=Re("RefreshCw",[["path",{d:"M3 12a9 9 0 0 1 9-9 9.75 9.75 0 0 1 6.74 2.74L21 8",key:"v9h5vc"}],["path",{d:"M21 3v5h-5",key:"1q7to0"}],["path",{d:"M21 12a9 9 0 0 1-9 9 9.75 9.75 0 0 1-6.74-2.74L3 16",key:"3uifl3"}],["path",{d:"M8 16H3v5",key:"1cv678"}]]);/** - * @license lucide-react v0.460.0 - ISC - * - * This source code is licensed under the ISC license. - * See the LICENSE file in the root directory of this source tree. - */const Iu=Re("RotateCcw",[["path",{d:"M3 12a9 9 0 1 0 9-9 9.75 9.75 0 0 0-6.74 2.74L3 8",key:"1357e3"}],["path",{d:"M3 3v5h5",key:"1xhq8a"}]]);/** - * @license lucide-react v0.460.0 - ISC - * - * This source code is licensed under the ISC license. - * See the LICENSE file in the root directory of this source tree. - */const Du=Re("Trash2",[["path",{d:"M3 6h18",key:"d0wm0j"}],["path",{d:"M19 6v14c0 1-1 2-2 2H7c-1 0-2-1-2-2V6",key:"4alrt4"}],["path",{d:"M8 6V4c0-1 1-2 2-2h4c1 0 2 1 2 2v2",key:"v07s0e"}],["line",{x1:"10",x2:"10",y1:"11",y2:"17",key:"1uufr5"}],["line",{x1:"14",x2:"14",y1:"11",y2:"17",key:"xtxkd"}]]);/** - * @license lucide-react v0.460.0 - ISC - * - * This source code is licensed under the ISC license. - * See the LICENSE file in the root directory of this source tree. - */const dh=Re("Upload",[["path",{d:"M21 15v4a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2v-4",key:"ih7n3h"}],["polyline",{points:"17 8 12 3 7 8",key:"t8dd8p"}],["line",{x1:"12",x2:"12",y1:"3",y2:"15",key:"widbto"}]]);/** - * @license lucide-react v0.460.0 - ISC - * - * This source code is licensed under the ISC license. - * See the LICENSE file in the root directory of this source tree. - */const Uu=Re("ZoomIn",[["circle",{cx:"11",cy:"11",r:"8",key:"4ej97u"}],["line",{x1:"21",x2:"16.65",y1:"21",y2:"16.65",key:"13gj7c"}],["line",{x1:"11",x2:"11",y1:"8",y2:"14",key:"1vmskp"}],["line",{x1:"8",x2:"14",y1:"11",y2:"11",key:"durymu"}]]);/** - * @license lucide-react v0.460.0 - ISC - * - * This source code is licensed under the ISC license. - * See the LICENSE file in the root directory of this source tree. - */const $u=Re("ZoomOut",[["circle",{cx:"11",cy:"11",r:"8",key:"4ej97u"}],["line",{x1:"21",x2:"16.65",y1:"21",y2:"16.65",key:"13gj7c"}],["line",{x1:"8",x2:"14",y1:"11",y2:"11",key:"durymu"}]]),X="/api";async function tt(e){if((e.headers.get("content-type")||"").includes("application/json"))return e.json();const n=await e.text();throw new Error(n||`HTTP ${e.status}`)}async function fh(){const e=await fetch(`${X}/analysis/uploads`);if(!e.ok)throw new Error(await e.text());return e.json()}async function ph(e,t){const n=new URLSearchParams;t!=null&&t.deleteExperiments&&n.set("delete_experiments","true");const r=n.toString(),l=await fetch(`${X}/analysis/uploads/${encodeURIComponent(e)}${r?`?${r}`:""}`,{method:"DELETE"});if(!l.ok)throw new Error(await l.text());return await l.json().catch(()=>({}))}async function mh(e,t="analysis_upload"){const n=new FormData;n.append("file",e),n.append("source",t);const r=await fetch(`${X}/analysis/upload`,{method:"POST",body:n}),l=await tt(r);if(!r.ok)throw new Error(String(l.detail||r.status));return l}async function hh(e){const t=await fetch(`${X}/analysis/uploads/import_from_debug`,{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({filename:e})}),n=await tt(t);if(!t.ok)throw new Error(String(n.detail||t.status));return n}async function vh(e,t){const n=await fetch(`${X}/analysis/solve/image`,{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({input_name:e,...t})}),r=await tt(n);if(!n.ok)throw new Error(String(r.detail||n.status));return r}async function yh(e,t){const n=await fetch(`${X}/analysis/solve/batch`,{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({input_name:e,runs:t})}),r=await tt(n);if(!n.ok)throw new Error(String(r.detail||n.status));return r}async function sa(e){const t=await fetch(`${X}/analysis/presets?scope=${e}`);if(!t.ok)throw new Error(await t.text());return t.json()}async function gh(e,t){const n=await fetch(`${X}/analysis/presets`,{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({name:e,params:t})}),r=await tt(n);if(!n.ok)throw new Error(String(r.detail||n.status));return r}async function ia(e){const t=await fetch(`${X}/analysis/experiments`,{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify(e)}),n=await tt(t);if(!t.ok)throw new Error(String(n.detail||t.status));return n}async function xh(e){const t=await fetch(`${X}/analysis/experiments/${encodeURIComponent(e)}`,{method:"DELETE"});if(!t.ok)throw new Error(await t.text())}async function ua(e,t,n=30){const r=new URLSearchParams({page:String(t),page_size:String(n)});e&&r.set("q",e);const l=await fetch(`${X}/analysis/experiments?${r}`);if(!l.ok)throw new Error(await l.text());return l.json()}async function wh(){const e=await fetch(`${X}/debug/files`);if(!e.ok)throw new Error(await e.text());return e.json()}function Sh(e){return`${X}/debug/files/${encodeURIComponent(e)}`}async function Hu(e){const t=await fetch(`${X}/debug/files/${encodeURIComponent(e)}/info`),n=await tt(t);if(!t.ok)throw new Error(String(n.detail||t.status));return n}async function kh(e){const t=await fetch(`${X}/analysis/uploads/${encodeURIComponent(e)}/info`),n=await tt(t);if(!t.ok)throw new Error(String(n.detail||t.status));return n}function Nh(e){return`${X}/analysis/uploads/file?filename=${encodeURIComponent(e)}`}async function Au(e){const t=await fetch(`${X}/analysis/experiments/export?format=${e}`);if(!t.ok)throw new Error(await t.text());return t.text()}async function jh(e){const t=await fetch(`${X}/analysis/uploads/${encodeURIComponent(e)}/experiment_count`);if(!t.ok)throw new Error(await t.text());return t.json()}async function _h(){const e=await fetch(`${X}/analysis/settings`);if(!e.ok)throw new Error(await e.text());return e.json()}async function Ch(e){const t=await fetch(`${X}/analysis/solve/frame`,{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify(e)}),n=await tt(t);if(!t.ok)throw new Error(String(n.detail||t.status));return n}async function Eh(e,t){const n=new FormData;n.append("file",e,"frame.jpg"),n.append("payload",JSON.stringify(t));const r=await fetch(`${X}/analysis/solve/frame_upload`,{method:"POST",body:n}),l=await tt(r);if(!r.ok)throw new Error(String(l.detail||r.status));return l}function bh(e){return`${X}/analysis/experiments/${encodeURIComponent(e)}/asset`}async function Ph(){const e=await fetch(`${X}/system/info`);if(!e.ok)throw new Error(await e.text());return e.json()}function af(e,t,n,r,l){var i,u,f,v;if(!r)return;if(e.clearRect(0,0,t,n),l.all&&Array.isArray(r.stars_all_centroids)){e.fillStyle="rgba(156, 163, 175, 0.85)";for(const h of r.stars_all_centroids)e.beginPath(),e.arc(h.x,h.y,2.4,0,Math.PI*2),e.fill()}if(l.pattern&&Array.isArray(r.stars_pattern)){e.strokeStyle="rgba(251, 146, 60, 0.95)",e.lineWidth=2;for(const h of r.stars_pattern)e.beginPath(),e.arc(h.x,h.y,6,0,Math.PI*2),e.stroke()}if(l.matched&&Array.isArray(r.stars_matched)){e.strokeStyle="rgba(34, 197, 94, 0.95)",e.fillStyle="rgba(34, 197, 94, 0.95)",e.lineWidth=2,e.font="11px system-ui, sans-serif";for(const h of r.stars_matched)e.beginPath(),e.arc(h.x,h.y,7,0,Math.PI*2),e.stroke(),h.mag!=null&&e.fillText(`m${Number(h.mag).toFixed(1)}`,h.x+4,h.y-4)}const o=r.overlay_ext;if(Array.isArray(o==null?void 0:o.labels_topn)&&o.labels_topn.length>0){e.fillStyle="rgba(96, 165, 250, 0.95)",e.font="12px system-ui, sans-serif";for(const h of o.labels_topn){if(typeof(h==null?void 0:h.x)!="number"||typeof(h==null?void 0:h.y)!="number")continue;const y=typeof h.mag=="number"?` m${h.mag.toFixed(1)}`:"",k=`${h.name??"Star"}${y}`;e.fillText(k,h.x+8,h.y+14)}}const a=o==null?void 0:o.polar_guide;if(a&&typeof((i=a.frame_center)==null?void 0:i.x)=="number"&&typeof((u=a.frame_center)==null?void 0:u.y)=="number"&&typeof((f=a.target)==null?void 0:f.x)=="number"&&typeof((v=a.target)==null?void 0:v.y)=="number"){const h=a.frame_center.x,y=a.frame_center.y,k=a.target.x,j=a.target.y;e.strokeStyle="rgba(244, 63, 94, 0.95)",e.fillStyle="rgba(244, 63, 94, 0.95)",e.lineWidth=2,e.beginPath(),e.moveTo(h,y),e.lineTo(k,j),e.stroke(),e.beginPath(),e.arc(k,j,6,0,Math.PI*2),e.stroke();const _=typeof a.angular_sep_deg=="number"?a.angular_sep_deg.toFixed(2):"-";e.font="12px system-ui, sans-serif",e.fillText(`Polar ${_}deg`,k+10,j-6)}}function Bu(e,t,n,r){if(!n)return;const l=t.naturalWidth||1,o=t.naturalHeight||1;e.width=l,e.height=o,e.style.width=`${t.clientWidth}px`,e.style.height=`${t.clientHeight}px`;const a=e.getContext("2d");a&&af(a,l,o,n,r)}function Mh(e,t,n,r){if(!n)return;const l=t.videoWidth||1,o=t.videoHeight||1;if(l<2||o<2)return;e.width=l,e.height=o,e.style.width=`${t.clientWidth}px`,e.style.height=`${t.clientHeight}px`;const a=e.getContext("2d");a&&af(a,l,o,n,r)}const zh={"app.title":"OGScope Plate Solve Console","nav.lab":"Lab","nav.labImage":"Image solve","nav.labVideo":"Video solve","delete.uploadCascade":"Delete {n} linked experiment record(s) as well?","lab.solveCurrentFrame":"Solve current frame (file)","lab.solveFileStart":"Start continuous file solve","lab.solveFileStop":"Stop continuous file solve","lab.cameraPreviewLoading":"Connecting to shared preview…","lab.solveCameraFrame":"Solve live camera frame","lab.solveCameraStart":"Start camera solve","lab.solveCameraStop":"Stop camera solve","lab.videoPreviewFailed":"This video format may be unsupported by the browser. Try MP4 (H.264) or WebM.","lab.previewModeFile":"Pool file","lab.previewModeCamera":"Device camera","lab.videoLiveIntro":"Shares the same camera as Camera Debug — preview and solve live frames here without opening debug. Both pages can run together.","lab.cameraSnapshotName":"ogscope_camera_live","lab.metric.probRaw":"Raw Prob","lab.systemLoad":"System load","results.saveBatchAll":"Save all to records","nav.pool":"Assets","nav.history":"Records","nav.cameraDebug":"Camera Debug","nav.home":"Home","lang.zh":"中文","lang.en":"EN","sidebar.assets":"Uploaded assets","sidebar.upload":"Upload","sidebar.refresh":"Refresh","sidebar.debugCaptures":"Debug console media","sidebar.assetTypeImage":"Image","sidebar.assetTypeVideo":"Video","sidebar.debugEmpty":"No debug files","sidebar.importToPool":"Import to pool","sidebar.debugPage":"Page {cur} / {total}","sidebar.batchPresets":"Batch presets","sidebar.batchHint":"Check presets, then use Batch solve to compare multiple param sets.","lab.selectOrUpload":"Pick an uploaded or imported asset from the left","lab.selectOrUploadVideo":"Pick a pool video to preview, or use the button above for the live camera frame.","lab.file":"File","lab.source":"Source","lab.layers":"Layers","lab.layer.matched":"Matched","lab.layer.pattern":"Pattern","lab.layer.all":"All centroids","lab.grid":"Grid","lab.zoomIn":"Zoom in","lab.zoomOut":"Zoom out","lab.zoomReset":"Reset","lab.resolution":"Resolution","lab.fwhm":"FWHM","lab.starsDetected":"Stars detected","lab.meta.title":"Capture & file info","lab.meta.noSidecar":"No sidecar (not from debug capture)","lab.meta.partial":"No detailed sidecar; file info only.","lab.solveSection":"Solve","lab.imageSection":"Image","lab.metric.solveMs":"Time","lab.metric.solveComputeMs":"Solve compute","lab.metric.solveComputeHelp":"Server-side Tetra3 + star extraction only (no network).","lab.metric.solveRoundTripMs":"End-to-end","lab.metric.solveRoundTripHelp":"From request start to UI updated: network + JSON + render.","lab.metric.backendTotalMs":"Backend total","lab.metric.openDecodeMs":"Open/decode","lab.metric.preprocessMs":"Preprocess","lab.metric.extractMs":"Extract","lab.metric.solveOnlyMs":"Solve match","lab.metric.probHelp":"Solver confidence (0–1); higher means a more trustworthy plate match.","lab.metric.probRawHelp":"Raw Tetra3 Prob (e.g. log-likelihood); compare with the normalized line above.","lab.metric.radec":"RA / Dec","lab.metric.matches":"Matches","lab.metric.rmse":"RMSE","lab.metric.prob":"Prob.","lab.metric.status":"Status","meta.exposure":"Exposure","meta.gain":"Gain","meta.fps":"FPS","meta.sensor":"Sensor","meta.colorMode":"Color","meta.outputResolution":"Output size","meta.fileTime":"File time","meta.fileSize":"File size","results.viewRaw":"Raw JSON","results.hideRaw":"Hide","params.title":"Solve parameters","params.blockSolveIntro":"Plate-solve (Tetra3): FOV, timeout, and coarse sky hints. FOV should match your lens.","params.centroid":"Star detection","params.blockCentroidIntro":"Star detection: threshold, blob area, and local background window for centroids.","params.fov":"FOV estimate (°)","params.fovHelp":"Horizontal field of view for lost-in-space solve.","params.fovErr":"FOV max error (°)","params.fovErrHelp":"Search range around estimated FOV.","params.timeout":"Timeout (ms)","params.timeoutHelp":"Max wait time per solve.","params.solveProfile":"Solve profile","params.solveProfileHelp":"Speed/Balanced/Robust tune timeout, centroid thresholds, and matching star count together.","params.solveProfileSpeed":"Speed first","params.solveProfileBalanced":"Balanced","params.solveProfileRobust":"Robust first","params.ra":"RA hint (°)","params.raHelp":"Approximate right ascension in degrees.","params.dec":"Dec hint (°)","params.decHelp":"Approximate declination in degrees.","params.maxSide":"Max long side before extract (px)","params.maxSideHelp":"Downscale long edge for faster centroid extraction.","params.detailLevelFull":"Include full Tetra3 raw block (larger payload, for debugging only).","params.largeScaleBg":"Large-scale background flattening","params.largeScaleBgHelp":"Before centroiding, estimate a low-frequency background on a downscaled image and correct uneven illumination (e.g. corner glow). Off by default to match legacy behavior.","params.sigma":"σ threshold","params.sigmaHelp":"Multiplier over background noise for star candidates.","params.maxArea":"max_area","params.maxAreaHelp":"Max connected component area in pixels.","params.minArea":"min_area","params.minAreaHelp":"Min connected component area in pixels.","params.filtsize":"filtsize (odd)","params.filtsizeHelp":"Local filter window size, must be odd.","btn.solveOne":"Solve once","btn.solveBatch":"Batch solve (presets)","btn.applyPresets":"Apply preset to form","btn.savePreset":"Save","placeholder.newPreset":"New preset name","pool.title":"Server asset pool","pool.col.name":"Filename","pool.col.source":"Source","pool.col.size":"Size","pool.col.time":"Modified","pool.delete":"Delete","history.title":"Experiment records","history.intro":"Saved solve snapshots from the Lab. After a solve, use Save to records in the Lab main panel (Result comparison), or Save on each batch result card. Search by filename or preset; export JSON/CSV for backup.","history.search":"Search…","history.searchBtn":"Search","history.exportJson":"Export JSON","history.exportCsv":"Export CSV","history.total":"Total {n}","history.preset":"Preset","history.metrics":"Metrics","history.detail":"Details","history.collapse":"Collapse","history.prev":"Prev","history.next":"Next","history.delete":"Delete","delete.uploadFirst":'Delete "{name}" from the asset pool?',"delete.uploadSecond":"This cannot be undone. Confirm again?","delete.experimentFirst":"Delete this experiment record?","delete.experimentSecond":"This cannot be undone. Confirm again?","results.title":"Results","results.saveCurrent":"Save to records","results.saveRow":"Save","results.expand":"Expand","results.collapseJson":"Collapse","err.selectFile":"Select a file","err.selectPresets":"Select at least one preset","common.placeholder":"—"},Th={"app.title":"OGScope 星空解算控制台","nav.lab":"解算台","nav.labImage":"图片解算","nav.labVideo":"视频解算","nav.pool":"素材池","nav.history":"实验记录","nav.cameraDebug":"相机调试控制台","nav.home":"首页","lang.zh":"中文","lang.en":"EN","sidebar.assets":"自行上传素材","sidebar.upload":"上传文件","sidebar.refresh":"刷新列表","sidebar.debugCaptures":"调试控制台素材","sidebar.assetTypeImage":"图片","sidebar.assetTypeVideo":"视频","sidebar.debugEmpty":"暂无调试文件","sidebar.importToPool":"导入到素材池","sidebar.debugPage":"第 {cur} / {total} 页","sidebar.batchPresets":"批量预设","sidebar.batchHint":"勾选后点击「批量解算」可一次用多组参数对比结果。","lab.selectOrUpload":"从左侧选择自行上传或已导入的素材","lab.selectOrUploadVideo":"从左侧选择视频素材预览;或使用上方按钮解算设备相机实时帧。","lab.file":"文件","lab.source":"来源","lab.layers":"叠加层","lab.layer.matched":"匹配星","lab.layer.pattern":"图案星","lab.layer.all":"全部质心","lab.grid":"网格","lab.zoomIn":"放大","lab.zoomOut":"缩小","lab.zoomReset":"复位","lab.resolution":"分辨率","lab.fwhm":"FWHM","lab.starsDetected":"检测星点","lab.meta.title":"拍摄与文件信息","lab.meta.noSidecar":"无侧车信息(非调试采集或仅本地上传)","lab.meta.partial":"暂无侧车详细字段,仅显示文件信息。","lab.solveSection":"解算","lab.imageSection":"图像","lab.metric.solveMs":"用时","lab.metric.solveComputeMs":"解算计算用时","lab.metric.solveComputeHelp":"服务端 Tetra3 与提星等纯计算耗时(与网络无关)。","lab.metric.solveRoundTripMs":"全链路用时","lab.metric.solveRoundTripHelp":"从本页发起请求到收到结果并完成界面刷新的总耗时,含网络往返与浏览器渲染。","lab.metric.backendTotalMs":"后端总用时","lab.metric.openDecodeMs":"读取/解码","lab.metric.preprocessMs":"预处理","lab.metric.extractMs":"提星","lab.metric.solveOnlyMs":"匹配解算","lab.metric.probHelp":"由解算器给出的匹配置信度(0–1),越高表示星图与天区匹配越可信。","lab.metric.probRawHelp":"Tetra3 返回的原始 Prob 字段,可能为对数似然等内部量;与上一行换算后的百分比对照查看即可。","lab.metric.radec":"RA / Dec","lab.metric.matches":"匹配","lab.metric.rmse":"RMSE","lab.metric.prob":"置信","lab.metric.status":"状态","meta.exposure":"曝光","meta.gain":"增益","meta.fps":"帧率","meta.sensor":"传感器","meta.colorMode":"色彩","meta.outputResolution":"输出分辨率","meta.fileTime":"文件时间","meta.fileSize":"文件大小","results.viewRaw":"原始 JSON","results.hideRaw":"收起","params.title":"解算参数","params.blockSolveIntro":"以下为板块求解(Tetra3)搜索天区、超时与粗略指向提示;FOV 需与镜头视场大致一致。","params.centroid":"提星","params.blockCentroidIntro":"以下为星点检测:阈值、连通域面积与局部背景窗口,用于从图像中提取星点质心。","params.fov":"FOV 估计 (°)","params.fovHelp":"水平视场角估计值,用于 lost-in-space 解算。","params.fovErr":"FOV 允许误差 (°)","params.fovErrHelp":"允许 Tetra3 在估计 FOV 附近的搜索范围。","params.timeout":"超时 (ms)","params.timeoutHelp":"单次解算最长等待时间。","params.solveProfile":"解算档位","params.solveProfileHelp":"速度/平衡/稳健三档会同时调整超时、提星阈值与参与匹配星点数。","params.solveProfileSpeed":"速度优先","params.solveProfileBalanced":"平衡","params.solveProfileRobust":"稳健优先","params.ra":"RA 提示 (°)","params.raHelp":"大致天球赤经,缩小搜索范围(度)。","params.dec":"Dec 提示 (°)","params.decHelp":"大致天球赤纬(度)。","params.maxSide":"提星前长边上界 (px)","params.maxSideHelp":"降采样长边上限,大图可加速提星。","params.detailLevelFull":"包含完整 Tetra3 原始结果(体积略大,仅调试时开启)","params.largeScaleBg":"大尺度背景减除","params.largeScaleBgHelp":"在提星前用低分辨率平滑估计并校正大尺度亮度不均,可减轻角部光晕导致的假星;默认关闭以保持与过往行为一致。","params.sigma":"σ(阈值倍数)","params.sigmaHelp":"高于背景噪声倍数的区域视为星点候选。","params.maxArea":"max_area","params.maxAreaHelp":"连通域最大像素面积。","params.minArea":"min_area","params.minAreaHelp":"连通域最小像素面积。","params.filtsize":"filtsize(奇数)","params.filtsizeHelp":"局部背景滤波窗口边长,须为奇数。","btn.solveOne":"单张解算","btn.solveBatch":"批量解算(勾选预设)","btn.applyPresets":"应用预设到表单","btn.savePreset":"保存","placeholder.newPreset":"新预设名称","pool.title":"服务器素材池","pool.col.name":"文件名","pool.col.source":"来源","pool.col.size":"大小","pool.col.time":"修改时间","pool.delete":"删除","history.title":"实验记录","history.intro":"此处展示你在解算台完成解算后手动保存的快照。用法:在「解算台」主栏「结果对比」中,单张解算后点「保存当前到实验记录」,或批量解算后在某张结果卡片上点「保存记录」。本页可按文件名或预设名搜索,支持导出 JSON/CSV 备份。","history.search":"搜索…","history.searchBtn":"搜索","history.exportJson":"导出 JSON","history.exportCsv":"导出 CSV","history.total":"共 {n} 条","history.preset":"预设","history.metrics":"指标","history.detail":"详情","history.collapse":"收起","history.prev":"上一页","history.next":"下一页","history.delete":"删除","delete.uploadFirst":"确定要从素材池删除「{name}」吗?","delete.uploadSecond":"此操作不可恢复,再次确认删除?","delete.experimentFirst":"确定要删除这条实验记录吗?","delete.experimentSecond":"此操作不可恢复,再次确认删除?","results.title":"结果对比","results.saveCurrent":"保存当前到实验记录","results.saveRow":"保存记录","results.expand":"展开","results.collapseJson":"收起","err.selectFile":"请选择素材","err.selectPresets":"请勾选至少一个预设","common.placeholder":"—","delete.uploadCascade":"该素材有 {n} 条实验记录,是否一并删除?","lab.solveCurrentFrame":"解算当前帧(文件)","lab.solveFileStart":"开始文件连续解算","lab.solveFileStop":"停止文件连续解算","lab.cameraPreviewLoading":"正在连接共享预览…","lab.solveCameraFrame":"解算相机当前帧","lab.solveCameraStart":"开始相机解算","lab.solveCameraStop":"停止相机解算","lab.videoPreviewFailed":"该视频格式可能不受浏览器支持,建议使用 MP4(H.264) 或 WebM。","lab.previewModeFile":"素材文件","lab.previewModeCamera":"设备相机","lab.videoLiveIntro":"与调试控制台共用同一相机;此处可预览并解算实时帧,无需单独打开调试页。两页可同时使用。","lab.cameraSnapshotName":"ogscope_camera_live","lab.metric.probRaw":"原始 Prob","lab.systemLoad":"系统负载","results.saveBatchAll":"保存全部到实验记录"},sf=w.createContext(null),Vu={zh:Th,en:zh};function Rh({children:e}){const[t,n]=w.useState("zh"),[r,l]=w.useState(Vu.zh);w.useEffect(()=>{l(Vu[t])},[t]);const o=w.useMemo(()=>(i,u)=>{let f=r[i]??i;if(u)for(const[v,h]of Object.entries(u))f=f.replace(new RegExp(`\\{${v}\\}`,"g"),String(h));return f},[r]),a=w.useMemo(()=>({locale:t,setLocale:n,t:o}),[t,o]);return s.jsx(sf.Provider,{value:a,children:e})}function uf(){const e=w.useContext(sf);if(!e)throw new Error("useI18n must be used within I18nProvider");return e}function as(e){return e==null||Number.isNaN(e)?"—":e<1024?`${e} B`:e<1024*1024?`${(e/1024).toFixed(1)} KB`:`${(e/(1024*1024)).toFixed(2)} MB`}function _l(e,t){if(!e)return"—";try{const n=new Date(e);return new Intl.DateTimeFormat(t==="en"?"en-GB":"zh-CN",{dateStyle:"short",timeStyle:"medium"}).format(n)}catch{return e}}function Ht(e){return e==null?null:typeof e=="string"&&e.trim()?e.trim():typeof e=="number"&&!Number.isNaN(e)?String(e):null}function Lh(e){return typeof e!="number"||e<=0?null:e>=1e6?`${(e/1e6).toFixed(2)} s`:e>=1e3?`${(e/1e3).toFixed(0)} ms`:`${e} µs`}function Fh(e,t){if(!e)return[];const n=[],r=Lh(e.exposure_us);r&&n.push({key:"meta.exposure",value:r});const l=Ht(e.analogue_gain),o=Ht(e.digital_gain);if(l||o){const y=[l?`A ${l}`:"",o?`D ${o}`:""].filter(Boolean);n.push({key:"meta.gain",value:y.join(" · ")})}const a=Ht(e.fps);a&&n.push({key:"meta.fps",value:a});const i=Ht(e.sensor);i&&n.push({key:"meta.sensor",value:i});const u=Ht(e.color_mode);u&&n.push({key:"meta.colorMode",value:u});const f=Ht(e.resolution);f&&n.push({key:"meta.outputResolution",value:f});const v=Ht(e.modified);v&&n.push({key:"meta.fileTime",value:_l(v,t)});const h=e.size;return typeof h=="number"&&h>0&&n.push({key:"meta.fileSize",value:as(h)}),n}function cf(e){const t=n=>typeof n=="number"&&!Number.isNaN(n)?n:null;return e?{tSolveMs:t(e.t_solve_ms),tExtractMs:t(e.t_extract_ms),tPreprocessMs:t(e.t_preprocess_ms),tOpenDecodeMs:t(e.t_open_decode_ms),tBackendTotalMs:t(e.t_backend_total_ms),raDeg:t(e.ra_deg),decDeg:t(e.dec_deg),matches:t(e.matches),rmseArcsec:t(e.rmse_arcsec),prob:t(e.prob),status:typeof e.status=="string"?e.status:null}:{tSolveMs:null,tExtractMs:null,tPreprocessMs:null,tOpenDecodeMs:null,tBackendTotalMs:null,raDeg:null,decDeg:null,matches:null,rmseArcsec:null,prob:null,status:null}}function Xl(e){return e==null?"—":`${e.toFixed(4)}°`}function Oh(e){return e==null?"—":e>=0&&e<=1?`${(e*100).toFixed(1)}%`:String(e)}function Ih(e){if(e==null)return"—";if(typeof e=="number"&&!Number.isNaN(e)){const t=Math.abs(e);return t>0&&t<.001?e.toExponential(4):t>=0&&t<=1?`${(e*100).toFixed(4)}%`:String(e)}return String(e)}function Mn(e,t){const n=t==null?void 0:t.tetra,r=n?n.Prob??n.prob:void 0;let l=Oh(e);e!=null&&e>=0&&e<=1&&e>0&&e<1e-4&&(l=`${(e*100).toExponential(2)}%`),e===0&&r!==void 0&&r!==null&&(l="—");const o=r!=null?Ih(r):null;return{line:l,rawLine:o}}const ca=()=>({hint_ra_deg:45,hint_dec_deg:80,fov_estimate:11,fov_max_error:void 0,solve_timeout_ms:1500,solve_profile:"balanced",max_image_side:1600,large_scale_bg_subtract:!1,detail_level:"summary",centroid:{sigma:2.5,max_area:400,min_area:5,filtsize:25,binary_open:!0,max_axis_ratio:void 0}});function da(e){return e?{matches:e.matches,rmse_arcsec:e.rmse_arcsec,status:e.status,prob:e.prob,t_solve_ms:e.t_solve_ms}:{}}function Dh(e){var n,r;if(!e)return null;const t=e.solve_overlay;return(n=t==null?void 0:t.stars_matched)!=null&&n.length?t.stars_matched.length:(r=t==null?void 0:t.stars_all_centroids)!=null&&r.length?t.stars_all_centroids.length:typeof e.matches=="number"?e.matches:null}const fl=30,fa=6;function Wu(e){return/\.(jpe?g|png|webp|bmp|gif|fits?)$/i.test(e)}function dn(e){return/\.(mp4|mov|webm|mkv|avi)$/i.test(e)}function Uh(){var xi,wi,Si,ki,Ni,ji,_i,Ci;const{t:e,locale:t,setLocale:n}=uf(),[r,l]=w.useState("lab_image"),[o,a]=w.useState([]),[i,u]=w.useState(null),[f,v]=w.useState(ca),[h,y]=w.useState([]),[k,j]=w.useState([]),[_,$]=w.useState(new Set),[p,d]=w.useState(!1),[m,g]=w.useState(null),[N,M]=w.useState(null),[E,z]=w.useState(null),[U,L]=w.useState(""),[le,nt]=w.useState(1),[me,nn]=w.useState(null),[Or,rn]=w.useState(null),[Ut,b]=w.useState(""),[T,R]=w.useState({matched:!0,pattern:!0,all:!0}),[H,Z]=w.useState([]),[_e,Ke]=w.useState(null),[rt,Ce]=w.useState(1),[lt,ti]=w.useState(!1),[ln,mo]=w.useState(1),[ni,df]=w.useState({w:0,h:0}),[on,ff]=w.useState({w:0,h:0}),[ri,pf]=w.useState({w:0,h:0}),[q,Ir]=w.useState("file"),[Dr,li]=w.useState(null),[Hn,ho]=w.useState(null),[vo,oi]=w.useState(!1),[Ur,ai]=w.useState(!1),[si,mf]=w.useState(!0),[ot,yo]=w.useState(!1),[hf,go]=w.useState(null),[ii,xo]=w.useState(null),[wo,$r]=w.useState(null),[So,ui]=w.useState(!1),[vf,ko]=w.useState({}),[ci,No]=w.useState(!1),[jo,at]=w.useState(null),[yf,An]=w.useState(null),_o=w.useRef(null),Bn=w.useRef(null),di=w.useRef(null),Hr=w.useRef(null),Ar=w.useRef(null),Br=w.useRef(null),Co=w.useRef(!1),Eo=w.useRef(!1),an=w.useRef(null),[Vr,gf]=w.useState(null),[Ge,fi]=w.useState(null),pi=w.useMemo(()=>{const c=(Ge==null?void 0:Ge.star_analysis_target_fps)??.6666666666666666,x=Math.min(Math.max(c,.2),5);return Math.round(1e3/x)},[Ge]),$t=w.useCallback(async()=>{const[c,x,C]=await Promise.all([fh(),sa("official"),sa("user")]);a(c.files),y(x.presets),j(C.presets)},[]),bo=w.useCallback(()=>{wh().then(c=>Z(c.files)).catch(()=>Z([]))},[]);w.useEffect(()=>{_h().then(c=>fi(c)).catch(()=>fi(null))},[]),w.useEffect(()=>{$t().catch(c=>g(String(c)))},[$t]),w.useEffect(()=>{bo()},[bo]),w.useEffect(()=>{r==="history"&&ua(U,le,fl).then(nn).catch(c=>g(String(c)))},[r,U,le]),w.useEffect(()=>{if(!i){$r(null);return}let c=!1;return ui(!0),(async()=>{try{const x=await kh(i);if(c)return;let C={...x};try{C={...await Hu(i),...x}}catch{}$r(C)}catch{try{const x=await Hu(i);c||$r(x)}catch{c||$r(null)}}finally{c||ui(!1)}})(),()=>{c=!0}},[i]),w.useEffect(()=>{M(null),z(null),ko({}),No(!1),at(null),An(null),Ir("file"),ho(null)},[i]);const st=w.useMemo(()=>{const c=N==null?void 0:N.result;if(!c)return null;const x=c.solve_overlay||null;if(!x)return null;const C=c.overlay_ext||null;return{...x,overlay_ext:C||void 0}},[N]),gt=w.useMemo(()=>(N==null?void 0:N.result)??null,[N]),mi=w.useMemo(()=>Dh(gt),[gt]),Po=w.useMemo(()=>r==="lab_image"?ni:r==="lab_video"?q==="file"?on:ri:{w:0,h:0},[r,q,ni,on,ri]),Wr=i?Nh(i):"";w.useEffect(()=>{if(r!=="lab_image")return;const c=_o.current,x=an.current;if(!c||!x||!st||!i)return;const C=()=>Bu(x,c,st,T);c.complete?C():c.onload=C},[st,i,N,T,r]),w.useEffect(()=>{if(r!=="lab_video"||q!=="file")return;const c=Bn.current,x=an.current;if(!c||!x||!st||!i)return;const C=()=>Mh(x,c,st,T);return c.addEventListener("loadeddata",C),c.addEventListener("seeked",C),c.readyState>=2&&C(),()=>{c.removeEventListener("loadeddata",C),c.removeEventListener("seeked",C)}},[st,T,r,q,i,N]),w.useEffect(()=>{if(r!=="lab_video"||q!=="camera")return;const c=di.current,x=an.current;if(!c||!x||!st)return;const C=()=>Bu(x,c,st,T);c.complete?C():c.onload=C},[st,T,r,q,N,Dr]),w.useEffect(()=>{if(r!=="lab_video"||q!=="camera")return;let c=!1;const x=async()=>{if(!(c||ot))try{const Q=Hr.current?`?since_frame_id=${encodeURIComponent(Hr.current)}`:"",xe=await fetch(`/api/camera/preview${Q}`,{cache:"no-store"});if(xe.status===304||!xe.ok)return;const un=xe.headers.get("X-Frame-Id");un!=null&&(Hr.current=un);const To=await xe.blob(),Ro=URL.createObjectURL(To);li(Wn=>(Wn&&URL.revokeObjectURL(Wn),Ro))}catch{}};x();const C=window.setInterval(()=>void x(),180);return()=>{c=!0,clearInterval(C),li(Q=>(Q&&URL.revokeObjectURL(Q),null)),Hr.current=null}},[r,q,ot]),w.useEffect(()=>{const c=_o.current;if(!c)return;const x=()=>df({w:c.naturalWidth||0,h:c.naturalHeight||0});return x(),c.addEventListener("load",x),()=>c.removeEventListener("load",x)},[i,Wr]),w.useEffect(()=>{const c=Bn.current;if(!c)return;const x=()=>ff({w:c.videoWidth||0,h:c.videoHeight||0});return c.addEventListener("loadedmetadata",x),c.addEventListener("loadeddata",x),x(),()=>{c.removeEventListener("loadedmetadata",x),c.removeEventListener("loadeddata",x)}},[i,Wr,r]);const xf=c=>{v({...ca(),...c,centroid:{...ca().centroid,...c.centroid}})},wf=async()=>{if(!i){g(e("err.selectFile"));return}g(null),d(!0);const c=performance.now();try{const x=await vh(i,f);M(x),z(null),An("file"),at(performance.now()-c)}catch(x){g(String(x)),at(null)}finally{d(!1)}},Sf=async()=>{if(!i){g(e("err.selectFile"));return}const c=[];for(const C of _){const xe=[...h,...k].find(un=>un.id===C);xe&&c.push({label:xe.name,params:structuredClone(xe.params)})}if(c.length===0){g(e("err.selectPresets"));return}g(null),d(!0);const x=performance.now();try{const C=await yh(i,c);z({results:C.results}),M(null),ko({}),An("file"),at(performance.now()-x)}catch(C){g(String(C)),at(null)}finally{d(!1)}},Mo=async()=>{if(Co.current)return;g(null),Co.current=!0;const c=performance.now();try{const x=await Ch({source:"camera",overlay_topn_count:3,enable_polar_guide:!0,solve_timeout_ms:Math.min(((Ge==null?void 0:Ge.solver_timeout_ms)??1500)*.6,1200),...f});M(x),z(null),An("camera"),Ir("camera");const C=x.result,Q=typeof(C==null?void 0:C.status)=="string"?String(C.status):"";si&&Q==="MATCH_FOUND"&&(yo(!0),go(x.frame_id!=null?String(x.frame_id):null),xo(Dr),Kr()),at(performance.now()-c)}catch(x){g(String(x)),at(null)}finally{Co.current=!1}},zo=async()=>{if(Eo.current||!i)return;const c=Bn.current;if(!c||c.videoWidth<2||c.videoHeight<2||Hn)return;g(null),Eo.current=!0;const x=performance.now();try{const C=document.createElement("canvas");C.width=c.videoWidth,C.height=c.videoHeight;const Q=C.getContext("2d");if(!Q)throw new Error("无法创建画布上下文 / Cannot create canvas context");Q.drawImage(c,0,0,C.width,C.height);const xe=await new Promise((To,Ro)=>{C.toBlob(Wn=>Wn?To(Wn):Ro(new Error("帧编码失败 / Frame encode failed")),"image/jpeg",.92)}),un=await Eh(xe,{...f,solve_timeout_ms:Math.min(((Ge==null?void 0:Ge.solver_timeout_ms)??1500)*.6,1200),overlay_topn_count:3,enable_polar_guide:!0});M(un),z(null),An("file"),at(performance.now()-x)}catch(C){g(String(C)),at(null)}finally{Eo.current=!1}},kf=()=>{Ur||!i||(ai(!0),zo(),Br.current=window.setInterval(()=>{zo()},pi))},hi=w.useMemo(()=>r!=="lab_video"||q!=="file"||!i||Hn?!1:on.w>1&&on.h>1,[r,q,i,Hn,on.w,on.h]),Qr=()=>{ai(!1),Br.current!=null&&(window.clearInterval(Br.current),Br.current=null)},Nf=()=>{vo||ot||(oi(!0),Mo(),Ar.current=window.setInterval(()=>{Mo()},pi))},Kr=()=>{oi(!1),Ar.current!=null&&(window.clearInterval(Ar.current),Ar.current=null)},jf=()=>{yo(!1),go(null),xo(null)},_f=c=>{$(x=>{const C=new Set(x);return C.has(c)?C.delete(c):C.add(c),C})},vi=async c=>{if(!window.confirm(e("delete.uploadFirst",{name:c})))return;let x=0;try{x=(await jh(c)).count}catch{x=0}if(x>0){if(!window.confirm(e("delete.uploadCascade",{n:x})))return}else if(!window.confirm(e("delete.uploadSecond")))return;d(!0),g(null);try{await ph(c,{deleteExperiments:x>0}),await $t(),i===c&&u(null)}catch(C){g(String(C))}finally{d(!1)}},Cf=async c=>{if(window.confirm(e("delete.experimentFirst"))&&window.confirm(e("delete.experimentSecond"))){d(!0),g(null);try{await xh(c),Or===c&&rn(null);const x=await ua(U,le,fl);nn(x)}catch(x){g(String(x))}finally{d(!1)}}},Gr=c=>mo(Math.min(4,Math.max(.5,c))),yi=Math.max(1,Math.ceil(((me==null?void 0:me.total)??0)/fl)),sn=w.useMemo(()=>r==="lab_image"?H.filter(c=>c.type==="image"||Wu(c.name)):r==="lab_video"?H.filter(c=>c.type==="video"||dn(c.name)):H,[H,r]),Vn=Math.max(1,Math.ceil(sn.length/fa)),Ef=w.useMemo(()=>{const c=(rt-1)*fa;return sn.slice(c,c+fa)},[sn,rt]);w.useEffect(()=>{Ce(1)},[r]),w.useEffect(()=>{Ce(c=>Math.min(c,Vn))},[Vn]),w.useEffect(()=>{_e&&!sn.some(c=>c.name===_e)&&Ke(null)},[sn,_e]);const gi=w.useMemo(()=>Fh(wo,t),[wo,t]),bf=w.useMemo(()=>r==="lab_image"?o.filter(c=>Wu(c.filename)):r==="lab_video"?o.filter(c=>dn(c.filename)):o,[o,r]),I=w.useMemo(()=>cf(gt),[gt]);return w.useEffect(()=>{No(!1)},[N]),w.useEffect(()=>{if(r!=="lab_video")return;let c;const x=()=>{Ph().then(gf).catch(()=>{})};return x(),c=setInterval(x,1500),()=>{c&&clearInterval(c)}},[r]),w.useEffect(()=>{(r!=="lab_video"||q!=="camera")&&(Kr(),yo(!1),go(null),xo(null)),(r!=="lab_video"||q!=="file")&&Qr()},[r,q]),w.useEffect(()=>{i||Qr()},[i]),w.useEffect(()=>()=>{Kr(),Qr()},[]),s.jsxs("div",{className:"flex min-h-full flex-col bg-surface text-on-surface",children:[s.jsxs("header",{className:"flex h-12 shrink-0 items-center justify-between border-b border-outline-variant/20 px-4",children:[s.jsxs("div",{className:"flex items-center gap-6",children:[s.jsx("span",{className:"font-headline text-sm font-bold tracking-wide text-on-surface",children:e("app.title")}),s.jsxs("nav",{className:"flex flex-wrap gap-3 text-xs",children:[s.jsx("button",{type:"button",className:r==="lab_image"?"text-primary":"text-on-surface-variant",onClick:()=>l("lab_image"),children:e("nav.labImage")}),s.jsx("button",{type:"button",className:r==="lab_video"?"text-primary":"text-on-surface-variant",onClick:()=>l("lab_video"),children:e("nav.labVideo")}),s.jsx("button",{type:"button",className:r==="pool"?"text-primary":"text-on-surface-variant",onClick:()=>l("pool"),children:e("nav.pool")}),s.jsx("button",{type:"button",className:r==="history"?"text-primary":"text-on-surface-variant",onClick:()=>l("history"),children:e("nav.history")})]})]}),s.jsxs("div",{className:"flex items-center gap-2",children:[s.jsxs("div",{className:"mr-2 flex gap-1 text-[10px]",children:[s.jsx("button",{type:"button",className:`rounded px-2 py-0.5 ${t==="zh"?"bg-primary-container text-on-primary-container":"text-on-surface-variant"}`,onClick:()=>n("zh"),children:e("lang.zh")}),s.jsx("button",{type:"button",className:`rounded px-2 py-0.5 ${t==="en"?"bg-primary-container text-on-primary-container":"text-on-surface-variant"}`,onClick:()=>n("en"),children:e("lang.en")})]}),s.jsxs("a",{className:"flex items-center gap-1 rounded border border-outline-variant/30 px-2 py-1 text-xs text-on-surface-variant hover:bg-surface-container",href:"/debug",children:[s.jsx(sh,{className:"h-3.5 w-3.5"})," ",e("nav.cameraDebug")]}),s.jsxs("a",{className:"flex items-center gap-1 rounded border border-outline-variant/30 px-2 py-1 text-xs text-on-surface-variant hover:bg-surface-container",href:"/",children:[s.jsx(uh,{className:"h-3.5 w-3.5"})," ",e("nav.home")]})]})]}),s.jsxs("div",{className:"flex min-h-0 flex-1",children:[s.jsxs("aside",{className:"w-64 shrink-0 border-r border-outline-variant/15 bg-surface-container-lowest p-3 text-xs",children:[s.jsx("div",{className:"mb-3 font-semibold text-on-surface-variant",children:e("sidebar.assets")}),s.jsxs("label",{className:"mb-3 flex cursor-pointer items-center gap-2 rounded bg-primary-container/30 px-2 py-2 text-on-primary-container",children:[s.jsx(dh,{className:"h-4 w-4"}),s.jsx("span",{children:e("sidebar.upload")}),s.jsx("input",{type:"file",accept:"image/*,video/*",className:"hidden",onChange:async c=>{var C;const x=(C=c.target.files)==null?void 0:C[0];if(x){d(!0),g(null);try{const Q=await mh(x);await $t(),u(Q.filename)}catch(Q){g(String(Q))}finally{d(!1),c.target.value=""}}}})]}),s.jsxs("button",{type:"button",className:"mb-2 flex w-full items-center gap-1 text-left text-on-surface-variant hover:text-on-surface",onClick:()=>{$t().catch(c=>g(String(c))),Ce(1),bo()},children:[s.jsx(ch,{className:"h-3 w-3"})," ",e("sidebar.refresh")]}),s.jsx("div",{className:"max-h-36 overflow-y-auto border-t border-outline-variant/10 pt-2",children:bf.map(c=>s.jsxs("div",{className:`mb-1 flex items-center gap-0.5 rounded px-1 ${i===c.filename?"bg-surface-container":""}`,children:[s.jsxs("button",{type:"button",className:`min-w-0 flex-1 truncate rounded px-1 py-1 text-left ${i===c.filename?"text-primary":""}`,title:c.filename,onClick:()=>{u(c.filename),l(dn(c.filename)?"lab_video":"lab_image")},children:[s.jsx("span",{className:"block truncate",children:c.filename}),s.jsx("span",{className:"block text-[9px] text-on-surface-variant/90",children:dn(c.filename)?e("sidebar.assetTypeVideo"):e("sidebar.assetTypeImage")})]}),s.jsx("button",{type:"button",className:"shrink-0 rounded p-1 text-on-surface-variant hover:bg-surface-container-high hover:text-error",title:e("pool.delete"),"aria-label":e("pool.delete"),onClick:x=>{x.stopPropagation(),vi(c.filename)},children:s.jsx(Du,{className:"h-3.5 w-3.5"})})]},c.filename))}),s.jsxs("div",{className:"mt-3 border-t border-outline-variant/10 pt-3",children:[s.jsxs("div",{className:"mb-2 flex items-start justify-between gap-2",children:[s.jsx("div",{className:"min-w-0 font-semibold leading-tight text-on-surface-variant",children:e("sidebar.debugCaptures")}),s.jsx("button",{type:"button",className:"shrink-0 rounded bg-primary px-2 py-1 text-[10px] font-medium text-on-primary disabled:opacity-40",disabled:!_e,title:_e??void 0,onClick:async()=>{if(_e){d(!0);try{const c=await hh(_e);await $t(),u(c.filename),l(dn(c.filename)?"lab_video":"lab_image")}catch(c){g(String(c))}finally{d(!1)}}},children:e("sidebar.importToPool")})]}),sn.length===0?s.jsx("p",{className:"text-[10px] text-on-surface-variant",children:e("sidebar.debugEmpty")}):s.jsxs(s.Fragment,{children:[s.jsx("div",{className:"max-h-[min(22rem,55vh)] space-y-1.5 overflow-y-auto pr-0.5",children:Ef.map(c=>s.jsxs("button",{type:"button",className:`flex w-full items-center gap-2 rounded-md border px-2 py-1.5 text-left transition-colors ${_e===c.name?"border-primary bg-primary-container/20":"border-outline-variant/25 hover:bg-surface-container"}`,onClick:()=>Ke(c.name),children:[c.type==="image"?s.jsx("img",{src:Sh(c.name),alt:"",className:"h-11 w-11 shrink-0 rounded object-cover"}):s.jsx("div",{className:"flex h-11 w-11 shrink-0 items-center justify-center rounded bg-surface-container-high text-[9px] text-on-surface-variant",children:"video"}),s.jsxs("div",{className:"min-w-0 flex-1",children:[s.jsx("div",{className:"truncate font-mono text-[10px] text-on-surface",title:c.name,children:c.name}),s.jsxs("div",{className:"text-[9px] text-on-surface-variant",children:[c.type==="video"||dn(c.name)?e("sidebar.assetTypeVideo"):e("sidebar.assetTypeImage")," ","· ",_l(c.modified,t)," · ",as(c.size)]})]})]},c.name))}),s.jsxs("div",{className:"mt-2 flex items-center justify-between gap-1 text-[10px] text-on-surface-variant",children:[s.jsx("button",{type:"button",className:"rounded border border-outline-variant/30 px-2 py-0.5 disabled:opacity-40",disabled:rt<=1,onClick:()=>Ce(c=>Math.max(1,c-1)),children:e("history.prev")}),s.jsx("span",{className:"tabular-nums",children:e("sidebar.debugPage",{cur:rt,total:Vn})}),s.jsx("button",{type:"button",className:"rounded border border-outline-variant/30 px-2 py-0.5 disabled:opacity-40",disabled:rt>=Vn,onClick:()=>Ce(c=>Math.min(Vn,c+1)),children:e("history.next")})]})]})]})]}),(r==="lab_image"||r==="lab_video")&&s.jsxs("div",{className:"flex min-h-0 min-w-0 flex-1",children:[s.jsxs("main",{className:"min-h-0 min-w-0 flex-1 overflow-y-auto p-4",children:[m&&s.jsx("div",{className:"mb-2 rounded border border-error/40 bg-error-container/20 px-3 py-2 text-xs text-error",children:m}),r==="lab_video"&&s.jsxs("div",{className:"mb-3 max-w-5xl rounded-lg border border-outline-variant/25 bg-surface-container-low/80 p-3 text-[11px] leading-relaxed text-on-surface",children:[s.jsx("p",{className:"text-[10px] text-on-surface-variant",children:e("lab.videoLiveIntro")}),s.jsxs("div",{className:"mt-2 flex flex-wrap gap-2",children:[s.jsx("button",{type:"button",className:`rounded px-3 py-1.5 text-[11px] font-medium ${q==="file"?"bg-primary text-on-primary-container":"border border-outline-variant/40 bg-surface-container text-on-surface"}`,onClick:()=>Ir("file"),children:e("lab.previewModeFile")}),s.jsx("button",{type:"button",className:`rounded px-3 py-1.5 text-[11px] font-medium ${q==="camera"?"bg-primary text-on-primary-container":"border border-outline-variant/40 bg-surface-container text-on-surface"}`,onClick:()=>Ir("camera"),children:e("lab.previewModeCamera")})]})]}),s.jsxs("div",{className:"relative aspect-video w-full max-w-5xl overflow-hidden rounded-lg border border-outline-variant/20 bg-surface-container-lowest",children:[r==="lab_video"&&q==="camera"?s.jsx("div",{className:"relative flex h-full min-h-[220px] flex-col items-center justify-center gap-3 bg-black p-2",children:s.jsxs("div",{className:"relative inline-block max-h-[70vh] max-w-full",children:[ot&&ii||Dr?s.jsx("img",{ref:di,src:ot&&ii||Dr||"",alt:"",className:"max-h-[70vh] w-full min-h-[120px] object-contain",onLoad:c=>pf({w:c.currentTarget.naturalWidth,h:c.currentTarget.naturalHeight})}):s.jsxs("div",{className:"flex min-h-[200px] w-full min-w-[280px] flex-col items-center justify-center gap-2 text-[11px] text-on-surface-variant",children:[s.jsx(aa,{className:"h-8 w-8 animate-spin text-primary"}),s.jsx("span",{children:e("lab.cameraPreviewLoading")})]}),s.jsx("canvas",{ref:an,className:"pointer-events-none absolute left-0 top-0"})]})}):i?s.jsx("div",{className:"relative h-full min-h-[200px] overflow-auto",children:r==="lab_video"?s.jsxs(s.Fragment,{children:[s.jsxs("div",{className:"absolute left-2 top-2 z-20 flex flex-wrap gap-1",children:[s.jsxs("button",{type:"button",title:e("lab.grid"),className:`rounded border px-2 py-1 text-[10px] ${lt?"border-primary bg-primary/20":"border-white/30 bg-black/40"} text-white`,onClick:()=>ti(c=>!c),children:[s.jsx(Ou,{className:"mr-1 inline h-3 w-3"}),e("lab.grid")]}),s.jsx("button",{type:"button",title:e("lab.zoomOut"),className:"rounded border border-white/30 bg-black/40 px-2 py-1 text-[10px] text-white",onClick:()=>Gr(ln-.25),children:s.jsx($u,{className:"inline h-3 w-3"})}),s.jsx("button",{type:"button",title:e("lab.zoomIn"),className:"rounded border border-white/30 bg-black/40 px-2 py-1 text-[10px] text-white",onClick:()=>Gr(ln+.25),children:s.jsx(Uu,{className:"inline h-3 w-3"})}),s.jsx("button",{type:"button",title:e("lab.zoomReset"),className:"rounded border border-white/30 bg-black/40 px-2 py-1 text-[10px] text-white",onClick:()=>mo(1),children:s.jsx(Iu,{className:"inline h-3 w-3"})})]}),s.jsxs("div",{className:"flex min-h-[200px] flex-col items-center justify-center gap-2 bg-black py-2",children:[s.jsx("div",{className:"inline-block origin-top-left transition-transform",style:{transform:`scale(${ln})`},children:s.jsxs("div",{className:"relative inline-block",children:[lt&&s.jsx("div",{className:"pointer-events-none absolute inset-0 z-[1]",style:{backgroundImage:["linear-gradient(to right, rgba(255,255,255,0.12) 1px, transparent 1px)","linear-gradient(to bottom, rgba(255,255,255,0.12) 1px, transparent 1px)"].join(","),backgroundSize:"48px 48px"}}),s.jsx("video",{ref:Bn,src:Wr,loop:!0,playsInline:!0,autoPlay:!0,muted:!0,preload:"metadata",className:"max-h-[70vh] w-full max-w-full object-contain",onError:()=>ho(e("lab.videoPreviewFailed")),onLoadedData:()=>{ho(null);const c=Bn.current;c&&c.play().catch(()=>{})}}),s.jsx("canvas",{ref:an,className:"pointer-events-none absolute left-0 top-0"})]})}),Hn&&s.jsx("div",{className:"rounded border border-error/40 bg-error-container/20 px-3 py-1.5 text-[11px] text-error",children:Hn})]})]}):s.jsxs(s.Fragment,{children:[s.jsxs("div",{className:"absolute left-2 top-2 z-20 flex flex-wrap gap-1",children:[s.jsxs("button",{type:"button",title:e("lab.grid"),className:`rounded border px-2 py-1 text-[10px] ${lt?"border-primary bg-primary/20":"border-white/30 bg-black/40"} text-white`,onClick:()=>ti(c=>!c),children:[s.jsx(Ou,{className:"mr-1 inline h-3 w-3"}),e("lab.grid")]}),s.jsx("button",{type:"button",title:e("lab.zoomOut"),className:"rounded border border-white/30 bg-black/40 px-2 py-1 text-[10px] text-white",onClick:()=>Gr(ln-.25),children:s.jsx($u,{className:"inline h-3 w-3"})}),s.jsx("button",{type:"button",title:e("lab.zoomIn"),className:"rounded border border-white/30 bg-black/40 px-2 py-1 text-[10px] text-white",onClick:()=>Gr(ln+.25),children:s.jsx(Uu,{className:"inline h-3 w-3"})}),s.jsx("button",{type:"button",title:e("lab.zoomReset"),className:"rounded border border-white/30 bg-black/40 px-2 py-1 text-[10px] text-white",onClick:()=>mo(1),children:s.jsx(Iu,{className:"inline h-3 w-3"})})]}),s.jsx("div",{className:"inline-block origin-top-left transition-transform",style:{transform:`scale(${ln})`},children:s.jsxs("div",{className:"relative inline-block",children:[lt&&s.jsx("div",{className:"pointer-events-none absolute inset-0 z-[1]",style:{backgroundImage:["linear-gradient(to right, rgba(255,255,255,0.12) 1px, transparent 1px)","linear-gradient(to bottom, rgba(255,255,255,0.12) 1px, transparent 1px)"].join(","),backgroundSize:"48px 48px"}}),s.jsx("img",{ref:_o,src:Wr,alt:"preview",className:"max-h-[70vh] w-full object-contain"}),s.jsx("canvas",{ref:an,className:"pointer-events-none absolute left-0 top-0"})]})})]})}):s.jsx("div",{className:"flex h-full items-center justify-center px-4 text-center text-on-surface-variant",children:e(r==="lab_video"?"lab.selectOrUploadVideo":"lab.selectOrUpload")}),p&&s.jsx("div",{className:"absolute inset-0 flex items-center justify-center bg-black/40",children:s.jsx(aa,{className:"h-8 w-8 animate-spin text-primary"})})]}),(i&&r==="lab_image"||r==="lab_video"&&(q==="file"&&i||q==="camera"))&&s.jsxs("div",{className:"mt-2 flex flex-wrap items-center gap-3 rounded-lg border border-outline-variant/25 bg-surface-container-lowest/90 px-3 py-2 text-xs text-on-surface",children:[s.jsx("span",{className:"shrink-0 font-medium text-on-surface-variant",children:e("lab.layers")}),s.jsx("div",{className:"flex flex-wrap gap-x-4 gap-y-1",children:["matched","pattern","all"].map(c=>s.jsxs("label",{className:"flex cursor-pointer items-center gap-1",children:[s.jsx("input",{type:"checkbox",className:"accent-primary",checked:T[c],onChange:x=>R(C=>({...C,[c]:x.target.checked}))}),s.jsx("span",{children:e(c==="matched"?"lab.layer.matched":c==="pattern"?"lab.layer.pattern":"lab.layer.all")})]},c))})]}),(i||r==="lab_video"&&q==="camera")&&s.jsxs(s.Fragment,{children:[s.jsxs("div",{className:"mt-2 grid gap-2 sm:grid-cols-2",children:[s.jsxs("details",{open:!0,className:"rounded-lg border border-outline-variant/25 bg-surface-container-lowest/90",children:[s.jsxs("summary",{className:"flex cursor-pointer list-none items-center justify-between gap-2 px-3 py-2 text-xs font-semibold text-on-surface [&::-webkit-details-marker]:hidden",children:[e("lab.solveSection"),s.jsx(er,{className:"h-4 w-4 shrink-0 text-on-surface-variant"})]}),s.jsx("div",{className:"border-t border-outline-variant/15 px-3 py-2 text-[10px]",children:gt?s.jsxs("div",{className:"space-y-1 text-on-surface",children:[I.tSolveMs!=null&&s.jsxs("div",{className:"space-y-0.5",children:[s.jsxs("div",{className:"flex justify-between gap-2",children:[s.jsx("span",{className:"text-on-surface-variant",children:e("lab.metric.solveComputeMs")}),s.jsxs("span",{className:"font-mono tabular-nums",children:[I.tSolveMs.toFixed(0)," ms"]})]}),s.jsx("p",{className:"text-[8px] leading-snug text-on-surface-variant/90",children:e("lab.metric.solveComputeHelp")})]}),jo!=null&&s.jsxs("div",{className:"space-y-0.5 border-t border-outline-variant/10 pt-1",children:[s.jsxs("div",{className:"flex justify-between gap-2",children:[s.jsx("span",{className:"text-on-surface-variant",children:e("lab.metric.solveRoundTripMs")}),s.jsxs("span",{className:"font-mono tabular-nums",children:[jo.toFixed(0)," ms"]})]}),s.jsx("p",{className:"text-[8px] leading-snug text-on-surface-variant/90",children:e("lab.metric.solveRoundTripHelp")})]}),(I.tBackendTotalMs!=null||I.tOpenDecodeMs!=null||I.tPreprocessMs!=null||I.tExtractMs!=null||I.tSolveMs!=null)&&s.jsxs("div",{className:"space-y-0.5 border-t border-outline-variant/10 pt-1",children:[s.jsxs("div",{className:"flex justify-between gap-2",children:[s.jsx("span",{className:"text-on-surface-variant",children:e("lab.metric.backendTotalMs")}),s.jsx("span",{className:"font-mono tabular-nums",children:I.tBackendTotalMs!=null?`${I.tBackendTotalMs.toFixed(0)} ms`:e("common.placeholder")})]}),s.jsxs("div",{className:"flex flex-wrap gap-x-2 gap-y-0.5 text-[8px] text-on-surface-variant/90",children:[s.jsxs("span",{children:[e("lab.metric.openDecodeMs"),":"," ",I.tOpenDecodeMs!=null?`${I.tOpenDecodeMs.toFixed(0)} ms`:e("common.placeholder")]}),s.jsxs("span",{children:[e("lab.metric.preprocessMs"),":"," ",I.tPreprocessMs!=null?`${I.tPreprocessMs.toFixed(0)} ms`:e("common.placeholder")]}),s.jsxs("span",{children:[e("lab.metric.extractMs"),":"," ",I.tExtractMs!=null?`${I.tExtractMs.toFixed(0)} ms`:e("common.placeholder")]}),s.jsxs("span",{children:[e("lab.metric.solveOnlyMs"),":"," ",I.tSolveMs!=null?`${I.tSolveMs.toFixed(0)} ms`:e("common.placeholder")]})]})]}),(I.raDeg!=null||I.decDeg!=null)&&s.jsxs("div",{className:"leading-tight",children:[s.jsx("span",{className:"text-on-surface-variant",children:e("lab.metric.radec")}),s.jsxs("div",{className:"mt-0.5 font-mono text-[9px]",children:["α ",Xl(I.raDeg)," · δ"," ",Xl(I.decDeg)]})]}),s.jsxs("div",{className:"flex flex-wrap gap-x-2 gap-y-0.5 text-[9px]",children:[I.matches!=null&&s.jsxs("span",{children:[s.jsx("span",{className:"text-on-surface-variant",children:e("lab.metric.matches")})," ",s.jsx("span",{className:"font-mono",children:I.matches})]}),I.rmseArcsec!=null&&s.jsxs("span",{children:[s.jsx("span",{className:"text-on-surface-variant",children:e("lab.metric.rmse")})," ",s.jsxs("span",{className:"font-mono",children:[I.rmseArcsec.toFixed(2),"″"]})]}),I.prob!=null&&s.jsxs("span",{className:"inline-flex max-w-full flex-col gap-0.5",children:[s.jsxs("span",{children:[s.jsx("span",{className:"text-on-surface-variant",children:e("lab.metric.prob")})," ",s.jsx("span",{className:"font-mono",children:Mn(I.prob,gt??void 0).line})]}),s.jsx("span",{className:"text-[8px] leading-snug text-on-surface-variant/90",children:e("lab.metric.probHelp")}),Mn(I.prob,gt??void 0).rawLine&&s.jsxs(s.Fragment,{children:[s.jsxs("span",{className:"text-[8px] text-on-surface-variant",children:[e("lab.metric.probRaw"),":"," ",Mn(I.prob,gt??void 0).rawLine]}),s.jsx("span",{className:"text-[8px] leading-snug text-on-surface-variant/90",children:e("lab.metric.probRawHelp")})]})]})]}),I.status&&s.jsxs("div",{className:"border-t border-outline-variant/15 pt-1 text-[9px]",children:[s.jsxs("span",{className:"text-on-surface-variant",children:[e("lab.metric.status")," "]}),s.jsx("span",{className:"font-mono text-secondary",children:I.status})]})]}):s.jsx("p",{className:"text-on-surface-variant",children:e("common.placeholder")})})]}),s.jsxs("details",{open:!0,className:"rounded-lg border border-outline-variant/25 bg-surface-container-lowest/90",children:[s.jsxs("summary",{className:"flex cursor-pointer list-none items-center justify-between gap-2 px-3 py-2 text-xs font-semibold text-on-surface [&::-webkit-details-marker]:hidden",children:[e("lab.imageSection"),s.jsx(er,{className:"h-4 w-4 shrink-0 text-on-surface-variant"})]}),s.jsxs("div",{className:"space-y-0.5 border-t border-outline-variant/15 px-3 py-2 text-[10px]",children:[s.jsxs("div",{className:"flex justify-between gap-2",children:[s.jsx("span",{className:"text-on-surface-variant",children:e("lab.resolution")}),s.jsx("span",{className:"font-mono tabular-nums",children:Po.w>0?`${Po.w}×${Po.h}`:e("common.placeholder")})]}),s.jsxs("div",{className:"flex justify-between gap-2",children:[s.jsx("span",{className:"text-on-surface-variant",children:e("lab.starsDetected")}),s.jsx("span",{className:"font-mono tabular-nums",children:mi??e("common.placeholder")})]}),s.jsxs("div",{className:"flex justify-between gap-2",children:[s.jsx("span",{className:"text-on-surface-variant",children:e("lab.fwhm")}),s.jsx("span",{children:e("common.placeholder")})]})]})]})]}),i&&s.jsxs(s.Fragment,{children:[s.jsxs("div",{className:"mt-2 flex flex-wrap gap-4 text-xs text-on-surface-variant",children:[s.jsxs("span",{children:[e("lab.file"),":"," ",s.jsx("span",{className:"font-mono text-on-surface",children:i})]}),((xi=o.find(c=>c.filename===i))==null?void 0:xi.source)&&s.jsxs("span",{className:"rounded bg-surface-container-high px-2 py-0.5",children:[e("lab.source"),":"," ",(wi=o.find(c=>c.filename===i))==null?void 0:wi.source]})]}),s.jsxs("details",{open:!0,className:"mt-2 rounded-lg border border-outline-variant/25 bg-surface-container-lowest/90 text-xs shadow-sm",children:[s.jsxs("summary",{className:"flex cursor-pointer list-none items-center gap-2 px-3 py-2 font-semibold text-on-surface [&::-webkit-details-marker]:hidden",children:[e("lab.meta.title"),So&&s.jsx(aa,{className:"h-3.5 w-3.5 animate-spin"}),s.jsx(er,{className:"ml-auto h-4 w-4 shrink-0 text-on-surface-variant"})]}),s.jsx("div",{className:"border-t border-outline-variant/15 p-3 pt-2",children:gi.length>0?s.jsx("dl",{className:"grid grid-cols-2 gap-x-4 gap-y-2 sm:grid-cols-3",children:gi.map(c=>s.jsxs("div",{children:[s.jsx("dt",{className:"text-[10px] text-on-surface-variant",children:e(c.key)}),s.jsx("dd",{className:"text-[11px] font-medium text-on-surface",children:c.value})]},`${c.key}-${c.value}`))}):wo&&!So?s.jsx("p",{className:"text-[10px] leading-relaxed text-on-surface-variant",children:e("lab.meta.partial")}):So?null:s.jsx("p",{className:"text-[10px] text-on-surface-variant",children:e("lab.meta.noSidecar")})})]})]})]}),(i||r==="lab_video"&&q==="camera")&&(N||E)&&s.jsxs("section",{className:"mt-3 max-h-[min(50vh,28rem)] rounded-lg border border-outline-variant/25 bg-surface-container-lowest/95",children:[s.jsxs("div",{className:"flex h-9 shrink-0 items-center justify-between border-b border-outline-variant/15 px-3 text-[10px] uppercase text-on-surface-variant",children:[s.jsx("span",{children:e("results.title")}),s.jsxs("div",{className:"flex gap-3",children:[(Si=E==null?void 0:E.results)!=null&&Si.length?s.jsx("button",{type:"button",className:"rounded bg-surface-container px-2 py-0.5 normal-case",onClick:async()=>{if(!(!i||!E)){for(const c of E.results){if(!c.success)continue;const x=c.result;await ia({input_name:i,preset_label:String(c.label),result_json:c,metrics:da(x??null),replay:{layers:T,params:f}})}g(null)}},children:e("results.saveBatchAll")}):null,N&&s.jsx("button",{type:"button",className:"rounded bg-surface-container px-2 py-0.5 normal-case",onClick:async()=>{if(!i&&yf!=="camera")return;const c=N.result;await ia({input_name:i??e("lab.cameraSnapshotName"),preset_label:"manual",result_json:N,metrics:da(c??null),replay:{layers:T,params:f}}),g(null)},children:e("results.saveCurrent")})]})]}),s.jsx("div",{className:"min-h-0 overflow-y-auto p-3",children:s.jsxs("div",{className:"flex gap-3 overflow-x-auto text-xs",children:[E==null?void 0:E.results.map((c,x)=>{const C=c.result,Q=vf[x]??!1;return s.jsxs("div",{className:"min-w-[15rem] max-w-sm shrink-0 rounded-lg border border-outline-variant/25 bg-surface-container p-3 shadow-sm",children:[s.jsx("div",{className:"border-b border-outline-variant/15 pb-2 font-semibold text-secondary",children:String(c.label)}),c.success?s.jsxs(s.Fragment,{children:[s.jsx(Qu,{result:C,t:e,roundTripMs:null}),s.jsx("button",{type:"button",className:"mt-2 text-[10px] text-primary hover:underline",onClick:()=>ko(xe=>({...xe,[x]:!Q})),children:e(Q?"results.hideRaw":"results.viewRaw")}),Q&&s.jsx("pre",{className:"mt-1 max-h-40 overflow-auto rounded bg-surface-container-highest p-2 text-[9px] text-on-surface-variant",children:JSON.stringify(c,null,2)})]}):s.jsx("div",{className:"mt-2 text-[10px] text-error",children:String(c.error)}),c.success&&i&&s.jsx("button",{type:"button",className:"mt-3 w-full rounded bg-surface-container-high py-1.5 text-[10px] font-medium",onClick:()=>ia({input_name:i,preset_label:String(c.label),result_json:c,metrics:da(C??null),replay:{layers:T,params:f}}).catch(xe=>g(String(xe))),children:e("results.saveRow")})]},x)}),N&&!E&&s.jsxs("div",{className:"min-w-[16rem] max-w-md shrink-0 rounded-lg border border-outline-variant/25 bg-surface-container p-3 shadow-sm",children:[s.jsx("div",{className:"border-b border-outline-variant/15 pb-2 text-[10px] font-semibold uppercase text-on-surface-variant",children:e("results.title")}),s.jsx(Qu,{result:N.result,t:e,roundTripMs:jo}),s.jsx("button",{type:"button",className:"mt-2 text-[10px] text-primary hover:underline",onClick:()=>No(c=>!c),children:e(ci?"results.hideRaw":"results.viewRaw")}),ci&&s.jsx("pre",{className:"mt-1 max-h-48 overflow-auto rounded bg-surface-container-highest p-2 text-[9px] text-on-surface-variant",children:JSON.stringify(N,null,2)})]})]})})]})]}),s.jsxs("aside",{className:"flex w-80 shrink-0 flex-col border-l border-outline-variant/15 bg-surface-container-low text-xs min-h-0",children:[r!=="lab_video"&&s.jsxs("div",{className:"shrink-0 space-y-2 border-b border-outline-variant/20 p-4 pb-3",children:[s.jsx("button",{type:"button",className:"w-full rounded bg-primary py-2.5 font-semibold text-on-primary-container",onClick:wf,disabled:p,children:e("btn.solveOne")}),s.jsx("button",{type:"button",className:"w-full rounded bg-secondary/80 py-2.5 font-semibold text-on-secondary",onClick:Sf,disabled:p,children:e("btn.solveBatch")})]}),r==="lab_video"&&s.jsx("div",{className:"shrink-0 space-y-2 border-b border-outline-variant/20 p-4 pb-3",children:q==="file"?s.jsxs(s.Fragment,{children:[s.jsx("button",{type:"button",className:"w-full rounded bg-primary py-2.5 font-semibold text-on-primary-container disabled:opacity-40",onClick:()=>kf(),disabled:p||!hi||Ur,children:e("lab.solveFileStart")}),s.jsx("button",{type:"button",className:"w-full rounded bg-error/80 py-2.5 font-semibold text-on-error disabled:opacity-40",onClick:()=>Qr(),disabled:!Ur,children:e("lab.solveFileStop")}),s.jsx("button",{type:"button",className:"w-full rounded bg-secondary/80 py-2.5 font-semibold text-on-secondary disabled:opacity-40",onClick:()=>void zo(),disabled:p||!hi||Ur,children:e("lab.solveCurrentFrame")})]}):s.jsxs(s.Fragment,{children:[s.jsx("button",{type:"button",className:"w-full rounded bg-primary py-2.5 font-semibold text-on-primary-container disabled:opacity-40",onClick:()=>Nf(),disabled:p||vo||ot,children:e("lab.solveCameraStart")}),s.jsx("button",{type:"button",className:"w-full rounded bg-error/80 py-2.5 font-semibold text-on-error disabled:opacity-40",onClick:()=>Kr(),disabled:!vo,children:e("lab.solveCameraStop")}),s.jsx("button",{type:"button",className:"w-full rounded bg-secondary/80 py-2.5 font-semibold text-on-secondary disabled:opacity-40",onClick:()=>void Mo(),disabled:p||ot,children:e("lab.solveCameraFrame")}),s.jsxs("label",{className:"flex items-center gap-2 rounded border border-outline-variant/25 px-2 py-1.5 text-[11px]",children:[s.jsx("input",{type:"checkbox",checked:si,onChange:c=>mf(c.target.checked)}),s.jsx("span",{children:"自动保持(解算成功后冻结) / Auto Hold"})]}),ot&&s.jsx("button",{type:"button",className:"w-full rounded bg-tertiary/80 py-2.5 font-semibold text-on-tertiary disabled:opacity-40",onClick:()=>jf(),children:"继续实时 / Resume Live"}),ot&&s.jsxs("div",{className:"text-[10px] text-on-surface-variant",children:["已冻结帧 / Frozen frame ",hf??"-"]})]})}),r==="lab_video"&&Vr&&s.jsxs("div",{className:"shrink-0 border-b border-outline-variant/20 px-4 py-2 text-[10px] text-on-surface-variant",children:[s.jsx("div",{className:"font-medium text-on-surface",children:e("lab.systemLoad")}),s.jsxs("div",{children:["CPU ",Vr.cpu_usage,"% · RAM ",Vr.memory_usage,"% ·"," ",Vr.temperature,"°C"]})]}),s.jsxs("div",{className:"min-h-0 flex-1 overflow-y-auto p-4",children:[s.jsxs("details",{open:!0,className:"rounded-lg border border-outline-variant/20 bg-surface-container-highest/30",children:[s.jsxs("summary",{className:"flex cursor-pointer list-none items-center justify-between gap-2 px-3 py-2 font-semibold text-on-surface-variant [&::-webkit-details-marker]:hidden",children:[e("params.title"),s.jsx(er,{className:"h-4 w-4 shrink-0"})]}),s.jsxs("div",{className:"border-t border-outline-variant/15 p-3 pt-2",children:[s.jsx("p",{className:"mb-3 text-[10px] leading-relaxed text-on-surface-variant/90",children:e("params.blockSolveIntro")}),s.jsxs("div",{className:"space-y-2",children:[s.jsxs("label",{className:"block",children:[s.jsx("span",{className:"text-[10px] font-medium text-on-surface-variant",children:e("params.solveProfile")}),s.jsx("p",{className:"mb-1 mt-0.5 text-[9px] leading-snug text-on-surface-variant/85",children:e("params.solveProfileHelp")}),s.jsxs("select",{className:"w-full rounded bg-surface-container-highest px-2 py-1",value:f.solve_profile??"balanced",onChange:c=>v(x=>({...x,solve_profile:c.target.value})),children:[s.jsx("option",{value:"speed",children:e("params.solveProfileSpeed")}),s.jsx("option",{value:"balanced",children:e("params.solveProfileBalanced")}),s.jsx("option",{value:"robust",children:e("params.solveProfileRobust")})]})]}),s.jsx(Xe,{label:e("params.fov"),helpKey:"params.fovHelp",value:f.fov_estimate??"",onChange:c=>v(x=>({...x,fov_estimate:c}))}),s.jsx(Xe,{label:e("params.fovErr"),helpKey:"params.fovErrHelp",value:f.fov_max_error??"",onChange:c=>v(x=>({...x,fov_max_error:c}))}),s.jsx(Xe,{label:e("params.timeout"),helpKey:"params.timeoutHelp",value:f.solve_timeout_ms??"",onChange:c=>v(x=>({...x,solve_timeout_ms:c}))}),s.jsx(Xe,{label:e("params.ra"),helpKey:"params.raHelp",value:f.hint_ra_deg??"",onChange:c=>v(x=>({...x,hint_ra_deg:c}))}),s.jsx(Xe,{label:e("params.dec"),helpKey:"params.decHelp",value:f.hint_dec_deg??"",onChange:c=>v(x=>({...x,hint_dec_deg:c}))}),s.jsx(Xe,{label:e("params.maxSide"),helpKey:"params.maxSideHelp",value:f.max_image_side??"",onChange:c=>v(x=>({...x,max_image_side:c}))}),s.jsxs("label",{className:"mt-1.5 flex cursor-pointer items-center gap-2",children:[s.jsx("input",{type:"checkbox",className:"accent-primary",checked:f.detail_level==="full",onChange:c=>v(x=>({...x,detail_level:c.target.checked?"full":"summary"}))}),s.jsx("span",{className:"text-[10px] text-on-surface-variant",children:e("params.detailLevelFull")})]}),s.jsxs("label",{className:"mt-1.5 flex cursor-pointer items-start gap-2",children:[s.jsx("input",{type:"checkbox",className:"accent-primary mt-0.5",checked:!!f.large_scale_bg_subtract,onChange:c=>v(x=>({...x,large_scale_bg_subtract:c.target.checked}))}),s.jsxs("span",{children:[s.jsx("span",{className:"text-[10px] text-on-surface-variant",children:e("params.largeScaleBg")}),s.jsx("p",{className:"mt-0.5 text-[9px] leading-snug text-on-surface-variant/85",children:e("params.largeScaleBgHelp")})]})]})]})]})]}),s.jsxs("details",{open:!0,className:"mt-3 rounded-lg border border-outline-variant/20 bg-surface-container-highest/30",children:[s.jsxs("summary",{className:"flex cursor-pointer list-none items-center justify-between gap-2 px-3 py-2 font-semibold text-on-surface-variant [&::-webkit-details-marker]:hidden",children:[e("params.centroid"),s.jsx(er,{className:"h-4 w-4 shrink-0"})]}),s.jsxs("div",{className:"border-t border-outline-variant/15 p-3 pt-2",children:[s.jsx("p",{className:"mb-3 text-[10px] leading-relaxed text-on-surface-variant/90",children:e("params.blockCentroidIntro")}),s.jsxs("div",{className:"space-y-2",children:[s.jsx(Xe,{label:e("params.sigma"),helpKey:"params.sigmaHelp",step:.1,value:((ki=f.centroid)==null?void 0:ki.sigma)??"",onChange:c=>v(x=>({...x,centroid:{...x.centroid,sigma:c}}))}),s.jsx(Xe,{label:e("params.maxArea"),helpKey:"params.maxAreaHelp",value:((Ni=f.centroid)==null?void 0:Ni.max_area)??"",onChange:c=>v(x=>({...x,centroid:{...x.centroid,max_area:c}}))}),s.jsx(Xe,{label:e("params.minArea"),helpKey:"params.minAreaHelp",value:((ji=f.centroid)==null?void 0:ji.min_area)??"",onChange:c=>v(x=>({...x,centroid:{...x.centroid,min_area:c}}))}),s.jsx(Xe,{label:e("params.filtsize"),helpKey:"params.filtsizeHelp",step:2,value:((_i=f.centroid)==null?void 0:_i.filtsize)??"",onChange:c=>v(x=>({...x,centroid:{...x.centroid,filtsize:c}}))})]})]})]}),s.jsxs("div",{className:"mt-4 rounded-md border border-outline-variant/20 bg-surface-container-highest/40 p-3",children:[s.jsx("div",{className:"mb-2 font-semibold text-on-surface-variant",children:e("sidebar.batchPresets")}),s.jsx("p",{className:"mb-2 text-[10px] leading-snug text-on-surface-variant",children:e("sidebar.batchHint")}),s.jsx("div",{className:"max-h-36 space-y-1.5 overflow-y-auto",children:[...h,...k].map(c=>s.jsxs("label",{className:"flex cursor-pointer items-center gap-2",children:[s.jsx("input",{type:"checkbox",checked:_.has(c.id),onChange:()=>_f(c.id)}),s.jsx("span",{className:"truncate",children:c.name})]},c.id))})]}),s.jsxs("div",{className:"mt-6 border-t border-outline-variant/20 pt-4",children:[s.jsx("div",{className:"mb-2 font-semibold",children:e("btn.applyPresets")}),s.jsx("div",{className:"max-h-24 space-y-1 overflow-y-auto",children:[...h,...k].map(c=>s.jsx("button",{type:"button",className:"block w-full truncate text-left text-primary hover:underline",onClick:()=>xf(c.params),children:c.name},c.id))}),s.jsxs("div",{className:"mt-3 flex gap-1",children:[s.jsx("input",{className:"flex-1 rounded bg-surface-container-highest px-2 py-1",placeholder:e("placeholder.newPreset"),value:Ut,onChange:c=>b(c.target.value)}),s.jsx("button",{type:"button",className:"rounded bg-surface-container-high px-2",onClick:async()=>{if(Ut.trim()){d(!0);try{await gh(Ut.trim(),f),b(""),await $t();const c=await sa("user");j(c.presets)}catch(c){g(String(c))}finally{d(!1)}}},children:e("btn.savePreset")})]})]})]})]})]}),r==="pool"&&s.jsxs("main",{className:"flex-1 overflow-auto p-4",children:[s.jsxs("h2",{className:"mb-4 flex items-center gap-2 text-lg font-semibold",children:[s.jsx(ah,{className:"h-5 w-5"})," ",e("pool.title")]}),s.jsxs("table",{className:"w-full text-left text-xs",children:[s.jsx("thead",{children:s.jsxs("tr",{className:"border-b border-outline-variant/30 text-on-surface-variant",children:[s.jsx("th",{className:"py-2",children:e("pool.col.name")}),s.jsx("th",{className:"py-2",children:e("pool.col.source")}),s.jsx("th",{className:"py-2",children:e("pool.col.size")}),s.jsx("th",{className:"py-2",children:e("pool.col.time")}),s.jsx("th",{className:"w-16 py-2 text-center",children:e("pool.delete")})]})}),s.jsx("tbody",{children:o.map(c=>s.jsxs("tr",{className:"border-b border-outline-variant/10",children:[s.jsx("td",{className:"py-2 font-mono",children:c.filename}),s.jsx("td",{className:"py-2",children:c.source??e("common.placeholder")}),s.jsx("td",{className:"py-2",children:as(c.size)}),s.jsx("td",{className:"py-2",children:_l(c.modified_at,t)}),s.jsx("td",{className:"py-2 text-center",children:s.jsx("button",{type:"button",className:"inline-flex rounded p-1 text-on-surface-variant hover:bg-error-container/30 hover:text-error",title:e("pool.delete"),"aria-label":e("pool.delete"),onClick:()=>void vi(c.filename),children:s.jsx(Du,{className:"h-3.5 w-3.5"})})})]},c.filename))})]})]}),r==="history"&&s.jsxs("main",{className:"flex-1 overflow-auto p-4",children:[s.jsx("p",{className:"mb-4 rounded-lg border border-outline-variant/25 bg-surface-container-lowest/90 p-3 text-xs leading-relaxed text-on-surface-variant",children:e("history.intro")}),s.jsxs("div",{className:"mb-4 flex flex-wrap items-center gap-4",children:[s.jsxs("h2",{className:"flex items-center gap-2 text-lg font-semibold",children:[s.jsx(ih,{className:"h-5 w-5"})," ",e("history.title")]}),s.jsx("input",{className:"rounded bg-surface-container-highest px-2 py-1 text-xs",placeholder:e("history.search"),value:U,onChange:c=>L(c.target.value)}),s.jsx("button",{type:"button",className:"rounded bg-surface-container px-2 py-1 text-xs",onClick:()=>ua(U,1,fl).then(c=>{nt(1),nn(c)}),children:e("history.searchBtn")}),s.jsx("button",{type:"button",className:"rounded bg-primary-container/50 px-2 py-1 text-xs",onClick:()=>Au("json").then(c=>{const x=new Blob([c],{type:"application/json"}),C=document.createElement("a");C.href=URL.createObjectURL(x),C.download="experiments.json",C.click()}),children:e("history.exportJson")}),s.jsx("button",{type:"button",className:"rounded bg-primary-container/50 px-2 py-1 text-xs",onClick:()=>Au("csv").then(c=>{const x=new Blob([c],{type:"text/csv"}),C=document.createElement("a");C.href=URL.createObjectURL(x),C.download="experiments.csv",C.click()}),children:e("history.exportCsv")})]}),s.jsxs("div",{className:"flex flex-wrap items-center gap-3 text-xs text-on-surface-variant",children:[s.jsx("span",{children:e("history.total",{n:(me==null?void 0:me.total)??0})}),s.jsx("button",{type:"button",className:"rounded border border-outline-variant/30 px-2 py-0.5 disabled:opacity-40",disabled:le<=1,onClick:()=>nt(c=>Math.max(1,c-1)),children:e("history.prev")}),s.jsxs("span",{children:[le," / ",yi]}),s.jsx("button",{type:"button",className:"rounded border border-outline-variant/30 px-2 py-0.5 disabled:opacity-40",disabled:le>=yi,onClick:()=>nt(c=>c+1),children:e("history.next")})]}),s.jsx("ul",{className:"mt-2 space-y-2",children:(Ci=me==null?void 0:me.items)==null?void 0:Ci.map(c=>{const x=String(c.id??""),C=c.metrics,Q=Or===x;return s.jsxs("li",{className:"rounded border border-outline-variant/20 p-2 text-[11px]",children:[s.jsxs("div",{className:"flex flex-wrap items-start justify-between gap-2 font-mono",children:[s.jsxs("div",{children:[s.jsxs("div",{className:"text-on-surface",children:[_l(String(c.created_at??""),t)," —"," ",String(c.input_name)," — ",String(c.preset_label)]}),s.jsxs("div",{className:"mt-1 text-on-surface-variant",children:[e("history.preset"),": ",String(c.preset_label)," · ",e("history.metrics"),":"," ","matches=",String((C==null?void 0:C.matches)??"—")," rmse=",String((C==null?void 0:C.rmse_arcsec)??"—")]})]}),s.jsxs("div",{className:"flex shrink-0 gap-1",children:[s.jsx("button",{type:"button",className:"rounded bg-surface-container px-2 py-0.5 text-[10px]",onClick:()=>rn(Q?null:x),children:e(Q?"history.collapse":"history.detail")}),s.jsx("button",{type:"button",className:"rounded px-2 py-0.5 text-[10px] text-error hover:bg-error-container/20",title:e("history.delete"),onClick:()=>void Cf(x),children:e("history.delete")})]})]}),Q&&s.jsxs("div",{className:"mt-2 space-y-2",children:[c.asset_snapshot_relpath?s.jsx("img",{src:bh(x),alt:"",className:"max-h-48 max-w-full rounded border border-outline-variant/20 object-contain"}):null,s.jsx("pre",{className:"max-h-64 overflow-auto rounded bg-surface-container p-2 text-[10px]",children:JSON.stringify(c.result_json,null,2)})]})]},x)})})]})]})]})}function Qu({result:e,t,roundTripMs:n}){const r=cf(e??void 0);return e?s.jsxs("div",{className:"mt-2 space-y-2 text-[10px]",children:[s.jsxs("div",{className:"grid grid-cols-2 gap-2",children:[r.tBackendTotalMs!=null&&s.jsxs("div",{children:[s.jsx("div",{className:"text-on-surface-variant",children:t("lab.metric.backendTotalMs")}),s.jsxs("div",{className:"font-semibold tabular-nums text-on-surface",children:[r.tBackendTotalMs.toFixed(0)," ms"]})]}),r.tSolveMs!=null&&s.jsxs("div",{children:[s.jsx("div",{className:"text-on-surface-variant",children:t("lab.metric.solveComputeMs")}),s.jsxs("div",{className:"font-semibold tabular-nums text-on-surface",children:[r.tSolveMs.toFixed(0)," ms"]})]}),r.tOpenDecodeMs!=null&&s.jsxs("div",{children:[s.jsx("div",{className:"text-on-surface-variant",children:t("lab.metric.openDecodeMs")}),s.jsxs("div",{className:"font-semibold tabular-nums text-on-surface",children:[r.tOpenDecodeMs.toFixed(0)," ms"]})]}),r.tPreprocessMs!=null&&s.jsxs("div",{children:[s.jsx("div",{className:"text-on-surface-variant",children:t("lab.metric.preprocessMs")}),s.jsxs("div",{className:"font-semibold tabular-nums text-on-surface",children:[r.tPreprocessMs.toFixed(0)," ms"]})]}),r.tExtractMs!=null&&s.jsxs("div",{children:[s.jsx("div",{className:"text-on-surface-variant",children:t("lab.metric.extractMs")}),s.jsxs("div",{className:"font-semibold tabular-nums text-on-surface",children:[r.tExtractMs.toFixed(0)," ms"]})]}),n!=null&&s.jsxs("div",{children:[s.jsx("div",{className:"text-on-surface-variant",children:t("lab.metric.solveRoundTripMs")}),s.jsxs("div",{className:"font-semibold tabular-nums text-on-surface",children:[n.toFixed(0)," ms"]})]}),r.matches!=null&&s.jsxs("div",{children:[s.jsx("div",{className:"text-on-surface-variant",children:t("lab.metric.matches")}),s.jsx("div",{className:"font-semibold tabular-nums text-on-surface",children:r.matches})]}),r.rmseArcsec!=null&&s.jsxs("div",{children:[s.jsx("div",{className:"text-on-surface-variant",children:t("lab.metric.rmse")}),s.jsxs("div",{className:"font-semibold tabular-nums text-on-surface",children:[r.rmseArcsec.toFixed(2),"″"]})]}),r.prob!=null&&s.jsxs("div",{className:"col-span-2",children:[s.jsx("div",{className:"text-on-surface-variant",children:t("lab.metric.prob")}),s.jsx("div",{className:"font-semibold text-on-surface",children:Mn(r.prob,e).line}),s.jsx("p",{className:"mt-0.5 text-[9px] leading-snug text-on-surface-variant/90",children:t("lab.metric.probHelp")}),Mn(r.prob,e).rawLine&&s.jsxs("div",{className:"mt-1 text-[9px] text-on-surface-variant",children:[t("lab.metric.probRaw"),": ",Mn(r.prob,e).rawLine,s.jsx("p",{className:"mt-0.5 text-[8px] leading-snug opacity-90",children:t("lab.metric.probRawHelp")})]})]})]}),s.jsxs("div",{children:[s.jsx("div",{className:"text-on-surface-variant",children:t("lab.metric.radec")}),s.jsxs("div",{className:"font-mono text-[9px] text-on-surface",children:["α ",Xl(r.raDeg)," · δ ",Xl(r.decDeg)]})]}),r.status&&s.jsxs("div",{className:"rounded bg-surface-container-high px-2 py-1 text-[9px] font-mono text-on-surface",children:[t("lab.metric.status"),": ",r.status]})]}):s.jsx("p",{className:"mt-2 text-[10px] text-on-surface-variant",children:"—"})}function Xe({label:e,helpKey:t,value:n,onChange:r,type:l="number",step:o}){const{t:a}=uf(),i=t?a(t):void 0;return s.jsxs("label",{className:"block",children:[s.jsx("span",{className:"text-[10px] font-medium text-on-surface-variant",children:e}),i&&s.jsx("p",{className:"mb-1 mt-0.5 text-[9px] leading-snug text-on-surface-variant/85",children:i}),s.jsx("input",{type:l,step:o,className:"w-full rounded bg-surface-container-highest px-2 py-1",value:n===""?"":n,onChange:u=>{const f=u.target.value;r(f===""?void 0:Number(f))}})]})}pa.createRoot(document.getElementById("root")).render(s.jsx(Wf.StrictMode,{children:s.jsx(Rh,{children:s.jsx(Uh,{})})})); diff --git a/web/static/analysis-lab/assets/index-BTbVhXi4.js b/web/static/analysis-lab/assets/index-BTbVhXi4.js new file mode 100644 index 0000000..8e7a6f2 --- /dev/null +++ b/web/static/analysis-lab/assets/index-BTbVhXi4.js @@ -0,0 +1,125 @@ +var jp=Object.defineProperty;var fu=e=>{throw TypeError(e)};var Ep=(e,t,n)=>t in e?jp(e,t,{enumerable:!0,configurable:!0,writable:!0,value:n}):e[t]=n;var we=(e,t,n)=>Ep(e,typeof t!="symbol"?t+"":t,n),pu=(e,t,n)=>t.has(e)||fu("Cannot "+n);var I=(e,t,n)=>(pu(e,t,"read from private field"),n?n.call(e):t.get(e)),Mt=(e,t,n)=>t.has(e)?fu("Cannot add the same private member more than once"):t instanceof WeakSet?t.add(e):t.set(e,n),ir=(e,t,n,r)=>(pu(e,t,"write to private field"),r?r.call(e,n):t.set(e,n),n);(function(){const t=document.createElement("link").relList;if(t&&t.supports&&t.supports("modulepreload"))return;for(const l of document.querySelectorAll('link[rel="modulepreload"]'))r(l);new MutationObserver(l=>{for(const a of l)if(a.type==="childList")for(const o of a.addedNodes)o.tagName==="LINK"&&o.rel==="modulepreload"&&r(o)}).observe(document,{childList:!0,subtree:!0});function n(l){const a={};return l.integrity&&(a.integrity=l.integrity),l.referrerPolicy&&(a.referrerPolicy=l.referrerPolicy),l.crossOrigin==="use-credentials"?a.credentials="include":l.crossOrigin==="anonymous"?a.credentials="omit":a.credentials="same-origin",a}function r(l){if(l.ep)return;l.ep=!0;const a=n(l);fetch(l.href,a)}})();function bp(e){return e&&e.__esModule&&Object.prototype.hasOwnProperty.call(e,"default")?e.default:e}var Dc={exports:{}},ga={},Uc={exports:{}},z={};/** + * @license React + * react.production.min.js + * + * Copyright (c) Facebook, Inc. and its affiliates. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + */var Yr=Symbol.for("react.element"),Cp=Symbol.for("react.portal"),Pp=Symbol.for("react.fragment"),Rp=Symbol.for("react.strict_mode"),Mp=Symbol.for("react.profiler"),Lp=Symbol.for("react.provider"),Tp=Symbol.for("react.context"),Ip=Symbol.for("react.forward_ref"),Op=Symbol.for("react.suspense"),zp=Symbol.for("react.memo"),Fp=Symbol.for("react.lazy"),mu=Symbol.iterator;function Dp(e){return e===null||typeof e!="object"?null:(e=mu&&e[mu]||e["@@iterator"],typeof e=="function"?e:null)}var Ac={isMounted:function(){return!1},enqueueForceUpdate:function(){},enqueueReplaceState:function(){},enqueueSetState:function(){}},$c=Object.assign,Hc={};function er(e,t,n){this.props=e,this.context=t,this.refs=Hc,this.updater=n||Ac}er.prototype.isReactComponent={};er.prototype.setState=function(e,t){if(typeof e!="object"&&typeof e!="function"&&e!=null)throw Error("setState(...): takes an object of state variables to update or a function which returns an object of state variables.");this.updater.enqueueSetState(this,e,t,"setState")};er.prototype.forceUpdate=function(e){this.updater.enqueueForceUpdate(this,e,"forceUpdate")};function Bc(){}Bc.prototype=er.prototype;function Ds(e,t,n){this.props=e,this.context=t,this.refs=Hc,this.updater=n||Ac}var Us=Ds.prototype=new Bc;Us.constructor=Ds;$c(Us,er.prototype);Us.isPureReactComponent=!0;var hu=Array.isArray,Vc=Object.prototype.hasOwnProperty,As={current:null},Wc={key:!0,ref:!0,__self:!0,__source:!0};function Qc(e,t,n){var r,l={},a=null,o=null;if(t!=null)for(r in t.ref!==void 0&&(o=t.ref),t.key!==void 0&&(a=""+t.key),t)Vc.call(t,r)&&!Wc.hasOwnProperty(r)&&(l[r]=t[r]);var i=arguments.length-2;if(i===1)l.children=n;else if(1>>1,ee=C[V];if(0>>1;Vl(dt,T))Lel(ft,dt)?(C[V]=ft,C[Le]=T,V=Le):(C[V]=dt,C[nt]=T,V=nt);else if(Lel(ft,T))C[V]=ft,C[Le]=T,V=Le;else break e}}return L}function l(C,L){var T=C.sortIndex-L.sortIndex;return T!==0?T:C.id-L.id}if(typeof performance=="object"&&typeof performance.now=="function"){var a=performance;e.unstable_now=function(){return a.now()}}else{var o=Date,i=o.now();e.unstable_now=function(){return o.now()-i}}var c=[],f=[],g=1,h=null,y=3,k=!1,j=!1,E=!1,B=typeof setTimeout=="function"?setTimeout:null,p=typeof clearTimeout=="function"?clearTimeout:null,d=typeof setImmediate<"u"?setImmediate:null;typeof navigator<"u"&&navigator.scheduling!==void 0&&navigator.scheduling.isInputPending!==void 0&&navigator.scheduling.isInputPending.bind(navigator.scheduling);function m(C){for(var L=n(f);L!==null;){if(L.callback===null)r(f);else if(L.startTime<=C)r(f),L.sortIndex=L.expirationTime,t(c,L);else break;L=n(f)}}function x(C){if(E=!1,m(C),!j)if(n(c)!==null)j=!0,wn(N);else{var L=n(f);L!==null&&qt(x,L.startTime-C)}}function N(C,L){j=!1,E&&(E=!1,p(M),M=-1),k=!0;var T=y;try{for(m(L),h=n(c);h!==null&&(!(h.expirationTime>L)||C&&!se());){var V=h.callback;if(typeof V=="function"){h.callback=null,y=h.priorityLevel;var ee=V(h.expirationTime<=L);L=e.unstable_now(),typeof ee=="function"?h.callback=ee:h===n(c)&&r(c),m(L)}else r(c);h=n(c)}if(h!==null)var pe=!0;else{var nt=n(f);nt!==null&&qt(x,nt.startTime-L),pe=!1}return pe}finally{h=null,y=T,k=!1}}var R=!1,b=null,M=-1,$=5,O=-1;function se(){return!(e.unstable_now()-O<$)}function ct(){if(b!==null){var C=e.unstable_now();O=C;var L=!0;try{L=b(!0,C)}finally{L?xe():(R=!1,b=null)}}else R=!1}var xe;if(typeof d=="function")xe=function(){d(ct)};else if(typeof MessageChannel<"u"){var xn=new MessageChannel,el=xn.port2;xn.port1.onmessage=ct,xe=function(){el.postMessage(null)}}else xe=function(){B(ct,0)};function wn(C){b=C,R||(R=!0,xe())}function qt(C,L){M=B(function(){C(e.unstable_now())},L)}e.unstable_IdlePriority=5,e.unstable_ImmediatePriority=1,e.unstable_LowPriority=4,e.unstable_NormalPriority=3,e.unstable_Profiling=null,e.unstable_UserBlockingPriority=2,e.unstable_cancelCallback=function(C){C.callback=null},e.unstable_continueExecution=function(){j||k||(j=!0,wn(N))},e.unstable_forceFrameRate=function(C){0>C||125V?(C.sortIndex=T,t(f,C),n(c)===null&&C===n(f)&&(E?(p(M),M=-1):E=!0,qt(x,T-V))):(C.sortIndex=ee,t(c,C),j||k||(j=!0,wn(N))),C},e.unstable_shouldYield=se,e.unstable_wrapCallback=function(C){var L=y;return function(){var T=y;y=L;try{return C.apply(this,arguments)}finally{y=T}}}})(Jc);Xc.exports=Jc;var Xp=Xc.exports;/** + * @license React + * react-dom.production.min.js + * + * Copyright (c) Facebook, Inc. and its affiliates. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + */var Jp=w,Fe=Xp;function S(e){for(var t="https://reactjs.org/docs/error-decoder.html?invariant="+e,n=1;n"u"||typeof window.document>"u"||typeof window.document.createElement>"u"),Wo=Object.prototype.hasOwnProperty,Zp=/^[:A-Z_a-z\u00C0-\u00D6\u00D8-\u00F6\u00F8-\u02FF\u0370-\u037D\u037F-\u1FFF\u200C-\u200D\u2070-\u218F\u2C00-\u2FEF\u3001-\uD7FF\uF900-\uFDCF\uFDF0-\uFFFD][:A-Z_a-z\u00C0-\u00D6\u00D8-\u00F6\u00F8-\u02FF\u0370-\u037D\u037F-\u1FFF\u200C-\u200D\u2070-\u218F\u2C00-\u2FEF\u3001-\uD7FF\uF900-\uFDCF\uFDF0-\uFFFD\-.0-9\u00B7\u0300-\u036F\u203F-\u2040]*$/,yu={},gu={};function qp(e){return Wo.call(gu,e)?!0:Wo.call(yu,e)?!1:Zp.test(e)?gu[e]=!0:(yu[e]=!0,!1)}function em(e,t,n,r){if(n!==null&&n.type===0)return!1;switch(typeof t){case"function":case"symbol":return!0;case"boolean":return r?!1:n!==null?!n.acceptsBooleans:(e=e.toLowerCase().slice(0,5),e!=="data-"&&e!=="aria-");default:return!1}}function tm(e,t,n,r){if(t===null||typeof t>"u"||em(e,t,n,r))return!0;if(r)return!1;if(n!==null)switch(n.type){case 3:return!t;case 4:return t===!1;case 5:return isNaN(t);case 6:return isNaN(t)||1>t}return!1}function je(e,t,n,r,l,a,o){this.acceptsBooleans=t===2||t===3||t===4,this.attributeName=r,this.attributeNamespace=l,this.mustUseProperty=n,this.propertyName=e,this.type=t,this.sanitizeURL=a,this.removeEmptyString=o}var fe={};"children dangerouslySetInnerHTML defaultValue defaultChecked innerHTML suppressContentEditableWarning suppressHydrationWarning style".split(" ").forEach(function(e){fe[e]=new je(e,0,!1,e,null,!1,!1)});[["acceptCharset","accept-charset"],["className","class"],["htmlFor","for"],["httpEquiv","http-equiv"]].forEach(function(e){var t=e[0];fe[t]=new je(t,1,!1,e[1],null,!1,!1)});["contentEditable","draggable","spellCheck","value"].forEach(function(e){fe[e]=new je(e,2,!1,e.toLowerCase(),null,!1,!1)});["autoReverse","externalResourcesRequired","focusable","preserveAlpha"].forEach(function(e){fe[e]=new je(e,2,!1,e,null,!1,!1)});"allowFullScreen async autoFocus autoPlay controls default defer disabled disablePictureInPicture disableRemotePlayback formNoValidate hidden loop noModule noValidate open playsInline readOnly required reversed scoped seamless itemScope".split(" ").forEach(function(e){fe[e]=new je(e,3,!1,e.toLowerCase(),null,!1,!1)});["checked","multiple","muted","selected"].forEach(function(e){fe[e]=new je(e,3,!0,e,null,!1,!1)});["capture","download"].forEach(function(e){fe[e]=new je(e,4,!1,e,null,!1,!1)});["cols","rows","size","span"].forEach(function(e){fe[e]=new je(e,6,!1,e,null,!1,!1)});["rowSpan","start"].forEach(function(e){fe[e]=new je(e,5,!1,e.toLowerCase(),null,!1,!1)});var Hs=/[\-:]([a-z])/g;function Bs(e){return e[1].toUpperCase()}"accent-height alignment-baseline arabic-form baseline-shift cap-height clip-path clip-rule color-interpolation color-interpolation-filters color-profile color-rendering dominant-baseline enable-background fill-opacity fill-rule flood-color flood-opacity font-family font-size font-size-adjust font-stretch font-style font-variant font-weight glyph-name glyph-orientation-horizontal glyph-orientation-vertical horiz-adv-x horiz-origin-x image-rendering letter-spacing lighting-color marker-end marker-mid marker-start overline-position overline-thickness paint-order panose-1 pointer-events rendering-intent shape-rendering stop-color stop-opacity strikethrough-position strikethrough-thickness stroke-dasharray stroke-dashoffset stroke-linecap stroke-linejoin stroke-miterlimit stroke-opacity stroke-width text-anchor text-decoration text-rendering underline-position underline-thickness unicode-bidi unicode-range units-per-em v-alphabetic v-hanging v-ideographic v-mathematical vector-effect vert-adv-y vert-origin-x vert-origin-y word-spacing writing-mode xmlns:xlink x-height".split(" ").forEach(function(e){var t=e.replace(Hs,Bs);fe[t]=new je(t,1,!1,e,null,!1,!1)});"xlink:actuate xlink:arcrole xlink:role xlink:show xlink:title xlink:type".split(" ").forEach(function(e){var t=e.replace(Hs,Bs);fe[t]=new je(t,1,!1,e,"http://www.w3.org/1999/xlink",!1,!1)});["xml:base","xml:lang","xml:space"].forEach(function(e){var t=e.replace(Hs,Bs);fe[t]=new je(t,1,!1,e,"http://www.w3.org/XML/1998/namespace",!1,!1)});["tabIndex","crossOrigin"].forEach(function(e){fe[e]=new je(e,1,!1,e.toLowerCase(),null,!1,!1)});fe.xlinkHref=new je("xlinkHref",1,!1,"xlink:href","http://www.w3.org/1999/xlink",!0,!1);["src","href","action","formAction"].forEach(function(e){fe[e]=new je(e,1,!1,e.toLowerCase(),null,!0,!0)});function Vs(e,t,n,r){var l=fe.hasOwnProperty(t)?fe[t]:null;(l!==null?l.type!==0:r||!(2i||l[o]!==a[i]){var c=` +`+l[o].replace(" at new "," at ");return e.displayName&&c.includes("")&&(c=c.replace("",e.displayName)),c}while(1<=o&&0<=i);break}}}finally{uo=!1,Error.prepareStackTrace=n}return(e=e?e.displayName||e.name:"")?xr(e):""}function nm(e){switch(e.tag){case 5:return xr(e.type);case 16:return xr("Lazy");case 13:return xr("Suspense");case 19:return xr("SuspenseList");case 0:case 2:case 15:return e=co(e.type,!1),e;case 11:return e=co(e.type.render,!1),e;case 1:return e=co(e.type,!0),e;default:return""}}function Yo(e){if(e==null)return null;if(typeof e=="function")return e.displayName||e.name||null;if(typeof e=="string")return e;switch(e){case Pn:return"Fragment";case Cn:return"Portal";case Qo:return"Profiler";case Ws:return"StrictMode";case Ko:return"Suspense";case Go:return"SuspenseList"}if(typeof e=="object")switch(e.$$typeof){case ed:return(e.displayName||"Context")+".Consumer";case qc:return(e._context.displayName||"Context")+".Provider";case Qs:var t=e.render;return e=e.displayName,e||(e=t.displayName||t.name||"",e=e!==""?"ForwardRef("+e+")":"ForwardRef"),e;case Ks:return t=e.displayName||null,t!==null?t:Yo(e.type)||"Memo";case Tt:t=e._payload,e=e._init;try{return Yo(e(t))}catch{}}return null}function rm(e){var t=e.type;switch(e.tag){case 24:return"Cache";case 9:return(t.displayName||"Context")+".Consumer";case 10:return(t._context.displayName||"Context")+".Provider";case 18:return"DehydratedFragment";case 11:return e=t.render,e=e.displayName||e.name||"",t.displayName||(e!==""?"ForwardRef("+e+")":"ForwardRef");case 7:return"Fragment";case 5:return t;case 4:return"Portal";case 3:return"Root";case 6:return"Text";case 16:return Yo(t);case 8:return t===Ws?"StrictMode":"Mode";case 22:return"Offscreen";case 12:return"Profiler";case 21:return"Scope";case 13:return"Suspense";case 19:return"SuspenseList";case 25:return"TracingMarker";case 1:case 0:case 17:case 2:case 14:case 15:if(typeof t=="function")return t.displayName||t.name||null;if(typeof t=="string")return t}return null}function Gt(e){switch(typeof e){case"boolean":case"number":case"string":case"undefined":return e;case"object":return e;default:return""}}function nd(e){var t=e.type;return(e=e.nodeName)&&e.toLowerCase()==="input"&&(t==="checkbox"||t==="radio")}function lm(e){var t=nd(e)?"checked":"value",n=Object.getOwnPropertyDescriptor(e.constructor.prototype,t),r=""+e[t];if(!e.hasOwnProperty(t)&&typeof n<"u"&&typeof n.get=="function"&&typeof n.set=="function"){var l=n.get,a=n.set;return Object.defineProperty(e,t,{configurable:!0,get:function(){return l.call(this)},set:function(o){r=""+o,a.call(this,o)}}),Object.defineProperty(e,t,{enumerable:n.enumerable}),{getValue:function(){return r},setValue:function(o){r=""+o},stopTracking:function(){e._valueTracker=null,delete e[t]}}}}function vl(e){e._valueTracker||(e._valueTracker=lm(e))}function rd(e){if(!e)return!1;var t=e._valueTracker;if(!t)return!0;var n=t.getValue(),r="";return e&&(r=nd(e)?e.checked?"true":"false":e.value),e=r,e!==n?(t.setValue(e),!0):!1}function Ql(e){if(e=e||(typeof document<"u"?document:void 0),typeof e>"u")return null;try{return e.activeElement||e.body}catch{return e.body}}function Xo(e,t){var n=t.checked;return Z({},t,{defaultChecked:void 0,defaultValue:void 0,value:void 0,checked:n??e._wrapperState.initialChecked})}function wu(e,t){var n=t.defaultValue==null?"":t.defaultValue,r=t.checked!=null?t.checked:t.defaultChecked;n=Gt(t.value!=null?t.value:n),e._wrapperState={initialChecked:r,initialValue:n,controlled:t.type==="checkbox"||t.type==="radio"?t.checked!=null:t.value!=null}}function ld(e,t){t=t.checked,t!=null&&Vs(e,"checked",t,!1)}function Jo(e,t){ld(e,t);var n=Gt(t.value),r=t.type;if(n!=null)r==="number"?(n===0&&e.value===""||e.value!=n)&&(e.value=""+n):e.value!==""+n&&(e.value=""+n);else if(r==="submit"||r==="reset"){e.removeAttribute("value");return}t.hasOwnProperty("value")?Zo(e,t.type,n):t.hasOwnProperty("defaultValue")&&Zo(e,t.type,Gt(t.defaultValue)),t.checked==null&&t.defaultChecked!=null&&(e.defaultChecked=!!t.defaultChecked)}function Su(e,t,n){if(t.hasOwnProperty("value")||t.hasOwnProperty("defaultValue")){var r=t.type;if(!(r!=="submit"&&r!=="reset"||t.value!==void 0&&t.value!==null))return;t=""+e._wrapperState.initialValue,n||t===e.value||(e.value=t),e.defaultValue=t}n=e.name,n!==""&&(e.name=""),e.defaultChecked=!!e._wrapperState.initialChecked,n!==""&&(e.name=n)}function Zo(e,t,n){(t!=="number"||Ql(e.ownerDocument)!==e)&&(n==null?e.defaultValue=""+e._wrapperState.initialValue:e.defaultValue!==""+n&&(e.defaultValue=""+n))}var wr=Array.isArray;function An(e,t,n,r){if(e=e.options,t){t={};for(var l=0;l"+t.valueOf().toString()+"",t=yl.firstChild;e.firstChild;)e.removeChild(e.firstChild);for(;t.firstChild;)e.appendChild(t.firstChild)}});function Tr(e,t){if(t){var n=e.firstChild;if(n&&n===e.lastChild&&n.nodeType===3){n.nodeValue=t;return}}e.textContent=t}var Nr={animationIterationCount:!0,aspectRatio:!0,borderImageOutset:!0,borderImageSlice:!0,borderImageWidth:!0,boxFlex:!0,boxFlexGroup:!0,boxOrdinalGroup:!0,columnCount:!0,columns:!0,flex:!0,flexGrow:!0,flexPositive:!0,flexShrink:!0,flexNegative:!0,flexOrder:!0,gridArea:!0,gridRow:!0,gridRowEnd:!0,gridRowSpan:!0,gridRowStart:!0,gridColumn:!0,gridColumnEnd:!0,gridColumnSpan:!0,gridColumnStart:!0,fontWeight:!0,lineClamp:!0,lineHeight:!0,opacity:!0,order:!0,orphans:!0,tabSize:!0,widows:!0,zIndex:!0,zoom:!0,fillOpacity:!0,floodOpacity:!0,stopOpacity:!0,strokeDasharray:!0,strokeDashoffset:!0,strokeMiterlimit:!0,strokeOpacity:!0,strokeWidth:!0},am=["Webkit","ms","Moz","O"];Object.keys(Nr).forEach(function(e){am.forEach(function(t){t=t+e.charAt(0).toUpperCase()+e.substring(1),Nr[t]=Nr[e]})});function id(e,t,n){return t==null||typeof t=="boolean"||t===""?"":n||typeof t!="number"||t===0||Nr.hasOwnProperty(e)&&Nr[e]?(""+t).trim():t+"px"}function ud(e,t){e=e.style;for(var n in t)if(t.hasOwnProperty(n)){var r=n.indexOf("--")===0,l=id(n,t[n],r);n==="float"&&(n="cssFloat"),r?e.setProperty(n,l):e[n]=l}}var om=Z({menuitem:!0},{area:!0,base:!0,br:!0,col:!0,embed:!0,hr:!0,img:!0,input:!0,keygen:!0,link:!0,meta:!0,param:!0,source:!0,track:!0,wbr:!0});function ts(e,t){if(t){if(om[e]&&(t.children!=null||t.dangerouslySetInnerHTML!=null))throw Error(S(137,e));if(t.dangerouslySetInnerHTML!=null){if(t.children!=null)throw Error(S(60));if(typeof t.dangerouslySetInnerHTML!="object"||!("__html"in t.dangerouslySetInnerHTML))throw Error(S(61))}if(t.style!=null&&typeof t.style!="object")throw Error(S(62))}}function ns(e,t){if(e.indexOf("-")===-1)return typeof t.is=="string";switch(e){case"annotation-xml":case"color-profile":case"font-face":case"font-face-src":case"font-face-uri":case"font-face-format":case"font-face-name":case"missing-glyph":return!1;default:return!0}}var rs=null;function Gs(e){return e=e.target||e.srcElement||window,e.correspondingUseElement&&(e=e.correspondingUseElement),e.nodeType===3?e.parentNode:e}var ls=null,$n=null,Hn=null;function _u(e){if(e=Zr(e)){if(typeof ls!="function")throw Error(S(280));var t=e.stateNode;t&&(t=Na(t),ls(e.stateNode,e.type,t))}}function cd(e){$n?Hn?Hn.push(e):Hn=[e]:$n=e}function dd(){if($n){var e=$n,t=Hn;if(Hn=$n=null,_u(e),t)for(e=0;e>>=0,e===0?32:31-(ym(e)/gm|0)|0}var gl=64,xl=4194304;function Sr(e){switch(e&-e){case 1:return 1;case 2:return 2;case 4:return 4;case 8:return 8;case 16:return 16;case 32:return 32;case 64: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 e&4194240;case 4194304:case 8388608:case 16777216:case 33554432:case 67108864:return e&130023424;case 134217728:return 134217728;case 268435456:return 268435456;case 536870912:return 536870912;case 1073741824:return 1073741824;default:return e}}function Xl(e,t){var n=e.pendingLanes;if(n===0)return 0;var r=0,l=e.suspendedLanes,a=e.pingedLanes,o=n&268435455;if(o!==0){var i=o&~l;i!==0?r=Sr(i):(a&=o,a!==0&&(r=Sr(a)))}else o=n&~l,o!==0?r=Sr(o):a!==0&&(r=Sr(a));if(r===0)return 0;if(t!==0&&t!==r&&!(t&l)&&(l=r&-r,a=t&-t,l>=a||l===16&&(a&4194240)!==0))return t;if(r&4&&(r|=n&16),t=e.entangledLanes,t!==0)for(e=e.entanglements,t&=r;0n;n++)t.push(e);return t}function Xr(e,t,n){e.pendingLanes|=t,t!==536870912&&(e.suspendedLanes=0,e.pingedLanes=0),e=e.eventTimes,t=31-Ze(t),e[t]=n}function km(e,t){var n=e.pendingLanes&~t;e.pendingLanes=t,e.suspendedLanes=0,e.pingedLanes=0,e.expiredLanes&=t,e.mutableReadLanes&=t,e.entangledLanes&=t,t=e.entanglements;var r=e.eventTimes;for(e=e.expirationTimes;0=jr),Tu=" ",Iu=!1;function Md(e,t){switch(e){case"keyup":return Xm.indexOf(t.keyCode)!==-1;case"keydown":return t.keyCode!==229;case"keypress":case"mousedown":case"focusout":return!0;default:return!1}}function Ld(e){return e=e.detail,typeof e=="object"&&"data"in e?e.data:null}var Rn=!1;function Zm(e,t){switch(e){case"compositionend":return Ld(t);case"keypress":return t.which!==32?null:(Iu=!0,Tu);case"textInput":return e=t.data,e===Tu&&Iu?null:e;default:return null}}function qm(e,t){if(Rn)return e==="compositionend"||!ni&&Md(e,t)?(e=Pd(),zl=qs=Dt=null,Rn=!1,e):null;switch(e){case"paste":return null;case"keypress":if(!(t.ctrlKey||t.altKey||t.metaKey)||t.ctrlKey&&t.altKey){if(t.char&&1=t)return{node:n,offset:t-e};e=r}e:{for(;n;){if(n.nextSibling){n=n.nextSibling;break e}n=n.parentNode}n=void 0}n=Du(n)}}function zd(e,t){return e&&t?e===t?!0:e&&e.nodeType===3?!1:t&&t.nodeType===3?zd(e,t.parentNode):"contains"in e?e.contains(t):e.compareDocumentPosition?!!(e.compareDocumentPosition(t)&16):!1:!1}function Fd(){for(var e=window,t=Ql();t instanceof e.HTMLIFrameElement;){try{var n=typeof t.contentWindow.location.href=="string"}catch{n=!1}if(n)e=t.contentWindow;else break;t=Ql(e.document)}return t}function ri(e){var t=e&&e.nodeName&&e.nodeName.toLowerCase();return t&&(t==="input"&&(e.type==="text"||e.type==="search"||e.type==="tel"||e.type==="url"||e.type==="password")||t==="textarea"||e.contentEditable==="true")}function ih(e){var t=Fd(),n=e.focusedElem,r=e.selectionRange;if(t!==n&&n&&n.ownerDocument&&zd(n.ownerDocument.documentElement,n)){if(r!==null&&ri(n)){if(t=r.start,e=r.end,e===void 0&&(e=t),"selectionStart"in n)n.selectionStart=t,n.selectionEnd=Math.min(e,n.value.length);else if(e=(t=n.ownerDocument||document)&&t.defaultView||window,e.getSelection){e=e.getSelection();var l=n.textContent.length,a=Math.min(r.start,l);r=r.end===void 0?a:Math.min(r.end,l),!e.extend&&a>r&&(l=r,r=a,a=l),l=Uu(n,a);var o=Uu(n,r);l&&o&&(e.rangeCount!==1||e.anchorNode!==l.node||e.anchorOffset!==l.offset||e.focusNode!==o.node||e.focusOffset!==o.offset)&&(t=t.createRange(),t.setStart(l.node,l.offset),e.removeAllRanges(),a>r?(e.addRange(t),e.extend(o.node,o.offset)):(t.setEnd(o.node,o.offset),e.addRange(t)))}}for(t=[],e=n;e=e.parentNode;)e.nodeType===1&&t.push({element:e,left:e.scrollLeft,top:e.scrollTop});for(typeof n.focus=="function"&&n.focus(),n=0;n=document.documentMode,Mn=null,cs=null,br=null,ds=!1;function Au(e,t,n){var r=n.window===n?n.document:n.nodeType===9?n:n.ownerDocument;ds||Mn==null||Mn!==Ql(r)||(r=Mn,"selectionStart"in r&&ri(r)?r={start:r.selectionStart,end:r.selectionEnd}:(r=(r.ownerDocument&&r.ownerDocument.defaultView||window).getSelection(),r={anchorNode:r.anchorNode,anchorOffset:r.anchorOffset,focusNode:r.focusNode,focusOffset:r.focusOffset}),br&&Ur(br,r)||(br=r,r=ql(cs,"onSelect"),0In||(e.current=ys[In],ys[In]=null,In--)}function W(e,t){In++,ys[In]=e.current,e.current=t}var Yt={},ge=Jt(Yt),Pe=Jt(!1),fn=Yt;function Gn(e,t){var n=e.type.contextTypes;if(!n)return Yt;var r=e.stateNode;if(r&&r.__reactInternalMemoizedUnmaskedChildContext===t)return r.__reactInternalMemoizedMaskedChildContext;var l={},a;for(a in n)l[a]=t[a];return r&&(e=e.stateNode,e.__reactInternalMemoizedUnmaskedChildContext=t,e.__reactInternalMemoizedMaskedChildContext=l),l}function Re(e){return e=e.childContextTypes,e!=null}function ta(){K(Pe),K(ge)}function Ku(e,t,n){if(ge.current!==Yt)throw Error(S(168));W(ge,t),W(Pe,n)}function Qd(e,t,n){var r=e.stateNode;if(t=t.childContextTypes,typeof r.getChildContext!="function")return n;r=r.getChildContext();for(var l in r)if(!(l in t))throw Error(S(108,rm(e)||"Unknown",l));return Z({},n,r)}function na(e){return e=(e=e.stateNode)&&e.__reactInternalMemoizedMergedChildContext||Yt,fn=ge.current,W(ge,e),W(Pe,Pe.current),!0}function Gu(e,t,n){var r=e.stateNode;if(!r)throw Error(S(169));n?(e=Qd(e,t,fn),r.__reactInternalMemoizedMergedChildContext=e,K(Pe),K(ge),W(ge,e)):K(Pe),W(Pe,n)}var gt=null,_a=!1,jo=!1;function Kd(e){gt===null?gt=[e]:gt.push(e)}function wh(e){_a=!0,Kd(e)}function Zt(){if(!jo&>!==null){jo=!0;var e=0,t=A;try{var n=gt;for(A=1;e>=o,l-=o,wt=1<<32-Ze(t)+l|n<M?($=b,b=null):$=b.sibling;var O=y(p,b,m[M],x);if(O===null){b===null&&(b=$);break}e&&b&&O.alternate===null&&t(p,b),d=a(O,d,M),R===null?N=O:R.sibling=O,R=O,b=$}if(M===m.length)return n(p,b),G&&nn(p,M),N;if(b===null){for(;MM?($=b,b=null):$=b.sibling;var se=y(p,b,O.value,x);if(se===null){b===null&&(b=$);break}e&&b&&se.alternate===null&&t(p,b),d=a(se,d,M),R===null?N=se:R.sibling=se,R=se,b=$}if(O.done)return n(p,b),G&&nn(p,M),N;if(b===null){for(;!O.done;M++,O=m.next())O=h(p,O.value,x),O!==null&&(d=a(O,d,M),R===null?N=O:R.sibling=O,R=O);return G&&nn(p,M),N}for(b=r(p,b);!O.done;M++,O=m.next())O=k(b,p,M,O.value,x),O!==null&&(e&&O.alternate!==null&&b.delete(O.key===null?M:O.key),d=a(O,d,M),R===null?N=O:R.sibling=O,R=O);return e&&b.forEach(function(ct){return t(p,ct)}),G&&nn(p,M),N}function B(p,d,m,x){if(typeof m=="object"&&m!==null&&m.type===Pn&&m.key===null&&(m=m.props.children),typeof m=="object"&&m!==null){switch(m.$$typeof){case hl:e:{for(var N=m.key,R=d;R!==null;){if(R.key===N){if(N=m.type,N===Pn){if(R.tag===7){n(p,R.sibling),d=l(R,m.props.children),d.return=p,p=d;break e}}else if(R.elementType===N||typeof N=="object"&&N!==null&&N.$$typeof===Tt&&Ju(N)===R.type){n(p,R.sibling),d=l(R,m.props),d.ref=mr(p,R,m),d.return=p,p=d;break e}n(p,R);break}else t(p,R);R=R.sibling}m.type===Pn?(d=dn(m.props.children,p.mode,x,m.key),d.return=p,p=d):(x=Vl(m.type,m.key,m.props,null,p.mode,x),x.ref=mr(p,d,m),x.return=p,p=x)}return o(p);case Cn:e:{for(R=m.key;d!==null;){if(d.key===R)if(d.tag===4&&d.stateNode.containerInfo===m.containerInfo&&d.stateNode.implementation===m.implementation){n(p,d.sibling),d=l(d,m.children||[]),d.return=p,p=d;break e}else{n(p,d);break}else t(p,d);d=d.sibling}d=To(m,p.mode,x),d.return=p,p=d}return o(p);case Tt:return R=m._init,B(p,d,R(m._payload),x)}if(wr(m))return j(p,d,m,x);if(ur(m))return E(p,d,m,x);El(p,m)}return typeof m=="string"&&m!==""||typeof m=="number"?(m=""+m,d!==null&&d.tag===6?(n(p,d.sibling),d=l(d,m),d.return=p,p=d):(n(p,d),d=Lo(m,p.mode,x),d.return=p,p=d),o(p)):n(p,d)}return B}var Xn=Jd(!0),Zd=Jd(!1),aa=Jt(null),oa=null,Fn=null,si=null;function ii(){si=Fn=oa=null}function ui(e){var t=aa.current;K(aa),e._currentValue=t}function ws(e,t,n){for(;e!==null;){var r=e.alternate;if((e.childLanes&t)!==t?(e.childLanes|=t,r!==null&&(r.childLanes|=t)):r!==null&&(r.childLanes&t)!==t&&(r.childLanes|=t),e===n)break;e=e.return}}function Vn(e,t){oa=e,si=Fn=null,e=e.dependencies,e!==null&&e.firstContext!==null&&(e.lanes&t&&(Ce=!0),e.firstContext=null)}function We(e){var t=e._currentValue;if(si!==e)if(e={context:e,memoizedValue:t,next:null},Fn===null){if(oa===null)throw Error(S(308));Fn=e,oa.dependencies={lanes:0,firstContext:e}}else Fn=Fn.next=e;return t}var an=null;function ci(e){an===null?an=[e]:an.push(e)}function qd(e,t,n,r){var l=t.interleaved;return l===null?(n.next=n,ci(t)):(n.next=l.next,l.next=n),t.interleaved=n,jt(e,r)}function jt(e,t){e.lanes|=t;var n=e.alternate;for(n!==null&&(n.lanes|=t),n=e,e=e.return;e!==null;)e.childLanes|=t,n=e.alternate,n!==null&&(n.childLanes|=t),n=e,e=e.return;return n.tag===3?n.stateNode:null}var It=!1;function di(e){e.updateQueue={baseState:e.memoizedState,firstBaseUpdate:null,lastBaseUpdate:null,shared:{pending:null,interleaved:null,lanes:0},effects:null}}function ef(e,t){e=e.updateQueue,t.updateQueue===e&&(t.updateQueue={baseState:e.baseState,firstBaseUpdate:e.firstBaseUpdate,lastBaseUpdate:e.lastBaseUpdate,shared:e.shared,effects:e.effects})}function kt(e,t){return{eventTime:e,lane:t,tag:0,payload:null,callback:null,next:null}}function Vt(e,t,n){var r=e.updateQueue;if(r===null)return null;if(r=r.shared,F&2){var l=r.pending;return l===null?t.next=t:(t.next=l.next,l.next=t),r.pending=t,jt(e,n)}return l=r.interleaved,l===null?(t.next=t,ci(r)):(t.next=l.next,l.next=t),r.interleaved=t,jt(e,n)}function Dl(e,t,n){if(t=t.updateQueue,t!==null&&(t=t.shared,(n&4194240)!==0)){var r=t.lanes;r&=e.pendingLanes,n|=r,t.lanes=n,Xs(e,n)}}function Zu(e,t){var n=e.updateQueue,r=e.alternate;if(r!==null&&(r=r.updateQueue,n===r)){var l=null,a=null;if(n=n.firstBaseUpdate,n!==null){do{var o={eventTime:n.eventTime,lane:n.lane,tag:n.tag,payload:n.payload,callback:n.callback,next:null};a===null?l=a=o:a=a.next=o,n=n.next}while(n!==null);a===null?l=a=t:a=a.next=t}else l=a=t;n={baseState:r.baseState,firstBaseUpdate:l,lastBaseUpdate:a,shared:r.shared,effects:r.effects},e.updateQueue=n;return}e=n.lastBaseUpdate,e===null?n.firstBaseUpdate=t:e.next=t,n.lastBaseUpdate=t}function sa(e,t,n,r){var l=e.updateQueue;It=!1;var a=l.firstBaseUpdate,o=l.lastBaseUpdate,i=l.shared.pending;if(i!==null){l.shared.pending=null;var c=i,f=c.next;c.next=null,o===null?a=f:o.next=f,o=c;var g=e.alternate;g!==null&&(g=g.updateQueue,i=g.lastBaseUpdate,i!==o&&(i===null?g.firstBaseUpdate=f:i.next=f,g.lastBaseUpdate=c))}if(a!==null){var h=l.baseState;o=0,g=f=c=null,i=a;do{var y=i.lane,k=i.eventTime;if((r&y)===y){g!==null&&(g=g.next={eventTime:k,lane:0,tag:i.tag,payload:i.payload,callback:i.callback,next:null});e:{var j=e,E=i;switch(y=t,k=n,E.tag){case 1:if(j=E.payload,typeof j=="function"){h=j.call(k,h,y);break e}h=j;break e;case 3:j.flags=j.flags&-65537|128;case 0:if(j=E.payload,y=typeof j=="function"?j.call(k,h,y):j,y==null)break e;h=Z({},h,y);break e;case 2:It=!0}}i.callback!==null&&i.lane!==0&&(e.flags|=64,y=l.effects,y===null?l.effects=[i]:y.push(i))}else k={eventTime:k,lane:y,tag:i.tag,payload:i.payload,callback:i.callback,next:null},g===null?(f=g=k,c=h):g=g.next=k,o|=y;if(i=i.next,i===null){if(i=l.shared.pending,i===null)break;y=i,i=y.next,y.next=null,l.lastBaseUpdate=y,l.shared.pending=null}}while(!0);if(g===null&&(c=h),l.baseState=c,l.firstBaseUpdate=f,l.lastBaseUpdate=g,t=l.shared.interleaved,t!==null){l=t;do o|=l.lane,l=l.next;while(l!==t)}else a===null&&(l.shared.lanes=0);hn|=o,e.lanes=o,e.memoizedState=h}}function qu(e,t,n){if(e=t.effects,t.effects=null,e!==null)for(t=0;tn?n:4,e(!0);var r=bo.transition;bo.transition={};try{e(!1),t()}finally{A=n,bo.transition=r}}function gf(){return Qe().memoizedState}function _h(e,t,n){var r=Qt(e);if(n={lane:r,action:n,hasEagerState:!1,eagerState:null,next:null},xf(e))wf(t,n);else if(n=qd(e,t,n,r),n!==null){var l=Ne();qe(n,e,r,l),Sf(n,t,r)}}function jh(e,t,n){var r=Qt(e),l={lane:r,action:n,hasEagerState:!1,eagerState:null,next:null};if(xf(e))wf(t,l);else{var a=e.alternate;if(e.lanes===0&&(a===null||a.lanes===0)&&(a=t.lastRenderedReducer,a!==null))try{var o=t.lastRenderedState,i=a(o,n);if(l.hasEagerState=!0,l.eagerState=i,et(i,o)){var c=t.interleaved;c===null?(l.next=l,ci(t)):(l.next=c.next,c.next=l),t.interleaved=l;return}}catch{}finally{}n=qd(e,t,l,r),n!==null&&(l=Ne(),qe(n,e,r,l),Sf(n,t,r))}}function xf(e){var t=e.alternate;return e===J||t!==null&&t===J}function wf(e,t){Cr=ua=!0;var n=e.pending;n===null?t.next=t:(t.next=n.next,n.next=t),e.pending=t}function Sf(e,t,n){if(n&4194240){var r=t.lanes;r&=e.pendingLanes,n|=r,t.lanes=n,Xs(e,n)}}var ca={readContext:We,useCallback:he,useContext:he,useEffect:he,useImperativeHandle:he,useInsertionEffect:he,useLayoutEffect:he,useMemo:he,useReducer:he,useRef:he,useState:he,useDebugValue:he,useDeferredValue:he,useTransition:he,useMutableSource:he,useSyncExternalStore:he,useId:he,unstable_isNewReconciler:!1},Eh={readContext:We,useCallback:function(e,t){return ot().memoizedState=[e,t===void 0?null:t],e},useContext:We,useEffect:tc,useImperativeHandle:function(e,t,n){return n=n!=null?n.concat([e]):null,Al(4194308,4,pf.bind(null,t,e),n)},useLayoutEffect:function(e,t){return Al(4194308,4,e,t)},useInsertionEffect:function(e,t){return Al(4,2,e,t)},useMemo:function(e,t){var n=ot();return t=t===void 0?null:t,e=e(),n.memoizedState=[e,t],e},useReducer:function(e,t,n){var r=ot();return t=n!==void 0?n(t):t,r.memoizedState=r.baseState=t,e={pending:null,interleaved:null,lanes:0,dispatch:null,lastRenderedReducer:e,lastRenderedState:t},r.queue=e,e=e.dispatch=_h.bind(null,J,e),[r.memoizedState,e]},useRef:function(e){var t=ot();return e={current:e},t.memoizedState=e},useState:ec,useDebugValue:xi,useDeferredValue:function(e){return ot().memoizedState=e},useTransition:function(){var e=ec(!1),t=e[0];return e=Nh.bind(null,e[1]),ot().memoizedState=e,[t,e]},useMutableSource:function(){},useSyncExternalStore:function(e,t,n){var r=J,l=ot();if(G){if(n===void 0)throw Error(S(407));n=n()}else{if(n=t(),ue===null)throw Error(S(349));mn&30||lf(r,t,n)}l.memoizedState=n;var a={value:n,getSnapshot:t};return l.queue=a,tc(of.bind(null,r,a,e),[e]),r.flags|=2048,Kr(9,af.bind(null,r,a,n,t),void 0,null),n},useId:function(){var e=ot(),t=ue.identifierPrefix;if(G){var n=St,r=wt;n=(r&~(1<<32-Ze(r)-1)).toString(32)+n,t=":"+t+"R"+n,n=Wr++,0<\/script>",e=e.removeChild(e.firstChild)):typeof r.is=="string"?e=o.createElement(n,{is:r.is}):(e=o.createElement(n),n==="select"&&(o=e,r.multiple?o.multiple=!0:r.size&&(o.size=r.size))):e=o.createElementNS(e,n),e[st]=t,e[Hr]=r,Mf(e,t,!1,!1),t.stateNode=e;e:{switch(o=ns(n,r),n){case"dialog":Q("cancel",e),Q("close",e),l=r;break;case"iframe":case"object":case"embed":Q("load",e),l=r;break;case"video":case"audio":for(l=0;lqn&&(t.flags|=128,r=!0,hr(a,!1),t.lanes=4194304)}else{if(!r)if(e=ia(o),e!==null){if(t.flags|=128,r=!0,n=e.updateQueue,n!==null&&(t.updateQueue=n,t.flags|=4),hr(a,!0),a.tail===null&&a.tailMode==="hidden"&&!o.alternate&&!G)return ve(t),null}else 2*ne()-a.renderingStartTime>qn&&n!==1073741824&&(t.flags|=128,r=!0,hr(a,!1),t.lanes=4194304);a.isBackwards?(o.sibling=t.child,t.child=o):(n=a.last,n!==null?n.sibling=o:t.child=o,a.last=o)}return a.tail!==null?(t=a.tail,a.rendering=t,a.tail=t.sibling,a.renderingStartTime=ne(),t.sibling=null,n=X.current,W(X,r?n&1|2:n&1),t):(ve(t),null);case 22:case 23:return ji(),r=t.memoizedState!==null,e!==null&&e.memoizedState!==null!==r&&(t.flags|=8192),r&&t.mode&1?Ie&1073741824&&(ve(t),t.subtreeFlags&6&&(t.flags|=8192)):ve(t),null;case 24:return null;case 25:return null}throw Error(S(156,t.tag))}function Ih(e,t){switch(ai(t),t.tag){case 1:return Re(t.type)&&ta(),e=t.flags,e&65536?(t.flags=e&-65537|128,t):null;case 3:return Jn(),K(Pe),K(ge),mi(),e=t.flags,e&65536&&!(e&128)?(t.flags=e&-65537|128,t):null;case 5:return pi(t),null;case 13:if(K(X),e=t.memoizedState,e!==null&&e.dehydrated!==null){if(t.alternate===null)throw Error(S(340));Yn()}return e=t.flags,e&65536?(t.flags=e&-65537|128,t):null;case 19:return K(X),null;case 4:return Jn(),null;case 10:return ui(t.type._context),null;case 22:case 23:return ji(),null;case 24:return null;default:return null}}var Cl=!1,ye=!1,Oh=typeof WeakSet=="function"?WeakSet:Set,P=null;function Dn(e,t){var n=e.ref;if(n!==null)if(typeof n=="function")try{n(null)}catch(r){q(e,t,r)}else n.current=null}function Ps(e,t,n){try{n()}catch(r){q(e,t,r)}}var fc=!1;function zh(e,t){if(fs=Jl,e=Fd(),ri(e)){if("selectionStart"in e)var n={start:e.selectionStart,end:e.selectionEnd};else e:{n=(n=e.ownerDocument)&&n.defaultView||window;var r=n.getSelection&&n.getSelection();if(r&&r.rangeCount!==0){n=r.anchorNode;var l=r.anchorOffset,a=r.focusNode;r=r.focusOffset;try{n.nodeType,a.nodeType}catch{n=null;break e}var o=0,i=-1,c=-1,f=0,g=0,h=e,y=null;t:for(;;){for(var k;h!==n||l!==0&&h.nodeType!==3||(i=o+l),h!==a||r!==0&&h.nodeType!==3||(c=o+r),h.nodeType===3&&(o+=h.nodeValue.length),(k=h.firstChild)!==null;)y=h,h=k;for(;;){if(h===e)break t;if(y===n&&++f===l&&(i=o),y===a&&++g===r&&(c=o),(k=h.nextSibling)!==null)break;h=y,y=h.parentNode}h=k}n=i===-1||c===-1?null:{start:i,end:c}}else n=null}n=n||{start:0,end:0}}else n=null;for(ps={focusedElem:e,selectionRange:n},Jl=!1,P=t;P!==null;)if(t=P,e=t.child,(t.subtreeFlags&1028)!==0&&e!==null)e.return=t,P=e;else for(;P!==null;){t=P;try{var j=t.alternate;if(t.flags&1024)switch(t.tag){case 0:case 11:case 15:break;case 1:if(j!==null){var E=j.memoizedProps,B=j.memoizedState,p=t.stateNode,d=p.getSnapshotBeforeUpdate(t.elementType===t.type?E:Ye(t.type,E),B);p.__reactInternalSnapshotBeforeUpdate=d}break;case 3:var m=t.stateNode.containerInfo;m.nodeType===1?m.textContent="":m.nodeType===9&&m.documentElement&&m.removeChild(m.documentElement);break;case 5:case 6:case 4:case 17:break;default:throw Error(S(163))}}catch(x){q(t,t.return,x)}if(e=t.sibling,e!==null){e.return=t.return,P=e;break}P=t.return}return j=fc,fc=!1,j}function Pr(e,t,n){var r=t.updateQueue;if(r=r!==null?r.lastEffect:null,r!==null){var l=r=r.next;do{if((l.tag&e)===e){var a=l.destroy;l.destroy=void 0,a!==void 0&&Ps(t,n,a)}l=l.next}while(l!==r)}}function ba(e,t){if(t=t.updateQueue,t=t!==null?t.lastEffect:null,t!==null){var n=t=t.next;do{if((n.tag&e)===e){var r=n.create;n.destroy=r()}n=n.next}while(n!==t)}}function Rs(e){var t=e.ref;if(t!==null){var n=e.stateNode;switch(e.tag){case 5:e=n;break;default:e=n}typeof t=="function"?t(e):t.current=e}}function If(e){var t=e.alternate;t!==null&&(e.alternate=null,If(t)),e.child=null,e.deletions=null,e.sibling=null,e.tag===5&&(t=e.stateNode,t!==null&&(delete t[st],delete t[Hr],delete t[vs],delete t[gh],delete t[xh])),e.stateNode=null,e.return=null,e.dependencies=null,e.memoizedProps=null,e.memoizedState=null,e.pendingProps=null,e.stateNode=null,e.updateQueue=null}function Of(e){return e.tag===5||e.tag===3||e.tag===4}function pc(e){e:for(;;){for(;e.sibling===null;){if(e.return===null||Of(e.return))return null;e=e.return}for(e.sibling.return=e.return,e=e.sibling;e.tag!==5&&e.tag!==6&&e.tag!==18;){if(e.flags&2||e.child===null||e.tag===4)continue e;e.child.return=e,e=e.child}if(!(e.flags&2))return e.stateNode}}function Ms(e,t,n){var r=e.tag;if(r===5||r===6)e=e.stateNode,t?n.nodeType===8?n.parentNode.insertBefore(e,t):n.insertBefore(e,t):(n.nodeType===8?(t=n.parentNode,t.insertBefore(e,n)):(t=n,t.appendChild(e)),n=n._reactRootContainer,n!=null||t.onclick!==null||(t.onclick=ea));else if(r!==4&&(e=e.child,e!==null))for(Ms(e,t,n),e=e.sibling;e!==null;)Ms(e,t,n),e=e.sibling}function Ls(e,t,n){var r=e.tag;if(r===5||r===6)e=e.stateNode,t?n.insertBefore(e,t):n.appendChild(e);else if(r!==4&&(e=e.child,e!==null))for(Ls(e,t,n),e=e.sibling;e!==null;)Ls(e,t,n),e=e.sibling}var ce=null,Xe=!1;function Lt(e,t,n){for(n=n.child;n!==null;)zf(e,t,n),n=n.sibling}function zf(e,t,n){if(it&&typeof it.onCommitFiberUnmount=="function")try{it.onCommitFiberUnmount(xa,n)}catch{}switch(n.tag){case 5:ye||Dn(n,t);case 6:var r=ce,l=Xe;ce=null,Lt(e,t,n),ce=r,Xe=l,ce!==null&&(Xe?(e=ce,n=n.stateNode,e.nodeType===8?e.parentNode.removeChild(n):e.removeChild(n)):ce.removeChild(n.stateNode));break;case 18:ce!==null&&(Xe?(e=ce,n=n.stateNode,e.nodeType===8?_o(e.parentNode,n):e.nodeType===1&&_o(e,n),Fr(e)):_o(ce,n.stateNode));break;case 4:r=ce,l=Xe,ce=n.stateNode.containerInfo,Xe=!0,Lt(e,t,n),ce=r,Xe=l;break;case 0:case 11:case 14:case 15:if(!ye&&(r=n.updateQueue,r!==null&&(r=r.lastEffect,r!==null))){l=r=r.next;do{var a=l,o=a.destroy;a=a.tag,o!==void 0&&(a&2||a&4)&&Ps(n,t,o),l=l.next}while(l!==r)}Lt(e,t,n);break;case 1:if(!ye&&(Dn(n,t),r=n.stateNode,typeof r.componentWillUnmount=="function"))try{r.props=n.memoizedProps,r.state=n.memoizedState,r.componentWillUnmount()}catch(i){q(n,t,i)}Lt(e,t,n);break;case 21:Lt(e,t,n);break;case 22:n.mode&1?(ye=(r=ye)||n.memoizedState!==null,Lt(e,t,n),ye=r):Lt(e,t,n);break;default:Lt(e,t,n)}}function mc(e){var t=e.updateQueue;if(t!==null){e.updateQueue=null;var n=e.stateNode;n===null&&(n=e.stateNode=new Oh),t.forEach(function(r){var l=Wh.bind(null,e,r);n.has(r)||(n.add(r),r.then(l,l))})}}function Ke(e,t){var n=t.deletions;if(n!==null)for(var r=0;rl&&(l=o),r&=~a}if(r=l,r=ne()-r,r=(120>r?120:480>r?480:1080>r?1080:1920>r?1920:3e3>r?3e3:4320>r?4320:1960*Dh(r/1960))-r,10e?16:e,Ut===null)var r=!1;else{if(e=Ut,Ut=null,pa=0,F&6)throw Error(S(331));var l=F;for(F|=4,P=e.current;P!==null;){var a=P,o=a.child;if(P.flags&16){var i=a.deletions;if(i!==null){for(var c=0;cne()-Ni?cn(e,0):ki|=n),Me(e,t)}function Vf(e,t){t===0&&(e.mode&1?(t=xl,xl<<=1,!(xl&130023424)&&(xl=4194304)):t=1);var n=Ne();e=jt(e,t),e!==null&&(Xr(e,t,n),Me(e,n))}function Vh(e){var t=e.memoizedState,n=0;t!==null&&(n=t.retryLane),Vf(e,n)}function Wh(e,t){var n=0;switch(e.tag){case 13:var r=e.stateNode,l=e.memoizedState;l!==null&&(n=l.retryLane);break;case 19:r=e.stateNode;break;default:throw Error(S(314))}r!==null&&r.delete(t),Vf(e,n)}var Wf;Wf=function(e,t,n){if(e!==null)if(e.memoizedProps!==t.pendingProps||Pe.current)Ce=!0;else{if(!(e.lanes&n)&&!(t.flags&128))return Ce=!1,Lh(e,t,n);Ce=!!(e.flags&131072)}else Ce=!1,G&&t.flags&1048576&&Gd(t,la,t.index);switch(t.lanes=0,t.tag){case 2:var r=t.type;$l(e,t),e=t.pendingProps;var l=Gn(t,ge.current);Vn(t,n),l=vi(null,t,r,e,l,n);var a=yi();return t.flags|=1,typeof l=="object"&&l!==null&&typeof l.render=="function"&&l.$$typeof===void 0?(t.tag=1,t.memoizedState=null,t.updateQueue=null,Re(r)?(a=!0,na(t)):a=!1,t.memoizedState=l.state!==null&&l.state!==void 0?l.state:null,di(t),l.updater=Ea,t.stateNode=l,l._reactInternals=t,ks(t,r,e,n),t=js(null,t,r,!0,a,n)):(t.tag=0,G&&a&&li(t),ke(null,t,l,n),t=t.child),t;case 16:r=t.elementType;e:{switch($l(e,t),e=t.pendingProps,l=r._init,r=l(r._payload),t.type=r,l=t.tag=Kh(r),e=Ye(r,e),l){case 0:t=_s(null,t,r,e,n);break e;case 1:t=uc(null,t,r,e,n);break e;case 11:t=sc(null,t,r,e,n);break e;case 14:t=ic(null,t,r,Ye(r.type,e),n);break e}throw Error(S(306,r,""))}return t;case 0:return r=t.type,l=t.pendingProps,l=t.elementType===r?l:Ye(r,l),_s(e,t,r,l,n);case 1:return r=t.type,l=t.pendingProps,l=t.elementType===r?l:Ye(r,l),uc(e,t,r,l,n);case 3:e:{if(Cf(t),e===null)throw Error(S(387));r=t.pendingProps,a=t.memoizedState,l=a.element,ef(e,t),sa(t,r,null,n);var o=t.memoizedState;if(r=o.element,a.isDehydrated)if(a={element:r,isDehydrated:!1,cache:o.cache,pendingSuspenseBoundaries:o.pendingSuspenseBoundaries,transitions:o.transitions},t.updateQueue.baseState=a,t.memoizedState=a,t.flags&256){l=Zn(Error(S(423)),t),t=cc(e,t,r,n,l);break e}else if(r!==l){l=Zn(Error(S(424)),t),t=cc(e,t,r,n,l);break e}else for(Oe=Bt(t.stateNode.containerInfo.firstChild),ze=t,G=!0,Je=null,n=Zd(t,null,r,n),t.child=n;n;)n.flags=n.flags&-3|4096,n=n.sibling;else{if(Yn(),r===l){t=Et(e,t,n);break e}ke(e,t,r,n)}t=t.child}return t;case 5:return tf(t),e===null&&xs(t),r=t.type,l=t.pendingProps,a=e!==null?e.memoizedProps:null,o=l.children,ms(r,l)?o=null:a!==null&&ms(r,a)&&(t.flags|=32),bf(e,t),ke(e,t,o,n),t.child;case 6:return e===null&&xs(t),null;case 13:return Pf(e,t,n);case 4:return fi(t,t.stateNode.containerInfo),r=t.pendingProps,e===null?t.child=Xn(t,null,r,n):ke(e,t,r,n),t.child;case 11:return r=t.type,l=t.pendingProps,l=t.elementType===r?l:Ye(r,l),sc(e,t,r,l,n);case 7:return ke(e,t,t.pendingProps,n),t.child;case 8:return ke(e,t,t.pendingProps.children,n),t.child;case 12:return ke(e,t,t.pendingProps.children,n),t.child;case 10:e:{if(r=t.type._context,l=t.pendingProps,a=t.memoizedProps,o=l.value,W(aa,r._currentValue),r._currentValue=o,a!==null)if(et(a.value,o)){if(a.children===l.children&&!Pe.current){t=Et(e,t,n);break e}}else for(a=t.child,a!==null&&(a.return=t);a!==null;){var i=a.dependencies;if(i!==null){o=a.child;for(var c=i.firstContext;c!==null;){if(c.context===r){if(a.tag===1){c=kt(-1,n&-n),c.tag=2;var f=a.updateQueue;if(f!==null){f=f.shared;var g=f.pending;g===null?c.next=c:(c.next=g.next,g.next=c),f.pending=c}}a.lanes|=n,c=a.alternate,c!==null&&(c.lanes|=n),ws(a.return,n,t),i.lanes|=n;break}c=c.next}}else if(a.tag===10)o=a.type===t.type?null:a.child;else if(a.tag===18){if(o=a.return,o===null)throw Error(S(341));o.lanes|=n,i=o.alternate,i!==null&&(i.lanes|=n),ws(o,n,t),o=a.sibling}else o=a.child;if(o!==null)o.return=a;else for(o=a;o!==null;){if(o===t){o=null;break}if(a=o.sibling,a!==null){a.return=o.return,o=a;break}o=o.return}a=o}ke(e,t,l.children,n),t=t.child}return t;case 9:return l=t.type,r=t.pendingProps.children,Vn(t,n),l=We(l),r=r(l),t.flags|=1,ke(e,t,r,n),t.child;case 14:return r=t.type,l=Ye(r,t.pendingProps),l=Ye(r.type,l),ic(e,t,r,l,n);case 15:return jf(e,t,t.type,t.pendingProps,n);case 17:return r=t.type,l=t.pendingProps,l=t.elementType===r?l:Ye(r,l),$l(e,t),t.tag=1,Re(r)?(e=!0,na(t)):e=!1,Vn(t,n),kf(t,r,l),ks(t,r,l,n),js(null,t,r,!0,e,n);case 19:return Rf(e,t,n);case 22:return Ef(e,t,n)}throw Error(S(156,t.tag))};function Qf(e,t){return gd(e,t)}function Qh(e,t,n,r){this.tag=e,this.key=n,this.sibling=this.child=this.return=this.stateNode=this.type=this.elementType=null,this.index=0,this.ref=null,this.pendingProps=t,this.dependencies=this.memoizedState=this.updateQueue=this.memoizedProps=null,this.mode=r,this.subtreeFlags=this.flags=0,this.deletions=null,this.childLanes=this.lanes=0,this.alternate=null}function Be(e,t,n,r){return new Qh(e,t,n,r)}function bi(e){return e=e.prototype,!(!e||!e.isReactComponent)}function Kh(e){if(typeof e=="function")return bi(e)?1:0;if(e!=null){if(e=e.$$typeof,e===Qs)return 11;if(e===Ks)return 14}return 2}function Kt(e,t){var n=e.alternate;return n===null?(n=Be(e.tag,t,e.key,e.mode),n.elementType=e.elementType,n.type=e.type,n.stateNode=e.stateNode,n.alternate=e,e.alternate=n):(n.pendingProps=t,n.type=e.type,n.flags=0,n.subtreeFlags=0,n.deletions=null),n.flags=e.flags&14680064,n.childLanes=e.childLanes,n.lanes=e.lanes,n.child=e.child,n.memoizedProps=e.memoizedProps,n.memoizedState=e.memoizedState,n.updateQueue=e.updateQueue,t=e.dependencies,n.dependencies=t===null?null:{lanes:t.lanes,firstContext:t.firstContext},n.sibling=e.sibling,n.index=e.index,n.ref=e.ref,n}function Vl(e,t,n,r,l,a){var o=2;if(r=e,typeof e=="function")bi(e)&&(o=1);else if(typeof e=="string")o=5;else e:switch(e){case Pn:return dn(n.children,l,a,t);case Ws:o=8,l|=8;break;case Qo:return e=Be(12,n,t,l|2),e.elementType=Qo,e.lanes=a,e;case Ko:return e=Be(13,n,t,l),e.elementType=Ko,e.lanes=a,e;case Go:return e=Be(19,n,t,l),e.elementType=Go,e.lanes=a,e;case td:return Pa(n,l,a,t);default:if(typeof e=="object"&&e!==null)switch(e.$$typeof){case qc:o=10;break e;case ed:o=9;break e;case Qs:o=11;break e;case Ks:o=14;break e;case Tt:o=16,r=null;break e}throw Error(S(130,e==null?e:typeof e,""))}return t=Be(o,n,t,l),t.elementType=e,t.type=r,t.lanes=a,t}function dn(e,t,n,r){return e=Be(7,e,r,t),e.lanes=n,e}function Pa(e,t,n,r){return e=Be(22,e,r,t),e.elementType=td,e.lanes=n,e.stateNode={isHidden:!1},e}function Lo(e,t,n){return e=Be(6,e,null,t),e.lanes=n,e}function To(e,t,n){return t=Be(4,e.children!==null?e.children:[],e.key,t),t.lanes=n,t.stateNode={containerInfo:e.containerInfo,pendingChildren:null,implementation:e.implementation},t}function Gh(e,t,n,r,l){this.tag=t,this.containerInfo=e,this.finishedWork=this.pingCache=this.current=this.pendingChildren=null,this.timeoutHandle=-1,this.callbackNode=this.pendingContext=this.context=null,this.callbackPriority=0,this.eventTimes=po(0),this.expirationTimes=po(-1),this.entangledLanes=this.finishedLanes=this.mutableReadLanes=this.expiredLanes=this.pingedLanes=this.suspendedLanes=this.pendingLanes=0,this.entanglements=po(0),this.identifierPrefix=r,this.onRecoverableError=l,this.mutableSourceEagerHydrationData=null}function Ci(e,t,n,r,l,a,o,i,c){return e=new Gh(e,t,n,i,c),t===1?(t=1,a===!0&&(t|=8)):t=0,a=Be(3,null,null,t),e.current=a,a.stateNode=e,a.memoizedState={element:r,isDehydrated:n,cache:null,transitions:null,pendingSuspenseBoundaries:null},di(a),e}function Yh(e,t,n){var r=3"u"||typeof __REACT_DEVTOOLS_GLOBAL_HOOK__.checkDCE!="function"))try{__REACT_DEVTOOLS_GLOBAL_HOOK__.checkDCE(Xf)}catch(e){console.error(e)}}Xf(),Yc.exports=De;var ev=Yc.exports,kc=ev;Vo.createRoot=kc.createRoot,Vo.hydrateRoot=kc.hydrateRoot;/** + * @license lucide-react v0.460.0 - ISC + * + * This source code is licensed under the ISC license. + * See the LICENSE file in the root directory of this source tree. + */const tv=e=>e.replace(/([a-z0-9])([A-Z])/g,"$1-$2").toLowerCase(),Jf=(...e)=>e.filter((t,n,r)=>!!t&&t.trim()!==""&&r.indexOf(t)===n).join(" ").trim();/** + * @license lucide-react v0.460.0 - ISC + * + * This source code is licensed under the ISC license. + * See the LICENSE file in the root directory of this source tree. + */var nv={xmlns:"http://www.w3.org/2000/svg",width:24,height:24,viewBox:"0 0 24 24",fill:"none",stroke:"currentColor",strokeWidth:2,strokeLinecap:"round",strokeLinejoin:"round"};/** + * @license lucide-react v0.460.0 - ISC + * + * This source code is licensed under the ISC license. + * See the LICENSE file in the root directory of this source tree. + */const rv=w.forwardRef(({color:e="currentColor",size:t=24,strokeWidth:n=2,absoluteStrokeWidth:r,className:l="",children:a,iconNode:o,...i},c)=>w.createElement("svg",{ref:c,...nv,width:t,height:t,stroke:e,strokeWidth:r?Number(n)*24/Number(t):n,className:Jf("lucide",l),...i},[...o.map(([f,g])=>w.createElement(f,g)),...Array.isArray(a)?a:[a]]));/** + * @license lucide-react v0.460.0 - ISC + * + * This source code is licensed under the ISC license. + * See the LICENSE file in the root directory of this source tree. + */const Ae=(e,t)=>{const n=w.forwardRef(({className:r,...l},a)=>w.createElement(rv,{ref:a,iconNode:t,className:Jf(`lucide-${tv(e)}`,r),...l}));return n.displayName=`${e}`,n};/** + * @license lucide-react v0.460.0 - ISC + * + * This source code is licensed under the ISC license. + * See the LICENSE file in the root directory of this source tree. + */const yr=Ae("ChevronDown",[["path",{d:"m6 9 6 6 6-6",key:"qrunsl"}]]);/** + * @license lucide-react v0.460.0 - ISC + * + * This source code is licensed under the ISC license. + * See the LICENSE file in the root directory of this source tree. + */const lv=Ae("Database",[["ellipse",{cx:"12",cy:"5",rx:"9",ry:"3",key:"msslwz"}],["path",{d:"M3 5V19A9 3 0 0 0 21 19V5",key:"1wlel7"}],["path",{d:"M3 12A9 3 0 0 0 21 12",key:"mv7ke4"}]]);/** + * @license lucide-react v0.460.0 - ISC + * + * This source code is licensed under the ISC license. + * See the LICENSE file in the root directory of this source tree. + */const av=Ae("FlaskConical",[["path",{d:"M10 2v7.527a2 2 0 0 1-.211.896L4.72 20.55a1 1 0 0 0 .9 1.45h12.76a1 1 0 0 0 .9-1.45l-5.069-10.127A2 2 0 0 1 14 9.527V2",key:"pzvekw"}],["path",{d:"M8.5 2h7",key:"csnxdl"}],["path",{d:"M7 16h10",key:"wp8him"}]]);/** + * @license lucide-react v0.460.0 - ISC + * + * This source code is licensed under the ISC license. + * See the LICENSE file in the root directory of this source tree. + */const Nc=Ae("Grid3x3",[["rect",{width:"18",height:"18",x:"3",y:"3",rx:"2",key:"afitv7"}],["path",{d:"M3 9h18",key:"1pudct"}],["path",{d:"M3 15h18",key:"5xshup"}],["path",{d:"M9 3v18",key:"fh3hqa"}],["path",{d:"M15 3v18",key:"14nvp0"}]]);/** + * @license lucide-react v0.460.0 - ISC + * + * This source code is licensed under the ISC license. + * See the LICENSE file in the root directory of this source tree. + */const ov=Ae("History",[["path",{d:"M3 12a9 9 0 1 0 9-9 9.75 9.75 0 0 0-6.74 2.74L3 8",key:"1357e3"}],["path",{d:"M3 3v5h5",key:"1xhq8a"}],["path",{d:"M12 7v5l4 2",key:"1fdv2h"}]]);/** + * @license lucide-react v0.460.0 - ISC + * + * This source code is licensed under the ISC license. + * See the LICENSE file in the root directory of this source tree. + */const sv=Ae("House",[["path",{d:"M15 21v-8a1 1 0 0 0-1-1h-4a1 1 0 0 0-1 1v8",key:"5wwlr5"}],["path",{d:"M3 10a2 2 0 0 1 .709-1.528l7-5.999a2 2 0 0 1 2.582 0l7 5.999A2 2 0 0 1 21 10v9a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2z",key:"1d0kgt"}]]);/** + * @license lucide-react v0.460.0 - ISC + * + * This source code is licensed under the ISC license. + * See the LICENSE file in the root directory of this source tree. + */const Io=Ae("LoaderCircle",[["path",{d:"M21 12a9 9 0 1 1-6.219-8.56",key:"13zald"}]]);/** + * @license lucide-react v0.460.0 - ISC + * + * This source code is licensed under the ISC license. + * See the LICENSE file in the root directory of this source tree. + */const iv=Ae("RefreshCw",[["path",{d:"M3 12a9 9 0 0 1 9-9 9.75 9.75 0 0 1 6.74 2.74L21 8",key:"v9h5vc"}],["path",{d:"M21 3v5h-5",key:"1q7to0"}],["path",{d:"M21 12a9 9 0 0 1-9 9 9.75 9.75 0 0 1-6.74-2.74L3 16",key:"3uifl3"}],["path",{d:"M8 16H3v5",key:"1cv678"}]]);/** + * @license lucide-react v0.460.0 - ISC + * + * This source code is licensed under the ISC license. + * See the LICENSE file in the root directory of this source tree. + */const _c=Ae("RotateCcw",[["path",{d:"M3 12a9 9 0 1 0 9-9 9.75 9.75 0 0 0-6.74 2.74L3 8",key:"1357e3"}],["path",{d:"M3 3v5h5",key:"1xhq8a"}]]);/** + * @license lucide-react v0.460.0 - ISC + * + * This source code is licensed under the ISC license. + * See the LICENSE file in the root directory of this source tree. + */const jc=Ae("Trash2",[["path",{d:"M3 6h18",key:"d0wm0j"}],["path",{d:"M19 6v14c0 1-1 2-2 2H7c-1 0-2-1-2-2V6",key:"4alrt4"}],["path",{d:"M8 6V4c0-1 1-2 2-2h4c1 0 2 1 2 2v2",key:"v07s0e"}],["line",{x1:"10",x2:"10",y1:"11",y2:"17",key:"1uufr5"}],["line",{x1:"14",x2:"14",y1:"11",y2:"17",key:"xtxkd"}]]);/** + * @license lucide-react v0.460.0 - ISC + * + * This source code is licensed under the ISC license. + * See the LICENSE file in the root directory of this source tree. + */const uv=Ae("Upload",[["path",{d:"M21 15v4a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2v-4",key:"ih7n3h"}],["polyline",{points:"17 8 12 3 7 8",key:"t8dd8p"}],["line",{x1:"12",x2:"12",y1:"3",y2:"15",key:"widbto"}]]);/** + * @license lucide-react v0.460.0 - ISC + * + * This source code is licensed under the ISC license. + * See the LICENSE file in the root directory of this source tree. + */const Ec=Ae("ZoomIn",[["circle",{cx:"11",cy:"11",r:"8",key:"4ej97u"}],["line",{x1:"21",x2:"16.65",y1:"21",y2:"16.65",key:"13gj7c"}],["line",{x1:"11",x2:"11",y1:"8",y2:"14",key:"1vmskp"}],["line",{x1:"8",x2:"14",y1:"11",y2:"11",key:"durymu"}]]);/** + * @license lucide-react v0.460.0 - ISC + * + * This source code is licensed under the ISC license. + * See the LICENSE file in the root directory of this source tree. + */const bc=Ae("ZoomOut",[["circle",{cx:"11",cy:"11",r:"8",key:"4ej97u"}],["line",{x1:"21",x2:"16.65",y1:"21",y2:"16.65",key:"13gj7c"}],["line",{x1:"8",x2:"14",y1:"11",y2:"11",key:"durymu"}]]),Y="/api";async function tt(e){if((e.headers.get("content-type")||"").includes("application/json"))return e.json();const n=await e.text();throw new Error(n||`HTTP ${e.status}`)}async function cv(){const e=await fetch(`${Y}/analysis/uploads`);if(!e.ok)throw new Error(await e.text());return e.json()}async function dv(e,t){const n=new URLSearchParams;t!=null&&t.deleteExperiments&&n.set("delete_experiments","true");const r=n.toString(),l=await fetch(`${Y}/analysis/uploads/${encodeURIComponent(e)}${r?`?${r}`:""}`,{method:"DELETE"});if(!l.ok)throw new Error(await l.text());return await l.json().catch(()=>({}))}async function Oo(e,t="analysis_upload"){const n=new FormData;n.append("file",e),n.append("source",t);const r=await fetch(`${Y}/analysis/upload`,{method:"POST",body:n}),l=await tt(r);if(!r.ok)throw new Error(String(l.detail||r.status));return l}async function fv(e){const t=await fetch(`${Y}/analysis/uploads/import_from_debug`,{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({filename:e})}),n=await tt(t);if(!t.ok)throw new Error(String(n.detail||t.status));return n}async function Cc(e){const t=await fetch(`${Y}/analysis/uploads/replace_video`,{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify(e)}),n=await tt(t);if(!t.ok)throw new Error(String(n.detail||t.status));return n}async function pv(e,t){const n=await fetch(`${Y}/analysis/solve/image`,{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({input_name:e,...t})}),r=await tt(n);if(!n.ok)throw new Error(String(r.detail||n.status));return r}async function mv(e,t){const n=await fetch(`${Y}/analysis/solve/batch`,{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({input_name:e,runs:t})}),r=await tt(n);if(!n.ok)throw new Error(String(r.detail||n.status));return r}async function zo(e){const t=await fetch(`${Y}/analysis/presets?scope=${e}`);if(!t.ok)throw new Error(await t.text());return t.json()}async function hv(e,t){const n=await fetch(`${Y}/analysis/presets`,{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({name:e,params:t})}),r=await tt(n);if(!n.ok)throw new Error(String(r.detail||n.status));return r}async function Fo(e){const t=await fetch(`${Y}/analysis/experiments`,{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify(e)}),n=await tt(t);if(!t.ok)throw new Error(String(n.detail||t.status));return n}async function vv(e){const t=await fetch(`${Y}/analysis/experiments/${encodeURIComponent(e)}`,{method:"DELETE"});if(!t.ok)throw new Error(await t.text())}async function Do(e,t,n=30){const r=new URLSearchParams({page:String(t),page_size:String(n)});e&&r.set("q",e);const l=await fetch(`${Y}/analysis/experiments?${r}`);if(!l.ok)throw new Error(await l.text());return l.json()}async function yv(){const e=await fetch(`${Y}/debug/files`);if(!e.ok)throw new Error(await e.text());return e.json()}function gv(e){return`${Y}/debug/files/${encodeURIComponent(e)}`}async function Pc(e){const t=await fetch(`${Y}/debug/files/${encodeURIComponent(e)}/info`),n=await tt(t);if(!t.ok)throw new Error(String(n.detail||t.status));return n}async function xv(e){const t=await fetch(`${Y}/analysis/uploads/${encodeURIComponent(e)}/info`),n=await tt(t);if(!t.ok)throw new Error(String(n.detail||t.status));return n}function Uo(e){return`${Y}/analysis/uploads/file?filename=${encodeURIComponent(e)}`}async function Rc(e){const t=await fetch(`${Y}/analysis/experiments/export?format=${e}`);if(!t.ok)throw new Error(await t.text());return t.text()}async function wv(e){const t=await fetch(`${Y}/analysis/uploads/${encodeURIComponent(e)}/experiment_count`);if(!t.ok)throw new Error(await t.text());return t.json()}async function Sv(){const e=await fetch(`${Y}/analysis/settings`);if(!e.ok)throw new Error(await e.text());return e.json()}async function kv(e){const t=await fetch(`${Y}/analysis/solve/frame`,{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify(e)}),n=await tt(t);if(!t.ok)throw new Error(String(n.detail||t.status));return n}async function Nv(e,t){const n=new FormData;n.append("file",e,"frame.jpg"),n.append("payload",JSON.stringify(t));const r=await fetch(`${Y}/analysis/solve/frame_upload`,{method:"POST",body:n}),l=await tt(r);if(!r.ok)throw new Error(String(l.detail||r.status));return l}function _v(e){return`${Y}/analysis/experiments/${encodeURIComponent(e)}/asset`}async function jv(){const e=await fetch(`${Y}/system/info`);if(!e.ok)throw new Error(await e.text());return e.json()}function Zf(e,t,n,r,l){var i,c,f,g;if(!r)return;if(e.clearRect(0,0,t,n),l.all&&Array.isArray(r.stars_all_centroids)){e.fillStyle="rgba(156, 163, 175, 0.85)";for(const h of r.stars_all_centroids)e.beginPath(),e.arc(h.x,h.y,2.4,0,Math.PI*2),e.fill()}if(l.pattern&&Array.isArray(r.stars_pattern)){e.strokeStyle="rgba(251, 146, 60, 0.95)",e.lineWidth=2;for(const h of r.stars_pattern)e.beginPath(),e.arc(h.x,h.y,6,0,Math.PI*2),e.stroke()}if(l.matched&&Array.isArray(r.stars_matched)){e.strokeStyle="rgba(34, 197, 94, 0.95)",e.fillStyle="rgba(34, 197, 94, 0.95)",e.lineWidth=2,e.font="11px system-ui, sans-serif";for(const h of r.stars_matched)e.beginPath(),e.arc(h.x,h.y,7,0,Math.PI*2),e.stroke(),h.mag!=null&&e.fillText(`m${Number(h.mag).toFixed(1)}`,h.x+4,h.y-4)}const a=r.overlay_ext;if(Array.isArray(a==null?void 0:a.labels_topn)&&a.labels_topn.length>0){e.fillStyle="rgba(96, 165, 250, 0.95)",e.font="12px system-ui, sans-serif";for(const h of a.labels_topn){if(typeof(h==null?void 0:h.x)!="number"||typeof(h==null?void 0:h.y)!="number")continue;const y=typeof h.mag=="number"?` m${h.mag.toFixed(1)}`:"",k=`${h.name??"Star"}${y}`;e.fillText(k,h.x+8,h.y+14)}}const o=a==null?void 0:a.polar_guide;if(o&&typeof((i=o.frame_center)==null?void 0:i.x)=="number"&&typeof((c=o.frame_center)==null?void 0:c.y)=="number"&&typeof((f=o.target)==null?void 0:f.x)=="number"&&typeof((g=o.target)==null?void 0:g.y)=="number"){const h=o.frame_center.x,y=o.frame_center.y,k=o.target.x,j=o.target.y;e.strokeStyle="rgba(244, 63, 94, 0.95)",e.fillStyle="rgba(244, 63, 94, 0.95)",e.lineWidth=2,e.beginPath(),e.moveTo(h,y),e.lineTo(k,j),e.stroke(),e.beginPath(),e.arc(k,j,6,0,Math.PI*2),e.stroke();const E=typeof o.angular_sep_deg=="number"?o.angular_sep_deg.toFixed(2):"-";e.font="12px system-ui, sans-serif",e.fillText(`Polar ${E}deg`,k+10,j-6)}}function Mc(e,t,n,r){if(!n)return;const l=t.naturalWidth||1,a=t.naturalHeight||1;e.width=l,e.height=a,e.style.width=`${t.clientWidth}px`,e.style.height=`${t.clientHeight}px`;const o=e.getContext("2d");o&&Zf(o,l,a,n,r)}function Ev(e,t,n,r){if(!n)return;const l=t.videoWidth||1,a=t.videoHeight||1;if(l<2||a<2)return;e.width=l,e.height=a,e.style.width=`${t.clientWidth}px`,e.style.height=`${t.clientHeight}px`;const o=e.getContext("2d");o&&Zf(o,l,a,n,r)}var U;(function(e){e.LOAD="LOAD",e.EXEC="EXEC",e.FFPROBE="FFPROBE",e.WRITE_FILE="WRITE_FILE",e.READ_FILE="READ_FILE",e.DELETE_FILE="DELETE_FILE",e.RENAME="RENAME",e.CREATE_DIR="CREATE_DIR",e.LIST_DIR="LIST_DIR",e.DELETE_DIR="DELETE_DIR",e.ERROR="ERROR",e.DOWNLOAD="DOWNLOAD",e.PROGRESS="PROGRESS",e.LOG="LOG",e.MOUNT="MOUNT",e.UNMOUNT="UNMOUNT"})(U||(U={}));const bv=(()=>{let e=0;return()=>e++})(),Cv=new Error("ffmpeg is not loaded, call `await ffmpeg.load()` first"),Pv=new Error("called FFmpeg.terminate()");var Te,Ft,xt,sn,un,ya,Se;class Rv{constructor(){Mt(this,Te,null);Mt(this,Ft,{});Mt(this,xt,{});Mt(this,sn,[]);Mt(this,un,[]);we(this,"loaded",!1);Mt(this,ya,()=>{I(this,Te)&&(I(this,Te).onmessage=({data:{id:t,type:n,data:r}})=>{switch(n){case U.LOAD:this.loaded=!0,I(this,Ft)[t](r);break;case U.MOUNT:case U.UNMOUNT:case U.EXEC:case U.FFPROBE:case U.WRITE_FILE:case U.READ_FILE:case U.DELETE_FILE:case U.RENAME:case U.CREATE_DIR:case U.LIST_DIR:case U.DELETE_DIR:I(this,Ft)[t](r);break;case U.LOG:I(this,sn).forEach(l=>l(r));break;case U.PROGRESS:I(this,un).forEach(l=>l(r));break;case U.ERROR:I(this,xt)[t](r);break}delete I(this,Ft)[t],delete I(this,xt)[t]})});Mt(this,Se,({type:t,data:n},r=[],l)=>I(this,Te)?new Promise((a,o)=>{const i=bv();I(this,Te)&&I(this,Te).postMessage({id:i,type:t,data:n},r),I(this,Ft)[i]=a,I(this,xt)[i]=o,l==null||l.addEventListener("abort",()=>{o(new DOMException(`Message # ${i} was aborted`,"AbortError"))},{once:!0})}):Promise.reject(Cv));we(this,"load",({classWorkerURL:t,...n}={},{signal:r}={})=>(I(this,Te)||(ir(this,Te,t?new Worker(new URL(t,import.meta.url),{type:"module"}):new Worker(new URL("/static/analysis-lab/assets/worker-BAOIWoxA.js",import.meta.url),{type:"module"})),I(this,ya).call(this)),I(this,Se).call(this,{type:U.LOAD,data:n},void 0,r)));we(this,"exec",(t,n=-1,{signal:r}={})=>I(this,Se).call(this,{type:U.EXEC,data:{args:t,timeout:n}},void 0,r));we(this,"ffprobe",(t,n=-1,{signal:r}={})=>I(this,Se).call(this,{type:U.FFPROBE,data:{args:t,timeout:n}},void 0,r));we(this,"terminate",()=>{const t=Object.keys(I(this,xt));for(const n of t)I(this,xt)[n](Pv),delete I(this,xt)[n],delete I(this,Ft)[n];I(this,Te)&&(I(this,Te).terminate(),ir(this,Te,null),this.loaded=!1)});we(this,"writeFile",(t,n,{signal:r}={})=>{const l=[];return n instanceof Uint8Array&&l.push(n.buffer),I(this,Se).call(this,{type:U.WRITE_FILE,data:{path:t,data:n}},l,r)});we(this,"mount",(t,n,r)=>{const l=[];return I(this,Se).call(this,{type:U.MOUNT,data:{fsType:t,options:n,mountPoint:r}},l)});we(this,"unmount",t=>{const n=[];return I(this,Se).call(this,{type:U.UNMOUNT,data:{mountPoint:t}},n)});we(this,"readFile",(t,n="binary",{signal:r}={})=>I(this,Se).call(this,{type:U.READ_FILE,data:{path:t,encoding:n}},void 0,r));we(this,"deleteFile",(t,{signal:n}={})=>I(this,Se).call(this,{type:U.DELETE_FILE,data:{path:t}},void 0,n));we(this,"rename",(t,n,{signal:r}={})=>I(this,Se).call(this,{type:U.RENAME,data:{oldPath:t,newPath:n}},void 0,r));we(this,"createDir",(t,{signal:n}={})=>I(this,Se).call(this,{type:U.CREATE_DIR,data:{path:t}},void 0,n));we(this,"listDir",(t,{signal:n}={})=>I(this,Se).call(this,{type:U.LIST_DIR,data:{path:t}},void 0,n));we(this,"deleteDir",(t,{signal:n}={})=>I(this,Se).call(this,{type:U.DELETE_DIR,data:{path:t}},void 0,n))}on(t,n){t==="log"?I(this,sn).push(n):t==="progress"&&I(this,un).push(n)}off(t,n){t==="log"?ir(this,sn,I(this,sn).filter(r=>r!==n)):t==="progress"&&ir(this,un,I(this,un).filter(r=>r!==n))}}Te=new WeakMap,Ft=new WeakMap,xt=new WeakMap,sn=new WeakMap,un=new WeakMap,ya=new WeakMap,Se=new WeakMap;var Lc;(function(e){e.MEMFS="MEMFS",e.NODEFS="NODEFS",e.NODERAWFS="NODERAWFS",e.IDBFS="IDBFS",e.WORKERFS="WORKERFS",e.PROXYFS="PROXYFS"})(Lc||(Lc={}));const Mv=new Error("failed to get response body reader"),Lv=new Error("failed to complete download"),Tv="Content-Length",Iv=e=>new Promise((t,n)=>{const r=new FileReader;r.onload=()=>{const{result:l}=r;l instanceof ArrayBuffer?t(new Uint8Array(l)):t(new Uint8Array)},r.onerror=l=>{var a,o;n(Error(`File could not be read! Code=${((o=(a=l==null?void 0:l.target)==null?void 0:a.error)==null?void 0:o.code)||-1}`))},r.readAsArrayBuffer(e)}),Ov=async e=>{let t;if(typeof e=="string")/data:_data\/([a-zA-Z]*);base64,([^"]*)/.test(e)?t=atob(e.split(",")[1]).split("").map(n=>n.charCodeAt(0)):t=await(await fetch(e)).arrayBuffer();else if(e instanceof URL)t=await(await fetch(e)).arrayBuffer();else if(e instanceof File||e instanceof Blob)t=await Iv(e);else return new Uint8Array;return new Uint8Array(t)},zv=async(e,t)=>{var l;const n=await fetch(e);let r;try{const a=parseInt(n.headers.get(Tv)||"-1"),o=(l=n.body)==null?void 0:l.getReader();if(!o)throw Mv;const i=[];let c=0;for(;;){const{done:h,value:y}=await o.read(),k=y?y.length:0;if(h){if(a!=-1&&a!==c)throw Lv;t&&t({url:e,total:a,received:c,delta:k,done:h});break}i.push(y),c+=k,t&&t({url:e,total:a,received:c,delta:k,done:h})}const f=new Uint8Array(c);let g=0;for(const h of i)f.set(h,g),g+=h.length;r=f.buffer}catch(a){console.log("failed to send download progress event: ",a),r=await n.arrayBuffer()}return r},Tc=async(e,t,n=!1,r)=>{const l=n?await zv(e,r):await(await fetch(e)).arrayBuffer(),a=new Blob([l],{type:t});return URL.createObjectURL(a)};let Ao=null,gr=null;async function Fv(e){if(Ao)return Ao;if(gr)return gr;gr=(async()=>{const t=new Rv;t.on("progress",({progress:a})=>{e&&e(a,"transcoding")});const n="https://unpkg.com/@ffmpeg/core@0.12.6/dist/esm",r=await Tc(`${n}/ffmpeg-core.js`,"text/javascript"),l=await Tc(`${n}/ffmpeg-core.wasm`,"application/wasm");return e&&e(.01,"loading_ffmpeg"),await t.load({coreURL:r,wasmURL:l}),Ao=t,t})();try{return await gr}finally{gr=null}}async function Dv(e){const t=URL.createObjectURL(e);try{return await new Promise(r=>{const l=document.createElement("video");l.preload="metadata",l.onloadedmetadata=()=>{const a=Number(l.duration);r(Number.isFinite(a)&&a>0?a:null)},l.onerror=()=>r(null),l.src=t})}finally{URL.revokeObjectURL(t)}}async function Ic(e,t){const n=await Fv(t),r="input.avi",l="output.mp4";t&&t(.03,"writing_input"),await n.writeFile(r,await Ov(e)),await n.exec(["-i",r,"-c:v","libx264","-preset","veryfast","-crf","24","-pix_fmt","yuv420p","-movflags","+faststart","-an",l]);const a=await n.readFile(l);t&&t(.98,"packing_output");const o=new Blob([a],{type:"video/mp4"}),i=new File([o],e.name.replace(/\.avi$/i,".mp4"),{type:"video/mp4"});await n.deleteFile(r),await n.deleteFile(l);const c=await Dv(i);return t&&t(1,"done"),{file:i,duration_s:c}}const Uv={"app.title":"OGScope Plate Solve Console","nav.lab":"Lab","nav.labImage":"Image solve","nav.labVideo":"Video solve","delete.uploadCascade":"Delete {n} linked experiment record(s) as well?","lab.solveCurrentFrame":"Solve current frame (file)","lab.solveFileStart":"Start continuous file solve","lab.solveFileStop":"Stop continuous file solve","lab.cameraPreviewLoading":"Connecting to shared preview…","lab.solveCameraFrame":"Solve live camera frame","lab.solveCameraStart":"Start camera solve","lab.solveCameraStop":"Stop camera solve","lab.videoPreviewFailed":"This video format may be unsupported by the browser. Try MP4 (H.264) or WebM.","lab.previewModeFile":"Pool file","lab.previewModeCamera":"Device camera","lab.videoLiveIntro":"Shares the same camera as Camera Debug — preview and solve live frames here without opening debug. Both pages can run together.","lab.cameraSnapshotName":"ogscope_camera_live","lab.metric.probRaw":"Raw Prob","lab.systemLoad":"System load","results.saveBatchAll":"Save all to records","nav.pool":"Assets","nav.history":"Records","nav.cameraDebug":"Camera Debug","nav.home":"Home","lang.zh":"中文","lang.en":"EN","sidebar.assets":"Uploaded assets","sidebar.upload":"Upload","sidebar.refresh":"Refresh","sidebar.debugCaptures":"Debug console media","sidebar.assetTypeImage":"Image","sidebar.assetTypeVideo":"Video","sidebar.debugEmpty":"No debug files","sidebar.importToPool":"Import to pool","sidebar.importToPoolWithTranscode":"Transcode & Import","sidebar.importToPoolDirect":"Import Directly","sidebar.flowPreparing":"Preparing...","sidebar.flowImportingDebug":"Importing debug capture...","sidebar.flowLoadingTranscoder":"Loading transcoder...","sidebar.flowTranscoding":"Transcoding AVI -> MP4...","sidebar.flowPackaging":"Packaging output...","sidebar.flowUploadingMp4":"Uploading MP4...","sidebar.flowReplacing":"Replacing and removing original AVI...","sidebar.flowNoTranscode":"No transcode required, imported directly.","sidebar.flowDone":"Completed","sidebar.flowDoneMsg":"Done: {name}","sidebar.flowFailed":"Failed","sidebar.debugPage":"Page {cur} / {total}","sidebar.batchPresets":"Batch presets","sidebar.batchHint":"Check presets, then use Batch solve to compare multiple param sets.","lab.selectOrUpload":"Pick an uploaded or imported asset from the left","lab.selectOrUploadVideo":"Pick a pool video to preview, or use the button above for the live camera frame.","lab.file":"File","lab.source":"Source","lab.layers":"Layers","lab.layer.matched":"Matched","lab.layer.pattern":"Pattern","lab.layer.all":"All centroids","lab.grid":"Grid","lab.zoomIn":"Zoom in","lab.zoomOut":"Zoom out","lab.zoomReset":"Reset","lab.resolution":"Resolution","lab.fwhm":"FWHM","lab.starsDetected":"Stars detected","lab.meta.title":"Capture & file info","lab.meta.noSidecar":"No sidecar (not from debug capture)","lab.meta.partial":"No detailed sidecar; file info only.","lab.solveSection":"Solve","lab.imageSection":"Image","lab.metric.solveMs":"Time","lab.metric.solveComputeMs":"Solve compute","lab.metric.solveComputeHelp":"Server-side Tetra3 + star extraction only (no network).","lab.metric.solveRoundTripMs":"End-to-end","lab.metric.solveRoundTripHelp":"From request start to UI updated: network + JSON + render.","lab.metric.backendTotalMs":"Backend total","lab.metric.openDecodeMs":"Open/decode","lab.metric.preprocessMs":"Preprocess","lab.metric.extractMs":"Extract","lab.metric.solveOnlyMs":"Solve match","lab.metric.probHelp":"Solver confidence (0–1); higher means a more trustworthy plate match.","lab.metric.probRawHelp":"Raw Tetra3 Prob (e.g. log-likelihood); compare with the normalized line above.","lab.metric.radec":"RA / Dec","lab.metric.matches":"Matches","lab.metric.rmse":"RMSE","lab.metric.prob":"Prob.","lab.metric.status":"Status","meta.exposure":"Exposure","meta.gain":"Gain","meta.fps":"FPS","meta.sensor":"Sensor","meta.colorMode":"Color","meta.outputResolution":"Output size","meta.fileTime":"File time","meta.fileSize":"File size","results.viewRaw":"Raw JSON","results.hideRaw":"Hide","params.title":"Solve parameters","params.blockSolveIntro":"Plate-solve (Tetra3): FOV, timeout, and coarse sky hints. FOV should match your lens.","params.centroid":"Star detection","params.blockCentroidIntro":"Star detection: threshold, blob area, and local background window for centroids.","params.fov":"FOV estimate (°)","params.fovHelp":"Horizontal field of view for lost-in-space solve.","params.fovErr":"FOV max error (°)","params.fovErrHelp":"Search range around estimated FOV.","params.timeout":"Timeout (ms)","params.timeoutHelp":"Max wait time per solve.","params.solveIntervalMs":"Realtime solve interval (ms)","params.solveIntervalMsHelp":"Desired interval is adjustable, but backend clamps it into a safe range.","params.solveIntervalBound":"Backend bounds: {min}-{max} ms, effective now: {effective} ms","params.solveProfile":"Solve profile","params.solveProfileHelp":"Speed/Balanced/Robust tune timeout, centroid thresholds, and matching star count together.","params.solveProfileSpeed":"Speed first","params.solveProfileBalanced":"Balanced","params.solveProfileRobust":"Robust first","params.ra":"RA hint (°)","params.raHelp":"Approximate right ascension in degrees.","params.dec":"Dec hint (°)","params.decHelp":"Approximate declination in degrees.","params.maxSide":"Max long side before extract (px)","params.maxSideHelp":"Downscale long edge for faster centroid extraction.","params.detailLevelFull":"Include full Tetra3 raw block (larger payload, for debugging only).","params.largeScaleBg":"Large-scale background flattening","params.largeScaleBgHelp":"Before centroiding, estimate a low-frequency background on a downscaled image and correct uneven illumination (e.g. corner glow). Off by default to match legacy behavior.","params.sigma":"σ threshold","params.sigmaHelp":"Multiplier over background noise for star candidates.","params.maxArea":"max_area","params.maxAreaHelp":"Max connected component area in pixels.","params.minArea":"min_area","params.minAreaHelp":"Min connected component area in pixels.","params.filtsize":"filtsize (odd)","params.filtsizeHelp":"Local filter window size, must be odd.","btn.solveOne":"Solve once","btn.solveBatch":"Batch solve (presets)","btn.applyPresets":"Apply preset to form","btn.savePreset":"Save","placeholder.newPreset":"New preset name","pool.title":"Server asset pool","pool.col.name":"Filename","pool.col.source":"Source","pool.col.size":"Size","pool.col.time":"Modified","pool.delete":"Delete","history.title":"Experiment records","history.intro":"Saved solve snapshots from the Lab. After a solve, use Save to records in the Lab main panel (Result comparison), or Save on each batch result card. Search by filename or preset; export JSON/CSV for backup.","history.search":"Search…","history.searchBtn":"Search","history.exportJson":"Export JSON","history.exportCsv":"Export CSV","history.total":"Total {n}","history.preset":"Preset","history.metrics":"Metrics","history.detail":"Details","history.collapse":"Collapse","history.prev":"Prev","history.next":"Next","history.delete":"Delete","delete.uploadFirst":'Delete "{name}" from the asset pool?',"delete.uploadSecond":"This cannot be undone. Confirm again?","delete.experimentFirst":"Delete this experiment record?","delete.experimentSecond":"This cannot be undone. Confirm again?","results.title":"Results","results.saveCurrent":"Save to records","results.saveRow":"Save","results.expand":"Expand","results.collapseJson":"Collapse","err.selectFile":"Select a file","err.selectPresets":"Select at least one preset","common.placeholder":"—","lab.transcode.title":"AVI requires transcoding","lab.transcode.desc":"This video is AVI. For browser preview and continuous solving, transcode it locally to MP4 and upload replacement. The original AVI on server will be removed after success.","lab.transcode.button":"Transcode and Upload Replacement","lab.transcode.loading":"Loading transcoder...","lab.transcode.running":"Transcoding AVI -> MP4...","lab.transcode.packaging":"Packaging transcoded output...","lab.transcode.uploading":"Uploading transcoded output...","lab.transcode.done":"Transcode and replacement completed.","lab.transcode.failed":"Transcode or upload failed. Please retry.","lab.transcode.fetchFailed":"Failed to fetch AVI from server."},Av={"app.title":"OGScope 星空解算控制台","nav.lab":"解算台","nav.labImage":"图片解算","nav.labVideo":"视频解算","nav.pool":"素材池","nav.history":"实验记录","nav.cameraDebug":"相机调试控制台","nav.home":"首页","lang.zh":"中文","lang.en":"EN","sidebar.assets":"自行上传素材","sidebar.upload":"上传文件","sidebar.refresh":"刷新列表","sidebar.debugCaptures":"调试控制台素材","sidebar.assetTypeImage":"图片","sidebar.assetTypeVideo":"视频","sidebar.debugEmpty":"暂无调试文件","sidebar.importToPool":"导入到素材池","sidebar.importToPoolWithTranscode":"转码并导入素材池","sidebar.importToPoolDirect":"直接导入素材池","sidebar.flowPreparing":"准备中…","sidebar.flowImportingDebug":"正在导入调试素材…","sidebar.flowLoadingTranscoder":"正在加载转码器…","sidebar.flowTranscoding":"正在转码 AVI -> MP4…","sidebar.flowPackaging":"正在封装转码结果…","sidebar.flowUploadingMp4":"正在上传 MP4…","sidebar.flowReplacing":"正在替换并清理原 AVI…","sidebar.flowNoTranscode":"该文件无需转码,已直接导入。","sidebar.flowDone":"流程完成","sidebar.flowDoneMsg":"已完成:{name}","sidebar.flowFailed":"流程失败","sidebar.debugPage":"第 {cur} / {total} 页","sidebar.batchPresets":"批量预设","sidebar.batchHint":"勾选后点击「批量解算」可一次用多组参数对比结果。","lab.selectOrUpload":"从左侧选择自行上传或已导入的素材","lab.selectOrUploadVideo":"从左侧选择视频素材预览;或使用上方按钮解算设备相机实时帧。","lab.file":"文件","lab.source":"来源","lab.layers":"叠加层","lab.layer.matched":"匹配星","lab.layer.pattern":"图案星","lab.layer.all":"全部质心","lab.grid":"网格","lab.zoomIn":"放大","lab.zoomOut":"缩小","lab.zoomReset":"复位","lab.resolution":"分辨率","lab.fwhm":"FWHM","lab.starsDetected":"检测星点","lab.meta.title":"拍摄与文件信息","lab.meta.noSidecar":"无侧车信息(非调试采集或仅本地上传)","lab.meta.partial":"暂无侧车详细字段,仅显示文件信息。","lab.solveSection":"解算","lab.imageSection":"图像","lab.metric.solveMs":"用时","lab.metric.solveComputeMs":"解算计算用时","lab.metric.solveComputeHelp":"服务端 Tetra3 与提星等纯计算耗时(与网络无关)。","lab.metric.solveRoundTripMs":"全链路用时","lab.metric.solveRoundTripHelp":"从本页发起请求到收到结果并完成界面刷新的总耗时,含网络往返与浏览器渲染。","lab.metric.backendTotalMs":"后端总用时","lab.metric.openDecodeMs":"读取/解码","lab.metric.preprocessMs":"预处理","lab.metric.extractMs":"提星","lab.metric.solveOnlyMs":"匹配解算","lab.metric.probHelp":"由解算器给出的匹配置信度(0–1),越高表示星图与天区匹配越可信。","lab.metric.probRawHelp":"Tetra3 返回的原始 Prob 字段,可能为对数似然等内部量;与上一行换算后的百分比对照查看即可。","lab.metric.radec":"RA / Dec","lab.metric.matches":"匹配","lab.metric.rmse":"RMSE","lab.metric.prob":"置信","lab.metric.status":"状态","meta.exposure":"曝光","meta.gain":"增益","meta.fps":"帧率","meta.sensor":"传感器","meta.colorMode":"色彩","meta.outputResolution":"输出分辨率","meta.fileTime":"文件时间","meta.fileSize":"文件大小","results.viewRaw":"原始 JSON","results.hideRaw":"收起","params.title":"解算参数","params.blockSolveIntro":"以下为板块求解(Tetra3)搜索天区、超时与粗略指向提示;FOV 需与镜头视场大致一致。","params.centroid":"提星","params.blockCentroidIntro":"以下为星点检测:阈值、连通域面积与局部背景窗口,用于从图像中提取星点质心。","params.fov":"FOV 估计 (°)","params.fovHelp":"水平视场角估计值,用于 lost-in-space 解算。","params.fovErr":"FOV 允许误差 (°)","params.fovErrHelp":"允许 Tetra3 在估计 FOV 附近的搜索范围。","params.timeout":"超时 (ms)","params.timeoutHelp":"单次解算最长等待时间。","params.solveIntervalMs":"实时解算间隔 (ms)","params.solveIntervalMsHelp":"期望间隔可调整,但会被后端限制在安全范围内。","params.solveIntervalBound":"后端限制范围:{min}-{max} ms,当前生效:{effective} ms","params.solveProfile":"解算档位","params.solveProfileHelp":"速度/平衡/稳健三档会同时调整超时、提星阈值与参与匹配星点数。","params.solveProfileSpeed":"速度优先","params.solveProfileBalanced":"平衡","params.solveProfileRobust":"稳健优先","params.ra":"RA 提示 (°)","params.raHelp":"大致天球赤经,缩小搜索范围(度)。","params.dec":"Dec 提示 (°)","params.decHelp":"大致天球赤纬(度)。","params.maxSide":"提星前长边上界 (px)","params.maxSideHelp":"降采样长边上限,大图可加速提星。","params.detailLevelFull":"包含完整 Tetra3 原始结果(体积略大,仅调试时开启)","params.largeScaleBg":"大尺度背景减除","params.largeScaleBgHelp":"在提星前用低分辨率平滑估计并校正大尺度亮度不均,可减轻角部光晕导致的假星;默认关闭以保持与过往行为一致。","params.sigma":"σ(阈值倍数)","params.sigmaHelp":"高于背景噪声倍数的区域视为星点候选。","params.maxArea":"max_area","params.maxAreaHelp":"连通域最大像素面积。","params.minArea":"min_area","params.minAreaHelp":"连通域最小像素面积。","params.filtsize":"filtsize(奇数)","params.filtsizeHelp":"局部背景滤波窗口边长,须为奇数。","btn.solveOne":"单张解算","btn.solveBatch":"批量解算(勾选预设)","btn.applyPresets":"应用预设到表单","btn.savePreset":"保存","placeholder.newPreset":"新预设名称","pool.title":"服务器素材池","pool.col.name":"文件名","pool.col.source":"来源","pool.col.size":"大小","pool.col.time":"修改时间","pool.delete":"删除","history.title":"实验记录","history.intro":"此处展示你在解算台完成解算后手动保存的快照。用法:在「解算台」主栏「结果对比」中,单张解算后点「保存当前到实验记录」,或批量解算后在某张结果卡片上点「保存记录」。本页可按文件名或预设名搜索,支持导出 JSON/CSV 备份。","history.search":"搜索…","history.searchBtn":"搜索","history.exportJson":"导出 JSON","history.exportCsv":"导出 CSV","history.total":"共 {n} 条","history.preset":"预设","history.metrics":"指标","history.detail":"详情","history.collapse":"收起","history.prev":"上一页","history.next":"下一页","history.delete":"删除","delete.uploadFirst":"确定要从素材池删除「{name}」吗?","delete.uploadSecond":"此操作不可恢复,再次确认删除?","delete.experimentFirst":"确定要删除这条实验记录吗?","delete.experimentSecond":"此操作不可恢复,再次确认删除?","results.title":"结果对比","results.saveCurrent":"保存当前到实验记录","results.saveRow":"保存记录","results.expand":"展开","results.collapseJson":"收起","err.selectFile":"请选择素材","err.selectPresets":"请勾选至少一个预设","common.placeholder":"—","delete.uploadCascade":"该素材有 {n} 条实验记录,是否一并删除?","lab.solveCurrentFrame":"解算当前帧(文件)","lab.solveFileStart":"开始文件连续解算","lab.solveFileStop":"停止文件连续解算","lab.cameraPreviewLoading":"正在连接共享预览…","lab.solveCameraFrame":"解算相机当前帧","lab.solveCameraStart":"开始相机解算","lab.solveCameraStop":"停止相机解算","lab.videoPreviewFailed":"该视频格式可能不受浏览器支持,建议使用 MP4(H.264) 或 WebM。","lab.previewModeFile":"素材文件","lab.previewModeCamera":"设备相机","lab.videoLiveIntro":"与调试控制台共用同一相机;此处可预览并解算实时帧,无需单独打开调试页。两页可同时使用。","lab.cameraSnapshotName":"ogscope_camera_live","lab.metric.probRaw":"原始 Prob","lab.systemLoad":"系统负载","results.saveBatchAll":"保存全部到实验记录","lab.transcode.title":"AVI 文件需先转码","lab.transcode.desc":"当前视频为 AVI。为保证浏览器可预览与连续解算,请先在本地转码为 MP4 并上传替换。上传成功后将自动删除服务器上的原 AVI。","lab.transcode.button":"转码并上传替换","lab.transcode.loading":"正在加载转码器…","lab.transcode.running":"正在转码 AVI -> MP4…","lab.transcode.packaging":"正在封装转码结果…","lab.transcode.uploading":"正在上传转码结果…","lab.transcode.done":"转码并替换完成,可继续预览与解算。","lab.transcode.failed":"转码或上传失败,请重试。","lab.transcode.fetchFailed":"无法读取服务器上的 AVI 文件。"},qf=w.createContext(null),Oc={zh:Av,en:Uv};function $v({children:e}){const[t,n]=w.useState("zh"),[r,l]=w.useState(Oc.zh);w.useEffect(()=>{l(Oc[t])},[t]);const a=w.useMemo(()=>(i,c)=>{let f=r[i]??i;if(c)for(const[g,h]of Object.entries(c))f=f.replace(new RegExp(`\\{${g}\\}`,"g"),String(h));return f},[r]),o=w.useMemo(()=>({locale:t,setLocale:n,t:a}),[t,a]);return s.jsx(qf.Provider,{value:o,children:e})}function ep(){const e=w.useContext(qf);if(!e)throw new Error("useI18n must be used within I18nProvider");return e}function Fs(e){return e==null||Number.isNaN(e)?"—":e<1024?`${e} B`:e<1024*1024?`${(e/1024).toFixed(1)} KB`:`${(e/(1024*1024)).toFixed(2)} MB`}function Wl(e,t){if(!e)return"—";try{const n=new Date(e);return new Intl.DateTimeFormat(t==="en"?"en-GB":"zh-CN",{dateStyle:"short",timeStyle:"medium"}).format(n)}catch{return e}}function tn(e){return e==null?null:typeof e=="string"&&e.trim()?e.trim():typeof e=="number"&&!Number.isNaN(e)?String(e):null}function Hv(e){return typeof e!="number"||e<=0?null:e>=1e6?`${(e/1e6).toFixed(2)} s`:e>=1e3?`${(e/1e3).toFixed(0)} ms`:`${e} µs`}function Bv(e,t){if(!e)return[];const n=[],r=Hv(e.exposure_us);r&&n.push({key:"meta.exposure",value:r});const l=tn(e.analogue_gain),a=tn(e.digital_gain);if(l||a){const y=[l?`A ${l}`:"",a?`D ${a}`:""].filter(Boolean);n.push({key:"meta.gain",value:y.join(" · ")})}const o=tn(e.fps);o&&n.push({key:"meta.fps",value:o});const i=tn(e.sensor);i&&n.push({key:"meta.sensor",value:i});const c=tn(e.color_mode);c&&n.push({key:"meta.colorMode",value:c});const f=tn(e.resolution);f&&n.push({key:"meta.outputResolution",value:f});const g=tn(e.modified);g&&n.push({key:"meta.fileTime",value:Wl(g,t)});const h=e.size;return typeof h=="number"&&h>0&&n.push({key:"meta.fileSize",value:Fs(h)}),n}function tp(e){const t=n=>typeof n=="number"&&!Number.isNaN(n)?n:null;return e?{tSolveMs:t(e.t_solve_ms),tExtractMs:t(e.t_extract_ms),tPreprocessMs:t(e.t_preprocess_ms),tOpenDecodeMs:t(e.t_open_decode_ms),tBackendTotalMs:t(e.t_backend_total_ms),raDeg:t(e.ra_deg),decDeg:t(e.dec_deg),matches:t(e.matches),rmseArcsec:t(e.rmse_arcsec),prob:t(e.prob),status:typeof e.status=="string"?e.status:null}:{tSolveMs:null,tExtractMs:null,tPreprocessMs:null,tOpenDecodeMs:null,tBackendTotalMs:null,raDeg:null,decDeg:null,matches:null,rmseArcsec:null,prob:null,status:null}}function va(e){return e==null?"—":`${e.toFixed(4)}°`}function Vv(e){return e==null?"—":e>=0&&e<=1?`${(e*100).toFixed(1)}%`:String(e)}function Wv(e){if(e==null)return"—";if(typeof e=="number"&&!Number.isNaN(e)){const t=Math.abs(e);return t>0&&t<.001?e.toExponential(4):t>=0&&t<=1?`${(e*100).toFixed(4)}%`:String(e)}return String(e)}function Qn(e,t){const n=t==null?void 0:t.tetra,r=n?n.Prob??n.prob:void 0;let l=Vv(e);e!=null&&e>=0&&e<=1&&e>0&&e<1e-4&&(l=`${(e*100).toExponential(2)}%`),e===0&&r!==void 0&&r!==null&&(l="—");const a=r!=null?Wv(r):null;return{line:l,rawLine:a}}const $o=()=>({hint_ra_deg:45,hint_dec_deg:80,fov_estimate:11,fov_max_error:void 0,solve_timeout_ms:1500,solve_profile:"balanced",max_image_side:1600,large_scale_bg_subtract:!1,detail_level:"summary",centroid:{sigma:2.5,max_area:400,min_area:5,filtsize:25,binary_open:!0,max_axis_ratio:void 0}});function Ho(e){return e?{matches:e.matches,rmse_arcsec:e.rmse_arcsec,status:e.status,prob:e.prob,t_solve_ms:e.t_solve_ms}:{}}function Qv(e){var n,r;if(!e)return null;const t=e.solve_overlay;return(n=t==null?void 0:t.stars_matched)!=null&&n.length?t.stars_matched.length:(r=t==null?void 0:t.stars_all_centroids)!=null&&r.length?t.stars_all_centroids.length:typeof e.matches=="number"?e.matches:null}const Ml=30,Bo=6;function zc(e){return/\.(jpe?g|png|webp|bmp|gif|fits?)$/i.test(e)}function bn(e){return/\.(mp4|mov|webm|mkv|avi)$/i.test(e)}function Ll(e,t=16,n=14){return e.length<=t+n+1?e:`${e.slice(0,t)}...${e.slice(-n)}`}function Kv(){var ru,lu,au,ou,su,iu,uu,cu;const{t:e,locale:t,setLocale:n}=ep(),[r,l]=w.useState("lab_image"),[a,o]=w.useState([]),[i,c]=w.useState(null),[f,g]=w.useState($o),[h,y]=w.useState([]),[k,j]=w.useState([]),[E,B]=w.useState(new Set),[p,d]=w.useState(!1),[m,x]=w.useState(null),[N,R]=w.useState(null),[b,M]=w.useState(null),[$,O]=w.useState(""),[se,ct]=w.useState(1),[xe,xn]=w.useState(null),[el,wn]=w.useState(null),[qt,C]=w.useState(""),[L,T]=w.useState({matched:!0,pattern:!0,all:!0}),[V,ee]=w.useState([]),[pe,nt]=w.useState(null),[dt,Le]=w.useState(1),[ft,Li]=w.useState(!1),[Sn,Ia]=w.useState(1),[Ti,np]=w.useState({w:0,h:0}),[kn,rp]=w.useState({w:0,h:0}),[Ii,lp]=w.useState({w:0,h:0}),[te,tl]=w.useState("file"),[nl,Oi]=w.useState(null),[rr,Oa]=w.useState(null),[za,zi]=w.useState(!1),[rl,Fi]=w.useState(!1),[Di,ap]=w.useState(!0),[pt,Fa]=w.useState(!1),[op,Da]=w.useState(null),[Ui,Ua]=w.useState(null),[Aa,ll]=w.useState(null),[$a,Ai]=w.useState(!1),[sp,Ha]=w.useState({}),[$i,Ba]=w.useState(!1),[Va,mt]=w.useState(null),[ip,lr]=w.useState(null),[Hi,al]=w.useState(null),[Wa,Bi]=w.useState(null),[Vi,Wi]=w.useState(!1),[Qi,Qa]=w.useState(null),[up,en]=w.useState(null),[Ki,Gi]=w.useState(!1),[Ka,Nn]=w.useState(null),[cp,ht]=w.useState(null),[Ga,Ya]=w.useState(null),Xa=w.useRef(null),ar=w.useRef(null),Yi=w.useRef(null),ol=w.useRef(null),sl=w.useRef(null),il=w.useRef(null),Ja=w.useRef(!1),Za=w.useRef(!1),_n=w.useRef(null),[ul,dp]=w.useState(null),[me,Xi]=w.useState(null),qa=(me==null?void 0:me.star_analysis_min_interval_ms)??2e3,eo=(me==null?void 0:me.star_analysis_max_interval_ms)??12e3,to=w.useMemo(()=>{const u=(me==null?void 0:me.star_analysis_target_fps)??.6666666666666666,v=Math.min(Math.max(u,.2),5);return Math.round(1e3/v)},[me]),or=w.useMemo(()=>Math.min(Math.max(Wa??to,qa),eo),[to,Wa,eo,qa]),Ct=w.useCallback(async()=>{const[u,v,_]=await Promise.all([cv(),zo("official"),zo("user")]);o(u.files),y(v.presets),j(_.presets)},[]),no=w.useCallback(()=>{yv().then(u=>ee(u.files)).catch(()=>ee([]))},[]);w.useEffect(()=>{Sv().then(u=>{Xi(u);const v=Math.round(1e3/Math.min(Math.max(u.star_analysis_target_fps,.2),5)),_=Math.min(Math.max(v,u.star_analysis_min_interval_ms),u.star_analysis_max_interval_ms);Bi(_)}).catch(()=>Xi(null))},[]),w.useEffect(()=>{Ct().catch(u=>x(String(u)))},[Ct]),w.useEffect(()=>{no()},[no]),w.useEffect(()=>{r==="history"&&Do($,se,Ml).then(xn).catch(u=>x(String(u)))},[r,$,se]),w.useEffect(()=>{if(!i){ll(null);return}let u=!1;return Ai(!0),(async()=>{try{const v=await xv(i);if(u)return;let _={...v};try{_={...await Pc(i),...v}}catch{}ll(_)}catch{try{const v=await Pc(i);u||ll(v)}catch{u||ll(null)}}finally{u||Ai(!1)}})(),()=>{u=!0}},[i]),w.useEffect(()=>{R(null),M(null),Ha({}),Ba(!1),mt(null),lr(null),tl("file"),Oa(null)},[i]);const vt=w.useMemo(()=>{const u=N==null?void 0:N.result;if(!u)return null;const v=u.solve_overlay||null;if(!v)return null;const _=u.overlay_ext||null;return{...v,overlay_ext:_||void 0}},[N]),Pt=w.useMemo(()=>(N==null?void 0:N.result)??null,[N]),Ji=w.useMemo(()=>Qv(Pt),[Pt]),ro=w.useMemo(()=>r==="lab_image"?Ti:r==="lab_video"?te==="file"?kn:Ii:{w:0,h:0},[r,te,Ti,kn,Ii]),cl=i?Uo(i):"";w.useEffect(()=>{if(r!=="lab_image")return;const u=Xa.current,v=_n.current;if(!u||!v||!vt||!i)return;const _=()=>Mc(v,u,vt,L);u.complete?_():u.onload=_},[vt,i,N,L,r]),w.useEffect(()=>{if(r!=="lab_video"||te!=="file")return;const u=ar.current,v=_n.current;if(!u||!v||!vt||!i)return;const _=()=>Ev(v,u,vt,L);return u.addEventListener("loadeddata",_),u.addEventListener("seeked",_),u.readyState>=2&&_(),()=>{u.removeEventListener("loadeddata",_),u.removeEventListener("seeked",_)}},[vt,L,r,te,i,N]),w.useEffect(()=>{if(r!=="lab_video"||te!=="camera")return;const u=Yi.current,v=_n.current;if(!u||!v||!vt)return;const _=()=>Mc(v,u,vt,L);u.complete?_():u.onload=_},[vt,L,r,te,N,nl]),w.useEffect(()=>{if(r!=="lab_video"||te!=="camera")return;let u=!1;const v=async()=>{if(!(u||pt))try{const H=ol.current?`?since_frame_id=${encodeURIComponent(ol.current)}`:"",le=await fetch(`/api/camera/preview${H}`,{cache:"no-store"});if(le.status===304||!le.ok)return;const Ee=le.headers.get("X-Frame-Id");Ee!=null&&(ol.current=Ee);const rt=await le.blob(),lt=URL.createObjectURL(rt);Oi(Rt=>(Rt&&URL.revokeObjectURL(Rt),lt))}catch{}};v();const _=window.setInterval(()=>void v(),180);return()=>{u=!0,clearInterval(_),Oi(H=>(H&&URL.revokeObjectURL(H),null)),ol.current=null}},[r,te,pt]),w.useEffect(()=>{const u=Xa.current;if(!u)return;const v=()=>np({w:u.naturalWidth||0,h:u.naturalHeight||0});return v(),u.addEventListener("load",v),()=>u.removeEventListener("load",v)},[i,cl]),w.useEffect(()=>{const u=ar.current;if(!u)return;const v=()=>rp({w:u.videoWidth||0,h:u.videoHeight||0});return u.addEventListener("loadedmetadata",v),u.addEventListener("loadeddata",v),v(),()=>{u.removeEventListener("loadedmetadata",v),u.removeEventListener("loadeddata",v)}},[i,cl,r]);const fp=u=>{g({...$o(),...u,centroid:{...$o().centroid,...u.centroid}})},pp=async()=>{if(!i){x(e("err.selectFile"));return}x(null),d(!0);const u=performance.now();try{const v=await pv(i,f);R(v),M(null),lr("file"),mt(performance.now()-u)}catch(v){x(String(v)),mt(null)}finally{d(!1)}},mp=async()=>{if(!i){x(e("err.selectFile"));return}const u=[];for(const _ of E){const le=[...h,...k].find(Ee=>Ee.id===_);le&&u.push({label:le.name,params:structuredClone(le.params)})}if(u.length===0){x(e("err.selectPresets"));return}x(null),d(!0);const v=performance.now();try{const _=await mv(i,u);M({results:_.results}),R(null),Ha({}),lr("file"),mt(performance.now()-v)}catch(_){x(String(_)),mt(null)}finally{d(!1)}},lo=async()=>{if(Ja.current)return;x(null),Ja.current=!0;const u=performance.now();try{const v=await kv({source:"camera",overlay_topn_count:3,enable_polar_guide:!0,solve_interval_ms:or,solve_timeout_ms:Math.min(((me==null?void 0:me.solver_timeout_ms)??1500)*.6,1200),...f});v.gate_status&&v.gate_status!=="SOLVED"?al(`${v.gate_status}${v.gate_reason?`: ${v.gate_reason}`:""}`):al(null),R(v),M(null),lr("camera"),tl("camera");const _=v.result,H=typeof(_==null?void 0:_.status)=="string"?String(_.status):"";Di&&H==="MATCH_FOUND"&&(Fa(!0),Da(v.frame_id!=null?String(v.frame_id):null),Ua(nl),fl()),mt(performance.now()-u)}catch(v){x(String(v)),mt(null)}finally{Ja.current=!1}},ao=async()=>{if(Za.current||!i)return;const u=ar.current;if(!u||u.videoWidth<2||u.videoHeight<2||rr)return;x(null),Za.current=!0;const v=performance.now();try{const _=document.createElement("canvas");_.width=u.videoWidth,_.height=u.videoHeight;const H=_.getContext("2d");if(!H)throw new Error("无法创建画布上下文 / Cannot create canvas context");H.drawImage(u,0,0,_.width,_.height);const le=await new Promise((Rt,du)=>{_.toBlob(oo=>oo?Rt(oo):du(new Error("帧编码失败 / Frame encode failed")),"image/jpeg",.92)}),Ee=await Nv(le,{...f,solve_interval_ms:or,solve_timeout_ms:Math.min(((me==null?void 0:me.solver_timeout_ms)??1500)*.6,1200),overlay_topn_count:3,enable_polar_guide:!0}),rt=Ee.gate_status,lt=Ee.gate_reason;al(rt&&rt!=="SOLVED"?`${rt}${lt?`: ${lt}`:""}`:null),R(Ee),M(null),lr("file"),mt(performance.now()-v)}catch(_){x(String(_)),mt(null)}finally{Za.current=!1}},hp=()=>{rl||!i||(Fi(!0),ao(),il.current=window.setInterval(()=>{ao()},or))},Zi=w.useMemo(()=>r!=="lab_video"||te!=="file"||!i||/\.avi$/i.test(i)||rr?!1:kn.w>1&&kn.h>1,[r,te,i,rr,kn.w,kn.h]),qi=w.useMemo(()=>!!i&&/\.avi$/i.test(i),[i]),vp=async()=>{if(!(!i||!qi||Vi)){x(null),Wi(!0),Qa(0),en(e("lab.transcode.loading"));try{const u=Uo(i),v=await fetch(u,{cache:"no-store"});if(!v.ok)throw new Error(e("lab.transcode.fetchFailed"));const _=await v.blob(),H=new File([_],i,{type:"video/x-msvideo"}),le=await Ic(H,(rt,lt)=>{Qa(Math.max(0,Math.min(1,rt))),lt==="loading_ffmpeg"?en(e("lab.transcode.loading")):lt==="transcoding"?en(e("lab.transcode.running")):lt==="packing_output"&&en(e("lab.transcode.packaging"))});en(e("lab.transcode.uploading"));const Ee=await Oo(le.file,"analysis_transcoded");await Cc({old_filename:i,new_filename:Ee.filename,duration_s:le.duration_s,nominal_fps:null,codec_fourcc:"libx264",container:"MP4"}),await Ct(),c(Ee.filename),Qa(1),en(e("lab.transcode.done"))}catch(u){x(String(u)),en(e("lab.transcode.failed"))}finally{Wi(!1)}}},yp=async u=>{const v=Uo(u),_=await fetch(v,{cache:"no-store"});if(!_.ok)throw new Error(e("lab.transcode.fetchFailed"));const H=await _.blob(),le=new File([H],u,{type:"video/x-msvideo"}),Ee=await Ic(le,(lt,Rt)=>{Nn(.2+Math.max(0,Math.min(1,lt))*.6),Rt==="loading_ffmpeg"?ht(e("sidebar.flowLoadingTranscoder")):Rt==="transcoding"?ht(e("sidebar.flowTranscoding")):Rt==="packing_output"&&ht(e("sidebar.flowPackaging"))});ht(e("sidebar.flowUploadingMp4")),Nn(.86);const rt=await Oo(Ee.file,"debug_console_transcoded");return ht(e("sidebar.flowReplacing")),Nn(.94),await Cc({old_filename:u,new_filename:rt.filename,duration_s:Ee.duration_s,nominal_fps:null,codec_fourcc:"libx264",container:"MP4"}),rt.filename},gp=async()=>{if(!pe||Ki)return;const u=/\.avi$/i.test(pe);x(null),Gi(!0),Nn(.02),Ya(null);try{ht(e("sidebar.flowImportingDebug"));const v=await fv(pe);Nn(u?.18:1);let _=v.filename;u?_=await yp(v.filename):ht(e("sidebar.flowNoTranscode")),await Ct(),c(_),l(bn(_)?"lab_video":"lab_image"),Nn(1),ht(e("sidebar.flowDone")),Ya(e("sidebar.flowDoneMsg",{name:Ll(_)}))}catch(v){x(String(v)),ht(e("sidebar.flowFailed")),Ya(String(v))}finally{Gi(!1)}},dl=()=>{Fi(!1),il.current!=null&&(window.clearInterval(il.current),il.current=null)},xp=()=>{za||pt||(zi(!0),lo(),sl.current=window.setInterval(()=>{lo()},or))},fl=()=>{zi(!1),sl.current!=null&&(window.clearInterval(sl.current),sl.current=null)},wp=()=>{Fa(!1),Da(null),Ua(null)},Sp=u=>{B(v=>{const _=new Set(v);return _.has(u)?_.delete(u):_.add(u),_})},eu=async u=>{if(!window.confirm(e("delete.uploadFirst",{name:u})))return;let v=0;try{v=(await wv(u)).count}catch{v=0}if(v>0){if(!window.confirm(e("delete.uploadCascade",{n:v})))return}else if(!window.confirm(e("delete.uploadSecond")))return;d(!0),x(null);try{await dv(u,{deleteExperiments:v>0}),await Ct(),i===u&&c(null)}catch(_){x(String(_))}finally{d(!1)}},kp=async u=>{if(window.confirm(e("delete.experimentFirst"))&&window.confirm(e("delete.experimentSecond"))){d(!0),x(null);try{await vv(u),el===u&&wn(null);const v=await Do($,se,Ml);xn(v)}catch(v){x(String(v))}finally{d(!1)}}},pl=u=>Ia(Math.min(4,Math.max(.5,u))),tu=Math.max(1,Math.ceil(((xe==null?void 0:xe.total)??0)/Ml)),jn=w.useMemo(()=>r==="lab_image"?V.filter(u=>u.type==="image"||zc(u.name)):r==="lab_video"?V.filter(u=>u.type==="video"||bn(u.name)):V,[V,r]),sr=Math.max(1,Math.ceil(jn.length/Bo)),Np=w.useMemo(()=>{const u=(dt-1)*Bo;return jn.slice(u,u+Bo)},[jn,dt]);w.useEffect(()=>{Le(1)},[r]),w.useEffect(()=>{Le(u=>Math.min(u,sr))},[sr]),w.useEffect(()=>{pe&&!jn.some(u=>u.name===pe)&&nt(null)},[jn,pe]);const nu=w.useMemo(()=>Bv(Aa,t),[Aa,t]),_p=w.useMemo(()=>r==="lab_image"?a.filter(u=>zc(u.filename)):r==="lab_video"?a.filter(u=>bn(u.filename)):a,[a,r]),D=w.useMemo(()=>tp(Pt),[Pt]);return w.useEffect(()=>{Ba(!1)},[N]),w.useEffect(()=>{if(r!=="lab_video")return;let u;const v=()=>{jv().then(dp).catch(()=>{})};return v(),u=setInterval(v,1500),()=>{u&&clearInterval(u)}},[r]),w.useEffect(()=>{(r!=="lab_video"||te!=="camera")&&(fl(),Fa(!1),Da(null),Ua(null)),(r!=="lab_video"||te!=="file")&&dl()},[r,te]),w.useEffect(()=>{i||dl()},[i]),w.useEffect(()=>()=>{fl(),dl()},[]),s.jsxs("div",{className:"flex min-h-full flex-col bg-surface text-on-surface",children:[s.jsxs("header",{className:"flex h-12 shrink-0 items-center justify-between border-b border-outline-variant/20 px-4",children:[s.jsxs("div",{className:"flex items-center gap-6",children:[s.jsx("span",{className:"font-headline text-sm font-bold tracking-wide text-on-surface",children:e("app.title")}),s.jsxs("nav",{className:"flex flex-wrap gap-3 text-xs",children:[s.jsx("button",{type:"button",className:r==="lab_image"?"text-primary":"text-on-surface-variant",onClick:()=>l("lab_image"),children:e("nav.labImage")}),s.jsx("button",{type:"button",className:r==="lab_video"?"text-primary":"text-on-surface-variant",onClick:()=>l("lab_video"),children:e("nav.labVideo")}),s.jsx("button",{type:"button",className:r==="pool"?"text-primary":"text-on-surface-variant",onClick:()=>l("pool"),children:e("nav.pool")}),s.jsx("button",{type:"button",className:r==="history"?"text-primary":"text-on-surface-variant",onClick:()=>l("history"),children:e("nav.history")})]})]}),s.jsxs("div",{className:"flex items-center gap-2",children:[s.jsxs("div",{className:"mr-2 flex gap-1 text-[10px]",children:[s.jsx("button",{type:"button",className:`rounded px-2 py-0.5 ${t==="zh"?"bg-primary-container text-on-primary-container":"text-on-surface-variant"}`,onClick:()=>n("zh"),children:e("lang.zh")}),s.jsx("button",{type:"button",className:`rounded px-2 py-0.5 ${t==="en"?"bg-primary-container text-on-primary-container":"text-on-surface-variant"}`,onClick:()=>n("en"),children:e("lang.en")})]}),s.jsxs("a",{className:"flex items-center gap-1 rounded border border-outline-variant/30 px-2 py-1 text-xs text-on-surface-variant hover:bg-surface-container",href:"/debug",children:[s.jsx(av,{className:"h-3.5 w-3.5"})," ",e("nav.cameraDebug")]}),s.jsxs("a",{className:"flex items-center gap-1 rounded border border-outline-variant/30 px-2 py-1 text-xs text-on-surface-variant hover:bg-surface-container",href:"/",children:[s.jsx(sv,{className:"h-3.5 w-3.5"})," ",e("nav.home")]})]})]}),s.jsxs("div",{className:"flex min-h-0 flex-1",children:[s.jsxs("aside",{className:"w-64 shrink-0 border-r border-outline-variant/15 bg-surface-container-lowest p-3 text-xs",children:[s.jsx("div",{className:"mb-3 font-semibold text-on-surface-variant",children:e("sidebar.assets")}),s.jsxs("label",{className:"mb-3 flex cursor-pointer items-center gap-2 rounded bg-primary-container/30 px-2 py-2 text-on-primary-container",children:[s.jsx(uv,{className:"h-4 w-4"}),s.jsx("span",{children:e("sidebar.upload")}),s.jsx("input",{type:"file",accept:"image/*,video/*",className:"hidden",onChange:async u=>{var _;const v=(_=u.target.files)==null?void 0:_[0];if(v){d(!0),x(null);try{const H=await Oo(v);await Ct(),c(H.filename)}catch(H){x(String(H))}finally{d(!1),u.target.value=""}}}})]}),s.jsxs("button",{type:"button",className:"mb-2 flex w-full items-center gap-1 text-left text-on-surface-variant hover:text-on-surface",onClick:()=>{Ct().catch(u=>x(String(u))),Le(1),no()},children:[s.jsx(iv,{className:"h-3 w-3"})," ",e("sidebar.refresh")]}),s.jsx("div",{className:"max-h-36 overflow-y-auto border-t border-outline-variant/10 pt-2",children:_p.map(u=>s.jsxs("div",{className:`mb-1 flex items-center gap-0.5 rounded px-1 ${i===u.filename?"bg-surface-container":""}`,children:[s.jsxs("button",{type:"button",className:`min-w-0 flex-1 truncate rounded px-1 py-1 text-left ${i===u.filename?"text-primary":""}`,title:u.filename,onClick:()=>{c(u.filename),l(bn(u.filename)?"lab_video":"lab_image")},children:[s.jsx("span",{className:"block truncate",children:Ll(u.filename)}),s.jsx("span",{className:"block text-[9px] text-on-surface-variant/90",children:bn(u.filename)?e("sidebar.assetTypeVideo"):e("sidebar.assetTypeImage")})]}),s.jsx("button",{type:"button",className:"shrink-0 rounded p-1 text-on-surface-variant hover:bg-surface-container-high hover:text-error",title:e("pool.delete"),"aria-label":e("pool.delete"),onClick:v=>{v.stopPropagation(),eu(u.filename)},children:s.jsx(jc,{className:"h-3.5 w-3.5"})})]},u.filename))}),s.jsxs("div",{className:"mt-3 border-t border-outline-variant/10 pt-3",children:[s.jsxs("div",{className:"mb-2 flex items-start justify-between gap-2",children:[s.jsx("div",{className:"min-w-0 font-semibold leading-tight text-on-surface-variant",children:e("sidebar.debugCaptures")}),s.jsx("button",{type:"button",className:"shrink-0 rounded bg-primary px-2 py-1 text-[10px] font-medium text-on-primary disabled:opacity-40",disabled:!pe||Ki,title:pe??void 0,onClick:()=>void gp(),children:pe&&/\.avi$/i.test(pe)?e("sidebar.importToPoolWithTranscode"):e("sidebar.importToPoolDirect")})]}),Ka!=null&&s.jsxs("div",{className:"mb-2 rounded border border-outline-variant/25 bg-surface-container-low px-2 py-2",children:[s.jsxs("div",{className:"flex items-center justify-between gap-2 text-[10px]",children:[s.jsx("span",{className:"text-on-surface-variant",children:cp||e("sidebar.flowPreparing")}),s.jsxs("span",{className:"font-mono text-on-surface",children:[Math.round((Ka||0)*100),"%"]})]}),s.jsx("div",{className:"mt-1.5 h-1.5 w-full overflow-hidden rounded bg-surface-container-highest",children:s.jsx("div",{className:"h-full bg-primary transition-all",style:{width:`${Math.round((Ka||0)*100)}%`}})}),Ga&&s.jsx("div",{className:"mt-1 text-[10px] text-on-surface-variant",title:Ga,children:Ga})]}),jn.length===0?s.jsx("p",{className:"text-[10px] text-on-surface-variant",children:e("sidebar.debugEmpty")}):s.jsxs(s.Fragment,{children:[s.jsx("div",{className:"max-h-[min(22rem,55vh)] space-y-1.5 overflow-y-auto pr-0.5",children:Np.map(u=>s.jsxs("button",{type:"button",className:`flex w-full items-center gap-2 rounded-md border px-2 py-1.5 text-left transition-colors ${pe===u.name?"border-primary bg-primary-container/20":"border-outline-variant/25 hover:bg-surface-container"}`,onClick:()=>nt(u.name),children:[u.type==="image"?s.jsx("img",{src:gv(u.name),alt:"",className:"h-11 w-11 shrink-0 rounded object-cover"}):s.jsx("div",{className:"flex h-11 w-11 shrink-0 items-center justify-center rounded bg-surface-container-high text-[9px] text-on-surface-variant",children:"video"}),s.jsxs("div",{className:"min-w-0 flex-1",children:[s.jsx("div",{className:"truncate font-mono text-[10px] text-on-surface",title:u.name,children:Ll(u.name)}),s.jsxs("div",{className:"text-[9px] text-on-surface-variant",children:[u.type==="video"||bn(u.name)?e("sidebar.assetTypeVideo"):e("sidebar.assetTypeImage")," ","· ",Wl(u.modified,t)," · ",Fs(u.size)]})]})]},u.name))}),s.jsxs("div",{className:"mt-2 flex items-center justify-between gap-1 text-[10px] text-on-surface-variant",children:[s.jsx("button",{type:"button",className:"rounded border border-outline-variant/30 px-2 py-0.5 disabled:opacity-40",disabled:dt<=1,onClick:()=>Le(u=>Math.max(1,u-1)),children:e("history.prev")}),s.jsx("span",{className:"tabular-nums",children:e("sidebar.debugPage",{cur:dt,total:sr})}),s.jsx("button",{type:"button",className:"rounded border border-outline-variant/30 px-2 py-0.5 disabled:opacity-40",disabled:dt>=sr,onClick:()=>Le(u=>Math.min(sr,u+1)),children:e("history.next")})]})]})]})]}),(r==="lab_image"||r==="lab_video")&&s.jsxs("div",{className:"flex min-h-0 min-w-0 flex-1",children:[s.jsxs("main",{className:"min-h-0 min-w-0 flex-1 overflow-y-auto p-4",children:[m&&s.jsx("div",{className:"mb-2 rounded border border-error/40 bg-error-container/20 px-3 py-2 text-xs text-error",children:m}),Hi&&s.jsx("div",{className:"mb-2 rounded border border-outline-variant/35 bg-surface-container px-3 py-2 text-xs text-on-surface-variant",children:Hi}),r==="lab_video"&&s.jsxs("div",{className:"mb-3 max-w-5xl rounded-lg border border-outline-variant/25 bg-surface-container-low/80 p-3 text-[11px] leading-relaxed text-on-surface",children:[s.jsx("p",{className:"text-[10px] text-on-surface-variant",children:e("lab.videoLiveIntro")}),s.jsxs("div",{className:"mt-2 flex flex-wrap gap-2",children:[s.jsx("button",{type:"button",className:`rounded px-3 py-1.5 text-[11px] font-medium ${te==="file"?"bg-primary text-on-primary-container":"border border-outline-variant/40 bg-surface-container text-on-surface"}`,onClick:()=>tl("file"),children:e("lab.previewModeFile")}),s.jsx("button",{type:"button",className:`rounded px-3 py-1.5 text-[11px] font-medium ${te==="camera"?"bg-primary text-on-primary-container":"border border-outline-variant/40 bg-surface-container text-on-surface"}`,onClick:()=>tl("camera"),children:e("lab.previewModeCamera")})]})]}),s.jsxs("div",{className:"relative aspect-video w-full max-w-5xl overflow-hidden rounded-lg border border-outline-variant/20 bg-surface-container-lowest",children:[r==="lab_video"&&te==="camera"?s.jsx("div",{className:"relative flex h-full min-h-[220px] flex-col items-center justify-center gap-3 bg-black p-2",children:s.jsxs("div",{className:"relative inline-block max-h-[70vh] max-w-full",children:[pt&&Ui||nl?s.jsx("img",{ref:Yi,src:pt&&Ui||nl||"",alt:"",className:"max-h-[70vh] w-full min-h-[120px] object-contain",onLoad:u=>lp({w:u.currentTarget.naturalWidth,h:u.currentTarget.naturalHeight})}):s.jsxs("div",{className:"flex min-h-[200px] w-full min-w-[280px] flex-col items-center justify-center gap-2 text-[11px] text-on-surface-variant",children:[s.jsx(Io,{className:"h-8 w-8 animate-spin text-primary"}),s.jsx("span",{children:e("lab.cameraPreviewLoading")})]}),s.jsx("canvas",{ref:_n,className:"pointer-events-none absolute left-0 top-0"})]})}):i?s.jsx("div",{className:"relative h-full min-h-[200px] overflow-auto",children:r==="lab_video"?s.jsxs(s.Fragment,{children:[s.jsxs("div",{className:"absolute left-2 top-2 z-20 flex flex-wrap gap-1",children:[s.jsxs("button",{type:"button",title:e("lab.grid"),className:`rounded border px-2 py-1 text-[10px] ${ft?"border-primary bg-primary/20":"border-white/30 bg-black/40"} text-white`,onClick:()=>Li(u=>!u),children:[s.jsx(Nc,{className:"mr-1 inline h-3 w-3"}),e("lab.grid")]}),s.jsx("button",{type:"button",title:e("lab.zoomOut"),className:"rounded border border-white/30 bg-black/40 px-2 py-1 text-[10px] text-white",onClick:()=>pl(Sn-.25),children:s.jsx(bc,{className:"inline h-3 w-3"})}),s.jsx("button",{type:"button",title:e("lab.zoomIn"),className:"rounded border border-white/30 bg-black/40 px-2 py-1 text-[10px] text-white",onClick:()=>pl(Sn+.25),children:s.jsx(Ec,{className:"inline h-3 w-3"})}),s.jsx("button",{type:"button",title:e("lab.zoomReset"),className:"rounded border border-white/30 bg-black/40 px-2 py-1 text-[10px] text-white",onClick:()=>Ia(1),children:s.jsx(_c,{className:"inline h-3 w-3"})})]}),s.jsxs("div",{className:"flex min-h-[200px] flex-col items-center justify-center gap-2 bg-black py-2",children:[s.jsx("div",{className:"inline-block origin-top-left transition-transform",style:{transform:`scale(${Sn})`},children:s.jsxs("div",{className:"relative inline-block",children:[ft&&s.jsx("div",{className:"pointer-events-none absolute inset-0 z-[1]",style:{backgroundImage:["linear-gradient(to right, rgba(255,255,255,0.12) 1px, transparent 1px)","linear-gradient(to bottom, rgba(255,255,255,0.12) 1px, transparent 1px)"].join(","),backgroundSize:"48px 48px"}}),s.jsx("video",{ref:ar,src:cl,loop:!0,playsInline:!0,autoPlay:!0,muted:!0,preload:"metadata",className:"max-h-[70vh] w-full max-w-full object-contain",onError:()=>Oa(e("lab.videoPreviewFailed")),onLoadedData:()=>{Oa(null);const u=ar.current;u&&u.play().catch(()=>{})}}),s.jsx("canvas",{ref:_n,className:"pointer-events-none absolute left-0 top-0"})]})}),rr&&s.jsx("div",{className:"rounded border border-error/40 bg-error-container/20 px-3 py-1.5 text-[11px] text-error",children:rr}),qi&&s.jsxs("div",{className:"max-w-3xl rounded border border-primary/40 bg-primary/10 px-3 py-2 text-[11px] text-on-surface",children:[s.jsx("div",{className:"font-semibold text-primary",children:e("lab.transcode.title")}),s.jsx("div",{className:"mt-1 text-on-surface-variant",children:e("lab.transcode.desc")}),Qi!=null&&s.jsxs("div",{className:"mt-2",children:[s.jsx("div",{className:"mb-1 text-[10px] text-on-surface-variant",children:up||e("lab.transcode.running")}),s.jsx("div",{className:"h-1.5 w-full overflow-hidden rounded bg-surface-container-highest",children:s.jsx("div",{className:"h-full bg-primary transition-all",style:{width:`${Math.round(Qi*100)}%`}})})]}),s.jsx("div",{className:"mt-2",children:s.jsx("button",{type:"button",className:"rounded bg-primary px-3 py-1.5 text-[11px] font-medium text-on-primary disabled:opacity-50",disabled:Vi,onClick:()=>void vp(),children:e("lab.transcode.button")})})]})]})]}):s.jsxs(s.Fragment,{children:[s.jsxs("div",{className:"absolute left-2 top-2 z-20 flex flex-wrap gap-1",children:[s.jsxs("button",{type:"button",title:e("lab.grid"),className:`rounded border px-2 py-1 text-[10px] ${ft?"border-primary bg-primary/20":"border-white/30 bg-black/40"} text-white`,onClick:()=>Li(u=>!u),children:[s.jsx(Nc,{className:"mr-1 inline h-3 w-3"}),e("lab.grid")]}),s.jsx("button",{type:"button",title:e("lab.zoomOut"),className:"rounded border border-white/30 bg-black/40 px-2 py-1 text-[10px] text-white",onClick:()=>pl(Sn-.25),children:s.jsx(bc,{className:"inline h-3 w-3"})}),s.jsx("button",{type:"button",title:e("lab.zoomIn"),className:"rounded border border-white/30 bg-black/40 px-2 py-1 text-[10px] text-white",onClick:()=>pl(Sn+.25),children:s.jsx(Ec,{className:"inline h-3 w-3"})}),s.jsx("button",{type:"button",title:e("lab.zoomReset"),className:"rounded border border-white/30 bg-black/40 px-2 py-1 text-[10px] text-white",onClick:()=>Ia(1),children:s.jsx(_c,{className:"inline h-3 w-3"})})]}),s.jsx("div",{className:"inline-block origin-top-left transition-transform",style:{transform:`scale(${Sn})`},children:s.jsxs("div",{className:"relative inline-block",children:[ft&&s.jsx("div",{className:"pointer-events-none absolute inset-0 z-[1]",style:{backgroundImage:["linear-gradient(to right, rgba(255,255,255,0.12) 1px, transparent 1px)","linear-gradient(to bottom, rgba(255,255,255,0.12) 1px, transparent 1px)"].join(","),backgroundSize:"48px 48px"}}),s.jsx("img",{ref:Xa,src:cl,alt:"preview",className:"max-h-[70vh] w-full object-contain"}),s.jsx("canvas",{ref:_n,className:"pointer-events-none absolute left-0 top-0"})]})})]})}):s.jsx("div",{className:"flex h-full items-center justify-center px-4 text-center text-on-surface-variant",children:e(r==="lab_video"?"lab.selectOrUploadVideo":"lab.selectOrUpload")}),p&&s.jsx("div",{className:"absolute inset-0 flex items-center justify-center bg-black/40",children:s.jsx(Io,{className:"h-8 w-8 animate-spin text-primary"})})]}),(i&&r==="lab_image"||r==="lab_video"&&(te==="file"&&i||te==="camera"))&&s.jsxs("div",{className:"mt-2 flex flex-wrap items-center gap-3 rounded-lg border border-outline-variant/25 bg-surface-container-lowest/90 px-3 py-2 text-xs text-on-surface",children:[s.jsx("span",{className:"shrink-0 font-medium text-on-surface-variant",children:e("lab.layers")}),s.jsx("div",{className:"flex flex-wrap gap-x-4 gap-y-1",children:["matched","pattern","all"].map(u=>s.jsxs("label",{className:"flex cursor-pointer items-center gap-1",children:[s.jsx("input",{type:"checkbox",className:"accent-primary",checked:L[u],onChange:v=>T(_=>({..._,[u]:v.target.checked}))}),s.jsx("span",{children:e(u==="matched"?"lab.layer.matched":u==="pattern"?"lab.layer.pattern":"lab.layer.all")})]},u))})]}),(i||r==="lab_video"&&te==="camera")&&s.jsxs(s.Fragment,{children:[s.jsxs("div",{className:"mt-2 grid gap-2 sm:grid-cols-2",children:[s.jsxs("details",{open:!0,className:"rounded-lg border border-outline-variant/25 bg-surface-container-lowest/90",children:[s.jsxs("summary",{className:"flex cursor-pointer list-none items-center justify-between gap-2 px-3 py-2 text-xs font-semibold text-on-surface [&::-webkit-details-marker]:hidden",children:[e("lab.solveSection"),s.jsx(yr,{className:"h-4 w-4 shrink-0 text-on-surface-variant"})]}),s.jsx("div",{className:"border-t border-outline-variant/15 px-3 py-2 text-[10px]",children:Pt?s.jsxs("div",{className:"space-y-1 text-on-surface",children:[D.tSolveMs!=null&&s.jsxs("div",{className:"space-y-0.5",children:[s.jsxs("div",{className:"flex justify-between gap-2",children:[s.jsx("span",{className:"text-on-surface-variant",children:e("lab.metric.solveComputeMs")}),s.jsxs("span",{className:"font-mono tabular-nums",children:[D.tSolveMs.toFixed(0)," ms"]})]}),s.jsx("p",{className:"text-[8px] leading-snug text-on-surface-variant/90",children:e("lab.metric.solveComputeHelp")})]}),Va!=null&&s.jsxs("div",{className:"space-y-0.5 border-t border-outline-variant/10 pt-1",children:[s.jsxs("div",{className:"flex justify-between gap-2",children:[s.jsx("span",{className:"text-on-surface-variant",children:e("lab.metric.solveRoundTripMs")}),s.jsxs("span",{className:"font-mono tabular-nums",children:[Va.toFixed(0)," ms"]})]}),s.jsx("p",{className:"text-[8px] leading-snug text-on-surface-variant/90",children:e("lab.metric.solveRoundTripHelp")})]}),(D.tBackendTotalMs!=null||D.tOpenDecodeMs!=null||D.tPreprocessMs!=null||D.tExtractMs!=null||D.tSolveMs!=null)&&s.jsxs("div",{className:"space-y-0.5 border-t border-outline-variant/10 pt-1",children:[s.jsxs("div",{className:"flex justify-between gap-2",children:[s.jsx("span",{className:"text-on-surface-variant",children:e("lab.metric.backendTotalMs")}),s.jsx("span",{className:"font-mono tabular-nums",children:D.tBackendTotalMs!=null?`${D.tBackendTotalMs.toFixed(0)} ms`:e("common.placeholder")})]}),s.jsxs("div",{className:"flex flex-wrap gap-x-2 gap-y-0.5 text-[8px] text-on-surface-variant/90",children:[s.jsxs("span",{children:[e("lab.metric.openDecodeMs"),":"," ",D.tOpenDecodeMs!=null?`${D.tOpenDecodeMs.toFixed(0)} ms`:e("common.placeholder")]}),s.jsxs("span",{children:[e("lab.metric.preprocessMs"),":"," ",D.tPreprocessMs!=null?`${D.tPreprocessMs.toFixed(0)} ms`:e("common.placeholder")]}),s.jsxs("span",{children:[e("lab.metric.extractMs"),":"," ",D.tExtractMs!=null?`${D.tExtractMs.toFixed(0)} ms`:e("common.placeholder")]}),s.jsxs("span",{children:[e("lab.metric.solveOnlyMs"),":"," ",D.tSolveMs!=null?`${D.tSolveMs.toFixed(0)} ms`:e("common.placeholder")]})]})]}),(D.raDeg!=null||D.decDeg!=null)&&s.jsxs("div",{className:"leading-tight",children:[s.jsx("span",{className:"text-on-surface-variant",children:e("lab.metric.radec")}),s.jsxs("div",{className:"mt-0.5 font-mono text-[9px]",children:["α ",va(D.raDeg)," · δ"," ",va(D.decDeg)]})]}),s.jsxs("div",{className:"flex flex-wrap gap-x-2 gap-y-0.5 text-[9px]",children:[D.matches!=null&&s.jsxs("span",{children:[s.jsx("span",{className:"text-on-surface-variant",children:e("lab.metric.matches")})," ",s.jsx("span",{className:"font-mono",children:D.matches})]}),D.rmseArcsec!=null&&s.jsxs("span",{children:[s.jsx("span",{className:"text-on-surface-variant",children:e("lab.metric.rmse")})," ",s.jsxs("span",{className:"font-mono",children:[D.rmseArcsec.toFixed(2),"″"]})]}),D.prob!=null&&s.jsxs("span",{className:"inline-flex max-w-full flex-col gap-0.5",children:[s.jsxs("span",{children:[s.jsx("span",{className:"text-on-surface-variant",children:e("lab.metric.prob")})," ",s.jsx("span",{className:"font-mono",children:Qn(D.prob,Pt??void 0).line})]}),s.jsx("span",{className:"text-[8px] leading-snug text-on-surface-variant/90",children:e("lab.metric.probHelp")}),Qn(D.prob,Pt??void 0).rawLine&&s.jsxs(s.Fragment,{children:[s.jsxs("span",{className:"text-[8px] text-on-surface-variant",children:[e("lab.metric.probRaw"),":"," ",Qn(D.prob,Pt??void 0).rawLine]}),s.jsx("span",{className:"text-[8px] leading-snug text-on-surface-variant/90",children:e("lab.metric.probRawHelp")})]})]})]}),D.status&&s.jsxs("div",{className:"border-t border-outline-variant/15 pt-1 text-[9px]",children:[s.jsxs("span",{className:"text-on-surface-variant",children:[e("lab.metric.status")," "]}),s.jsx("span",{className:"font-mono text-secondary",children:D.status})]})]}):s.jsx("p",{className:"text-on-surface-variant",children:e("common.placeholder")})})]}),s.jsxs("details",{open:!0,className:"rounded-lg border border-outline-variant/25 bg-surface-container-lowest/90",children:[s.jsxs("summary",{className:"flex cursor-pointer list-none items-center justify-between gap-2 px-3 py-2 text-xs font-semibold text-on-surface [&::-webkit-details-marker]:hidden",children:[e("lab.imageSection"),s.jsx(yr,{className:"h-4 w-4 shrink-0 text-on-surface-variant"})]}),s.jsxs("div",{className:"space-y-0.5 border-t border-outline-variant/15 px-3 py-2 text-[10px]",children:[s.jsxs("div",{className:"flex justify-between gap-2",children:[s.jsx("span",{className:"text-on-surface-variant",children:e("lab.resolution")}),s.jsx("span",{className:"font-mono tabular-nums",children:ro.w>0?`${ro.w}×${ro.h}`:e("common.placeholder")})]}),s.jsxs("div",{className:"flex justify-between gap-2",children:[s.jsx("span",{className:"text-on-surface-variant",children:e("lab.starsDetected")}),s.jsx("span",{className:"font-mono tabular-nums",children:Ji??e("common.placeholder")})]}),s.jsxs("div",{className:"flex justify-between gap-2",children:[s.jsx("span",{className:"text-on-surface-variant",children:e("lab.fwhm")}),s.jsx("span",{children:e("common.placeholder")})]})]})]})]}),i&&s.jsxs(s.Fragment,{children:[s.jsxs("div",{className:"mt-2 flex flex-wrap gap-4 text-xs text-on-surface-variant",children:[s.jsxs("span",{children:[e("lab.file"),":"," ",s.jsx("span",{className:"font-mono text-on-surface",title:i,children:Ll(i,22,18)})]}),((ru=a.find(u=>u.filename===i))==null?void 0:ru.source)&&s.jsxs("span",{className:"rounded bg-surface-container-high px-2 py-0.5",children:[e("lab.source"),":"," ",(lu=a.find(u=>u.filename===i))==null?void 0:lu.source]})]}),s.jsxs("details",{open:!0,className:"mt-2 rounded-lg border border-outline-variant/25 bg-surface-container-lowest/90 text-xs shadow-sm",children:[s.jsxs("summary",{className:"flex cursor-pointer list-none items-center gap-2 px-3 py-2 font-semibold text-on-surface [&::-webkit-details-marker]:hidden",children:[e("lab.meta.title"),$a&&s.jsx(Io,{className:"h-3.5 w-3.5 animate-spin"}),s.jsx(yr,{className:"ml-auto h-4 w-4 shrink-0 text-on-surface-variant"})]}),s.jsx("div",{className:"border-t border-outline-variant/15 p-3 pt-2",children:nu.length>0?s.jsx("dl",{className:"grid grid-cols-2 gap-x-4 gap-y-2 sm:grid-cols-3",children:nu.map(u=>s.jsxs("div",{children:[s.jsx("dt",{className:"text-[10px] text-on-surface-variant",children:e(u.key)}),s.jsx("dd",{className:"text-[11px] font-medium text-on-surface",children:u.value})]},`${u.key}-${u.value}`))}):Aa&&!$a?s.jsx("p",{className:"text-[10px] leading-relaxed text-on-surface-variant",children:e("lab.meta.partial")}):$a?null:s.jsx("p",{className:"text-[10px] text-on-surface-variant",children:e("lab.meta.noSidecar")})})]})]})]}),(i||r==="lab_video"&&te==="camera")&&(N||b)&&s.jsxs("section",{className:"mt-3 max-h-[min(50vh,28rem)] rounded-lg border border-outline-variant/25 bg-surface-container-lowest/95",children:[s.jsxs("div",{className:"flex h-9 shrink-0 items-center justify-between border-b border-outline-variant/15 px-3 text-[10px] uppercase text-on-surface-variant",children:[s.jsx("span",{children:e("results.title")}),s.jsxs("div",{className:"flex gap-3",children:[(au=b==null?void 0:b.results)!=null&&au.length?s.jsx("button",{type:"button",className:"rounded bg-surface-container px-2 py-0.5 normal-case",onClick:async()=>{if(!(!i||!b)){for(const u of b.results){if(!u.success)continue;const v=u.result;await Fo({input_name:i,preset_label:String(u.label),result_json:u,metrics:Ho(v??null),replay:{layers:L,params:f}})}x(null)}},children:e("results.saveBatchAll")}):null,N&&s.jsx("button",{type:"button",className:"rounded bg-surface-container px-2 py-0.5 normal-case",onClick:async()=>{if(!i&&ip!=="camera")return;const u=N.result;await Fo({input_name:i??e("lab.cameraSnapshotName"),preset_label:"manual",result_json:N,metrics:Ho(u??null),replay:{layers:L,params:f}}),x(null)},children:e("results.saveCurrent")})]})]}),s.jsx("div",{className:"min-h-0 overflow-y-auto p-3",children:s.jsxs("div",{className:"flex gap-3 overflow-x-auto text-xs",children:[b==null?void 0:b.results.map((u,v)=>{const _=u.result,H=sp[v]??!1;return s.jsxs("div",{className:"min-w-[15rem] max-w-sm shrink-0 rounded-lg border border-outline-variant/25 bg-surface-container p-3 shadow-sm",children:[s.jsx("div",{className:"border-b border-outline-variant/15 pb-2 font-semibold text-secondary",children:String(u.label)}),u.success?s.jsxs(s.Fragment,{children:[s.jsx(Fc,{result:_,t:e,roundTripMs:null}),s.jsx("button",{type:"button",className:"mt-2 text-[10px] text-primary hover:underline",onClick:()=>Ha(le=>({...le,[v]:!H})),children:e(H?"results.hideRaw":"results.viewRaw")}),H&&s.jsx("pre",{className:"mt-1 max-h-40 overflow-auto rounded bg-surface-container-highest p-2 text-[9px] text-on-surface-variant",children:JSON.stringify(u,null,2)})]}):s.jsx("div",{className:"mt-2 text-[10px] text-error",children:String(u.error)}),u.success&&i&&s.jsx("button",{type:"button",className:"mt-3 w-full rounded bg-surface-container-high py-1.5 text-[10px] font-medium",onClick:()=>Fo({input_name:i,preset_label:String(u.label),result_json:u,metrics:Ho(_??null),replay:{layers:L,params:f}}).catch(le=>x(String(le))),children:e("results.saveRow")})]},v)}),N&&!b&&s.jsxs("div",{className:"min-w-[16rem] max-w-md shrink-0 rounded-lg border border-outline-variant/25 bg-surface-container p-3 shadow-sm",children:[s.jsx("div",{className:"border-b border-outline-variant/15 pb-2 text-[10px] font-semibold uppercase text-on-surface-variant",children:e("results.title")}),s.jsx(Fc,{result:N.result,t:e,roundTripMs:Va}),s.jsx("button",{type:"button",className:"mt-2 text-[10px] text-primary hover:underline",onClick:()=>Ba(u=>!u),children:e($i?"results.hideRaw":"results.viewRaw")}),$i&&s.jsx("pre",{className:"mt-1 max-h-48 overflow-auto rounded bg-surface-container-highest p-2 text-[9px] text-on-surface-variant",children:JSON.stringify(N,null,2)})]})]})})]})]}),s.jsxs("aside",{className:"flex w-80 shrink-0 flex-col border-l border-outline-variant/15 bg-surface-container-low text-xs min-h-0",children:[r!=="lab_video"&&s.jsxs("div",{className:"shrink-0 space-y-2 border-b border-outline-variant/20 p-4 pb-3",children:[s.jsx("button",{type:"button",className:"w-full rounded bg-primary py-2.5 font-semibold text-on-primary-container",onClick:pp,disabled:p,children:e("btn.solveOne")}),s.jsx("button",{type:"button",className:"w-full rounded bg-secondary/80 py-2.5 font-semibold text-on-secondary",onClick:mp,disabled:p,children:e("btn.solveBatch")})]}),r==="lab_video"&&s.jsx("div",{className:"shrink-0 space-y-2 border-b border-outline-variant/20 p-4 pb-3",children:te==="file"?s.jsxs(s.Fragment,{children:[s.jsx("button",{type:"button",className:"w-full rounded bg-primary py-2.5 font-semibold text-on-primary-container disabled:opacity-40",onClick:()=>hp(),disabled:p||!Zi||rl,children:e("lab.solveFileStart")}),s.jsx("button",{type:"button",className:"w-full rounded bg-error/80 py-2.5 font-semibold text-on-error disabled:opacity-40",onClick:()=>dl(),disabled:!rl,children:e("lab.solveFileStop")}),s.jsx("button",{type:"button",className:"w-full rounded bg-secondary/80 py-2.5 font-semibold text-on-secondary disabled:opacity-40",onClick:()=>void ao(),disabled:p||!Zi||rl,children:e("lab.solveCurrentFrame")})]}):s.jsxs(s.Fragment,{children:[s.jsx("button",{type:"button",className:"w-full rounded bg-primary py-2.5 font-semibold text-on-primary-container disabled:opacity-40",onClick:()=>xp(),disabled:p||za||pt,children:e("lab.solveCameraStart")}),s.jsx("button",{type:"button",className:"w-full rounded bg-error/80 py-2.5 font-semibold text-on-error disabled:opacity-40",onClick:()=>fl(),disabled:!za,children:e("lab.solveCameraStop")}),s.jsx("button",{type:"button",className:"w-full rounded bg-secondary/80 py-2.5 font-semibold text-on-secondary disabled:opacity-40",onClick:()=>void lo(),disabled:p||pt,children:e("lab.solveCameraFrame")}),s.jsxs("label",{className:"flex items-center gap-2 rounded border border-outline-variant/25 px-2 py-1.5 text-[11px]",children:[s.jsx("input",{type:"checkbox",checked:Di,onChange:u=>ap(u.target.checked)}),s.jsx("span",{children:"自动保持(解算成功后冻结) / Auto Hold"})]}),pt&&s.jsx("button",{type:"button",className:"w-full rounded bg-tertiary/80 py-2.5 font-semibold text-on-tertiary disabled:opacity-40",onClick:()=>wp(),children:"继续实时 / Resume Live"}),pt&&s.jsxs("div",{className:"text-[10px] text-on-surface-variant",children:["已冻结帧 / Frozen frame ",op??"-"]})]})}),r==="lab_video"&&ul&&s.jsxs("div",{className:"shrink-0 border-b border-outline-variant/20 px-4 py-2 text-[10px] text-on-surface-variant",children:[s.jsx("div",{className:"font-medium text-on-surface",children:e("lab.systemLoad")}),s.jsxs("div",{children:["CPU ",ul.cpu_usage,"% · RAM ",ul.memory_usage,"% ·"," ",ul.temperature,"°C"]})]}),s.jsxs("div",{className:"min-h-0 flex-1 overflow-y-auto p-4",children:[s.jsxs("details",{open:!0,className:"rounded-lg border border-outline-variant/20 bg-surface-container-highest/30",children:[s.jsxs("summary",{className:"flex cursor-pointer list-none items-center justify-between gap-2 px-3 py-2 font-semibold text-on-surface-variant [&::-webkit-details-marker]:hidden",children:[e("params.title"),s.jsx(yr,{className:"h-4 w-4 shrink-0"})]}),s.jsxs("div",{className:"border-t border-outline-variant/15 p-3 pt-2",children:[s.jsx("p",{className:"mb-3 text-[10px] leading-relaxed text-on-surface-variant/90",children:e("params.blockSolveIntro")}),s.jsxs("div",{className:"space-y-2",children:[s.jsxs("label",{className:"block",children:[s.jsx("span",{className:"text-[10px] font-medium text-on-surface-variant",children:e("params.solveProfile")}),s.jsx("p",{className:"mb-1 mt-0.5 text-[9px] leading-snug text-on-surface-variant/85",children:e("params.solveProfileHelp")}),s.jsxs("select",{className:"w-full rounded bg-surface-container-highest px-2 py-1",value:f.solve_profile??"balanced",onChange:u=>g(v=>({...v,solve_profile:u.target.value})),children:[s.jsx("option",{value:"speed",children:e("params.solveProfileSpeed")}),s.jsx("option",{value:"balanced",children:e("params.solveProfileBalanced")}),s.jsx("option",{value:"robust",children:e("params.solveProfileRobust")})]})]}),s.jsx(Ge,{label:e("params.fov"),helpKey:"params.fovHelp",value:f.fov_estimate??"",onChange:u=>g(v=>({...v,fov_estimate:u}))}),s.jsx(Ge,{label:e("params.fovErr"),helpKey:"params.fovErrHelp",value:f.fov_max_error??"",onChange:u=>g(v=>({...v,fov_max_error:u}))}),s.jsx(Ge,{label:e("params.timeout"),helpKey:"params.timeoutHelp",value:f.solve_timeout_ms??"",onChange:u=>g(v=>({...v,solve_timeout_ms:u}))}),s.jsx(Ge,{label:e("params.solveIntervalMs"),helpKey:"params.solveIntervalMsHelp",value:Wa??"",onChange:u=>Bi(u==null?to:Math.round(u))}),s.jsx("p",{className:"text-[9px] leading-snug text-on-surface-variant/80",children:e("params.solveIntervalBound",{min:qa,max:eo,effective:or})}),s.jsx(Ge,{label:e("params.ra"),helpKey:"params.raHelp",value:f.hint_ra_deg??"",onChange:u=>g(v=>({...v,hint_ra_deg:u}))}),s.jsx(Ge,{label:e("params.dec"),helpKey:"params.decHelp",value:f.hint_dec_deg??"",onChange:u=>g(v=>({...v,hint_dec_deg:u}))}),s.jsx(Ge,{label:e("params.maxSide"),helpKey:"params.maxSideHelp",value:f.max_image_side??"",onChange:u=>g(v=>({...v,max_image_side:u}))}),s.jsxs("label",{className:"mt-1.5 flex cursor-pointer items-center gap-2",children:[s.jsx("input",{type:"checkbox",className:"accent-primary",checked:f.detail_level==="full",onChange:u=>g(v=>({...v,detail_level:u.target.checked?"full":"summary"}))}),s.jsx("span",{className:"text-[10px] text-on-surface-variant",children:e("params.detailLevelFull")})]}),s.jsxs("label",{className:"mt-1.5 flex cursor-pointer items-start gap-2",children:[s.jsx("input",{type:"checkbox",className:"accent-primary mt-0.5",checked:!!f.large_scale_bg_subtract,onChange:u=>g(v=>({...v,large_scale_bg_subtract:u.target.checked}))}),s.jsxs("span",{children:[s.jsx("span",{className:"text-[10px] text-on-surface-variant",children:e("params.largeScaleBg")}),s.jsx("p",{className:"mt-0.5 text-[9px] leading-snug text-on-surface-variant/85",children:e("params.largeScaleBgHelp")})]})]})]})]})]}),s.jsxs("details",{open:!0,className:"mt-3 rounded-lg border border-outline-variant/20 bg-surface-container-highest/30",children:[s.jsxs("summary",{className:"flex cursor-pointer list-none items-center justify-between gap-2 px-3 py-2 font-semibold text-on-surface-variant [&::-webkit-details-marker]:hidden",children:[e("params.centroid"),s.jsx(yr,{className:"h-4 w-4 shrink-0"})]}),s.jsxs("div",{className:"border-t border-outline-variant/15 p-3 pt-2",children:[s.jsx("p",{className:"mb-3 text-[10px] leading-relaxed text-on-surface-variant/90",children:e("params.blockCentroidIntro")}),s.jsxs("div",{className:"space-y-2",children:[s.jsx(Ge,{label:e("params.sigma"),helpKey:"params.sigmaHelp",step:.1,value:((ou=f.centroid)==null?void 0:ou.sigma)??"",onChange:u=>g(v=>({...v,centroid:{...v.centroid,sigma:u}}))}),s.jsx(Ge,{label:e("params.maxArea"),helpKey:"params.maxAreaHelp",value:((su=f.centroid)==null?void 0:su.max_area)??"",onChange:u=>g(v=>({...v,centroid:{...v.centroid,max_area:u}}))}),s.jsx(Ge,{label:e("params.minArea"),helpKey:"params.minAreaHelp",value:((iu=f.centroid)==null?void 0:iu.min_area)??"",onChange:u=>g(v=>({...v,centroid:{...v.centroid,min_area:u}}))}),s.jsx(Ge,{label:e("params.filtsize"),helpKey:"params.filtsizeHelp",step:2,value:((uu=f.centroid)==null?void 0:uu.filtsize)??"",onChange:u=>g(v=>({...v,centroid:{...v.centroid,filtsize:u}}))})]})]})]}),s.jsxs("div",{className:"mt-4 rounded-md border border-outline-variant/20 bg-surface-container-highest/40 p-3",children:[s.jsx("div",{className:"mb-2 font-semibold text-on-surface-variant",children:e("sidebar.batchPresets")}),s.jsx("p",{className:"mb-2 text-[10px] leading-snug text-on-surface-variant",children:e("sidebar.batchHint")}),s.jsx("div",{className:"max-h-36 space-y-1.5 overflow-y-auto",children:[...h,...k].map(u=>s.jsxs("label",{className:"flex cursor-pointer items-center gap-2",children:[s.jsx("input",{type:"checkbox",checked:E.has(u.id),onChange:()=>Sp(u.id)}),s.jsx("span",{className:"truncate",children:u.name})]},u.id))})]}),s.jsxs("div",{className:"mt-6 border-t border-outline-variant/20 pt-4",children:[s.jsx("div",{className:"mb-2 font-semibold",children:e("btn.applyPresets")}),s.jsx("div",{className:"max-h-24 space-y-1 overflow-y-auto",children:[...h,...k].map(u=>s.jsx("button",{type:"button",className:"block w-full truncate text-left text-primary hover:underline",onClick:()=>fp(u.params),children:u.name},u.id))}),s.jsxs("div",{className:"mt-3 flex gap-1",children:[s.jsx("input",{className:"flex-1 rounded bg-surface-container-highest px-2 py-1",placeholder:e("placeholder.newPreset"),value:qt,onChange:u=>C(u.target.value)}),s.jsx("button",{type:"button",className:"rounded bg-surface-container-high px-2",onClick:async()=>{if(qt.trim()){d(!0);try{await hv(qt.trim(),f),C(""),await Ct();const u=await zo("user");j(u.presets)}catch(u){x(String(u))}finally{d(!1)}}},children:e("btn.savePreset")})]})]})]})]})]}),r==="pool"&&s.jsxs("main",{className:"flex-1 overflow-auto p-4",children:[s.jsxs("h2",{className:"mb-4 flex items-center gap-2 text-lg font-semibold",children:[s.jsx(lv,{className:"h-5 w-5"})," ",e("pool.title")]}),s.jsxs("table",{className:"w-full text-left text-xs",children:[s.jsx("thead",{children:s.jsxs("tr",{className:"border-b border-outline-variant/30 text-on-surface-variant",children:[s.jsx("th",{className:"py-2",children:e("pool.col.name")}),s.jsx("th",{className:"py-2",children:e("pool.col.source")}),s.jsx("th",{className:"py-2",children:e("pool.col.size")}),s.jsx("th",{className:"py-2",children:e("pool.col.time")}),s.jsx("th",{className:"w-16 py-2 text-center",children:e("pool.delete")})]})}),s.jsx("tbody",{children:a.map(u=>s.jsxs("tr",{className:"border-b border-outline-variant/10",children:[s.jsx("td",{className:"py-2 font-mono",children:u.filename}),s.jsx("td",{className:"py-2",children:u.source??e("common.placeholder")}),s.jsx("td",{className:"py-2",children:Fs(u.size)}),s.jsx("td",{className:"py-2",children:Wl(u.modified_at,t)}),s.jsx("td",{className:"py-2 text-center",children:s.jsx("button",{type:"button",className:"inline-flex rounded p-1 text-on-surface-variant hover:bg-error-container/30 hover:text-error",title:e("pool.delete"),"aria-label":e("pool.delete"),onClick:()=>void eu(u.filename),children:s.jsx(jc,{className:"h-3.5 w-3.5"})})})]},u.filename))})]})]}),r==="history"&&s.jsxs("main",{className:"flex-1 overflow-auto p-4",children:[s.jsx("p",{className:"mb-4 rounded-lg border border-outline-variant/25 bg-surface-container-lowest/90 p-3 text-xs leading-relaxed text-on-surface-variant",children:e("history.intro")}),s.jsxs("div",{className:"mb-4 flex flex-wrap items-center gap-4",children:[s.jsxs("h2",{className:"flex items-center gap-2 text-lg font-semibold",children:[s.jsx(ov,{className:"h-5 w-5"})," ",e("history.title")]}),s.jsx("input",{className:"rounded bg-surface-container-highest px-2 py-1 text-xs",placeholder:e("history.search"),value:$,onChange:u=>O(u.target.value)}),s.jsx("button",{type:"button",className:"rounded bg-surface-container px-2 py-1 text-xs",onClick:()=>Do($,1,Ml).then(u=>{ct(1),xn(u)}),children:e("history.searchBtn")}),s.jsx("button",{type:"button",className:"rounded bg-primary-container/50 px-2 py-1 text-xs",onClick:()=>Rc("json").then(u=>{const v=new Blob([u],{type:"application/json"}),_=document.createElement("a");_.href=URL.createObjectURL(v),_.download="experiments.json",_.click()}),children:e("history.exportJson")}),s.jsx("button",{type:"button",className:"rounded bg-primary-container/50 px-2 py-1 text-xs",onClick:()=>Rc("csv").then(u=>{const v=new Blob([u],{type:"text/csv"}),_=document.createElement("a");_.href=URL.createObjectURL(v),_.download="experiments.csv",_.click()}),children:e("history.exportCsv")})]}),s.jsxs("div",{className:"flex flex-wrap items-center gap-3 text-xs text-on-surface-variant",children:[s.jsx("span",{children:e("history.total",{n:(xe==null?void 0:xe.total)??0})}),s.jsx("button",{type:"button",className:"rounded border border-outline-variant/30 px-2 py-0.5 disabled:opacity-40",disabled:se<=1,onClick:()=>ct(u=>Math.max(1,u-1)),children:e("history.prev")}),s.jsxs("span",{children:[se," / ",tu]}),s.jsx("button",{type:"button",className:"rounded border border-outline-variant/30 px-2 py-0.5 disabled:opacity-40",disabled:se>=tu,onClick:()=>ct(u=>u+1),children:e("history.next")})]}),s.jsx("ul",{className:"mt-2 space-y-2",children:(cu=xe==null?void 0:xe.items)==null?void 0:cu.map(u=>{const v=String(u.id??""),_=u.metrics,H=el===v;return s.jsxs("li",{className:"rounded border border-outline-variant/20 p-2 text-[11px]",children:[s.jsxs("div",{className:"flex flex-wrap items-start justify-between gap-2 font-mono",children:[s.jsxs("div",{children:[s.jsxs("div",{className:"text-on-surface",children:[Wl(String(u.created_at??""),t)," —"," ",String(u.input_name)," — ",String(u.preset_label)]}),s.jsxs("div",{className:"mt-1 text-on-surface-variant",children:[e("history.preset"),": ",String(u.preset_label)," · ",e("history.metrics"),":"," ","matches=",String((_==null?void 0:_.matches)??"—")," rmse=",String((_==null?void 0:_.rmse_arcsec)??"—")]})]}),s.jsxs("div",{className:"flex shrink-0 gap-1",children:[s.jsx("button",{type:"button",className:"rounded bg-surface-container px-2 py-0.5 text-[10px]",onClick:()=>wn(H?null:v),children:e(H?"history.collapse":"history.detail")}),s.jsx("button",{type:"button",className:"rounded px-2 py-0.5 text-[10px] text-error hover:bg-error-container/20",title:e("history.delete"),onClick:()=>void kp(v),children:e("history.delete")})]})]}),H&&s.jsxs("div",{className:"mt-2 space-y-2",children:[u.asset_snapshot_relpath?s.jsx("img",{src:_v(v),alt:"",className:"max-h-48 max-w-full rounded border border-outline-variant/20 object-contain"}):null,s.jsx("pre",{className:"max-h-64 overflow-auto rounded bg-surface-container p-2 text-[10px]",children:JSON.stringify(u.result_json,null,2)})]})]},v)})})]})]})]})}function Fc({result:e,t,roundTripMs:n}){const r=tp(e??void 0);return e?s.jsxs("div",{className:"mt-2 space-y-2 text-[10px]",children:[s.jsxs("div",{className:"grid grid-cols-2 gap-2",children:[r.tBackendTotalMs!=null&&s.jsxs("div",{children:[s.jsx("div",{className:"text-on-surface-variant",children:t("lab.metric.backendTotalMs")}),s.jsxs("div",{className:"font-semibold tabular-nums text-on-surface",children:[r.tBackendTotalMs.toFixed(0)," ms"]})]}),r.tSolveMs!=null&&s.jsxs("div",{children:[s.jsx("div",{className:"text-on-surface-variant",children:t("lab.metric.solveComputeMs")}),s.jsxs("div",{className:"font-semibold tabular-nums text-on-surface",children:[r.tSolveMs.toFixed(0)," ms"]})]}),r.tOpenDecodeMs!=null&&s.jsxs("div",{children:[s.jsx("div",{className:"text-on-surface-variant",children:t("lab.metric.openDecodeMs")}),s.jsxs("div",{className:"font-semibold tabular-nums text-on-surface",children:[r.tOpenDecodeMs.toFixed(0)," ms"]})]}),r.tPreprocessMs!=null&&s.jsxs("div",{children:[s.jsx("div",{className:"text-on-surface-variant",children:t("lab.metric.preprocessMs")}),s.jsxs("div",{className:"font-semibold tabular-nums text-on-surface",children:[r.tPreprocessMs.toFixed(0)," ms"]})]}),r.tExtractMs!=null&&s.jsxs("div",{children:[s.jsx("div",{className:"text-on-surface-variant",children:t("lab.metric.extractMs")}),s.jsxs("div",{className:"font-semibold tabular-nums text-on-surface",children:[r.tExtractMs.toFixed(0)," ms"]})]}),n!=null&&s.jsxs("div",{children:[s.jsx("div",{className:"text-on-surface-variant",children:t("lab.metric.solveRoundTripMs")}),s.jsxs("div",{className:"font-semibold tabular-nums text-on-surface",children:[n.toFixed(0)," ms"]})]}),r.matches!=null&&s.jsxs("div",{children:[s.jsx("div",{className:"text-on-surface-variant",children:t("lab.metric.matches")}),s.jsx("div",{className:"font-semibold tabular-nums text-on-surface",children:r.matches})]}),r.rmseArcsec!=null&&s.jsxs("div",{children:[s.jsx("div",{className:"text-on-surface-variant",children:t("lab.metric.rmse")}),s.jsxs("div",{className:"font-semibold tabular-nums text-on-surface",children:[r.rmseArcsec.toFixed(2),"″"]})]}),r.prob!=null&&s.jsxs("div",{className:"col-span-2",children:[s.jsx("div",{className:"text-on-surface-variant",children:t("lab.metric.prob")}),s.jsx("div",{className:"font-semibold text-on-surface",children:Qn(r.prob,e).line}),s.jsx("p",{className:"mt-0.5 text-[9px] leading-snug text-on-surface-variant/90",children:t("lab.metric.probHelp")}),Qn(r.prob,e).rawLine&&s.jsxs("div",{className:"mt-1 text-[9px] text-on-surface-variant",children:[t("lab.metric.probRaw"),": ",Qn(r.prob,e).rawLine,s.jsx("p",{className:"mt-0.5 text-[8px] leading-snug opacity-90",children:t("lab.metric.probRawHelp")})]})]})]}),s.jsxs("div",{children:[s.jsx("div",{className:"text-on-surface-variant",children:t("lab.metric.radec")}),s.jsxs("div",{className:"font-mono text-[9px] text-on-surface",children:["α ",va(r.raDeg)," · δ ",va(r.decDeg)]})]}),r.status&&s.jsxs("div",{className:"rounded bg-surface-container-high px-2 py-1 text-[9px] font-mono text-on-surface",children:[t("lab.metric.status"),": ",r.status]})]}):s.jsx("p",{className:"mt-2 text-[10px] text-on-surface-variant",children:"—"})}function Ge({label:e,helpKey:t,value:n,onChange:r,type:l="number",step:a}){const{t:o}=ep(),i=t?o(t):void 0;return s.jsxs("label",{className:"block",children:[s.jsx("span",{className:"text-[10px] font-medium text-on-surface-variant",children:e}),i&&s.jsx("p",{className:"mb-1 mt-0.5 text-[9px] leading-snug text-on-surface-variant/85",children:i}),s.jsx("input",{type:l,step:a,className:"w-full rounded bg-surface-container-highest px-2 py-1",value:n===""?"":n,onChange:c=>{const f=c.target.value;r(f===""?void 0:Number(f))}})]})}Vo.createRoot(document.getElementById("root")).render(s.jsx(Bp.StrictMode,{children:s.jsx($v,{children:s.jsx(Kv,{})})})); diff --git a/web/static/analysis-lab/assets/index-CiLgcict.css b/web/static/analysis-lab/assets/index-CiLgcict.css new file mode 100644 index 0000000..516dcd8 --- /dev/null +++ b/web/static/analysis-lab/assets/index-CiLgcict.css @@ -0,0 +1 @@ +*,:before,:after{--tw-border-spacing-x: 0;--tw-border-spacing-y: 0;--tw-translate-x: 0;--tw-translate-y: 0;--tw-rotate: 0;--tw-skew-x: 0;--tw-skew-y: 0;--tw-scale-x: 1;--tw-scale-y: 1;--tw-pan-x: ;--tw-pan-y: ;--tw-pinch-zoom: ;--tw-scroll-snap-strictness: proximity;--tw-gradient-from-position: ;--tw-gradient-via-position: ;--tw-gradient-to-position: ;--tw-ordinal: ;--tw-slashed-zero: ;--tw-numeric-figure: ;--tw-numeric-spacing: ;--tw-numeric-fraction: ;--tw-ring-inset: ;--tw-ring-offset-width: 0px;--tw-ring-offset-color: #fff;--tw-ring-color: rgb(59 130 246 / .5);--tw-ring-offset-shadow: 0 0 #0000;--tw-ring-shadow: 0 0 #0000;--tw-shadow: 0 0 #0000;--tw-shadow-colored: 0 0 #0000;--tw-blur: ;--tw-brightness: ;--tw-contrast: ;--tw-grayscale: ;--tw-hue-rotate: ;--tw-invert: ;--tw-saturate: ;--tw-sepia: ;--tw-drop-shadow: ;--tw-backdrop-blur: ;--tw-backdrop-brightness: ;--tw-backdrop-contrast: ;--tw-backdrop-grayscale: ;--tw-backdrop-hue-rotate: ;--tw-backdrop-invert: ;--tw-backdrop-opacity: ;--tw-backdrop-saturate: ;--tw-backdrop-sepia: ;--tw-contain-size: ;--tw-contain-layout: ;--tw-contain-paint: ;--tw-contain-style: }::backdrop{--tw-border-spacing-x: 0;--tw-border-spacing-y: 0;--tw-translate-x: 0;--tw-translate-y: 0;--tw-rotate: 0;--tw-skew-x: 0;--tw-skew-y: 0;--tw-scale-x: 1;--tw-scale-y: 1;--tw-pan-x: ;--tw-pan-y: ;--tw-pinch-zoom: ;--tw-scroll-snap-strictness: proximity;--tw-gradient-from-position: ;--tw-gradient-via-position: ;--tw-gradient-to-position: ;--tw-ordinal: ;--tw-slashed-zero: ;--tw-numeric-figure: ;--tw-numeric-spacing: ;--tw-numeric-fraction: ;--tw-ring-inset: ;--tw-ring-offset-width: 0px;--tw-ring-offset-color: #fff;--tw-ring-color: rgb(59 130 246 / .5);--tw-ring-offset-shadow: 0 0 #0000;--tw-ring-shadow: 0 0 #0000;--tw-shadow: 0 0 #0000;--tw-shadow-colored: 0 0 #0000;--tw-blur: ;--tw-brightness: ;--tw-contrast: ;--tw-grayscale: ;--tw-hue-rotate: ;--tw-invert: ;--tw-saturate: ;--tw-sepia: ;--tw-drop-shadow: ;--tw-backdrop-blur: ;--tw-backdrop-brightness: ;--tw-backdrop-contrast: ;--tw-backdrop-grayscale: ;--tw-backdrop-hue-rotate: ;--tw-backdrop-invert: ;--tw-backdrop-opacity: ;--tw-backdrop-saturate: ;--tw-backdrop-sepia: ;--tw-contain-size: ;--tw-contain-layout: ;--tw-contain-paint: ;--tw-contain-style: }*,:before,:after{box-sizing:border-box;border-width:0;border-style:solid;border-color:#e5e7eb}:before,:after{--tw-content: ""}html,:host{line-height:1.5;-webkit-text-size-adjust:100%;-moz-tab-size:4;-o-tab-size:4;tab-size:4;font-family:ui-sans-serif,system-ui,sans-serif,"Apple Color Emoji","Segoe UI Emoji",Segoe UI Symbol,"Noto Color Emoji";font-feature-settings:normal;font-variation-settings:normal;-webkit-tap-highlight-color:transparent}body{margin:0;line-height:inherit}hr{height:0;color:inherit;border-top-width:1px}abbr:where([title]){-webkit-text-decoration:underline dotted;text-decoration:underline dotted}h1,h2,h3,h4,h5,h6{font-size:inherit;font-weight:inherit}a{color:inherit;text-decoration:inherit}b,strong{font-weight:bolder}code,kbd,samp,pre{font-family:ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,Liberation Mono,Courier New,monospace;font-feature-settings:normal;font-variation-settings:normal;font-size:1em}small{font-size:80%}sub,sup{font-size:75%;line-height:0;position:relative;vertical-align:baseline}sub{bottom:-.25em}sup{top:-.5em}table{text-indent:0;border-color:inherit;border-collapse:collapse}button,input,optgroup,select,textarea{font-family:inherit;font-feature-settings:inherit;font-variation-settings:inherit;font-size:100%;font-weight:inherit;line-height:inherit;letter-spacing:inherit;color:inherit;margin:0;padding:0}button,select{text-transform:none}button,input:where([type=button]),input:where([type=reset]),input:where([type=submit]){-webkit-appearance:button;background-color:transparent;background-image:none}:-moz-focusring{outline:auto}:-moz-ui-invalid{box-shadow:none}progress{vertical-align:baseline}::-webkit-inner-spin-button,::-webkit-outer-spin-button{height:auto}[type=search]{-webkit-appearance:textfield;outline-offset:-2px}::-webkit-search-decoration{-webkit-appearance:none}::-webkit-file-upload-button{-webkit-appearance:button;font:inherit}summary{display:list-item}blockquote,dl,dd,h1,h2,h3,h4,h5,h6,hr,figure,p,pre{margin:0}fieldset{margin:0;padding:0}legend{padding:0}ol,ul,menu{list-style:none;margin:0;padding:0}dialog{padding:0}textarea{resize:vertical}input::-moz-placeholder,textarea::-moz-placeholder{opacity:1;color:#9ca3af}input::placeholder,textarea::placeholder{opacity:1;color:#9ca3af}button,[role=button]{cursor:pointer}:disabled{cursor:default}img,svg,video,canvas,audio,iframe,embed,object{display:block;vertical-align:middle}img,video{max-width:100%;height:auto}[hidden]:where(:not([hidden=until-found])){display:none}.container{width:100%}@media (min-width: 640px){.container{max-width:640px}}@media (min-width: 768px){.container{max-width:768px}}@media (min-width: 1024px){.container{max-width:1024px}}@media (min-width: 1280px){.container{max-width:1280px}}@media (min-width: 1536px){.container{max-width:1536px}}.pointer-events-none{pointer-events:none}.collapse{visibility:collapse}.static{position:static}.absolute{position:absolute}.relative{position:relative}.inset-0{top:0;right:0;bottom:0;left:0}.left-0{left:0}.left-2{left:.5rem}.top-0{top:0}.top-2{top:.5rem}.z-20{z-index:20}.z-\[1\]{z-index:1}.col-span-2{grid-column:span 2 / span 2}.mb-1{margin-bottom:.25rem}.mb-2{margin-bottom:.5rem}.mb-3{margin-bottom:.75rem}.mb-4{margin-bottom:1rem}.ml-auto{margin-left:auto}.mr-1{margin-right:.25rem}.mr-2{margin-right:.5rem}.mt-0\.5{margin-top:.125rem}.mt-1{margin-top:.25rem}.mt-1\.5{margin-top:.375rem}.mt-2{margin-top:.5rem}.mt-3{margin-top:.75rem}.mt-4{margin-top:1rem}.mt-6{margin-top:1.5rem}.block{display:block}.inline-block{display:inline-block}.inline{display:inline}.flex{display:flex}.inline-flex{display:inline-flex}.table{display:table}.grid{display:grid}.hidden{display:none}.aspect-video{aspect-ratio:16 / 9}.h-1\.5{height:.375rem}.h-11{height:2.75rem}.h-12{height:3rem}.h-3{height:.75rem}.h-3\.5{height:.875rem}.h-4{height:1rem}.h-5{height:1.25rem}.h-8{height:2rem}.h-9{height:2.25rem}.h-full{height:100%}.max-h-24{max-height:6rem}.max-h-36{max-height:9rem}.max-h-40{max-height:10rem}.max-h-48{max-height:12rem}.max-h-64{max-height:16rem}.max-h-\[70vh\]{max-height:70vh}.max-h-\[min\(22rem\,55vh\)\]{max-height:min(22rem,55vh)}.max-h-\[min\(50vh\,28rem\)\]{max-height:min(50vh,28rem)}.min-h-0{min-height:0px}.min-h-\[120px\]{min-height:120px}.min-h-\[200px\]{min-height:200px}.min-h-\[220px\]{min-height:220px}.min-h-full{min-height:100%}.w-11{width:2.75rem}.w-16{width:4rem}.w-3{width:.75rem}.w-3\.5{width:.875rem}.w-4{width:1rem}.w-5{width:1.25rem}.w-64{width:16rem}.w-8{width:2rem}.w-80{width:20rem}.w-full{width:100%}.min-w-0{min-width:0px}.min-w-\[15rem\]{min-width:15rem}.min-w-\[16rem\]{min-width:16rem}.min-w-\[280px\]{min-width:280px}.max-w-3xl{max-width:48rem}.max-w-5xl{max-width:64rem}.max-w-full{max-width:100%}.max-w-md{max-width:28rem}.max-w-sm{max-width:24rem}.flex-1{flex:1 1 0%}.shrink-0{flex-shrink:0}.origin-top-left{transform-origin:top left}.transform{transform:translate(var(--tw-translate-x),var(--tw-translate-y)) rotate(var(--tw-rotate)) skew(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y))}@keyframes spin{to{transform:rotate(360deg)}}.animate-spin{animation:spin 1s linear infinite}.cursor-pointer{cursor:pointer}.list-none{list-style-type:none}.grid-cols-2{grid-template-columns:repeat(2,minmax(0,1fr))}.flex-col{flex-direction:column}.flex-wrap{flex-wrap:wrap}.items-start{align-items:flex-start}.items-center{align-items:center}.justify-center{justify-content:center}.justify-between{justify-content:space-between}.gap-0\.5{gap:.125rem}.gap-1{gap:.25rem}.gap-2{gap:.5rem}.gap-3{gap:.75rem}.gap-4{gap:1rem}.gap-6{gap:1.5rem}.gap-x-2{-moz-column-gap:.5rem;column-gap:.5rem}.gap-x-4{-moz-column-gap:1rem;column-gap:1rem}.gap-y-0\.5{row-gap:.125rem}.gap-y-1{row-gap:.25rem}.gap-y-2{row-gap:.5rem}.space-y-0\.5>:not([hidden])~:not([hidden]){--tw-space-y-reverse: 0;margin-top:calc(.125rem * calc(1 - var(--tw-space-y-reverse)));margin-bottom:calc(.125rem * var(--tw-space-y-reverse))}.space-y-1>:not([hidden])~:not([hidden]){--tw-space-y-reverse: 0;margin-top:calc(.25rem * calc(1 - var(--tw-space-y-reverse)));margin-bottom:calc(.25rem * var(--tw-space-y-reverse))}.space-y-1\.5>:not([hidden])~:not([hidden]){--tw-space-y-reverse: 0;margin-top:calc(.375rem * calc(1 - var(--tw-space-y-reverse)));margin-bottom:calc(.375rem * var(--tw-space-y-reverse))}.space-y-2>:not([hidden])~:not([hidden]){--tw-space-y-reverse: 0;margin-top:calc(.5rem * calc(1 - var(--tw-space-y-reverse)));margin-bottom:calc(.5rem * var(--tw-space-y-reverse))}.overflow-auto{overflow:auto}.overflow-hidden{overflow:hidden}.overflow-x-auto{overflow-x:auto}.overflow-y-auto{overflow-y:auto}.truncate{overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.rounded{border-radius:.25rem}.rounded-lg{border-radius:.5rem}.rounded-md{border-radius:.375rem}.border{border-width:1px}.border-b{border-bottom-width:1px}.border-l{border-left-width:1px}.border-r{border-right-width:1px}.border-t{border-top-width:1px}.border-error\/40{border-color:#ffb4ab66}.border-outline-variant\/10{border-color:#4147541a}.border-outline-variant\/15{border-color:#41475426}.border-outline-variant\/20{border-color:#41475433}.border-outline-variant\/25{border-color:#41475440}.border-outline-variant\/30{border-color:#4147544d}.border-outline-variant\/35{border-color:#41475459}.border-outline-variant\/40{border-color:#41475466}.border-primary{--tw-border-opacity: 1;border-color:rgb(173 198 255 / var(--tw-border-opacity, 1))}.border-primary\/40{border-color:#adc6ff66}.border-white\/30{border-color:#ffffff4d}.bg-black{--tw-bg-opacity: 1;background-color:rgb(0 0 0 / var(--tw-bg-opacity, 1))}.bg-black\/40{background-color:#0006}.bg-error-container\/20{background-color:#93000a33}.bg-error\/80{background-color:#ffb4abcc}.bg-primary{--tw-bg-opacity: 1;background-color:rgb(173 198 255 / var(--tw-bg-opacity, 1))}.bg-primary-container{--tw-bg-opacity: 1;background-color:rgb(76 142 255 / var(--tw-bg-opacity, 1))}.bg-primary-container\/20{background-color:#4c8eff33}.bg-primary-container\/30{background-color:#4c8eff4d}.bg-primary-container\/50{background-color:#4c8eff80}.bg-primary\/10{background-color:#adc6ff1a}.bg-primary\/20{background-color:#adc6ff33}.bg-secondary\/80{background-color:#40e56ccc}.bg-surface{--tw-bg-opacity: 1;background-color:rgb(16 19 26 / var(--tw-bg-opacity, 1))}.bg-surface-container{--tw-bg-opacity: 1;background-color:rgb(29 32 38 / var(--tw-bg-opacity, 1))}.bg-surface-container-high{--tw-bg-opacity: 1;background-color:rgb(39 42 49 / var(--tw-bg-opacity, 1))}.bg-surface-container-highest{--tw-bg-opacity: 1;background-color:rgb(50 53 60 / var(--tw-bg-opacity, 1))}.bg-surface-container-highest\/30{background-color:#32353c4d}.bg-surface-container-highest\/40{background-color:#32353c66}.bg-surface-container-low{--tw-bg-opacity: 1;background-color:rgb(25 28 34 / var(--tw-bg-opacity, 1))}.bg-surface-container-low\/80{background-color:#191c22cc}.bg-surface-container-lowest{--tw-bg-opacity: 1;background-color:rgb(11 14 20 / var(--tw-bg-opacity, 1))}.bg-surface-container-lowest\/90{background-color:#0b0e14e6}.bg-surface-container-lowest\/95{background-color:#0b0e14f2}.object-contain{-o-object-fit:contain;object-fit:contain}.object-cover{-o-object-fit:cover;object-fit:cover}.p-1{padding:.25rem}.p-2{padding:.5rem}.p-3{padding:.75rem}.p-4{padding:1rem}.px-1{padding-left:.25rem;padding-right:.25rem}.px-2{padding-left:.5rem;padding-right:.5rem}.px-3{padding-left:.75rem;padding-right:.75rem}.px-4{padding-left:1rem;padding-right:1rem}.py-0\.5{padding-top:.125rem;padding-bottom:.125rem}.py-1{padding-top:.25rem;padding-bottom:.25rem}.py-1\.5{padding-top:.375rem;padding-bottom:.375rem}.py-2{padding-top:.5rem;padding-bottom:.5rem}.py-2\.5{padding-top:.625rem;padding-bottom:.625rem}.pb-2{padding-bottom:.5rem}.pb-3{padding-bottom:.75rem}.pr-0\.5{padding-right:.125rem}.pt-1{padding-top:.25rem}.pt-2{padding-top:.5rem}.pt-3{padding-top:.75rem}.pt-4{padding-top:1rem}.text-left{text-align:left}.text-center{text-align:center}.font-headline{font-family:system-ui,Segoe UI,sans-serif}.font-mono{font-family:ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,Liberation Mono,Courier New,monospace}.text-\[10px\]{font-size:10px}.text-\[11px\]{font-size:11px}.text-\[8px\]{font-size:8px}.text-\[9px\]{font-size:9px}.text-lg{font-size:1.125rem;line-height:1.75rem}.text-sm{font-size:.875rem;line-height:1.25rem}.text-xs{font-size:.75rem;line-height:1rem}.font-bold{font-weight:700}.font-medium{font-weight:500}.font-semibold{font-weight:600}.uppercase{text-transform:uppercase}.normal-case{text-transform:none}.tabular-nums{--tw-numeric-spacing: tabular-nums;font-variant-numeric:var(--tw-ordinal) var(--tw-slashed-zero) var(--tw-numeric-figure) var(--tw-numeric-spacing) var(--tw-numeric-fraction)}.leading-relaxed{line-height:1.625}.leading-snug{line-height:1.375}.leading-tight{line-height:1.25}.tracking-wide{letter-spacing:.025em}.text-error{--tw-text-opacity: 1;color:rgb(255 180 171 / var(--tw-text-opacity, 1))}.text-on-primary-container{--tw-text-opacity: 1;color:rgb(0 40 93 / var(--tw-text-opacity, 1))}.text-on-surface{--tw-text-opacity: 1;color:rgb(225 226 235 / var(--tw-text-opacity, 1))}.text-on-surface-variant{--tw-text-opacity: 1;color:rgb(194 198 214 / var(--tw-text-opacity, 1))}.text-on-surface-variant\/80{color:#c2c6d6cc}.text-on-surface-variant\/85{color:#c2c6d6d9}.text-on-surface-variant\/90{color:#c2c6d6e6}.text-primary{--tw-text-opacity: 1;color:rgb(173 198 255 / var(--tw-text-opacity, 1))}.text-secondary{--tw-text-opacity: 1;color:rgb(64 229 108 / var(--tw-text-opacity, 1))}.text-white{--tw-text-opacity: 1;color:rgb(255 255 255 / var(--tw-text-opacity, 1))}.antialiased{-webkit-font-smoothing:antialiased;-moz-osx-font-smoothing:grayscale}.accent-primary{accent-color:#adc6ff}.opacity-90{opacity:.9}.shadow-sm{--tw-shadow: 0 1px 2px 0 rgb(0 0 0 / .05);--tw-shadow-colored: 0 1px 2px 0 var(--tw-shadow-color);box-shadow:var(--tw-ring-offset-shadow, 0 0 #0000),var(--tw-ring-shadow, 0 0 #0000),var(--tw-shadow)}.filter{filter:var(--tw-blur) var(--tw-brightness) var(--tw-contrast) var(--tw-grayscale) var(--tw-hue-rotate) var(--tw-invert) var(--tw-saturate) var(--tw-sepia) var(--tw-drop-shadow)}.transition-all{transition-property:all;transition-timing-function:cubic-bezier(.4,0,.2,1);transition-duration:.15s}.transition-colors{transition-property:color,background-color,border-color,text-decoration-color,fill,stroke;transition-timing-function:cubic-bezier(.4,0,.2,1);transition-duration:.15s}.transition-transform{transition-property:transform;transition-timing-function:cubic-bezier(.4,0,.2,1);transition-duration:.15s}html,body,#root{height:100%}.hover\:bg-error-container\/20:hover{background-color:#93000a33}.hover\:bg-error-container\/30:hover{background-color:#93000a4d}.hover\:bg-surface-container:hover{--tw-bg-opacity: 1;background-color:rgb(29 32 38 / var(--tw-bg-opacity, 1))}.hover\:bg-surface-container-high:hover{--tw-bg-opacity: 1;background-color:rgb(39 42 49 / var(--tw-bg-opacity, 1))}.hover\:text-error:hover{--tw-text-opacity: 1;color:rgb(255 180 171 / var(--tw-text-opacity, 1))}.hover\:text-on-surface:hover{--tw-text-opacity: 1;color:rgb(225 226 235 / var(--tw-text-opacity, 1))}.hover\:underline:hover{text-decoration-line:underline}.disabled\:opacity-40:disabled{opacity:.4}.disabled\:opacity-50:disabled{opacity:.5}@media (min-width: 640px){.sm\:grid-cols-2{grid-template-columns:repeat(2,minmax(0,1fr))}.sm\:grid-cols-3{grid-template-columns:repeat(3,minmax(0,1fr))}}.\[\&\:\:-webkit-details-marker\]\:hidden::-webkit-details-marker{display:none} diff --git a/web/static/analysis-lab/assets/index-ClgTRc4R.css b/web/static/analysis-lab/assets/index-ClgTRc4R.css deleted file mode 100644 index a4a8851..0000000 --- a/web/static/analysis-lab/assets/index-ClgTRc4R.css +++ /dev/null @@ -1 +0,0 @@ -*,:before,:after{--tw-border-spacing-x: 0;--tw-border-spacing-y: 0;--tw-translate-x: 0;--tw-translate-y: 0;--tw-rotate: 0;--tw-skew-x: 0;--tw-skew-y: 0;--tw-scale-x: 1;--tw-scale-y: 1;--tw-pan-x: ;--tw-pan-y: ;--tw-pinch-zoom: ;--tw-scroll-snap-strictness: proximity;--tw-gradient-from-position: ;--tw-gradient-via-position: ;--tw-gradient-to-position: ;--tw-ordinal: ;--tw-slashed-zero: ;--tw-numeric-figure: ;--tw-numeric-spacing: ;--tw-numeric-fraction: ;--tw-ring-inset: ;--tw-ring-offset-width: 0px;--tw-ring-offset-color: #fff;--tw-ring-color: rgb(59 130 246 / .5);--tw-ring-offset-shadow: 0 0 #0000;--tw-ring-shadow: 0 0 #0000;--tw-shadow: 0 0 #0000;--tw-shadow-colored: 0 0 #0000;--tw-blur: ;--tw-brightness: ;--tw-contrast: ;--tw-grayscale: ;--tw-hue-rotate: ;--tw-invert: ;--tw-saturate: ;--tw-sepia: ;--tw-drop-shadow: ;--tw-backdrop-blur: ;--tw-backdrop-brightness: ;--tw-backdrop-contrast: ;--tw-backdrop-grayscale: ;--tw-backdrop-hue-rotate: ;--tw-backdrop-invert: ;--tw-backdrop-opacity: ;--tw-backdrop-saturate: ;--tw-backdrop-sepia: ;--tw-contain-size: ;--tw-contain-layout: ;--tw-contain-paint: ;--tw-contain-style: }::backdrop{--tw-border-spacing-x: 0;--tw-border-spacing-y: 0;--tw-translate-x: 0;--tw-translate-y: 0;--tw-rotate: 0;--tw-skew-x: 0;--tw-skew-y: 0;--tw-scale-x: 1;--tw-scale-y: 1;--tw-pan-x: ;--tw-pan-y: ;--tw-pinch-zoom: ;--tw-scroll-snap-strictness: proximity;--tw-gradient-from-position: ;--tw-gradient-via-position: ;--tw-gradient-to-position: ;--tw-ordinal: ;--tw-slashed-zero: ;--tw-numeric-figure: ;--tw-numeric-spacing: ;--tw-numeric-fraction: ;--tw-ring-inset: ;--tw-ring-offset-width: 0px;--tw-ring-offset-color: #fff;--tw-ring-color: rgb(59 130 246 / .5);--tw-ring-offset-shadow: 0 0 #0000;--tw-ring-shadow: 0 0 #0000;--tw-shadow: 0 0 #0000;--tw-shadow-colored: 0 0 #0000;--tw-blur: ;--tw-brightness: ;--tw-contrast: ;--tw-grayscale: ;--tw-hue-rotate: ;--tw-invert: ;--tw-saturate: ;--tw-sepia: ;--tw-drop-shadow: ;--tw-backdrop-blur: ;--tw-backdrop-brightness: ;--tw-backdrop-contrast: ;--tw-backdrop-grayscale: ;--tw-backdrop-hue-rotate: ;--tw-backdrop-invert: ;--tw-backdrop-opacity: ;--tw-backdrop-saturate: ;--tw-backdrop-sepia: ;--tw-contain-size: ;--tw-contain-layout: ;--tw-contain-paint: ;--tw-contain-style: }*,:before,:after{box-sizing:border-box;border-width:0;border-style:solid;border-color:#e5e7eb}:before,:after{--tw-content: ""}html,:host{line-height:1.5;-webkit-text-size-adjust:100%;-moz-tab-size:4;-o-tab-size:4;tab-size:4;font-family:ui-sans-serif,system-ui,sans-serif,"Apple Color Emoji","Segoe UI Emoji",Segoe UI Symbol,"Noto Color Emoji";font-feature-settings:normal;font-variation-settings:normal;-webkit-tap-highlight-color:transparent}body{margin:0;line-height:inherit}hr{height:0;color:inherit;border-top-width:1px}abbr:where([title]){-webkit-text-decoration:underline dotted;text-decoration:underline dotted}h1,h2,h3,h4,h5,h6{font-size:inherit;font-weight:inherit}a{color:inherit;text-decoration:inherit}b,strong{font-weight:bolder}code,kbd,samp,pre{font-family:ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,Liberation Mono,Courier New,monospace;font-feature-settings:normal;font-variation-settings:normal;font-size:1em}small{font-size:80%}sub,sup{font-size:75%;line-height:0;position:relative;vertical-align:baseline}sub{bottom:-.25em}sup{top:-.5em}table{text-indent:0;border-color:inherit;border-collapse:collapse}button,input,optgroup,select,textarea{font-family:inherit;font-feature-settings:inherit;font-variation-settings:inherit;font-size:100%;font-weight:inherit;line-height:inherit;letter-spacing:inherit;color:inherit;margin:0;padding:0}button,select{text-transform:none}button,input:where([type=button]),input:where([type=reset]),input:where([type=submit]){-webkit-appearance:button;background-color:transparent;background-image:none}:-moz-focusring{outline:auto}:-moz-ui-invalid{box-shadow:none}progress{vertical-align:baseline}::-webkit-inner-spin-button,::-webkit-outer-spin-button{height:auto}[type=search]{-webkit-appearance:textfield;outline-offset:-2px}::-webkit-search-decoration{-webkit-appearance:none}::-webkit-file-upload-button{-webkit-appearance:button;font:inherit}summary{display:list-item}blockquote,dl,dd,h1,h2,h3,h4,h5,h6,hr,figure,p,pre{margin:0}fieldset{margin:0;padding:0}legend{padding:0}ol,ul,menu{list-style:none;margin:0;padding:0}dialog{padding:0}textarea{resize:vertical}input::-moz-placeholder,textarea::-moz-placeholder{opacity:1;color:#9ca3af}input::placeholder,textarea::placeholder{opacity:1;color:#9ca3af}button,[role=button]{cursor:pointer}:disabled{cursor:default}img,svg,video,canvas,audio,iframe,embed,object{display:block;vertical-align:middle}img,video{max-width:100%;height:auto}[hidden]:where(:not([hidden=until-found])){display:none}.pointer-events-none{pointer-events:none}.collapse{visibility:collapse}.static{position:static}.absolute{position:absolute}.relative{position:relative}.inset-0{top:0;right:0;bottom:0;left:0}.left-0{left:0}.left-2{left:.5rem}.top-0{top:0}.top-2{top:.5rem}.z-20{z-index:20}.z-\[1\]{z-index:1}.col-span-2{grid-column:span 2 / span 2}.mb-1{margin-bottom:.25rem}.mb-2{margin-bottom:.5rem}.mb-3{margin-bottom:.75rem}.mb-4{margin-bottom:1rem}.ml-auto{margin-left:auto}.mr-1{margin-right:.25rem}.mr-2{margin-right:.5rem}.mt-0\.5{margin-top:.125rem}.mt-1{margin-top:.25rem}.mt-1\.5{margin-top:.375rem}.mt-2{margin-top:.5rem}.mt-3{margin-top:.75rem}.mt-4{margin-top:1rem}.mt-6{margin-top:1.5rem}.block{display:block}.inline-block{display:inline-block}.inline{display:inline}.flex{display:flex}.inline-flex{display:inline-flex}.table{display:table}.grid{display:grid}.hidden{display:none}.aspect-video{aspect-ratio:16 / 9}.h-11{height:2.75rem}.h-12{height:3rem}.h-3{height:.75rem}.h-3\.5{height:.875rem}.h-4{height:1rem}.h-5{height:1.25rem}.h-8{height:2rem}.h-9{height:2.25rem}.h-full{height:100%}.max-h-24{max-height:6rem}.max-h-36{max-height:9rem}.max-h-40{max-height:10rem}.max-h-48{max-height:12rem}.max-h-64{max-height:16rem}.max-h-\[70vh\]{max-height:70vh}.max-h-\[min\(22rem\,55vh\)\]{max-height:min(22rem,55vh)}.max-h-\[min\(50vh\,28rem\)\]{max-height:min(50vh,28rem)}.min-h-0{min-height:0px}.min-h-\[120px\]{min-height:120px}.min-h-\[200px\]{min-height:200px}.min-h-\[220px\]{min-height:220px}.min-h-full{min-height:100%}.w-11{width:2.75rem}.w-16{width:4rem}.w-3{width:.75rem}.w-3\.5{width:.875rem}.w-4{width:1rem}.w-5{width:1.25rem}.w-64{width:16rem}.w-8{width:2rem}.w-80{width:20rem}.w-full{width:100%}.min-w-0{min-width:0px}.min-w-\[15rem\]{min-width:15rem}.min-w-\[16rem\]{min-width:16rem}.min-w-\[280px\]{min-width:280px}.max-w-5xl{max-width:64rem}.max-w-full{max-width:100%}.max-w-md{max-width:28rem}.max-w-sm{max-width:24rem}.flex-1{flex:1 1 0%}.shrink-0{flex-shrink:0}.origin-top-left{transform-origin:top left}.transform{transform:translate(var(--tw-translate-x),var(--tw-translate-y)) rotate(var(--tw-rotate)) skew(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y))}@keyframes spin{to{transform:rotate(360deg)}}.animate-spin{animation:spin 1s linear infinite}.cursor-pointer{cursor:pointer}.list-none{list-style-type:none}.grid-cols-2{grid-template-columns:repeat(2,minmax(0,1fr))}.flex-col{flex-direction:column}.flex-wrap{flex-wrap:wrap}.items-start{align-items:flex-start}.items-center{align-items:center}.justify-center{justify-content:center}.justify-between{justify-content:space-between}.gap-0\.5{gap:.125rem}.gap-1{gap:.25rem}.gap-2{gap:.5rem}.gap-3{gap:.75rem}.gap-4{gap:1rem}.gap-6{gap:1.5rem}.gap-x-2{-moz-column-gap:.5rem;column-gap:.5rem}.gap-x-4{-moz-column-gap:1rem;column-gap:1rem}.gap-y-0\.5{row-gap:.125rem}.gap-y-1{row-gap:.25rem}.gap-y-2{row-gap:.5rem}.space-y-0\.5>:not([hidden])~:not([hidden]){--tw-space-y-reverse: 0;margin-top:calc(.125rem * calc(1 - var(--tw-space-y-reverse)));margin-bottom:calc(.125rem * var(--tw-space-y-reverse))}.space-y-1>:not([hidden])~:not([hidden]){--tw-space-y-reverse: 0;margin-top:calc(.25rem * calc(1 - var(--tw-space-y-reverse)));margin-bottom:calc(.25rem * var(--tw-space-y-reverse))}.space-y-1\.5>:not([hidden])~:not([hidden]){--tw-space-y-reverse: 0;margin-top:calc(.375rem * calc(1 - var(--tw-space-y-reverse)));margin-bottom:calc(.375rem * var(--tw-space-y-reverse))}.space-y-2>:not([hidden])~:not([hidden]){--tw-space-y-reverse: 0;margin-top:calc(.5rem * calc(1 - var(--tw-space-y-reverse)));margin-bottom:calc(.5rem * var(--tw-space-y-reverse))}.overflow-auto{overflow:auto}.overflow-hidden{overflow:hidden}.overflow-x-auto{overflow-x:auto}.overflow-y-auto{overflow-y:auto}.truncate{overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.rounded{border-radius:.25rem}.rounded-lg{border-radius:.5rem}.rounded-md{border-radius:.375rem}.border{border-width:1px}.border-b{border-bottom-width:1px}.border-l{border-left-width:1px}.border-r{border-right-width:1px}.border-t{border-top-width:1px}.border-error\/40{border-color:#ffb4ab66}.border-outline-variant\/10{border-color:#4147541a}.border-outline-variant\/15{border-color:#41475426}.border-outline-variant\/20{border-color:#41475433}.border-outline-variant\/25{border-color:#41475440}.border-outline-variant\/30{border-color:#4147544d}.border-outline-variant\/40{border-color:#41475466}.border-primary{--tw-border-opacity: 1;border-color:rgb(173 198 255 / var(--tw-border-opacity, 1))}.border-white\/30{border-color:#ffffff4d}.bg-black{--tw-bg-opacity: 1;background-color:rgb(0 0 0 / var(--tw-bg-opacity, 1))}.bg-black\/40{background-color:#0006}.bg-error-container\/20{background-color:#93000a33}.bg-error\/80{background-color:#ffb4abcc}.bg-primary{--tw-bg-opacity: 1;background-color:rgb(173 198 255 / var(--tw-bg-opacity, 1))}.bg-primary-container{--tw-bg-opacity: 1;background-color:rgb(76 142 255 / var(--tw-bg-opacity, 1))}.bg-primary-container\/20{background-color:#4c8eff33}.bg-primary-container\/30{background-color:#4c8eff4d}.bg-primary-container\/50{background-color:#4c8eff80}.bg-primary\/20{background-color:#adc6ff33}.bg-secondary\/80{background-color:#40e56ccc}.bg-surface{--tw-bg-opacity: 1;background-color:rgb(16 19 26 / var(--tw-bg-opacity, 1))}.bg-surface-container{--tw-bg-opacity: 1;background-color:rgb(29 32 38 / var(--tw-bg-opacity, 1))}.bg-surface-container-high{--tw-bg-opacity: 1;background-color:rgb(39 42 49 / var(--tw-bg-opacity, 1))}.bg-surface-container-highest{--tw-bg-opacity: 1;background-color:rgb(50 53 60 / var(--tw-bg-opacity, 1))}.bg-surface-container-highest\/30{background-color:#32353c4d}.bg-surface-container-highest\/40{background-color:#32353c66}.bg-surface-container-low{--tw-bg-opacity: 1;background-color:rgb(25 28 34 / var(--tw-bg-opacity, 1))}.bg-surface-container-low\/80{background-color:#191c22cc}.bg-surface-container-lowest{--tw-bg-opacity: 1;background-color:rgb(11 14 20 / var(--tw-bg-opacity, 1))}.bg-surface-container-lowest\/90{background-color:#0b0e14e6}.bg-surface-container-lowest\/95{background-color:#0b0e14f2}.object-contain{-o-object-fit:contain;object-fit:contain}.object-cover{-o-object-fit:cover;object-fit:cover}.p-1{padding:.25rem}.p-2{padding:.5rem}.p-3{padding:.75rem}.p-4{padding:1rem}.px-1{padding-left:.25rem;padding-right:.25rem}.px-2{padding-left:.5rem;padding-right:.5rem}.px-3{padding-left:.75rem;padding-right:.75rem}.px-4{padding-left:1rem;padding-right:1rem}.py-0\.5{padding-top:.125rem;padding-bottom:.125rem}.py-1{padding-top:.25rem;padding-bottom:.25rem}.py-1\.5{padding-top:.375rem;padding-bottom:.375rem}.py-2{padding-top:.5rem;padding-bottom:.5rem}.py-2\.5{padding-top:.625rem;padding-bottom:.625rem}.pb-2{padding-bottom:.5rem}.pb-3{padding-bottom:.75rem}.pr-0\.5{padding-right:.125rem}.pt-1{padding-top:.25rem}.pt-2{padding-top:.5rem}.pt-3{padding-top:.75rem}.pt-4{padding-top:1rem}.text-left{text-align:left}.text-center{text-align:center}.font-headline{font-family:system-ui,Segoe UI,sans-serif}.font-mono{font-family:ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,Liberation Mono,Courier New,monospace}.text-\[10px\]{font-size:10px}.text-\[11px\]{font-size:11px}.text-\[8px\]{font-size:8px}.text-\[9px\]{font-size:9px}.text-lg{font-size:1.125rem;line-height:1.75rem}.text-sm{font-size:.875rem;line-height:1.25rem}.text-xs{font-size:.75rem;line-height:1rem}.font-bold{font-weight:700}.font-medium{font-weight:500}.font-semibold{font-weight:600}.uppercase{text-transform:uppercase}.normal-case{text-transform:none}.tabular-nums{--tw-numeric-spacing: tabular-nums;font-variant-numeric:var(--tw-ordinal) var(--tw-slashed-zero) var(--tw-numeric-figure) var(--tw-numeric-spacing) var(--tw-numeric-fraction)}.leading-relaxed{line-height:1.625}.leading-snug{line-height:1.375}.leading-tight{line-height:1.25}.tracking-wide{letter-spacing:.025em}.text-error{--tw-text-opacity: 1;color:rgb(255 180 171 / var(--tw-text-opacity, 1))}.text-on-primary-container{--tw-text-opacity: 1;color:rgb(0 40 93 / var(--tw-text-opacity, 1))}.text-on-surface{--tw-text-opacity: 1;color:rgb(225 226 235 / var(--tw-text-opacity, 1))}.text-on-surface-variant{--tw-text-opacity: 1;color:rgb(194 198 214 / var(--tw-text-opacity, 1))}.text-on-surface-variant\/85{color:#c2c6d6d9}.text-on-surface-variant\/90{color:#c2c6d6e6}.text-primary{--tw-text-opacity: 1;color:rgb(173 198 255 / var(--tw-text-opacity, 1))}.text-secondary{--tw-text-opacity: 1;color:rgb(64 229 108 / var(--tw-text-opacity, 1))}.text-white{--tw-text-opacity: 1;color:rgb(255 255 255 / var(--tw-text-opacity, 1))}.antialiased{-webkit-font-smoothing:antialiased;-moz-osx-font-smoothing:grayscale}.accent-primary{accent-color:#adc6ff}.opacity-90{opacity:.9}.shadow-sm{--tw-shadow: 0 1px 2px 0 rgb(0 0 0 / .05);--tw-shadow-colored: 0 1px 2px 0 var(--tw-shadow-color);box-shadow:var(--tw-ring-offset-shadow, 0 0 #0000),var(--tw-ring-shadow, 0 0 #0000),var(--tw-shadow)}.filter{filter:var(--tw-blur) var(--tw-brightness) var(--tw-contrast) var(--tw-grayscale) var(--tw-hue-rotate) var(--tw-invert) var(--tw-saturate) var(--tw-sepia) var(--tw-drop-shadow)}.transition-colors{transition-property:color,background-color,border-color,text-decoration-color,fill,stroke;transition-timing-function:cubic-bezier(.4,0,.2,1);transition-duration:.15s}.transition-transform{transition-property:transform;transition-timing-function:cubic-bezier(.4,0,.2,1);transition-duration:.15s}html,body,#root{height:100%}.hover\:bg-error-container\/20:hover{background-color:#93000a33}.hover\:bg-error-container\/30:hover{background-color:#93000a4d}.hover\:bg-surface-container:hover{--tw-bg-opacity: 1;background-color:rgb(29 32 38 / var(--tw-bg-opacity, 1))}.hover\:bg-surface-container-high:hover{--tw-bg-opacity: 1;background-color:rgb(39 42 49 / var(--tw-bg-opacity, 1))}.hover\:text-error:hover{--tw-text-opacity: 1;color:rgb(255 180 171 / var(--tw-text-opacity, 1))}.hover\:text-on-surface:hover{--tw-text-opacity: 1;color:rgb(225 226 235 / var(--tw-text-opacity, 1))}.hover\:underline:hover{text-decoration-line:underline}.disabled\:opacity-40:disabled{opacity:.4}@media (min-width: 640px){.sm\:grid-cols-2{grid-template-columns:repeat(2,minmax(0,1fr))}.sm\:grid-cols-3{grid-template-columns:repeat(3,minmax(0,1fr))}}.\[\&\:\:-webkit-details-marker\]\:hidden::-webkit-details-marker{display:none} diff --git a/web/static/analysis-lab/assets/worker-BAOIWoxA.js b/web/static/analysis-lab/assets/worker-BAOIWoxA.js new file mode 100644 index 0000000..4be75e9 --- /dev/null +++ b/web/static/analysis-lab/assets/worker-BAOIWoxA.js @@ -0,0 +1 @@ +(function(){"use strict";const i="https://unpkg.com/@ffmpeg/core@0.12.9/dist/umd/ffmpeg-core.js";var E;(function(t){t.LOAD="LOAD",t.EXEC="EXEC",t.FFPROBE="FFPROBE",t.WRITE_FILE="WRITE_FILE",t.READ_FILE="READ_FILE",t.DELETE_FILE="DELETE_FILE",t.RENAME="RENAME",t.CREATE_DIR="CREATE_DIR",t.LIST_DIR="LIST_DIR",t.DELETE_DIR="DELETE_DIR",t.ERROR="ERROR",t.DOWNLOAD="DOWNLOAD",t.PROGRESS="PROGRESS",t.LOG="LOG",t.MOUNT="MOUNT",t.UNMOUNT="UNMOUNT"})(E||(E={}));const f=new Error("unknown message type"),a=new Error("ffmpeg is not loaded, call `await ffmpeg.load()` first"),u=new Error("failed to import ffmpeg-core.js");let r;const O=async({coreURL:t,wasmURL:n,workerURL:e})=>{const o=!r;try{t||(t=i),importScripts(t)}catch{if((!t||t===i)&&(t=i.replace("/umd/","/esm/")),self.createFFmpegCore=(await import(t)).default,!self.createFFmpegCore)throw u}const s=t,c=n||t.replace(/.js$/g,".wasm"),p=e||t.replace(/.js$/g,".worker.js");return r=await self.createFFmpegCore({mainScriptUrlOrBlob:`${s}#${btoa(JSON.stringify({wasmURL:c,workerURL:p}))}`}),r.setLogger(R=>self.postMessage({type:E.LOG,data:R})),r.setProgress(R=>self.postMessage({type:E.PROGRESS,data:R})),o},m=({args:t,timeout:n=-1})=>{r.setTimeout(n),r.exec(...t);const e=r.ret;return r.reset(),e},l=({args:t,timeout:n=-1})=>{r.setTimeout(n),r.ffprobe(...t);const e=r.ret;return r.reset(),e},D=({path:t,data:n})=>(r.FS.writeFile(t,n),!0),S=({path:t,encoding:n})=>r.FS.readFile(t,{encoding:n}),I=({path:t})=>(r.FS.unlink(t),!0),L=({oldPath:t,newPath:n})=>(r.FS.rename(t,n),!0),N=({path:t})=>(r.FS.mkdir(t),!0),A=({path:t})=>{const n=r.FS.readdir(t),e=[];for(const o of n){const s=r.FS.stat(`${t}/${o}`),c=r.FS.isDir(s.mode);e.push({name:o,isDir:c})}return e},k=({path:t})=>(r.FS.rmdir(t),!0),w=({fsType:t,options:n,mountPoint:e})=>{const o=t,s=r.FS.filesystems[o];return s?(r.FS.mount(s,n,e),!0):!1},b=({mountPoint:t})=>(r.FS.unmount(t),!0);self.onmessage=async({data:{id:t,type:n,data:e}})=>{const o=[];let s;try{if(n!==E.LOAD&&!r)throw a;switch(n){case E.LOAD:s=await O(e);break;case E.EXEC:s=m(e);break;case E.FFPROBE:s=l(e);break;case E.WRITE_FILE:s=D(e);break;case E.READ_FILE:s=S(e);break;case E.DELETE_FILE:s=I(e);break;case E.RENAME:s=L(e);break;case E.CREATE_DIR:s=N(e);break;case E.LIST_DIR:s=A(e);break;case E.DELETE_DIR:s=k(e);break;case E.MOUNT:s=w(e);break;case E.UNMOUNT:s=b(e);break;default:throw f}}catch(c){self.postMessage({id:t,type:E.ERROR,data:c.toString()});return}s instanceof Uint8Array&&o.push(s.buffer),self.postMessage({id:t,type:n,data:s},o)}})(); diff --git a/web/static/analysis-lab/index.html b/web/static/analysis-lab/index.html index aaba632..f10d6d4 100644 --- a/web/static/analysis-lab/index.html +++ b/web/static/analysis-lab/index.html @@ -4,8 +4,8 @@ OGScope 星空解算控制台 - - + +
diff --git a/web/static/css/debug.css b/web/static/css/debug.css index a2f81fa..6cc1a0b 100644 --- a/web/static/css/debug.css +++ b/web/static/css/debug.css @@ -944,6 +944,9 @@ button:disabled:hover, transform: translateX(100%); transition: transform 0.3s ease; max-width: 300px; + overflow-wrap: anywhere; + word-break: break-word; + line-height: 1.35; } .notification.show { diff --git a/web/static/i18n/analysis.en.json b/web/static/i18n/analysis.en.json index 46a9cc0..fe7c5a8 100644 --- a/web/static/i18n/analysis.en.json +++ b/web/static/i18n/analysis.en.json @@ -33,6 +33,19 @@ "sidebar.assetTypeVideo": "Video", "sidebar.debugEmpty": "No debug files", "sidebar.importToPool": "Import to pool", + "sidebar.importToPoolWithTranscode": "Transcode & Import", + "sidebar.importToPoolDirect": "Import Directly", + "sidebar.flowPreparing": "Preparing...", + "sidebar.flowImportingDebug": "Importing debug capture...", + "sidebar.flowLoadingTranscoder": "Loading transcoder...", + "sidebar.flowTranscoding": "Transcoding AVI -> MP4...", + "sidebar.flowPackaging": "Packaging output...", + "sidebar.flowUploadingMp4": "Uploading MP4...", + "sidebar.flowReplacing": "Replacing and removing original AVI...", + "sidebar.flowNoTranscode": "No transcode required, imported directly.", + "sidebar.flowDone": "Completed", + "sidebar.flowDoneMsg": "Done: {name}", + "sidebar.flowFailed": "Failed", "sidebar.debugPage": "Page {cur} / {total}", "sidebar.batchPresets": "Batch presets", "sidebar.batchHint": "Check presets, then use Batch solve to compare multiple param sets.", @@ -93,6 +106,9 @@ "params.fovErrHelp": "Search range around estimated FOV.", "params.timeout": "Timeout (ms)", "params.timeoutHelp": "Max wait time per solve.", + "params.solveIntervalMs": "Realtime solve interval (ms)", + "params.solveIntervalMsHelp": "Desired interval is adjustable, but backend clamps it into a safe range.", + "params.solveIntervalBound": "Backend bounds: {min}-{max} ms, effective now: {effective} ms", "params.solveProfile": "Solve profile", "params.solveProfileHelp": "Speed/Balanced/Robust tune timeout, centroid thresholds, and matching star count together.", "params.solveProfileSpeed": "Speed first", @@ -151,5 +167,15 @@ "results.collapseJson": "Collapse", "err.selectFile": "Select a file", "err.selectPresets": "Select at least one preset", - "common.placeholder": "—" + "common.placeholder": "—", + "lab.transcode.title": "AVI requires transcoding", + "lab.transcode.desc": "This video is AVI. For browser preview and continuous solving, transcode it locally to MP4 and upload replacement. The original AVI on server will be removed after success.", + "lab.transcode.button": "Transcode and Upload Replacement", + "lab.transcode.loading": "Loading transcoder...", + "lab.transcode.running": "Transcoding AVI -> MP4...", + "lab.transcode.packaging": "Packaging transcoded output...", + "lab.transcode.uploading": "Uploading transcoded output...", + "lab.transcode.done": "Transcode and replacement completed.", + "lab.transcode.failed": "Transcode or upload failed. Please retry.", + "lab.transcode.fetchFailed": "Failed to fetch AVI from server." } diff --git a/web/static/i18n/analysis.zh.json b/web/static/i18n/analysis.zh.json index 3514822..28775cd 100644 --- a/web/static/i18n/analysis.zh.json +++ b/web/static/i18n/analysis.zh.json @@ -17,6 +17,19 @@ "sidebar.assetTypeVideo": "视频", "sidebar.debugEmpty": "暂无调试文件", "sidebar.importToPool": "导入到素材池", + "sidebar.importToPoolWithTranscode": "转码并导入素材池", + "sidebar.importToPoolDirect": "直接导入素材池", + "sidebar.flowPreparing": "准备中…", + "sidebar.flowImportingDebug": "正在导入调试素材…", + "sidebar.flowLoadingTranscoder": "正在加载转码器…", + "sidebar.flowTranscoding": "正在转码 AVI -> MP4…", + "sidebar.flowPackaging": "正在封装转码结果…", + "sidebar.flowUploadingMp4": "正在上传 MP4…", + "sidebar.flowReplacing": "正在替换并清理原 AVI…", + "sidebar.flowNoTranscode": "该文件无需转码,已直接导入。", + "sidebar.flowDone": "流程完成", + "sidebar.flowDoneMsg": "已完成:{name}", + "sidebar.flowFailed": "流程失败", "sidebar.debugPage": "第 {cur} / {total} 页", "sidebar.batchPresets": "批量预设", "sidebar.batchHint": "勾选后点击「批量解算」可一次用多组参数对比结果。", @@ -77,6 +90,9 @@ "params.fovErrHelp": "允许 Tetra3 在估计 FOV 附近的搜索范围。", "params.timeout": "超时 (ms)", "params.timeoutHelp": "单次解算最长等待时间。", + "params.solveIntervalMs": "实时解算间隔 (ms)", + "params.solveIntervalMsHelp": "期望间隔可调整,但会被后端限制在安全范围内。", + "params.solveIntervalBound": "后端限制范围:{min}-{max} ms,当前生效:{effective} ms", "params.solveProfile": "解算档位", "params.solveProfileHelp": "速度/平衡/稳健三档会同时调整超时、提星阈值与参与匹配星点数。", "params.solveProfileSpeed": "速度优先", @@ -151,5 +167,15 @@ "lab.cameraSnapshotName": "ogscope_camera_live", "lab.metric.probRaw": "原始 Prob", "lab.systemLoad": "系统负载", - "results.saveBatchAll": "保存全部到实验记录" + "results.saveBatchAll": "保存全部到实验记录", + "lab.transcode.title": "AVI 文件需先转码", + "lab.transcode.desc": "当前视频为 AVI。为保证浏览器可预览与连续解算,请先在本地转码为 MP4 并上传替换。上传成功后将自动删除服务器上的原 AVI。", + "lab.transcode.button": "转码并上传替换", + "lab.transcode.loading": "正在加载转码器…", + "lab.transcode.running": "正在转码 AVI -> MP4…", + "lab.transcode.packaging": "正在封装转码结果…", + "lab.transcode.uploading": "正在上传转码结果…", + "lab.transcode.done": "转码并替换完成,可继续预览与解算。", + "lab.transcode.failed": "转码或上传失败,请重试。", + "lab.transcode.fetchFailed": "无法读取服务器上的 AVI 文件。" } diff --git a/web/static/i18n/debug.en.json b/web/static/i18n/debug.en.json index 6b98910..3012696 100644 --- a/web/static/i18n/debug.en.json +++ b/web/static/i18n/debug.en.json @@ -135,6 +135,7 @@ "recording.stop": "Stop Recording", "recording.statusLabel": "Recording Status:", "recording.durationLabel": "Duration:", + "recording.aviHint": "Recording uses low-FPS AVI to reduce board load; transcode to MP4 in Analysis Lab before solving.", "histogram.button": "📊 Histogram", "histogram.rgbTitle": "RGB Histogram", "histogram.frontendOnly": "Frontend-only", diff --git a/web/static/i18n/debug.zh.json b/web/static/i18n/debug.zh.json index 49e154b..6dae950 100644 --- a/web/static/i18n/debug.zh.json +++ b/web/static/i18n/debug.zh.json @@ -135,6 +135,7 @@ "recording.stop": "停止录制", "recording.statusLabel": "录制状态:", "recording.durationLabel": "录制时长:", + "recording.aviHint": "录制将使用低帧率 AVI 以降低开发板负载;可在解算控制台转码为 MP4 后再分析。", "histogram.button": "📊 直方图", "histogram.rgbTitle": "RGB 直方图", "histogram.frontendOnly": "纯前端实现", diff --git a/web/static/js/debug.js b/web/static/js/debug.js index ff55607..ca26969 100644 --- a/web/static/js/debug.js +++ b/web/static/js/debug.js @@ -35,6 +35,7 @@ class DebugConsole { this.files = []; this.recordingStartTime = null; this.recordingInterval = null; + this.recordingCommandInFlight = false; this.statusInterval = null; this.systemInfoInterval = null; this.previewObjectUrl = null; @@ -1703,11 +1704,12 @@ class DebugConsole { * 开始录制 / Start recording */ async startRecording() { + if (this.recordingCommandInFlight) return; if (!this.cameraStatus.streaming) { this.showNotification('请先启动相机预览', 'warning'); return; } - + this.recordingCommandInFlight = true; try { const response = await fetch('/api/debug/camera/record/start', { method: 'POST' @@ -1715,7 +1717,10 @@ class DebugConsole { if (response.ok) { const result = await response.json(); - this.showNotification(`开始录制: ${result.filename}`, 'success'); + this.showNotification( + `开始录制: ${this.compactFilename(result.filename)}`, + 'success' + ); // 开始计时 / Start timing this.recordingStartTime = Date.now(); @@ -1734,6 +1739,10 @@ class DebugConsole { } catch (error) { console.error('[DebugConsole] 开始录制失败:', error); this.showNotification(`开始录制失败: ${error.message}`, 'error'); + } finally { + this.recordingCommandInFlight = false; + // 请求结束立即恢复按钮可用态,避免必须刷新页面 / Refresh button state immediately after request. + this.updateRecordingButtons(!!this.cameraStatus.recording); } } @@ -1741,10 +1750,22 @@ class DebugConsole { * 停止录制 / Stop recording */ async stopRecording() { + if (this.recordingCommandInFlight) return; + this.recordingCommandInFlight = true; try { - await fetch('/api/debug/camera/record/stop', { + const response = await fetch('/api/debug/camera/record/stop', { method: 'POST' }); + if (!response.ok) { + let detail = '停止录制失败'; + try { + const payload = await response.json(); + detail = payload.detail || detail; + } catch (_) { + // ignore parse failure + } + throw new Error(detail); + } this.stopRecordingTimer(); this.updateRecordingButtons(false); @@ -1756,7 +1777,11 @@ class DebugConsole { } catch (error) { console.error('[DebugConsole] 停止录制失败:', error); - this.showNotification('停止录制失败', 'error'); + this.showNotification(`停止录制失败: ${error.message}`, 'error'); + } finally { + this.recordingCommandInFlight = false; + // 请求结束立即恢复按钮可用态,避免必须刷新页面 / Refresh button state immediately after request. + this.updateRecordingButtons(!!this.cameraStatus.recording); } } @@ -1821,8 +1846,9 @@ class DebugConsole { const startBtn = document.getElementById('start-recording'); const stopBtn = document.getElementById('stop-recording'); - if (startBtn) startBtn.disabled = isRecording; - if (stopBtn) stopBtn.disabled = !isRecording; + const opBusy = !!this.recordingCommandInFlight; + if (startBtn) startBtn.disabled = opBusy || isRecording; + if (stopBtn) stopBtn.disabled = opBusy || !isRecording; } /** @@ -3298,6 +3324,12 @@ class DebugConsole { }, 300); }, 3000); } + + compactFilename(name, head = 18, tail = 14) { + if (typeof name !== 'string') return String(name || ''); + if (name.length <= head + tail + 1) return name; + return `${name.slice(0, head)}...${name.slice(-tail)}`; + } /** * 显示模态框 / Show modal box diff --git a/web/templates/debug.html b/web/templates/debug.html index 6a2decc..297ef59 100644 --- a/web/templates/debug.html +++ b/web/templates/debug.html @@ -387,6 +387,9 @@

🎥 视频录制

停止录制
+

+ 录制将使用低帧率 AVI 以降低开发板负载;可在解算控制台转码为 MP4 后再分析。 +

From 99c47ac0162111742f9480cb4b43839af107b74c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E6=88=91=E6=98=AF=E5=B0=8F=E4=B8=80=E7=81=B0?= Date: Tue, 31 Mar 2026 23:52:54 +0800 Subject: [PATCH 2/3] =?UTF-8?q?chore:=20=E5=AF=B9=E9=BD=90=E5=90=8E?= =?UTF-8?q?=E7=AB=AF=E4=BB=A3=E7=A0=81=E6=A0=BC=E5=BC=8F=E4=BB=A5=E9=80=9A?= =?UTF-8?q?=E8=BF=87=20CI=20/=20Align=20backend=20formatting=20to=20satisf?= =?UTF-8?q?y=20CI?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 使用 Ruff 调整 analysis 路由导入顺序 / Use Ruff to normalize imports in analysis routes - 使用 Black 格式化 analysis 与 debug 服务模块 / Format analysis and debug services with Black - 不更改任何业务逻辑,仅修复样式检查失败 / No behavioral changes, only style fixes for CI Made-with: Cursor --- ogscope/web/api/analysis/routes.py | 2 +- ogscope/web/api/analysis/services.py | 38 ++++++++++++++++++++-------- ogscope/web/api/debug/services.py | 6 ++++- 3 files changed, 33 insertions(+), 13 deletions(-) diff --git a/ogscope/web/api/analysis/routes.py b/ogscope/web/api/analysis/routes.py index b1557ba..0248ed1 100644 --- a/ogscope/web/api/analysis/routes.py +++ b/ogscope/web/api/analysis/routes.py @@ -10,12 +10,12 @@ from ogscope.web.api.analysis.services import analysis_service from ogscope.web.api.models.schemas import ( - AnalysisReplaceVideoRequest, AnalysisBatchSolveRequest, AnalysisExperimentCreate, AnalysisExtractPreviewRequest, AnalysisJobCreateRequest, AnalysisPresetCreate, + AnalysisReplaceVideoRequest, AnalysisSolveImageRequest, AnalysisSolveVideoFrameRequest, ImportFromDebugRequest, diff --git a/ogscope/web/api/analysis/services.py b/ogscope/web/api/analysis/services.py index 0f687ce..e00b402 100644 --- a/ogscope/web/api/analysis/services.py +++ b/ogscope/web/api/analysis/services.py @@ -188,7 +188,9 @@ async def _try_enter_realtime_gate( now = time.monotonic() interval = max(0.0, float(interval_ms) / 1000.0) async with self._realtime_gate_lock: - state = self._realtime_gate_states.setdefault(source, RealtimeSolveGateState()) + state = self._realtime_gate_states.setdefault( + source, RealtimeSolveGateState() + ) if state.in_flight: return { "success": True, @@ -214,7 +216,9 @@ async def _leave_realtime_gate(self, source: str) -> None: """释放实时解算门禁 / Leave realtime solve gate.""" now = time.monotonic() async with self._realtime_gate_lock: - state = self._realtime_gate_states.setdefault(source, RealtimeSolveGateState()) + state = self._realtime_gate_states.setdefault( + source, RealtimeSolveGateState() + ) state.in_flight = False state.last_finished_mono = now @@ -223,7 +227,9 @@ def is_realtime_source_busy(self, source: str) -> bool: state = self._realtime_gate_states.get(source) return bool(state and state.in_flight) - def _resolve_realtime_interval_ms(self, requested_ms: int | None) -> tuple[int, int]: + def _resolve_realtime_interval_ms( + self, requested_ms: int | None + ) -> tuple[int, int]: """解析实时解算间隔并按系统上下限裁剪 / Resolve realtime interval with server bounds.""" if requested_ms is None: return 0, 0 @@ -348,16 +354,20 @@ def _build_polar_guide(self, row: dict[str, Any]) -> dict[str, Any] | None: c_dec = math.radians(dec_center) t_dec = math.radians(target_dec) d_ra_rad = math.radians(d_ra) - cos_ang = ( - math.sin(c_dec) * math.sin(t_dec) - + math.cos(c_dec) * math.cos(t_dec) * math.cos(d_ra_rad) - ) + cos_ang = math.sin(c_dec) * math.sin(t_dec) + math.cos(c_dec) * math.cos( + t_dec + ) * math.cos(d_ra_rad) cos_ang = max(-1.0, min(1.0, cos_ang)) angular_sep_deg = math.degrees(math.acos(cos_ang)) return { "target_kind": "north_celestial_pole", - "frame_center": {"x": cx, "y": cy, "ra_deg": ra_center, "dec_deg": dec_center}, + "frame_center": { + "x": cx, + "y": cy, + "ra_deg": ra_center, + "dec_deg": dec_center, + }, "target": {"x": tx, "y": ty, "ra_deg": target_ra, "dec_deg": target_dec}, "delta_px": {"dx": dx_px, "dy": dy_px}, "angular_sep_deg": angular_sep_deg, @@ -784,7 +794,9 @@ def replace_transcoded_video( if not old_name or not new_name: raise ValueError("文件名无效 / Invalid filename") if old_name == new_name: - raise ValueError("新旧文件名不能相同 / old_filename and new_filename must differ") + raise ValueError( + "新旧文件名不能相同 / old_filename and new_filename must differ" + ) old_path = self.resolve_upload_path(old_name) new_path = self.resolve_upload_path(new_name) if not old_path.is_file(): @@ -792,7 +804,9 @@ def replace_transcoded_video( if not new_path.is_file(): raise FileNotFoundError("转码文件不存在 / Transcoded file not found") if old_path.suffix.lower() != ".avi": - raise ValueError("仅支持替换 AVI 原始文件 / only AVI source can be replaced") + raise ValueError( + "仅支持替换 AVI 原始文件 / only AVI source can be replaced" + ) if new_path.suffix.lower() != ".mp4": raise ValueError("新文件必须为 MP4 / new file must be MP4") @@ -987,7 +1001,9 @@ def _run() -> dict[str, Any]: overlay_ext["polar_guide"] = None row["overlay_ext"] = overlay_ext row["solve_profile"] = effective_profile - row["t_backend_total_ms"] = round((time.perf_counter() - t_total) * 1000.0, 3) + row["t_backend_total_ms"] = round( + (time.perf_counter() - t_total) * 1000.0, 3 + ) detail_level = getattr(solve_params, "detail_level", None) or "summary" if detail_level != "full": row.pop("tetra", None) diff --git a/ogscope/web/api/debug/services.py b/ogscope/web/api/debug/services.py index 248af6f..2927f3a 100644 --- a/ogscope/web/api/debug/services.py +++ b/ogscope/web/api/debug/services.py @@ -563,7 +563,11 @@ async def record_video(): video_writer.release() recording_task = asyncio.create_task(record_video()) - return {"success": True, "filename": f"{stem}.avi", "path": str(video_path)} + return { + "success": True, + "filename": f"{stem}.avi", + "path": str(video_path), + } except ImportError: raise Exception("OpenCV未安装") except Exception as e: From 7c75f3a2b689b6a5a8d56be90ede1ec55364e1ef Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E6=88=91=E6=98=AF=E5=B0=8F=E4=B8=80=E7=81=B0?= Date: Wed, 1 Apr 2026 00:16:14 +0800 Subject: [PATCH 3/3] =?UTF-8?q?fix:=20=E7=A8=B3=E5=AE=9A=E5=AE=9E=E6=97=B6?= =?UTF-8?q?=E8=A7=A3=E7=AE=97=E8=B6=85=E6=97=B6=E9=97=A8=E7=A6=81=E5=8D=95?= =?UTF-8?q?=E6=B5=8B=20/=20Stabilize=20realtime=20timeout=20gate=20unit=20?= =?UTF-8?q?test?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 将固定 sleep 断言改为限时轮询,兼容不同 Python 版本的调度抖动 / Replace fixed-sleep assertion with bounded polling to handle scheduler jitter across Python versions - 在 SKIPPED_INTERVAL 时短暂重试,最终仅接受 SOLVED,降低 CI 偶发失败 / Retry briefly on SKIPPED_INTERVAL and require SOLVED to reduce flaky CI failures Made-with: Cursor --- tests/unit/test_analysis_api.py | 28 +++++++++++++++++++--------- 1 file changed, 19 insertions(+), 9 deletions(-) diff --git a/tests/unit/test_analysis_api.py b/tests/unit/test_analysis_api.py index 741250e..02132ec 100644 --- a/tests/unit/test_analysis_api.py +++ b/tests/unit/test_analysis_api.py @@ -468,15 +468,25 @@ def _fast(*_args, **_kwargs): } monkeypatch.setattr(analysis_service, "_solve_bgr_to_row", _fast) - time.sleep(0.06) - with image_path.open("rb") as f: - resp2 = client.post( - "/api/analysis/solve/frame_upload", - files={"file": ("frame.jpg", f, "image/jpeg")}, - data={"payload": json.dumps({"solve_interval_ms": 50})}, - ) - assert resp2.status_code == 200 - assert resp2.json().get("gate_status") == "SOLVED" + deadline = time.time() + 1.0 + final_status = None + while time.time() < deadline: + with image_path.open("rb") as f: + resp2 = client.post( + "/api/analysis/solve/frame_upload", + files={"file": ("frame.jpg", f, "image/jpeg")}, + data={"payload": json.dumps({"solve_interval_ms": 50})}, + ) + assert resp2.status_code == 200 + final_status = resp2.json().get("gate_status") + if final_status == "SOLVED": + break + if final_status == "SKIPPED_INTERVAL": + time.sleep(0.02) + continue + break + + assert final_status == "SOLVED" @pytest.mark.unit