From da8ddba84f4ebc89232b1d42cdbf512e23f898bf Mon Sep 17 00:00:00 2001 From: "Richard.Lu" Date: Tue, 1 Sep 2026 23:44:25 +0800 Subject: [PATCH 1/5] =?UTF-8?q?fix:=20use=20capture=20time=20for=20alignme?= =?UTF-8?q?nt=20solves=20/=20=E4=BD=BF=E7=94=A8=E6=8B=8D=E6=91=84=E6=97=B6?= =?UTF-8?q?=E5=88=BB=E6=8F=90=E5=8D=87=E5=AF=B9=E5=87=86=E7=B2=BE=E5=BA=A6?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- docs/contracts/core-rest-v1.md | 4 ++ docs/contracts/core-rest-v1_EN.md | 4 ++ ogscope/core/realtime/service.py | 59 ++++++++++++++++++++++++++-- ogscope/web/api/analysis/services.py | 13 ++++++ ogscope/web/camera_shared.py | 6 ++- tests/unit/test_analysis_api.py | 19 +++++++++ tests/unit/test_realtime_api.py | 20 ++++++++++ 7 files changed, 121 insertions(+), 4 deletions(-) diff --git a/docs/contracts/core-rest-v1.md b/docs/contracts/core-rest-v1.md index bc21a67..e3bdfe3 100644 --- a/docs/contracts/core-rest-v1.md +++ b/docs/contracts/core-rest-v1.md @@ -44,10 +44,14 @@ - `session_id: str` - `state: "running" | "completed" | "stopped"` - `result: object | null` + - `observation_time_utc`:可选,当前图像曝光中点 UTC;天文坐标换算应优先使用该时刻 + - `capture_completed_at_utc`、`capture_exposure_us`:可选抓帧诊断字段 - `last_error: str` - `frame_count: int` - `fullsolve_count: int` +Core 实时分析运行时,开发者相机单帧解算返回 `SKIPPED_BUSY`,避免调试轮询与产品对准争抢相机和 CPU;文件解算不受影响。 + ### 3) Stop Analysis - `POST /api/core/v1/analysis/stop` diff --git a/docs/contracts/core-rest-v1_EN.md b/docs/contracts/core-rest-v1_EN.md index 5923fca..082c1f3 100644 --- a/docs/contracts/core-rest-v1_EN.md +++ b/docs/contracts/core-rest-v1_EN.md @@ -45,10 +45,14 @@ upstream consumers should ignore results from an older session. - `session_id: str` - `state: "running" | "completed" | "stopped"` - `result: object | null` + - `observation_time_utc`: optional exposure-midpoint UTC for the current frame; astronomical coordinate conversion should prefer it + - `capture_completed_at_utc`, `capture_exposure_us`: optional capture diagnostics - `last_error: str` - `frame_count: int` - `fullsolve_count: int` +While Core realtime analysis is active, developer single-frame camera solves return `SKIPPED_BUSY` so debug polling cannot contend with product alignment for camera and CPU resources. File solving is unaffected. + ### 3) Stop Analysis - `POST /api/core/v1/analysis/stop` diff --git a/ogscope/core/realtime/service.py b/ogscope/core/realtime/service.py index 4d1db96..ca8084a 100644 --- a/ogscope/core/realtime/service.py +++ b/ogscope/core/realtime/service.py @@ -8,6 +8,7 @@ import json import time from dataclasses import dataclass +from datetime import UTC, datetime from typing import Any from loguru import logger @@ -57,6 +58,40 @@ def __init__(self) -> None: 1.0 / max(0.01, float(settings.star_analysis_target_fps)), ) + @staticmethod + def _capture_time_payload( + capture_completed_ts: float, + camera_info: dict[str, Any] | None, + ) -> dict[str, Any]: + """Build exposure-midpoint UTC telemetry / 构造曝光中点 UTC 遥测。""" + try: + completed_ts = float(capture_completed_ts) + if completed_ts <= 0: + return {} + info = camera_info if isinstance(camera_info, dict) else {} + exposure_us = max( + 0, + int(info.get("actual_exposure_us", info.get("exposure_us", 0)) or 0), + ) + midpoint_ts = completed_ts - exposure_us / 2_000_000.0 + completed_iso = ( + datetime.fromtimestamp(completed_ts, UTC) + .isoformat() + .replace("+00:00", "Z") + ) + midpoint_iso = ( + datetime.fromtimestamp(midpoint_ts, UTC) + .isoformat() + .replace("+00:00", "Z") + ) + except (OSError, OverflowError, TypeError, ValueError): + return {} + return { + "observation_time_utc": midpoint_iso, + "capture_completed_at_utc": completed_iso, + "capture_exposure_us": exposure_us, + } + async def start( self, hint_ra_deg: float | None = None, @@ -152,7 +187,7 @@ async def _loop(self) -> None: # 必须与共享预览走同一套读锁 + 线程卸载,禁止在事件循环线程里直接 capture_array # Must share the same read lock as shared preview; never call capture_array on the event-loop thread. try: - frame, frame_id, _ts = await manager.get_raw_frame() + frame, frame_id, frame_ts = await manager.get_raw_frame() except RuntimeError: await asyncio.sleep(0.1) continue @@ -165,6 +200,13 @@ async def _loop(self) -> None: last_frame_id = frame_id last_started_mono = time.monotonic() self.state.frame_count += 1 + try: + camera_info = cam.get_camera_info() + except ( + Exception + ): # noqa: BLE001 - timing remains optional / 时间遥测可降级 + camera_info = {} + capture_time = self._capture_time_payload(frame_ts, camera_info) use_fullsolve = ( self.state.frame_count % self._fullsolve_interval == 0 @@ -178,7 +220,7 @@ async def _loop(self) -> None: self._solve_frame_sync, frame, ) - self._apply_solve_result(solved) + self._apply_solve_result(solved, capture_time=capture_time) self.state.fullsolve_count += 1 self._log_event( "fullsolve_finished", @@ -191,6 +233,8 @@ async def _loop(self) -> None: t_extract_ms=solved.t_extract_ms, t_preprocess_ms=solved.t_preprocess_ms, wall_ms=int((time.monotonic() - solve_started) * 1000), + rmse_arcsec=solved.rmse_arcsec, + observation_time_utc=capture_time.get("observation_time_utc"), ) # ``solve_from_bgr_frame`` is the authoritative production # image pipeline. Keep only a sentinel here; StarExtractor @@ -236,9 +280,18 @@ def _solve_frame_sync( solve_timeout_ms=self._solve_timeout_ms, ) - def _apply_solve_result(self, solved: SolveResult) -> None: + def _apply_solve_result( + self, + solved: SolveResult, + *, + capture_time: dict[str, Any] | None = None, + ) -> None: """写入解算结果 / Persist solve result""" row = solved.to_dict() + if capture_time: + # Optional additive Core fields keep old consumers compatible. / + # 可选增量字段保持旧版 Core 消费方兼容。 + row.update(capture_time) attach_sensor_prediction(row, self._solve_context) self.state.last_result = row # A later completed frame supersedes a transient capture/solve exception. diff --git a/ogscope/web/api/analysis/services.py b/ogscope/web/api/analysis/services.py index 5667e3e..229ec05 100644 --- a/ogscope/web/api/analysis/services.py +++ b/ogscope/web/api/analysis/services.py @@ -1285,6 +1285,19 @@ async def solve_video_frame( ) -> dict[str, Any]: """相机或视频文件单帧解算 / Single-frame solve from camera or video file.""" if body.source == "camera": + # Product/Core alignment owns the live camera solver while active; developer + # polling must yield instead of adding capture and CPU latency. / + # 产品级 Core 对准运行时独占实时解算;开发者轮询必须让路,避免增加抓帧和 CPU 延迟。 + from ogscope.core.realtime import realtime_solve_service + + if realtime_solve_service.state.running: + return { + "success": True, + "input_name": body.input_name or "", + "gate_status": "SKIPPED_BUSY", + "gate_reason": "core realtime analysis is active", + "next_allowed_in_ms": 0, + } try: from ogscope.web.api.debug.services import is_recording_active diff --git a/ogscope/web/camera_shared.py b/ogscope/web/camera_shared.py index 409ed88..de600e7 100644 --- a/ogscope/web/camera_shared.py +++ b/ogscope/web/camera_shared.py @@ -601,6 +601,10 @@ async def _grabber_loop(self) -> None: ): self._inflight_read_future = None if frame is not None: + # Timestamp the completed sensor read before JPEG work so cached raw + # frames retain capture time rather than encoder completion time. / + # 在 JPEG 编码前记录传感器读帧完成时刻,避免缓存 raw 帧误用编码完成时间。 + capture_completed_ts = time.time() self._consecutive_grab_failures = 0 encode_t0 = time.perf_counter() encoded = await loop.run_in_executor( @@ -620,7 +624,7 @@ async def _grabber_loop(self) -> None: # By default do not retain raw to avoid dual large buffers; set env to keep. self._latest_raw = frame if self._keep_raw_cache else None self._latest_jpeg = jpeg - self._latest_ts = time.time() + self._latest_ts = capture_completed_ts self._latest_w = w self._latest_h = h self._last_jpeg_encoder = encoded.encoder diff --git a/tests/unit/test_analysis_api.py b/tests/unit/test_analysis_api.py index d2dc38b..8e242de 100644 --- a/tests/unit/test_analysis_api.py +++ b/tests/unit/test_analysis_api.py @@ -559,6 +559,25 @@ def test_analysis_camera_solve_skipped_when_recording_active( assert "recording" in str(data.get("gate_reason", "")) +@pytest.mark.unit +def test_analysis_camera_solve_yields_to_core_realtime( + client, temp_analysis_dir, mock_plate_solve, monkeypatch +): + """正式 Core 解算运行时开发者相机解算让路 / Developer solve yields to Core realtime.""" + from ogscope.core.realtime import realtime_solve_service + + monkeypatch.setattr(realtime_solve_service.state, "running", True) + resp = client.post( + "/api/dev/analysis/solve/frame", + json={"source": "camera"}, + ) + + assert resp.status_code == 200 + data = resp.json() + assert data["gate_status"] == "SKIPPED_BUSY" + assert "core realtime" in data["gate_reason"] + + @pytest.mark.unit def test_analysis_replace_transcoded_video_updates_sidecar( client, temp_analysis_dir, tmp_path: Path diff --git a/tests/unit/test_realtime_api.py b/tests/unit/test_realtime_api.py index 19ccca0..ea119ad 100644 --- a/tests/unit/test_realtime_api.py +++ b/tests/unit/test_realtime_api.py @@ -4,6 +4,7 @@ import asyncio from dataclasses import dataclass +from datetime import UTC, datetime import numpy as np import pytest @@ -21,6 +22,25 @@ def get_video_frame(self): frame[60, 100] = (255, 255, 255) return frame + def get_camera_info(self): + return {"actual_exposure_us": 1_000_000} + + +@pytest.mark.unit +def test_capture_time_payload_uses_exposure_midpoint() -> None: + """解算时刻取曝光中点而非解算完成时刻 / Use exposure midpoint, not solve completion.""" + from ogscope.core.realtime.service import RealtimeSolveService + + completed = datetime(2026, 9, 1, 15, 0, 1, tzinfo=UTC).timestamp() + payload = RealtimeSolveService._capture_time_payload( + completed, + {"actual_exposure_us": 1_000_000}, + ) + + assert payload["observation_time_utc"] == "2026-09-01T15:00:00.500000Z" + assert payload["capture_completed_at_utc"] == "2026-09-01T15:00:01Z" + assert payload["capture_exposure_us"] == 1_000_000 + @pytest.mark.unit def test_realtime_solver_status_endpoints(client, monkeypatch, mock_plate_solve): From f798cdddeebf5b8822d15b9c46b37f70f3b75da9 Mon Sep 17 00:00:00 2001 From: "Richard.Lu" Date: Wed, 2 Sep 2026 15:28:40 +0800 Subject: [PATCH 2/5] =?UTF-8?q?feat(debug):=20=E5=90=8C=E6=98=BE=E8=A7=92?= =?UTF-8?q?=E5=88=86=E4=B8=8E=E8=A7=92=E7=A7=92=20/=20show=20arcminutes=20?= =?UTF-8?q?and=20arcseconds=20together?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 仅更新开发者分析后台的 RMSE 展示与历史记录,不修改用户首页。 / Update RMSE and history only in the developer analysis console without changing the user home page. --- web/spa/src/apps/lab/AnalysisLabApp.tsx | 3 +- web/spa/src/apps/lab/LabHistoryView.tsx | 5 +- web/spa/src/apps/lab/SolveFooterSummary.tsx | 3 +- web/spa/src/shared/utils/solveDisplay.ts | 6 +++ .../analysis-lab/assets/analysis-C66CAkmp.js | 46 +++++++++++++++++++ .../analysis-lab/assets/analysis-fOaEdJ6w.js | 46 ------------------- web/static/analysis-lab/index.html | 2 +- 7 files changed, 61 insertions(+), 50 deletions(-) create mode 100644 web/static/analysis-lab/assets/analysis-C66CAkmp.js delete mode 100644 web/static/analysis-lab/assets/analysis-fOaEdJ6w.js diff --git a/web/spa/src/apps/lab/AnalysisLabApp.tsx b/web/spa/src/apps/lab/AnalysisLabApp.tsx index 6b91f11..60e6755 100644 --- a/web/spa/src/apps/lab/AnalysisLabApp.tsx +++ b/web/spa/src/apps/lab/AnalysisLabApp.tsx @@ -45,6 +45,7 @@ import { useI18n } from "@shared/i18n/I18nProvider"; import { buildMetaCaptionRows } from "@shared/utils/metaCaption"; import { formatDateTime, formatFileSize } from "@shared/utils/format"; import { + formatArcminArcsecFromArcsec, formatAngleDeg, formatProbLine, parseSolveResult, @@ -1808,7 +1809,7 @@ export default function AnalysisLabApp() { {t("lab.metric.rmse")} {" "} - {solveHud.rmseArcsec.toFixed(2)}″ + {formatArcminArcsecFromArcsec(solveHud.rmseArcsec)} )} diff --git a/web/spa/src/apps/lab/LabHistoryView.tsx b/web/spa/src/apps/lab/LabHistoryView.tsx index ec7adbe..a091004 100644 --- a/web/spa/src/apps/lab/LabHistoryView.tsx +++ b/web/spa/src/apps/lab/LabHistoryView.tsx @@ -4,6 +4,7 @@ import { History } from "lucide-react"; import { experimentAssetUrl, exportExperiments } from "@dev-api/analysis"; import { useI18n } from "@shared/i18n/I18nProvider"; import { formatDateTime } from "@shared/utils/format"; +import { formatArcminArcsecFromArcsec } from "@shared/utils/solveDisplay"; export function LabHistoryView({ historyQ, @@ -120,7 +121,9 @@ export function LabHistoryView({
{t("history.preset")}: {String(row.preset_label)} · {t("history.metrics")}:{" "} matches={String(metrics?.matches ?? "—")} rmse= - {String(metrics?.rmse_arcsec ?? "—")} + {formatArcminArcsecFromArcsec( + typeof metrics?.rmse_arcsec === "number" ? metrics.rmse_arcsec : null, + )}
diff --git a/web/spa/src/apps/lab/SolveFooterSummary.tsx b/web/spa/src/apps/lab/SolveFooterSummary.tsx index b44a2ba..e704421 100644 --- a/web/spa/src/apps/lab/SolveFooterSummary.tsx +++ b/web/spa/src/apps/lab/SolveFooterSummary.tsx @@ -1,4 +1,5 @@ import { + formatArcminArcsecFromArcsec, formatAngleDeg, formatProbLine, parseSolveResult, @@ -79,7 +80,7 @@ export function SolveFooterSummary({
{t("lab.metric.rmse")}
- {s.rmseArcsec.toFixed(2)}″ + {formatArcminArcsecFromArcsec(s.rmseArcsec)}
)} diff --git a/web/spa/src/shared/utils/solveDisplay.ts b/web/spa/src/shared/utils/solveDisplay.ts index 7487cf6..1891a87 100644 --- a/web/spa/src/shared/utils/solveDisplay.ts +++ b/web/spa/src/shared/utils/solveDisplay.ts @@ -54,6 +54,12 @@ export function formatAngleDeg(v: number | null): string { return `${v.toFixed(4)}°`; } +/** 同时显示角分与角秒,避免调试时误读单位 / Show arcminutes and arcseconds together for unambiguous debugging. */ +export function formatArcminArcsecFromArcsec(value: number | null | undefined): string { + if (value == null || !Number.isFinite(value)) return "—"; + return `${(value / 60).toFixed(2)}′ / ${value.toFixed(1)}″`; +} + /** * Tetra3 `Prob`:假阳性概率(越低越可信)/ Tetra3 Prob = false-positive probability (lower is better). * diff --git a/web/static/analysis-lab/assets/analysis-C66CAkmp.js b/web/static/analysis-lab/assets/analysis-C66CAkmp.js new file mode 100644 index 0000000..13d1795 --- /dev/null +++ b/web/static/analysis-lab/assets/analysis-C66CAkmp.js @@ -0,0 +1,46 @@ +var Us=Object.defineProperty;var Ba=e=>{throw TypeError(e)};var Bs=(e,s,n)=>s in e?Us(e,s,{enumerable:!0,configurable:!0,writable:!0,value:n}):e[s]=n;var O=(e,s,n)=>Bs(e,typeof s!="symbol"?s+"":s,n),Ha=(e,s,n)=>s.has(e)||Ba("Cannot "+n);var h=(e,s,n)=>(Ha(e,s,"read from private field"),n?n.call(e):s.get(e)),te=(e,s,n)=>s.has(e)?Ba("Cannot add the same private member more than once"):s instanceof WeakSet?s.add(e):s.set(e,n),Oe=(e,s,n,r)=>(Ha(e,s,"write to private field"),r?r.call(e,n):s.set(e,n),n);import{j as t,r as i,a as Hs,R as zs}from"./client-D1ZVDB-N.js";import{c as Y,u as Te,T as ns,I as Ws}from"./index-BoSUzpvv.js";import{R as Vs}from"./refresh-cw-B1jE4hda.js";import{R as za}from"./rotate-ccw-D6okH6-S.js";/** + * @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 Fe=Y("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 Gs=Y("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 Wa=Y("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 Ks=Y("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 qs=Y("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 st=Y("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 Js=Y("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 Va=Y("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 Ga=Y("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"}]]),_="/api/dev";async function W(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 Zs(){const e=await fetch(`${_}/analysis/uploads`);if(!e.ok)throw new Error(await e.text());return e.json()}async function Ys(e,s){const n=new URLSearchParams;s!=null&&s.deleteExperiments&&n.set("delete_experiments","true");const r=n.toString(),o=await fetch(`${_}/analysis/uploads/${encodeURIComponent(e)}${r?`?${r}`:""}`,{method:"DELETE"});if(!o.ok)throw new Error(await o.text());return await o.json().catch(()=>({}))}async function Bt(e,s="analysis_upload"){const n=new FormData;n.append("file",e),n.append("source",s);const r=await fetch(`${_}/analysis/upload`,{method:"POST",body:n}),o=await W(r);if(!r.ok)throw new Error(String(o.detail||r.status));return o}async function Xs(e){const s=await fetch(`${_}/analysis/uploads/import_from_debug`,{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({filename:e})}),n=await W(s);if(!s.ok)throw new Error(String(n.detail||s.status));return n}async function Ka(e){const s=await fetch(`${_}/analysis/uploads/replace_video`,{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify(e)}),n=await W(s);if(!s.ok)throw new Error(String(n.detail||s.status));return n}async function Qs(e,s){const n=await fetch(`${_}/analysis/solve/image`,{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({input_name:e,...s})}),r=await W(n);if(!n.ok)throw new Error(String(r.detail||n.status));return r}async function en(e,s){const n=await fetch(`${_}/analysis/solve/batch`,{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({input_name:e,runs:s})}),r=await W(n);if(!n.ok)throw new Error(String(r.detail||n.status));return r}async function Ht(e){const s=await fetch(`${_}/analysis/presets?scope=${e}`);if(!s.ok)throw new Error(await s.text());return s.json()}async function tn(e,s){const n=await fetch(`${_}/analysis/presets`,{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({name:e,params:s})}),r=await W(n);if(!n.ok)throw new Error(String(r.detail||n.status));return r}async function zt(e){const s=await fetch(`${_}/analysis/experiments`,{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify(e)}),n=await W(s);if(!s.ok)throw new Error(String(n.detail||s.status));return n}async function an(e){const s=await fetch(`${_}/analysis/experiments/${encodeURIComponent(e)}`,{method:"DELETE"});if(!s.ok)throw new Error(await s.text())}async function Wt(e,s,n=30){const r=new URLSearchParams({page:String(s),page_size:String(n)});e&&r.set("q",e);const o=await fetch(`${_}/analysis/experiments?${r}`);if(!o.ok)throw new Error(await o.text());return o.json()}async function sn(){const e=await fetch(`${_}/debug/files`);if(!e.ok)throw new Error(await e.text());return e.json()}function nn(e){return`${_}/debug/files/${encodeURIComponent(e)}`}async function qa(e){const s=await fetch(`${_}/debug/files/${encodeURIComponent(e)}/info`),n=await W(s);if(!s.ok)throw new Error(String(n.detail||s.status));return n}async function rn(e){const s=await fetch(`${_}/analysis/uploads/${encodeURIComponent(e)}/info`),n=await W(s);if(!s.ok)throw new Error(String(n.detail||s.status));return n}function Vt(e){return`${_}/analysis/uploads/file?filename=${encodeURIComponent(e)}`}async function Ja(e){const s=await fetch(`${_}/analysis/experiments/export?format=${e}`);if(!s.ok)throw new Error(await s.text());return s.text()}async function ln(e){const s=await fetch(`${_}/analysis/uploads/${encodeURIComponent(e)}/experiment_count`);if(!s.ok)throw new Error(await s.text());return s.json()}async function on(){const e=await fetch(`${_}/analysis/settings`);if(!e.ok)throw new Error(await e.text());return e.json()}async function cn(e){const s=await fetch(`${_}/analysis/solve/frame`,{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify(e)}),n=await W(s);if(!s.ok)throw new Error(String(n.detail||s.status));return n}async function dn(e,s){const n=new FormData;n.append("file",e,"frame.jpg"),n.append("payload",JSON.stringify(s));const r=await fetch(`${_}/analysis/solve/frame_upload`,{method:"POST",body:n}),o=await W(r);if(!r.ok)throw new Error(String(o.detail||r.status));return o}function un(e){return`${_}/analysis/experiments/${encodeURIComponent(e)}/asset`}async function mn(){const e=await fetch("/api/dev/system/info");if(!e.ok)throw new Error(await e.text());return e.json()}function rs(e,s,n,r,o){var g,y,M,d;if(!r)return;if(e.clearRect(0,0,s,n),o.all&&Array.isArray(r.stars_all_centroids)){e.fillStyle="rgba(156, 163, 175, 0.85)";for(const m of r.stars_all_centroids)e.beginPath(),e.arc(m.x,m.y,2.4,0,Math.PI*2),e.fill()}if(o.rejected&&Array.isArray(r.stars_rejected_centroids)){e.strokeStyle="rgba(248, 113, 113, 0.95)",e.lineWidth=1.6;for(const m of r.stars_rejected_centroids)e.beginPath(),e.moveTo(m.x-4,m.y-4),e.lineTo(m.x+4,m.y+4),e.moveTo(m.x+4,m.y-4),e.lineTo(m.x-4,m.y+4),e.stroke()}if(o.pattern&&Array.isArray(r.stars_pattern)){e.strokeStyle="rgba(251, 146, 60, 0.95)",e.lineWidth=2;for(const m of r.stars_pattern)e.beginPath(),e.arc(m.x,m.y,6,0,Math.PI*2),e.stroke()}if(o.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 m of r.stars_matched)e.beginPath(),e.arc(m.x,m.y,7,0,Math.PI*2),e.stroke(),m.mag!=null&&e.fillText(`m${Number(m.mag).toFixed(1)}`,m.x+4,m.y-4)}const u=r.overlay_ext;if(Array.isArray(u==null?void 0:u.labels_topn)&&u.labels_topn.length>0){e.fillStyle="rgba(96, 165, 250, 0.95)",e.font="12px system-ui, sans-serif";for(const m of u.labels_topn){if(typeof(m==null?void 0:m.x)!="number"||typeof(m==null?void 0:m.y)!="number")continue;const b=typeof m.mag=="number"?` m${m.mag.toFixed(1)}`:"",x=`${m.name??"Star"}${b}`;e.fillText(x,m.x+8,m.y+14)}}const p=u==null?void 0:u.polar_guide;if(p&&typeof((g=p.frame_center)==null?void 0:g.x)=="number"&&typeof((y=p.frame_center)==null?void 0:y.y)=="number"&&typeof((M=p.target)==null?void 0:M.x)=="number"&&typeof((d=p.target)==null?void 0:d.y)=="number"){const m=p.frame_center.x,b=p.frame_center.y,x=p.target.x,I=p.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(m,b),e.lineTo(x,I),e.stroke(),e.beginPath(),e.arc(x,I,6,0,Math.PI*2),e.stroke();const R=typeof p.angular_sep_deg=="number"?p.angular_sep_deg.toFixed(2):"-";e.font="12px system-ui, sans-serif",e.fillText(`Polar ${R}deg`,x+10,I-6)}}function Za(e,s,n,r){if(!n)return;const o=s.naturalWidth||1,u=s.naturalHeight||1;e.width=o,e.height=u,e.style.width=`${s.clientWidth}px`,e.style.height=`${s.clientHeight}px`;const p=e.getContext("2d");p&&rs(p,o,u,n,r)}function xn(e,s,n,r){if(!n)return;const o=s.videoWidth||1,u=s.videoHeight||1;if(o<2||u<2)return;e.width=o,e.height=u,e.style.width=`${s.clientWidth}px`,e.style.height=`${s.clientHeight}px`;const p=e.getContext("2d");p&&rs(p,o,u,n,r)}var N;(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"})(N||(N={}));const pn=(()=>{let e=0;return()=>e++})(),fn=new Error("ffmpeg is not loaded, call `await ffmpeg.load()` first"),hn=new Error("called FFmpeg.terminate()");var A,ae,Z,ue,me,ot,P;class bn{constructor(){te(this,A,null);te(this,ae,{});te(this,Z,{});te(this,ue,[]);te(this,me,[]);O(this,"loaded",!1);te(this,ot,()=>{h(this,A)&&(h(this,A).onmessage=({data:{id:s,type:n,data:r}})=>{switch(n){case N.LOAD:this.loaded=!0,h(this,ae)[s](r);break;case N.MOUNT:case N.UNMOUNT:case N.EXEC:case N.FFPROBE:case N.WRITE_FILE:case N.READ_FILE:case N.DELETE_FILE:case N.RENAME:case N.CREATE_DIR:case N.LIST_DIR:case N.DELETE_DIR:h(this,ae)[s](r);break;case N.LOG:h(this,ue).forEach(o=>o(r));break;case N.PROGRESS:h(this,me).forEach(o=>o(r));break;case N.ERROR:h(this,Z)[s](r);break}delete h(this,ae)[s],delete h(this,Z)[s]})});te(this,P,({type:s,data:n},r=[],o)=>h(this,A)?new Promise((u,p)=>{const g=pn();h(this,A)&&h(this,A).postMessage({id:g,type:s,data:n},r),h(this,ae)[g]=u,h(this,Z)[g]=p,o==null||o.addEventListener("abort",()=>{p(new DOMException(`Message # ${g} was aborted`,"AbortError"))},{once:!0})}):Promise.reject(fn));O(this,"load",({classWorkerURL:s,...n}={},{signal:r}={})=>(h(this,A)||(Oe(this,A,s?new Worker(new URL(s,import.meta.url),{type:"module"}):new Worker(new URL("/static/analysis-lab/assets/worker-BAOIWoxA.js",import.meta.url),{type:"module"})),h(this,ot).call(this)),h(this,P).call(this,{type:N.LOAD,data:n},void 0,r)));O(this,"exec",(s,n=-1,{signal:r}={})=>h(this,P).call(this,{type:N.EXEC,data:{args:s,timeout:n}},void 0,r));O(this,"ffprobe",(s,n=-1,{signal:r}={})=>h(this,P).call(this,{type:N.FFPROBE,data:{args:s,timeout:n}},void 0,r));O(this,"terminate",()=>{const s=Object.keys(h(this,Z));for(const n of s)h(this,Z)[n](hn),delete h(this,Z)[n],delete h(this,ae)[n];h(this,A)&&(h(this,A).terminate(),Oe(this,A,null),this.loaded=!1)});O(this,"writeFile",(s,n,{signal:r}={})=>{const o=[];return n instanceof Uint8Array&&o.push(n.buffer),h(this,P).call(this,{type:N.WRITE_FILE,data:{path:s,data:n}},o,r)});O(this,"mount",(s,n,r)=>{const o=[];return h(this,P).call(this,{type:N.MOUNT,data:{fsType:s,options:n,mountPoint:r}},o)});O(this,"unmount",s=>{const n=[];return h(this,P).call(this,{type:N.UNMOUNT,data:{mountPoint:s}},n)});O(this,"readFile",(s,n="binary",{signal:r}={})=>h(this,P).call(this,{type:N.READ_FILE,data:{path:s,encoding:n}},void 0,r));O(this,"deleteFile",(s,{signal:n}={})=>h(this,P).call(this,{type:N.DELETE_FILE,data:{path:s}},void 0,n));O(this,"rename",(s,n,{signal:r}={})=>h(this,P).call(this,{type:N.RENAME,data:{oldPath:s,newPath:n}},void 0,r));O(this,"createDir",(s,{signal:n}={})=>h(this,P).call(this,{type:N.CREATE_DIR,data:{path:s}},void 0,n));O(this,"listDir",(s,{signal:n}={})=>h(this,P).call(this,{type:N.LIST_DIR,data:{path:s}},void 0,n));O(this,"deleteDir",(s,{signal:n}={})=>h(this,P).call(this,{type:N.DELETE_DIR,data:{path:s}},void 0,n))}on(s,n){s==="log"?h(this,ue).push(n):s==="progress"&&h(this,me).push(n)}off(s,n){s==="log"?Oe(this,ue,h(this,ue).filter(r=>r!==n)):s==="progress"&&Oe(this,me,h(this,me).filter(r=>r!==n))}}A=new WeakMap,ae=new WeakMap,Z=new WeakMap,ue=new WeakMap,me=new WeakMap,ot=new WeakMap,P=new WeakMap;var Ya;(function(e){e.MEMFS="MEMFS",e.NODEFS="NODEFS",e.NODERAWFS="NODERAWFS",e.IDBFS="IDBFS",e.WORKERFS="WORKERFS",e.PROXYFS="PROXYFS"})(Ya||(Ya={}));const vn=new Error("failed to get response body reader"),gn=new Error("failed to complete download"),yn="Content-Length",wn=e=>new Promise((s,n)=>{const r=new FileReader;r.onload=()=>{const{result:o}=r;o instanceof ArrayBuffer?s(new Uint8Array(o)):s(new Uint8Array)},r.onerror=o=>{var u,p;n(Error(`File could not be read! Code=${((p=(u=o==null?void 0:o.target)==null?void 0:u.error)==null?void 0:p.code)||-1}`))},r.readAsArrayBuffer(e)}),jn=async e=>{let s;if(typeof e=="string")/data:_data\/([a-zA-Z]*);base64,([^"]*)/.test(e)?s=atob(e.split(",")[1]).split("").map(n=>n.charCodeAt(0)):s=await(await fetch(e)).arrayBuffer();else if(e instanceof URL)s=await(await fetch(e)).arrayBuffer();else if(e instanceof File||e instanceof Blob)s=await wn(e);else return new Uint8Array;return new Uint8Array(s)},Nn=async(e,s)=>{var o;const n=await fetch(e);let r;try{const u=parseInt(n.headers.get(yn)||"-1"),p=(o=n.body)==null?void 0:o.getReader();if(!p)throw vn;const g=[];let y=0;for(;;){const{done:m,value:b}=await p.read(),x=b?b.length:0;if(m){if(u!=-1&&u!==y)throw gn;s&&s({url:e,total:u,received:y,delta:x,done:m});break}g.push(b),y+=x,s&&s({url:e,total:u,received:y,delta:x,done:m})}const M=new Uint8Array(y);let d=0;for(const m of g)M.set(m,d),d+=m.length;r=M.buffer}catch(u){console.log("failed to send download progress event: ",u),r=await n.arrayBuffer()}return r},Xa=async(e,s,n=!1,r)=>{const o=n?await Nn(e,r):await(await fetch(e)).arrayBuffer(),u=new Blob([o],{type:s});return URL.createObjectURL(u)};let Gt=null,Pe=null,F;async function _n(){if(Gt)return Gt;if(Pe)return Pe;Pe=(async()=>{const e=new bn;e.on("progress",({progress:o})=>{F==null||F(o,"transcoding")}),F==null||F(.01,"loading_ffmpeg");const s="https://unpkg.com/@ffmpeg/core@0.12.6/dist/esm",n=await Xa(`${s}/ffmpeg-core.js`,"text/javascript");F==null||F(.04,"loading_ffmpeg");const r=await Xa(`${s}/ffmpeg-core.wasm`,"application/wasm");return F==null||F(.07,"loading_ffmpeg"),await e.load({coreURL:n,wasmURL:r}),Gt=e,e})();try{return await Pe}finally{Pe=null}}async function kn(e){const s=URL.createObjectURL(e);try{return await new Promise(r=>{const o=document.createElement("video");o.preload="metadata",o.onloadedmetadata=()=>{const u=Number(o.duration);r(Number.isFinite(u)&&u>0?u:null)},o.onerror=()=>r(null),o.src=s})}finally{URL.revokeObjectURL(s)}}async function Qa(e,s){const n=F;F=s;try{const r=await _n(),o="input.avi",u="output.mp4";s==null||s(.1,"writing_input"),await r.writeFile(o,await jn(e)),await r.exec(["-i",o,"-c:v","libx264","-preset","veryfast","-crf","24","-pix_fmt","yuv420p","-movflags","+faststart","-an",u]);const p=await r.readFile(u);s==null||s(.98,"packing_output");const g=new Blob([p],{type:"video/mp4"}),y=new File([g],e.name.replace(/\.avi$/i,".mp4"),{type:"video/mp4"});await r.deleteFile(o),await r.deleteFile(u);const M=await kn(y);return s==null||s(1,"done"),{file:y,duration_s:M}}finally{F=n}}function Zt(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 $e(e,s){if(!e)return"—";try{const n=new Date(e);return new Intl.DateTimeFormat(s==="en"?"en-GB":"zh-CN",{dateStyle:"short",timeStyle:"medium"}).format(n)}catch{return e}}function de(e){return e==null?null:typeof e=="string"&&e.trim()?e.trim():typeof e=="number"&&!Number.isNaN(e)?String(e):null}function Sn(e){return typeof e!="number"||e<=0?null:`${(e/1e6).toFixed(4)} s`}function En(e,s){if(!e)return[];const n=[],r=Sn(e.exposure_us);r&&n.push({key:"meta.exposure",value:r});const o=de(e.analogue_gain),u=de(e.digital_gain);if(o||u){const b=[o?`A ${o}`:"",u?`D ${u}`:""].filter(Boolean);n.push({key:"meta.gain",value:b.join(" · ")})}const p=de(e.fps);p&&n.push({key:"meta.fps",value:p});const g=de(e.sensor);g&&n.push({key:"meta.sensor",value:g});const y=de(e.color_mode);y&&n.push({key:"meta.colorMode",value:y});const M=de(e.resolution);M&&n.push({key:"meta.outputResolution",value:M});const d=de(e.modified);d&&n.push({key:"meta.fileTime",value:$e(d,s)});const m=e.size;return typeof m=="number"&&m>0&&n.push({key:"meta.fileSize",value:Zt(m)}),n}function ls(e){const s=n=>typeof n=="number"&&!Number.isNaN(n)?n:null;return e?{tSolveMs:s(e.t_solve_ms),tExtractMs:s(e.t_extract_ms),tPreprocessMs:s(e.t_preprocess_ms),tOpenDecodeMs:s(e.t_open_decode_ms),tBackendTotalMs:s(e.t_backend_total_ms),raDeg:s(e.ra_deg),decDeg:s(e.dec_deg),matches:s(e.matches),rmseArcsec:s(e.rmse_arcsec),prob:s(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 it(e){return e==null?"—":`${e.toFixed(4)}°`}function Yt(e){return e==null||!Number.isFinite(e)?"—":`${(e/60).toFixed(2)}′ / ${e.toFixed(1)}″`}function is(e){if(e==null||typeof e!="number"||Number.isNaN(e)||e<0)return null;if(e>=1)return 0;if(e===0)return 100;const s=(1-e)*100;if(s<=100-1e-12)return Math.min(100,Math.max(0,s));const n=-Math.log10(Math.max(e,Number.MIN_VALUE)),r=14,u=Math.min(1,Math.max(0,(n-r)/(50-r)));return Math.min(100,Math.max(0,70+30*u))}function Rn(e){const s=is(e);return s==null?"—":`${s.toFixed(1)}%`}function Mn(e){if(e==null)return"—";if(typeof e=="number"&&!Number.isNaN(e)){const s=Math.abs(e);return s>0&&s<.001?e.toExponential(4):s>=0&&s<=1?`${(e*100).toFixed(4)}%`:String(e)}return String(e)}function ye(e,s){const n=s==null?void 0:s.tetra,r=n?n.Prob??n.prob:void 0,o=Rn(e),u=r!=null?Mn(r):null;return{line:o,rawLine:u}}function nt({result:e,t:s,roundTripMs:n}){const r=ls(e??void 0);return e?t.jsxs("div",{className:"mt-2 space-y-2 text-[10px]",children:[t.jsxs("div",{className:"grid grid-cols-2 gap-2",children:[r.tBackendTotalMs!=null&&t.jsxs("div",{children:[t.jsx("div",{className:"text-on-surface-variant",children:s("lab.metric.backendTotalMs")}),t.jsxs("div",{className:"font-semibold tabular-nums text-on-surface",children:[r.tBackendTotalMs.toFixed(0)," ms"]})]}),r.tSolveMs!=null&&t.jsxs("div",{children:[t.jsx("div",{className:"text-on-surface-variant",children:s("lab.metric.solveComputeMs")}),t.jsxs("div",{className:"font-semibold tabular-nums text-on-surface",children:[r.tSolveMs.toFixed(0)," ms"]})]}),r.tOpenDecodeMs!=null&&t.jsxs("div",{children:[t.jsx("div",{className:"text-on-surface-variant",children:s("lab.metric.openDecodeMs")}),t.jsxs("div",{className:"font-semibold tabular-nums text-on-surface",children:[r.tOpenDecodeMs.toFixed(0)," ms"]})]}),r.tPreprocessMs!=null&&t.jsxs("div",{children:[t.jsx("div",{className:"text-on-surface-variant",children:s("lab.metric.preprocessMs")}),t.jsxs("div",{className:"font-semibold tabular-nums text-on-surface",children:[r.tPreprocessMs.toFixed(0)," ms"]})]}),r.tExtractMs!=null&&t.jsxs("div",{children:[t.jsx("div",{className:"text-on-surface-variant",children:s("lab.metric.extractMs")}),t.jsxs("div",{className:"font-semibold tabular-nums text-on-surface",children:[r.tExtractMs.toFixed(0)," ms"]})]}),n!=null&&t.jsxs("div",{children:[t.jsx("div",{className:"text-on-surface-variant",children:s("lab.metric.solveRoundTripMs")}),t.jsxs("div",{className:"font-semibold tabular-nums text-on-surface",children:[n.toFixed(0)," ms"]})]}),r.matches!=null&&t.jsxs("div",{children:[t.jsx("div",{className:"text-on-surface-variant",children:s("lab.metric.matches")}),t.jsx("div",{className:"font-semibold tabular-nums text-on-surface",children:r.matches})]}),r.rmseArcsec!=null&&t.jsxs("div",{children:[t.jsx("div",{className:"text-on-surface-variant",children:s("lab.metric.rmse")}),t.jsx("div",{className:"font-semibold tabular-nums text-on-surface",children:Yt(r.rmseArcsec)})]}),r.prob!=null&&t.jsxs("div",{className:"col-span-2",children:[t.jsx("div",{className:"text-on-surface-variant",children:s("lab.metric.prob")}),t.jsx("div",{className:"font-semibold text-on-surface",children:ye(r.prob,e).line}),t.jsx("p",{className:"mt-0.5 text-[9px] leading-snug text-on-surface-variant/90",children:s("lab.metric.probHelp")}),ye(r.prob,e).rawLine&&t.jsxs("div",{className:"mt-1 text-[9px] text-on-surface-variant",children:[s("lab.metric.probRaw"),": ",ye(r.prob,e).rawLine,t.jsx("p",{className:"mt-0.5 text-[8px] leading-snug opacity-90",children:s("lab.metric.probRawHelp")})]})]})]}),e.centroid_quality!=null&&typeof e.centroid_quality=="object"&&t.jsxs("div",{className:"rounded border border-outline-variant/25 bg-surface-container-high/40 p-2",children:[t.jsxs("div",{className:"text-[9px] font-medium text-on-surface-variant",children:[s("lab.centroidQualityTitle"),typeof e.centroid_quality.level=="number"?` · L${e.centroid_quality.level}`:""]}),Array.isArray(e.centroid_quality.hints)&&e.centroid_quality.hints.length>0?t.jsx("ul",{className:"mt-1 list-inside list-disc text-[9px] leading-snug text-on-surface-variant",children:e.centroid_quality.hints.map((o,u)=>t.jsx("li",{children:o},u))}):null,(()=>{const o=e.centroid_quality.metrics;if(!o)return null;const u=o.input_count,p=o.output_count,g=o.removed_dense,y=o.removed_line;return typeof u!="number"||typeof p!="number"||typeof g!="number"||typeof y!="number"?null:t.jsx("p",{className:"mt-1 font-mono text-[9px] text-on-surface-variant/95",children:s("lab.centroidQualityMetrics",{in:u,out:p,dense:g,line:y})})})()]}),t.jsxs("div",{children:[t.jsx("div",{className:"text-on-surface-variant",children:s("lab.metric.radec")}),t.jsxs("div",{className:"font-mono text-[9px] text-on-surface",children:["α ",it(r.raDeg)," · δ ",it(r.decDeg)]})]}),r.status&&t.jsxs("div",{className:"rounded bg-surface-container-high px-2 py-1 text-[9px] font-mono text-on-surface",children:[s("lab.metric.status"),": ",r.status]})]}):t.jsx("p",{className:"mt-2 text-[10px] text-on-surface-variant",children:"—"})}function z({label:e,helpKey:s,value:n,onChange:r,type:o="number",step:u}){const{t:p}=Te(),g=s?p(s):void 0;return t.jsxs("label",{className:"block",children:[t.jsx("span",{className:"text-[10px] font-medium text-on-surface-variant",children:e}),g&&t.jsx("p",{className:"mb-1 mt-0.5 text-[9px] leading-snug text-on-surface-variant/85",children:g}),t.jsx("input",{type:o,step:u,className:"w-full rounded bg-surface-container-highest px-2 py-1",value:n===""?"":n,onChange:y=>{const M=y.target.value;r(M===""?void 0:Number(M))}})]})}function Cn({view:e,setView:s}){const{t:n,locale:r,setLocale:o}=Te();return t.jsxs("header",{className:"flex h-12 shrink-0 items-center justify-between border-b border-outline-variant/20 px-4",children:[t.jsxs("div",{className:"flex items-center gap-6",children:[t.jsx("span",{className:"font-headline text-sm font-bold tracking-wide text-on-surface",children:n("app.title")}),t.jsxs("nav",{className:"flex flex-wrap gap-3 text-xs",children:[t.jsx("button",{type:"button",className:e==="lab_image"?"text-primary":"text-on-surface-variant",onClick:()=>s("lab_image"),children:n("nav.labImage")}),t.jsx("button",{type:"button",className:e==="lab_video"?"text-primary":"text-on-surface-variant",onClick:()=>s("lab_video"),children:n("nav.labVideo")}),t.jsx("button",{type:"button",className:e==="pool"?"text-primary":"text-on-surface-variant",onClick:()=>s("pool"),children:n("nav.pool")}),t.jsx("button",{type:"button",className:e==="history"?"text-primary":"text-on-surface-variant",onClick:()=>s("history"),children:n("nav.history")})]})]}),t.jsxs("div",{className:"flex items-center gap-2",children:[t.jsxs("div",{className:"mr-2 flex gap-1 text-[10px]",children:[t.jsx("button",{type:"button",className:`rounded px-2 py-0.5 ${r==="zh"?"bg-primary-container text-on-primary-container":"text-on-surface-variant"}`,onClick:()=>o("zh"),children:n("lang.zh")}),t.jsx("button",{type:"button",className:`rounded px-2 py-0.5 ${r==="en"?"bg-primary-container text-on-primary-container":"text-on-surface-variant"}`,onClick:()=>o("en"),children:n("lang.en")})]}),t.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:[t.jsx(qs,{className:"h-3.5 w-3.5"})," ",n("nav.systemAdmin")]})]})]})}function In({historyQ:e,setHistoryQ:s,historyPage:n,setHistoryPage:r,historyData:o,historyExpandId:u,setHistoryExpandId:p,historyTotalPages:g,onDeleteExperiment:y,onSearch:M}){var b;const{t:d,locale:m}=Te();return t.jsxs("main",{className:"og-scrollbar flex-1 overflow-auto p-4",children:[t.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:d("history.intro")}),t.jsxs("div",{className:"mb-4 flex flex-wrap items-center gap-4",children:[t.jsxs("h2",{className:"flex items-center gap-2 text-lg font-semibold",children:[t.jsx(Ks,{className:"h-5 w-5"})," ",d("history.title")]}),t.jsx("input",{className:"rounded bg-surface-container-highest px-2 py-1 text-xs",placeholder:d("history.search"),value:e,onChange:x=>s(x.target.value)}),t.jsx("button",{type:"button",className:"rounded bg-surface-container px-2 py-1 text-xs",onClick:M,children:d("history.searchBtn")}),t.jsx("button",{type:"button",className:"rounded bg-primary-container/50 px-2 py-1 text-xs",onClick:()=>Ja("json").then(x=>{const I=new Blob([x],{type:"application/json"}),R=document.createElement("a");R.href=URL.createObjectURL(I),R.download="experiments.json",R.click()}),children:d("history.exportJson")}),t.jsx("button",{type:"button",className:"rounded bg-primary-container/50 px-2 py-1 text-xs",onClick:()=>Ja("csv").then(x=>{const I=new Blob([x],{type:"text/csv"}),R=document.createElement("a");R.href=URL.createObjectURL(I),R.download="experiments.csv",R.click()}),children:d("history.exportCsv")})]}),t.jsxs("div",{className:"flex flex-wrap items-center gap-3 text-xs text-on-surface-variant",children:[t.jsx("span",{children:d("history.total",{n:(o==null?void 0:o.total)??0})}),t.jsx("button",{type:"button",className:"rounded border border-outline-variant/30 px-2 py-0.5 disabled:opacity-40",disabled:n<=1,onClick:()=>r(x=>Math.max(1,x-1)),children:d("history.prev")}),t.jsxs("span",{children:[n," / ",g]}),t.jsx("button",{type:"button",className:"rounded border border-outline-variant/30 px-2 py-0.5 disabled:opacity-40",disabled:n>=g,onClick:()=>r(x=>x+1),children:d("history.next")})]}),t.jsx("ul",{className:"mt-2 space-y-2",children:(b=o==null?void 0:o.items)==null?void 0:b.map(x=>{const I=String(x.id??""),R=x.metrics,se=u===I;return t.jsxs("li",{className:"rounded border border-outline-variant/20 p-2 text-[11px]",children:[t.jsxs("div",{className:"flex flex-wrap items-start justify-between gap-2 font-mono",children:[t.jsxs("div",{children:[t.jsxs("div",{className:"text-on-surface",children:[$e(String(x.created_at??""),m)," —"," ",String(x.input_name)," — ",String(x.preset_label)]}),t.jsxs("div",{className:"mt-1 text-on-surface-variant",children:[d("history.preset"),": ",String(x.preset_label)," · ",d("history.metrics"),":"," ","matches=",String((R==null?void 0:R.matches)??"—")," rmse=",Yt(typeof(R==null?void 0:R.rmse_arcsec)=="number"?R.rmse_arcsec:null)]})]}),t.jsxs("div",{className:"flex shrink-0 gap-1",children:[t.jsx("button",{type:"button",className:"rounded bg-surface-container px-2 py-0.5 text-[10px]",onClick:()=>p(se?null:I),children:d(se?"history.collapse":"history.detail")}),t.jsx("button",{type:"button",className:"rounded px-2 py-0.5 text-[10px] text-error hover:bg-error-container/20",title:d("history.delete"),onClick:()=>void y(I),children:d("history.delete")})]})]}),se&&t.jsxs("div",{className:"mt-2 space-y-2",children:[x.asset_snapshot_relpath?t.jsx("img",{src:un(I),alt:"",className:"max-h-48 max-w-full rounded border border-outline-variant/20 object-contain"}):null,t.jsx("pre",{className:"og-scrollbar max-h-64 overflow-auto rounded bg-surface-container p-2 text-[10px]",children:JSON.stringify(x.result_json,null,2)})]})]},I)})})]})}function Ln({uploads:e,onDelete:s}){const{t:n,locale:r}=Te();return t.jsxs("main",{className:"og-scrollbar flex-1 overflow-auto p-4",children:[t.jsxs("h2",{className:"mb-4 flex items-center gap-2 text-lg font-semibold",children:[t.jsx(Gs,{className:"h-5 w-5"})," ",n("pool.title")]}),t.jsxs("table",{className:"w-full text-left text-xs",children:[t.jsx("thead",{children:t.jsxs("tr",{className:"border-b border-outline-variant/30 text-on-surface-variant",children:[t.jsx("th",{className:"py-2",children:n("pool.col.name")}),t.jsx("th",{className:"py-2",children:n("pool.col.source")}),t.jsx("th",{className:"py-2",children:n("pool.col.size")}),t.jsx("th",{className:"py-2",children:n("pool.col.time")}),t.jsx("th",{className:"w-16 py-2 text-center",children:n("pool.delete")})]})}),t.jsx("tbody",{children:e.map(o=>t.jsxs("tr",{className:"border-b border-outline-variant/10",children:[t.jsx("td",{className:"py-2 font-mono",children:o.filename}),t.jsx("td",{className:"py-2",children:o.source??n("common.placeholder")}),t.jsx("td",{className:"py-2",children:Zt(o.size)}),t.jsx("td",{className:"py-2",children:$e(o.modified_at,r)}),t.jsx("td",{className:"py-2 text-center",children:t.jsx("button",{type:"button",className:"inline-flex rounded p-1 text-on-surface-variant hover:bg-error-container/30 hover:text-error",title:n("pool.delete"),"aria-label":n("pool.delete"),onClick:()=>void s(o.filename),children:t.jsx(ns,{className:"h-3.5 w-3.5"})})})]},o.filename))})]})]})}function Kt(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 Dn(e){var n,r;if(!e)return null;const s=e.solve_overlay;return(n=s==null?void 0:s.stars_matched)!=null&&n.length?s.stars_matched.length:(r=s==null?void 0:s.stars_all_centroids)!=null&&r.length?s.stars_all_centroids.length:typeof e.matches=="number"?e.matches:null}function es(e){return/\.(jpe?g|png|webp|bmp|gif|fits?)$/i.test(e)}function ge(e){return/\.(mp4|mov|webm|mkv|avi)$/i.test(e)}function rt(e,s=16,n=14){return e.length<=s+n+1?e:`${e.slice(0,s)}...${e.slice(-n)}`}const ts=10,lt=30,qt=6,Jt=()=>({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:1280,large_scale_bg_subtract:!1,detail_level:"summary",centroid_rejection_level:3,centroid:{sigma:2.5,max_area:400,min_area:5,filtsize:25,binary_open:!0,max_axis_ratio:void 0}});function as(e,s,n,r){const o=typeof r=="number"&&r>0?` ${e("lab.gateNextMs",{ms:r})}`:"",u=String(n??"").toLowerCase();return s==="SKIPPED_BUSY"?u.includes("recording")?`${e("lab.gate.skipRecording")}${o}`:u.includes("previous")?`${e("lab.gate.skipBusyInFlight")}${o}`:`${e("lab.gate.skipBusyFallback",{detail:String(n??"")})}${o}`:s==="SKIPPED_INTERVAL"?`${e("lab.gate.skipInterval")}${o}`:`${s??"?"}${n?`: ${String(n)}`:""}${o}`}async function ss(){try{const e=await fetch("/api/dev/debug/camera/stream/status",{cache:"no-store",credentials:"same-origin"});if(!e.ok)return"fail";const s=await e.json();if(s.success===!1)return"fail";const n=Number(s.max_clients??0),r=Number(s.active_clients??0);return n>0&&r>=n?"busy":"ok"}catch{return"fail"}}function On(){var Da,Oa,Fa,Pa,$a,Ta,Aa;const{t:e,locale:s}=Te(),[n,r]=i.useState("lab_image"),[o,u]=i.useState(!1),[p,g]=i.useState(!1),[y,M]=i.useState([]),[d,m]=i.useState(null),[b,x]=i.useState(Jt),[I,R]=i.useState([]),[se,Xt]=i.useState([]),[Qt,os]=i.useState(new Set),[ne,U]=i.useState(!1),[ct,w]=i.useState(null),[k,we]=i.useState(null),[T,je]=i.useState(null),[Ae,ea]=i.useState([]),[cs,ta]=i.useState([]),aa=i.useRef(null),sa=i.useRef(null),[Ne,ds]=i.useState(""),[Ue,na]=i.useState(1),[Be,dt]=i.useState(null),[ra,la]=i.useState(null),[ut,ia]=i.useState(""),[V,us]=i.useState({matched:!0,pattern:!0,all:!0,rejected:!0}),[He,oa]=i.useState([]),[B,ca]=i.useState(null),[_e,ke]=i.useState(1),[ze,da]=i.useState(!1),[xe,mt]=i.useState(1),[ua,ms]=i.useState({w:0,h:0}),[pe,xs]=i.useState({w:0,h:0}),[ma,xa]=i.useState({w:0,h:0}),[S,Se]=i.useState("file"),[pa,We]=i.useState(()=>Date.now()),[xt,G]=i.useState("unknown"),[Ee,pt]=i.useState(null),[Re,fa]=i.useState(!1),[fe,ha]=i.useState(!1),[ba,ps]=i.useState(!1),[K,ft]=i.useState(!1),[fs,ht]=i.useState(null),[bt,vt]=i.useState(null),[gt,Ve]=i.useState(null),[yt,va]=i.useState(!1),[hs,wt]=i.useState({}),[ga,jt]=i.useState(!1),[Nt,q]=i.useState(null),[bs,Me]=i.useState(null),[_t,Ce]=i.useState(null),[kt,ya]=i.useState(null),[wa,ja]=i.useState(!1),[Na,St]=i.useState(null),[vs,X]=i.useState(null),[_a,ka]=i.useState(!1),[Et,re]=i.useState(null),[gs,H]=i.useState(null),[Rt,Mt]=i.useState(null),Ct=i.useRef(null),Ie=i.useRef(null),Le=i.useRef(null),It=i.useRef(0),le=i.useRef(null),ie=i.useRef(null),Lt=i.useRef(!1),Dt=i.useRef(!1),Ot=i.useRef(!1),Ft=i.useRef(!1),he=i.useRef(null),ys=i.useCallback(async()=>{const a=Le.current;if(!a||a.naturalWidth<2)return null;const l=document.createElement("canvas");l.width=a.naturalWidth,l.height=a.naturalHeight;const c=l.getContext("2d");if(!c)return null;try{c.drawImage(a,0,0)}catch{return null}return await new Promise(f=>{l.toBlob(j=>f(j?URL.createObjectURL(j):null),"image/jpeg",.92)})},[]),[Ge,ws]=i.useState(null),[L,Sa]=i.useState(null),Ke=i.useCallback((a,l)=>{const c=`${Date.now()}-${Math.random().toString(36).slice(2,9)}`;ta(f=>[...f,{id:c,text:a,variant:l}]),window.setTimeout(()=>{ta(f=>f.filter(j=>j.id!==c))},4800)},[]);i.useEffect(()=>{aa.current=k},[k]),i.useEffect(()=>{sa.current=T},[T]),i.useEffect(()=>{Ot.current=fe},[fe]),i.useEffect(()=>{Ft.current=Re},[Re]),i.useEffect(()=>{ct&&(Ke(ct,"error"),w(null))},[ct,Ke]),i.useEffect(()=>{_t&&(Ke(_t,"warn"),Ce(null))},[_t,Ke]);const qe=i.useCallback(()=>{const a=aa.current,l=sa.current;if(!a&&!l)return;const c=`${Date.now()}-${Math.random().toString(36).slice(2,9)}`;ea(f=>[{id:c,at:Date.now(),single:a??void 0,batch:l??void 0},...f].slice(0,ts))},[]),Je=()=>{ie.current!=null&&(window.clearTimeout(ie.current),ie.current=null)},Ze=()=>{le.current!=null&&(window.clearTimeout(le.current),le.current=null)},Pt=(L==null?void 0:L.star_analysis_min_interval_ms)??2e3,$t=(L==null?void 0:L.star_analysis_max_interval_ms)??12e3,Tt=i.useMemo(()=>{const a=(L==null?void 0:L.star_analysis_target_fps)??.6666666666666666,l=Math.min(Math.max(a,.2),5);return Math.round(1e3/l)},[L]),oe=i.useMemo(()=>Math.min(Math.max(kt??Tt,Pt),$t),[Tt,kt,$t,Pt]),Q=i.useCallback(async()=>{const[a,l,c]=await Promise.all([Zs(),Ht("official"),Ht("user")]);M(a.files),R(l.presets),Xt(c.presets)},[]),At=i.useCallback(()=>{sn().then(a=>oa(a.files)).catch(()=>oa([]))},[]);i.useEffect(()=>{on().then(a=>{Sa(a);const l=Math.round(1e3/Math.min(Math.max(a.star_analysis_target_fps,.2),5)),c=Math.min(Math.max(l,a.star_analysis_min_interval_ms),a.star_analysis_max_interval_ms);ya(c);const f=a.centroid_rejection_level_default;typeof f=="number"&&f>=1&&f<=5&&x(j=>({...j,centroid_rejection_level:f}))}).catch(()=>Sa(null))},[]),i.useEffect(()=>{Q().catch(a=>w(String(a)))},[Q]),i.useEffect(()=>{At()},[At]),i.useEffect(()=>{n==="history"&&Wt(Ne,Ue,lt).then(dt).catch(a=>w(String(a)))},[n,Ne,Ue]),i.useEffect(()=>{if(!d){Ve(null);return}let a=!1;return va(!0),(async()=>{try{const l=await rn(d);if(a)return;let c={...l};try{c={...await qa(d),...l}}catch{}Ve(c)}catch{try{const l=await qa(d);a||Ve(l)}catch{a||Ve(null)}}finally{a||va(!1)}})(),()=>{a=!0}},[d]),i.useEffect(()=>{we(null),je(null),wt({}),jt(!1),q(null),Me(null),Se("file"),pt(null),ea([])},[d]);const J=i.useMemo(()=>{const a=k==null?void 0:k.result;if(!a)return null;const l=a.solve_overlay||null;if(!l)return null;const c=a.overlay_ext||null;return{...l,overlay_ext:c||void 0}},[k]),ee=i.useMemo(()=>(k==null?void 0:k.result)??null,[k]),Ea=i.useMemo(()=>Dn(ee),[ee]),Ut=i.useMemo(()=>n==="lab_image"?ua:n==="lab_video"?S==="file"?pe:ma:{w:0,h:0},[n,S,ua,pe,ma]),Ye=d?Vt(d):"";i.useEffect(()=>{if(n!=="lab_image")return;const a=Ct.current,l=he.current;if(!a||!l||!J||!d)return;const c=()=>Za(l,a,J,V);a.complete?c():a.onload=c},[J,d,k,V,n]),i.useEffect(()=>{if(n!=="lab_video"||S!=="file")return;const a=Ie.current,l=he.current;if(!a||!l||!J||!d)return;const c=()=>xn(l,a,J,V);return a.addEventListener("loadeddata",c),a.addEventListener("seeked",c),a.readyState>=2&&c(),()=>{a.removeEventListener("loadeddata",c),a.removeEventListener("seeked",c)}},[J,V,n,S,d,k]),i.useEffect(()=>{if(n!=="lab_video"||S!=="camera")return;const a=Le.current,l=he.current;if(!a||!l||!J)return;const c=()=>Za(l,a,J,V);a.complete?c():a.onload=c},[J,V,n,S,k,pa,K]),i.useEffect(()=>{if(n!=="lab_video"||S!=="camera"){G("unknown");return}if(K){G("ok");return}let a=!1;return G("unknown"),(async()=>{try{const l=await ss();if(a)return;if(l==="fail"){G("fail");return}if(l==="busy"){G("busy");return}G("ok"),It.current=0,We(Date.now())}catch{a||G("fail")}})(),()=>{a=!0}},[n,S,K]),i.useEffect(()=>{const a=Ct.current;if(!a)return;const l=()=>ms({w:a.naturalWidth||0,h:a.naturalHeight||0});return l(),a.addEventListener("load",l),()=>a.removeEventListener("load",l)},[d,Ye]),i.useEffect(()=>{const a=Ie.current;if(!a)return;const l=()=>xs({w:a.videoWidth||0,h:a.videoHeight||0});return a.addEventListener("loadedmetadata",l),a.addEventListener("loadeddata",l),l(),()=>{a.removeEventListener("loadedmetadata",l),a.removeEventListener("loadeddata",l)}},[d,Ye,n]);const js=a=>{x({...Jt(),...a,centroid:{...Jt().centroid,...a.centroid}})},Ns=async()=>{if(!d){w(e("err.selectFile"));return}w(null),U(!0);const a=performance.now();try{const l=await Qs(d,b);qe(),we(l),je(null),Me("file"),q(performance.now()-a)}catch(l){w(String(l)),q(null)}finally{U(!1)}},_s=async()=>{if(!d){w(e("err.selectFile"));return}const a=[];for(const c of Qt){const j=[...I,...se].find($=>$.id===c);j&&a.push({label:j.name,params:structuredClone(j.params)})}if(a.length===0){w(e("err.selectPresets"));return}w(null),U(!0);const l=performance.now();try{const c=await en(d,a);qe(),je({results:c.results}),we(null),wt({}),Me("file"),q(performance.now()-l)}catch(c){w(String(c)),q(null)}finally{U(!1)}},Xe=async a=>{if(Lt.current)return;w(null),Lt.current=!0;const l=performance.now();try{const c=await cn({source:"camera",overlay_topn_count:3,enable_polar_guide:!0,solve_interval_ms:oe,solve_timeout_ms:Math.min(((L==null?void 0:L.solver_timeout_ms)??1500)*.6,1200),...b}),f=c.gate_status,j=c.next_allowed_in_ms,$=c.effective_interval_ms;if(f==="SKIPPED_INTERVAL"||f==="SKIPPED_BUSY"){if(Ce(as(e,f,c.gate_reason,typeof j=="number"?j:null)),a){Ze();const C=Math.max(50,Number(j??oe));le.current=window.setTimeout(()=>{le.current=null,Ft.current&&Xe(!0)},C)}return}Ce(null),c.result!=null&&(qe(),we(c)),je(null),Me("camera"),Se("camera");const E=c.result,D=typeof(E==null?void 0:E.status)=="string"?String(E.status):"";if(ba&&D==="MATCH_FOUND"){ft(!0),ht(c.frame_id!=null?String(c.frame_id):null);const C=await ys();vt(ce=>(ce&&URL.revokeObjectURL(ce),C)),tt()}else if(a){Ze();const C=Math.max(50,Number(j??$??oe));le.current=window.setTimeout(()=>{le.current=null,Ft.current&&Xe(!0)},C)}q(performance.now()-l)}catch(c){w(String(c)),q(null)}finally{Lt.current=!1}},ks=()=>void Xe(!1),Qe=async a=>{if(Dt.current||!d)return;const l=Ie.current;if(!l||l.videoWidth<2||l.videoHeight<2||Ee)return;w(null),Dt.current=!0;const c=performance.now();try{const f=document.createElement("canvas");f.width=l.videoWidth,f.height=l.videoHeight;const j=f.getContext("2d");if(!j)throw new Error("无法创建画布上下文 / Cannot create canvas context");j.drawImage(l,0,0,f.width,f.height);const $=await new Promise((ve,As)=>{f.toBlob(Ua=>Ua?ve(Ua):As(new Error("帧编码失败 / Frame encode failed")),"image/jpeg",.92)}),E=await dn($,{...b,solve_interval_ms:oe,solve_timeout_ms:Math.min(((L==null?void 0:L.solver_timeout_ms)??1500)*.6,1200),overlay_topn_count:3,enable_polar_guide:!0}),D=E.gate_status,C=E.next_allowed_in_ms,ce=E.effective_interval_ms;if(D==="SKIPPED_INTERVAL"||D==="SKIPPED_BUSY"){if(Ce(as(e,D,E.gate_reason,typeof C=="number"?C:null)),a){Je();const ve=Math.max(50,Number(C??oe));ie.current=window.setTimeout(()=>{ie.current=null,Ot.current&&Qe(!0)},ve)}return}if(Ce(null),E.result!=null&&(qe(),we(E)),je(null),Me("file"),q(performance.now()-c),a){Je();const ve=Math.max(50,Number(C??ce??oe));ie.current=window.setTimeout(()=>{ie.current=null,Ot.current&&Qe(!0)},ve)}}catch(f){w(String(f)),q(null)}finally{Dt.current=!1}},Ss=()=>void Qe(!1),Es=()=>{fe||!d||(ha(!0),Je(),Qe(!0))},Ra=i.useMemo(()=>n!=="lab_video"||S!=="file"||!d||/\.avi$/i.test(d)||Ee?!1:pe.w>1&&pe.h>1,[n,S,d,Ee,pe.w,pe.h]),Ma=i.useMemo(()=>!!d&&/\.avi$/i.test(d),[d]),Rs=async()=>{if(!(!d||!Ma||wa)){w(null),ja(!0),St(0),X(e("lab.transcode.loading"));try{const a=Vt(d),l=await fetch(a,{cache:"no-store"});if(!l.ok)throw new Error(e("lab.transcode.fetchFailed"));const c=await l.blob(),f=new File([c],d,{type:"video/x-msvideo"}),j=await Qa(f,(E,D)=>{St(Math.max(0,Math.min(1,E))),D==="loading_ffmpeg"?X(e("lab.transcode.loading")):D==="writing_input"?X(e("lab.transcode.writingBuffer")):D==="transcoding"?X(e("lab.transcode.running")):D==="packing_output"&&X(e("lab.transcode.packaging"))});X(e("lab.transcode.uploading"));const $=await Bt(j.file,"analysis_transcoded");await Ka({old_filename:d,new_filename:$.filename,duration_s:j.duration_s,nominal_fps:null,codec_fourcc:"libx264",container:"MP4"}),await Q(),m($.filename),St(1),X(e("lab.transcode.done"))}catch(a){w(String(a)),X(e("lab.transcode.failed"))}finally{ja(!1)}}},Ms=async a=>{H(e("sidebar.flowDownloadingPool")),re(.19);const l=Vt(a),c=await fetch(l,{cache:"no-store"});if(!c.ok)throw new Error(e("lab.transcode.fetchFailed"));const f=await c.blob(),j=new File([f],a,{type:"video/x-msvideo"}),$=await Qa(j,(D,C)=>{re(.2+Math.max(0,Math.min(1,D))*.6),C==="loading_ffmpeg"?H(e("sidebar.flowLoadingTranscoder")):C==="writing_input"?H(e("sidebar.flowWritingBuffer")):C==="transcoding"?H(e("sidebar.flowTranscoding")):C==="packing_output"&&H(e("sidebar.flowPackaging"))});H(e("sidebar.flowUploadingMp4")),re(.86);const E=await Bt($.file,"debug_console_transcoded");return H(e("sidebar.flowReplacing")),re(.94),await Ka({old_filename:a,new_filename:E.filename,duration_s:$.duration_s,nominal_fps:null,codec_fourcc:"libx264",container:"MP4"}),E.filename},Cs=async()=>{if(!B||_a)return;const a=/\.avi$/i.test(B);w(null),ka(!0),re(.02),Mt(null);try{H(e("sidebar.flowImportingDebug"));const l=await Xs(B);re(a?.18:1);let c=l.filename;a?c=await Ms(l.filename):H(e("sidebar.flowNoTranscode")),await Q(),m(c),r(ge(c)?"lab_video":"lab_image"),re(1),H(e("sidebar.flowDone")),Mt(e("sidebar.flowDoneMsg",{name:rt(c)}))}catch(l){w(String(l)),H(e("sidebar.flowFailed")),Mt(String(l))}finally{ka(!1)}},et=()=>{ha(!1),Je()},Is=()=>{Re||K||(fa(!0),Ze(),Xe(!0))},tt=()=>{fa(!1),Ze()},Ls=async()=>{try{const a=Le.current;a&&(a.onload=null,a.onerror=null,a.src="",a.removeAttribute("src")),await new Promise(l=>window.requestAnimationFrame(()=>l())),await fetch("/api/dev/debug/camera/stop",{method:"POST"}),We(Date.now()),S==="camera"&&Se("file")}catch{}},Ds=()=>{ft(!1),ht(null),vt(a=>(a&&URL.revokeObjectURL(a),null)),We(Date.now())},Os=a=>{os(l=>{const c=new Set(l);return c.has(a)?c.delete(a):c.add(a),c})},Ca=async a=>{if(!window.confirm(e("delete.uploadFirst",{name:a})))return;let l=0;try{l=(await ln(a)).count}catch{l=0}if(l>0){if(!window.confirm(e("delete.uploadCascade",{n:l})))return}else if(!window.confirm(e("delete.uploadSecond")))return;U(!0),w(null);try{await Ys(a,{deleteExperiments:l>0}),await Q(),d===a&&m(null)}catch(c){w(String(c))}finally{U(!1)}},Fs=async a=>{if(window.confirm(e("delete.experimentFirst"))&&window.confirm(e("delete.experimentSecond"))){U(!0),w(null);try{await an(a),ra===a&&la(null);const l=await Wt(Ne,Ue,lt);dt(l)}catch(l){w(String(l))}finally{U(!1)}}},at=a=>mt(Math.min(4,Math.max(.5,a))),Ps=Math.max(1,Math.ceil(((Be==null?void 0:Be.total)??0)/lt)),be=i.useMemo(()=>n==="lab_image"?He.filter(a=>a.type==="image"||es(a.name)):n==="lab_video"?He.filter(a=>a.type==="video"||ge(a.name)):He,[He,n]),De=Math.max(1,Math.ceil(be.length/qt)),$s=i.useMemo(()=>{const a=(_e-1)*qt;return be.slice(a,a+qt)},[be,_e]);i.useEffect(()=>{ke(1)},[n]),i.useEffect(()=>{ke(a=>Math.min(a,De))},[De]),i.useEffect(()=>{B&&!be.some(a=>a.name===B)&&ca(null)},[be,B]);const Ia=i.useMemo(()=>En(gt,s),[gt,s]),Ts=i.useMemo(()=>n==="lab_image"?y.filter(a=>es(a.filename)):n==="lab_video"?y.filter(a=>ge(a.filename)):y,[y,n]),v=i.useMemo(()=>ls(ee),[ee]),La=i.useMemo(()=>is(v.prob),[v.prob]);return i.useEffect(()=>{jt(!1)},[k]),i.useEffect(()=>{if(n!=="lab_video")return;let a;const l=()=>{mn().then(ws).catch(()=>{})};return l(),a=setInterval(l,1500),()=>{a&&clearInterval(a)}},[n]),i.useEffect(()=>{(n!=="lab_video"||S!=="camera")&&(tt(),ft(!1),ht(null),vt(a=>(a&&URL.revokeObjectURL(a),null))),(n!=="lab_video"||S!=="file")&&et()},[n,S]),i.useEffect(()=>{d||et()},[d]),i.useEffect(()=>()=>{tt(),et()},[]),t.jsxs("div",{className:"flex h-dvh min-h-0 flex-col overflow-hidden bg-surface text-on-surface",children:[t.jsx(Cn,{view:n,setView:r}),(n==="lab_image"||n==="lab_video")&&t.jsxs("div",{className:"flex shrink-0 items-center justify-between gap-2 border-b border-outline-variant/15 bg-surface-container-low px-3 py-2 text-xs 2xl:hidden",children:[t.jsx("button",{type:"button",className:"rounded border border-outline-variant/30 px-3 py-1.5 text-on-surface",onClick:()=>{g(!1),u(!0)},children:s==="zh"?"素材":"Assets"}),t.jsx("button",{type:"button",className:"rounded border border-outline-variant/30 px-3 py-1.5 text-on-surface",onClick:()=>{u(!1),g(!0)},children:s==="zh"?"解算参数":"Solve parameters"})]}),(o||p)&&t.jsx("button",{type:"button",className:"fixed inset-x-0 bottom-0 top-[88px] z-40 bg-black/60 2xl:hidden","aria-label":s==="zh"?"关闭面板":"Close panel",onClick:()=>{u(!1),g(!1)}}),t.jsxs("div",{className:"flex min-h-0 flex-1",children:[t.jsxs("aside",{className:`fixed bottom-0 left-0 top-[88px] z-50 w-64 shrink-0 overflow-y-auto border-r border-outline-variant/15 bg-surface-container-lowest p-3 text-xs shadow-2xl transition-transform 2xl:static 2xl:translate-x-0 2xl:shadow-none ${o?"translate-x-0":"-translate-x-full"}`,children:[t.jsx("div",{className:"mb-2 flex justify-end 2xl:hidden",children:t.jsx("button",{type:"button",className:"rounded border border-outline-variant/30 px-2 py-1",onClick:()=>u(!1),children:s==="zh"?"关闭":"Close"})}),t.jsx("div",{className:"mb-3 font-semibold text-on-surface-variant",children:e("sidebar.assets")}),t.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:[t.jsx(Js,{className:"h-4 w-4"}),t.jsx("span",{children:e("sidebar.upload")}),t.jsx("input",{type:"file",accept:"image/*,video/*",className:"hidden",onChange:async a=>{var c;const l=(c=a.target.files)==null?void 0:c[0];if(l){U(!0),w(null);try{const f=await Bt(l);await Q(),m(f.filename)}catch(f){w(String(f))}finally{U(!1),a.target.value=""}}}})]}),t.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:()=>{Q().catch(a=>w(String(a))),ke(1),At()},children:[t.jsx(Vs,{className:"h-3 w-3"})," ",e("sidebar.refresh")]}),t.jsx("div",{className:"og-scrollbar max-h-36 overflow-y-auto border-t border-outline-variant/10 pt-2",children:Ts.map(a=>t.jsxs("div",{className:`mb-1 flex items-center gap-0.5 rounded px-1 ${d===a.filename?"bg-surface-container":""}`,children:[t.jsxs("button",{type:"button",className:`min-w-0 flex-1 truncate rounded px-1 py-1 text-left ${d===a.filename?"text-primary":""}`,title:a.filename,onClick:()=>{m(a.filename),r(ge(a.filename)?"lab_video":"lab_image")},children:[t.jsx("span",{className:"block truncate",children:rt(a.filename)}),t.jsx("span",{className:"block text-[9px] text-on-surface-variant/90",children:ge(a.filename)?e("sidebar.assetTypeVideo"):e("sidebar.assetTypeImage")})]}),t.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:l=>{l.stopPropagation(),Ca(a.filename)},children:t.jsx(ns,{className:"h-3.5 w-3.5"})})]},a.filename))}),t.jsxs("div",{className:"mt-3 border-t border-outline-variant/10 pt-3",children:[t.jsxs("div",{className:"mb-2 flex items-start justify-between gap-2",children:[t.jsx("div",{className:"min-w-0 font-semibold leading-tight text-on-surface-variant",children:e("sidebar.debugCaptures")}),t.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:!B||_a,title:B??void 0,onClick:()=>void Cs(),children:B&&/\.avi$/i.test(B)?e("sidebar.importToPoolWithTranscode"):e("sidebar.importToPoolDirect")})]}),Et!=null&&t.jsxs("div",{className:"mb-2 rounded border border-outline-variant/25 bg-surface-container-low px-2 py-2",children:[t.jsxs("div",{className:"flex items-center justify-between gap-2 text-[10px]",children:[t.jsx("span",{className:"text-on-surface-variant",children:gs||e("sidebar.flowPreparing")}),t.jsxs("span",{className:"font-mono text-on-surface",children:[Math.round((Et||0)*100),"%"]})]}),t.jsx("div",{className:"mt-1.5 h-1.5 w-full overflow-hidden rounded bg-surface-container-highest",children:t.jsx("div",{className:"h-full bg-primary transition-all",style:{width:`${Math.round((Et||0)*100)}%`}})}),Rt&&t.jsx("div",{className:"mt-1 text-[10px] text-on-surface-variant",title:Rt,children:Rt})]}),be.length===0?t.jsx("p",{className:"text-[10px] text-on-surface-variant",children:e("sidebar.debugEmpty")}):t.jsxs(t.Fragment,{children:[t.jsx("div",{className:"og-scrollbar max-h-[min(22rem,55vh)] space-y-1.5 overflow-y-auto pr-0.5",children:$s.map(a=>t.jsxs("button",{type:"button",className:`flex w-full items-center gap-2 rounded-md border px-2 py-1.5 text-left transition-colors ${B===a.name?"border-primary bg-primary-container/20":"border-outline-variant/25 hover:bg-surface-container"}`,onClick:()=>ca(a.name),children:[a.type==="image"?t.jsx("img",{src:nn(a.name),alt:"",className:"h-11 w-11 shrink-0 rounded object-cover"}):t.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"}),t.jsxs("div",{className:"min-w-0 flex-1",children:[t.jsx("div",{className:"truncate font-mono text-[10px] text-on-surface",title:a.name,children:rt(a.name)}),t.jsxs("div",{className:"text-[9px] text-on-surface-variant",children:[a.type==="video"||ge(a.name)?e("sidebar.assetTypeVideo"):e("sidebar.assetTypeImage")," ","· ",$e(a.modified,s)," · ",Zt(a.size)]})]})]},a.name))}),t.jsxs("div",{className:"mt-2 flex items-center justify-between gap-1 text-[10px] text-on-surface-variant",children:[t.jsx("button",{type:"button",className:"rounded border border-outline-variant/30 px-2 py-0.5 disabled:opacity-40",disabled:_e<=1,onClick:()=>ke(a=>Math.max(1,a-1)),children:e("history.prev")}),t.jsx("span",{className:"tabular-nums",children:e("sidebar.debugPage",{cur:_e,total:De})}),t.jsx("button",{type:"button",className:"rounded border border-outline-variant/30 px-2 py-0.5 disabled:opacity-40",disabled:_e>=De,onClick:()=>ke(a=>Math.min(De,a+1)),children:e("history.next")})]})]})]})]}),(n==="lab_image"||n==="lab_video")&&t.jsxs("div",{className:"flex min-h-0 min-w-0 flex-1",children:[t.jsxs("main",{className:"og-scrollbar min-h-0 min-w-0 flex-1 overflow-y-auto p-4",children:[n==="lab_video"&&t.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:[t.jsx("p",{className:"text-[10px] text-on-surface-variant",children:e("lab.videoLiveIntro")}),t.jsx("p",{className:"mt-1.5 text-[10px] leading-snug text-on-surface-variant/95",children:e("lab.videoMjpegHint")}),t.jsxs("div",{className:"mt-2 flex flex-wrap gap-2",children:[t.jsx("button",{type:"button",className:`rounded px-3 py-1.5 text-[11px] font-medium ${S==="file"?"bg-primary text-on-primary-container":"border border-outline-variant/40 bg-surface-container text-on-surface"}`,onClick:()=>Se("file"),children:e("lab.previewModeFile")}),t.jsx("button",{type:"button",className:`rounded px-3 py-1.5 text-[11px] font-medium ${S==="camera"?"bg-primary text-on-primary-container":"border border-outline-variant/40 bg-surface-container text-on-surface"}`,onClick:()=>Se("camera"),children:e("lab.previewModeCamera")}),t.jsx("button",{type:"button",className:"rounded border border-outline-variant/40 bg-surface-container px-3 py-1.5 text-[11px] font-medium text-on-surface",onClick:()=>void Ls(),children:e("lab.stopCameraPreview")})]})]}),t.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:[La!=null&&t.jsxs("div",{className:"pointer-events-none absolute right-2 top-2 z-20 rounded border border-white/30 bg-black/55 px-2 py-1 text-[10px] font-semibold tabular-nums text-white",children:[e("lab.previewConfidence"),": ",La.toFixed(1),"%"]}),n==="lab_video"&&S==="camera"?t.jsx("div",{className:"relative flex h-full min-h-[220px] flex-col items-center justify-center gap-3 bg-black p-2",children:t.jsxs("div",{className:"relative inline-block max-h-[70vh] max-w-full",children:[K&&bt?t.jsx("img",{ref:Le,src:bt,alt:"",className:"max-h-[70vh] w-full min-h-[120px] object-contain",onLoad:a=>xa({w:a.currentTarget.naturalWidth,h:a.currentTarget.naturalHeight})}):K&&!bt?t.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:[t.jsx(st,{className:"h-8 w-8 animate-spin text-primary"}),t.jsx("span",{children:e("lab.cameraPreviewLoading")})]}):xt==="busy"?t.jsx("div",{className:"flex min-h-[200px] max-w-md flex-col items-center justify-center gap-2 px-4 text-center text-[11px] text-on-surface-variant",children:t.jsx("span",{className:"text-error",children:e("lab.videoMjpegHint")})}):xt==="fail"?t.jsx("div",{className:"flex min-h-[200px] max-w-md flex-col items-center justify-center gap-2 px-4 text-center text-[11px] text-on-surface-variant",children:t.jsx("span",{className:"text-error",children:e("lab.cameraStreamStatusFail")})}):xt==="unknown"?t.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:[t.jsx(st,{className:"h-8 w-8 animate-spin text-primary"}),t.jsx("span",{children:e("lab.cameraPreviewLoading")})]}):t.jsx("img",{ref:Le,src:`/api/dev/debug/camera/stream?t=${pa}`,alt:"",className:"max-h-[70vh] w-full min-h-[120px] object-contain",onLoad:a=>xa({w:a.currentTarget.naturalWidth,h:a.currentTarget.naturalHeight}),onError:()=>{(async()=>{const a=await ss();if(a==="busy"){G("busy");return}if(a==="fail"){G("fail");return}if(It.current>=6){G("fail");return}It.current+=1,window.setTimeout(()=>We(Date.now()),400)})()}}),t.jsx("canvas",{ref:he,className:"pointer-events-none absolute left-0 top-0"})]})}):d?t.jsx("div",{className:"og-scrollbar relative h-full min-h-[200px] overflow-auto",children:n==="lab_video"?t.jsxs(t.Fragment,{children:[t.jsxs("div",{className:"absolute left-2 top-2 z-20 flex flex-wrap gap-1",children:[t.jsxs("button",{type:"button",title:e("lab.grid"),className:`rounded border px-2 py-1 text-[10px] ${ze?"border-primary bg-primary/20":"border-white/30 bg-black/40"} text-white`,onClick:()=>da(a=>!a),children:[t.jsx(Wa,{className:"mr-1 inline h-3 w-3"}),e("lab.grid")]}),t.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:()=>at(xe-.25),children:t.jsx(Ga,{className:"inline h-3 w-3"})}),t.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:()=>at(xe+.25),children:t.jsx(Va,{className:"inline h-3 w-3"})}),t.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:()=>mt(1),children:t.jsx(za,{className:"inline h-3 w-3"})})]}),t.jsxs("div",{className:"flex min-h-[200px] flex-col items-center justify-center gap-2 bg-black py-2",children:[t.jsx("div",{className:"inline-block origin-top-left transition-transform",style:{transform:`scale(${xe})`},children:t.jsxs("div",{className:"relative inline-block",children:[ze&&t.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"}}),t.jsx("video",{ref:Ie,src:Ye,loop:!0,playsInline:!0,autoPlay:!0,muted:!0,preload:"metadata",className:"max-h-[70vh] w-full max-w-full object-contain",onError:()=>pt(e("lab.videoPreviewFailed")),onLoadedData:()=>{pt(null);const a=Ie.current;a&&a.play().catch(()=>{})}}),t.jsx("canvas",{ref:he,className:"pointer-events-none absolute left-0 top-0"})]})}),Ee&&t.jsx("div",{className:"rounded border border-error/40 bg-error-container/20 px-3 py-1.5 text-[11px] text-error",children:Ee}),Ma&&t.jsxs("div",{className:"max-w-3xl rounded border border-primary/40 bg-primary/10 px-3 py-2 text-[11px] text-on-surface",children:[t.jsx("div",{className:"font-semibold text-primary",children:e("lab.transcode.title")}),t.jsx("div",{className:"mt-1 text-on-surface-variant",children:e("lab.transcode.desc")}),Na!=null&&t.jsxs("div",{className:"mt-2",children:[t.jsx("div",{className:"mb-1 text-[10px] text-on-surface-variant",children:vs||e("lab.transcode.running")}),t.jsx("div",{className:"h-1.5 w-full overflow-hidden rounded bg-surface-container-highest",children:t.jsx("div",{className:"h-full bg-primary transition-all",style:{width:`${Math.round(Na*100)}%`}})})]}),t.jsx("div",{className:"mt-2",children:t.jsx("button",{type:"button",className:"rounded bg-primary px-3 py-1.5 text-[11px] font-medium text-on-primary disabled:opacity-50",disabled:wa,onClick:()=>void Rs(),children:e("lab.transcode.button")})})]})]})]}):t.jsxs(t.Fragment,{children:[t.jsxs("div",{className:"absolute left-2 top-2 z-20 flex flex-wrap gap-1",children:[t.jsxs("button",{type:"button",title:e("lab.grid"),className:`rounded border px-2 py-1 text-[10px] ${ze?"border-primary bg-primary/20":"border-white/30 bg-black/40"} text-white`,onClick:()=>da(a=>!a),children:[t.jsx(Wa,{className:"mr-1 inline h-3 w-3"}),e("lab.grid")]}),t.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:()=>at(xe-.25),children:t.jsx(Ga,{className:"inline h-3 w-3"})}),t.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:()=>at(xe+.25),children:t.jsx(Va,{className:"inline h-3 w-3"})}),t.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:()=>mt(1),children:t.jsx(za,{className:"inline h-3 w-3"})})]}),t.jsx("div",{className:"inline-block origin-top-left transition-transform",style:{transform:`scale(${xe})`},children:t.jsxs("div",{className:"relative inline-block",children:[ze&&t.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"}}),t.jsx("img",{ref:Ct,src:Ye,alt:"preview",className:"max-h-[70vh] w-full object-contain"}),t.jsx("canvas",{ref:he,className:"pointer-events-none absolute left-0 top-0"})]})})]})}):t.jsx("div",{className:"flex h-full items-center justify-center px-4 text-center text-on-surface-variant",children:e(n==="lab_video"?"lab.selectOrUploadVideo":"lab.selectOrUpload")}),ne&&t.jsx("div",{className:"absolute inset-0 flex items-center justify-center bg-black/40",children:t.jsx(st,{className:"h-8 w-8 animate-spin text-primary"})})]}),(d&&n==="lab_image"||n==="lab_video"&&(S==="file"&&d||S==="camera"))&&t.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:[t.jsx("span",{className:"shrink-0 font-medium text-on-surface-variant",children:e("lab.layers")}),t.jsx("div",{className:"flex flex-wrap gap-x-4 gap-y-1",children:["matched","pattern","all","rejected"].map(a=>t.jsxs("label",{className:"flex cursor-pointer items-center gap-1",children:[t.jsx("input",{type:"checkbox",className:"accent-primary",checked:V[a],onChange:l=>us(c=>({...c,[a]:l.target.checked}))}),t.jsx("span",{children:e(a==="matched"?"lab.layer.matched":a==="pattern"?"lab.layer.pattern":a==="all"?"lab.layer.all":"lab.layer.rejected")})]},a))})]}),(d||n==="lab_video"&&S==="camera")&&t.jsxs(t.Fragment,{children:[t.jsxs("div",{className:"mt-2 grid gap-2 sm:grid-cols-2",children:[t.jsxs("details",{open:!0,className:"rounded-lg border border-outline-variant/25 bg-surface-container-lowest/90",children:[t.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"),t.jsx(Fe,{className:"h-4 w-4 shrink-0 text-on-surface-variant"})]}),t.jsx("div",{className:"border-t border-outline-variant/15 px-3 py-2 text-[10px]",children:ee?t.jsxs("div",{className:"space-y-1 text-on-surface",children:[v.tSolveMs!=null&&t.jsxs("div",{className:"space-y-0.5",children:[t.jsxs("div",{className:"flex justify-between gap-2",children:[t.jsx("span",{className:"text-on-surface-variant",children:e("lab.metric.solveComputeMs")}),t.jsxs("span",{className:"font-mono tabular-nums",children:[v.tSolveMs.toFixed(0)," ms"]})]}),t.jsx("p",{className:"text-[8px] leading-snug text-on-surface-variant/90",children:e("lab.metric.solveComputeHelp")})]}),Nt!=null&&t.jsxs("div",{className:"space-y-0.5 border-t border-outline-variant/10 pt-1",children:[t.jsxs("div",{className:"flex justify-between gap-2",children:[t.jsx("span",{className:"text-on-surface-variant",children:e("lab.metric.solveRoundTripMs")}),t.jsxs("span",{className:"font-mono tabular-nums",children:[Nt.toFixed(0)," ms"]})]}),t.jsx("p",{className:"text-[8px] leading-snug text-on-surface-variant/90",children:e("lab.metric.solveRoundTripHelp")})]}),(v.tBackendTotalMs!=null||v.tOpenDecodeMs!=null||v.tPreprocessMs!=null||v.tExtractMs!=null||v.tSolveMs!=null)&&t.jsxs("div",{className:"space-y-0.5 border-t border-outline-variant/10 pt-1",children:[t.jsxs("div",{className:"flex justify-between gap-2",children:[t.jsx("span",{className:"text-on-surface-variant",children:e("lab.metric.backendTotalMs")}),t.jsx("span",{className:"font-mono tabular-nums",children:v.tBackendTotalMs!=null?`${v.tBackendTotalMs.toFixed(0)} ms`:e("common.placeholder")})]}),t.jsxs("div",{className:"flex flex-wrap gap-x-2 gap-y-0.5 text-[8px] text-on-surface-variant/90",children:[t.jsxs("span",{children:[e("lab.metric.openDecodeMs"),":"," ",v.tOpenDecodeMs!=null?`${v.tOpenDecodeMs.toFixed(0)} ms`:e("common.placeholder")]}),t.jsxs("span",{children:[e("lab.metric.preprocessMs"),":"," ",v.tPreprocessMs!=null?`${v.tPreprocessMs.toFixed(0)} ms`:e("common.placeholder")]}),t.jsxs("span",{children:[e("lab.metric.extractMs"),":"," ",v.tExtractMs!=null?`${v.tExtractMs.toFixed(0)} ms`:e("common.placeholder")]}),t.jsxs("span",{children:[e("lab.metric.solveOnlyMs"),":"," ",v.tSolveMs!=null?`${v.tSolveMs.toFixed(0)} ms`:e("common.placeholder")]})]})]}),(v.raDeg!=null||v.decDeg!=null)&&t.jsxs("div",{className:"leading-tight",children:[t.jsx("span",{className:"text-on-surface-variant",children:e("lab.metric.radec")}),t.jsxs("div",{className:"mt-0.5 font-mono text-[9px]",children:["α ",it(v.raDeg)," · δ"," ",it(v.decDeg)]})]}),t.jsxs("div",{className:"flex flex-wrap gap-x-2 gap-y-0.5 text-[9px]",children:[v.matches!=null&&t.jsxs("span",{children:[t.jsx("span",{className:"text-on-surface-variant",children:e("lab.metric.matches")})," ",t.jsx("span",{className:"font-mono",children:v.matches})]}),v.rmseArcsec!=null&&t.jsxs("span",{children:[t.jsx("span",{className:"text-on-surface-variant",children:e("lab.metric.rmse")})," ",t.jsx("span",{className:"font-mono",children:Yt(v.rmseArcsec)})]}),v.prob!=null&&t.jsxs("span",{className:"inline-flex max-w-full flex-col gap-0.5",children:[t.jsxs("span",{children:[t.jsx("span",{className:"text-on-surface-variant",children:e("lab.metric.prob")})," ",t.jsx("span",{className:"font-mono",children:ye(v.prob,ee??void 0).line})]}),t.jsx("span",{className:"text-[8px] leading-snug text-on-surface-variant/90",children:e("lab.metric.probHelp")}),ye(v.prob,ee??void 0).rawLine&&t.jsxs(t.Fragment,{children:[t.jsxs("span",{className:"text-[8px] text-on-surface-variant",children:[e("lab.metric.probRaw"),":"," ",ye(v.prob,ee??void 0).rawLine]}),t.jsx("span",{className:"text-[8px] leading-snug text-on-surface-variant/90",children:e("lab.metric.probRawHelp")})]})]})]}),v.status&&t.jsxs("div",{className:"border-t border-outline-variant/15 pt-1 text-[9px]",children:[t.jsxs("span",{className:"text-on-surface-variant",children:[e("lab.metric.status")," "]}),t.jsx("span",{className:"font-mono text-secondary",children:v.status})]})]}):t.jsx("p",{className:"text-on-surface-variant",children:e("common.placeholder")})})]}),t.jsxs("details",{open:!0,className:"rounded-lg border border-outline-variant/25 bg-surface-container-lowest/90",children:[t.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"),t.jsx(Fe,{className:"h-4 w-4 shrink-0 text-on-surface-variant"})]}),t.jsxs("div",{className:"space-y-0.5 border-t border-outline-variant/15 px-3 py-2 text-[10px]",children:[t.jsxs("div",{className:"flex justify-between gap-2",children:[t.jsx("span",{className:"text-on-surface-variant",children:e("lab.resolution")}),t.jsx("span",{className:"font-mono tabular-nums",children:Ut.w>0?`${Ut.w}×${Ut.h}`:e("common.placeholder")})]}),t.jsxs("div",{className:"flex justify-between gap-2",children:[t.jsx("span",{className:"text-on-surface-variant",children:e("lab.starsDetected")}),t.jsx("span",{className:"font-mono tabular-nums",children:Ea??e("common.placeholder")})]}),t.jsxs("div",{className:"flex justify-between gap-2",children:[t.jsx("span",{className:"text-on-surface-variant",children:e("lab.fwhm")}),t.jsx("span",{children:e("common.placeholder")})]})]})]})]}),d&&t.jsxs(t.Fragment,{children:[t.jsxs("div",{className:"mt-2 flex flex-wrap gap-4 text-xs text-on-surface-variant",children:[t.jsxs("span",{children:[e("lab.file"),":"," ",t.jsx("span",{className:"font-mono text-on-surface",title:d,children:rt(d,22,18)})]}),((Da=y.find(a=>a.filename===d))==null?void 0:Da.source)&&t.jsxs("span",{className:"rounded bg-surface-container-high px-2 py-0.5",children:[e("lab.source"),":"," ",(Oa=y.find(a=>a.filename===d))==null?void 0:Oa.source]})]}),t.jsxs("details",{open:!0,className:"mt-2 rounded-lg border border-outline-variant/25 bg-surface-container-lowest/90 text-xs shadow-sm",children:[t.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"),yt&&t.jsx(st,{className:"h-3.5 w-3.5 animate-spin"}),t.jsx(Fe,{className:"ml-auto h-4 w-4 shrink-0 text-on-surface-variant"})]}),t.jsx("div",{className:"border-t border-outline-variant/15 p-3 pt-2",children:Ia.length>0?t.jsx("dl",{className:"grid grid-cols-2 gap-x-4 gap-y-2 sm:grid-cols-3",children:Ia.map(a=>t.jsxs("div",{children:[t.jsx("dt",{className:"text-[10px] text-on-surface-variant",children:e(a.key)}),t.jsx("dd",{className:"text-[11px] font-medium text-on-surface",children:a.value})]},`${a.key}-${a.value}`))}):gt&&!yt?t.jsx("p",{className:"text-[10px] leading-relaxed text-on-surface-variant",children:e("lab.meta.partial")}):yt?null:t.jsx("p",{className:"text-[10px] text-on-surface-variant",children:e("lab.meta.noSidecar")})})]})]})]}),(d||n==="lab_video"&&S==="camera")&&(Ae.length>0||k||T)&&t.jsxs(t.Fragment,{children:[Ae.length>0&&t.jsxs("section",{className:"mt-3 rounded-lg border border-outline-variant/25 bg-surface-container-lowest/90",children:[t.jsxs("div",{className:"flex h-9 shrink-0 items-center border-b border-outline-variant/15 px-3 text-[10px] uppercase text-on-surface-variant",children:[t.jsx("span",{children:e("results.solveHistoryTitle")}),t.jsxs("span",{className:"ml-2 normal-case text-on-surface-variant/70",children:["(",Ae.length,"/",ts,")"]})]}),t.jsx("div",{className:"og-scrollbar max-h-[min(36vh,18rem)] space-y-2 overflow-y-auto p-3",children:Ae.map(a=>{var $;const l=a.batch,c=a.single,f=$e(new Date(a.at).toISOString(),s),j=l&&l.results.length?e("results.historyBatch",{count:l.results.length}):e("results.historySingle");return t.jsxs("details",{className:"rounded border border-outline-variant/20 bg-surface-container/80 text-xs",children:[t.jsx("summary",{className:"cursor-pointer list-none px-3 py-2 font-medium text-on-surface [&::-webkit-details-marker]:hidden",children:t.jsxs("span",{children:[f," · ",j]})}),t.jsxs("div",{className:"border-t border-outline-variant/15 p-3 pt-2",children:[($=l==null?void 0:l.results)!=null&&$.length?t.jsx("div",{className:"og-scrollbar flex gap-2 overflow-x-auto pb-1",children:l.results.map((E,D)=>{const C=E.result,ce=E.success===!0;return t.jsxs("div",{className:"min-w-[12rem] max-w-xs shrink-0 rounded border border-outline-variant/20 bg-surface-container-lowest p-2",children:[t.jsx("div",{className:"border-b border-outline-variant/10 pb-1 text-[10px] font-semibold text-secondary",children:String(E.label??D)}),ce&&C?t.jsx(nt,{result:C,t:e,roundTripMs:null}):t.jsx("div",{className:"mt-1 text-[10px] text-error",children:String(E.error??"—")})]},D)})}):null,c&&c.result?t.jsx("div",{className:"mt-2 rounded border border-outline-variant/15 p-2",children:t.jsx(nt,{result:c.result,t:e,roundTripMs:null})}):null,t.jsxs("details",{className:"mt-2",children:[t.jsx("summary",{className:"cursor-pointer text-[10px] text-primary hover:underline",children:e("results.viewRaw")}),t.jsx("pre",{className:"og-scrollbar mt-1 max-h-32 overflow-auto rounded bg-surface-container-highest p-2 text-[9px] text-on-surface-variant",children:JSON.stringify({single:c,batch:l},null,2)})]})]})]},a.id)})})]}),(k||T)&&t.jsxs("section",{className:"mt-3 max-h-[min(50vh,28rem)] rounded-lg border border-outline-variant/25 bg-surface-container-lowest/95",children:[t.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:[t.jsx("span",{children:e("results.title")}),t.jsxs("div",{className:"flex gap-3",children:[(Fa=T==null?void 0:T.results)!=null&&Fa.length?t.jsx("button",{type:"button",className:"rounded bg-surface-container px-2 py-0.5 normal-case",onClick:async()=>{if(!(!d||!T)){for(const a of T.results){if(!a.success)continue;const l=a.result;await zt({input_name:d,preset_label:String(a.label),result_json:a,metrics:Kt(l??null),replay:{layers:V,params:b}})}w(null)}},children:e("results.saveBatchAll")}):null,k&&t.jsx("button",{type:"button",className:"rounded bg-surface-container px-2 py-0.5 normal-case",onClick:async()=>{if(!d&&bs!=="camera")return;const a=k.result;await zt({input_name:d??e("lab.cameraSnapshotName"),preset_label:"manual",result_json:k,metrics:Kt(a??null),replay:{layers:V,params:b}}),w(null)},children:e("results.saveCurrent")})]})]}),t.jsx("div",{className:"og-scrollbar min-h-0 overflow-y-auto p-3",children:t.jsxs("div",{className:"og-scrollbar flex gap-3 overflow-x-auto text-xs",children:[T==null?void 0:T.results.map((a,l)=>{const c=a.result,f=hs[l]??!1;return t.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:[t.jsx("div",{className:"border-b border-outline-variant/15 pb-2 font-semibold text-secondary",children:String(a.label)}),a.success?t.jsxs(t.Fragment,{children:[t.jsx(nt,{result:c,t:e,roundTripMs:null}),t.jsx("button",{type:"button",className:"mt-2 text-[10px] text-primary hover:underline",onClick:()=>wt(j=>({...j,[l]:!f})),children:e(f?"results.hideRaw":"results.viewRaw")}),f&&t.jsx("pre",{className:"og-scrollbar mt-1 max-h-40 overflow-auto rounded bg-surface-container-highest p-2 text-[9px] text-on-surface-variant",children:JSON.stringify(a,null,2)})]}):t.jsx("div",{className:"mt-2 text-[10px] text-error",children:String(a.error)}),a.success===!0&&d?t.jsx("button",{type:"button",className:"mt-3 w-full rounded bg-surface-container-high py-1.5 text-[10px] font-medium",onClick:()=>zt({input_name:d,preset_label:String(a.label),result_json:a,metrics:Kt(c??null),replay:{layers:V,params:b}}).catch(j=>w(String(j))),children:e("results.saveRow")}):null]},l)}),k&&!T&&t.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:[t.jsx("div",{className:"border-b border-outline-variant/15 pb-2 text-[10px] font-semibold uppercase text-on-surface-variant",children:e("results.title")}),t.jsx(nt,{result:k.result,t:e,roundTripMs:Nt}),t.jsx("button",{type:"button",className:"mt-2 text-[10px] text-primary hover:underline",onClick:()=>jt(a=>!a),children:e(ga?"results.hideRaw":"results.viewRaw")}),ga&&t.jsx("pre",{className:"og-scrollbar mt-1 max-h-48 overflow-auto rounded bg-surface-container-highest p-2 text-[9px] text-on-surface-variant",children:JSON.stringify(k,null,2)})]})]})})]})]})]}),t.jsxs("aside",{className:`fixed bottom-0 right-0 top-[88px] z-50 flex min-h-0 w-80 shrink-0 flex-col border-l border-outline-variant/15 bg-surface-container-low text-xs shadow-2xl transition-transform 2xl:static 2xl:translate-x-0 2xl:shadow-none ${p?"translate-x-0":"translate-x-full"}`,children:[t.jsx("div",{className:"flex shrink-0 justify-end border-b border-outline-variant/15 p-2 2xl:hidden",children:t.jsx("button",{type:"button",className:"rounded border border-outline-variant/30 px-2 py-1",onClick:()=>g(!1),children:s==="zh"?"关闭":"Close"})}),n!=="lab_video"&&t.jsxs("div",{className:"shrink-0 space-y-2 border-b border-outline-variant/20 p-4 pb-3",children:[t.jsx("button",{type:"button",className:"w-full rounded bg-primary py-2.5 font-semibold text-on-primary-container",onClick:Ns,disabled:ne,children:e("btn.solveOne")}),t.jsx("button",{type:"button",className:"w-full rounded bg-secondary/80 py-2.5 font-semibold text-on-secondary",onClick:_s,disabled:ne,children:e("btn.solveBatch")})]}),n==="lab_video"&&t.jsx("div",{className:"shrink-0 space-y-2 border-b border-outline-variant/20 p-4 pb-3",children:S==="file"?t.jsxs(t.Fragment,{children:[t.jsx("button",{type:"button",className:"w-full rounded bg-primary py-2.5 font-semibold text-on-primary-container disabled:opacity-40",onClick:()=>Es(),disabled:ne||!Ra||fe,children:e("lab.solveFileStart")}),t.jsx("button",{type:"button",className:"w-full rounded bg-error/80 py-2.5 font-semibold text-on-error disabled:opacity-40",onClick:()=>et(),disabled:!fe,children:e("lab.solveFileStop")}),t.jsx("button",{type:"button",className:"w-full rounded bg-secondary/80 py-2.5 font-semibold text-on-secondary disabled:opacity-40",onClick:()=>void Ss(),disabled:ne||!Ra||fe,children:e("lab.solveCurrentFrame")})]}):t.jsxs(t.Fragment,{children:[t.jsx("button",{type:"button",className:"w-full rounded bg-primary py-2.5 font-semibold text-on-primary-container disabled:opacity-40",onClick:()=>Is(),disabled:ne||Re||K,children:e("lab.solveCameraStart")}),t.jsx("button",{type:"button",className:"w-full rounded bg-error/80 py-2.5 font-semibold text-on-error disabled:opacity-40",onClick:()=>tt(),disabled:!Re,children:e("lab.solveCameraStop")}),t.jsx("button",{type:"button",className:"w-full rounded bg-secondary/80 py-2.5 font-semibold text-on-secondary disabled:opacity-40",onClick:()=>void ks(),disabled:ne||K,children:e("lab.solveCameraFrame")}),t.jsxs("label",{className:"flex items-center gap-2 rounded border border-outline-variant/25 px-2 py-1.5 text-[11px]",children:[t.jsx("input",{type:"checkbox",checked:ba,onChange:a=>ps(a.target.checked)}),t.jsx("span",{children:"自动保持(解算成功后冻结) / Auto Hold"})]}),K&&t.jsx("button",{type:"button",className:"w-full rounded bg-tertiary/80 py-2.5 font-semibold text-on-tertiary disabled:opacity-40",onClick:()=>Ds(),children:"继续实时 / Resume Live"}),K&&t.jsxs("div",{className:"text-[10px] text-on-surface-variant",children:["已冻结帧 / Frozen frame ",fs??"-"]})]})}),n==="lab_video"&&Ge&&t.jsxs("div",{className:"shrink-0 border-b border-outline-variant/20 px-4 py-2 text-[10px] text-on-surface-variant",children:[t.jsx("div",{className:"font-medium text-on-surface",children:e("lab.systemLoad")}),t.jsxs("div",{children:["CPU ",Ge.cpu_usage,"% · RAM ",Ge.memory_usage,"% ·"," ",Ge.temperature,"°C"]})]}),t.jsxs("div",{className:"og-scrollbar min-h-0 flex-1 overflow-y-auto p-4",children:[t.jsxs("details",{open:!0,className:"rounded-lg border border-outline-variant/20 bg-surface-container-highest/30",children:[t.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"),t.jsx(Fe,{className:"h-4 w-4 shrink-0"})]}),t.jsxs("div",{className:"border-t border-outline-variant/15 p-3 pt-2",children:[t.jsx("p",{className:"mb-3 text-[10px] leading-relaxed text-on-surface-variant/90",children:e("params.blockSolveIntro")}),t.jsxs("div",{className:"space-y-2",children:[t.jsxs("label",{className:"block",children:[t.jsx("span",{className:"text-[10px] font-medium text-on-surface-variant",children:e("params.solveProfile")}),t.jsx("p",{className:"mb-1 mt-0.5 text-[9px] leading-snug text-on-surface-variant/85",children:e("params.solveProfileHelp")}),t.jsxs("select",{className:"w-full rounded bg-surface-container-highest px-2 py-1",value:b.solve_profile??"balanced",onChange:a=>x(l=>({...l,solve_profile:a.target.value})),children:[t.jsx("option",{value:"speed",children:e("params.solveProfileSpeed")}),t.jsx("option",{value:"balanced",children:e("params.solveProfileBalanced")}),t.jsx("option",{value:"robust",children:e("params.solveProfileRobust")})]})]}),t.jsx(z,{label:e("params.fov"),helpKey:"params.fovHelp",value:b.fov_estimate??"",onChange:a=>x(l=>({...l,fov_estimate:a}))}),t.jsx(z,{label:e("params.fovErr"),helpKey:"params.fovErrHelp",value:b.fov_max_error??"",onChange:a=>x(l=>({...l,fov_max_error:a}))}),t.jsx(z,{label:e("params.timeout"),helpKey:"params.timeoutHelp",value:b.solve_timeout_ms??"",onChange:a=>x(l=>({...l,solve_timeout_ms:a}))}),t.jsx(z,{label:e("params.solveIntervalMs"),helpKey:"params.solveIntervalMsHelp",value:kt??"",onChange:a=>ya(a==null?Tt:Math.round(a))}),t.jsx("p",{className:"text-[9px] leading-snug text-on-surface-variant/80",children:e("params.solveIntervalBound",{min:Pt,max:$t,effective:oe})}),t.jsx("p",{className:"text-[9px] leading-snug text-on-surface-variant/80",children:e("params.solveIntervalIndependent")}),n==="lab_video"&&t.jsxs("label",{className:"mt-2 block",children:[t.jsx("span",{className:"text-[10px] font-medium text-on-surface-variant",children:e("params.centroidRejectionLevel")}),t.jsx("p",{className:"mb-1 mt-0.5 text-[9px] leading-snug text-on-surface-variant/85",children:e("params.centroidRejectionLevelHelp")}),t.jsxs("div",{className:"flex items-center gap-2",children:[t.jsx("span",{className:"w-5 shrink-0 text-center text-[9px] text-on-surface-variant",children:"1"}),t.jsx("input",{type:"range",min:1,max:5,step:1,className:"h-2 flex-1 accent-primary",value:b.centroid_rejection_level??3,onChange:a=>x(l=>({...l,centroid_rejection_level:Number(a.target.value)}))}),t.jsx("span",{className:"w-5 shrink-0 text-center text-[9px] text-on-surface-variant",children:"5"}),t.jsx("span",{className:"ml-1 min-w-[1.25rem] tabular-nums text-[10px] font-semibold text-on-surface",children:b.centroid_rejection_level??3})]}),t.jsx("p",{className:"mt-0.5 text-[9px] text-on-surface-variant/90",children:e("params.centroidRejectionScale")})]}),t.jsx(z,{label:e("params.ra"),helpKey:"params.raHelp",value:b.hint_ra_deg??"",onChange:a=>x(l=>({...l,hint_ra_deg:a}))}),t.jsx(z,{label:e("params.dec"),helpKey:"params.decHelp",value:b.hint_dec_deg??"",onChange:a=>x(l=>({...l,hint_dec_deg:a}))}),t.jsx(z,{label:e("params.maxSide"),helpKey:"params.maxSideHelp",value:b.max_image_side??"",onChange:a=>x(l=>({...l,max_image_side:a}))}),t.jsxs("label",{className:"mt-1.5 flex cursor-pointer items-center gap-2",children:[t.jsx("input",{type:"checkbox",className:"accent-primary",checked:b.detail_level==="full",onChange:a=>x(l=>({...l,detail_level:a.target.checked?"full":"summary"}))}),t.jsx("span",{className:"text-[10px] text-on-surface-variant",children:e("params.detailLevelFull")})]}),t.jsxs("label",{className:"mt-1.5 flex cursor-pointer items-start gap-2",children:[t.jsx("input",{type:"checkbox",className:"accent-primary mt-0.5",checked:!!b.large_scale_bg_subtract,onChange:a=>x(l=>({...l,large_scale_bg_subtract:a.target.checked}))}),t.jsxs("span",{children:[t.jsx("span",{className:"text-[10px] text-on-surface-variant",children:e("params.largeScaleBg")}),t.jsx("p",{className:"mt-0.5 text-[9px] leading-snug text-on-surface-variant/85",children:e("params.largeScaleBgHelp")})]})]})]})]})]}),t.jsxs("details",{open:!0,className:"mt-3 rounded-lg border border-outline-variant/20 bg-surface-container-highest/30",children:[t.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"),t.jsx(Fe,{className:"h-4 w-4 shrink-0"})]}),t.jsxs("div",{className:"border-t border-outline-variant/15 p-3 pt-2",children:[t.jsx("p",{className:"mb-3 text-[10px] leading-relaxed text-on-surface-variant/90",children:e("params.blockCentroidIntro")}),t.jsxs("div",{className:"space-y-2",children:[t.jsx(z,{label:e("params.sigma"),helpKey:"params.sigmaHelp",step:.1,value:((Pa=b.centroid)==null?void 0:Pa.sigma)??"",onChange:a=>x(l=>({...l,centroid:{...l.centroid,sigma:a}}))}),t.jsx(z,{label:e("params.maxArea"),helpKey:"params.maxAreaHelp",value:(($a=b.centroid)==null?void 0:$a.max_area)??"",onChange:a=>x(l=>({...l,centroid:{...l.centroid,max_area:a}}))}),t.jsx(z,{label:e("params.minArea"),helpKey:"params.minAreaHelp",value:((Ta=b.centroid)==null?void 0:Ta.min_area)??"",onChange:a=>x(l=>({...l,centroid:{...l.centroid,min_area:a}}))}),t.jsx(z,{label:e("params.filtsize"),helpKey:"params.filtsizeHelp",step:2,value:((Aa=b.centroid)==null?void 0:Aa.filtsize)??"",onChange:a=>x(l=>({...l,centroid:{...l.centroid,filtsize:a}}))})]})]})]}),t.jsxs("div",{className:"mt-4 rounded-md border border-outline-variant/20 bg-surface-container-highest/40 p-3",children:[t.jsx("div",{className:"mb-2 font-semibold text-on-surface-variant",children:e("sidebar.batchPresets")}),t.jsx("p",{className:"mb-2 text-[10px] leading-snug text-on-surface-variant",children:e("sidebar.batchHint")}),t.jsx("div",{className:"og-scrollbar max-h-36 space-y-1.5 overflow-y-auto",children:[...I,...se].map(a=>t.jsxs("label",{className:"flex cursor-pointer items-center gap-2",children:[t.jsx("input",{type:"checkbox",checked:Qt.has(a.id),onChange:()=>Os(a.id)}),t.jsx("span",{className:"truncate",children:a.name})]},a.id))})]}),t.jsxs("div",{className:"mt-6 border-t border-outline-variant/20 pt-4",children:[t.jsx("div",{className:"mb-2 font-semibold",children:e("btn.applyPresets")}),t.jsx("div",{className:"og-scrollbar max-h-24 space-y-1 overflow-y-auto",children:[...I,...se].map(a=>t.jsx("button",{type:"button",className:"block w-full truncate text-left text-primary hover:underline",onClick:()=>js(a.params),children:a.name},a.id))}),t.jsxs("div",{className:"mt-3 flex gap-1",children:[t.jsx("input",{className:"flex-1 rounded bg-surface-container-highest px-2 py-1",placeholder:e("placeholder.newPreset"),value:ut,onChange:a=>ia(a.target.value)}),t.jsx("button",{type:"button",className:"rounded bg-surface-container-high px-2",onClick:async()=>{if(ut.trim()){U(!0);try{await tn(ut.trim(),b),ia(""),await Q();const a=await Ht("user");Xt(a.presets)}catch(a){w(String(a))}finally{U(!1)}}},children:e("btn.savePreset")})]})]})]})]})]}),n==="pool"&&t.jsx(Ln,{uploads:y,onDelete:a=>void Ca(a)}),n==="history"&&t.jsx(In,{historyQ:Ne,setHistoryQ:ds,historyPage:Ue,setHistoryPage:na,historyData:Be,historyExpandId:ra,setHistoryExpandId:la,historyTotalPages:Ps,onDeleteExperiment:a=>void Fs(a),onSearch:()=>Wt(Ne,1,lt).then(a=>{na(1),dt(a)})})]}),t.jsx("div",{className:"pointer-events-none fixed bottom-4 right-4 z-[100] flex max-w-sm flex-col gap-2",children:cs.map(a=>t.jsx("div",{className:`pointer-events-auto rounded border px-3 py-2 text-left text-xs shadow-lg ${a.variant==="error"?"border-error/50 bg-error-container/95 text-on-error-container":a.variant==="warn"?"border-amber-500/40 bg-surface-container-highest/95 text-on-surface":"border-outline-variant/40 bg-surface-container-high/95 text-on-surface"}`,children:a.text},a.id))})]})}Hs.createRoot(document.getElementById("root")).render(t.jsx(zs.StrictMode,{children:t.jsx(Ws,{children:t.jsx(On,{})})})); diff --git a/web/static/analysis-lab/assets/analysis-fOaEdJ6w.js b/web/static/analysis-lab/assets/analysis-fOaEdJ6w.js deleted file mode 100644 index a22215c..0000000 --- a/web/static/analysis-lab/assets/analysis-fOaEdJ6w.js +++ /dev/null @@ -1,46 +0,0 @@ -var As=Object.defineProperty;var Ua=e=>{throw TypeError(e)};var Us=(e,s,n)=>s in e?As(e,s,{enumerable:!0,configurable:!0,writable:!0,value:n}):e[s]=n;var O=(e,s,n)=>Us(e,typeof s!="symbol"?s+"":s,n),Ba=(e,s,n)=>s.has(e)||Ua("Cannot "+n);var h=(e,s,n)=>(Ba(e,s,"read from private field"),n?n.call(e):s.get(e)),te=(e,s,n)=>s.has(e)?Ua("Cannot add the same private member more than once"):s instanceof WeakSet?s.add(e):s.set(e,n),Oe=(e,s,n,r)=>(Ba(e,s,"write to private field"),r?r.call(e,n):s.set(e,n),n);import{j as t,r as i,a as Bs,R as Hs}from"./client-D1ZVDB-N.js";import{c as Y,u as Te,T as ss,I as zs}from"./index-BoSUzpvv.js";import{R as Ws}from"./refresh-cw-B1jE4hda.js";import{R as Ha}from"./rotate-ccw-D6okH6-S.js";/** - * @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 Fe=Y("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 Vs=Y("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 za=Y("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 Gs=Y("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 Ks=Y("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 st=Y("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 qs=Y("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 Wa=Y("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 Va=Y("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"}]]),_="/api/dev";async function W(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 Js(){const e=await fetch(`${_}/analysis/uploads`);if(!e.ok)throw new Error(await e.text());return e.json()}async function Zs(e,s){const n=new URLSearchParams;s!=null&&s.deleteExperiments&&n.set("delete_experiments","true");const r=n.toString(),o=await fetch(`${_}/analysis/uploads/${encodeURIComponent(e)}${r?`?${r}`:""}`,{method:"DELETE"});if(!o.ok)throw new Error(await o.text());return await o.json().catch(()=>({}))}async function Bt(e,s="analysis_upload"){const n=new FormData;n.append("file",e),n.append("source",s);const r=await fetch(`${_}/analysis/upload`,{method:"POST",body:n}),o=await W(r);if(!r.ok)throw new Error(String(o.detail||r.status));return o}async function Ys(e){const s=await fetch(`${_}/analysis/uploads/import_from_debug`,{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({filename:e})}),n=await W(s);if(!s.ok)throw new Error(String(n.detail||s.status));return n}async function Ga(e){const s=await fetch(`${_}/analysis/uploads/replace_video`,{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify(e)}),n=await W(s);if(!s.ok)throw new Error(String(n.detail||s.status));return n}async function Xs(e,s){const n=await fetch(`${_}/analysis/solve/image`,{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({input_name:e,...s})}),r=await W(n);if(!n.ok)throw new Error(String(r.detail||n.status));return r}async function Qs(e,s){const n=await fetch(`${_}/analysis/solve/batch`,{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({input_name:e,runs:s})}),r=await W(n);if(!n.ok)throw new Error(String(r.detail||n.status));return r}async function Ht(e){const s=await fetch(`${_}/analysis/presets?scope=${e}`);if(!s.ok)throw new Error(await s.text());return s.json()}async function en(e,s){const n=await fetch(`${_}/analysis/presets`,{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({name:e,params:s})}),r=await W(n);if(!n.ok)throw new Error(String(r.detail||n.status));return r}async function zt(e){const s=await fetch(`${_}/analysis/experiments`,{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify(e)}),n=await W(s);if(!s.ok)throw new Error(String(n.detail||s.status));return n}async function tn(e){const s=await fetch(`${_}/analysis/experiments/${encodeURIComponent(e)}`,{method:"DELETE"});if(!s.ok)throw new Error(await s.text())}async function Wt(e,s,n=30){const r=new URLSearchParams({page:String(s),page_size:String(n)});e&&r.set("q",e);const o=await fetch(`${_}/analysis/experiments?${r}`);if(!o.ok)throw new Error(await o.text());return o.json()}async function an(){const e=await fetch(`${_}/debug/files`);if(!e.ok)throw new Error(await e.text());return e.json()}function sn(e){return`${_}/debug/files/${encodeURIComponent(e)}`}async function Ka(e){const s=await fetch(`${_}/debug/files/${encodeURIComponent(e)}/info`),n=await W(s);if(!s.ok)throw new Error(String(n.detail||s.status));return n}async function nn(e){const s=await fetch(`${_}/analysis/uploads/${encodeURIComponent(e)}/info`),n=await W(s);if(!s.ok)throw new Error(String(n.detail||s.status));return n}function Vt(e){return`${_}/analysis/uploads/file?filename=${encodeURIComponent(e)}`}async function qa(e){const s=await fetch(`${_}/analysis/experiments/export?format=${e}`);if(!s.ok)throw new Error(await s.text());return s.text()}async function rn(e){const s=await fetch(`${_}/analysis/uploads/${encodeURIComponent(e)}/experiment_count`);if(!s.ok)throw new Error(await s.text());return s.json()}async function ln(){const e=await fetch(`${_}/analysis/settings`);if(!e.ok)throw new Error(await e.text());return e.json()}async function on(e){const s=await fetch(`${_}/analysis/solve/frame`,{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify(e)}),n=await W(s);if(!s.ok)throw new Error(String(n.detail||s.status));return n}async function cn(e,s){const n=new FormData;n.append("file",e,"frame.jpg"),n.append("payload",JSON.stringify(s));const r=await fetch(`${_}/analysis/solve/frame_upload`,{method:"POST",body:n}),o=await W(r);if(!r.ok)throw new Error(String(o.detail||r.status));return o}function dn(e){return`${_}/analysis/experiments/${encodeURIComponent(e)}/asset`}async function un(){const e=await fetch("/api/dev/system/info");if(!e.ok)throw new Error(await e.text());return e.json()}function ns(e,s,n,r,o){var g,y,R,d;if(!r)return;if(e.clearRect(0,0,s,n),o.all&&Array.isArray(r.stars_all_centroids)){e.fillStyle="rgba(156, 163, 175, 0.85)";for(const m of r.stars_all_centroids)e.beginPath(),e.arc(m.x,m.y,2.4,0,Math.PI*2),e.fill()}if(o.rejected&&Array.isArray(r.stars_rejected_centroids)){e.strokeStyle="rgba(248, 113, 113, 0.95)",e.lineWidth=1.6;for(const m of r.stars_rejected_centroids)e.beginPath(),e.moveTo(m.x-4,m.y-4),e.lineTo(m.x+4,m.y+4),e.moveTo(m.x+4,m.y-4),e.lineTo(m.x-4,m.y+4),e.stroke()}if(o.pattern&&Array.isArray(r.stars_pattern)){e.strokeStyle="rgba(251, 146, 60, 0.95)",e.lineWidth=2;for(const m of r.stars_pattern)e.beginPath(),e.arc(m.x,m.y,6,0,Math.PI*2),e.stroke()}if(o.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 m of r.stars_matched)e.beginPath(),e.arc(m.x,m.y,7,0,Math.PI*2),e.stroke(),m.mag!=null&&e.fillText(`m${Number(m.mag).toFixed(1)}`,m.x+4,m.y-4)}const u=r.overlay_ext;if(Array.isArray(u==null?void 0:u.labels_topn)&&u.labels_topn.length>0){e.fillStyle="rgba(96, 165, 250, 0.95)",e.font="12px system-ui, sans-serif";for(const m of u.labels_topn){if(typeof(m==null?void 0:m.x)!="number"||typeof(m==null?void 0:m.y)!="number")continue;const b=typeof m.mag=="number"?` m${m.mag.toFixed(1)}`:"",x=`${m.name??"Star"}${b}`;e.fillText(x,m.x+8,m.y+14)}}const p=u==null?void 0:u.polar_guide;if(p&&typeof((g=p.frame_center)==null?void 0:g.x)=="number"&&typeof((y=p.frame_center)==null?void 0:y.y)=="number"&&typeof((R=p.target)==null?void 0:R.x)=="number"&&typeof((d=p.target)==null?void 0:d.y)=="number"){const m=p.frame_center.x,b=p.frame_center.y,x=p.target.x,I=p.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(m,b),e.lineTo(x,I),e.stroke(),e.beginPath(),e.arc(x,I,6,0,Math.PI*2),e.stroke();const M=typeof p.angular_sep_deg=="number"?p.angular_sep_deg.toFixed(2):"-";e.font="12px system-ui, sans-serif",e.fillText(`Polar ${M}deg`,x+10,I-6)}}function Ja(e,s,n,r){if(!n)return;const o=s.naturalWidth||1,u=s.naturalHeight||1;e.width=o,e.height=u,e.style.width=`${s.clientWidth}px`,e.style.height=`${s.clientHeight}px`;const p=e.getContext("2d");p&&ns(p,o,u,n,r)}function mn(e,s,n,r){if(!n)return;const o=s.videoWidth||1,u=s.videoHeight||1;if(o<2||u<2)return;e.width=o,e.height=u,e.style.width=`${s.clientWidth}px`,e.style.height=`${s.clientHeight}px`;const p=e.getContext("2d");p&&ns(p,o,u,n,r)}var N;(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"})(N||(N={}));const xn=(()=>{let e=0;return()=>e++})(),pn=new Error("ffmpeg is not loaded, call `await ffmpeg.load()` first"),fn=new Error("called FFmpeg.terminate()");var A,ae,Z,ue,me,ot,P;class hn{constructor(){te(this,A,null);te(this,ae,{});te(this,Z,{});te(this,ue,[]);te(this,me,[]);O(this,"loaded",!1);te(this,ot,()=>{h(this,A)&&(h(this,A).onmessage=({data:{id:s,type:n,data:r}})=>{switch(n){case N.LOAD:this.loaded=!0,h(this,ae)[s](r);break;case N.MOUNT:case N.UNMOUNT:case N.EXEC:case N.FFPROBE:case N.WRITE_FILE:case N.READ_FILE:case N.DELETE_FILE:case N.RENAME:case N.CREATE_DIR:case N.LIST_DIR:case N.DELETE_DIR:h(this,ae)[s](r);break;case N.LOG:h(this,ue).forEach(o=>o(r));break;case N.PROGRESS:h(this,me).forEach(o=>o(r));break;case N.ERROR:h(this,Z)[s](r);break}delete h(this,ae)[s],delete h(this,Z)[s]})});te(this,P,({type:s,data:n},r=[],o)=>h(this,A)?new Promise((u,p)=>{const g=xn();h(this,A)&&h(this,A).postMessage({id:g,type:s,data:n},r),h(this,ae)[g]=u,h(this,Z)[g]=p,o==null||o.addEventListener("abort",()=>{p(new DOMException(`Message # ${g} was aborted`,"AbortError"))},{once:!0})}):Promise.reject(pn));O(this,"load",({classWorkerURL:s,...n}={},{signal:r}={})=>(h(this,A)||(Oe(this,A,s?new Worker(new URL(s,import.meta.url),{type:"module"}):new Worker(new URL("/static/analysis-lab/assets/worker-BAOIWoxA.js",import.meta.url),{type:"module"})),h(this,ot).call(this)),h(this,P).call(this,{type:N.LOAD,data:n},void 0,r)));O(this,"exec",(s,n=-1,{signal:r}={})=>h(this,P).call(this,{type:N.EXEC,data:{args:s,timeout:n}},void 0,r));O(this,"ffprobe",(s,n=-1,{signal:r}={})=>h(this,P).call(this,{type:N.FFPROBE,data:{args:s,timeout:n}},void 0,r));O(this,"terminate",()=>{const s=Object.keys(h(this,Z));for(const n of s)h(this,Z)[n](fn),delete h(this,Z)[n],delete h(this,ae)[n];h(this,A)&&(h(this,A).terminate(),Oe(this,A,null),this.loaded=!1)});O(this,"writeFile",(s,n,{signal:r}={})=>{const o=[];return n instanceof Uint8Array&&o.push(n.buffer),h(this,P).call(this,{type:N.WRITE_FILE,data:{path:s,data:n}},o,r)});O(this,"mount",(s,n,r)=>{const o=[];return h(this,P).call(this,{type:N.MOUNT,data:{fsType:s,options:n,mountPoint:r}},o)});O(this,"unmount",s=>{const n=[];return h(this,P).call(this,{type:N.UNMOUNT,data:{mountPoint:s}},n)});O(this,"readFile",(s,n="binary",{signal:r}={})=>h(this,P).call(this,{type:N.READ_FILE,data:{path:s,encoding:n}},void 0,r));O(this,"deleteFile",(s,{signal:n}={})=>h(this,P).call(this,{type:N.DELETE_FILE,data:{path:s}},void 0,n));O(this,"rename",(s,n,{signal:r}={})=>h(this,P).call(this,{type:N.RENAME,data:{oldPath:s,newPath:n}},void 0,r));O(this,"createDir",(s,{signal:n}={})=>h(this,P).call(this,{type:N.CREATE_DIR,data:{path:s}},void 0,n));O(this,"listDir",(s,{signal:n}={})=>h(this,P).call(this,{type:N.LIST_DIR,data:{path:s}},void 0,n));O(this,"deleteDir",(s,{signal:n}={})=>h(this,P).call(this,{type:N.DELETE_DIR,data:{path:s}},void 0,n))}on(s,n){s==="log"?h(this,ue).push(n):s==="progress"&&h(this,me).push(n)}off(s,n){s==="log"?Oe(this,ue,h(this,ue).filter(r=>r!==n)):s==="progress"&&Oe(this,me,h(this,me).filter(r=>r!==n))}}A=new WeakMap,ae=new WeakMap,Z=new WeakMap,ue=new WeakMap,me=new WeakMap,ot=new WeakMap,P=new WeakMap;var Za;(function(e){e.MEMFS="MEMFS",e.NODEFS="NODEFS",e.NODERAWFS="NODERAWFS",e.IDBFS="IDBFS",e.WORKERFS="WORKERFS",e.PROXYFS="PROXYFS"})(Za||(Za={}));const bn=new Error("failed to get response body reader"),vn=new Error("failed to complete download"),gn="Content-Length",yn=e=>new Promise((s,n)=>{const r=new FileReader;r.onload=()=>{const{result:o}=r;o instanceof ArrayBuffer?s(new Uint8Array(o)):s(new Uint8Array)},r.onerror=o=>{var u,p;n(Error(`File could not be read! Code=${((p=(u=o==null?void 0:o.target)==null?void 0:u.error)==null?void 0:p.code)||-1}`))},r.readAsArrayBuffer(e)}),wn=async e=>{let s;if(typeof e=="string")/data:_data\/([a-zA-Z]*);base64,([^"]*)/.test(e)?s=atob(e.split(",")[1]).split("").map(n=>n.charCodeAt(0)):s=await(await fetch(e)).arrayBuffer();else if(e instanceof URL)s=await(await fetch(e)).arrayBuffer();else if(e instanceof File||e instanceof Blob)s=await yn(e);else return new Uint8Array;return new Uint8Array(s)},jn=async(e,s)=>{var o;const n=await fetch(e);let r;try{const u=parseInt(n.headers.get(gn)||"-1"),p=(o=n.body)==null?void 0:o.getReader();if(!p)throw bn;const g=[];let y=0;for(;;){const{done:m,value:b}=await p.read(),x=b?b.length:0;if(m){if(u!=-1&&u!==y)throw vn;s&&s({url:e,total:u,received:y,delta:x,done:m});break}g.push(b),y+=x,s&&s({url:e,total:u,received:y,delta:x,done:m})}const R=new Uint8Array(y);let d=0;for(const m of g)R.set(m,d),d+=m.length;r=R.buffer}catch(u){console.log("failed to send download progress event: ",u),r=await n.arrayBuffer()}return r},Ya=async(e,s,n=!1,r)=>{const o=n?await jn(e,r):await(await fetch(e)).arrayBuffer(),u=new Blob([o],{type:s});return URL.createObjectURL(u)};let Gt=null,Pe=null,F;async function Nn(){if(Gt)return Gt;if(Pe)return Pe;Pe=(async()=>{const e=new hn;e.on("progress",({progress:o})=>{F==null||F(o,"transcoding")}),F==null||F(.01,"loading_ffmpeg");const s="https://unpkg.com/@ffmpeg/core@0.12.6/dist/esm",n=await Ya(`${s}/ffmpeg-core.js`,"text/javascript");F==null||F(.04,"loading_ffmpeg");const r=await Ya(`${s}/ffmpeg-core.wasm`,"application/wasm");return F==null||F(.07,"loading_ffmpeg"),await e.load({coreURL:n,wasmURL:r}),Gt=e,e})();try{return await Pe}finally{Pe=null}}async function _n(e){const s=URL.createObjectURL(e);try{return await new Promise(r=>{const o=document.createElement("video");o.preload="metadata",o.onloadedmetadata=()=>{const u=Number(o.duration);r(Number.isFinite(u)&&u>0?u:null)},o.onerror=()=>r(null),o.src=s})}finally{URL.revokeObjectURL(s)}}async function Xa(e,s){const n=F;F=s;try{const r=await Nn(),o="input.avi",u="output.mp4";s==null||s(.1,"writing_input"),await r.writeFile(o,await wn(e)),await r.exec(["-i",o,"-c:v","libx264","-preset","veryfast","-crf","24","-pix_fmt","yuv420p","-movflags","+faststart","-an",u]);const p=await r.readFile(u);s==null||s(.98,"packing_output");const g=new Blob([p],{type:"video/mp4"}),y=new File([g],e.name.replace(/\.avi$/i,".mp4"),{type:"video/mp4"});await r.deleteFile(o),await r.deleteFile(u);const R=await _n(y);return s==null||s(1,"done"),{file:y,duration_s:R}}finally{F=n}}function Zt(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 $e(e,s){if(!e)return"—";try{const n=new Date(e);return new Intl.DateTimeFormat(s==="en"?"en-GB":"zh-CN",{dateStyle:"short",timeStyle:"medium"}).format(n)}catch{return e}}function de(e){return e==null?null:typeof e=="string"&&e.trim()?e.trim():typeof e=="number"&&!Number.isNaN(e)?String(e):null}function kn(e){return typeof e!="number"||e<=0?null:`${(e/1e6).toFixed(4)} s`}function Sn(e,s){if(!e)return[];const n=[],r=kn(e.exposure_us);r&&n.push({key:"meta.exposure",value:r});const o=de(e.analogue_gain),u=de(e.digital_gain);if(o||u){const b=[o?`A ${o}`:"",u?`D ${u}`:""].filter(Boolean);n.push({key:"meta.gain",value:b.join(" · ")})}const p=de(e.fps);p&&n.push({key:"meta.fps",value:p});const g=de(e.sensor);g&&n.push({key:"meta.sensor",value:g});const y=de(e.color_mode);y&&n.push({key:"meta.colorMode",value:y});const R=de(e.resolution);R&&n.push({key:"meta.outputResolution",value:R});const d=de(e.modified);d&&n.push({key:"meta.fileTime",value:$e(d,s)});const m=e.size;return typeof m=="number"&&m>0&&n.push({key:"meta.fileSize",value:Zt(m)}),n}function rs(e){const s=n=>typeof n=="number"&&!Number.isNaN(n)?n:null;return e?{tSolveMs:s(e.t_solve_ms),tExtractMs:s(e.t_extract_ms),tPreprocessMs:s(e.t_preprocess_ms),tOpenDecodeMs:s(e.t_open_decode_ms),tBackendTotalMs:s(e.t_backend_total_ms),raDeg:s(e.ra_deg),decDeg:s(e.dec_deg),matches:s(e.matches),rmseArcsec:s(e.rmse_arcsec),prob:s(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 it(e){return e==null?"—":`${e.toFixed(4)}°`}function ls(e){if(e==null||typeof e!="number"||Number.isNaN(e)||e<0)return null;if(e>=1)return 0;if(e===0)return 100;const s=(1-e)*100;if(s<=100-1e-12)return Math.min(100,Math.max(0,s));const n=-Math.log10(Math.max(e,Number.MIN_VALUE)),r=14,u=Math.min(1,Math.max(0,(n-r)/(50-r)));return Math.min(100,Math.max(0,70+30*u))}function En(e){const s=ls(e);return s==null?"—":`${s.toFixed(1)}%`}function Rn(e){if(e==null)return"—";if(typeof e=="number"&&!Number.isNaN(e)){const s=Math.abs(e);return s>0&&s<.001?e.toExponential(4):s>=0&&s<=1?`${(e*100).toFixed(4)}%`:String(e)}return String(e)}function ye(e,s){const n=s==null?void 0:s.tetra,r=n?n.Prob??n.prob:void 0,o=En(e),u=r!=null?Rn(r):null;return{line:o,rawLine:u}}function nt({result:e,t:s,roundTripMs:n}){const r=rs(e??void 0);return e?t.jsxs("div",{className:"mt-2 space-y-2 text-[10px]",children:[t.jsxs("div",{className:"grid grid-cols-2 gap-2",children:[r.tBackendTotalMs!=null&&t.jsxs("div",{children:[t.jsx("div",{className:"text-on-surface-variant",children:s("lab.metric.backendTotalMs")}),t.jsxs("div",{className:"font-semibold tabular-nums text-on-surface",children:[r.tBackendTotalMs.toFixed(0)," ms"]})]}),r.tSolveMs!=null&&t.jsxs("div",{children:[t.jsx("div",{className:"text-on-surface-variant",children:s("lab.metric.solveComputeMs")}),t.jsxs("div",{className:"font-semibold tabular-nums text-on-surface",children:[r.tSolveMs.toFixed(0)," ms"]})]}),r.tOpenDecodeMs!=null&&t.jsxs("div",{children:[t.jsx("div",{className:"text-on-surface-variant",children:s("lab.metric.openDecodeMs")}),t.jsxs("div",{className:"font-semibold tabular-nums text-on-surface",children:[r.tOpenDecodeMs.toFixed(0)," ms"]})]}),r.tPreprocessMs!=null&&t.jsxs("div",{children:[t.jsx("div",{className:"text-on-surface-variant",children:s("lab.metric.preprocessMs")}),t.jsxs("div",{className:"font-semibold tabular-nums text-on-surface",children:[r.tPreprocessMs.toFixed(0)," ms"]})]}),r.tExtractMs!=null&&t.jsxs("div",{children:[t.jsx("div",{className:"text-on-surface-variant",children:s("lab.metric.extractMs")}),t.jsxs("div",{className:"font-semibold tabular-nums text-on-surface",children:[r.tExtractMs.toFixed(0)," ms"]})]}),n!=null&&t.jsxs("div",{children:[t.jsx("div",{className:"text-on-surface-variant",children:s("lab.metric.solveRoundTripMs")}),t.jsxs("div",{className:"font-semibold tabular-nums text-on-surface",children:[n.toFixed(0)," ms"]})]}),r.matches!=null&&t.jsxs("div",{children:[t.jsx("div",{className:"text-on-surface-variant",children:s("lab.metric.matches")}),t.jsx("div",{className:"font-semibold tabular-nums text-on-surface",children:r.matches})]}),r.rmseArcsec!=null&&t.jsxs("div",{children:[t.jsx("div",{className:"text-on-surface-variant",children:s("lab.metric.rmse")}),t.jsxs("div",{className:"font-semibold tabular-nums text-on-surface",children:[r.rmseArcsec.toFixed(2),"″"]})]}),r.prob!=null&&t.jsxs("div",{className:"col-span-2",children:[t.jsx("div",{className:"text-on-surface-variant",children:s("lab.metric.prob")}),t.jsx("div",{className:"font-semibold text-on-surface",children:ye(r.prob,e).line}),t.jsx("p",{className:"mt-0.5 text-[9px] leading-snug text-on-surface-variant/90",children:s("lab.metric.probHelp")}),ye(r.prob,e).rawLine&&t.jsxs("div",{className:"mt-1 text-[9px] text-on-surface-variant",children:[s("lab.metric.probRaw"),": ",ye(r.prob,e).rawLine,t.jsx("p",{className:"mt-0.5 text-[8px] leading-snug opacity-90",children:s("lab.metric.probRawHelp")})]})]})]}),e.centroid_quality!=null&&typeof e.centroid_quality=="object"&&t.jsxs("div",{className:"rounded border border-outline-variant/25 bg-surface-container-high/40 p-2",children:[t.jsxs("div",{className:"text-[9px] font-medium text-on-surface-variant",children:[s("lab.centroidQualityTitle"),typeof e.centroid_quality.level=="number"?` · L${e.centroid_quality.level}`:""]}),Array.isArray(e.centroid_quality.hints)&&e.centroid_quality.hints.length>0?t.jsx("ul",{className:"mt-1 list-inside list-disc text-[9px] leading-snug text-on-surface-variant",children:e.centroid_quality.hints.map((o,u)=>t.jsx("li",{children:o},u))}):null,(()=>{const o=e.centroid_quality.metrics;if(!o)return null;const u=o.input_count,p=o.output_count,g=o.removed_dense,y=o.removed_line;return typeof u!="number"||typeof p!="number"||typeof g!="number"||typeof y!="number"?null:t.jsx("p",{className:"mt-1 font-mono text-[9px] text-on-surface-variant/95",children:s("lab.centroidQualityMetrics",{in:u,out:p,dense:g,line:y})})})()]}),t.jsxs("div",{children:[t.jsx("div",{className:"text-on-surface-variant",children:s("lab.metric.radec")}),t.jsxs("div",{className:"font-mono text-[9px] text-on-surface",children:["α ",it(r.raDeg)," · δ ",it(r.decDeg)]})]}),r.status&&t.jsxs("div",{className:"rounded bg-surface-container-high px-2 py-1 text-[9px] font-mono text-on-surface",children:[s("lab.metric.status"),": ",r.status]})]}):t.jsx("p",{className:"mt-2 text-[10px] text-on-surface-variant",children:"—"})}function z({label:e,helpKey:s,value:n,onChange:r,type:o="number",step:u}){const{t:p}=Te(),g=s?p(s):void 0;return t.jsxs("label",{className:"block",children:[t.jsx("span",{className:"text-[10px] font-medium text-on-surface-variant",children:e}),g&&t.jsx("p",{className:"mb-1 mt-0.5 text-[9px] leading-snug text-on-surface-variant/85",children:g}),t.jsx("input",{type:o,step:u,className:"w-full rounded bg-surface-container-highest px-2 py-1",value:n===""?"":n,onChange:y=>{const R=y.target.value;r(R===""?void 0:Number(R))}})]})}function Mn({view:e,setView:s}){const{t:n,locale:r,setLocale:o}=Te();return t.jsxs("header",{className:"flex h-12 shrink-0 items-center justify-between border-b border-outline-variant/20 px-4",children:[t.jsxs("div",{className:"flex items-center gap-6",children:[t.jsx("span",{className:"font-headline text-sm font-bold tracking-wide text-on-surface",children:n("app.title")}),t.jsxs("nav",{className:"flex flex-wrap gap-3 text-xs",children:[t.jsx("button",{type:"button",className:e==="lab_image"?"text-primary":"text-on-surface-variant",onClick:()=>s("lab_image"),children:n("nav.labImage")}),t.jsx("button",{type:"button",className:e==="lab_video"?"text-primary":"text-on-surface-variant",onClick:()=>s("lab_video"),children:n("nav.labVideo")}),t.jsx("button",{type:"button",className:e==="pool"?"text-primary":"text-on-surface-variant",onClick:()=>s("pool"),children:n("nav.pool")}),t.jsx("button",{type:"button",className:e==="history"?"text-primary":"text-on-surface-variant",onClick:()=>s("history"),children:n("nav.history")})]})]}),t.jsxs("div",{className:"flex items-center gap-2",children:[t.jsxs("div",{className:"mr-2 flex gap-1 text-[10px]",children:[t.jsx("button",{type:"button",className:`rounded px-2 py-0.5 ${r==="zh"?"bg-primary-container text-on-primary-container":"text-on-surface-variant"}`,onClick:()=>o("zh"),children:n("lang.zh")}),t.jsx("button",{type:"button",className:`rounded px-2 py-0.5 ${r==="en"?"bg-primary-container text-on-primary-container":"text-on-surface-variant"}`,onClick:()=>o("en"),children:n("lang.en")})]}),t.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:[t.jsx(Ks,{className:"h-3.5 w-3.5"})," ",n("nav.systemAdmin")]})]})]})}function Cn({historyQ:e,setHistoryQ:s,historyPage:n,setHistoryPage:r,historyData:o,historyExpandId:u,setHistoryExpandId:p,historyTotalPages:g,onDeleteExperiment:y,onSearch:R}){var b;const{t:d,locale:m}=Te();return t.jsxs("main",{className:"og-scrollbar flex-1 overflow-auto p-4",children:[t.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:d("history.intro")}),t.jsxs("div",{className:"mb-4 flex flex-wrap items-center gap-4",children:[t.jsxs("h2",{className:"flex items-center gap-2 text-lg font-semibold",children:[t.jsx(Gs,{className:"h-5 w-5"})," ",d("history.title")]}),t.jsx("input",{className:"rounded bg-surface-container-highest px-2 py-1 text-xs",placeholder:d("history.search"),value:e,onChange:x=>s(x.target.value)}),t.jsx("button",{type:"button",className:"rounded bg-surface-container px-2 py-1 text-xs",onClick:R,children:d("history.searchBtn")}),t.jsx("button",{type:"button",className:"rounded bg-primary-container/50 px-2 py-1 text-xs",onClick:()=>qa("json").then(x=>{const I=new Blob([x],{type:"application/json"}),M=document.createElement("a");M.href=URL.createObjectURL(I),M.download="experiments.json",M.click()}),children:d("history.exportJson")}),t.jsx("button",{type:"button",className:"rounded bg-primary-container/50 px-2 py-1 text-xs",onClick:()=>qa("csv").then(x=>{const I=new Blob([x],{type:"text/csv"}),M=document.createElement("a");M.href=URL.createObjectURL(I),M.download="experiments.csv",M.click()}),children:d("history.exportCsv")})]}),t.jsxs("div",{className:"flex flex-wrap items-center gap-3 text-xs text-on-surface-variant",children:[t.jsx("span",{children:d("history.total",{n:(o==null?void 0:o.total)??0})}),t.jsx("button",{type:"button",className:"rounded border border-outline-variant/30 px-2 py-0.5 disabled:opacity-40",disabled:n<=1,onClick:()=>r(x=>Math.max(1,x-1)),children:d("history.prev")}),t.jsxs("span",{children:[n," / ",g]}),t.jsx("button",{type:"button",className:"rounded border border-outline-variant/30 px-2 py-0.5 disabled:opacity-40",disabled:n>=g,onClick:()=>r(x=>x+1),children:d("history.next")})]}),t.jsx("ul",{className:"mt-2 space-y-2",children:(b=o==null?void 0:o.items)==null?void 0:b.map(x=>{const I=String(x.id??""),M=x.metrics,se=u===I;return t.jsxs("li",{className:"rounded border border-outline-variant/20 p-2 text-[11px]",children:[t.jsxs("div",{className:"flex flex-wrap items-start justify-between gap-2 font-mono",children:[t.jsxs("div",{children:[t.jsxs("div",{className:"text-on-surface",children:[$e(String(x.created_at??""),m)," —"," ",String(x.input_name)," — ",String(x.preset_label)]}),t.jsxs("div",{className:"mt-1 text-on-surface-variant",children:[d("history.preset"),": ",String(x.preset_label)," · ",d("history.metrics"),":"," ","matches=",String((M==null?void 0:M.matches)??"—")," rmse=",String((M==null?void 0:M.rmse_arcsec)??"—")]})]}),t.jsxs("div",{className:"flex shrink-0 gap-1",children:[t.jsx("button",{type:"button",className:"rounded bg-surface-container px-2 py-0.5 text-[10px]",onClick:()=>p(se?null:I),children:d(se?"history.collapse":"history.detail")}),t.jsx("button",{type:"button",className:"rounded px-2 py-0.5 text-[10px] text-error hover:bg-error-container/20",title:d("history.delete"),onClick:()=>void y(I),children:d("history.delete")})]})]}),se&&t.jsxs("div",{className:"mt-2 space-y-2",children:[x.asset_snapshot_relpath?t.jsx("img",{src:dn(I),alt:"",className:"max-h-48 max-w-full rounded border border-outline-variant/20 object-contain"}):null,t.jsx("pre",{className:"og-scrollbar max-h-64 overflow-auto rounded bg-surface-container p-2 text-[10px]",children:JSON.stringify(x.result_json,null,2)})]})]},I)})})]})}function In({uploads:e,onDelete:s}){const{t:n,locale:r}=Te();return t.jsxs("main",{className:"og-scrollbar flex-1 overflow-auto p-4",children:[t.jsxs("h2",{className:"mb-4 flex items-center gap-2 text-lg font-semibold",children:[t.jsx(Vs,{className:"h-5 w-5"})," ",n("pool.title")]}),t.jsxs("table",{className:"w-full text-left text-xs",children:[t.jsx("thead",{children:t.jsxs("tr",{className:"border-b border-outline-variant/30 text-on-surface-variant",children:[t.jsx("th",{className:"py-2",children:n("pool.col.name")}),t.jsx("th",{className:"py-2",children:n("pool.col.source")}),t.jsx("th",{className:"py-2",children:n("pool.col.size")}),t.jsx("th",{className:"py-2",children:n("pool.col.time")}),t.jsx("th",{className:"w-16 py-2 text-center",children:n("pool.delete")})]})}),t.jsx("tbody",{children:e.map(o=>t.jsxs("tr",{className:"border-b border-outline-variant/10",children:[t.jsx("td",{className:"py-2 font-mono",children:o.filename}),t.jsx("td",{className:"py-2",children:o.source??n("common.placeholder")}),t.jsx("td",{className:"py-2",children:Zt(o.size)}),t.jsx("td",{className:"py-2",children:$e(o.modified_at,r)}),t.jsx("td",{className:"py-2 text-center",children:t.jsx("button",{type:"button",className:"inline-flex rounded p-1 text-on-surface-variant hover:bg-error-container/30 hover:text-error",title:n("pool.delete"),"aria-label":n("pool.delete"),onClick:()=>void s(o.filename),children:t.jsx(ss,{className:"h-3.5 w-3.5"})})})]},o.filename))})]})]})}function Kt(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 Ln(e){var n,r;if(!e)return null;const s=e.solve_overlay;return(n=s==null?void 0:s.stars_matched)!=null&&n.length?s.stars_matched.length:(r=s==null?void 0:s.stars_all_centroids)!=null&&r.length?s.stars_all_centroids.length:typeof e.matches=="number"?e.matches:null}function Qa(e){return/\.(jpe?g|png|webp|bmp|gif|fits?)$/i.test(e)}function ge(e){return/\.(mp4|mov|webm|mkv|avi)$/i.test(e)}function rt(e,s=16,n=14){return e.length<=s+n+1?e:`${e.slice(0,s)}...${e.slice(-n)}`}const es=10,lt=30,qt=6,Jt=()=>({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:1280,large_scale_bg_subtract:!1,detail_level:"summary",centroid_rejection_level:3,centroid:{sigma:2.5,max_area:400,min_area:5,filtsize:25,binary_open:!0,max_axis_ratio:void 0}});function ts(e,s,n,r){const o=typeof r=="number"&&r>0?` ${e("lab.gateNextMs",{ms:r})}`:"",u=String(n??"").toLowerCase();return s==="SKIPPED_BUSY"?u.includes("recording")?`${e("lab.gate.skipRecording")}${o}`:u.includes("previous")?`${e("lab.gate.skipBusyInFlight")}${o}`:`${e("lab.gate.skipBusyFallback",{detail:String(n??"")})}${o}`:s==="SKIPPED_INTERVAL"?`${e("lab.gate.skipInterval")}${o}`:`${s??"?"}${n?`: ${String(n)}`:""}${o}`}async function as(){try{const e=await fetch("/api/dev/debug/camera/stream/status",{cache:"no-store",credentials:"same-origin"});if(!e.ok)return"fail";const s=await e.json();if(s.success===!1)return"fail";const n=Number(s.max_clients??0),r=Number(s.active_clients??0);return n>0&&r>=n?"busy":"ok"}catch{return"fail"}}function Dn(){var La,Da,Oa,Fa,Pa,$a,Ta;const{t:e,locale:s}=Te(),[n,r]=i.useState("lab_image"),[o,u]=i.useState(!1),[p,g]=i.useState(!1),[y,R]=i.useState([]),[d,m]=i.useState(null),[b,x]=i.useState(Jt),[I,M]=i.useState([]),[se,Yt]=i.useState([]),[Xt,is]=i.useState(new Set),[ne,U]=i.useState(!1),[ct,w]=i.useState(null),[k,we]=i.useState(null),[T,je]=i.useState(null),[Ae,Qt]=i.useState([]),[os,ea]=i.useState([]),ta=i.useRef(null),aa=i.useRef(null),[Ne,cs]=i.useState(""),[Ue,sa]=i.useState(1),[Be,dt]=i.useState(null),[na,ra]=i.useState(null),[ut,la]=i.useState(""),[V,ds]=i.useState({matched:!0,pattern:!0,all:!0,rejected:!0}),[He,ia]=i.useState([]),[B,oa]=i.useState(null),[_e,ke]=i.useState(1),[ze,ca]=i.useState(!1),[xe,mt]=i.useState(1),[da,us]=i.useState({w:0,h:0}),[pe,ms]=i.useState({w:0,h:0}),[ua,ma]=i.useState({w:0,h:0}),[S,Se]=i.useState("file"),[xa,We]=i.useState(()=>Date.now()),[xt,G]=i.useState("unknown"),[Ee,pt]=i.useState(null),[Re,pa]=i.useState(!1),[fe,fa]=i.useState(!1),[ha,xs]=i.useState(!1),[K,ft]=i.useState(!1),[ps,ht]=i.useState(null),[bt,vt]=i.useState(null),[gt,Ve]=i.useState(null),[yt,ba]=i.useState(!1),[fs,wt]=i.useState({}),[va,jt]=i.useState(!1),[Nt,q]=i.useState(null),[hs,Me]=i.useState(null),[_t,Ce]=i.useState(null),[kt,ga]=i.useState(null),[ya,wa]=i.useState(!1),[ja,St]=i.useState(null),[bs,X]=i.useState(null),[Na,_a]=i.useState(!1),[Et,re]=i.useState(null),[vs,H]=i.useState(null),[Rt,Mt]=i.useState(null),Ct=i.useRef(null),Ie=i.useRef(null),Le=i.useRef(null),It=i.useRef(0),le=i.useRef(null),ie=i.useRef(null),Lt=i.useRef(!1),Dt=i.useRef(!1),Ot=i.useRef(!1),Ft=i.useRef(!1),he=i.useRef(null),gs=i.useCallback(async()=>{const a=Le.current;if(!a||a.naturalWidth<2)return null;const l=document.createElement("canvas");l.width=a.naturalWidth,l.height=a.naturalHeight;const c=l.getContext("2d");if(!c)return null;try{c.drawImage(a,0,0)}catch{return null}return await new Promise(f=>{l.toBlob(j=>f(j?URL.createObjectURL(j):null),"image/jpeg",.92)})},[]),[Ge,ys]=i.useState(null),[L,ka]=i.useState(null),Ke=i.useCallback((a,l)=>{const c=`${Date.now()}-${Math.random().toString(36).slice(2,9)}`;ea(f=>[...f,{id:c,text:a,variant:l}]),window.setTimeout(()=>{ea(f=>f.filter(j=>j.id!==c))},4800)},[]);i.useEffect(()=>{ta.current=k},[k]),i.useEffect(()=>{aa.current=T},[T]),i.useEffect(()=>{Ot.current=fe},[fe]),i.useEffect(()=>{Ft.current=Re},[Re]),i.useEffect(()=>{ct&&(Ke(ct,"error"),w(null))},[ct,Ke]),i.useEffect(()=>{_t&&(Ke(_t,"warn"),Ce(null))},[_t,Ke]);const qe=i.useCallback(()=>{const a=ta.current,l=aa.current;if(!a&&!l)return;const c=`${Date.now()}-${Math.random().toString(36).slice(2,9)}`;Qt(f=>[{id:c,at:Date.now(),single:a??void 0,batch:l??void 0},...f].slice(0,es))},[]),Je=()=>{ie.current!=null&&(window.clearTimeout(ie.current),ie.current=null)},Ze=()=>{le.current!=null&&(window.clearTimeout(le.current),le.current=null)},Pt=(L==null?void 0:L.star_analysis_min_interval_ms)??2e3,$t=(L==null?void 0:L.star_analysis_max_interval_ms)??12e3,Tt=i.useMemo(()=>{const a=(L==null?void 0:L.star_analysis_target_fps)??.6666666666666666,l=Math.min(Math.max(a,.2),5);return Math.round(1e3/l)},[L]),oe=i.useMemo(()=>Math.min(Math.max(kt??Tt,Pt),$t),[Tt,kt,$t,Pt]),Q=i.useCallback(async()=>{const[a,l,c]=await Promise.all([Js(),Ht("official"),Ht("user")]);R(a.files),M(l.presets),Yt(c.presets)},[]),At=i.useCallback(()=>{an().then(a=>ia(a.files)).catch(()=>ia([]))},[]);i.useEffect(()=>{ln().then(a=>{ka(a);const l=Math.round(1e3/Math.min(Math.max(a.star_analysis_target_fps,.2),5)),c=Math.min(Math.max(l,a.star_analysis_min_interval_ms),a.star_analysis_max_interval_ms);ga(c);const f=a.centroid_rejection_level_default;typeof f=="number"&&f>=1&&f<=5&&x(j=>({...j,centroid_rejection_level:f}))}).catch(()=>ka(null))},[]),i.useEffect(()=>{Q().catch(a=>w(String(a)))},[Q]),i.useEffect(()=>{At()},[At]),i.useEffect(()=>{n==="history"&&Wt(Ne,Ue,lt).then(dt).catch(a=>w(String(a)))},[n,Ne,Ue]),i.useEffect(()=>{if(!d){Ve(null);return}let a=!1;return ba(!0),(async()=>{try{const l=await nn(d);if(a)return;let c={...l};try{c={...await Ka(d),...l}}catch{}Ve(c)}catch{try{const l=await Ka(d);a||Ve(l)}catch{a||Ve(null)}}finally{a||ba(!1)}})(),()=>{a=!0}},[d]),i.useEffect(()=>{we(null),je(null),wt({}),jt(!1),q(null),Me(null),Se("file"),pt(null),Qt([])},[d]);const J=i.useMemo(()=>{const a=k==null?void 0:k.result;if(!a)return null;const l=a.solve_overlay||null;if(!l)return null;const c=a.overlay_ext||null;return{...l,overlay_ext:c||void 0}},[k]),ee=i.useMemo(()=>(k==null?void 0:k.result)??null,[k]),Sa=i.useMemo(()=>Ln(ee),[ee]),Ut=i.useMemo(()=>n==="lab_image"?da:n==="lab_video"?S==="file"?pe:ua:{w:0,h:0},[n,S,da,pe,ua]),Ye=d?Vt(d):"";i.useEffect(()=>{if(n!=="lab_image")return;const a=Ct.current,l=he.current;if(!a||!l||!J||!d)return;const c=()=>Ja(l,a,J,V);a.complete?c():a.onload=c},[J,d,k,V,n]),i.useEffect(()=>{if(n!=="lab_video"||S!=="file")return;const a=Ie.current,l=he.current;if(!a||!l||!J||!d)return;const c=()=>mn(l,a,J,V);return a.addEventListener("loadeddata",c),a.addEventListener("seeked",c),a.readyState>=2&&c(),()=>{a.removeEventListener("loadeddata",c),a.removeEventListener("seeked",c)}},[J,V,n,S,d,k]),i.useEffect(()=>{if(n!=="lab_video"||S!=="camera")return;const a=Le.current,l=he.current;if(!a||!l||!J)return;const c=()=>Ja(l,a,J,V);a.complete?c():a.onload=c},[J,V,n,S,k,xa,K]),i.useEffect(()=>{if(n!=="lab_video"||S!=="camera"){G("unknown");return}if(K){G("ok");return}let a=!1;return G("unknown"),(async()=>{try{const l=await as();if(a)return;if(l==="fail"){G("fail");return}if(l==="busy"){G("busy");return}G("ok"),It.current=0,We(Date.now())}catch{a||G("fail")}})(),()=>{a=!0}},[n,S,K]),i.useEffect(()=>{const a=Ct.current;if(!a)return;const l=()=>us({w:a.naturalWidth||0,h:a.naturalHeight||0});return l(),a.addEventListener("load",l),()=>a.removeEventListener("load",l)},[d,Ye]),i.useEffect(()=>{const a=Ie.current;if(!a)return;const l=()=>ms({w:a.videoWidth||0,h:a.videoHeight||0});return a.addEventListener("loadedmetadata",l),a.addEventListener("loadeddata",l),l(),()=>{a.removeEventListener("loadedmetadata",l),a.removeEventListener("loadeddata",l)}},[d,Ye,n]);const ws=a=>{x({...Jt(),...a,centroid:{...Jt().centroid,...a.centroid}})},js=async()=>{if(!d){w(e("err.selectFile"));return}w(null),U(!0);const a=performance.now();try{const l=await Xs(d,b);qe(),we(l),je(null),Me("file"),q(performance.now()-a)}catch(l){w(String(l)),q(null)}finally{U(!1)}},Ns=async()=>{if(!d){w(e("err.selectFile"));return}const a=[];for(const c of Xt){const j=[...I,...se].find($=>$.id===c);j&&a.push({label:j.name,params:structuredClone(j.params)})}if(a.length===0){w(e("err.selectPresets"));return}w(null),U(!0);const l=performance.now();try{const c=await Qs(d,a);qe(),je({results:c.results}),we(null),wt({}),Me("file"),q(performance.now()-l)}catch(c){w(String(c)),q(null)}finally{U(!1)}},Xe=async a=>{if(Lt.current)return;w(null),Lt.current=!0;const l=performance.now();try{const c=await on({source:"camera",overlay_topn_count:3,enable_polar_guide:!0,solve_interval_ms:oe,solve_timeout_ms:Math.min(((L==null?void 0:L.solver_timeout_ms)??1500)*.6,1200),...b}),f=c.gate_status,j=c.next_allowed_in_ms,$=c.effective_interval_ms;if(f==="SKIPPED_INTERVAL"||f==="SKIPPED_BUSY"){if(Ce(ts(e,f,c.gate_reason,typeof j=="number"?j:null)),a){Ze();const C=Math.max(50,Number(j??oe));le.current=window.setTimeout(()=>{le.current=null,Ft.current&&Xe(!0)},C)}return}Ce(null),c.result!=null&&(qe(),we(c)),je(null),Me("camera"),Se("camera");const E=c.result,D=typeof(E==null?void 0:E.status)=="string"?String(E.status):"";if(ha&&D==="MATCH_FOUND"){ft(!0),ht(c.frame_id!=null?String(c.frame_id):null);const C=await gs();vt(ce=>(ce&&URL.revokeObjectURL(ce),C)),tt()}else if(a){Ze();const C=Math.max(50,Number(j??$??oe));le.current=window.setTimeout(()=>{le.current=null,Ft.current&&Xe(!0)},C)}q(performance.now()-l)}catch(c){w(String(c)),q(null)}finally{Lt.current=!1}},_s=()=>void Xe(!1),Qe=async a=>{if(Dt.current||!d)return;const l=Ie.current;if(!l||l.videoWidth<2||l.videoHeight<2||Ee)return;w(null),Dt.current=!0;const c=performance.now();try{const f=document.createElement("canvas");f.width=l.videoWidth,f.height=l.videoHeight;const j=f.getContext("2d");if(!j)throw new Error("无法创建画布上下文 / Cannot create canvas context");j.drawImage(l,0,0,f.width,f.height);const $=await new Promise((ve,Ts)=>{f.toBlob(Aa=>Aa?ve(Aa):Ts(new Error("帧编码失败 / Frame encode failed")),"image/jpeg",.92)}),E=await cn($,{...b,solve_interval_ms:oe,solve_timeout_ms:Math.min(((L==null?void 0:L.solver_timeout_ms)??1500)*.6,1200),overlay_topn_count:3,enable_polar_guide:!0}),D=E.gate_status,C=E.next_allowed_in_ms,ce=E.effective_interval_ms;if(D==="SKIPPED_INTERVAL"||D==="SKIPPED_BUSY"){if(Ce(ts(e,D,E.gate_reason,typeof C=="number"?C:null)),a){Je();const ve=Math.max(50,Number(C??oe));ie.current=window.setTimeout(()=>{ie.current=null,Ot.current&&Qe(!0)},ve)}return}if(Ce(null),E.result!=null&&(qe(),we(E)),je(null),Me("file"),q(performance.now()-c),a){Je();const ve=Math.max(50,Number(C??ce??oe));ie.current=window.setTimeout(()=>{ie.current=null,Ot.current&&Qe(!0)},ve)}}catch(f){w(String(f)),q(null)}finally{Dt.current=!1}},ks=()=>void Qe(!1),Ss=()=>{fe||!d||(fa(!0),Je(),Qe(!0))},Ea=i.useMemo(()=>n!=="lab_video"||S!=="file"||!d||/\.avi$/i.test(d)||Ee?!1:pe.w>1&&pe.h>1,[n,S,d,Ee,pe.w,pe.h]),Ra=i.useMemo(()=>!!d&&/\.avi$/i.test(d),[d]),Es=async()=>{if(!(!d||!Ra||ya)){w(null),wa(!0),St(0),X(e("lab.transcode.loading"));try{const a=Vt(d),l=await fetch(a,{cache:"no-store"});if(!l.ok)throw new Error(e("lab.transcode.fetchFailed"));const c=await l.blob(),f=new File([c],d,{type:"video/x-msvideo"}),j=await Xa(f,(E,D)=>{St(Math.max(0,Math.min(1,E))),D==="loading_ffmpeg"?X(e("lab.transcode.loading")):D==="writing_input"?X(e("lab.transcode.writingBuffer")):D==="transcoding"?X(e("lab.transcode.running")):D==="packing_output"&&X(e("lab.transcode.packaging"))});X(e("lab.transcode.uploading"));const $=await Bt(j.file,"analysis_transcoded");await Ga({old_filename:d,new_filename:$.filename,duration_s:j.duration_s,nominal_fps:null,codec_fourcc:"libx264",container:"MP4"}),await Q(),m($.filename),St(1),X(e("lab.transcode.done"))}catch(a){w(String(a)),X(e("lab.transcode.failed"))}finally{wa(!1)}}},Rs=async a=>{H(e("sidebar.flowDownloadingPool")),re(.19);const l=Vt(a),c=await fetch(l,{cache:"no-store"});if(!c.ok)throw new Error(e("lab.transcode.fetchFailed"));const f=await c.blob(),j=new File([f],a,{type:"video/x-msvideo"}),$=await Xa(j,(D,C)=>{re(.2+Math.max(0,Math.min(1,D))*.6),C==="loading_ffmpeg"?H(e("sidebar.flowLoadingTranscoder")):C==="writing_input"?H(e("sidebar.flowWritingBuffer")):C==="transcoding"?H(e("sidebar.flowTranscoding")):C==="packing_output"&&H(e("sidebar.flowPackaging"))});H(e("sidebar.flowUploadingMp4")),re(.86);const E=await Bt($.file,"debug_console_transcoded");return H(e("sidebar.flowReplacing")),re(.94),await Ga({old_filename:a,new_filename:E.filename,duration_s:$.duration_s,nominal_fps:null,codec_fourcc:"libx264",container:"MP4"}),E.filename},Ms=async()=>{if(!B||Na)return;const a=/\.avi$/i.test(B);w(null),_a(!0),re(.02),Mt(null);try{H(e("sidebar.flowImportingDebug"));const l=await Ys(B);re(a?.18:1);let c=l.filename;a?c=await Rs(l.filename):H(e("sidebar.flowNoTranscode")),await Q(),m(c),r(ge(c)?"lab_video":"lab_image"),re(1),H(e("sidebar.flowDone")),Mt(e("sidebar.flowDoneMsg",{name:rt(c)}))}catch(l){w(String(l)),H(e("sidebar.flowFailed")),Mt(String(l))}finally{_a(!1)}},et=()=>{fa(!1),Je()},Cs=()=>{Re||K||(pa(!0),Ze(),Xe(!0))},tt=()=>{pa(!1),Ze()},Is=async()=>{try{const a=Le.current;a&&(a.onload=null,a.onerror=null,a.src="",a.removeAttribute("src")),await new Promise(l=>window.requestAnimationFrame(()=>l())),await fetch("/api/dev/debug/camera/stop",{method:"POST"}),We(Date.now()),S==="camera"&&Se("file")}catch{}},Ls=()=>{ft(!1),ht(null),vt(a=>(a&&URL.revokeObjectURL(a),null)),We(Date.now())},Ds=a=>{is(l=>{const c=new Set(l);return c.has(a)?c.delete(a):c.add(a),c})},Ma=async a=>{if(!window.confirm(e("delete.uploadFirst",{name:a})))return;let l=0;try{l=(await rn(a)).count}catch{l=0}if(l>0){if(!window.confirm(e("delete.uploadCascade",{n:l})))return}else if(!window.confirm(e("delete.uploadSecond")))return;U(!0),w(null);try{await Zs(a,{deleteExperiments:l>0}),await Q(),d===a&&m(null)}catch(c){w(String(c))}finally{U(!1)}},Os=async a=>{if(window.confirm(e("delete.experimentFirst"))&&window.confirm(e("delete.experimentSecond"))){U(!0),w(null);try{await tn(a),na===a&&ra(null);const l=await Wt(Ne,Ue,lt);dt(l)}catch(l){w(String(l))}finally{U(!1)}}},at=a=>mt(Math.min(4,Math.max(.5,a))),Fs=Math.max(1,Math.ceil(((Be==null?void 0:Be.total)??0)/lt)),be=i.useMemo(()=>n==="lab_image"?He.filter(a=>a.type==="image"||Qa(a.name)):n==="lab_video"?He.filter(a=>a.type==="video"||ge(a.name)):He,[He,n]),De=Math.max(1,Math.ceil(be.length/qt)),Ps=i.useMemo(()=>{const a=(_e-1)*qt;return be.slice(a,a+qt)},[be,_e]);i.useEffect(()=>{ke(1)},[n]),i.useEffect(()=>{ke(a=>Math.min(a,De))},[De]),i.useEffect(()=>{B&&!be.some(a=>a.name===B)&&oa(null)},[be,B]);const Ca=i.useMemo(()=>Sn(gt,s),[gt,s]),$s=i.useMemo(()=>n==="lab_image"?y.filter(a=>Qa(a.filename)):n==="lab_video"?y.filter(a=>ge(a.filename)):y,[y,n]),v=i.useMemo(()=>rs(ee),[ee]),Ia=i.useMemo(()=>ls(v.prob),[v.prob]);return i.useEffect(()=>{jt(!1)},[k]),i.useEffect(()=>{if(n!=="lab_video")return;let a;const l=()=>{un().then(ys).catch(()=>{})};return l(),a=setInterval(l,1500),()=>{a&&clearInterval(a)}},[n]),i.useEffect(()=>{(n!=="lab_video"||S!=="camera")&&(tt(),ft(!1),ht(null),vt(a=>(a&&URL.revokeObjectURL(a),null))),(n!=="lab_video"||S!=="file")&&et()},[n,S]),i.useEffect(()=>{d||et()},[d]),i.useEffect(()=>()=>{tt(),et()},[]),t.jsxs("div",{className:"flex h-dvh min-h-0 flex-col overflow-hidden bg-surface text-on-surface",children:[t.jsx(Mn,{view:n,setView:r}),(n==="lab_image"||n==="lab_video")&&t.jsxs("div",{className:"flex shrink-0 items-center justify-between gap-2 border-b border-outline-variant/15 bg-surface-container-low px-3 py-2 text-xs 2xl:hidden",children:[t.jsx("button",{type:"button",className:"rounded border border-outline-variant/30 px-3 py-1.5 text-on-surface",onClick:()=>{g(!1),u(!0)},children:s==="zh"?"素材":"Assets"}),t.jsx("button",{type:"button",className:"rounded border border-outline-variant/30 px-3 py-1.5 text-on-surface",onClick:()=>{u(!1),g(!0)},children:s==="zh"?"解算参数":"Solve parameters"})]}),(o||p)&&t.jsx("button",{type:"button",className:"fixed inset-x-0 bottom-0 top-[88px] z-40 bg-black/60 2xl:hidden","aria-label":s==="zh"?"关闭面板":"Close panel",onClick:()=>{u(!1),g(!1)}}),t.jsxs("div",{className:"flex min-h-0 flex-1",children:[t.jsxs("aside",{className:`fixed bottom-0 left-0 top-[88px] z-50 w-64 shrink-0 overflow-y-auto border-r border-outline-variant/15 bg-surface-container-lowest p-3 text-xs shadow-2xl transition-transform 2xl:static 2xl:translate-x-0 2xl:shadow-none ${o?"translate-x-0":"-translate-x-full"}`,children:[t.jsx("div",{className:"mb-2 flex justify-end 2xl:hidden",children:t.jsx("button",{type:"button",className:"rounded border border-outline-variant/30 px-2 py-1",onClick:()=>u(!1),children:s==="zh"?"关闭":"Close"})}),t.jsx("div",{className:"mb-3 font-semibold text-on-surface-variant",children:e("sidebar.assets")}),t.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:[t.jsx(qs,{className:"h-4 w-4"}),t.jsx("span",{children:e("sidebar.upload")}),t.jsx("input",{type:"file",accept:"image/*,video/*",className:"hidden",onChange:async a=>{var c;const l=(c=a.target.files)==null?void 0:c[0];if(l){U(!0),w(null);try{const f=await Bt(l);await Q(),m(f.filename)}catch(f){w(String(f))}finally{U(!1),a.target.value=""}}}})]}),t.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:()=>{Q().catch(a=>w(String(a))),ke(1),At()},children:[t.jsx(Ws,{className:"h-3 w-3"})," ",e("sidebar.refresh")]}),t.jsx("div",{className:"og-scrollbar max-h-36 overflow-y-auto border-t border-outline-variant/10 pt-2",children:$s.map(a=>t.jsxs("div",{className:`mb-1 flex items-center gap-0.5 rounded px-1 ${d===a.filename?"bg-surface-container":""}`,children:[t.jsxs("button",{type:"button",className:`min-w-0 flex-1 truncate rounded px-1 py-1 text-left ${d===a.filename?"text-primary":""}`,title:a.filename,onClick:()=>{m(a.filename),r(ge(a.filename)?"lab_video":"lab_image")},children:[t.jsx("span",{className:"block truncate",children:rt(a.filename)}),t.jsx("span",{className:"block text-[9px] text-on-surface-variant/90",children:ge(a.filename)?e("sidebar.assetTypeVideo"):e("sidebar.assetTypeImage")})]}),t.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:l=>{l.stopPropagation(),Ma(a.filename)},children:t.jsx(ss,{className:"h-3.5 w-3.5"})})]},a.filename))}),t.jsxs("div",{className:"mt-3 border-t border-outline-variant/10 pt-3",children:[t.jsxs("div",{className:"mb-2 flex items-start justify-between gap-2",children:[t.jsx("div",{className:"min-w-0 font-semibold leading-tight text-on-surface-variant",children:e("sidebar.debugCaptures")}),t.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:!B||Na,title:B??void 0,onClick:()=>void Ms(),children:B&&/\.avi$/i.test(B)?e("sidebar.importToPoolWithTranscode"):e("sidebar.importToPoolDirect")})]}),Et!=null&&t.jsxs("div",{className:"mb-2 rounded border border-outline-variant/25 bg-surface-container-low px-2 py-2",children:[t.jsxs("div",{className:"flex items-center justify-between gap-2 text-[10px]",children:[t.jsx("span",{className:"text-on-surface-variant",children:vs||e("sidebar.flowPreparing")}),t.jsxs("span",{className:"font-mono text-on-surface",children:[Math.round((Et||0)*100),"%"]})]}),t.jsx("div",{className:"mt-1.5 h-1.5 w-full overflow-hidden rounded bg-surface-container-highest",children:t.jsx("div",{className:"h-full bg-primary transition-all",style:{width:`${Math.round((Et||0)*100)}%`}})}),Rt&&t.jsx("div",{className:"mt-1 text-[10px] text-on-surface-variant",title:Rt,children:Rt})]}),be.length===0?t.jsx("p",{className:"text-[10px] text-on-surface-variant",children:e("sidebar.debugEmpty")}):t.jsxs(t.Fragment,{children:[t.jsx("div",{className:"og-scrollbar max-h-[min(22rem,55vh)] space-y-1.5 overflow-y-auto pr-0.5",children:Ps.map(a=>t.jsxs("button",{type:"button",className:`flex w-full items-center gap-2 rounded-md border px-2 py-1.5 text-left transition-colors ${B===a.name?"border-primary bg-primary-container/20":"border-outline-variant/25 hover:bg-surface-container"}`,onClick:()=>oa(a.name),children:[a.type==="image"?t.jsx("img",{src:sn(a.name),alt:"",className:"h-11 w-11 shrink-0 rounded object-cover"}):t.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"}),t.jsxs("div",{className:"min-w-0 flex-1",children:[t.jsx("div",{className:"truncate font-mono text-[10px] text-on-surface",title:a.name,children:rt(a.name)}),t.jsxs("div",{className:"text-[9px] text-on-surface-variant",children:[a.type==="video"||ge(a.name)?e("sidebar.assetTypeVideo"):e("sidebar.assetTypeImage")," ","· ",$e(a.modified,s)," · ",Zt(a.size)]})]})]},a.name))}),t.jsxs("div",{className:"mt-2 flex items-center justify-between gap-1 text-[10px] text-on-surface-variant",children:[t.jsx("button",{type:"button",className:"rounded border border-outline-variant/30 px-2 py-0.5 disabled:opacity-40",disabled:_e<=1,onClick:()=>ke(a=>Math.max(1,a-1)),children:e("history.prev")}),t.jsx("span",{className:"tabular-nums",children:e("sidebar.debugPage",{cur:_e,total:De})}),t.jsx("button",{type:"button",className:"rounded border border-outline-variant/30 px-2 py-0.5 disabled:opacity-40",disabled:_e>=De,onClick:()=>ke(a=>Math.min(De,a+1)),children:e("history.next")})]})]})]})]}),(n==="lab_image"||n==="lab_video")&&t.jsxs("div",{className:"flex min-h-0 min-w-0 flex-1",children:[t.jsxs("main",{className:"og-scrollbar min-h-0 min-w-0 flex-1 overflow-y-auto p-4",children:[n==="lab_video"&&t.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:[t.jsx("p",{className:"text-[10px] text-on-surface-variant",children:e("lab.videoLiveIntro")}),t.jsx("p",{className:"mt-1.5 text-[10px] leading-snug text-on-surface-variant/95",children:e("lab.videoMjpegHint")}),t.jsxs("div",{className:"mt-2 flex flex-wrap gap-2",children:[t.jsx("button",{type:"button",className:`rounded px-3 py-1.5 text-[11px] font-medium ${S==="file"?"bg-primary text-on-primary-container":"border border-outline-variant/40 bg-surface-container text-on-surface"}`,onClick:()=>Se("file"),children:e("lab.previewModeFile")}),t.jsx("button",{type:"button",className:`rounded px-3 py-1.5 text-[11px] font-medium ${S==="camera"?"bg-primary text-on-primary-container":"border border-outline-variant/40 bg-surface-container text-on-surface"}`,onClick:()=>Se("camera"),children:e("lab.previewModeCamera")}),t.jsx("button",{type:"button",className:"rounded border border-outline-variant/40 bg-surface-container px-3 py-1.5 text-[11px] font-medium text-on-surface",onClick:()=>void Is(),children:e("lab.stopCameraPreview")})]})]}),t.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:[Ia!=null&&t.jsxs("div",{className:"pointer-events-none absolute right-2 top-2 z-20 rounded border border-white/30 bg-black/55 px-2 py-1 text-[10px] font-semibold tabular-nums text-white",children:[e("lab.previewConfidence"),": ",Ia.toFixed(1),"%"]}),n==="lab_video"&&S==="camera"?t.jsx("div",{className:"relative flex h-full min-h-[220px] flex-col items-center justify-center gap-3 bg-black p-2",children:t.jsxs("div",{className:"relative inline-block max-h-[70vh] max-w-full",children:[K&&bt?t.jsx("img",{ref:Le,src:bt,alt:"",className:"max-h-[70vh] w-full min-h-[120px] object-contain",onLoad:a=>ma({w:a.currentTarget.naturalWidth,h:a.currentTarget.naturalHeight})}):K&&!bt?t.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:[t.jsx(st,{className:"h-8 w-8 animate-spin text-primary"}),t.jsx("span",{children:e("lab.cameraPreviewLoading")})]}):xt==="busy"?t.jsx("div",{className:"flex min-h-[200px] max-w-md flex-col items-center justify-center gap-2 px-4 text-center text-[11px] text-on-surface-variant",children:t.jsx("span",{className:"text-error",children:e("lab.videoMjpegHint")})}):xt==="fail"?t.jsx("div",{className:"flex min-h-[200px] max-w-md flex-col items-center justify-center gap-2 px-4 text-center text-[11px] text-on-surface-variant",children:t.jsx("span",{className:"text-error",children:e("lab.cameraStreamStatusFail")})}):xt==="unknown"?t.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:[t.jsx(st,{className:"h-8 w-8 animate-spin text-primary"}),t.jsx("span",{children:e("lab.cameraPreviewLoading")})]}):t.jsx("img",{ref:Le,src:`/api/dev/debug/camera/stream?t=${xa}`,alt:"",className:"max-h-[70vh] w-full min-h-[120px] object-contain",onLoad:a=>ma({w:a.currentTarget.naturalWidth,h:a.currentTarget.naturalHeight}),onError:()=>{(async()=>{const a=await as();if(a==="busy"){G("busy");return}if(a==="fail"){G("fail");return}if(It.current>=6){G("fail");return}It.current+=1,window.setTimeout(()=>We(Date.now()),400)})()}}),t.jsx("canvas",{ref:he,className:"pointer-events-none absolute left-0 top-0"})]})}):d?t.jsx("div",{className:"og-scrollbar relative h-full min-h-[200px] overflow-auto",children:n==="lab_video"?t.jsxs(t.Fragment,{children:[t.jsxs("div",{className:"absolute left-2 top-2 z-20 flex flex-wrap gap-1",children:[t.jsxs("button",{type:"button",title:e("lab.grid"),className:`rounded border px-2 py-1 text-[10px] ${ze?"border-primary bg-primary/20":"border-white/30 bg-black/40"} text-white`,onClick:()=>ca(a=>!a),children:[t.jsx(za,{className:"mr-1 inline h-3 w-3"}),e("lab.grid")]}),t.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:()=>at(xe-.25),children:t.jsx(Va,{className:"inline h-3 w-3"})}),t.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:()=>at(xe+.25),children:t.jsx(Wa,{className:"inline h-3 w-3"})}),t.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:()=>mt(1),children:t.jsx(Ha,{className:"inline h-3 w-3"})})]}),t.jsxs("div",{className:"flex min-h-[200px] flex-col items-center justify-center gap-2 bg-black py-2",children:[t.jsx("div",{className:"inline-block origin-top-left transition-transform",style:{transform:`scale(${xe})`},children:t.jsxs("div",{className:"relative inline-block",children:[ze&&t.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"}}),t.jsx("video",{ref:Ie,src:Ye,loop:!0,playsInline:!0,autoPlay:!0,muted:!0,preload:"metadata",className:"max-h-[70vh] w-full max-w-full object-contain",onError:()=>pt(e("lab.videoPreviewFailed")),onLoadedData:()=>{pt(null);const a=Ie.current;a&&a.play().catch(()=>{})}}),t.jsx("canvas",{ref:he,className:"pointer-events-none absolute left-0 top-0"})]})}),Ee&&t.jsx("div",{className:"rounded border border-error/40 bg-error-container/20 px-3 py-1.5 text-[11px] text-error",children:Ee}),Ra&&t.jsxs("div",{className:"max-w-3xl rounded border border-primary/40 bg-primary/10 px-3 py-2 text-[11px] text-on-surface",children:[t.jsx("div",{className:"font-semibold text-primary",children:e("lab.transcode.title")}),t.jsx("div",{className:"mt-1 text-on-surface-variant",children:e("lab.transcode.desc")}),ja!=null&&t.jsxs("div",{className:"mt-2",children:[t.jsx("div",{className:"mb-1 text-[10px] text-on-surface-variant",children:bs||e("lab.transcode.running")}),t.jsx("div",{className:"h-1.5 w-full overflow-hidden rounded bg-surface-container-highest",children:t.jsx("div",{className:"h-full bg-primary transition-all",style:{width:`${Math.round(ja*100)}%`}})})]}),t.jsx("div",{className:"mt-2",children:t.jsx("button",{type:"button",className:"rounded bg-primary px-3 py-1.5 text-[11px] font-medium text-on-primary disabled:opacity-50",disabled:ya,onClick:()=>void Es(),children:e("lab.transcode.button")})})]})]})]}):t.jsxs(t.Fragment,{children:[t.jsxs("div",{className:"absolute left-2 top-2 z-20 flex flex-wrap gap-1",children:[t.jsxs("button",{type:"button",title:e("lab.grid"),className:`rounded border px-2 py-1 text-[10px] ${ze?"border-primary bg-primary/20":"border-white/30 bg-black/40"} text-white`,onClick:()=>ca(a=>!a),children:[t.jsx(za,{className:"mr-1 inline h-3 w-3"}),e("lab.grid")]}),t.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:()=>at(xe-.25),children:t.jsx(Va,{className:"inline h-3 w-3"})}),t.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:()=>at(xe+.25),children:t.jsx(Wa,{className:"inline h-3 w-3"})}),t.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:()=>mt(1),children:t.jsx(Ha,{className:"inline h-3 w-3"})})]}),t.jsx("div",{className:"inline-block origin-top-left transition-transform",style:{transform:`scale(${xe})`},children:t.jsxs("div",{className:"relative inline-block",children:[ze&&t.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"}}),t.jsx("img",{ref:Ct,src:Ye,alt:"preview",className:"max-h-[70vh] w-full object-contain"}),t.jsx("canvas",{ref:he,className:"pointer-events-none absolute left-0 top-0"})]})})]})}):t.jsx("div",{className:"flex h-full items-center justify-center px-4 text-center text-on-surface-variant",children:e(n==="lab_video"?"lab.selectOrUploadVideo":"lab.selectOrUpload")}),ne&&t.jsx("div",{className:"absolute inset-0 flex items-center justify-center bg-black/40",children:t.jsx(st,{className:"h-8 w-8 animate-spin text-primary"})})]}),(d&&n==="lab_image"||n==="lab_video"&&(S==="file"&&d||S==="camera"))&&t.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:[t.jsx("span",{className:"shrink-0 font-medium text-on-surface-variant",children:e("lab.layers")}),t.jsx("div",{className:"flex flex-wrap gap-x-4 gap-y-1",children:["matched","pattern","all","rejected"].map(a=>t.jsxs("label",{className:"flex cursor-pointer items-center gap-1",children:[t.jsx("input",{type:"checkbox",className:"accent-primary",checked:V[a],onChange:l=>ds(c=>({...c,[a]:l.target.checked}))}),t.jsx("span",{children:e(a==="matched"?"lab.layer.matched":a==="pattern"?"lab.layer.pattern":a==="all"?"lab.layer.all":"lab.layer.rejected")})]},a))})]}),(d||n==="lab_video"&&S==="camera")&&t.jsxs(t.Fragment,{children:[t.jsxs("div",{className:"mt-2 grid gap-2 sm:grid-cols-2",children:[t.jsxs("details",{open:!0,className:"rounded-lg border border-outline-variant/25 bg-surface-container-lowest/90",children:[t.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"),t.jsx(Fe,{className:"h-4 w-4 shrink-0 text-on-surface-variant"})]}),t.jsx("div",{className:"border-t border-outline-variant/15 px-3 py-2 text-[10px]",children:ee?t.jsxs("div",{className:"space-y-1 text-on-surface",children:[v.tSolveMs!=null&&t.jsxs("div",{className:"space-y-0.5",children:[t.jsxs("div",{className:"flex justify-between gap-2",children:[t.jsx("span",{className:"text-on-surface-variant",children:e("lab.metric.solveComputeMs")}),t.jsxs("span",{className:"font-mono tabular-nums",children:[v.tSolveMs.toFixed(0)," ms"]})]}),t.jsx("p",{className:"text-[8px] leading-snug text-on-surface-variant/90",children:e("lab.metric.solveComputeHelp")})]}),Nt!=null&&t.jsxs("div",{className:"space-y-0.5 border-t border-outline-variant/10 pt-1",children:[t.jsxs("div",{className:"flex justify-between gap-2",children:[t.jsx("span",{className:"text-on-surface-variant",children:e("lab.metric.solveRoundTripMs")}),t.jsxs("span",{className:"font-mono tabular-nums",children:[Nt.toFixed(0)," ms"]})]}),t.jsx("p",{className:"text-[8px] leading-snug text-on-surface-variant/90",children:e("lab.metric.solveRoundTripHelp")})]}),(v.tBackendTotalMs!=null||v.tOpenDecodeMs!=null||v.tPreprocessMs!=null||v.tExtractMs!=null||v.tSolveMs!=null)&&t.jsxs("div",{className:"space-y-0.5 border-t border-outline-variant/10 pt-1",children:[t.jsxs("div",{className:"flex justify-between gap-2",children:[t.jsx("span",{className:"text-on-surface-variant",children:e("lab.metric.backendTotalMs")}),t.jsx("span",{className:"font-mono tabular-nums",children:v.tBackendTotalMs!=null?`${v.tBackendTotalMs.toFixed(0)} ms`:e("common.placeholder")})]}),t.jsxs("div",{className:"flex flex-wrap gap-x-2 gap-y-0.5 text-[8px] text-on-surface-variant/90",children:[t.jsxs("span",{children:[e("lab.metric.openDecodeMs"),":"," ",v.tOpenDecodeMs!=null?`${v.tOpenDecodeMs.toFixed(0)} ms`:e("common.placeholder")]}),t.jsxs("span",{children:[e("lab.metric.preprocessMs"),":"," ",v.tPreprocessMs!=null?`${v.tPreprocessMs.toFixed(0)} ms`:e("common.placeholder")]}),t.jsxs("span",{children:[e("lab.metric.extractMs"),":"," ",v.tExtractMs!=null?`${v.tExtractMs.toFixed(0)} ms`:e("common.placeholder")]}),t.jsxs("span",{children:[e("lab.metric.solveOnlyMs"),":"," ",v.tSolveMs!=null?`${v.tSolveMs.toFixed(0)} ms`:e("common.placeholder")]})]})]}),(v.raDeg!=null||v.decDeg!=null)&&t.jsxs("div",{className:"leading-tight",children:[t.jsx("span",{className:"text-on-surface-variant",children:e("lab.metric.radec")}),t.jsxs("div",{className:"mt-0.5 font-mono text-[9px]",children:["α ",it(v.raDeg)," · δ"," ",it(v.decDeg)]})]}),t.jsxs("div",{className:"flex flex-wrap gap-x-2 gap-y-0.5 text-[9px]",children:[v.matches!=null&&t.jsxs("span",{children:[t.jsx("span",{className:"text-on-surface-variant",children:e("lab.metric.matches")})," ",t.jsx("span",{className:"font-mono",children:v.matches})]}),v.rmseArcsec!=null&&t.jsxs("span",{children:[t.jsx("span",{className:"text-on-surface-variant",children:e("lab.metric.rmse")})," ",t.jsxs("span",{className:"font-mono",children:[v.rmseArcsec.toFixed(2),"″"]})]}),v.prob!=null&&t.jsxs("span",{className:"inline-flex max-w-full flex-col gap-0.5",children:[t.jsxs("span",{children:[t.jsx("span",{className:"text-on-surface-variant",children:e("lab.metric.prob")})," ",t.jsx("span",{className:"font-mono",children:ye(v.prob,ee??void 0).line})]}),t.jsx("span",{className:"text-[8px] leading-snug text-on-surface-variant/90",children:e("lab.metric.probHelp")}),ye(v.prob,ee??void 0).rawLine&&t.jsxs(t.Fragment,{children:[t.jsxs("span",{className:"text-[8px] text-on-surface-variant",children:[e("lab.metric.probRaw"),":"," ",ye(v.prob,ee??void 0).rawLine]}),t.jsx("span",{className:"text-[8px] leading-snug text-on-surface-variant/90",children:e("lab.metric.probRawHelp")})]})]})]}),v.status&&t.jsxs("div",{className:"border-t border-outline-variant/15 pt-1 text-[9px]",children:[t.jsxs("span",{className:"text-on-surface-variant",children:[e("lab.metric.status")," "]}),t.jsx("span",{className:"font-mono text-secondary",children:v.status})]})]}):t.jsx("p",{className:"text-on-surface-variant",children:e("common.placeholder")})})]}),t.jsxs("details",{open:!0,className:"rounded-lg border border-outline-variant/25 bg-surface-container-lowest/90",children:[t.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"),t.jsx(Fe,{className:"h-4 w-4 shrink-0 text-on-surface-variant"})]}),t.jsxs("div",{className:"space-y-0.5 border-t border-outline-variant/15 px-3 py-2 text-[10px]",children:[t.jsxs("div",{className:"flex justify-between gap-2",children:[t.jsx("span",{className:"text-on-surface-variant",children:e("lab.resolution")}),t.jsx("span",{className:"font-mono tabular-nums",children:Ut.w>0?`${Ut.w}×${Ut.h}`:e("common.placeholder")})]}),t.jsxs("div",{className:"flex justify-between gap-2",children:[t.jsx("span",{className:"text-on-surface-variant",children:e("lab.starsDetected")}),t.jsx("span",{className:"font-mono tabular-nums",children:Sa??e("common.placeholder")})]}),t.jsxs("div",{className:"flex justify-between gap-2",children:[t.jsx("span",{className:"text-on-surface-variant",children:e("lab.fwhm")}),t.jsx("span",{children:e("common.placeholder")})]})]})]})]}),d&&t.jsxs(t.Fragment,{children:[t.jsxs("div",{className:"mt-2 flex flex-wrap gap-4 text-xs text-on-surface-variant",children:[t.jsxs("span",{children:[e("lab.file"),":"," ",t.jsx("span",{className:"font-mono text-on-surface",title:d,children:rt(d,22,18)})]}),((La=y.find(a=>a.filename===d))==null?void 0:La.source)&&t.jsxs("span",{className:"rounded bg-surface-container-high px-2 py-0.5",children:[e("lab.source"),":"," ",(Da=y.find(a=>a.filename===d))==null?void 0:Da.source]})]}),t.jsxs("details",{open:!0,className:"mt-2 rounded-lg border border-outline-variant/25 bg-surface-container-lowest/90 text-xs shadow-sm",children:[t.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"),yt&&t.jsx(st,{className:"h-3.5 w-3.5 animate-spin"}),t.jsx(Fe,{className:"ml-auto h-4 w-4 shrink-0 text-on-surface-variant"})]}),t.jsx("div",{className:"border-t border-outline-variant/15 p-3 pt-2",children:Ca.length>0?t.jsx("dl",{className:"grid grid-cols-2 gap-x-4 gap-y-2 sm:grid-cols-3",children:Ca.map(a=>t.jsxs("div",{children:[t.jsx("dt",{className:"text-[10px] text-on-surface-variant",children:e(a.key)}),t.jsx("dd",{className:"text-[11px] font-medium text-on-surface",children:a.value})]},`${a.key}-${a.value}`))}):gt&&!yt?t.jsx("p",{className:"text-[10px] leading-relaxed text-on-surface-variant",children:e("lab.meta.partial")}):yt?null:t.jsx("p",{className:"text-[10px] text-on-surface-variant",children:e("lab.meta.noSidecar")})})]})]})]}),(d||n==="lab_video"&&S==="camera")&&(Ae.length>0||k||T)&&t.jsxs(t.Fragment,{children:[Ae.length>0&&t.jsxs("section",{className:"mt-3 rounded-lg border border-outline-variant/25 bg-surface-container-lowest/90",children:[t.jsxs("div",{className:"flex h-9 shrink-0 items-center border-b border-outline-variant/15 px-3 text-[10px] uppercase text-on-surface-variant",children:[t.jsx("span",{children:e("results.solveHistoryTitle")}),t.jsxs("span",{className:"ml-2 normal-case text-on-surface-variant/70",children:["(",Ae.length,"/",es,")"]})]}),t.jsx("div",{className:"og-scrollbar max-h-[min(36vh,18rem)] space-y-2 overflow-y-auto p-3",children:Ae.map(a=>{var $;const l=a.batch,c=a.single,f=$e(new Date(a.at).toISOString(),s),j=l&&l.results.length?e("results.historyBatch",{count:l.results.length}):e("results.historySingle");return t.jsxs("details",{className:"rounded border border-outline-variant/20 bg-surface-container/80 text-xs",children:[t.jsx("summary",{className:"cursor-pointer list-none px-3 py-2 font-medium text-on-surface [&::-webkit-details-marker]:hidden",children:t.jsxs("span",{children:[f," · ",j]})}),t.jsxs("div",{className:"border-t border-outline-variant/15 p-3 pt-2",children:[($=l==null?void 0:l.results)!=null&&$.length?t.jsx("div",{className:"og-scrollbar flex gap-2 overflow-x-auto pb-1",children:l.results.map((E,D)=>{const C=E.result,ce=E.success===!0;return t.jsxs("div",{className:"min-w-[12rem] max-w-xs shrink-0 rounded border border-outline-variant/20 bg-surface-container-lowest p-2",children:[t.jsx("div",{className:"border-b border-outline-variant/10 pb-1 text-[10px] font-semibold text-secondary",children:String(E.label??D)}),ce&&C?t.jsx(nt,{result:C,t:e,roundTripMs:null}):t.jsx("div",{className:"mt-1 text-[10px] text-error",children:String(E.error??"—")})]},D)})}):null,c&&c.result?t.jsx("div",{className:"mt-2 rounded border border-outline-variant/15 p-2",children:t.jsx(nt,{result:c.result,t:e,roundTripMs:null})}):null,t.jsxs("details",{className:"mt-2",children:[t.jsx("summary",{className:"cursor-pointer text-[10px] text-primary hover:underline",children:e("results.viewRaw")}),t.jsx("pre",{className:"og-scrollbar mt-1 max-h-32 overflow-auto rounded bg-surface-container-highest p-2 text-[9px] text-on-surface-variant",children:JSON.stringify({single:c,batch:l},null,2)})]})]})]},a.id)})})]}),(k||T)&&t.jsxs("section",{className:"mt-3 max-h-[min(50vh,28rem)] rounded-lg border border-outline-variant/25 bg-surface-container-lowest/95",children:[t.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:[t.jsx("span",{children:e("results.title")}),t.jsxs("div",{className:"flex gap-3",children:[(Oa=T==null?void 0:T.results)!=null&&Oa.length?t.jsx("button",{type:"button",className:"rounded bg-surface-container px-2 py-0.5 normal-case",onClick:async()=>{if(!(!d||!T)){for(const a of T.results){if(!a.success)continue;const l=a.result;await zt({input_name:d,preset_label:String(a.label),result_json:a,metrics:Kt(l??null),replay:{layers:V,params:b}})}w(null)}},children:e("results.saveBatchAll")}):null,k&&t.jsx("button",{type:"button",className:"rounded bg-surface-container px-2 py-0.5 normal-case",onClick:async()=>{if(!d&&hs!=="camera")return;const a=k.result;await zt({input_name:d??e("lab.cameraSnapshotName"),preset_label:"manual",result_json:k,metrics:Kt(a??null),replay:{layers:V,params:b}}),w(null)},children:e("results.saveCurrent")})]})]}),t.jsx("div",{className:"og-scrollbar min-h-0 overflow-y-auto p-3",children:t.jsxs("div",{className:"og-scrollbar flex gap-3 overflow-x-auto text-xs",children:[T==null?void 0:T.results.map((a,l)=>{const c=a.result,f=fs[l]??!1;return t.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:[t.jsx("div",{className:"border-b border-outline-variant/15 pb-2 font-semibold text-secondary",children:String(a.label)}),a.success?t.jsxs(t.Fragment,{children:[t.jsx(nt,{result:c,t:e,roundTripMs:null}),t.jsx("button",{type:"button",className:"mt-2 text-[10px] text-primary hover:underline",onClick:()=>wt(j=>({...j,[l]:!f})),children:e(f?"results.hideRaw":"results.viewRaw")}),f&&t.jsx("pre",{className:"og-scrollbar mt-1 max-h-40 overflow-auto rounded bg-surface-container-highest p-2 text-[9px] text-on-surface-variant",children:JSON.stringify(a,null,2)})]}):t.jsx("div",{className:"mt-2 text-[10px] text-error",children:String(a.error)}),a.success===!0&&d?t.jsx("button",{type:"button",className:"mt-3 w-full rounded bg-surface-container-high py-1.5 text-[10px] font-medium",onClick:()=>zt({input_name:d,preset_label:String(a.label),result_json:a,metrics:Kt(c??null),replay:{layers:V,params:b}}).catch(j=>w(String(j))),children:e("results.saveRow")}):null]},l)}),k&&!T&&t.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:[t.jsx("div",{className:"border-b border-outline-variant/15 pb-2 text-[10px] font-semibold uppercase text-on-surface-variant",children:e("results.title")}),t.jsx(nt,{result:k.result,t:e,roundTripMs:Nt}),t.jsx("button",{type:"button",className:"mt-2 text-[10px] text-primary hover:underline",onClick:()=>jt(a=>!a),children:e(va?"results.hideRaw":"results.viewRaw")}),va&&t.jsx("pre",{className:"og-scrollbar mt-1 max-h-48 overflow-auto rounded bg-surface-container-highest p-2 text-[9px] text-on-surface-variant",children:JSON.stringify(k,null,2)})]})]})})]})]})]}),t.jsxs("aside",{className:`fixed bottom-0 right-0 top-[88px] z-50 flex min-h-0 w-80 shrink-0 flex-col border-l border-outline-variant/15 bg-surface-container-low text-xs shadow-2xl transition-transform 2xl:static 2xl:translate-x-0 2xl:shadow-none ${p?"translate-x-0":"translate-x-full"}`,children:[t.jsx("div",{className:"flex shrink-0 justify-end border-b border-outline-variant/15 p-2 2xl:hidden",children:t.jsx("button",{type:"button",className:"rounded border border-outline-variant/30 px-2 py-1",onClick:()=>g(!1),children:s==="zh"?"关闭":"Close"})}),n!=="lab_video"&&t.jsxs("div",{className:"shrink-0 space-y-2 border-b border-outline-variant/20 p-4 pb-3",children:[t.jsx("button",{type:"button",className:"w-full rounded bg-primary py-2.5 font-semibold text-on-primary-container",onClick:js,disabled:ne,children:e("btn.solveOne")}),t.jsx("button",{type:"button",className:"w-full rounded bg-secondary/80 py-2.5 font-semibold text-on-secondary",onClick:Ns,disabled:ne,children:e("btn.solveBatch")})]}),n==="lab_video"&&t.jsx("div",{className:"shrink-0 space-y-2 border-b border-outline-variant/20 p-4 pb-3",children:S==="file"?t.jsxs(t.Fragment,{children:[t.jsx("button",{type:"button",className:"w-full rounded bg-primary py-2.5 font-semibold text-on-primary-container disabled:opacity-40",onClick:()=>Ss(),disabled:ne||!Ea||fe,children:e("lab.solveFileStart")}),t.jsx("button",{type:"button",className:"w-full rounded bg-error/80 py-2.5 font-semibold text-on-error disabled:opacity-40",onClick:()=>et(),disabled:!fe,children:e("lab.solveFileStop")}),t.jsx("button",{type:"button",className:"w-full rounded bg-secondary/80 py-2.5 font-semibold text-on-secondary disabled:opacity-40",onClick:()=>void ks(),disabled:ne||!Ea||fe,children:e("lab.solveCurrentFrame")})]}):t.jsxs(t.Fragment,{children:[t.jsx("button",{type:"button",className:"w-full rounded bg-primary py-2.5 font-semibold text-on-primary-container disabled:opacity-40",onClick:()=>Cs(),disabled:ne||Re||K,children:e("lab.solveCameraStart")}),t.jsx("button",{type:"button",className:"w-full rounded bg-error/80 py-2.5 font-semibold text-on-error disabled:opacity-40",onClick:()=>tt(),disabled:!Re,children:e("lab.solveCameraStop")}),t.jsx("button",{type:"button",className:"w-full rounded bg-secondary/80 py-2.5 font-semibold text-on-secondary disabled:opacity-40",onClick:()=>void _s(),disabled:ne||K,children:e("lab.solveCameraFrame")}),t.jsxs("label",{className:"flex items-center gap-2 rounded border border-outline-variant/25 px-2 py-1.5 text-[11px]",children:[t.jsx("input",{type:"checkbox",checked:ha,onChange:a=>xs(a.target.checked)}),t.jsx("span",{children:"自动保持(解算成功后冻结) / Auto Hold"})]}),K&&t.jsx("button",{type:"button",className:"w-full rounded bg-tertiary/80 py-2.5 font-semibold text-on-tertiary disabled:opacity-40",onClick:()=>Ls(),children:"继续实时 / Resume Live"}),K&&t.jsxs("div",{className:"text-[10px] text-on-surface-variant",children:["已冻结帧 / Frozen frame ",ps??"-"]})]})}),n==="lab_video"&&Ge&&t.jsxs("div",{className:"shrink-0 border-b border-outline-variant/20 px-4 py-2 text-[10px] text-on-surface-variant",children:[t.jsx("div",{className:"font-medium text-on-surface",children:e("lab.systemLoad")}),t.jsxs("div",{children:["CPU ",Ge.cpu_usage,"% · RAM ",Ge.memory_usage,"% ·"," ",Ge.temperature,"°C"]})]}),t.jsxs("div",{className:"og-scrollbar min-h-0 flex-1 overflow-y-auto p-4",children:[t.jsxs("details",{open:!0,className:"rounded-lg border border-outline-variant/20 bg-surface-container-highest/30",children:[t.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"),t.jsx(Fe,{className:"h-4 w-4 shrink-0"})]}),t.jsxs("div",{className:"border-t border-outline-variant/15 p-3 pt-2",children:[t.jsx("p",{className:"mb-3 text-[10px] leading-relaxed text-on-surface-variant/90",children:e("params.blockSolveIntro")}),t.jsxs("div",{className:"space-y-2",children:[t.jsxs("label",{className:"block",children:[t.jsx("span",{className:"text-[10px] font-medium text-on-surface-variant",children:e("params.solveProfile")}),t.jsx("p",{className:"mb-1 mt-0.5 text-[9px] leading-snug text-on-surface-variant/85",children:e("params.solveProfileHelp")}),t.jsxs("select",{className:"w-full rounded bg-surface-container-highest px-2 py-1",value:b.solve_profile??"balanced",onChange:a=>x(l=>({...l,solve_profile:a.target.value})),children:[t.jsx("option",{value:"speed",children:e("params.solveProfileSpeed")}),t.jsx("option",{value:"balanced",children:e("params.solveProfileBalanced")}),t.jsx("option",{value:"robust",children:e("params.solveProfileRobust")})]})]}),t.jsx(z,{label:e("params.fov"),helpKey:"params.fovHelp",value:b.fov_estimate??"",onChange:a=>x(l=>({...l,fov_estimate:a}))}),t.jsx(z,{label:e("params.fovErr"),helpKey:"params.fovErrHelp",value:b.fov_max_error??"",onChange:a=>x(l=>({...l,fov_max_error:a}))}),t.jsx(z,{label:e("params.timeout"),helpKey:"params.timeoutHelp",value:b.solve_timeout_ms??"",onChange:a=>x(l=>({...l,solve_timeout_ms:a}))}),t.jsx(z,{label:e("params.solveIntervalMs"),helpKey:"params.solveIntervalMsHelp",value:kt??"",onChange:a=>ga(a==null?Tt:Math.round(a))}),t.jsx("p",{className:"text-[9px] leading-snug text-on-surface-variant/80",children:e("params.solveIntervalBound",{min:Pt,max:$t,effective:oe})}),t.jsx("p",{className:"text-[9px] leading-snug text-on-surface-variant/80",children:e("params.solveIntervalIndependent")}),n==="lab_video"&&t.jsxs("label",{className:"mt-2 block",children:[t.jsx("span",{className:"text-[10px] font-medium text-on-surface-variant",children:e("params.centroidRejectionLevel")}),t.jsx("p",{className:"mb-1 mt-0.5 text-[9px] leading-snug text-on-surface-variant/85",children:e("params.centroidRejectionLevelHelp")}),t.jsxs("div",{className:"flex items-center gap-2",children:[t.jsx("span",{className:"w-5 shrink-0 text-center text-[9px] text-on-surface-variant",children:"1"}),t.jsx("input",{type:"range",min:1,max:5,step:1,className:"h-2 flex-1 accent-primary",value:b.centroid_rejection_level??3,onChange:a=>x(l=>({...l,centroid_rejection_level:Number(a.target.value)}))}),t.jsx("span",{className:"w-5 shrink-0 text-center text-[9px] text-on-surface-variant",children:"5"}),t.jsx("span",{className:"ml-1 min-w-[1.25rem] tabular-nums text-[10px] font-semibold text-on-surface",children:b.centroid_rejection_level??3})]}),t.jsx("p",{className:"mt-0.5 text-[9px] text-on-surface-variant/90",children:e("params.centroidRejectionScale")})]}),t.jsx(z,{label:e("params.ra"),helpKey:"params.raHelp",value:b.hint_ra_deg??"",onChange:a=>x(l=>({...l,hint_ra_deg:a}))}),t.jsx(z,{label:e("params.dec"),helpKey:"params.decHelp",value:b.hint_dec_deg??"",onChange:a=>x(l=>({...l,hint_dec_deg:a}))}),t.jsx(z,{label:e("params.maxSide"),helpKey:"params.maxSideHelp",value:b.max_image_side??"",onChange:a=>x(l=>({...l,max_image_side:a}))}),t.jsxs("label",{className:"mt-1.5 flex cursor-pointer items-center gap-2",children:[t.jsx("input",{type:"checkbox",className:"accent-primary",checked:b.detail_level==="full",onChange:a=>x(l=>({...l,detail_level:a.target.checked?"full":"summary"}))}),t.jsx("span",{className:"text-[10px] text-on-surface-variant",children:e("params.detailLevelFull")})]}),t.jsxs("label",{className:"mt-1.5 flex cursor-pointer items-start gap-2",children:[t.jsx("input",{type:"checkbox",className:"accent-primary mt-0.5",checked:!!b.large_scale_bg_subtract,onChange:a=>x(l=>({...l,large_scale_bg_subtract:a.target.checked}))}),t.jsxs("span",{children:[t.jsx("span",{className:"text-[10px] text-on-surface-variant",children:e("params.largeScaleBg")}),t.jsx("p",{className:"mt-0.5 text-[9px] leading-snug text-on-surface-variant/85",children:e("params.largeScaleBgHelp")})]})]})]})]})]}),t.jsxs("details",{open:!0,className:"mt-3 rounded-lg border border-outline-variant/20 bg-surface-container-highest/30",children:[t.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"),t.jsx(Fe,{className:"h-4 w-4 shrink-0"})]}),t.jsxs("div",{className:"border-t border-outline-variant/15 p-3 pt-2",children:[t.jsx("p",{className:"mb-3 text-[10px] leading-relaxed text-on-surface-variant/90",children:e("params.blockCentroidIntro")}),t.jsxs("div",{className:"space-y-2",children:[t.jsx(z,{label:e("params.sigma"),helpKey:"params.sigmaHelp",step:.1,value:((Fa=b.centroid)==null?void 0:Fa.sigma)??"",onChange:a=>x(l=>({...l,centroid:{...l.centroid,sigma:a}}))}),t.jsx(z,{label:e("params.maxArea"),helpKey:"params.maxAreaHelp",value:((Pa=b.centroid)==null?void 0:Pa.max_area)??"",onChange:a=>x(l=>({...l,centroid:{...l.centroid,max_area:a}}))}),t.jsx(z,{label:e("params.minArea"),helpKey:"params.minAreaHelp",value:(($a=b.centroid)==null?void 0:$a.min_area)??"",onChange:a=>x(l=>({...l,centroid:{...l.centroid,min_area:a}}))}),t.jsx(z,{label:e("params.filtsize"),helpKey:"params.filtsizeHelp",step:2,value:((Ta=b.centroid)==null?void 0:Ta.filtsize)??"",onChange:a=>x(l=>({...l,centroid:{...l.centroid,filtsize:a}}))})]})]})]}),t.jsxs("div",{className:"mt-4 rounded-md border border-outline-variant/20 bg-surface-container-highest/40 p-3",children:[t.jsx("div",{className:"mb-2 font-semibold text-on-surface-variant",children:e("sidebar.batchPresets")}),t.jsx("p",{className:"mb-2 text-[10px] leading-snug text-on-surface-variant",children:e("sidebar.batchHint")}),t.jsx("div",{className:"og-scrollbar max-h-36 space-y-1.5 overflow-y-auto",children:[...I,...se].map(a=>t.jsxs("label",{className:"flex cursor-pointer items-center gap-2",children:[t.jsx("input",{type:"checkbox",checked:Xt.has(a.id),onChange:()=>Ds(a.id)}),t.jsx("span",{className:"truncate",children:a.name})]},a.id))})]}),t.jsxs("div",{className:"mt-6 border-t border-outline-variant/20 pt-4",children:[t.jsx("div",{className:"mb-2 font-semibold",children:e("btn.applyPresets")}),t.jsx("div",{className:"og-scrollbar max-h-24 space-y-1 overflow-y-auto",children:[...I,...se].map(a=>t.jsx("button",{type:"button",className:"block w-full truncate text-left text-primary hover:underline",onClick:()=>ws(a.params),children:a.name},a.id))}),t.jsxs("div",{className:"mt-3 flex gap-1",children:[t.jsx("input",{className:"flex-1 rounded bg-surface-container-highest px-2 py-1",placeholder:e("placeholder.newPreset"),value:ut,onChange:a=>la(a.target.value)}),t.jsx("button",{type:"button",className:"rounded bg-surface-container-high px-2",onClick:async()=>{if(ut.trim()){U(!0);try{await en(ut.trim(),b),la(""),await Q();const a=await Ht("user");Yt(a.presets)}catch(a){w(String(a))}finally{U(!1)}}},children:e("btn.savePreset")})]})]})]})]})]}),n==="pool"&&t.jsx(In,{uploads:y,onDelete:a=>void Ma(a)}),n==="history"&&t.jsx(Cn,{historyQ:Ne,setHistoryQ:cs,historyPage:Ue,setHistoryPage:sa,historyData:Be,historyExpandId:na,setHistoryExpandId:ra,historyTotalPages:Fs,onDeleteExperiment:a=>void Os(a),onSearch:()=>Wt(Ne,1,lt).then(a=>{sa(1),dt(a)})})]}),t.jsx("div",{className:"pointer-events-none fixed bottom-4 right-4 z-[100] flex max-w-sm flex-col gap-2",children:os.map(a=>t.jsx("div",{className:`pointer-events-auto rounded border px-3 py-2 text-left text-xs shadow-lg ${a.variant==="error"?"border-error/50 bg-error-container/95 text-on-error-container":a.variant==="warn"?"border-amber-500/40 bg-surface-container-highest/95 text-on-surface":"border-outline-variant/40 bg-surface-container-high/95 text-on-surface"}`,children:a.text},a.id))})]})}Bs.createRoot(document.getElementById("root")).render(t.jsx(Hs.StrictMode,{children:t.jsx(zs,{children:t.jsx(Dn,{})})})); diff --git a/web/static/analysis-lab/index.html b/web/static/analysis-lab/index.html index cc7abc9..b1a8d77 100644 --- a/web/static/analysis-lab/index.html +++ b/web/static/analysis-lab/index.html @@ -4,7 +4,7 @@ OGScope 星空解算控制台 - + From 2e8c0a29e341567cfe6454891787e80759da231b Mon Sep 17 00:00:00 2001 From: "Richard.Lu" Date: Wed, 2 Sep 2026 20:33:47 +0800 Subject: [PATCH 3/5] =?UTF-8?q?fix:=20=E6=8C=81=E4=B9=85=E5=8C=96=E7=9B=B8?= =?UTF-8?q?=E6=9C=BA=E8=B0=83=E8=AF=95=E6=8B=8D=E6=91=84=E5=B9=B6=E6=94=AF?= =?UTF-8?q?=E6=8C=81=E5=AE=8C=E6=95=B4=E5=AF=BC=E5=87=BA=20/=20persist=20c?= =?UTF-8?q?amera=20debug=20captures=20and=20support=20full=20export?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 默认保存到 data/dev_captures 并无覆盖迁移历史临时文件 / Save under data/dev_captures and migrate legacy temporary files without overwrite - 统一分析导入路径并显示持久或临时存储状态 / Unify analysis imports and show persistent or temporary storage state - 增加 ZIP 全量导出与部署、文档和回归测试 / Add ZIP export plus deployment, documentation, and regression coverage --- .gitignore | 2 +- docs/DEBUG_CONSOLE.md | 9 ++- docs/DEBUG_CONSOLE_EN.md | 10 ++- ogscope/config.py | 11 +++ ogscope/config_catalog.py | 1 + ogscope/domain/camera/services.py | 4 + ogscope/domain/shared/filesystem.py | 77 ++++++++++++++++++- ogscope/web/api/analysis/services.py | 6 +- ogscope/web/api/debug/routes.py | 16 ++++ ogscope/web/api/debug/services.py | 39 +++++++++- ogscope/web/app.py | 12 +++ scripts/sync_board_code.sh | 6 +- tests/unit/test_analysis_api.py | 24 ++++++ tests/unit/test_config_catalog.py | 1 + tests/unit/test_debug_files_api.py | 26 ++++++- tests/unit/test_domain_shared_filesystem.py | 77 ++++++++++++++++++- web/spa/src/apps/camera/CameraConsoleApp.tsx | 31 +++++++- ...lysis-fOaEdJ6w.js => analysis-DqbIrqpF.js} | 2 +- .../analysis-lab/assets/camera-C3Lweuht.js | 61 --------------- .../analysis-lab/assets/camera-CNwCW116.js | 61 +++++++++++++++ .../{http-CaEmWEuL.js => http-Dln9HPhZ.js} | 2 +- .../analysis-lab/assets/index-6doD6GKE.css | 1 + .../analysis-lab/assets/index-BoSUzpvv.js | 26 ------- .../analysis-lab/assets/index-BwAjnQW0.js | 26 +++++++ .../analysis-lab/assets/index-Cm13xeJE.css | 1 - ...-cw-B1jE4hda.js => refresh-cw-krpJBiQY.js} | 2 +- ...ccw-D6okH6-S.js => rotate-ccw-BE-GHC4e.js} | 2 +- ...{system-C3fbtn0M.js => system-Bcfh7l2_.js} | 2 +- web/static/analysis-lab/camera.html | 10 +-- web/static/analysis-lab/index.html | 10 +-- web/static/analysis-lab/system.html | 10 +-- web/static/i18n/analysis.en.json | 4 + web/static/i18n/analysis.zh.json | 4 + 33 files changed, 449 insertions(+), 127 deletions(-) rename web/static/analysis-lab/assets/{analysis-fOaEdJ6w.js => analysis-DqbIrqpF.js} (99%) delete mode 100644 web/static/analysis-lab/assets/camera-C3Lweuht.js create mode 100644 web/static/analysis-lab/assets/camera-CNwCW116.js rename web/static/analysis-lab/assets/{http-CaEmWEuL.js => http-Dln9HPhZ.js} (97%) create mode 100644 web/static/analysis-lab/assets/index-6doD6GKE.css delete mode 100644 web/static/analysis-lab/assets/index-BoSUzpvv.js create mode 100644 web/static/analysis-lab/assets/index-BwAjnQW0.js delete mode 100644 web/static/analysis-lab/assets/index-Cm13xeJE.css rename web/static/analysis-lab/assets/{refresh-cw-B1jE4hda.js => refresh-cw-krpJBiQY.js} (91%) rename web/static/analysis-lab/assets/{rotate-ccw-D6okH6-S.js => rotate-ccw-BE-GHC4e.js} (88%) rename web/static/analysis-lab/assets/{system-C3fbtn0M.js => system-Bcfh7l2_.js} (99%) diff --git a/.gitignore b/.gitignore index 1ddd99b..5ec9f21 100644 --- a/.gitignore +++ b/.gitignore @@ -151,6 +151,7 @@ Thumbs.db # ==================== # 用户上传的图像 uploads/ +data/dev_captures/ captured_images/ *.fits *.fit @@ -205,4 +206,3 @@ TODO.md NOTES.md .obsidian/ PiFinder-release - diff --git a/docs/DEBUG_CONSOLE.md b/docs/DEBUG_CONSOLE.md index 7566735..95538e7 100644 --- a/docs/DEBUG_CONSOLE.md +++ b/docs/DEBUG_CONSOLE.md @@ -99,7 +99,8 @@ python -m ogscope.web.app 4. **拍摄照片** - 切换到 "拍摄控制" 标签页 - 点击 "拍摄照片" 按钮 - - 照片自动保存到 `~/dev_captures/` 目录 + - 照片默认保存到持久化目录 `./data/dev_captures/` + - 可用 `OGSCOPE_DEV_CAPTURES_DIR` 指定其他目录;指向 `/tmp` 或 `/run` 时界面会警告重启后清空 5. **录制视频** - 点击 "开始录制" 按钮 @@ -116,6 +117,7 @@ python -m ogscope.web.app - 切换到 "文件管理" 标签页 - 查看所有拍摄文件 - 点击 "下载" 下载文件到本地 + - 点击 "导出全部" 下载包含媒体、参数侧车和预设的 ZIP 备份 - 点击 "详情" 查看拍摄参数 ### 键盘快捷键 @@ -129,7 +131,7 @@ python -m ogscope.web.app ## 📂 文件结构 ``` -~/dev_captures/ # 拍摄文件存储目录 +./data/dev_captures/ # 默认持久化拍摄文件目录 ├── IMG_20241201_143022.jpg # 拍摄的照片 ├── IMG_20241201_143022.txt # 对应的参数文件 ├── VID_20241201_143045.mp4 # 录制的视频 @@ -137,6 +139,9 @@ python -m ogscope.web.app └── presets.json # 预设配置文件 ``` +服务首次使用持久化目录时,会从历史的 `/tmp/dev_captures/` 和 +`~/dev_captures/` 无覆盖复制现有文件。源文件保留,同名的持久化文件优先。 + ### 参数文件格式 每个拍摄文件都会生成对应的 `.txt` 参数文件,包含以下信息: diff --git a/docs/DEBUG_CONSOLE_EN.md b/docs/DEBUG_CONSOLE_EN.md index 0d709f5..d863805 100644 --- a/docs/DEBUG_CONSOLE_EN.md +++ b/docs/DEBUG_CONSOLE_EN.md @@ -76,10 +76,10 @@ Browser: `http://localhost:8000/debug` 1. **Start preview** — click Start, wait for init, view stream. 2. **Tune parameters** — Parameters tab, adjust sliders, Apply. 3. **Calibrate focus (optional)** — start focus calibration, rotate the lens slowly, and minimize HFD. Click a preview star when you want to lock the target. -4. **Capture still** — Capture tab, Capture photo; files under `~/dev_captures/`. +4. **Capture still** — Capture tab, Capture photo; files persist under `./data/dev_captures/` by default. Set `OGSCOPE_DEV_CAPTURES_DIR` to override it. Paths under `/tmp` or `/run` are marked as temporary in the UI and are cleared by a reboot. 5. **Record video** — Start recording, stop when done. 6. **Presets** — Presets tab: name, description, Save; Apply from cards. -7. **Files** — Files tab: list, download, details. +7. **Files** — Files tab: list, download, export all media and sidecars as a ZIP, or inspect details. ### Keyboard shortcuts @@ -92,7 +92,7 @@ Browser: `http://localhost:8000/debug` ## File layout ``` -~/dev_captures/ +./data/dev_captures/ ├── IMG_20241201_143022.jpg ├── IMG_20241201_143022.txt ├── VID_20241201_143045.mp4 @@ -100,6 +100,10 @@ Browser: `http://localhost:8000/debug` └── presets.json ``` +When persistent capture storage is first used, OGScope copies existing files from +legacy `/tmp/dev_captures/` and `~/dev_captures/` directories without overwriting +newer persistent files. Legacy source files remain in place. + ### Sidecar format (example) ```json diff --git a/ogscope/config.py b/ogscope/config.py index dca49c8..70cf9c6 100644 --- a/ogscope/config.py +++ b/ogscope/config.py @@ -220,6 +220,13 @@ class Settings(BaseSettings): # 文件路径配置 / File path configuration data_dir: Path = Field(default=Path("./data"), description="数据目录") upload_dir: Path = Field(default=Path("./uploads"), description="上传目录") + dev_captures_dir: Optional[Path] = Field( + default=None, + description=( + "开发者相机拍摄持久化目录;None 时使用 data/dev_captures / " + "Persistent developer-camera capture directory; defaults to data/dev_captures" + ), + ) analysis_dir: Path = Field( default=Path("./data/analysis"), description="分析任务目录" ) @@ -613,9 +620,13 @@ def _apply_development_mode_defaults(self) -> "Settings": def __init__(self, **kwargs): super().__init__(**kwargs) + if self.dev_captures_dir is None: + object.__setattr__(self, "dev_captures_dir", self.data_dir / "dev_captures") # 创建必要的目录 / Create necessary directories self.data_dir.mkdir(parents=True, exist_ok=True) self.upload_dir.mkdir(parents=True, exist_ok=True) + assert self.dev_captures_dir is not None + self.dev_captures_dir.mkdir(parents=True, exist_ok=True) self.analysis_dir.mkdir(parents=True, exist_ok=True) self.plate_solve_dir.mkdir(parents=True, exist_ok=True) diff --git a/ogscope/config_catalog.py b/ogscope/config_catalog.py index aa0f8ab..75d5de6 100644 --- a/ogscope/config_catalog.py +++ b/ogscope/config_catalog.py @@ -137,6 +137,7 @@ "database_url", "data_dir", "upload_dir", + "dev_captures_dir", "analysis_dir", "plate_solve_dir", "solver_tetra_database_path", diff --git a/ogscope/domain/camera/services.py b/ogscope/domain/camera/services.py index 984709e..8102bcd 100644 --- a/ogscope/domain/camera/services.py +++ b/ogscope/domain/camera/services.py @@ -344,6 +344,10 @@ class DebugFileService: async def get_files(): return await _debug_services_module().DebugFileService.get_files() + @staticmethod + async def create_export_archive(): + return await _debug_services_module().DebugFileService.create_export_archive() + @staticmethod async def get_file_info(filename: str): return await _debug_services_module().DebugFileService.get_file_info(filename) diff --git a/ogscope/domain/shared/filesystem.py b/ogscope/domain/shared/filesystem.py index 2d8aa5c..432adbf 100644 --- a/ogscope/domain/shared/filesystem.py +++ b/ogscope/domain/shared/filesystem.py @@ -4,10 +4,17 @@ from __future__ import annotations +import os +import shutil +from collections.abc import Iterable from pathlib import Path, PurePath -DEV_CAPTURES_DIR = Path("/tmp/dev_captures") -DEV_CAPTURES_DIR.mkdir(exist_ok=True) +from ogscope.config import get_settings + +_configured_dev_captures_dir = get_settings().dev_captures_dir +assert _configured_dev_captures_dir is not None +DEV_CAPTURES_DIR = Path(_configured_dev_captures_dir) +DEV_CAPTURES_DIR.mkdir(parents=True, exist_ok=True) IMAGE_EXTENSIONS = { ".jpg", @@ -39,3 +46,69 @@ def ensure_safe_basename(filename: str) -> str: if "/" in safe_name or "\\" in safe_name: raise ValueError("invalid filename") return safe_name + + +def dev_captures_storage_info(path: Path = DEV_CAPTURES_DIR) -> dict[str, object]: + """返回调试拍摄目录的持久化语义 / Describe capture storage persistence.""" + resolved = path.expanduser().resolve() + volatile_roots = (Path("/tmp").resolve(), Path("/run").resolve()) + is_temporary = any( + resolved == root or root in resolved.parents for root in volatile_roots + ) + return { + "path": str(resolved), + "persistence": "temporary" if is_temporary else "persistent", + "is_persistent": not is_temporary, + } + + +def migrate_legacy_dev_captures( + legacy_dirs: Iterable[Path] | None = None, + target_dir: Path = DEV_CAPTURES_DIR, +) -> dict[str, object]: + """ + 无覆盖迁移历史调试拍摄文件 / Migrate legacy captures without overwriting. + + 源文件保留不删,避免更新中断时两边都丢失。 + Source files stay in place so an interrupted update cannot lose both copies. + """ + target = target_dir.expanduser().resolve() + target.mkdir(parents=True, exist_ok=True) + sources = tuple( + (Path("/tmp/dev_captures"), Path.home() / "dev_captures") + if legacy_dirs is None + else legacy_dirs + ) + migrated: list[str] = [] + skipped: list[str] = [] + errors: list[str] = [] + + for source_dir in sources: + source = source_dir.expanduser().resolve() + if source == target or not source.is_dir(): + continue + try: + source_paths = sorted(source.iterdir(), key=lambda item: item.name) + except OSError as exc: + errors.append(f"{source}: {exc}") + continue + for source_path in source_paths: + if not source_path.is_file() or source_path.is_symlink(): + continue + try: + safe_name = ensure_safe_basename(source_path.name) + destination = target / safe_name + if destination.exists(): + skipped.append(safe_name) + continue + temporary = target / f".{safe_name}.migrating-{os.getpid()}" + try: + shutil.copy2(source_path, temporary) + os.replace(temporary, destination) + finally: + temporary.unlink(missing_ok=True) + migrated.append(safe_name) + except (OSError, ValueError) as exc: + errors.append(f"{source_path}: {exc}") + + return {"migrated": migrated, "skipped": skipped, "errors": errors} diff --git a/ogscope/web/api/analysis/services.py b/ogscope/web/api/analysis/services.py index 5667e3e..73fdd2d 100644 --- a/ogscope/web/api/analysis/services.py +++ b/ogscope/web/api/analysis/services.py @@ -804,15 +804,15 @@ async def batch_solve(self, body: AnalysisBatchSolveRequest) -> dict[str, Any]: return {"input_name": body.input_name, "results": results} def import_from_debug_capture(self, filename: str) -> dict[str, Any]: - """从 ~/dev_captures 复制到分析素材池并标记来源 / Copy debug capture into pool.""" - src = Path.home() / "dev_captures" / Path(filename).name + """从统一调试拍摄目录复制到素材池 / Copy configured debug capture into pool.""" + src = DEV_CAPTURES_DIR / ensure_safe_basename(filename.strip()) if not src.is_file(): raise FileNotFoundError( "调试采集文件不存在 / Debug capture file not found in dev_captures" ) dst = self.upload_root / src.name shutil.copy2(src, dst) - side_txt = Path.home() / "dev_captures" / f"{src.stem}.txt" + side_txt = DEV_CAPTURES_DIR / f"{src.stem}.txt" if side_txt.is_file(): shutil.copy2(side_txt, self.upload_root / side_txt.name) self._lab.set_file_source(dst.name, "debug_console") diff --git a/ogscope/web/api/debug/routes.py b/ogscope/web/api/debug/routes.py index 4cdbdc9..7729912 100644 --- a/ogscope/web/api/debug/routes.py +++ b/ogscope/web/api/debug/routes.py @@ -7,6 +7,7 @@ from fastapi import APIRouter, HTTPException, Query, Request from fastapi.responses import FileResponse, StreamingResponse +from starlette.background import BackgroundTask from ogscope.config import get_settings from ogscope.core.application import core_contract_service @@ -428,6 +429,21 @@ async def get_capture_files(): raise HTTPException(status_code=500, detail=str(e)) +@router.get("/debug/files/export") +async def export_capture_files(): + """导出全部拍摄文件及参数侧车 / Export all captures and sidecars.""" + try: + archive_path, archive_name = await DebugFileService.create_export_archive() + return FileResponse( + path=str(archive_path), + filename=archive_name, + media_type="application/zip", + background=BackgroundTask(archive_path.unlink, missing_ok=True), + ) + except Exception as e: + raise HTTPException(status_code=500, detail=str(e)) + + @router.get("/debug/files/{filename}") async def download_capture_file(filename: str): """下载拍摄文件 / Download shooting files""" diff --git a/ogscope/web/api/debug/services.py b/ogscope/web/api/debug/services.py index 5ef398c..2a6acc2 100644 --- a/ogscope/web/api/debug/services.py +++ b/ogscope/web/api/debug/services.py @@ -5,7 +5,10 @@ import asyncio import json import logging +import os +import tempfile import time +import zipfile from datetime import datetime from pathlib import Path from typing import Any, Optional @@ -17,6 +20,7 @@ DEV_CAPTURES_DIR, IMAGE_EXTENSIONS, VIDEO_EXTENSIONS, + dev_captures_storage_info, ensure_safe_basename, ) from ogscope.web.camera_shared import get_camera_manager @@ -62,6 +66,31 @@ _camera_ensure_lock = asyncio.Lock() +def _build_debug_capture_archive() -> tuple[Path, str]: + """在持久化卷上构建导出包 / Build export archive on the capture volume.""" + archive_name = f"ogscope_debug_captures_{datetime.now():%Y%m%d_%H%M%S}.zip" + fd, temporary_name = tempfile.mkstemp( + prefix=".ogscope-debug-captures-", + suffix=".zip", + dir=str(DEBUG_CAPTURES_DIR.parent), + ) + os.close(fd) + archive_path = Path(temporary_name) + try: + with zipfile.ZipFile( + archive_path, mode="w", compression=zipfile.ZIP_DEFLATED + ) as archive: + for source_path in sorted( + DEBUG_CAPTURES_DIR.iterdir(), key=lambda item: item.name + ): + if source_path.is_file() and not source_path.is_symlink(): + archive.write(source_path, arcname=source_path.name) + return archive_path, archive_name + except Exception: + archive_path.unlink(missing_ok=True) + raise + + def _get_recording_state_lock() -> asyncio.Lock: """懒加载录制状态锁 / Lazy-init lock for recording state.""" global recording_state_lock @@ -1687,11 +1716,19 @@ async def get_files(): # 按修改时间排序(最新的在前) / Sort by modification time (newest first) files.sort(key=lambda x: x["modified"], reverse=True) - return {"files": files} + return { + "files": files, + "storage": dev_captures_storage_info(DEBUG_CAPTURES_DIR), + } except Exception as e: raise Exception(f"获取文件列表失败: {str(e)}") + @staticmethod + async def create_export_archive() -> tuple[Path, str]: + """创建调试拍摄导出包 / Create a debug-capture export archive.""" + return await asyncio.to_thread(_build_debug_capture_archive) + @staticmethod async def get_file_info(filename: str): """获取文件信息 / Get file information""" diff --git a/ogscope/web/app.py b/ogscope/web/app.py index 77650e9..9f32544 100644 --- a/ogscope/web/app.py +++ b/ogscope/web/app.py @@ -26,6 +26,7 @@ from ogscope.__version__ import __version__ from ogscope.config import get_settings +from ogscope.domain.shared.filesystem import migrate_legacy_dev_captures from ogscope.platform.hardware_plane.runtime import ( describe_hardware_plane_profile, get_hardware_plane_client, @@ -42,6 +43,17 @@ async def lifespan(app: FastAPI) -> AsyncGenerator: # 启动时执行 / Execute at startup logger.info("初始化 Web 应用...") settings = get_settings() + capture_migration = migrate_legacy_dev_captures() + if capture_migration["migrated"]: + logger.info( + "已迁移历史调试拍摄文件 / Migrated legacy debug captures: {}", + len(capture_migration["migrated"]), + ) + if capture_migration["errors"]: + logger.warning( + "历史调试拍摄迁移部分失败 / Some legacy debug captures failed to migrate: {}", + capture_migration["errors"], + ) hp_profile = describe_hardware_plane_profile(settings) logger.info( "硬件角色={} 传感器来源={} 本地传感器={} HMI={} UI={} 远端UDS={}", diff --git a/scripts/sync_board_code.sh b/scripts/sync_board_code.sh index 3db7f0c..9042530 100755 --- a/scripts/sync_board_code.sh +++ b/scripts/sync_board_code.sh @@ -1,6 +1,6 @@ #!/usr/bin/env bash -# 同步 OGScope 源码到开发板并执行 board-update(保留 uploads/logs/data) -# Sync OGScope source to dev board and run board-update (keeps uploads/logs/data) +# 同步 OGScope 源码到开发板并执行 board-update(保留 uploads/logs/data 及调试拍摄) +# Sync OGScope source and run board-update (keeps uploads/logs/data and debug captures) # # 用法 / Usage: # export OGSCOPE_DEV_HOST=192.168.31.231 @@ -41,7 +41,7 @@ do fi done -echo "== Sync OGScope code → ${REMOTE}:${DEV_PATH} (uploads/logs/data preserved) ==" +echo "== Sync OGScope code → ${REMOTE}:${DEV_PATH} (uploads/logs/data and debug captures preserved) ==" rsync -avz --delete \ -e "${RSYNC_SSH}" \ diff --git a/tests/unit/test_analysis_api.py b/tests/unit/test_analysis_api.py index d2dc38b..00d7867 100644 --- a/tests/unit/test_analysis_api.py +++ b/tests/unit/test_analysis_api.py @@ -83,6 +83,30 @@ def test_analysis_upload_and_single_image_solve( assert "status" in result +@pytest.mark.unit +def test_analysis_import_uses_configured_debug_capture_directory( + client, temp_analysis_dir, monkeypatch, tmp_path: Path +): + """分析导入与调试台共用同一采集目录 / Analysis import shares capture storage.""" + from ogscope.web.api.analysis import services as analysis_services + + debug_root = tmp_path / "configured-debug-captures" + debug_root.mkdir() + (debug_root / "field.jpg").write_bytes(b"field-image") + (debug_root / "field.txt").write_text('{"exposure_us": 500000}', encoding="utf-8") + monkeypatch.setattr(analysis_services, "DEV_CAPTURES_DIR", debug_root) + + response = client.post( + "/api/dev/analysis/uploads/import_from_debug", + json={"filename": "field.jpg"}, + ) + + assert response.status_code == 200 + assert response.json()["filename"] == "field.jpg" + assert (temp_analysis_dir / "uploads" / "field.jpg").read_bytes() == b"field-image" + assert (temp_analysis_dir / "uploads" / "field.txt").is_file() + + @pytest.mark.unit def test_analysis_solve_image_overlay_ext( client, temp_analysis_dir, mock_plate_solve, tmp_path: Path diff --git a/tests/unit/test_config_catalog.py b/tests/unit/test_config_catalog.py index d48c289..a0010df 100644 --- a/tests/unit/test_config_catalog.py +++ b/tests/unit/test_config_catalog.py @@ -21,6 +21,7 @@ def test_build_config_catalog_includes_new_preview_fields() -> None: assert "OGSCOPE_CAMERA_AUTO_EXPOSURE_MAX_US" in keys assert "OGSCOPE_CAMERA_NOISE_REDUCTION_MODE" in keys assert "OGSCOPE_SIMULATION_MODE" in keys + assert "OGSCOPE_DEV_CAPTURES_DIR" in keys @pytest.mark.unit diff --git a/tests/unit/test_debug_files_api.py b/tests/unit/test_debug_files_api.py index d5cbf44..8451fd4 100644 --- a/tests/unit/test_debug_files_api.py +++ b/tests/unit/test_debug_files_api.py @@ -2,7 +2,9 @@ 调试文件 API 的最小回归测试。 """ +import io import json +import zipfile import pytest @@ -11,7 +13,10 @@ def test_debug_files_empty(client, temp_debug_dir): response = client.get("/api/dev/debug/files") assert response.status_code == 200 - assert response.json() == {"files": []} + payload = response.json() + assert payload["files"] == [] + assert payload["storage"]["path"] == str(temp_debug_dir) + assert payload["storage"]["is_persistent"] is True @pytest.mark.unit @@ -55,3 +60,22 @@ def test_debug_files_delete_removes_image_and_info(client, temp_debug_dir): assert "message_key" in delete_resp.json() assert not image_path.exists() assert not info_path.exists() + + +@pytest.mark.unit +def test_debug_files_export_includes_media_sidecar_and_presets(client, temp_debug_dir): + (temp_debug_dir / "IMG_20260101_000002.jpg").write_bytes(b"image") + (temp_debug_dir / "IMG_20260101_000002.txt").write_text("{}", encoding="utf-8") + (temp_debug_dir / "presets.json").write_text("{}", encoding="utf-8") + + response = client.get("/api/dev/debug/files/export") + + assert response.status_code == 200 + assert response.headers["content-type"] == "application/zip" + with zipfile.ZipFile(io.BytesIO(response.content)) as archive: + assert archive.namelist() == [ + "IMG_20260101_000002.jpg", + "IMG_20260101_000002.txt", + "presets.json", + ] + assert list(temp_debug_dir.parent.glob(".ogscope-debug-captures-*.zip")) == [] diff --git a/tests/unit/test_domain_shared_filesystem.py b/tests/unit/test_domain_shared_filesystem.py index 77e430a..9108546 100644 --- a/tests/unit/test_domain_shared_filesystem.py +++ b/tests/unit/test_domain_shared_filesystem.py @@ -1,8 +1,15 @@ from __future__ import annotations +from pathlib import Path + import pytest -from ogscope.domain.shared.filesystem import ensure_safe_basename +from ogscope.config import Settings +from ogscope.domain.shared.filesystem import ( + dev_captures_storage_info, + ensure_safe_basename, + migrate_legacy_dev_captures, +) @pytest.mark.unit @@ -34,3 +41,71 @@ def test_ensure_safe_basename_accepts_valid_names(name: str) -> None: def test_ensure_safe_basename_rejects_invalid_names(name: str) -> None: with pytest.raises(ValueError): ensure_safe_basename(name) + + +@pytest.mark.unit +def test_settings_defaults_debug_captures_under_data_dir(tmp_path: Path) -> None: + data_dir = tmp_path / "data" + settings = Settings( + data_dir=data_dir, + upload_dir=tmp_path / "uploads", + dev_captures_dir=None, + analysis_dir=tmp_path / "analysis", + plate_solve_dir=tmp_path / "plate_solve", + ) + + assert settings.dev_captures_dir == data_dir / "dev_captures" + assert settings.dev_captures_dir.is_dir() + + +@pytest.mark.unit +def test_settings_accepts_explicit_debug_capture_directory(tmp_path: Path) -> None: + capture_dir = tmp_path / "volatile-or-external-captures" + settings = Settings( + data_dir=tmp_path / "data", + upload_dir=tmp_path / "uploads", + dev_captures_dir=capture_dir, + analysis_dir=tmp_path / "analysis", + plate_solve_dir=tmp_path / "plate_solve", + ) + + assert settings.dev_captures_dir == capture_dir + assert capture_dir.is_dir() + + +@pytest.mark.unit +def test_storage_info_distinguishes_persistent_and_temporary(tmp_path: Path) -> None: + persistent = dev_captures_storage_info(tmp_path / "captures") + temporary = dev_captures_storage_info(Path("/tmp/dev_captures")) + + assert persistent["is_persistent"] is True + assert persistent["persistence"] == "persistent" + assert temporary["is_persistent"] is False + assert temporary["persistence"] == "temporary" + + +@pytest.mark.unit +def test_migrate_legacy_captures_preserves_sources_and_existing_target( + tmp_path: Path, +) -> None: + legacy_tmp = tmp_path / "legacy-tmp" + legacy_home = tmp_path / "legacy-home" + target = tmp_path / "persistent" + for directory in (legacy_tmp, legacy_home, target): + directory.mkdir() + (legacy_tmp / "IMG_1.jpg").write_bytes(b"from-tmp") + (legacy_home / "IMG_2.jpg").write_bytes(b"from-home") + (legacy_home / "IMG_1.jpg").write_bytes(b"home-conflict") + (target / "IMG_1.jpg").write_bytes(b"persistent-wins") + + result = migrate_legacy_dev_captures( + legacy_dirs=(legacy_tmp, legacy_home), target_dir=target + ) + + assert result["migrated"] == ["IMG_2.jpg"] + assert result["skipped"] == ["IMG_1.jpg", "IMG_1.jpg"] + assert result["errors"] == [] + assert (target / "IMG_1.jpg").read_bytes() == b"persistent-wins" + assert (target / "IMG_2.jpg").read_bytes() == b"from-home" + assert (legacy_tmp / "IMG_1.jpg").is_file() + assert (legacy_home / "IMG_2.jpg").is_file() diff --git a/web/spa/src/apps/camera/CameraConsoleApp.tsx b/web/spa/src/apps/camera/CameraConsoleApp.tsx index 863afb3..246b1f6 100644 --- a/web/spa/src/apps/camera/CameraConsoleApp.tsx +++ b/web/spa/src/apps/camera/CameraConsoleApp.tsx @@ -229,6 +229,12 @@ type DebugFileItem = { type: "image" | "video"; }; +type DebugStorageInfo = { + path: string; + persistence: "persistent" | "temporary"; + is_persistent: boolean; +}; + type DebugFileInfo = { filename: string; size: number; @@ -510,6 +516,7 @@ export function CameraConsoleApp() { const [presets, setPresets] = useState([]); const [presetBusy, setPresetBusy] = useState(false); const [files, setFiles] = useState([]); + const [fileStorage, setFileStorage] = useState(null); const [fileBusy, setFileBusy] = useState(false); const [fileInfo, setFileInfo] = useState(null); const [fileInfoBusy, setFileInfoBusy] = useState(false); @@ -1033,8 +1040,9 @@ export function CameraConsoleApp() { const loadFiles = async () => { setFileBusy(true); try { - const data = await requestJson<{ files?: DebugFileItem[] }>("/api/debug/files", { cache: "no-store" }); + const data = await requestJson<{ files?: DebugFileItem[]; storage?: DebugStorageInfo }>("/api/debug/files", { cache: "no-store" }); setFiles(data.files ?? []); + setFileStorage(data.storage ?? null); setFilePage(1); } catch (e) { setErr(e instanceof Error ? e.message : String(e)); @@ -1043,6 +1051,16 @@ export function CameraConsoleApp() { } }; + const exportAllFiles = () => { + const anchor = document.createElement("a"); + anchor.href = `${debugApi("/files")}/export`; + anchor.download = ""; + document.body.appendChild(anchor); + anchor.click(); + document.body.removeChild(anchor); + setNotice(t("cam.notice.exportAll")); + }; + const closeFileInfo = () => { setFileDetailKey(null); setFileInfo(null); @@ -2094,10 +2112,19 @@ export function CameraConsoleApp() {

{t("cam.files.title")}

-
+ {fileStorage && ( +
+
{fileStorage.is_persistent === false ? t("cam.files.storageTemporary") : t("cam.files.storagePersistent")}
+
{fileStorage.path}
+
+ )} +
+
{fileInfoBusy &&
{t("cam.files.loadingInfo")}
} {fileInfo && ( diff --git a/web/static/analysis-lab/assets/analysis-fOaEdJ6w.js b/web/static/analysis-lab/assets/analysis-DqbIrqpF.js similarity index 99% rename from web/static/analysis-lab/assets/analysis-fOaEdJ6w.js rename to web/static/analysis-lab/assets/analysis-DqbIrqpF.js index a22215c..bc3c669 100644 --- a/web/static/analysis-lab/assets/analysis-fOaEdJ6w.js +++ b/web/static/analysis-lab/assets/analysis-DqbIrqpF.js @@ -1,4 +1,4 @@ -var As=Object.defineProperty;var Ua=e=>{throw TypeError(e)};var Us=(e,s,n)=>s in e?As(e,s,{enumerable:!0,configurable:!0,writable:!0,value:n}):e[s]=n;var O=(e,s,n)=>Us(e,typeof s!="symbol"?s+"":s,n),Ba=(e,s,n)=>s.has(e)||Ua("Cannot "+n);var h=(e,s,n)=>(Ba(e,s,"read from private field"),n?n.call(e):s.get(e)),te=(e,s,n)=>s.has(e)?Ua("Cannot add the same private member more than once"):s instanceof WeakSet?s.add(e):s.set(e,n),Oe=(e,s,n,r)=>(Ba(e,s,"write to private field"),r?r.call(e,n):s.set(e,n),n);import{j as t,r as i,a as Bs,R as Hs}from"./client-D1ZVDB-N.js";import{c as Y,u as Te,T as ss,I as zs}from"./index-BoSUzpvv.js";import{R as Ws}from"./refresh-cw-B1jE4hda.js";import{R as Ha}from"./rotate-ccw-D6okH6-S.js";/** +var As=Object.defineProperty;var Ua=e=>{throw TypeError(e)};var Us=(e,s,n)=>s in e?As(e,s,{enumerable:!0,configurable:!0,writable:!0,value:n}):e[s]=n;var O=(e,s,n)=>Us(e,typeof s!="symbol"?s+"":s,n),Ba=(e,s,n)=>s.has(e)||Ua("Cannot "+n);var h=(e,s,n)=>(Ba(e,s,"read from private field"),n?n.call(e):s.get(e)),te=(e,s,n)=>s.has(e)?Ua("Cannot add the same private member more than once"):s instanceof WeakSet?s.add(e):s.set(e,n),Oe=(e,s,n,r)=>(Ba(e,s,"write to private field"),r?r.call(e,n):s.set(e,n),n);import{j as t,r as i,a as Bs,R as Hs}from"./client-D1ZVDB-N.js";import{c as Y,u as Te,T as ss,I as zs}from"./index-BwAjnQW0.js";import{R as Ws}from"./refresh-cw-krpJBiQY.js";import{R as Ha}from"./rotate-ccw-BE-GHC4e.js";/** * @license lucide-react v0.460.0 - ISC * * This source code is licensed under the ISC license. diff --git a/web/static/analysis-lab/assets/camera-C3Lweuht.js b/web/static/analysis-lab/assets/camera-C3Lweuht.js deleted file mode 100644 index c2d91d3..0000000 --- a/web/static/analysis-lab/assets/camera-C3Lweuht.js +++ /dev/null @@ -1,61 +0,0 @@ -import{r as o,j as e,a as Fr,R as Er}from"./client-D1ZVDB-N.js";import{c as T,u as Rr,T as $r,I as Br}from"./index-BoSUzpvv.js";import{u as Pr,S as Mr,C as Ia,X as Tr,r as Ir,b as Ar}from"./http-CaEmWEuL.js";import{R as Or}from"./rotate-ccw-D6okH6-S.js";/** - * @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=T("Circle",[["circle",{cx:"12",cy:"12",r:"10",key:"1mglay"}]]);/** - * @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 yt=T("Crosshair",[["circle",{cx:"12",cy:"12",r:"10",key:"1mglay"}],["line",{x1:"22",x2:"18",y1:"12",y2:"12",key:"l9bcsi"}],["line",{x1:"6",x2:"2",y1:"12",y2:"12",key:"13hhkx"}],["line",{x1:"12",x2:"12",y1:"6",y2:"2",key:"10w3f3"}],["line",{x1:"12",x2:"12",y1:"22",y2:"18",key:"15g9kq"}]]);/** - * @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 Hr=T("Download",[["path",{d:"M21 15v4a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2v-4",key:"ih7n3h"}],["polyline",{points:"7 10 12 15 17 10",key:"2ggqvy"}],["line",{x1:"12",x2:"12",y1:"15",y2:"3",key:"1vk2je"}]]);/** - * @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 Dr=T("FileText",[["path",{d:"M15 2H6a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V7Z",key:"1rqfz7"}],["path",{d:"M14 2v4a2 2 0 0 0 2 2h4",key:"tnqrlb"}],["path",{d:"M10 9H8",key:"b1mrlr"}],["path",{d:"M16 13H8",key:"t4e002"}],["path",{d:"M16 17H8",key:"z1uh3a"}]]);/** - * @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 Gr=T("FolderOpen",[["path",{d:"m6 14 1.5-2.9A2 2 0 0 1 9.24 10H20a2 2 0 0 1 1.94 2.5l-1.54 6a2 2 0 0 1-1.95 1.5H4a2 2 0 0 1-2-2V5a2 2 0 0 1 2-2h3.9a2 2 0 0 1 1.69.9l.81 1.2a2 2 0 0 0 1.67.9H18a2 2 0 0 1 2 2v2",key:"usdka0"}]]);/** - * @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 zr=T("Info",[["circle",{cx:"12",cy:"12",r:"10",key:"1mglay"}],["path",{d:"M12 16v-4",key:"1dtifu"}],["path",{d:"M12 8h.01",key:"e9boi3"}]]);/** - * @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 Lr=T("Moon",[["path",{d:"M12 3a6 6 0 0 0 9 9 9 9 0 1 1-9-9Z",key:"a7tn18"}]]);/** - * @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 Oa=T("Play",[["polygon",{points:"6 3 20 12 6 21 6 3",key:"1oa8hb"}]]);/** - * @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 qr=T("Settings2",[["path",{d:"M20 7h-9",key:"3s1dr2"}],["path",{d:"M14 17H5",key:"gfn3mx"}],["circle",{cx:"17",cy:"17",r:"3",key:"18b49y"}],["circle",{cx:"7",cy:"7",r:"3",key:"dfmy0x"}]]);/** - * @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 Ur=T("SlidersHorizontal",[["line",{x1:"21",x2:"14",y1:"4",y2:"4",key:"obuewd"}],["line",{x1:"10",x2:"3",y1:"4",y2:"4",key:"1q6298"}],["line",{x1:"21",x2:"12",y1:"12",y2:"12",key:"1iu8h1"}],["line",{x1:"8",x2:"3",y1:"12",y2:"12",key:"ntss68"}],["line",{x1:"21",x2:"16",y1:"20",y2:"20",key:"14d8ph"}],["line",{x1:"12",x2:"3",y1:"20",y2:"20",key:"m0wm8r"}],["line",{x1:"14",x2:"14",y1:"2",y2:"6",key:"14e1ph"}],["line",{x1:"8",x2:"8",y1:"10",y2:"14",key:"1i6ji0"}],["line",{x1:"16",x2:"16",y1:"18",y2:"22",key:"1lctlv"}]]);/** - * @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 Wr=T("Square",[["rect",{width:"18",height:"18",x:"3",y:"3",rx:"2",key:"afitv7"}]]);/** - * @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 Vr=T("Sun",[["circle",{cx:"12",cy:"12",r:"4",key:"4exip2"}],["path",{d:"M12 2v2",key:"tus03m"}],["path",{d:"M12 20v2",key:"1lh1kg"}],["path",{d:"m4.93 4.93 1.41 1.41",key:"149t6j"}],["path",{d:"m17.66 17.66 1.41 1.41",key:"ptbguv"}],["path",{d:"M2 12h2",key:"1t8f8n"}],["path",{d:"M20 12h2",key:"1q8mjw"}],["path",{d:"m6.34 17.66-1.41 1.41",key:"1m8zz5"}],["path",{d:"m19.07 4.93-1.41 1.41",key:"1shlcs"}]]),Xr="/api/dev/debug";function H(a){return`${Xr}${a}`}async function g(a,b={}){const f=a.startsWith("/api/debug/")?a.replace("/api/debug/","/api/dev/debug/"):a;return Ir(f,b)}const Jr=["640x360","1280x720","1600x900","1920x1020"],Kr=[0,90,180,270],Je=12,ce=1e-4,le=1,Yr=1e-4;function N(a,b,f){return Math.min(f,Math.max(b,a))}function k(a,b){return a==null||Number.isNaN(Number(a))?b:Number(a)}function de(a,b){return k(a,b)/1e6}function wt(a){return Math.round(a*1e6)}function jt(a){return`${de(a,0).toFixed(4)} s`}function Ha(a){const{exposureSeconds:b,autoExposureMaxSeconds:f,...p}=a;return{...p,exposure:wt(b),autoExposureMaxUs:wt(f)}}function Da(a){if(!a)return"0 B";const b=["B","KB","MB","GB"];let f=0,p=a;for(;p>=1024&&fE.hfd),p=Math.min(...f),r=Math.max(...f),J=Math.max(.2,r-p),m=f.map((E,K)=>{const ue=K/Math.max(1,f.length-1)*320,l=76-(E-p)/J*60;return`${ue.toFixed(1)},${l.toFixed(1)}`}).join(" ");return e.jsxs("svg",{viewBox:"0 0 320 88",className:"h-24 w-full rounded-lg border border-outline-variant/20 bg-black/25",role:"img",children:[e.jsx("line",{x1:"0",y1:"76",x2:"320",y2:"76",stroke:"currentColor",opacity:"0.18"}),e.jsx("polyline",{points:m,fill:"none",stroke:"currentColor",strokeWidth:"3",strokeLinejoin:"round",className:"text-primary"}),e.jsx("circle",{cx:"320",cy:((B=m.split(" ").at(-1))==null?void 0:B.split(",")[1])??"44",r:"4",fill:"currentColor",className:"text-primary"})]})}async function Ga(){try{const a=await fetch(`${H("/camera/stream/status")}`,{cache:"no-store",credentials:"same-origin"});if(!a.ok)return"fail";const b=await a.json(),f=Number(b.max_clients??0),p=Number(b.active_clients??0);return f>0&&p>=f?"busy":"ok"}catch{return"fail"}}function X({label:a,value:b,min:f,max:p,step:r,onChange:J,disabled:m=!1,unit:B=""}){const E=r>=1?0:Math.min(6,Math.max(1,Math.ceil(-Math.log10(r))));return e.jsxs("label",{className:`block ${m?"opacity-50":""}`,children:[e.jsxs("div",{className:"mb-1 flex items-center justify-between",children:[e.jsx("span",{children:a}),e.jsxs("span",{className:"font-mono text-[11px]",children:[b.toFixed(E),B]})]}),e.jsx("input",{type:"range",min:f,max:p,step:r,value:b,disabled:m,onChange:K=>J(Number(K.target.value)),className:"w-full accent-primary"})]})}function Qr({label:a,value:b,disabled:f,onChange:p}){const r=N(b,ce,le),J=Math.log10(ce),m=Math.log10(le);return e.jsxs("div",{className:`block ${f?"opacity-50":""}`,children:[e.jsxs("div",{className:"mb-1 flex items-center justify-between gap-2",children:[e.jsx("span",{children:a}),e.jsxs("span",{className:"flex items-center gap-1",children:[e.jsx("input",{type:"number",min:ce,max:le,step:Yr,value:Number(r.toFixed(6)),disabled:f,"aria-label":a,onChange:B=>{const E=Number(B.target.value);Number.isFinite(E)&&p(N(E,ce,le))},className:"w-24 rounded border border-outline-variant/30 bg-surface-container-low px-1.5 py-0.5 text-right font-mono text-[11px]"}),e.jsx("span",{className:"font-mono text-[11px]",children:"s"})]})]}),e.jsx("input",{type:"range",min:J,max:m,step:.001,value:Math.log10(r),disabled:f,"aria-label":a,onChange:B=>{const E=Number((10**Number(B.target.value)).toFixed(6));p(N(E,ce,le))},className:"w-full accent-primary"})]})}function en(){var ua,ma,xa,pa,ha,fa,ga,ba,va,ya,ja,wa,Na,Sa,_a,ka,Ca,Fa,Ea,Ra,$a,Ba,Pa,Ma;const{t:a,locale:b,setLocale:f}=Rr(),{info:p}=Pr(),[r,J]=o.useState(null),[m,B]=o.useState(!1),[E,K]=o.useState(()=>Date.now()),[ue,l]=o.useState(null),[we,Q]=o.useState(null),[Nt,ee]=o.useState(!1),[me,v]=o.useState(null),[I,Ne]=o.useState(!1),[Ke,St]=o.useState(!1),[_t,kt]=o.useState(!1),[Ye,Ct]=o.useState("8"),[Ft,Et]=o.useState("8"),[Rt,$t]=o.useState("1280x720"),[Bt,Pt]=o.useState("supersample"),[xe,te]=o.useState(!1),[Mt,za]=o.useState(!0),[Ze,La]=o.useState(!0),[Qe,qa]=o.useState(!1),[Tt,Ua]=o.useState(!1),[It,At]=o.useState(!1),[et,Wa]=o.useState({mean:0,std:0,over:0}),[Va,Se]=o.useState(0),[i,tt]=o.useState(null),[_,at]=o.useState(!1),[ae,_e]=o.useState(!1),[s,Ot]=o.useState(null),[ke,pe]=o.useState([]),[re,he]=o.useState(null),[j,rt]=o.useState(null),[Ht,D]=o.useState(null),[Dt,Gt]=o.useState(0),[zt,Lt]=o.useState(180),[Ce,qt]=o.useState(!1),[Fe,Ut]=o.useState(!1),[d,S]=o.useState({exposureSeconds:.005,gain:1,digitalGain:1,autoExposure:!0,contrast:1,brightness:0,saturation:1,sharpness:1,noiseReduction:0,noiseReductionMode:"fast",aeFlickerMode:"off",autoExposureMaxSeconds:1,whiteBalanceMode:"auto",whiteBalanceGainR:1,whiteBalanceGainB:1,colorMode:"color"}),[Ee,w]=o.useState(!1),[nt,Wt]=o.useState(""),[Vt,Xt]=o.useState(""),[Jt,Xa]=o.useState([]),[st,L]=o.useState(!1),[fe,Ja]=o.useState([]),[Kt,Yt]=o.useState(!1),[C,ot]=o.useState(null),[Ka,it]=o.useState(!1),[Re,ct]=o.useState(null),[lt,$e]=o.useState(1),P=o.useRef(null),Be=o.useRef(null),ge=o.useRef(null),Pe=o.useRef(null),Me=o.useRef(null),q=o.useRef(null),Te=o.useRef(null),dt=o.useRef(!1),ne=o.useRef(!1),Y=o.useRef(!1),Ie=o.useRef(null),U=o.useRef([]),se=o.useRef(0),ut=o.useRef(null),Zt=()=>{U.current=[],se.current=0,Ie.current=null,Se(0)},R=async()=>{try{const t=await g("/api/debug/camera/status",{cache:"no-store"});J(t),t.streaming||(B(!1),Y.current=!1)}catch(t){l(t instanceof Error?t.message:String(t))}},be=()=>{Te.current!=null&&(window.clearTimeout(Te.current),Te.current=null)},mt=async()=>{if(I)return!1;Ne(!0),l(null),Q(null),ee(!1);try{be(),r!=null&&r.streaming||await g("/api/debug/camera/start",{method:"POST"});const t=Date.now(),n=await Ga();return n==="busy"?(Q(a("cam.err.streamBusy")),ee(!0),!1):n==="fail"?(Q(a("cam.err.streamProbeFailed")),ee(!1),!1):(B(!0),Y.current=!0,v(a("cam.notice.previewStart")),Zt(),Ie.current=performance.now(),K(t),await R(),!0)}catch(t){return l(t instanceof Error?t.message:String(t)),!1}finally{Ne(!1)}},Qt=async()=>{if(!ae){_e(!0),D(null);try{if(!(m||await mt()))return;const n=await g(H("/camera/focus/session/start"),{method:"POST"});ne.current=n.active,S(c=>({...c,autoExposure:!1,exposureSeconds:de(n.locked.exposure,5e3),gain:n.locked.gain,digitalGain:n.locked.digitalGain,noiseReduction:0,noiseReductionMode:n.locked.noiseReductionMode})),w(!1),Ot(null),pe([]),he(null),rt(null),At(!0),at(!0),window.setTimeout(()=>{var c;(c=document.getElementById("focus-calibration-panel"))==null||c.scrollIntoView({behavior:"smooth",block:"start"})},0)}catch(t){D(t instanceof Error?t.message:String(t))}finally{_e(!1)}}},xt=async()=>{if(!ae){_e(!0),at(!1),D(null);try{if(ne.current){const t=await g(H("/camera/focus/session/stop"),{method:"POST"});ne.current=!1,t.restored_settings&&(S(n=>({...n,autoExposure:t.restored_settings.autoExposure,exposureSeconds:de(t.restored_settings.exposure,5e3),gain:t.restored_settings.gain,digitalGain:t.restored_settings.digitalGain,noiseReductionMode:t.restored_settings.noiseReductionMode,noiseReduction:t.restored_settings.noiseReductionMode==="off"?0:1})),w(!1))}}catch(t){D(t instanceof Error?t.message:String(t))}finally{_e(!1)}}},Ya=()=>{pe([]),he(null),D(null)},Za=t=>{if(!_||!P.current||t.target.closest("button, label, input"))return;const n=P.current,c=t.currentTarget.getBoundingClientRect(),h=n.naturalWidth||(s==null?void 0:s.frame.width)||0,u=n.naturalHeight||(s==null?void 0:s.frame.height)||0;if(h<=0||u<=0)return;const x=Math.min(c.width/h,c.height/u),F=h*x,y=u*x,$=(c.width-F)/2,M=(c.height-y)/2,z=(t.clientX-c.left-$)/F,oe=(t.clientY-c.top-M)/y;z<0||z>1||oe<0||oe>1||(rt({x:z,y:oe}),pe([]),he(null),D(null))},Qa=()=>{rt(null),pe([]),he(null),D(null)},er=async()=>{if(!I){Ne(!0),l(null);try{be(),B(!1),Y.current=!1,Q(null),ee(!1),Zt(),P.current&&(P.current.onload=null,P.current.onerror=null,P.current.src="",P.current.removeAttribute("src")),K(Date.now()),v(a("cam.notice.previewStop"))}catch(t){l(t instanceof Error?t.message:String(t))}finally{Ne(!1)}}},tr=async()=>{if(!(!m&&!(r!=null&&r.streaming))){kt(!0),l(null);try{const t=await g("/api/debug/camera/capture",{method:"POST"});v(a("cam.notice.captureSaved",{name:t.filename||"capture"}))}catch(t){l(t instanceof Error?t.message:String(t))}finally{kt(!1)}}},ar=async()=>{if(!Ke){St(!0),l(null);try{if(r!=null&&r.recording)await g("/api/debug/camera/record/stop",{method:"POST"}),v(a("cam.notice.recordStop"));else{const t=await g("/api/debug/camera/record/start",{method:"POST"});v(a("cam.notice.recordStart",{name:t.filename||"video.avi"}))}await R()}catch(t){l(t instanceof Error?t.message:String(t))}finally{St(!1)}}},rr=async()=>{l(null);try{const t=N(parseInt(Ye,10)||5,1,60);await g(`/api/debug/camera/fps?fps=${t}`,{method:"POST"});const n=N(parseInt(Ft,10)||8,1,30);await g(`/api/debug/camera/preview-fps?fps=${n}`,{method:"POST"});const[c,h]=Rt.split("x").map(u=>parseInt(u,10));c&&h&&await g(`/api/debug/camera/size?width=${c}&height=${h}`,{method:"POST"}),await g(`/api/debug/camera/sampling?mode=${encodeURIComponent(Bt)}`,{method:"POST"}),v(a("cam.notice.runtimeApplied")),te(!1),await R()}catch(t){l(t instanceof Error?t.message:String(t))}},nr=async()=>{l(null);try{await g("/api/debug/camera/settings",{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify(Ha(d))}),v(a("cam.notice.settingsApplied")),w(!1),await R()}catch(t){l(t instanceof Error?t.message:String(t))}},sr=async()=>{l(null);try{await g("/api/debug/camera/settings",{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify(Ha(d))}),v(a("cam.notice.modeApplied")),w(!1),await R()}catch(t){l(t instanceof Error?t.message:String(t))}},or=(t,n)=>{if(!t)return;const c=(n==null?void 0:n.syncRuntime)??!0;S({exposureSeconds:N(de(t.exposure_us,5e3),ce,le),gain:N(k(t.analogue_gain,1),1,24),digitalGain:N(k(t.digital_gain,1),1,8),autoExposure:!!(t.auto_exposure??!0),contrast:N(k(t.contrast,1),0,2),brightness:N(k(t.brightness,0),-1,1),saturation:N(k(t.saturation,1),0,2),sharpness:N(k(t.sharpness,1),0,2),noiseReduction:N(Math.round(k(t.noise_reduction,0)),0,4),noiseReductionMode:String(t.noise_reduction_mode??"fast"),aeFlickerMode:String(t.ae_flicker_mode??"off"),autoExposureMaxSeconds:N(de(t.auto_exposure_max_us,1e6),.01,1),whiteBalanceMode:String(t.white_balance_mode??"auto"),whiteBalanceGainR:N(k(t.white_balance_gain_r,1),.1,3),whiteBalanceGainB:N(k(t.white_balance_gain_b,1),.1,3),colorMode:String(t.color_mode??"color")}),c&&(Ct(String(Math.round(k(t.fps,8)))),$t(`${Math.round(k(t.width,1280))}x${Math.round(k(t.height,720))}`),Pt(String(t.sampling_mode??"supersample")),te(!1)),Lt(N(Math.round(k(t.rotation,180)),0,270)),qt(!!t.flip_horizontal),Ut(!!t.flip_vertical),w(!1)},ea=async(t,n)=>{l(null);try{await g("/api/debug/camera/mirror",{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({flip_horizontal:t,flip_vertical:n})}),qt(t),Ut(n),v(a("cam.notice.mirrorApplied")),await R()}catch(c){l(c instanceof Error?c.message:String(c))}},ir=async t=>{l(null);try{await g(`/api/debug/camera/rotation/${t}`,{method:"POST"}),Lt(t),v(a("cam.notice.rotationApplied",{value:t})),await R()}catch(n){l(n instanceof Error?n.message:String(n))}},ta=async t=>{l(null);try{await g(`/api/debug/camera/night-mode?enabled=${t?"true":"false"}`,{method:"POST"}),v(a(t?"cam.notice.nightOn":"cam.notice.nightOff")),await R()}catch(n){l(n instanceof Error?n.message:String(n))}},cr=async()=>{if(window.confirm(a("cam.confirm.reset"))){l(null);try{await g("/api/debug/camera/reset",{method:"POST"}),v(a("cam.notice.reset")),await R()}catch(t){l(t instanceof Error?t.message:String(t))}}},lr=async()=>{l(null);try{await g("/api/debug/camera/backup-settings",{method:"POST"}),v(a("cam.notice.backup"))}catch(t){l(t instanceof Error?t.message:String(t))}},dr=async()=>{l(null);try{await g("/api/debug/camera/restore-settings",{method:"POST"}),v(a("cam.notice.restore")),await R()}catch(t){l(t instanceof Error?t.message:String(t))}},Ae=async()=>{L(!0);try{const t=await g("/api/debug/camera/presets",{cache:"no-store"});Xa(t.presets??[])}catch(t){l(t instanceof Error?t.message:String(t))}finally{L(!1)}},ur=async()=>{const t=nt.trim();if(t){L(!0),l(null);try{await g("/api/debug/camera/presets",{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({name:t,description:Vt.trim(),exposure_us:wt(d.exposureSeconds),analogue_gain:d.gain,digital_gain:d.digitalGain,auto_exposure:d.autoExposure,contrast:d.contrast,brightness:d.brightness,saturation:d.saturation,sharpness:d.sharpness,noise_reduction:d.noiseReduction,white_balance_mode:d.whiteBalanceMode,white_balance_gain_r:d.whiteBalanceGainR,white_balance_gain_b:d.whiteBalanceGainB,rotation:zt,flip_horizontal:Ce,flip_vertical:Fe,color_mode:d.colorMode})}),v(a("cam.notice.presetSaved",{name:t})),Wt(""),Xt(""),await Ae()}catch(n){l(n instanceof Error?n.message:String(n))}finally{L(!1)}}},mr=async t=>{L(!0),l(null);try{await g(`/api/debug/camera/presets/${encodeURIComponent(t)}/apply`,{method:"POST"}),v(a("cam.notice.presetApplied",{name:t})),await R(),await Ae()}catch(n){l(n instanceof Error?n.message:String(n))}finally{L(!1)}},xr=async t=>{if(window.confirm(a("cam.confirm.deletePreset",{name:t}))){L(!0),l(null);try{await g(`/api/debug/camera/presets/${encodeURIComponent(t)}`,{method:"DELETE"}),v(a("cam.notice.presetDeleted",{name:t})),await Ae()}catch(n){l(n instanceof Error?n.message:String(n))}finally{L(!1)}}},pt=async()=>{Yt(!0);try{const t=await g("/api/debug/files",{cache:"no-store"});Ja(t.files??[]),$e(1)}catch(t){l(t instanceof Error?t.message:String(t))}finally{Yt(!1)}},ht=()=>{ct(null),ot(null),it(!1)},pr=async t=>{if(Re===t){ht();return}ct(t),ot(null),it(!0),l(null);try{const n=await g(`/api/debug/files/${encodeURIComponent(t)}/info`,{cache:"no-store"});ot(n)}catch(n){l(n instanceof Error?n.message:String(n)),ct(null)}finally{it(!1)}},hr=t=>{const n=(h,u)=>{const x=document.createElement("a");x.href=u,x.download=h,document.body.appendChild(x),x.click(),document.body.removeChild(x)};n(t,`${H("/files")}/${encodeURIComponent(t)}`);const c=t.match(/\.(jpe?g|png|bmp|tiff?|webp|mp4|avi|mov|mkv|wmv|flv|webm|m4v)$/i);if(c){const u=`${t.slice(0,-c[0].length)}.txt`;(async()=>{try{if(!(await fetch(`${H("/files")}/${encodeURIComponent(u)}`)).ok)return;n(u,`${H("/files")}/${encodeURIComponent(u)}`),v(a("cam.notice.downloadWithSidecar",{name:t,sidecar:u}))}catch{v(a("cam.notice.download",{name:t}))}})();return}v(a("cam.notice.download",{name:t}))},fr=async t=>{if(window.confirm(a("cam.confirm.deleteFile",{name:t}))){l(null);try{await g(`/api/debug/files/${encodeURIComponent(t)}`,{method:"DELETE"}),v(a("cam.notice.fileDeleted",{name:t})),(Re===t||(C==null?void 0:C.filename)===t)&&ht(),await pt()}catch(n){l(n instanceof Error?n.message:String(n))}}},gr=()=>{if(!Mt||!P.current||!Be.current)return;const t=P.current;if(!t.naturalWidth||!t.naturalHeight||(ge.current||(ge.current=document.createElement("canvas"),Pe.current=ge.current.getContext("2d",{willReadFrequently:!0})),Me.current||(Me.current=Be.current.getContext("2d")),!Pe.current||!Me.current))return;const c=Math.min(1,320/t.naturalWidth),h=Math.max(1,Math.round(t.naturalWidth*c)),u=Math.max(1,Math.round(t.naturalHeight*c));ge.current.width=h,ge.current.height=u,Pe.current.drawImage(t,0,0,h,u);const x=Pe.current.getImageData(0,0,h,u).data,F=new Array(256).fill(0),y=new Array(256).fill(0),$=new Array(256).fill(0),M=new Array(256).fill(0);let z=0,oe=0,Ta=0;const ie=h*u;for(let O=0;O=250&&(Ta+=1)}const vt=ie?z/ie:0,kr=ie?oe/ie-vt*vt:0;Wa({mean:vt,std:Math.sqrt(Math.max(0,kr)),over:ie?Ta/ie*100:0});const Ue=Be.current,A=Me.current,We=window.devicePixelRatio||1,ve=Math.max(1,Ue.clientWidth||240),ye=Math.max(1,Ue.clientHeight||120);Ue.width=Math.floor(ve*We),Ue.height=Math.floor(ye*We),A.setTransform(We,0,0,We,0,0),A.clearRect(0,0,ve,ye);const Cr=Math.max(1,...Ze?[Math.max(...F),Math.max(...y),Math.max(...$)]:[0],...Qe?[Math.max(...M)]:[0]),Ve=(O,Xe)=>{A.beginPath(),A.strokeStyle=Xe,A.lineWidth=1.2;for(let W=0;W<256;W+=1){const je=W/255*ve,V=ye-O[W]/Cr*ye;W===0?A.moveTo(je,V):A.lineTo(je,V)}A.stroke()};if(Ze&&(Ve(F,"rgba(255,80,80,0.85)"),Ve(y,"rgba(80,255,80,0.85)"),Ve($,"rgba(80,160,255,0.85)")),Qe&&Ve(M,"rgba(255,255,255,0.95)"),Tt){const O=.9803921568627451*ve;A.fillStyle="rgba(255,100,100,0.12)",A.fillRect(O,0,ve-O,ye)}};o.useEffect(()=>{R(),Ae(),pt();const t=window.setInterval(()=>{document.hidden||R()},2e3);return()=>window.clearInterval(t)},[r==null?void 0:r.streaming]),o.useEffect(()=>{!(r!=null&&r.info)||Ee||or(r.info,{syncRuntime:!xe})},[r==null?void 0:r.info,Ee,xe]),o.useEffect(()=>{if(!(r!=null&&r.recording)){q.current&&(window.clearInterval(q.current),q.current=null),Gt(0);return}if(q.current)return;const t=Date.now();return q.current=window.setInterval(()=>{Gt(Math.max(0,Math.floor((Date.now()-t)/1e3)))},1e3),()=>{q.current&&(window.clearInterval(q.current),q.current=null)}},[r==null?void 0:r.recording]),o.useEffect(()=>{Y.current=m,m||(be(),at(!1),ne.current&&xt())},[m]),o.useEffect(()=>()=>{ne.current&&(navigator.sendBeacon(H("/camera/focus/session/stop")),ne.current=!1)},[]),o.useEffect(()=>{if(!_||!m)return;let t=!1;const n=async()=>{var h,u;if(!(dt.current||document.hidden)){dt.current=!0;try{const x=new URLSearchParams;j&&(x.set("target_x",j.x.toFixed(6)),x.set("target_y",j.y.toFixed(6)));const F=x.size>0?`?${x.toString()}`:"",y=await g(`${H("/camera/focus/metrics")}${F}`,{cache:"no-store"});if(t)return;Ot(y),D(null);const $=j?(h=y.selected_star)==null?void 0:h.hfd_px:(u=y.aggregate)==null?void 0:u.median_hfd_px;typeof $=="number"&&Number.isFinite($)&&(pe(M=>{var z;return((z=M.at(-1))==null?void 0:z.frameId)===y.frame_id?M:[...M,{frameId:y.frame_id,hfd:$}].slice(-60)}),he(M=>M==null?$:Math.min(M,$)))}catch(x){t||D(x instanceof Error?x.message:String(x))}finally{dt.current=!1}}};n();const c=window.setInterval(n,850);return()=>{t=!0,window.clearInterval(c)}},[_,m,j==null?void 0:j.x,j==null?void 0:j.y]),o.useEffect(()=>{m&&(U.current=[],se.current=0)},[E,m]),o.useEffect(()=>{if(!m){U.current=[],se.current=0,Se(0);return}if(!ut.current){const u=document.createElement("canvas");u.width=32,u.height=32,ut.current=u}const n=ut.current.getContext("2d",{willReadFrequently:!0});if(!n)return;let c=0;const h=()=>{const u=P.current;if(u!=null&&u.complete&&u.naturalWidth>0)try{n.drawImage(u,0,0,32,32);const x=n.getImageData(0,0,32,32).data;let F=2166136261;for(let y=0;ywindow.cancelAnimationFrame(c)},[m]),o.useEffect(()=>{if(!m){Se(0);return}const t=window.setInterval(()=>{const c=performance.now()-1e3,h=U.current;for(;h.length&&h[0]window.clearInterval(t)},[m]),o.useEffect(()=>{if(!m){tt(null);return}let t=!1;const n=async()=>{try{const h=await fetch(`${H("/camera/stream/status")}`,{cache:"no-store",credentials:"same-origin"});if(!h.ok||t)return;const u=await h.json();if(!t){tt(u);const x=Number(u.preview_target_fps??u.target_preview_fps??0);Number.isFinite(x)&&x>0&&!xe&&Et(String(Math.round(x)))}}catch{t||tt(null)}};n();const c=window.setInterval(n,2e3);return()=>{t=!0,window.clearInterval(c)}},[m,xe]),o.useEffect(()=>{if(!me)return;const t=window.setTimeout(()=>v(null),3200);return()=>window.clearTimeout(t)},[me]),o.useEffect(()=>()=>be(),[]),o.useEffect(()=>{const t=Math.max(1,Math.ceil(fe.length/Je));lt>t&&$e(t)},[fe.length,lt]);const br=m?`${H("/camera/stream")}?t=${E}`:"",Oe=d.autoExposure,ft=d.whiteBalanceMode==="manual",vr=N(parseInt(Ye,10)||Math.round(k((ua=r==null?void 0:r.info)==null?void 0:ua.fps,8)),1,60),aa=!d.autoExposure&&1/d.exposureSeconds0?Z.awb_modes:["auto","daylight","cloudy","tungsten","fluorescent","indoor","manual","night"],jr=Array.isArray(Z.noise_reduction_modes)&&Z.noise_reduction_modes.length>0?Z.noise_reduction_modes:["off","fast","high_quality"],ra=Z.manual_digital_gain!==!1,na=!!((xa=r==null?void 0:r.info)!=null&&xa.night_mode),sa=m,He=!I&&!m&&!(r!=null&&r.recording),oa=!I&&m,wr=!I&&!_t&&sa,Nr=!I&&!Ke&&sa,G=j?s==null?void 0:s.selected_star:null,De=j?(G==null?void 0:G.hfd_px)??null:((pa=s==null?void 0:s.aggregate)==null?void 0:pa.median_hfd_px)??null,ia=j?(G==null?void 0:G.fwhm_px)??null:((ha=s==null?void 0:s.aggregate)==null?void 0:ha.median_fwhm_px)??null,ca=j?(G==null?void 0:G.concentration)??null:((fa=s==null?void 0:s.aggregate)==null?void 0:fa.median_concentration)??null,Ge=De!=null&&re!=null&&re>0?(De-re)/re*100:null,la=ke.slice(-3).map(t=>t.hfd),gt=ke.slice(-6,-3).map(t=>t.hfd),bt=t=>t.reduce((n,c)=>n+c,0)/Math.max(1,t.length);let ze="cam.focus.guidance.collecting";if((s==null?void 0:s.state)==="no_stars")ze="cam.focus.guidance.noStars";else if(ke.length>=5&&Ge!=null&&Ge<=3)ze="cam.focus.guidance.best";else if(la.length===3&>.length===3){const t=bt(la)-bt(gt),n=Math.max(.04,bt(gt)*.02);ze=t<-n?"cam.focus.guidance.improving":t>n?"cam.focus.guidance.worsening":"cam.focus.guidance.steady"}const Sr=((s==null?void 0:s.stars_used)??0)>=5?"cam.focus.confidence.high":((s==null?void 0:s.stars_used)??0)>=3?"cam.focus.confidence.medium":"cam.focus.confidence.low",Le=Math.max(1,Math.ceil(fe.length/Je)),qe=Math.min(lt,Le),da=(qe-1)*Je,_r=fe.slice(da,da+Je);return e.jsxs("div",{className:"min-h-screen bg-background text-on-surface",children:[e.jsx("header",{className:"sticky top-0 z-30 border-b border-outline-variant/20 bg-surface-container-low/90 px-4 py-3 backdrop-blur",children:e.jsxs("div",{className:"flex w-full flex-wrap items-center justify-between gap-3",children:[e.jsx("div",{className:"min-w-0",children:e.jsx("h1",{className:"break-words font-headline text-lg font-bold text-primary sm:text-xl",children:`OGScope ${a("cam.title")}`})}),e.jsxs("div",{className:"flex items-center gap-2 text-xs",children:[e.jsx("button",{type:"button",className:`inline-flex h-7 items-center rounded px-2 py-1 ${b==="zh"?"bg-primary-container text-on-primary-container":"text-on-surface-variant"}`,onClick:()=>f("zh"),children:a("lang.zh")}),e.jsx("button",{type:"button",className:`inline-flex h-7 items-center rounded px-2 py-1 ${b==="en"?"bg-primary-container text-on-primary-container":"text-on-surface-variant"}`,onClick:()=>f("en"),children:a("lang.en")}),e.jsxs("a",{href:"/debug",className:"inline-flex h-7 items-center gap-1 rounded border border-outline-variant/30 px-2 py-1 hover:bg-surface-container",children:[e.jsx(qr,{className:"h-3.5 w-3.5"})," ",a("cam.btn.system")]})]})]})}),e.jsxs("main",{className:"mx-auto grid max-w-[1880px] grid-cols-12 gap-4 p-4",children:[e.jsxs("aside",{className:"order-3 col-span-12 space-y-4 2xl:order-1 2xl:col-span-2",children:[e.jsxs("section",{className:"rounded-xl border border-outline-variant/20 bg-surface-container p-4",children:[e.jsx("div",{className:"mb-2 text-sm font-semibold uppercase tracking-wider",children:a("cam.controls.tools")}),e.jsx("div",{className:"mb-2 flex flex-wrap gap-1 text-xs",children:Kr.map(t=>e.jsxs("button",{type:"button",onClick:()=>void ir(t),className:`rounded border px-2 py-1 ${zt===t?"border-primary text-primary":"border-outline-variant/30"}`,children:[t,"°"]},t))}),e.jsx("div",{className:"mb-2 text-[11px] text-on-surface-variant",children:a("cam.mirror.hint")}),e.jsxs("div",{className:"mb-2 flex flex-wrap gap-1 text-xs",children:[e.jsx("button",{type:"button",onClick:()=>void ea(!Ce,Fe),className:`rounded border px-2 py-1 ${Ce?"border-primary text-primary":"border-outline-variant/30"}`,children:a("cam.mirror.horizontal")}),e.jsx("button",{type:"button",onClick:()=>void ea(Ce,!Fe),className:`rounded border px-2 py-1 ${Fe?"border-primary text-primary":"border-outline-variant/30"}`,children:a("cam.mirror.vertical")})]}),e.jsxs("div",{className:"flex flex-wrap gap-2 text-xs",children:[e.jsxs("button",{type:"button",onClick:()=>void lr(),className:"rounded border border-outline-variant/40 px-2 py-1",children:[e.jsx(Mr,{className:"mr-1 inline h-3.5 w-3.5"}),a("cam.controls.backup")]}),e.jsxs("button",{type:"button",onClick:()=>void dr(),className:"rounded border border-outline-variant/40 px-2 py-1",children:[e.jsx(Gr,{className:"mr-1 inline h-3.5 w-3.5"}),a("cam.controls.restore")]}),e.jsx("button",{type:"button",onClick:()=>void cr(),className:"rounded border border-outline-variant/40 px-2 py-1",children:a("cam.controls.reset")})]})]}),e.jsxs("section",{className:"rounded-xl border border-outline-variant/20 bg-surface-container p-4 text-xs",children:[e.jsx("h2",{className:"mb-3 text-sm font-semibold uppercase tracking-wider",children:a("cam.quick.title")}),e.jsxs("div",{className:"flex flex-wrap items-center gap-2",children:[e.jsx("span",{className:"text-[11px] text-on-surface-variant",children:a("cam.quick.nightHint")}),e.jsxs("button",{type:"button",onClick:()=>void ta(!0),className:`rounded border px-2 py-1 ${na?"border-primary/70 text-primary":"border-outline-variant/40"}`,children:[e.jsx(Lr,{className:"mr-1 inline h-3.5 w-3.5"}),a("cam.controls.nightOn")]}),e.jsxs("button",{type:"button",onClick:()=>void ta(!1),className:`rounded border px-2 py-1 ${na?"border-error/60 text-error":"border-outline-variant/40 text-on-surface-variant"}`,children:[e.jsx(Vr,{className:"mr-1 inline h-3.5 w-3.5"}),a("cam.controls.nightOff")]})]})]}),e.jsxs("section",{className:"rounded-xl border border-outline-variant/20 bg-surface-container p-4 text-xs",children:[e.jsx("h2",{className:"mb-3 text-sm font-semibold uppercase tracking-wider",children:a("cam.presets.title")}),e.jsxs("div",{className:"grid grid-cols-1 gap-2",children:[e.jsx("input",{value:nt,onChange:t=>Wt(t.target.value),placeholder:a("cam.presets.name"),className:"rounded border border-outline-variant/30 bg-surface-container-low px-2 py-1.5"}),e.jsx("input",{value:Vt,onChange:t=>Xt(t.target.value),placeholder:a("cam.presets.desc"),className:"rounded border border-outline-variant/30 bg-surface-container-low px-2 py-1.5"})]}),e.jsx("div",{className:"mt-2",children:e.jsx("button",{type:"button",disabled:st||!nt.trim(),onClick:()=>void ur(),className:"rounded border border-outline-variant/40 px-2 py-1 disabled:opacity-50",children:a("cam.presets.save")})}),e.jsxs("div",{className:"mt-3 max-h-48 space-y-2 overflow-auto",children:[Jt.length===0&&e.jsx("div",{className:"text-on-surface-variant",children:a("cam.presets.empty")}),Jt.map(t=>e.jsxs("div",{className:"rounded border border-outline-variant/20 p-2",children:[e.jsx("div",{className:"font-semibold",children:t.name}),e.jsx("div",{className:"text-on-surface-variant",children:t.description||a("cam.presets.noDesc")}),e.jsxs("div",{className:"mt-1 text-on-surface-variant",children:[a("cam.controls.exposure"),": ",jt(t.exposure_us)," | ",a("cam.controls.gain"),": ",t.analogue_gain]}),e.jsxs("div",{className:"mt-2 flex gap-2",children:[e.jsx("button",{type:"button",disabled:st,onClick:()=>void mr(t.name),className:"rounded border border-outline-variant/40 px-2 py-1 disabled:opacity-50",children:a("cam.presets.apply")}),e.jsx("button",{type:"button",disabled:st,onClick:()=>void xr(t.name),className:"rounded border border-outline-variant/40 px-2 py-1 disabled:opacity-50",children:a("cam.presets.delete")})]})]},t.name))]})]})]}),e.jsxs("section",{className:"order-1 col-span-12 grid grid-cols-12 items-start gap-4 2xl:order-2 2xl:col-span-10",children:[e.jsxs("div",{className:"col-span-12 space-y-4 2xl:col-span-9",children:[e.jsxs("section",{className:"self-start rounded-xl border border-outline-variant/20 bg-surface-container p-4",children:[e.jsxs("div",{className:"mb-2 grid grid-cols-1 gap-2 text-xs sm:grid-cols-3",children:[e.jsxs("div",{className:"rounded border border-outline-variant/20 bg-surface-container-low px-2 py-1",children:["CPU: ",e.jsx("span",{className:"font-mono",children:(p==null?void 0:p.cpu_usage)==null?"—":`${Number(p.cpu_usage).toFixed(1)}%`})]}),e.jsxs("div",{className:"rounded border border-outline-variant/20 bg-surface-container-low px-2 py-1",children:["MEM: ",e.jsx("span",{className:"font-mono",children:(p==null?void 0:p.memory_usage)==null?"—":`${Number(p.memory_usage).toFixed(1)}%`})]}),e.jsxs("div",{className:"rounded border border-outline-variant/20 bg-surface-container-low px-2 py-1",children:["TEMP: ",e.jsx("span",{className:"font-mono",children:(p==null?void 0:p.temperature)==null?"—":`${Number(p.temperature).toFixed(1)}°C`})]})]}),e.jsxs("div",{className:"mb-2 flex flex-wrap items-start justify-between gap-2",children:[e.jsxs("div",{children:[e.jsx("h2",{className:"text-sm font-semibold uppercase tracking-wider",children:a("cam.preview.title")}),e.jsx("p",{className:"mt-1 max-w-2xl text-[11px] leading-snug text-on-surface-variant",children:a("cam.hint.mjpegSingleStream")})]}),e.jsxs("div",{className:"flex shrink-0 flex-wrap items-center justify-end gap-2",children:[e.jsxs("button",{type:"button",disabled:ae||I||!!(r!=null&&r.recording),onClick:()=>_?void xt():void Qt(),className:`rounded-lg px-2.5 py-1.5 text-xs font-medium disabled:opacity-50 ${_?"border border-primary/50 text-primary":"bg-primary-container text-on-primary-container"}`,children:[e.jsx(yt,{className:"mr-1 inline h-3.5 w-3.5"}),a(ae?"cam.focus.starting":_?"cam.focus.finish":"cam.focus.start")]}),e.jsxs("span",{className:"font-mono text-xs text-on-surface-variant",children:[a("cam.preview.state"),": ",a(m?"cam.state.streaming":"cam.state.idle")]})]})]}),we&&!m&&e.jsxs("div",{className:"mb-2 rounded-lg border border-error/35 bg-error-container/15 px-3 py-2 text-left",role:"alert",children:[e.jsx("p",{className:"text-sm font-medium text-error",children:we}),Nt?e.jsx("p",{className:"mt-1 max-w-2xl text-[11px] leading-snug text-on-surface-variant",children:a("cam.err.streamBusyHint")}):e.jsx("p",{className:"mt-1 max-w-2xl text-[11px] leading-snug text-on-surface-variant",children:a("cam.err.streamProbeDetail")})]}),e.jsxs("div",{className:`relative aspect-video overflow-hidden rounded border border-outline-variant/20 bg-black ${_?"cursor-crosshair":""}`,onClick:Za,children:[m&&we&&e.jsxs("div",{className:"pointer-events-none absolute inset-0 z-20 flex flex-col items-center justify-center gap-2 bg-black/80 px-4 text-center",role:"alert",children:[e.jsx("p",{className:"text-sm font-medium text-error",children:we}),Nt?e.jsx("p",{className:"max-w-md text-[11px] leading-snug text-on-surface-variant",children:a("cam.err.streamBusyHint")}):e.jsx("p",{className:"max-w-md text-[11px] leading-snug text-on-surface-variant",children:a("cam.err.streamProbeDetail")})]}),m?e.jsx("img",{ref:P,alt:"camera-preview",className:"h-full w-full object-contain",src:br,onLoad:()=>{Q(null),ee(!1),gr()},onError:()=>{var n;if(be(),!Y.current)return;const t=(n=P.current)==null?void 0:n.src;(async()=>{if(!Y.current||!t)return;if(await Ga()==="busy"){Q(a("cam.err.streamBusy")),ee(!0);return}Te.current=window.setTimeout(()=>{Y.current&&K(Date.now())},400)})()}}):e.jsxs("div",{className:"flex h-full w-full flex-col items-center justify-center gap-3 text-center text-on-surface-variant",children:[e.jsx(Ia,{className:"h-10 w-10 text-primary/80"}),e.jsx("div",{className:"text-sm",children:a("cam.preview.emptyTitle")}),e.jsx("div",{className:"text-xs",children:a("cam.preview.emptyDesc")}),e.jsxs("button",{type:"button",disabled:!He,onClick:()=>void mt(),className:`rounded px-3 py-1.5 text-xs disabled:opacity-50 ${He?"bg-primary-container text-on-primary-container":"border border-outline-variant/40 text-on-surface-variant"}`,children:[e.jsx(Oa,{className:"mr-1 inline h-3.5 w-3.5"}),a(I?"cam.btn.starting":"cam.btn.start")]})]}),m&&_&&(s!=null&&s.frame)?e.jsxs("svg",{className:"pointer-events-none absolute inset-0 z-10 h-full w-full",viewBox:`0 0 ${s.frame.width} ${s.frame.height}`,preserveAspectRatio:"xMidYMid meet","aria-hidden":"true",children:[s.stars.map((t,n)=>{const c=s.selected_star&&Math.abs(s.selected_star.x-t.x)<1&&Math.abs(s.selected_star.y-t.y)<1;return e.jsx("circle",{cx:t.x,cy:t.y,r:c?16:10,fill:"none",stroke:c?"#facc15":"#38bdf8",strokeWidth:c?3:2,vectorEffect:"non-scaling-stroke"},`${s.frame_id}-${n}`)}),j?e.jsxs("g",{transform:`translate(${j.x*s.frame.width} ${j.y*s.frame.height})`,stroke:"#facc15",strokeWidth:"2",vectorEffect:"non-scaling-stroke",children:[e.jsx("line",{x1:"-12",y1:"0",x2:"12",y2:"0"}),e.jsx("line",{x1:"0",y1:"-12",x2:"0",y2:"12"})]}):null]}):null,(r==null?void 0:r.recording)&&e.jsxs("div",{className:"absolute right-3 top-3 flex items-center gap-2 rounded bg-black/60 px-2 py-1 text-xs text-error",children:[e.jsx(Aa,{className:"h-3.5 w-3.5 fill-current"})," ",a("cam.state.rec"),e.jsx("span",{className:"font-mono",children:`${Math.floor(Dt/60).toString().padStart(2,"0")}:${(Dt%60).toString().padStart(2,"0")}`})]}),e.jsx("div",{className:"absolute left-3 top-3",children:e.jsx("button",{type:"button",onClick:()=>At(t=>!t),className:"rounded border border-outline-variant/40 bg-black/70 px-2 py-1 text-xs text-white",children:a(It?"cam.hist.expand":"cam.hist.collapse")})}),!It&&e.jsxs("div",{className:"absolute left-3 top-12 w-[360px] max-w-[calc(100%-1.5rem)] rounded border border-outline-variant/30 bg-black/70 p-2 text-white",children:[e.jsxs("div",{className:"mb-2 flex flex-wrap items-center gap-3 text-[11px]",children:[e.jsxs("label",{className:"inline-flex items-center gap-1",children:[e.jsx("input",{type:"checkbox",checked:Mt,onChange:t=>za(t.target.checked)})," ",a("cam.hist.enabled")]}),e.jsxs("label",{className:"inline-flex items-center gap-1",children:[e.jsx("input",{type:"checkbox",checked:Ze,onChange:t=>La(t.target.checked)})," RGB"]}),e.jsxs("label",{className:"inline-flex items-center gap-1",children:[e.jsx("input",{type:"checkbox",checked:Qe,onChange:t=>qa(t.target.checked)})," ",a("cam.hist.luminance")]}),e.jsxs("label",{className:"inline-flex items-center gap-1",children:[e.jsx("input",{type:"checkbox",checked:Tt,onChange:t=>Ua(t.target.checked)})," ",a("cam.hist.over")]})]}),e.jsx("canvas",{ref:Be,className:"h-24 w-full rounded border border-white/20 bg-black/60"}),e.jsxs("div",{className:"mt-2 grid grid-cols-3 gap-2 text-[11px]",children:[e.jsxs("div",{children:["mean: ",e.jsx("span",{className:"font-mono",children:et.mean.toFixed(1)})]}),e.jsxs("div",{children:["std: ",e.jsx("span",{className:"font-mono",children:et.std.toFixed(1)})]}),e.jsxs("div",{children:["over: ",e.jsxs("span",{className:"font-mono",children:[et.over.toFixed(2),"%"]})]})]})]})]}),e.jsxs("section",{id:"focus-calibration-panel",className:`mt-3 scroll-mt-20 rounded-xl border p-3 ${_?"border-primary/45 bg-primary-container/10":"border-outline-variant/20 bg-surface-container-low"}`,children:[e.jsxs("div",{className:"flex flex-wrap items-start justify-between gap-3",children:[e.jsxs("div",{className:"min-w-0",children:[e.jsxs("div",{className:"flex items-center gap-2",children:[e.jsx(yt,{className:"h-5 w-5 text-primary"}),e.jsx("h3",{className:"text-sm font-semibold",children:a("cam.focus.title")}),e.jsx("span",{className:`rounded-full px-2 py-0.5 text-[10px] font-medium ${_?"bg-primary-container text-on-primary-container":"bg-surface-container-high text-on-surface-variant"}`,children:a(_?"cam.focus.active":"cam.focus.idle")})]}),e.jsx("p",{className:"mt-1 max-w-3xl text-[11px] leading-relaxed text-on-surface-variant",children:a("cam.focus.intro")}),_?e.jsx("p",{className:"mt-1 text-[10px] text-primary",children:a("cam.focus.sessionLocked")}):null]}),e.jsxs("div",{className:"flex flex-wrap gap-2",children:[_?e.jsx("button",{type:"button",onClick:()=>void xt(),className:"rounded-lg border border-error/50 px-3 py-2 text-xs text-error",children:a("cam.focus.finish")}):e.jsxs("button",{type:"button",disabled:ae||I||!!(r!=null&&r.recording),onClick:()=>void Qt(),className:"rounded-lg bg-primary-container px-3 py-2 text-xs font-medium text-on-primary-container disabled:opacity-50",children:[e.jsx(yt,{className:"mr-1 inline h-4 w-4"}),a(ae?"cam.focus.starting":"cam.focus.start")]}),e.jsxs("button",{type:"button",disabled:!_,onClick:Ya,className:"rounded-lg border border-outline-variant/40 px-3 py-2 text-xs disabled:opacity-40",children:[e.jsx(Or,{className:"mr-1 inline h-3.5 w-3.5"}),a("cam.focus.resetBest")]}),e.jsx("button",{type:"button",disabled:!_||j==null,onClick:Qa,className:"rounded-lg border border-outline-variant/40 px-3 py-2 text-xs disabled:opacity-40",children:a("cam.focus.autoSelect")})]})]}),_?e.jsxs("div",{className:"mt-3 grid gap-3 xl:grid-cols-[minmax(0,1.25fr)_minmax(280px,0.75fr)]",children:[e.jsxs("div",{className:"grid gap-3 sm:grid-cols-2 lg:grid-cols-4",children:[e.jsxs("div",{className:"rounded-lg border border-primary/30 bg-black/20 p-3 sm:col-span-2",children:[e.jsx("div",{className:"text-[10px] uppercase tracking-wider text-on-surface-variant",children:a(j?"cam.focus.currentHfd.single":"cam.focus.currentHfd.multi")}),e.jsxs("div",{className:"mt-1 flex items-end gap-2",children:[e.jsx("span",{className:"font-mono text-3xl font-semibold text-primary",children:De==null?"—":De.toFixed(2)}),e.jsxs("span",{className:"pb-1 text-xs text-on-surface-variant",children:["px · ",a("cam.focus.lowerBetter")]})]}),e.jsx("p",{className:"mt-2 rounded-md border border-outline-variant/20 bg-surface-container-low/70 px-2 py-1.5 text-xs font-medium",children:a(ze)})]}),e.jsxs("div",{className:"rounded-lg border border-outline-variant/20 p-3",children:[e.jsx("div",{className:"text-[10px] uppercase tracking-wider text-on-surface-variant",children:a("cam.focus.bestHfd")}),e.jsx("div",{className:"mt-1 font-mono text-xl",children:re==null?"—":`${re.toFixed(2)} px`}),e.jsx("div",{className:"mt-1 text-[11px] text-on-surface-variant",children:Ge==null?"—":`+${Math.max(0,Ge).toFixed(1)}%`})]}),e.jsxs("div",{className:"rounded-lg border border-outline-variant/20 p-3",children:[e.jsx("div",{className:"text-[10px] uppercase tracking-wider text-on-surface-variant",children:a("cam.focus.stars")}),e.jsxs("div",{className:"mt-1 font-mono text-xl",children:[(s==null?void 0:s.stars_used)??0," / ",(s==null?void 0:s.stars_measured)??0," / ",(s==null?void 0:s.stars_detected)??0]}),e.jsx("div",{className:"mt-1 text-[11px] text-on-surface-variant",children:a(Sr)})]}),e.jsxs("div",{className:"rounded-lg border border-outline-variant/20 p-3",children:[e.jsx("div",{className:"text-[10px] uppercase tracking-wider text-on-surface-variant",children:a("cam.focus.fwhm")}),e.jsx("div",{className:"mt-1 font-mono text-lg",children:ia==null?"—":`${ia.toFixed(2)} px`})]}),e.jsxs("div",{className:"rounded-lg border border-outline-variant/20 p-3",children:[e.jsx("div",{className:"text-[10px] uppercase tracking-wider text-on-surface-variant",children:a("cam.focus.concentration")}),e.jsx("div",{className:"mt-1 font-mono text-lg",children:ca==null?"—":`${(ca*100).toFixed(0)}%`})]}),e.jsxs("div",{className:"rounded-lg border border-outline-variant/20 p-3",children:[e.jsx("div",{className:"text-[10px] uppercase tracking-wider text-on-surface-variant",children:a("cam.focus.selectedStar")}),e.jsx("div",{className:"mt-1 font-mono text-lg",children:s!=null&&s.selected_star?`HFD ${s.selected_star.hfd_px.toFixed(2)}`:"—"}),e.jsx("div",{className:"mt-1 text-[11px] text-on-surface-variant",children:a(j?"cam.focus.manualTarget":"cam.focus.autoTarget")})]}),e.jsxs("div",{className:"rounded-lg border border-outline-variant/20 p-3",children:[e.jsx("div",{className:"text-[10px] uppercase tracking-wider text-on-surface-variant",children:a("cam.focus.stability")}),e.jsx("div",{className:"mt-1 font-mono text-lg",children:s!=null&&s.aggregate?`±${s.aggregate.hfd_mad_px.toFixed(2)} px`:"—"})]})]}),e.jsxs("div",{className:"space-y-2",children:[e.jsxs("div",{className:"flex items-center justify-between text-[11px] text-on-surface-variant",children:[e.jsx("span",{children:a("cam.focus.history")}),e.jsx("span",{children:a("cam.focus.clickHint")})]}),e.jsx(Zr,{samples:ke,emptyLabel:a("cam.focus.historyEmpty")}),Ht?e.jsx("p",{className:"rounded-lg border border-error/35 bg-error-container/15 px-3 py-2 text-xs text-error",role:"alert",children:Ht}):null,s!=null&&s.warnings.includes("saturated_stars_rejected")?e.jsx("p",{className:"rounded-lg border border-tertiary/35 bg-tertiary-container/15 px-3 py-2 text-[11px] text-tertiary",children:a("cam.focus.warning.saturated")}):null,s!=null&&s.warnings.includes("target_star_not_found")?e.jsx("p",{className:"rounded-lg border border-tertiary/35 bg-tertiary-container/15 px-3 py-2 text-[11px] text-tertiary",children:a("cam.focus.warning.targetNotFound")}):null,s!=null&&s.warnings.includes("undersampled_stars")?e.jsx("p",{className:"rounded-lg border border-primary/30 bg-primary-container/10 px-3 py-2 text-[11px] text-on-surface",children:a("cam.focus.warning.undersampled")}):null,s!=null&&s.warnings.includes("target_may_be_hot_pixel")?e.jsx("p",{className:"rounded-lg border border-tertiary/35 bg-tertiary-container/15 px-3 py-2 text-[11px] text-tertiary",children:a("cam.focus.warning.hotPixel")}):null,s!=null&&s.detection?e.jsx("p",{className:"text-[10px] leading-relaxed text-on-surface-variant",children:a("cam.focus.detectionSummary",{measured:s.stars_measured,detected:s.stars_detected,rejected:Object.values(s.detection.quality_rejections).reduce((t,n)=>t+n,0)})}):null]})]}):e.jsxs("div",{className:"mt-3 grid gap-2 text-[11px] text-on-surface-variant sm:grid-cols-3",children:[e.jsxs("div",{className:"rounded-lg border border-outline-variant/20 px-3 py-2",children:["1. ",a("cam.focus.step.start")]}),e.jsxs("div",{className:"rounded-lg border border-outline-variant/20 px-3 py-2",children:["2. ",a("cam.focus.step.adjust")]}),e.jsxs("div",{className:"rounded-lg border border-outline-variant/20 px-3 py-2",children:["3. ",a("cam.focus.step.lock")]})]})]}),e.jsxs("div",{className:"mt-3 grid grid-cols-1 gap-2 text-xs md:grid-cols-4",children:[e.jsxs("div",{className:"rounded border border-outline-variant/20 bg-surface-container-low px-2 py-1.5 text-left",children:[e.jsxs("span",{className:"text-on-surface-variant",children:[a("cam.stats.frameFps"),": "]}),e.jsx("span",{className:"font-mono text-on-surface",children:Number((i==null?void 0:i.actual_preview_fps)??Va).toFixed(2)}),e.jsx("p",{className:"mt-0.5 text-[10px] leading-tight text-on-surface-variant/90",children:a("cam.stats.fpsMeasureNote")})]}),e.jsxs("div",{className:"rounded border border-outline-variant/20 bg-surface-container-low px-2 py-1.5 text-left",children:[e.jsxs("span",{className:"text-on-surface-variant",children:[a("cam.stats.targetFps"),": "]}),e.jsx("span",{className:"font-mono text-on-surface",children:Number(((ga=r==null?void 0:r.info)==null?void 0:ga.fps)??0).toFixed(2)})]}),e.jsxs("div",{className:"rounded border border-outline-variant/20 bg-surface-container-low px-2 py-1.5 text-left",children:[e.jsxs("span",{className:"text-on-surface-variant",children:[a("cam.stats.streamPacingFps"),": "]}),e.jsx("span",{className:"font-mono text-on-surface",children:(i==null?void 0:i.preview_target_fps)??(i==null?void 0:i.target_preview_fps)??"—"}),e.jsx("p",{className:"mt-0.5 text-[10px] leading-tight text-on-surface-variant/90",children:a("cam.stats.streamPacingHint")})]}),e.jsxs("div",{className:"rounded border border-outline-variant/20 bg-surface-container-low px-2 py-1.5 text-left",children:[e.jsxs("span",{className:"text-on-surface-variant",children:[a("cam.stats.captureFps"),": "]}),e.jsx("span",{className:"font-mono text-on-surface",children:Number((i==null?void 0:i.actual_capture_fps)??0).toFixed(2)})]}),e.jsxs("div",{className:"rounded border border-outline-variant/20 bg-surface-container-low px-2 py-1.5 text-left",children:[e.jsxs("span",{className:"text-on-surface-variant",children:[a("cam.controls.exposure"),": "]}),e.jsx("span",{className:"font-mono text-on-surface",children:(i==null?void 0:i.actual_exposure_us)!=null||((ba=r==null?void 0:r.info)==null?void 0:ba.actual_exposure_us)!=null?jt((i==null?void 0:i.actual_exposure_us)??((va=r==null?void 0:r.info)==null?void 0:va.actual_exposure_us)??0):"—"}),e.jsx("p",{className:"mt-0.5 text-[10px] leading-tight text-on-surface-variant/90",children:(ya=r==null?void 0:r.info)!=null&&ya.auto_exposure?a("cam.controls.auto"):a("cam.controls.manual")})]}),e.jsxs("div",{className:"rounded border border-outline-variant/20 bg-surface-container-low px-2 py-1.5 text-left",children:[e.jsxs("span",{className:"text-on-surface-variant",children:[a("cam.stats.encodeMs"),": "]}),e.jsxs("span",{className:"font-mono text-on-surface",children:[Number((i==null?void 0:i.jpeg_average_encode_ms)??0).toFixed(1)," ms"]})]}),e.jsxs("div",{className:"rounded border border-outline-variant/20 bg-surface-container-low px-2 py-1.5 text-left",children:[e.jsxs("span",{className:"text-on-surface-variant",children:[a("cam.stats.encoder"),": "]}),e.jsx("span",{className:"font-mono text-on-surface",children:String((i==null?void 0:i.preview_encoder)??"—")}),e.jsx("p",{className:"mt-0.5 text-[10px] leading-tight text-on-surface-variant/90",children:`${(i==null?void 0:i.jpeg_source_format)??"RGB888"} / fail ${(i==null?void 0:i.jpeg_encode_failures)??0}`})]}),e.jsxs("div",{className:"rounded border border-outline-variant/20 bg-surface-container-low px-2 py-1.5 text-left",children:[e.jsxs("span",{className:"text-on-surface-variant",children:[a("cam.stats.consumers"),": "]}),e.jsx("span",{className:"font-mono text-on-surface",children:`${(i==null?void 0:i.preview_consumers)??0}/${(i==null?void 0:i.analysis_consumers)??0}/${(i==null?void 0:i.recording_consumers)??0}`})]}),e.jsxs("div",{className:"rounded border border-outline-variant/20 bg-surface-container-low px-2 py-1.5 text-left",children:[e.jsxs("span",{className:"text-on-surface-variant",children:[a("cam.stats.cameraMemory"),": "]}),e.jsx("span",{className:"font-mono text-on-surface",children:`${Math.round(Number((i==null?void 0:i.process_rss_kb)??0)/1024)} / ${Math.round(Number((i==null?void 0:i.process_swap_kb)??0)/1024)} MB`})]}),(i==null?void 0:i.throttle_reason)==="auto_exposure_long"&&e.jsx("div",{className:"rounded border border-tertiary/40 bg-tertiary-container/20 px-2 py-1.5 text-left text-tertiary",children:a("cam.stats.longExposureThrottle",{exposure:de(i.actual_exposure_us,0).toFixed(4)})}),e.jsxs("div",{className:"rounded border border-outline-variant/20 bg-surface-container-low px-2 py-1.5 text-left",children:[e.jsxs("span",{className:"text-on-surface-variant",children:[a("cam.stats.uptime"),": "]}),e.jsx("span",{className:"font-mono text-on-surface",children:Ie.current!=null?`${Math.max(0,Math.round((performance.now()-Ie.current)/1e3))}s`:"0s"})]}),e.jsxs("div",{className:"rounded border border-outline-variant/20 bg-surface-container-low px-2 py-1.5 text-left",children:[e.jsxs("span",{className:"text-on-surface-variant",children:[a("cam.system.sensor"),": "]}),e.jsx("span",{className:"font-mono",children:String(((ja=r==null?void 0:r.info)==null?void 0:ja.sensor)??"—")})]}),e.jsxs("div",{className:"rounded border border-outline-variant/20 bg-surface-container-low px-2 py-1.5 text-left",children:[e.jsxs("span",{className:"text-on-surface-variant",children:[a("cam.stats.driver"),": "]}),e.jsx("span",{className:"font-mono",children:String(((wa=r==null?void 0:r.info)==null?void 0:wa.driver)??(i==null?void 0:i.camera_driver)??"—")}),e.jsx("p",{className:"mt-0.5 text-[10px] leading-tight text-on-surface-variant/90",children:`${((Na=r==null?void 0:r.info)==null?void 0:Na.backend)??(i==null?void 0:i.camera_backend)??"—"} · lores ${(Sa=r==null?void 0:r.info)!=null&&Sa.lores_available||i!=null&&i.lores_available?"on":"off"}`}),e.jsxs("p",{className:"mt-0.5 break-all text-[10px] leading-tight text-on-surface-variant/90",children:[`tuning ${((_a=r==null?void 0:r.info)==null?void 0:_a.tuning_source)??"—"} · frame ${((ka=r==null?void 0:r.info)==null?void 0:ka.frame_duration_control)??"—"}`,(Ca=r==null?void 0:r.info)!=null&&Ca.tuning_error||(Fa=r==null?void 0:r.info)!=null&&Fa.frame_duration_control_error?` · fallback ${r.info.tuning_error??r.info.frame_duration_control_error}`:""]})]}),e.jsxs("div",{className:"rounded border border-outline-variant/20 bg-surface-container-low px-2 py-1.5 text-left",children:[e.jsxs("span",{className:"text-on-surface-variant",children:[a("cam.stats.metadata"),": "]}),e.jsx("span",{className:"font-mono",children:((Ea=r==null?void 0:r.info)==null?void 0:Ea.lux)!=null?`${Number(r.info.lux).toFixed(1)} lux`:"—"}),e.jsx("p",{className:"mt-0.5 text-[10px] leading-tight text-on-surface-variant/90",children:((Ra=r==null?void 0:r.info)==null?void 0:Ra.colour_temperature)!=null?`${Math.round(Number(r.info.colour_temperature))}K`:"—"})]}),e.jsxs("div",{className:"rounded border border-outline-variant/20 bg-surface-container-low px-2 py-1.5 text-left",children:[e.jsxs("span",{className:"text-on-surface-variant",children:[a("cam.controls.resolution"),": "]}),e.jsx("span",{className:"font-mono",children:`${(($a=r==null?void 0:r.info)==null?void 0:$a.width)??"—"}x${((Ba=r==null?void 0:r.info)==null?void 0:Ba.height)??"—"}`})]}),e.jsxs("div",{className:"rounded border border-outline-variant/20 bg-surface-container-low px-2 py-1.5 text-left",children:[e.jsxs("span",{className:"text-on-surface-variant",children:[a("cam.preview.mode"),": "]}),e.jsx("span",{className:"font-mono",children:(Pa=r==null?void 0:r.info)!=null&&Pa.auto_exposure?a("cam.controls.auto"):a("cam.controls.manual")})]})]}),e.jsxs("div",{className:"mt-3 flex flex-wrap gap-2",children:[e.jsxs("button",{type:"button",disabled:!He,onClick:()=>void mt(),className:`rounded px-3 py-2 text-sm disabled:opacity-50 ${He?"bg-primary-container text-on-primary-container":"border border-outline-variant/40 text-on-surface-variant"}`,children:[e.jsx(Oa,{className:"inline h-4 w-4"})," ",a(I?"cam.btn.starting":"cam.btn.start")]}),e.jsxs("button",{type:"button",disabled:!oa,onClick:()=>void er(),className:`rounded border px-3 py-2 text-sm disabled:opacity-50 ${oa?"border-error/60 text-error hover:bg-error/10":"border-outline-variant/40 text-on-surface-variant"}`,children:[e.jsx(Wr,{className:"inline h-4 w-4"})," ",a(I?"cam.btn.stopping":"cam.btn.stop")]}),e.jsxs("button",{type:"button",disabled:!wr,onClick:()=>void tr(),className:"rounded border border-outline-variant/40 px-3 py-2 text-sm disabled:opacity-50",children:[e.jsx(Ia,{className:"inline h-4 w-4"})," ",a(_t?"cam.btn.capturing":"cam.btn.capture")]}),e.jsxs("button",{type:"button",disabled:!Nr,onClick:()=>void ar(),className:`rounded border px-3 py-2 text-sm disabled:opacity-50 ${r!=null&&r.recording?"border-error/60 bg-error/10 text-error":"border-outline-variant/40"}`,children:[e.jsx(Aa,{className:"inline h-4 w-4"})," ",Ke?a("cam.btn.recordBusy"):r!=null&&r.recording?a("cam.btn.recordStop"):a("cam.btn.recordStart")]})]})]}),e.jsxs("section",{className:"rounded-xl border border-outline-variant/20 bg-surface-container p-4 text-xs",children:[e.jsx("h2",{className:"mb-3 text-sm font-semibold uppercase tracking-wider",children:a("cam.files.title")}),e.jsx("div",{className:"mb-2",children:e.jsx("button",{type:"button",disabled:Kt,onClick:()=>void pt(),className:"rounded border border-outline-variant/40 px-2 py-1 disabled:opacity-50",children:a("cam.files.refresh")})}),Ka&&e.jsx("div",{className:"mb-2 text-on-surface-variant",children:a("cam.files.loadingInfo")}),C&&e.jsxs("div",{className:"mb-3 rounded border border-outline-variant/20 p-2",children:[e.jsxs("div",{className:"mb-2 flex items-center justify-between gap-2 font-semibold",children:[e.jsxs("div",{className:"flex min-w-0 items-center gap-1",children:[e.jsx(Dr,{className:"h-3.5 w-3.5 shrink-0"}),e.jsx("span",{className:"truncate",children:C.filename})]}),e.jsx("button",{type:"button",onClick:()=>ht(),className:"shrink-0 rounded border border-outline-variant/40 p-1 text-on-surface-variant hover:bg-surface-container","aria-label":a("cam.files.closeDetail"),children:e.jsx(Tr,{className:"h-3.5 w-3.5"})})]}),e.jsxs("div",{children:[a("cam.files.size"),": ",e.jsx("span",{className:"font-mono",children:Da(C.size)})]}),e.jsxs("div",{children:[a("cam.files.type"),": ",e.jsx("span",{className:"font-mono",children:C.type})]}),e.jsxs("div",{children:[a("cam.files.modified"),": ",e.jsx("span",{className:"font-mono",children:new Date(C.modified).toLocaleString()})]}),C.exposure_us!=null&&e.jsxs("div",{children:[a("cam.controls.exposure"),": ",e.jsx("span",{className:"font-mono",children:jt(C.exposure_us)})]}),C.analogue_gain!=null&&e.jsxs("div",{children:[a("cam.controls.gain"),": ",e.jsx("span",{className:"font-mono",children:C.analogue_gain})]}),C.resolution&&e.jsxs("div",{children:[a("cam.controls.resolution"),": ",e.jsx("span",{className:"font-mono",children:C.resolution})]})]}),e.jsxs("div",{className:"max-h-96 space-y-2 overflow-auto",children:[fe.length===0&&!Kt&&e.jsx("div",{className:"text-on-surface-variant",children:a("cam.files.empty")}),_r.map(t=>e.jsxs("div",{className:`rounded border p-2 ${Re===t.name?"border-primary/50 bg-primary/5":"border-outline-variant/20"}`,children:[e.jsxs("div",{className:"flex items-center justify-between gap-2",children:[e.jsxs("div",{className:"min-w-0",children:[e.jsx("div",{className:"truncate font-semibold",children:t.name}),e.jsxs("div",{className:"text-on-surface-variant",children:[Da(t.size)," | ",new Date(t.modified).toLocaleString()]})]}),e.jsx("div",{className:"shrink-0 text-on-surface-variant",children:t.type})]}),e.jsxs("div",{className:"mt-2 flex gap-2",children:[e.jsxs("button",{type:"button",onClick:()=>hr(t.name),className:"rounded border border-outline-variant/40 px-2 py-1",children:[e.jsx(Hr,{className:"mr-1 inline h-3.5 w-3.5"}),a("cam.files.download")]}),e.jsxs("button",{type:"button",onClick:()=>void pr(t.name),className:`rounded border px-2 py-1 ${Re===t.name?"border-primary text-primary":"border-outline-variant/40"}`,children:[e.jsx(zr,{className:"mr-1 inline h-3.5 w-3.5"}),a("cam.files.info")]}),e.jsxs("button",{type:"button",onClick:()=>void fr(t.name),className:"rounded border border-outline-variant/40 px-2 py-1",children:[e.jsx($r,{className:"mr-1 inline h-3.5 w-3.5"}),a("cam.files.delete")]})]})]},t.name))]}),e.jsxs("div",{className:"mt-3 flex items-center justify-between",children:[e.jsx("button",{type:"button",disabled:qe<=1,onClick:()=>$e(t=>Math.max(1,t-1)),className:"rounded border border-outline-variant/40 px-2 py-1 disabled:opacity-50",children:a("cam.files.prev")}),e.jsx("div",{className:"text-on-surface-variant",children:a("cam.files.page",{current:qe,total:Le})}),e.jsx("button",{type:"button",disabled:qe>=Le,onClick:()=>$e(t=>Math.min(Le,t+1)),className:"rounded border border-outline-variant/40 px-2 py-1 disabled:opacity-50",children:a("cam.files.next")})]})]})]}),e.jsxs("section",{className:"col-span-12 grid grid-cols-12 gap-4 2xl:col-span-3 2xl:self-start",children:[e.jsxs("section",{className:"col-span-12 rounded-xl border border-outline-variant/20 bg-surface-container p-4",children:[e.jsx("h2",{className:"mb-3 text-sm font-semibold uppercase tracking-wider",children:a("cam.controls.title")}),e.jsxs("div",{className:"space-y-3 text-xs",children:[e.jsxs("div",{className:"grid grid-cols-2 gap-2",children:[e.jsxs("label",{className:"block",children:[a("cam.controls.sensorFps"),e.jsx("input",{value:Ye,onChange:t=>{Ct(t.target.value),te(!0)},className:"mt-1 w-full rounded border border-outline-variant/30 bg-surface-container-low px-2 py-1.5"})]}),e.jsxs("label",{className:"block",children:[a("cam.controls.previewFps"),e.jsx("input",{value:Ft,onChange:t=>{Et(t.target.value),te(!0)},className:"mt-1 w-full rounded border border-outline-variant/30 bg-surface-container-low px-2 py-1.5"})]})]}),e.jsx("div",{className:"grid grid-cols-1 gap-2",children:e.jsxs("label",{className:"block",children:[a("cam.controls.sampling"),e.jsxs("select",{value:Bt,onChange:t=>{Pt(t.target.value),te(!0)},className:"mt-1 w-full rounded border border-outline-variant/30 bg-surface-container-low px-2 py-1.5",children:[e.jsx("option",{value:"supersample",children:"supersample"}),e.jsx("option",{value:"native",children:"native"}),e.jsx("option",{value:"crop",children:"crop"})]})]})}),e.jsxs("label",{className:"block",children:[a("cam.controls.resolution"),e.jsx("div",{className:"mt-1 grid grid-cols-2 gap-1",children:Jr.map(t=>e.jsx("button",{type:"button",onClick:()=>{$t(t),te(!0)},className:`rounded border px-2 py-1 ${Rt===t?"border-primary text-primary":"border-outline-variant/30"}`,children:t},t))})]}),e.jsx("button",{type:"button",disabled:!xe,onClick:()=>void rr(),className:"w-full rounded border border-outline-variant/40 px-2 py-1.5 disabled:opacity-50",children:a("cam.controls.applyRuntime")})]})]}),e.jsxs("section",{className:"col-span-12 rounded-xl border border-outline-variant/20 bg-surface-container p-4",children:[e.jsxs("div",{className:"mb-2 flex items-center gap-1 text-sm font-semibold uppercase tracking-wider",children:[e.jsx(Ur,{className:"h-3.5 w-3.5"})," ",a("cam.controls.core")]}),Ee&&e.jsx("div",{className:"mb-2 rounded border border-primary/40 bg-primary/10 px-2 py-1 text-[11px] text-primary",children:a("cam.controls.pendingChanges")}),Oe&&e.jsx("p",{className:"mb-2 text-[11px] text-on-surface-variant",children:a("cam.controls.lockedByAe")}),e.jsxs("div",{className:"grid grid-cols-2 gap-2 text-xs",children:[e.jsx(Qr,{label:a("cam.controls.exposure"),value:d.exposureSeconds,disabled:Oe,onChange:t=>{w(!0),S(n=>({...n,exposureSeconds:t}))}}),e.jsx(X,{label:a("cam.controls.gain"),value:d.gain,min:1,max:24,step:.1,disabled:Oe,onChange:t=>{w(!0),S(n=>({...n,gain:Number(t.toFixed(1))}))}}),e.jsx(X,{label:a("cam.controls.digitalGain"),value:d.digitalGain,min:1,max:8,step:.1,disabled:Oe||!ra,onChange:t=>{w(!0),S(n=>({...n,digitalGain:Number(t.toFixed(1))}))}}),e.jsxs("label",{className:"block",children:[a("cam.controls.noiseReductionMode"),e.jsx("select",{value:d.noiseReductionMode,onChange:t=>{w(!0),S(n=>({...n,noiseReductionMode:t.target.value}))},className:"mt-1 w-full rounded border border-outline-variant/30 bg-surface-container-low px-2 py-1.5",children:jr.map(t=>e.jsx("option",{value:t,children:a(`cam.controls.nr.${t}`)},t))})]}),e.jsx(X,{label:a("cam.controls.contrast"),value:d.contrast,min:0,max:2,step:.1,onChange:t=>{w(!0),S(n=>({...n,contrast:Number(t.toFixed(1))}))}}),e.jsx(X,{label:a("cam.controls.brightness"),value:d.brightness,min:-1,max:1,step:.1,onChange:t=>{w(!0),S(n=>({...n,brightness:Number(t.toFixed(1))}))}}),e.jsx(X,{label:a("cam.controls.saturation"),value:d.saturation,min:0,max:2,step:.1,onChange:t=>{w(!0),S(n=>({...n,saturation:Number(t.toFixed(1))}))}}),e.jsx(X,{label:a("cam.controls.sharpness"),value:d.sharpness,min:0,max:2,step:.1,onChange:t=>{w(!0),S(n=>({...n,sharpness:Number(t.toFixed(1))}))}})]}),aa!=null&&e.jsx("p",{className:"mt-2 text-[11px] text-tertiary",children:a("cam.controls.manualExposureFpsLimit",{exposure:d.exposureSeconds.toFixed(4),fps:aa.toFixed(2)})}),!ra&&e.jsxs("p",{className:"mt-2 text-[11px] text-on-surface-variant",children:[a("cam.controls.digitalGainReadOnly"),": ",e.jsx("span",{className:"font-mono",children:Number(((Ma=r==null?void 0:r.info)==null?void 0:Ma.actual_digital_gain)??d.digitalGain).toFixed(2)})]}),e.jsx("div",{className:"mt-2",children:e.jsx("button",{type:"button",disabled:!Ee,onClick:()=>void nr(),className:"w-full rounded border border-outline-variant/40 px-2 py-1.5 disabled:opacity-50",children:a("cam.controls.applySettings")})})]}),e.jsxs("section",{className:"col-span-12 rounded-xl border border-outline-variant/20 bg-surface-container p-4",children:[e.jsx("div",{className:"mb-2 text-sm font-semibold uppercase tracking-wider",children:a("cam.controls.mode")}),e.jsxs("div",{className:"grid grid-cols-2 gap-2 text-xs",children:[e.jsxs("label",{className:"block",children:[a("cam.controls.autoExposure"),e.jsxs("select",{value:d.autoExposure?"auto":"manual",onChange:t=>{S(n=>({...n,autoExposure:t.target.value==="auto"})),w(!0)},className:"mt-1 w-full rounded border border-outline-variant/30 bg-surface-container-low px-2 py-1.5",children:[e.jsx("option",{value:"auto",children:a("cam.controls.auto")}),e.jsx("option",{value:"manual",children:a("cam.controls.manual")})]})]}),e.jsxs("label",{className:"block",children:[a("cam.controls.colorMode"),e.jsxs("select",{value:d.colorMode,onChange:t=>{S(n=>({...n,colorMode:t.target.value})),w(!0)},className:"mt-1 w-full rounded border border-outline-variant/30 bg-surface-container-low px-2 py-1.5",children:[e.jsx("option",{value:"color",children:a("cam.controls.color")}),e.jsx("option",{value:"mono",children:a("cam.controls.mono")})]})]}),e.jsxs("label",{className:"block",children:[a("cam.controls.whiteBalance"),e.jsx("select",{value:d.whiteBalanceMode,onChange:t=>{S(n=>({...n,whiteBalanceMode:t.target.value})),w(!0)},className:"mt-1 w-full rounded border border-outline-variant/30 bg-surface-container-low px-2 py-1.5",children:yr.map(t=>e.jsx("option",{value:t,children:a(`cam.controls.wb.${t}`)},t))})]}),e.jsxs("label",{className:"block",children:[a("cam.controls.aeFlicker"),e.jsxs("select",{value:d.aeFlickerMode,onChange:t=>{S(n=>({...n,aeFlickerMode:t.target.value})),w(!0)},className:"mt-1 w-full rounded border border-outline-variant/30 bg-surface-container-low px-2 py-1.5",children:[e.jsx("option",{value:"off",children:a("cam.controls.off")}),e.jsx("option",{value:"50hz",children:"50 Hz"}),e.jsx("option",{value:"60hz",children:"60 Hz"})]})]}),e.jsxs("label",{className:"block",children:[a("cam.controls.maxAeFrame"),e.jsx("input",{type:"number",min:.01,max:1,step:.01,value:d.autoExposureMaxSeconds,onChange:t=>{S(n=>({...n,autoExposureMaxSeconds:N(Number(t.target.value)||1,.01,1)})),w(!0)},className:"mt-1 w-full rounded border border-outline-variant/30 bg-surface-container-low px-2 py-1.5"})]}),e.jsx(X,{label:"R Gain",value:d.whiteBalanceGainR,min:.1,max:3,step:.1,disabled:!ft,onChange:t=>{S(n=>({...n,whiteBalanceGainR:Number(t.toFixed(1))})),w(!0)}}),e.jsx(X,{label:"B Gain",value:d.whiteBalanceGainB,min:.1,max:3,step:.1,disabled:!ft,onChange:t=>{S(n=>({...n,whiteBalanceGainB:Number(t.toFixed(1))})),w(!0)}})]}),!ft&&e.jsx("p",{className:"mt-2 text-[11px] text-on-surface-variant",children:a("cam.controls.lockedByWb")}),e.jsx("div",{className:"mt-2",children:e.jsx("button",{type:"button",onClick:()=>void sr(),className:"w-full rounded border border-outline-variant/40 px-2 py-1.5",children:a("cam.controls.applyMode")})})]})]})]})]}),(ue||me)&&e.jsxs("div",{className:"fixed bottom-4 right-4 z-40 max-w-md space-y-2 text-xs",children:[ue&&e.jsx("div",{className:"rounded border border-error/40 bg-error-container/20 px-3 py-2 text-on-error-container",children:ue}),me&&e.jsx("div",{className:"rounded border border-primary/30 bg-primary/10 px-3 py-2 text-on-surface",children:me})]})]})}Fr.createRoot(document.getElementById("root")).render(e.jsx(Er.StrictMode,{children:e.jsx(Br,{children:e.jsx(Ar,{children:e.jsx(en,{})})})})); diff --git a/web/static/analysis-lab/assets/camera-CNwCW116.js b/web/static/analysis-lab/assets/camera-CNwCW116.js new file mode 100644 index 0000000..cedea0d --- /dev/null +++ b/web/static/analysis-lab/assets/camera-CNwCW116.js @@ -0,0 +1,61 @@ +import{r as o,j as e,a as Br,R as Pr}from"./client-D1ZVDB-N.js";import{c as T,u as Mr,T as Tr,I as Ir}from"./index-BwAjnQW0.js";import{u as Ar,S as Or,C as Aa,X as Hr,r as Dr,b as Gr}from"./http-Dln9HPhZ.js";import{R as zr}from"./rotate-ccw-BE-GHC4e.js";/** + * @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 Oa=T("Circle",[["circle",{cx:"12",cy:"12",r:"10",key:"1mglay"}]]);/** + * @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 wt=T("Crosshair",[["circle",{cx:"12",cy:"12",r:"10",key:"1mglay"}],["line",{x1:"22",x2:"18",y1:"12",y2:"12",key:"l9bcsi"}],["line",{x1:"6",x2:"2",y1:"12",y2:"12",key:"13hhkx"}],["line",{x1:"12",x2:"12",y1:"6",y2:"2",key:"10w3f3"}],["line",{x1:"12",x2:"12",y1:"22",y2:"18",key:"15g9kq"}]]);/** + * @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 Ha=T("Download",[["path",{d:"M21 15v4a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2v-4",key:"ih7n3h"}],["polyline",{points:"7 10 12 15 17 10",key:"2ggqvy"}],["line",{x1:"12",x2:"12",y1:"15",y2:"3",key:"1vk2je"}]]);/** + * @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 Lr=T("FileText",[["path",{d:"M15 2H6a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V7Z",key:"1rqfz7"}],["path",{d:"M14 2v4a2 2 0 0 0 2 2h4",key:"tnqrlb"}],["path",{d:"M10 9H8",key:"b1mrlr"}],["path",{d:"M16 13H8",key:"t4e002"}],["path",{d:"M16 17H8",key:"z1uh3a"}]]);/** + * @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 qr=T("FolderOpen",[["path",{d:"m6 14 1.5-2.9A2 2 0 0 1 9.24 10H20a2 2 0 0 1 1.94 2.5l-1.54 6a2 2 0 0 1-1.95 1.5H4a2 2 0 0 1-2-2V5a2 2 0 0 1 2-2h3.9a2 2 0 0 1 1.69.9l.81 1.2a2 2 0 0 0 1.67.9H18a2 2 0 0 1 2 2v2",key:"usdka0"}]]);/** + * @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 Ur=T("Info",[["circle",{cx:"12",cy:"12",r:"10",key:"1mglay"}],["path",{d:"M12 16v-4",key:"1dtifu"}],["path",{d:"M12 8h.01",key:"e9boi3"}]]);/** + * @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 Wr=T("Moon",[["path",{d:"M12 3a6 6 0 0 0 9 9 9 9 0 1 1-9-9Z",key:"a7tn18"}]]);/** + * @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 Da=T("Play",[["polygon",{points:"6 3 20 12 6 21 6 3",key:"1oa8hb"}]]);/** + * @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 Vr=T("Settings2",[["path",{d:"M20 7h-9",key:"3s1dr2"}],["path",{d:"M14 17H5",key:"gfn3mx"}],["circle",{cx:"17",cy:"17",r:"3",key:"18b49y"}],["circle",{cx:"7",cy:"7",r:"3",key:"dfmy0x"}]]);/** + * @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 Xr=T("SlidersHorizontal",[["line",{x1:"21",x2:"14",y1:"4",y2:"4",key:"obuewd"}],["line",{x1:"10",x2:"3",y1:"4",y2:"4",key:"1q6298"}],["line",{x1:"21",x2:"12",y1:"12",y2:"12",key:"1iu8h1"}],["line",{x1:"8",x2:"3",y1:"12",y2:"12",key:"ntss68"}],["line",{x1:"21",x2:"16",y1:"20",y2:"20",key:"14d8ph"}],["line",{x1:"12",x2:"3",y1:"20",y2:"20",key:"m0wm8r"}],["line",{x1:"14",x2:"14",y1:"2",y2:"6",key:"14e1ph"}],["line",{x1:"8",x2:"8",y1:"10",y2:"14",key:"1i6ji0"}],["line",{x1:"16",x2:"16",y1:"18",y2:"22",key:"1lctlv"}]]);/** + * @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 Jr=T("Square",[["rect",{width:"18",height:"18",x:"3",y:"3",rx:"2",key:"afitv7"}]]);/** + * @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 Kr=T("Sun",[["circle",{cx:"12",cy:"12",r:"4",key:"4exip2"}],["path",{d:"M12 2v2",key:"tus03m"}],["path",{d:"M12 20v2",key:"1lh1kg"}],["path",{d:"m4.93 4.93 1.41 1.41",key:"149t6j"}],["path",{d:"m17.66 17.66 1.41 1.41",key:"ptbguv"}],["path",{d:"M2 12h2",key:"1t8f8n"}],["path",{d:"M20 12h2",key:"1q8mjw"}],["path",{d:"m6.34 17.66-1.41 1.41",key:"1m8zz5"}],["path",{d:"m19.07 4.93-1.41 1.41",key:"1shlcs"}]]),Yr="/api/dev/debug";function A(a){return`${Yr}${a}`}async function b(a,g={}){const f=a.startsWith("/api/debug/")?a.replace("/api/debug/","/api/dev/debug/"):a;return Dr(f,g)}const Zr=["640x360","1280x720","1600x900","1920x1020"],Qr=[0,90,180,270],Ke=12,le=1e-4,de=1,en=1e-4;function N(a,g,f){return Math.min(f,Math.max(g,a))}function k(a,g){return a==null||Number.isNaN(Number(a))?g:Number(a)}function ue(a,g){return k(a,g)/1e6}function St(a){return Math.round(a*1e6)}function Nt(a){return`${ue(a,0).toFixed(4)} s`}function Ga(a){const{exposureSeconds:g,autoExposureMaxSeconds:f,...p}=a;return{...p,exposure:St(g),autoExposureMaxUs:St(f)}}function za(a){if(!a)return"0 B";const g=["B","KB","MB","GB"];let f=0,p=a;for(;p>=1024&&fE.hfd),p=Math.min(...f),r=Math.max(...f),J=Math.max(.2,r-p),m=f.map((E,K)=>{const me=K/Math.max(1,f.length-1)*320,l=76-(E-p)/J*60;return`${me.toFixed(1)},${l.toFixed(1)}`}).join(" ");return e.jsxs("svg",{viewBox:"0 0 320 88",className:"h-24 w-full rounded-lg border border-outline-variant/20 bg-black/25",role:"img",children:[e.jsx("line",{x1:"0",y1:"76",x2:"320",y2:"76",stroke:"currentColor",opacity:"0.18"}),e.jsx("polyline",{points:m,fill:"none",stroke:"currentColor",strokeWidth:"3",strokeLinejoin:"round",className:"text-primary"}),e.jsx("circle",{cx:"320",cy:((B=m.split(" ").at(-1))==null?void 0:B.split(",")[1])??"44",r:"4",fill:"currentColor",className:"text-primary"})]})}async function La(){try{const a=await fetch(`${A("/camera/stream/status")}`,{cache:"no-store",credentials:"same-origin"});if(!a.ok)return"fail";const g=await a.json(),f=Number(g.max_clients??0),p=Number(g.active_clients??0);return f>0&&p>=f?"busy":"ok"}catch{return"fail"}}function X({label:a,value:g,min:f,max:p,step:r,onChange:J,disabled:m=!1,unit:B=""}){const E=r>=1?0:Math.min(6,Math.max(1,Math.ceil(-Math.log10(r))));return e.jsxs("label",{className:`block ${m?"opacity-50":""}`,children:[e.jsxs("div",{className:"mb-1 flex items-center justify-between",children:[e.jsx("span",{children:a}),e.jsxs("span",{className:"font-mono text-[11px]",children:[g.toFixed(E),B]})]}),e.jsx("input",{type:"range",min:f,max:p,step:r,value:g,disabled:m,onChange:K=>J(Number(K.target.value)),className:"w-full accent-primary"})]})}function an({label:a,value:g,disabled:f,onChange:p}){const r=N(g,le,de),J=Math.log10(le),m=Math.log10(de);return e.jsxs("div",{className:`block ${f?"opacity-50":""}`,children:[e.jsxs("div",{className:"mb-1 flex items-center justify-between gap-2",children:[e.jsx("span",{children:a}),e.jsxs("span",{className:"flex items-center gap-1",children:[e.jsx("input",{type:"number",min:le,max:de,step:en,value:Number(r.toFixed(6)),disabled:f,"aria-label":a,onChange:B=>{const E=Number(B.target.value);Number.isFinite(E)&&p(N(E,le,de))},className:"w-24 rounded border border-outline-variant/30 bg-surface-container-low px-1.5 py-0.5 text-right font-mono text-[11px]"}),e.jsx("span",{className:"font-mono text-[11px]",children:"s"})]})]}),e.jsx("input",{type:"range",min:J,max:m,step:.001,value:Math.log10(r),disabled:f,"aria-label":a,onChange:B=>{const E=Number((10**Number(B.target.value)).toFixed(6));p(N(E,le,de))},className:"w-full accent-primary"})]})}function rn(){var ma,xa,pa,ha,fa,ba,ga,ya,va,ja,wa,Na,Sa,_a,ka,Ca,Fa,Ea,Ra,$a,Ba,Pa,Ma,Ta;const{t:a,locale:g,setLocale:f}=Mr(),{info:p}=Ar(),[r,J]=o.useState(null),[m,B]=o.useState(!1),[E,K]=o.useState(()=>Date.now()),[me,l]=o.useState(null),[we,Q]=o.useState(null),[_t,ee]=o.useState(!1),[xe,y]=o.useState(null),[I,Ne]=o.useState(!1),[Ye,kt]=o.useState(!1),[Ct,Ft]=o.useState(!1),[Ze,Et]=o.useState("8"),[Rt,$t]=o.useState("8"),[Bt,Pt]=o.useState("1280x720"),[Mt,Tt]=o.useState("supersample"),[pe,te]=o.useState(!1),[It,qa]=o.useState(!0),[Qe,Ua]=o.useState(!0),[et,Wa]=o.useState(!1),[At,Va]=o.useState(!1),[Ot,Ht]=o.useState(!1),[tt,Xa]=o.useState({mean:0,std:0,over:0}),[Ja,Se]=o.useState(0),[i,at]=o.useState(null),[_,rt]=o.useState(!1),[ae,_e]=o.useState(!1),[s,Dt]=o.useState(null),[ke,he]=o.useState([]),[re,fe]=o.useState(null),[j,nt]=o.useState(null),[Gt,D]=o.useState(null),[zt,Lt]=o.useState(0),[qt,Ut]=o.useState(180),[Ce,Wt]=o.useState(!1),[Fe,Vt]=o.useState(!1),[d,S]=o.useState({exposureSeconds:.005,gain:1,digitalGain:1,autoExposure:!0,contrast:1,brightness:0,saturation:1,sharpness:1,noiseReduction:0,noiseReductionMode:"fast",aeFlickerMode:"off",autoExposureMaxSeconds:1,whiteBalanceMode:"auto",whiteBalanceGainR:1,whiteBalanceGainB:1,colorMode:"color"}),[Ee,w]=o.useState(!1),[st,Xt]=o.useState(""),[Jt,Kt]=o.useState(""),[Yt,Ka]=o.useState([]),[ot,L]=o.useState(!1),[ne,Ya]=o.useState([]),[Re,Za]=o.useState(null),[it,Zt]=o.useState(!1),[C,ct]=o.useState(null),[Qa,lt]=o.useState(!1),[$e,dt]=o.useState(null),[ut,Be]=o.useState(1),P=o.useRef(null),Pe=o.useRef(null),be=o.useRef(null),Me=o.useRef(null),Te=o.useRef(null),q=o.useRef(null),Ie=o.useRef(null),mt=o.useRef(!1),se=o.useRef(!1),Y=o.useRef(!1),Ae=o.useRef(null),U=o.useRef([]),oe=o.useRef(0),xt=o.useRef(null),Qt=()=>{U.current=[],oe.current=0,Ae.current=null,Se(0)},R=async()=>{try{const t=await b("/api/debug/camera/status",{cache:"no-store"});J(t),t.streaming||(B(!1),Y.current=!1)}catch(t){l(t instanceof Error?t.message:String(t))}},ge=()=>{Ie.current!=null&&(window.clearTimeout(Ie.current),Ie.current=null)},pt=async()=>{if(I)return!1;Ne(!0),l(null),Q(null),ee(!1);try{ge(),r!=null&&r.streaming||await b("/api/debug/camera/start",{method:"POST"});const t=Date.now(),n=await La();return n==="busy"?(Q(a("cam.err.streamBusy")),ee(!0),!1):n==="fail"?(Q(a("cam.err.streamProbeFailed")),ee(!1),!1):(B(!0),Y.current=!0,y(a("cam.notice.previewStart")),Qt(),Ae.current=performance.now(),K(t),await R(),!0)}catch(t){return l(t instanceof Error?t.message:String(t)),!1}finally{Ne(!1)}},ea=async()=>{if(!ae){_e(!0),D(null);try{if(!(m||await pt()))return;const n=await b(A("/camera/focus/session/start"),{method:"POST"});se.current=n.active,S(c=>({...c,autoExposure:!1,exposureSeconds:ue(n.locked.exposure,5e3),gain:n.locked.gain,digitalGain:n.locked.digitalGain,noiseReduction:0,noiseReductionMode:n.locked.noiseReductionMode})),w(!1),Dt(null),he([]),fe(null),nt(null),Ht(!0),rt(!0),window.setTimeout(()=>{var c;(c=document.getElementById("focus-calibration-panel"))==null||c.scrollIntoView({behavior:"smooth",block:"start"})},0)}catch(t){D(t instanceof Error?t.message:String(t))}finally{_e(!1)}}},ht=async()=>{if(!ae){_e(!0),rt(!1),D(null);try{if(se.current){const t=await b(A("/camera/focus/session/stop"),{method:"POST"});se.current=!1,t.restored_settings&&(S(n=>({...n,autoExposure:t.restored_settings.autoExposure,exposureSeconds:ue(t.restored_settings.exposure,5e3),gain:t.restored_settings.gain,digitalGain:t.restored_settings.digitalGain,noiseReductionMode:t.restored_settings.noiseReductionMode,noiseReduction:t.restored_settings.noiseReductionMode==="off"?0:1})),w(!1))}}catch(t){D(t instanceof Error?t.message:String(t))}finally{_e(!1)}}},er=()=>{he([]),fe(null),D(null)},tr=t=>{if(!_||!P.current||t.target.closest("button, label, input"))return;const n=P.current,c=t.currentTarget.getBoundingClientRect(),h=n.naturalWidth||(s==null?void 0:s.frame.width)||0,u=n.naturalHeight||(s==null?void 0:s.frame.height)||0;if(h<=0||u<=0)return;const x=Math.min(c.width/h,c.height/u),F=h*x,v=u*x,$=(c.width-F)/2,M=(c.height-v)/2,z=(t.clientX-c.left-$)/F,ie=(t.clientY-c.top-M)/v;z<0||z>1||ie<0||ie>1||(nt({x:z,y:ie}),he([]),fe(null),D(null))},ar=()=>{nt(null),he([]),fe(null),D(null)},rr=async()=>{if(!I){Ne(!0),l(null);try{ge(),B(!1),Y.current=!1,Q(null),ee(!1),Qt(),P.current&&(P.current.onload=null,P.current.onerror=null,P.current.src="",P.current.removeAttribute("src")),K(Date.now()),y(a("cam.notice.previewStop"))}catch(t){l(t instanceof Error?t.message:String(t))}finally{Ne(!1)}}},nr=async()=>{if(!(!m&&!(r!=null&&r.streaming))){Ft(!0),l(null);try{const t=await b("/api/debug/camera/capture",{method:"POST"});y(a("cam.notice.captureSaved",{name:t.filename||"capture"}))}catch(t){l(t instanceof Error?t.message:String(t))}finally{Ft(!1)}}},sr=async()=>{if(!Ye){kt(!0),l(null);try{if(r!=null&&r.recording)await b("/api/debug/camera/record/stop",{method:"POST"}),y(a("cam.notice.recordStop"));else{const t=await b("/api/debug/camera/record/start",{method:"POST"});y(a("cam.notice.recordStart",{name:t.filename||"video.avi"}))}await R()}catch(t){l(t instanceof Error?t.message:String(t))}finally{kt(!1)}}},or=async()=>{l(null);try{const t=N(parseInt(Ze,10)||5,1,60);await b(`/api/debug/camera/fps?fps=${t}`,{method:"POST"});const n=N(parseInt(Rt,10)||8,1,30);await b(`/api/debug/camera/preview-fps?fps=${n}`,{method:"POST"});const[c,h]=Bt.split("x").map(u=>parseInt(u,10));c&&h&&await b(`/api/debug/camera/size?width=${c}&height=${h}`,{method:"POST"}),await b(`/api/debug/camera/sampling?mode=${encodeURIComponent(Mt)}`,{method:"POST"}),y(a("cam.notice.runtimeApplied")),te(!1),await R()}catch(t){l(t instanceof Error?t.message:String(t))}},ir=async()=>{l(null);try{await b("/api/debug/camera/settings",{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify(Ga(d))}),y(a("cam.notice.settingsApplied")),w(!1),await R()}catch(t){l(t instanceof Error?t.message:String(t))}},cr=async()=>{l(null);try{await b("/api/debug/camera/settings",{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify(Ga(d))}),y(a("cam.notice.modeApplied")),w(!1),await R()}catch(t){l(t instanceof Error?t.message:String(t))}},lr=(t,n)=>{if(!t)return;const c=(n==null?void 0:n.syncRuntime)??!0;S({exposureSeconds:N(ue(t.exposure_us,5e3),le,de),gain:N(k(t.analogue_gain,1),1,24),digitalGain:N(k(t.digital_gain,1),1,8),autoExposure:!!(t.auto_exposure??!0),contrast:N(k(t.contrast,1),0,2),brightness:N(k(t.brightness,0),-1,1),saturation:N(k(t.saturation,1),0,2),sharpness:N(k(t.sharpness,1),0,2),noiseReduction:N(Math.round(k(t.noise_reduction,0)),0,4),noiseReductionMode:String(t.noise_reduction_mode??"fast"),aeFlickerMode:String(t.ae_flicker_mode??"off"),autoExposureMaxSeconds:N(ue(t.auto_exposure_max_us,1e6),.01,1),whiteBalanceMode:String(t.white_balance_mode??"auto"),whiteBalanceGainR:N(k(t.white_balance_gain_r,1),.1,3),whiteBalanceGainB:N(k(t.white_balance_gain_b,1),.1,3),colorMode:String(t.color_mode??"color")}),c&&(Et(String(Math.round(k(t.fps,8)))),Pt(`${Math.round(k(t.width,1280))}x${Math.round(k(t.height,720))}`),Tt(String(t.sampling_mode??"supersample")),te(!1)),Ut(N(Math.round(k(t.rotation,180)),0,270)),Wt(!!t.flip_horizontal),Vt(!!t.flip_vertical),w(!1)},ta=async(t,n)=>{l(null);try{await b("/api/debug/camera/mirror",{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({flip_horizontal:t,flip_vertical:n})}),Wt(t),Vt(n),y(a("cam.notice.mirrorApplied")),await R()}catch(c){l(c instanceof Error?c.message:String(c))}},dr=async t=>{l(null);try{await b(`/api/debug/camera/rotation/${t}`,{method:"POST"}),Ut(t),y(a("cam.notice.rotationApplied",{value:t})),await R()}catch(n){l(n instanceof Error?n.message:String(n))}},aa=async t=>{l(null);try{await b(`/api/debug/camera/night-mode?enabled=${t?"true":"false"}`,{method:"POST"}),y(a(t?"cam.notice.nightOn":"cam.notice.nightOff")),await R()}catch(n){l(n instanceof Error?n.message:String(n))}},ur=async()=>{if(window.confirm(a("cam.confirm.reset"))){l(null);try{await b("/api/debug/camera/reset",{method:"POST"}),y(a("cam.notice.reset")),await R()}catch(t){l(t instanceof Error?t.message:String(t))}}},mr=async()=>{l(null);try{await b("/api/debug/camera/backup-settings",{method:"POST"}),y(a("cam.notice.backup"))}catch(t){l(t instanceof Error?t.message:String(t))}},xr=async()=>{l(null);try{await b("/api/debug/camera/restore-settings",{method:"POST"}),y(a("cam.notice.restore")),await R()}catch(t){l(t instanceof Error?t.message:String(t))}},Oe=async()=>{L(!0);try{const t=await b("/api/debug/camera/presets",{cache:"no-store"});Ka(t.presets??[])}catch(t){l(t instanceof Error?t.message:String(t))}finally{L(!1)}},pr=async()=>{const t=st.trim();if(t){L(!0),l(null);try{await b("/api/debug/camera/presets",{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({name:t,description:Jt.trim(),exposure_us:St(d.exposureSeconds),analogue_gain:d.gain,digital_gain:d.digitalGain,auto_exposure:d.autoExposure,contrast:d.contrast,brightness:d.brightness,saturation:d.saturation,sharpness:d.sharpness,noise_reduction:d.noiseReduction,white_balance_mode:d.whiteBalanceMode,white_balance_gain_r:d.whiteBalanceGainR,white_balance_gain_b:d.whiteBalanceGainB,rotation:qt,flip_horizontal:Ce,flip_vertical:Fe,color_mode:d.colorMode})}),y(a("cam.notice.presetSaved",{name:t})),Xt(""),Kt(""),await Oe()}catch(n){l(n instanceof Error?n.message:String(n))}finally{L(!1)}}},hr=async t=>{L(!0),l(null);try{await b(`/api/debug/camera/presets/${encodeURIComponent(t)}/apply`,{method:"POST"}),y(a("cam.notice.presetApplied",{name:t})),await R(),await Oe()}catch(n){l(n instanceof Error?n.message:String(n))}finally{L(!1)}},fr=async t=>{if(window.confirm(a("cam.confirm.deletePreset",{name:t}))){L(!0),l(null);try{await b(`/api/debug/camera/presets/${encodeURIComponent(t)}`,{method:"DELETE"}),y(a("cam.notice.presetDeleted",{name:t})),await Oe()}catch(n){l(n instanceof Error?n.message:String(n))}finally{L(!1)}}},ft=async()=>{Zt(!0);try{const t=await b("/api/debug/files",{cache:"no-store"});Ya(t.files??[]),Za(t.storage??null),Be(1)}catch(t){l(t instanceof Error?t.message:String(t))}finally{Zt(!1)}},br=()=>{const t=document.createElement("a");t.href=`${A("/files")}/export`,t.download="",document.body.appendChild(t),t.click(),document.body.removeChild(t),y(a("cam.notice.exportAll"))},bt=()=>{dt(null),ct(null),lt(!1)},gr=async t=>{if($e===t){bt();return}dt(t),ct(null),lt(!0),l(null);try{const n=await b(`/api/debug/files/${encodeURIComponent(t)}/info`,{cache:"no-store"});ct(n)}catch(n){l(n instanceof Error?n.message:String(n)),dt(null)}finally{lt(!1)}},yr=t=>{const n=(h,u)=>{const x=document.createElement("a");x.href=u,x.download=h,document.body.appendChild(x),x.click(),document.body.removeChild(x)};n(t,`${A("/files")}/${encodeURIComponent(t)}`);const c=t.match(/\.(jpe?g|png|bmp|tiff?|webp|mp4|avi|mov|mkv|wmv|flv|webm|m4v)$/i);if(c){const u=`${t.slice(0,-c[0].length)}.txt`;(async()=>{try{if(!(await fetch(`${A("/files")}/${encodeURIComponent(u)}`)).ok)return;n(u,`${A("/files")}/${encodeURIComponent(u)}`),y(a("cam.notice.downloadWithSidecar",{name:t,sidecar:u}))}catch{y(a("cam.notice.download",{name:t}))}})();return}y(a("cam.notice.download",{name:t}))},vr=async t=>{if(window.confirm(a("cam.confirm.deleteFile",{name:t}))){l(null);try{await b(`/api/debug/files/${encodeURIComponent(t)}`,{method:"DELETE"}),y(a("cam.notice.fileDeleted",{name:t})),($e===t||(C==null?void 0:C.filename)===t)&&bt(),await ft()}catch(n){l(n instanceof Error?n.message:String(n))}}},jr=()=>{if(!It||!P.current||!Pe.current)return;const t=P.current;if(!t.naturalWidth||!t.naturalHeight||(be.current||(be.current=document.createElement("canvas"),Me.current=be.current.getContext("2d",{willReadFrequently:!0})),Te.current||(Te.current=Pe.current.getContext("2d")),!Me.current||!Te.current))return;const c=Math.min(1,320/t.naturalWidth),h=Math.max(1,Math.round(t.naturalWidth*c)),u=Math.max(1,Math.round(t.naturalHeight*c));be.current.width=h,be.current.height=u,Me.current.drawImage(t,0,0,h,u);const x=Me.current.getImageData(0,0,h,u).data,F=new Array(256).fill(0),v=new Array(256).fill(0),$=new Array(256).fill(0),M=new Array(256).fill(0);let z=0,ie=0,Ia=0;const ce=h*u;for(let H=0;H=250&&(Ia+=1)}const jt=ce?z/ce:0,Rr=ce?ie/ce-jt*jt:0;Xa({mean:jt,std:Math.sqrt(Math.max(0,Rr)),over:ce?Ia/ce*100:0});const We=Pe.current,O=Te.current,Ve=window.devicePixelRatio||1,ye=Math.max(1,We.clientWidth||240),ve=Math.max(1,We.clientHeight||120);We.width=Math.floor(ye*Ve),We.height=Math.floor(ve*Ve),O.setTransform(Ve,0,0,Ve,0,0),O.clearRect(0,0,ye,ve);const $r=Math.max(1,...Qe?[Math.max(...F),Math.max(...v),Math.max(...$)]:[0],...et?[Math.max(...M)]:[0]),Xe=(H,Je)=>{O.beginPath(),O.strokeStyle=Je,O.lineWidth=1.2;for(let W=0;W<256;W+=1){const je=W/255*ye,V=ve-H[W]/$r*ve;W===0?O.moveTo(je,V):O.lineTo(je,V)}O.stroke()};if(Qe&&(Xe(F,"rgba(255,80,80,0.85)"),Xe(v,"rgba(80,255,80,0.85)"),Xe($,"rgba(80,160,255,0.85)")),et&&Xe(M,"rgba(255,255,255,0.95)"),At){const H=.9803921568627451*ye;O.fillStyle="rgba(255,100,100,0.12)",O.fillRect(H,0,ye-H,ve)}};o.useEffect(()=>{R(),Oe(),ft();const t=window.setInterval(()=>{document.hidden||R()},2e3);return()=>window.clearInterval(t)},[r==null?void 0:r.streaming]),o.useEffect(()=>{!(r!=null&&r.info)||Ee||lr(r.info,{syncRuntime:!pe})},[r==null?void 0:r.info,Ee,pe]),o.useEffect(()=>{if(!(r!=null&&r.recording)){q.current&&(window.clearInterval(q.current),q.current=null),Lt(0);return}if(q.current)return;const t=Date.now();return q.current=window.setInterval(()=>{Lt(Math.max(0,Math.floor((Date.now()-t)/1e3)))},1e3),()=>{q.current&&(window.clearInterval(q.current),q.current=null)}},[r==null?void 0:r.recording]),o.useEffect(()=>{Y.current=m,m||(ge(),rt(!1),se.current&&ht())},[m]),o.useEffect(()=>()=>{se.current&&(navigator.sendBeacon(A("/camera/focus/session/stop")),se.current=!1)},[]),o.useEffect(()=>{if(!_||!m)return;let t=!1;const n=async()=>{var h,u;if(!(mt.current||document.hidden)){mt.current=!0;try{const x=new URLSearchParams;j&&(x.set("target_x",j.x.toFixed(6)),x.set("target_y",j.y.toFixed(6)));const F=x.size>0?`?${x.toString()}`:"",v=await b(`${A("/camera/focus/metrics")}${F}`,{cache:"no-store"});if(t)return;Dt(v),D(null);const $=j?(h=v.selected_star)==null?void 0:h.hfd_px:(u=v.aggregate)==null?void 0:u.median_hfd_px;typeof $=="number"&&Number.isFinite($)&&(he(M=>{var z;return((z=M.at(-1))==null?void 0:z.frameId)===v.frame_id?M:[...M,{frameId:v.frame_id,hfd:$}].slice(-60)}),fe(M=>M==null?$:Math.min(M,$)))}catch(x){t||D(x instanceof Error?x.message:String(x))}finally{mt.current=!1}}};n();const c=window.setInterval(n,850);return()=>{t=!0,window.clearInterval(c)}},[_,m,j==null?void 0:j.x,j==null?void 0:j.y]),o.useEffect(()=>{m&&(U.current=[],oe.current=0)},[E,m]),o.useEffect(()=>{if(!m){U.current=[],oe.current=0,Se(0);return}if(!xt.current){const u=document.createElement("canvas");u.width=32,u.height=32,xt.current=u}const n=xt.current.getContext("2d",{willReadFrequently:!0});if(!n)return;let c=0;const h=()=>{const u=P.current;if(u!=null&&u.complete&&u.naturalWidth>0)try{n.drawImage(u,0,0,32,32);const x=n.getImageData(0,0,32,32).data;let F=2166136261;for(let v=0;vwindow.cancelAnimationFrame(c)},[m]),o.useEffect(()=>{if(!m){Se(0);return}const t=window.setInterval(()=>{const c=performance.now()-1e3,h=U.current;for(;h.length&&h[0]window.clearInterval(t)},[m]),o.useEffect(()=>{if(!m){at(null);return}let t=!1;const n=async()=>{try{const h=await fetch(`${A("/camera/stream/status")}`,{cache:"no-store",credentials:"same-origin"});if(!h.ok||t)return;const u=await h.json();if(!t){at(u);const x=Number(u.preview_target_fps??u.target_preview_fps??0);Number.isFinite(x)&&x>0&&!pe&&$t(String(Math.round(x)))}}catch{t||at(null)}};n();const c=window.setInterval(n,2e3);return()=>{t=!0,window.clearInterval(c)}},[m,pe]),o.useEffect(()=>{if(!xe)return;const t=window.setTimeout(()=>y(null),3200);return()=>window.clearTimeout(t)},[xe]),o.useEffect(()=>()=>ge(),[]),o.useEffect(()=>{const t=Math.max(1,Math.ceil(ne.length/Ke));ut>t&&Be(t)},[ne.length,ut]);const wr=m?`${A("/camera/stream")}?t=${E}`:"",He=d.autoExposure,gt=d.whiteBalanceMode==="manual",Nr=N(parseInt(Ze,10)||Math.round(k((ma=r==null?void 0:r.info)==null?void 0:ma.fps,8)),1,60),ra=!d.autoExposure&&1/d.exposureSeconds0?Z.awb_modes:["auto","daylight","cloudy","tungsten","fluorescent","indoor","manual","night"],_r=Array.isArray(Z.noise_reduction_modes)&&Z.noise_reduction_modes.length>0?Z.noise_reduction_modes:["off","fast","high_quality"],na=Z.manual_digital_gain!==!1,sa=!!((pa=r==null?void 0:r.info)!=null&&pa.night_mode),oa=m,De=!I&&!m&&!(r!=null&&r.recording),ia=!I&&m,kr=!I&&!Ct&&oa,Cr=!I&&!Ye&&oa,G=j?s==null?void 0:s.selected_star:null,Ge=j?(G==null?void 0:G.hfd_px)??null:((ha=s==null?void 0:s.aggregate)==null?void 0:ha.median_hfd_px)??null,ca=j?(G==null?void 0:G.fwhm_px)??null:((fa=s==null?void 0:s.aggregate)==null?void 0:fa.median_fwhm_px)??null,la=j?(G==null?void 0:G.concentration)??null:((ba=s==null?void 0:s.aggregate)==null?void 0:ba.median_concentration)??null,ze=Ge!=null&&re!=null&&re>0?(Ge-re)/re*100:null,da=ke.slice(-3).map(t=>t.hfd),yt=ke.slice(-6,-3).map(t=>t.hfd),vt=t=>t.reduce((n,c)=>n+c,0)/Math.max(1,t.length);let Le="cam.focus.guidance.collecting";if((s==null?void 0:s.state)==="no_stars")Le="cam.focus.guidance.noStars";else if(ke.length>=5&&ze!=null&&ze<=3)Le="cam.focus.guidance.best";else if(da.length===3&&yt.length===3){const t=vt(da)-vt(yt),n=Math.max(.04,vt(yt)*.02);Le=t<-n?"cam.focus.guidance.improving":t>n?"cam.focus.guidance.worsening":"cam.focus.guidance.steady"}const Fr=((s==null?void 0:s.stars_used)??0)>=5?"cam.focus.confidence.high":((s==null?void 0:s.stars_used)??0)>=3?"cam.focus.confidence.medium":"cam.focus.confidence.low",qe=Math.max(1,Math.ceil(ne.length/Ke)),Ue=Math.min(ut,qe),ua=(Ue-1)*Ke,Er=ne.slice(ua,ua+Ke);return e.jsxs("div",{className:"min-h-screen bg-background text-on-surface",children:[e.jsx("header",{className:"sticky top-0 z-30 border-b border-outline-variant/20 bg-surface-container-low/90 px-4 py-3 backdrop-blur",children:e.jsxs("div",{className:"flex w-full flex-wrap items-center justify-between gap-3",children:[e.jsx("div",{className:"min-w-0",children:e.jsx("h1",{className:"break-words font-headline text-lg font-bold text-primary sm:text-xl",children:`OGScope ${a("cam.title")}`})}),e.jsxs("div",{className:"flex items-center gap-2 text-xs",children:[e.jsx("button",{type:"button",className:`inline-flex h-7 items-center rounded px-2 py-1 ${g==="zh"?"bg-primary-container text-on-primary-container":"text-on-surface-variant"}`,onClick:()=>f("zh"),children:a("lang.zh")}),e.jsx("button",{type:"button",className:`inline-flex h-7 items-center rounded px-2 py-1 ${g==="en"?"bg-primary-container text-on-primary-container":"text-on-surface-variant"}`,onClick:()=>f("en"),children:a("lang.en")}),e.jsxs("a",{href:"/debug",className:"inline-flex h-7 items-center gap-1 rounded border border-outline-variant/30 px-2 py-1 hover:bg-surface-container",children:[e.jsx(Vr,{className:"h-3.5 w-3.5"})," ",a("cam.btn.system")]})]})]})}),e.jsxs("main",{className:"mx-auto grid max-w-[1880px] grid-cols-12 gap-4 p-4",children:[e.jsxs("aside",{className:"order-3 col-span-12 space-y-4 2xl:order-1 2xl:col-span-2",children:[e.jsxs("section",{className:"rounded-xl border border-outline-variant/20 bg-surface-container p-4",children:[e.jsx("div",{className:"mb-2 text-sm font-semibold uppercase tracking-wider",children:a("cam.controls.tools")}),e.jsx("div",{className:"mb-2 flex flex-wrap gap-1 text-xs",children:Qr.map(t=>e.jsxs("button",{type:"button",onClick:()=>void dr(t),className:`rounded border px-2 py-1 ${qt===t?"border-primary text-primary":"border-outline-variant/30"}`,children:[t,"°"]},t))}),e.jsx("div",{className:"mb-2 text-[11px] text-on-surface-variant",children:a("cam.mirror.hint")}),e.jsxs("div",{className:"mb-2 flex flex-wrap gap-1 text-xs",children:[e.jsx("button",{type:"button",onClick:()=>void ta(!Ce,Fe),className:`rounded border px-2 py-1 ${Ce?"border-primary text-primary":"border-outline-variant/30"}`,children:a("cam.mirror.horizontal")}),e.jsx("button",{type:"button",onClick:()=>void ta(Ce,!Fe),className:`rounded border px-2 py-1 ${Fe?"border-primary text-primary":"border-outline-variant/30"}`,children:a("cam.mirror.vertical")})]}),e.jsxs("div",{className:"flex flex-wrap gap-2 text-xs",children:[e.jsxs("button",{type:"button",onClick:()=>void mr(),className:"rounded border border-outline-variant/40 px-2 py-1",children:[e.jsx(Or,{className:"mr-1 inline h-3.5 w-3.5"}),a("cam.controls.backup")]}),e.jsxs("button",{type:"button",onClick:()=>void xr(),className:"rounded border border-outline-variant/40 px-2 py-1",children:[e.jsx(qr,{className:"mr-1 inline h-3.5 w-3.5"}),a("cam.controls.restore")]}),e.jsx("button",{type:"button",onClick:()=>void ur(),className:"rounded border border-outline-variant/40 px-2 py-1",children:a("cam.controls.reset")})]})]}),e.jsxs("section",{className:"rounded-xl border border-outline-variant/20 bg-surface-container p-4 text-xs",children:[e.jsx("h2",{className:"mb-3 text-sm font-semibold uppercase tracking-wider",children:a("cam.quick.title")}),e.jsxs("div",{className:"flex flex-wrap items-center gap-2",children:[e.jsx("span",{className:"text-[11px] text-on-surface-variant",children:a("cam.quick.nightHint")}),e.jsxs("button",{type:"button",onClick:()=>void aa(!0),className:`rounded border px-2 py-1 ${sa?"border-primary/70 text-primary":"border-outline-variant/40"}`,children:[e.jsx(Wr,{className:"mr-1 inline h-3.5 w-3.5"}),a("cam.controls.nightOn")]}),e.jsxs("button",{type:"button",onClick:()=>void aa(!1),className:`rounded border px-2 py-1 ${sa?"border-error/60 text-error":"border-outline-variant/40 text-on-surface-variant"}`,children:[e.jsx(Kr,{className:"mr-1 inline h-3.5 w-3.5"}),a("cam.controls.nightOff")]})]})]}),e.jsxs("section",{className:"rounded-xl border border-outline-variant/20 bg-surface-container p-4 text-xs",children:[e.jsx("h2",{className:"mb-3 text-sm font-semibold uppercase tracking-wider",children:a("cam.presets.title")}),e.jsxs("div",{className:"grid grid-cols-1 gap-2",children:[e.jsx("input",{value:st,onChange:t=>Xt(t.target.value),placeholder:a("cam.presets.name"),className:"rounded border border-outline-variant/30 bg-surface-container-low px-2 py-1.5"}),e.jsx("input",{value:Jt,onChange:t=>Kt(t.target.value),placeholder:a("cam.presets.desc"),className:"rounded border border-outline-variant/30 bg-surface-container-low px-2 py-1.5"})]}),e.jsx("div",{className:"mt-2",children:e.jsx("button",{type:"button",disabled:ot||!st.trim(),onClick:()=>void pr(),className:"rounded border border-outline-variant/40 px-2 py-1 disabled:opacity-50",children:a("cam.presets.save")})}),e.jsxs("div",{className:"mt-3 max-h-48 space-y-2 overflow-auto",children:[Yt.length===0&&e.jsx("div",{className:"text-on-surface-variant",children:a("cam.presets.empty")}),Yt.map(t=>e.jsxs("div",{className:"rounded border border-outline-variant/20 p-2",children:[e.jsx("div",{className:"font-semibold",children:t.name}),e.jsx("div",{className:"text-on-surface-variant",children:t.description||a("cam.presets.noDesc")}),e.jsxs("div",{className:"mt-1 text-on-surface-variant",children:[a("cam.controls.exposure"),": ",Nt(t.exposure_us)," | ",a("cam.controls.gain"),": ",t.analogue_gain]}),e.jsxs("div",{className:"mt-2 flex gap-2",children:[e.jsx("button",{type:"button",disabled:ot,onClick:()=>void hr(t.name),className:"rounded border border-outline-variant/40 px-2 py-1 disabled:opacity-50",children:a("cam.presets.apply")}),e.jsx("button",{type:"button",disabled:ot,onClick:()=>void fr(t.name),className:"rounded border border-outline-variant/40 px-2 py-1 disabled:opacity-50",children:a("cam.presets.delete")})]})]},t.name))]})]})]}),e.jsxs("section",{className:"order-1 col-span-12 grid grid-cols-12 items-start gap-4 2xl:order-2 2xl:col-span-10",children:[e.jsxs("div",{className:"col-span-12 space-y-4 2xl:col-span-9",children:[e.jsxs("section",{className:"self-start rounded-xl border border-outline-variant/20 bg-surface-container p-4",children:[e.jsxs("div",{className:"mb-2 grid grid-cols-1 gap-2 text-xs sm:grid-cols-3",children:[e.jsxs("div",{className:"rounded border border-outline-variant/20 bg-surface-container-low px-2 py-1",children:["CPU: ",e.jsx("span",{className:"font-mono",children:(p==null?void 0:p.cpu_usage)==null?"—":`${Number(p.cpu_usage).toFixed(1)}%`})]}),e.jsxs("div",{className:"rounded border border-outline-variant/20 bg-surface-container-low px-2 py-1",children:["MEM: ",e.jsx("span",{className:"font-mono",children:(p==null?void 0:p.memory_usage)==null?"—":`${Number(p.memory_usage).toFixed(1)}%`})]}),e.jsxs("div",{className:"rounded border border-outline-variant/20 bg-surface-container-low px-2 py-1",children:["TEMP: ",e.jsx("span",{className:"font-mono",children:(p==null?void 0:p.temperature)==null?"—":`${Number(p.temperature).toFixed(1)}°C`})]})]}),e.jsxs("div",{className:"mb-2 flex flex-wrap items-start justify-between gap-2",children:[e.jsxs("div",{children:[e.jsx("h2",{className:"text-sm font-semibold uppercase tracking-wider",children:a("cam.preview.title")}),e.jsx("p",{className:"mt-1 max-w-2xl text-[11px] leading-snug text-on-surface-variant",children:a("cam.hint.mjpegSingleStream")})]}),e.jsxs("div",{className:"flex shrink-0 flex-wrap items-center justify-end gap-2",children:[e.jsxs("button",{type:"button",disabled:ae||I||!!(r!=null&&r.recording),onClick:()=>_?void ht():void ea(),className:`rounded-lg px-2.5 py-1.5 text-xs font-medium disabled:opacity-50 ${_?"border border-primary/50 text-primary":"bg-primary-container text-on-primary-container"}`,children:[e.jsx(wt,{className:"mr-1 inline h-3.5 w-3.5"}),a(ae?"cam.focus.starting":_?"cam.focus.finish":"cam.focus.start")]}),e.jsxs("span",{className:"font-mono text-xs text-on-surface-variant",children:[a("cam.preview.state"),": ",a(m?"cam.state.streaming":"cam.state.idle")]})]})]}),we&&!m&&e.jsxs("div",{className:"mb-2 rounded-lg border border-error/35 bg-error-container/15 px-3 py-2 text-left",role:"alert",children:[e.jsx("p",{className:"text-sm font-medium text-error",children:we}),_t?e.jsx("p",{className:"mt-1 max-w-2xl text-[11px] leading-snug text-on-surface-variant",children:a("cam.err.streamBusyHint")}):e.jsx("p",{className:"mt-1 max-w-2xl text-[11px] leading-snug text-on-surface-variant",children:a("cam.err.streamProbeDetail")})]}),e.jsxs("div",{className:`relative aspect-video overflow-hidden rounded border border-outline-variant/20 bg-black ${_?"cursor-crosshair":""}`,onClick:tr,children:[m&&we&&e.jsxs("div",{className:"pointer-events-none absolute inset-0 z-20 flex flex-col items-center justify-center gap-2 bg-black/80 px-4 text-center",role:"alert",children:[e.jsx("p",{className:"text-sm font-medium text-error",children:we}),_t?e.jsx("p",{className:"max-w-md text-[11px] leading-snug text-on-surface-variant",children:a("cam.err.streamBusyHint")}):e.jsx("p",{className:"max-w-md text-[11px] leading-snug text-on-surface-variant",children:a("cam.err.streamProbeDetail")})]}),m?e.jsx("img",{ref:P,alt:"camera-preview",className:"h-full w-full object-contain",src:wr,onLoad:()=>{Q(null),ee(!1),jr()},onError:()=>{var n;if(ge(),!Y.current)return;const t=(n=P.current)==null?void 0:n.src;(async()=>{if(!Y.current||!t)return;if(await La()==="busy"){Q(a("cam.err.streamBusy")),ee(!0);return}Ie.current=window.setTimeout(()=>{Y.current&&K(Date.now())},400)})()}}):e.jsxs("div",{className:"flex h-full w-full flex-col items-center justify-center gap-3 text-center text-on-surface-variant",children:[e.jsx(Aa,{className:"h-10 w-10 text-primary/80"}),e.jsx("div",{className:"text-sm",children:a("cam.preview.emptyTitle")}),e.jsx("div",{className:"text-xs",children:a("cam.preview.emptyDesc")}),e.jsxs("button",{type:"button",disabled:!De,onClick:()=>void pt(),className:`rounded px-3 py-1.5 text-xs disabled:opacity-50 ${De?"bg-primary-container text-on-primary-container":"border border-outline-variant/40 text-on-surface-variant"}`,children:[e.jsx(Da,{className:"mr-1 inline h-3.5 w-3.5"}),a(I?"cam.btn.starting":"cam.btn.start")]})]}),m&&_&&(s!=null&&s.frame)?e.jsxs("svg",{className:"pointer-events-none absolute inset-0 z-10 h-full w-full",viewBox:`0 0 ${s.frame.width} ${s.frame.height}`,preserveAspectRatio:"xMidYMid meet","aria-hidden":"true",children:[s.stars.map((t,n)=>{const c=s.selected_star&&Math.abs(s.selected_star.x-t.x)<1&&Math.abs(s.selected_star.y-t.y)<1;return e.jsx("circle",{cx:t.x,cy:t.y,r:c?16:10,fill:"none",stroke:c?"#facc15":"#38bdf8",strokeWidth:c?3:2,vectorEffect:"non-scaling-stroke"},`${s.frame_id}-${n}`)}),j?e.jsxs("g",{transform:`translate(${j.x*s.frame.width} ${j.y*s.frame.height})`,stroke:"#facc15",strokeWidth:"2",vectorEffect:"non-scaling-stroke",children:[e.jsx("line",{x1:"-12",y1:"0",x2:"12",y2:"0"}),e.jsx("line",{x1:"0",y1:"-12",x2:"0",y2:"12"})]}):null]}):null,(r==null?void 0:r.recording)&&e.jsxs("div",{className:"absolute right-3 top-3 flex items-center gap-2 rounded bg-black/60 px-2 py-1 text-xs text-error",children:[e.jsx(Oa,{className:"h-3.5 w-3.5 fill-current"})," ",a("cam.state.rec"),e.jsx("span",{className:"font-mono",children:`${Math.floor(zt/60).toString().padStart(2,"0")}:${(zt%60).toString().padStart(2,"0")}`})]}),e.jsx("div",{className:"absolute left-3 top-3",children:e.jsx("button",{type:"button",onClick:()=>Ht(t=>!t),className:"rounded border border-outline-variant/40 bg-black/70 px-2 py-1 text-xs text-white",children:a(Ot?"cam.hist.expand":"cam.hist.collapse")})}),!Ot&&e.jsxs("div",{className:"absolute left-3 top-12 w-[360px] max-w-[calc(100%-1.5rem)] rounded border border-outline-variant/30 bg-black/70 p-2 text-white",children:[e.jsxs("div",{className:"mb-2 flex flex-wrap items-center gap-3 text-[11px]",children:[e.jsxs("label",{className:"inline-flex items-center gap-1",children:[e.jsx("input",{type:"checkbox",checked:It,onChange:t=>qa(t.target.checked)})," ",a("cam.hist.enabled")]}),e.jsxs("label",{className:"inline-flex items-center gap-1",children:[e.jsx("input",{type:"checkbox",checked:Qe,onChange:t=>Ua(t.target.checked)})," RGB"]}),e.jsxs("label",{className:"inline-flex items-center gap-1",children:[e.jsx("input",{type:"checkbox",checked:et,onChange:t=>Wa(t.target.checked)})," ",a("cam.hist.luminance")]}),e.jsxs("label",{className:"inline-flex items-center gap-1",children:[e.jsx("input",{type:"checkbox",checked:At,onChange:t=>Va(t.target.checked)})," ",a("cam.hist.over")]})]}),e.jsx("canvas",{ref:Pe,className:"h-24 w-full rounded border border-white/20 bg-black/60"}),e.jsxs("div",{className:"mt-2 grid grid-cols-3 gap-2 text-[11px]",children:[e.jsxs("div",{children:["mean: ",e.jsx("span",{className:"font-mono",children:tt.mean.toFixed(1)})]}),e.jsxs("div",{children:["std: ",e.jsx("span",{className:"font-mono",children:tt.std.toFixed(1)})]}),e.jsxs("div",{children:["over: ",e.jsxs("span",{className:"font-mono",children:[tt.over.toFixed(2),"%"]})]})]})]})]}),e.jsxs("section",{id:"focus-calibration-panel",className:`mt-3 scroll-mt-20 rounded-xl border p-3 ${_?"border-primary/45 bg-primary-container/10":"border-outline-variant/20 bg-surface-container-low"}`,children:[e.jsxs("div",{className:"flex flex-wrap items-start justify-between gap-3",children:[e.jsxs("div",{className:"min-w-0",children:[e.jsxs("div",{className:"flex items-center gap-2",children:[e.jsx(wt,{className:"h-5 w-5 text-primary"}),e.jsx("h3",{className:"text-sm font-semibold",children:a("cam.focus.title")}),e.jsx("span",{className:`rounded-full px-2 py-0.5 text-[10px] font-medium ${_?"bg-primary-container text-on-primary-container":"bg-surface-container-high text-on-surface-variant"}`,children:a(_?"cam.focus.active":"cam.focus.idle")})]}),e.jsx("p",{className:"mt-1 max-w-3xl text-[11px] leading-relaxed text-on-surface-variant",children:a("cam.focus.intro")}),_?e.jsx("p",{className:"mt-1 text-[10px] text-primary",children:a("cam.focus.sessionLocked")}):null]}),e.jsxs("div",{className:"flex flex-wrap gap-2",children:[_?e.jsx("button",{type:"button",onClick:()=>void ht(),className:"rounded-lg border border-error/50 px-3 py-2 text-xs text-error",children:a("cam.focus.finish")}):e.jsxs("button",{type:"button",disabled:ae||I||!!(r!=null&&r.recording),onClick:()=>void ea(),className:"rounded-lg bg-primary-container px-3 py-2 text-xs font-medium text-on-primary-container disabled:opacity-50",children:[e.jsx(wt,{className:"mr-1 inline h-4 w-4"}),a(ae?"cam.focus.starting":"cam.focus.start")]}),e.jsxs("button",{type:"button",disabled:!_,onClick:er,className:"rounded-lg border border-outline-variant/40 px-3 py-2 text-xs disabled:opacity-40",children:[e.jsx(zr,{className:"mr-1 inline h-3.5 w-3.5"}),a("cam.focus.resetBest")]}),e.jsx("button",{type:"button",disabled:!_||j==null,onClick:ar,className:"rounded-lg border border-outline-variant/40 px-3 py-2 text-xs disabled:opacity-40",children:a("cam.focus.autoSelect")})]})]}),_?e.jsxs("div",{className:"mt-3 grid gap-3 xl:grid-cols-[minmax(0,1.25fr)_minmax(280px,0.75fr)]",children:[e.jsxs("div",{className:"grid gap-3 sm:grid-cols-2 lg:grid-cols-4",children:[e.jsxs("div",{className:"rounded-lg border border-primary/30 bg-black/20 p-3 sm:col-span-2",children:[e.jsx("div",{className:"text-[10px] uppercase tracking-wider text-on-surface-variant",children:a(j?"cam.focus.currentHfd.single":"cam.focus.currentHfd.multi")}),e.jsxs("div",{className:"mt-1 flex items-end gap-2",children:[e.jsx("span",{className:"font-mono text-3xl font-semibold text-primary",children:Ge==null?"—":Ge.toFixed(2)}),e.jsxs("span",{className:"pb-1 text-xs text-on-surface-variant",children:["px · ",a("cam.focus.lowerBetter")]})]}),e.jsx("p",{className:"mt-2 rounded-md border border-outline-variant/20 bg-surface-container-low/70 px-2 py-1.5 text-xs font-medium",children:a(Le)})]}),e.jsxs("div",{className:"rounded-lg border border-outline-variant/20 p-3",children:[e.jsx("div",{className:"text-[10px] uppercase tracking-wider text-on-surface-variant",children:a("cam.focus.bestHfd")}),e.jsx("div",{className:"mt-1 font-mono text-xl",children:re==null?"—":`${re.toFixed(2)} px`}),e.jsx("div",{className:"mt-1 text-[11px] text-on-surface-variant",children:ze==null?"—":`+${Math.max(0,ze).toFixed(1)}%`})]}),e.jsxs("div",{className:"rounded-lg border border-outline-variant/20 p-3",children:[e.jsx("div",{className:"text-[10px] uppercase tracking-wider text-on-surface-variant",children:a("cam.focus.stars")}),e.jsxs("div",{className:"mt-1 font-mono text-xl",children:[(s==null?void 0:s.stars_used)??0," / ",(s==null?void 0:s.stars_measured)??0," / ",(s==null?void 0:s.stars_detected)??0]}),e.jsx("div",{className:"mt-1 text-[11px] text-on-surface-variant",children:a(Fr)})]}),e.jsxs("div",{className:"rounded-lg border border-outline-variant/20 p-3",children:[e.jsx("div",{className:"text-[10px] uppercase tracking-wider text-on-surface-variant",children:a("cam.focus.fwhm")}),e.jsx("div",{className:"mt-1 font-mono text-lg",children:ca==null?"—":`${ca.toFixed(2)} px`})]}),e.jsxs("div",{className:"rounded-lg border border-outline-variant/20 p-3",children:[e.jsx("div",{className:"text-[10px] uppercase tracking-wider text-on-surface-variant",children:a("cam.focus.concentration")}),e.jsx("div",{className:"mt-1 font-mono text-lg",children:la==null?"—":`${(la*100).toFixed(0)}%`})]}),e.jsxs("div",{className:"rounded-lg border border-outline-variant/20 p-3",children:[e.jsx("div",{className:"text-[10px] uppercase tracking-wider text-on-surface-variant",children:a("cam.focus.selectedStar")}),e.jsx("div",{className:"mt-1 font-mono text-lg",children:s!=null&&s.selected_star?`HFD ${s.selected_star.hfd_px.toFixed(2)}`:"—"}),e.jsx("div",{className:"mt-1 text-[11px] text-on-surface-variant",children:a(j?"cam.focus.manualTarget":"cam.focus.autoTarget")})]}),e.jsxs("div",{className:"rounded-lg border border-outline-variant/20 p-3",children:[e.jsx("div",{className:"text-[10px] uppercase tracking-wider text-on-surface-variant",children:a("cam.focus.stability")}),e.jsx("div",{className:"mt-1 font-mono text-lg",children:s!=null&&s.aggregate?`±${s.aggregate.hfd_mad_px.toFixed(2)} px`:"—"})]})]}),e.jsxs("div",{className:"space-y-2",children:[e.jsxs("div",{className:"flex items-center justify-between text-[11px] text-on-surface-variant",children:[e.jsx("span",{children:a("cam.focus.history")}),e.jsx("span",{children:a("cam.focus.clickHint")})]}),e.jsx(tn,{samples:ke,emptyLabel:a("cam.focus.historyEmpty")}),Gt?e.jsx("p",{className:"rounded-lg border border-error/35 bg-error-container/15 px-3 py-2 text-xs text-error",role:"alert",children:Gt}):null,s!=null&&s.warnings.includes("saturated_stars_rejected")?e.jsx("p",{className:"rounded-lg border border-tertiary/35 bg-tertiary-container/15 px-3 py-2 text-[11px] text-tertiary",children:a("cam.focus.warning.saturated")}):null,s!=null&&s.warnings.includes("target_star_not_found")?e.jsx("p",{className:"rounded-lg border border-tertiary/35 bg-tertiary-container/15 px-3 py-2 text-[11px] text-tertiary",children:a("cam.focus.warning.targetNotFound")}):null,s!=null&&s.warnings.includes("undersampled_stars")?e.jsx("p",{className:"rounded-lg border border-primary/30 bg-primary-container/10 px-3 py-2 text-[11px] text-on-surface",children:a("cam.focus.warning.undersampled")}):null,s!=null&&s.warnings.includes("target_may_be_hot_pixel")?e.jsx("p",{className:"rounded-lg border border-tertiary/35 bg-tertiary-container/15 px-3 py-2 text-[11px] text-tertiary",children:a("cam.focus.warning.hotPixel")}):null,s!=null&&s.detection?e.jsx("p",{className:"text-[10px] leading-relaxed text-on-surface-variant",children:a("cam.focus.detectionSummary",{measured:s.stars_measured,detected:s.stars_detected,rejected:Object.values(s.detection.quality_rejections).reduce((t,n)=>t+n,0)})}):null]})]}):e.jsxs("div",{className:"mt-3 grid gap-2 text-[11px] text-on-surface-variant sm:grid-cols-3",children:[e.jsxs("div",{className:"rounded-lg border border-outline-variant/20 px-3 py-2",children:["1. ",a("cam.focus.step.start")]}),e.jsxs("div",{className:"rounded-lg border border-outline-variant/20 px-3 py-2",children:["2. ",a("cam.focus.step.adjust")]}),e.jsxs("div",{className:"rounded-lg border border-outline-variant/20 px-3 py-2",children:["3. ",a("cam.focus.step.lock")]})]})]}),e.jsxs("div",{className:"mt-3 grid grid-cols-1 gap-2 text-xs md:grid-cols-4",children:[e.jsxs("div",{className:"rounded border border-outline-variant/20 bg-surface-container-low px-2 py-1.5 text-left",children:[e.jsxs("span",{className:"text-on-surface-variant",children:[a("cam.stats.frameFps"),": "]}),e.jsx("span",{className:"font-mono text-on-surface",children:Number((i==null?void 0:i.actual_preview_fps)??Ja).toFixed(2)}),e.jsx("p",{className:"mt-0.5 text-[10px] leading-tight text-on-surface-variant/90",children:a("cam.stats.fpsMeasureNote")})]}),e.jsxs("div",{className:"rounded border border-outline-variant/20 bg-surface-container-low px-2 py-1.5 text-left",children:[e.jsxs("span",{className:"text-on-surface-variant",children:[a("cam.stats.targetFps"),": "]}),e.jsx("span",{className:"font-mono text-on-surface",children:Number(((ga=r==null?void 0:r.info)==null?void 0:ga.fps)??0).toFixed(2)})]}),e.jsxs("div",{className:"rounded border border-outline-variant/20 bg-surface-container-low px-2 py-1.5 text-left",children:[e.jsxs("span",{className:"text-on-surface-variant",children:[a("cam.stats.streamPacingFps"),": "]}),e.jsx("span",{className:"font-mono text-on-surface",children:(i==null?void 0:i.preview_target_fps)??(i==null?void 0:i.target_preview_fps)??"—"}),e.jsx("p",{className:"mt-0.5 text-[10px] leading-tight text-on-surface-variant/90",children:a("cam.stats.streamPacingHint")})]}),e.jsxs("div",{className:"rounded border border-outline-variant/20 bg-surface-container-low px-2 py-1.5 text-left",children:[e.jsxs("span",{className:"text-on-surface-variant",children:[a("cam.stats.captureFps"),": "]}),e.jsx("span",{className:"font-mono text-on-surface",children:Number((i==null?void 0:i.actual_capture_fps)??0).toFixed(2)})]}),e.jsxs("div",{className:"rounded border border-outline-variant/20 bg-surface-container-low px-2 py-1.5 text-left",children:[e.jsxs("span",{className:"text-on-surface-variant",children:[a("cam.controls.exposure"),": "]}),e.jsx("span",{className:"font-mono text-on-surface",children:(i==null?void 0:i.actual_exposure_us)!=null||((ya=r==null?void 0:r.info)==null?void 0:ya.actual_exposure_us)!=null?Nt((i==null?void 0:i.actual_exposure_us)??((va=r==null?void 0:r.info)==null?void 0:va.actual_exposure_us)??0):"—"}),e.jsx("p",{className:"mt-0.5 text-[10px] leading-tight text-on-surface-variant/90",children:(ja=r==null?void 0:r.info)!=null&&ja.auto_exposure?a("cam.controls.auto"):a("cam.controls.manual")})]}),e.jsxs("div",{className:"rounded border border-outline-variant/20 bg-surface-container-low px-2 py-1.5 text-left",children:[e.jsxs("span",{className:"text-on-surface-variant",children:[a("cam.stats.encodeMs"),": "]}),e.jsxs("span",{className:"font-mono text-on-surface",children:[Number((i==null?void 0:i.jpeg_average_encode_ms)??0).toFixed(1)," ms"]})]}),e.jsxs("div",{className:"rounded border border-outline-variant/20 bg-surface-container-low px-2 py-1.5 text-left",children:[e.jsxs("span",{className:"text-on-surface-variant",children:[a("cam.stats.encoder"),": "]}),e.jsx("span",{className:"font-mono text-on-surface",children:String((i==null?void 0:i.preview_encoder)??"—")}),e.jsx("p",{className:"mt-0.5 text-[10px] leading-tight text-on-surface-variant/90",children:`${(i==null?void 0:i.jpeg_source_format)??"RGB888"} / fail ${(i==null?void 0:i.jpeg_encode_failures)??0}`})]}),e.jsxs("div",{className:"rounded border border-outline-variant/20 bg-surface-container-low px-2 py-1.5 text-left",children:[e.jsxs("span",{className:"text-on-surface-variant",children:[a("cam.stats.consumers"),": "]}),e.jsx("span",{className:"font-mono text-on-surface",children:`${(i==null?void 0:i.preview_consumers)??0}/${(i==null?void 0:i.analysis_consumers)??0}/${(i==null?void 0:i.recording_consumers)??0}`})]}),e.jsxs("div",{className:"rounded border border-outline-variant/20 bg-surface-container-low px-2 py-1.5 text-left",children:[e.jsxs("span",{className:"text-on-surface-variant",children:[a("cam.stats.cameraMemory"),": "]}),e.jsx("span",{className:"font-mono text-on-surface",children:`${Math.round(Number((i==null?void 0:i.process_rss_kb)??0)/1024)} / ${Math.round(Number((i==null?void 0:i.process_swap_kb)??0)/1024)} MB`})]}),(i==null?void 0:i.throttle_reason)==="auto_exposure_long"&&e.jsx("div",{className:"rounded border border-tertiary/40 bg-tertiary-container/20 px-2 py-1.5 text-left text-tertiary",children:a("cam.stats.longExposureThrottle",{exposure:ue(i.actual_exposure_us,0).toFixed(4)})}),e.jsxs("div",{className:"rounded border border-outline-variant/20 bg-surface-container-low px-2 py-1.5 text-left",children:[e.jsxs("span",{className:"text-on-surface-variant",children:[a("cam.stats.uptime"),": "]}),e.jsx("span",{className:"font-mono text-on-surface",children:Ae.current!=null?`${Math.max(0,Math.round((performance.now()-Ae.current)/1e3))}s`:"0s"})]}),e.jsxs("div",{className:"rounded border border-outline-variant/20 bg-surface-container-low px-2 py-1.5 text-left",children:[e.jsxs("span",{className:"text-on-surface-variant",children:[a("cam.system.sensor"),": "]}),e.jsx("span",{className:"font-mono",children:String(((wa=r==null?void 0:r.info)==null?void 0:wa.sensor)??"—")})]}),e.jsxs("div",{className:"rounded border border-outline-variant/20 bg-surface-container-low px-2 py-1.5 text-left",children:[e.jsxs("span",{className:"text-on-surface-variant",children:[a("cam.stats.driver"),": "]}),e.jsx("span",{className:"font-mono",children:String(((Na=r==null?void 0:r.info)==null?void 0:Na.driver)??(i==null?void 0:i.camera_driver)??"—")}),e.jsx("p",{className:"mt-0.5 text-[10px] leading-tight text-on-surface-variant/90",children:`${((Sa=r==null?void 0:r.info)==null?void 0:Sa.backend)??(i==null?void 0:i.camera_backend)??"—"} · lores ${(_a=r==null?void 0:r.info)!=null&&_a.lores_available||i!=null&&i.lores_available?"on":"off"}`}),e.jsxs("p",{className:"mt-0.5 break-all text-[10px] leading-tight text-on-surface-variant/90",children:[`tuning ${((ka=r==null?void 0:r.info)==null?void 0:ka.tuning_source)??"—"} · frame ${((Ca=r==null?void 0:r.info)==null?void 0:Ca.frame_duration_control)??"—"}`,(Fa=r==null?void 0:r.info)!=null&&Fa.tuning_error||(Ea=r==null?void 0:r.info)!=null&&Ea.frame_duration_control_error?` · fallback ${r.info.tuning_error??r.info.frame_duration_control_error}`:""]})]}),e.jsxs("div",{className:"rounded border border-outline-variant/20 bg-surface-container-low px-2 py-1.5 text-left",children:[e.jsxs("span",{className:"text-on-surface-variant",children:[a("cam.stats.metadata"),": "]}),e.jsx("span",{className:"font-mono",children:((Ra=r==null?void 0:r.info)==null?void 0:Ra.lux)!=null?`${Number(r.info.lux).toFixed(1)} lux`:"—"}),e.jsx("p",{className:"mt-0.5 text-[10px] leading-tight text-on-surface-variant/90",children:(($a=r==null?void 0:r.info)==null?void 0:$a.colour_temperature)!=null?`${Math.round(Number(r.info.colour_temperature))}K`:"—"})]}),e.jsxs("div",{className:"rounded border border-outline-variant/20 bg-surface-container-low px-2 py-1.5 text-left",children:[e.jsxs("span",{className:"text-on-surface-variant",children:[a("cam.controls.resolution"),": "]}),e.jsx("span",{className:"font-mono",children:`${((Ba=r==null?void 0:r.info)==null?void 0:Ba.width)??"—"}x${((Pa=r==null?void 0:r.info)==null?void 0:Pa.height)??"—"}`})]}),e.jsxs("div",{className:"rounded border border-outline-variant/20 bg-surface-container-low px-2 py-1.5 text-left",children:[e.jsxs("span",{className:"text-on-surface-variant",children:[a("cam.preview.mode"),": "]}),e.jsx("span",{className:"font-mono",children:(Ma=r==null?void 0:r.info)!=null&&Ma.auto_exposure?a("cam.controls.auto"):a("cam.controls.manual")})]})]}),e.jsxs("div",{className:"mt-3 flex flex-wrap gap-2",children:[e.jsxs("button",{type:"button",disabled:!De,onClick:()=>void pt(),className:`rounded px-3 py-2 text-sm disabled:opacity-50 ${De?"bg-primary-container text-on-primary-container":"border border-outline-variant/40 text-on-surface-variant"}`,children:[e.jsx(Da,{className:"inline h-4 w-4"})," ",a(I?"cam.btn.starting":"cam.btn.start")]}),e.jsxs("button",{type:"button",disabled:!ia,onClick:()=>void rr(),className:`rounded border px-3 py-2 text-sm disabled:opacity-50 ${ia?"border-error/60 text-error hover:bg-error/10":"border-outline-variant/40 text-on-surface-variant"}`,children:[e.jsx(Jr,{className:"inline h-4 w-4"})," ",a(I?"cam.btn.stopping":"cam.btn.stop")]}),e.jsxs("button",{type:"button",disabled:!kr,onClick:()=>void nr(),className:"rounded border border-outline-variant/40 px-3 py-2 text-sm disabled:opacity-50",children:[e.jsx(Aa,{className:"inline h-4 w-4"})," ",a(Ct?"cam.btn.capturing":"cam.btn.capture")]}),e.jsxs("button",{type:"button",disabled:!Cr,onClick:()=>void sr(),className:`rounded border px-3 py-2 text-sm disabled:opacity-50 ${r!=null&&r.recording?"border-error/60 bg-error/10 text-error":"border-outline-variant/40"}`,children:[e.jsx(Oa,{className:"inline h-4 w-4"})," ",Ye?a("cam.btn.recordBusy"):r!=null&&r.recording?a("cam.btn.recordStop"):a("cam.btn.recordStart")]})]})]}),e.jsxs("section",{className:"rounded-xl border border-outline-variant/20 bg-surface-container p-4 text-xs",children:[e.jsx("h2",{className:"mb-3 text-sm font-semibold uppercase tracking-wider",children:a("cam.files.title")}),Re&&e.jsxs("div",{className:`mb-3 rounded border px-2 py-1.5 ${Re.is_persistent===!1?"border-amber-400/40 bg-amber-400/10 text-amber-200":"border-primary/30 bg-primary/5 text-on-surface-variant"}`,children:[e.jsx("div",{children:Re.is_persistent===!1?a("cam.files.storageTemporary"):a("cam.files.storagePersistent")}),e.jsx("div",{className:"mt-1 break-all font-mono text-[10px] opacity-80",children:Re.path})]}),e.jsxs("div",{className:"mb-2 flex flex-wrap gap-2",children:[e.jsx("button",{type:"button",disabled:it,onClick:()=>void ft(),className:"rounded border border-outline-variant/40 px-2 py-1 disabled:opacity-50",children:a("cam.files.refresh")}),e.jsxs("button",{type:"button",disabled:it||ne.length===0,onClick:br,className:"rounded border border-outline-variant/40 px-2 py-1 disabled:opacity-50",children:[e.jsx(Ha,{className:"mr-1 inline h-3.5 w-3.5"}),a("cam.files.exportAll")]})]}),Qa&&e.jsx("div",{className:"mb-2 text-on-surface-variant",children:a("cam.files.loadingInfo")}),C&&e.jsxs("div",{className:"mb-3 rounded border border-outline-variant/20 p-2",children:[e.jsxs("div",{className:"mb-2 flex items-center justify-between gap-2 font-semibold",children:[e.jsxs("div",{className:"flex min-w-0 items-center gap-1",children:[e.jsx(Lr,{className:"h-3.5 w-3.5 shrink-0"}),e.jsx("span",{className:"truncate",children:C.filename})]}),e.jsx("button",{type:"button",onClick:()=>bt(),className:"shrink-0 rounded border border-outline-variant/40 p-1 text-on-surface-variant hover:bg-surface-container","aria-label":a("cam.files.closeDetail"),children:e.jsx(Hr,{className:"h-3.5 w-3.5"})})]}),e.jsxs("div",{children:[a("cam.files.size"),": ",e.jsx("span",{className:"font-mono",children:za(C.size)})]}),e.jsxs("div",{children:[a("cam.files.type"),": ",e.jsx("span",{className:"font-mono",children:C.type})]}),e.jsxs("div",{children:[a("cam.files.modified"),": ",e.jsx("span",{className:"font-mono",children:new Date(C.modified).toLocaleString()})]}),C.exposure_us!=null&&e.jsxs("div",{children:[a("cam.controls.exposure"),": ",e.jsx("span",{className:"font-mono",children:Nt(C.exposure_us)})]}),C.analogue_gain!=null&&e.jsxs("div",{children:[a("cam.controls.gain"),": ",e.jsx("span",{className:"font-mono",children:C.analogue_gain})]}),C.resolution&&e.jsxs("div",{children:[a("cam.controls.resolution"),": ",e.jsx("span",{className:"font-mono",children:C.resolution})]})]}),e.jsxs("div",{className:"max-h-96 space-y-2 overflow-auto",children:[ne.length===0&&!it&&e.jsx("div",{className:"text-on-surface-variant",children:a("cam.files.empty")}),Er.map(t=>e.jsxs("div",{className:`rounded border p-2 ${$e===t.name?"border-primary/50 bg-primary/5":"border-outline-variant/20"}`,children:[e.jsxs("div",{className:"flex items-center justify-between gap-2",children:[e.jsxs("div",{className:"min-w-0",children:[e.jsx("div",{className:"truncate font-semibold",children:t.name}),e.jsxs("div",{className:"text-on-surface-variant",children:[za(t.size)," | ",new Date(t.modified).toLocaleString()]})]}),e.jsx("div",{className:"shrink-0 text-on-surface-variant",children:t.type})]}),e.jsxs("div",{className:"mt-2 flex gap-2",children:[e.jsxs("button",{type:"button",onClick:()=>yr(t.name),className:"rounded border border-outline-variant/40 px-2 py-1",children:[e.jsx(Ha,{className:"mr-1 inline h-3.5 w-3.5"}),a("cam.files.download")]}),e.jsxs("button",{type:"button",onClick:()=>void gr(t.name),className:`rounded border px-2 py-1 ${$e===t.name?"border-primary text-primary":"border-outline-variant/40"}`,children:[e.jsx(Ur,{className:"mr-1 inline h-3.5 w-3.5"}),a("cam.files.info")]}),e.jsxs("button",{type:"button",onClick:()=>void vr(t.name),className:"rounded border border-outline-variant/40 px-2 py-1",children:[e.jsx(Tr,{className:"mr-1 inline h-3.5 w-3.5"}),a("cam.files.delete")]})]})]},t.name))]}),e.jsxs("div",{className:"mt-3 flex items-center justify-between",children:[e.jsx("button",{type:"button",disabled:Ue<=1,onClick:()=>Be(t=>Math.max(1,t-1)),className:"rounded border border-outline-variant/40 px-2 py-1 disabled:opacity-50",children:a("cam.files.prev")}),e.jsx("div",{className:"text-on-surface-variant",children:a("cam.files.page",{current:Ue,total:qe})}),e.jsx("button",{type:"button",disabled:Ue>=qe,onClick:()=>Be(t=>Math.min(qe,t+1)),className:"rounded border border-outline-variant/40 px-2 py-1 disabled:opacity-50",children:a("cam.files.next")})]})]})]}),e.jsxs("section",{className:"col-span-12 grid grid-cols-12 gap-4 2xl:col-span-3 2xl:self-start",children:[e.jsxs("section",{className:"col-span-12 rounded-xl border border-outline-variant/20 bg-surface-container p-4",children:[e.jsx("h2",{className:"mb-3 text-sm font-semibold uppercase tracking-wider",children:a("cam.controls.title")}),e.jsxs("div",{className:"space-y-3 text-xs",children:[e.jsxs("div",{className:"grid grid-cols-2 gap-2",children:[e.jsxs("label",{className:"block",children:[a("cam.controls.sensorFps"),e.jsx("input",{value:Ze,onChange:t=>{Et(t.target.value),te(!0)},className:"mt-1 w-full rounded border border-outline-variant/30 bg-surface-container-low px-2 py-1.5"})]}),e.jsxs("label",{className:"block",children:[a("cam.controls.previewFps"),e.jsx("input",{value:Rt,onChange:t=>{$t(t.target.value),te(!0)},className:"mt-1 w-full rounded border border-outline-variant/30 bg-surface-container-low px-2 py-1.5"})]})]}),e.jsx("div",{className:"grid grid-cols-1 gap-2",children:e.jsxs("label",{className:"block",children:[a("cam.controls.sampling"),e.jsxs("select",{value:Mt,onChange:t=>{Tt(t.target.value),te(!0)},className:"mt-1 w-full rounded border border-outline-variant/30 bg-surface-container-low px-2 py-1.5",children:[e.jsx("option",{value:"supersample",children:"supersample"}),e.jsx("option",{value:"native",children:"native"}),e.jsx("option",{value:"crop",children:"crop"})]})]})}),e.jsxs("label",{className:"block",children:[a("cam.controls.resolution"),e.jsx("div",{className:"mt-1 grid grid-cols-2 gap-1",children:Zr.map(t=>e.jsx("button",{type:"button",onClick:()=>{Pt(t),te(!0)},className:`rounded border px-2 py-1 ${Bt===t?"border-primary text-primary":"border-outline-variant/30"}`,children:t},t))})]}),e.jsx("button",{type:"button",disabled:!pe,onClick:()=>void or(),className:"w-full rounded border border-outline-variant/40 px-2 py-1.5 disabled:opacity-50",children:a("cam.controls.applyRuntime")})]})]}),e.jsxs("section",{className:"col-span-12 rounded-xl border border-outline-variant/20 bg-surface-container p-4",children:[e.jsxs("div",{className:"mb-2 flex items-center gap-1 text-sm font-semibold uppercase tracking-wider",children:[e.jsx(Xr,{className:"h-3.5 w-3.5"})," ",a("cam.controls.core")]}),Ee&&e.jsx("div",{className:"mb-2 rounded border border-primary/40 bg-primary/10 px-2 py-1 text-[11px] text-primary",children:a("cam.controls.pendingChanges")}),He&&e.jsx("p",{className:"mb-2 text-[11px] text-on-surface-variant",children:a("cam.controls.lockedByAe")}),e.jsxs("div",{className:"grid grid-cols-2 gap-2 text-xs",children:[e.jsx(an,{label:a("cam.controls.exposure"),value:d.exposureSeconds,disabled:He,onChange:t=>{w(!0),S(n=>({...n,exposureSeconds:t}))}}),e.jsx(X,{label:a("cam.controls.gain"),value:d.gain,min:1,max:24,step:.1,disabled:He,onChange:t=>{w(!0),S(n=>({...n,gain:Number(t.toFixed(1))}))}}),e.jsx(X,{label:a("cam.controls.digitalGain"),value:d.digitalGain,min:1,max:8,step:.1,disabled:He||!na,onChange:t=>{w(!0),S(n=>({...n,digitalGain:Number(t.toFixed(1))}))}}),e.jsxs("label",{className:"block",children:[a("cam.controls.noiseReductionMode"),e.jsx("select",{value:d.noiseReductionMode,onChange:t=>{w(!0),S(n=>({...n,noiseReductionMode:t.target.value}))},className:"mt-1 w-full rounded border border-outline-variant/30 bg-surface-container-low px-2 py-1.5",children:_r.map(t=>e.jsx("option",{value:t,children:a(`cam.controls.nr.${t}`)},t))})]}),e.jsx(X,{label:a("cam.controls.contrast"),value:d.contrast,min:0,max:2,step:.1,onChange:t=>{w(!0),S(n=>({...n,contrast:Number(t.toFixed(1))}))}}),e.jsx(X,{label:a("cam.controls.brightness"),value:d.brightness,min:-1,max:1,step:.1,onChange:t=>{w(!0),S(n=>({...n,brightness:Number(t.toFixed(1))}))}}),e.jsx(X,{label:a("cam.controls.saturation"),value:d.saturation,min:0,max:2,step:.1,onChange:t=>{w(!0),S(n=>({...n,saturation:Number(t.toFixed(1))}))}}),e.jsx(X,{label:a("cam.controls.sharpness"),value:d.sharpness,min:0,max:2,step:.1,onChange:t=>{w(!0),S(n=>({...n,sharpness:Number(t.toFixed(1))}))}})]}),ra!=null&&e.jsx("p",{className:"mt-2 text-[11px] text-tertiary",children:a("cam.controls.manualExposureFpsLimit",{exposure:d.exposureSeconds.toFixed(4),fps:ra.toFixed(2)})}),!na&&e.jsxs("p",{className:"mt-2 text-[11px] text-on-surface-variant",children:[a("cam.controls.digitalGainReadOnly"),": ",e.jsx("span",{className:"font-mono",children:Number(((Ta=r==null?void 0:r.info)==null?void 0:Ta.actual_digital_gain)??d.digitalGain).toFixed(2)})]}),e.jsx("div",{className:"mt-2",children:e.jsx("button",{type:"button",disabled:!Ee,onClick:()=>void ir(),className:"w-full rounded border border-outline-variant/40 px-2 py-1.5 disabled:opacity-50",children:a("cam.controls.applySettings")})})]}),e.jsxs("section",{className:"col-span-12 rounded-xl border border-outline-variant/20 bg-surface-container p-4",children:[e.jsx("div",{className:"mb-2 text-sm font-semibold uppercase tracking-wider",children:a("cam.controls.mode")}),e.jsxs("div",{className:"grid grid-cols-2 gap-2 text-xs",children:[e.jsxs("label",{className:"block",children:[a("cam.controls.autoExposure"),e.jsxs("select",{value:d.autoExposure?"auto":"manual",onChange:t=>{S(n=>({...n,autoExposure:t.target.value==="auto"})),w(!0)},className:"mt-1 w-full rounded border border-outline-variant/30 bg-surface-container-low px-2 py-1.5",children:[e.jsx("option",{value:"auto",children:a("cam.controls.auto")}),e.jsx("option",{value:"manual",children:a("cam.controls.manual")})]})]}),e.jsxs("label",{className:"block",children:[a("cam.controls.colorMode"),e.jsxs("select",{value:d.colorMode,onChange:t=>{S(n=>({...n,colorMode:t.target.value})),w(!0)},className:"mt-1 w-full rounded border border-outline-variant/30 bg-surface-container-low px-2 py-1.5",children:[e.jsx("option",{value:"color",children:a("cam.controls.color")}),e.jsx("option",{value:"mono",children:a("cam.controls.mono")})]})]}),e.jsxs("label",{className:"block",children:[a("cam.controls.whiteBalance"),e.jsx("select",{value:d.whiteBalanceMode,onChange:t=>{S(n=>({...n,whiteBalanceMode:t.target.value})),w(!0)},className:"mt-1 w-full rounded border border-outline-variant/30 bg-surface-container-low px-2 py-1.5",children:Sr.map(t=>e.jsx("option",{value:t,children:a(`cam.controls.wb.${t}`)},t))})]}),e.jsxs("label",{className:"block",children:[a("cam.controls.aeFlicker"),e.jsxs("select",{value:d.aeFlickerMode,onChange:t=>{S(n=>({...n,aeFlickerMode:t.target.value})),w(!0)},className:"mt-1 w-full rounded border border-outline-variant/30 bg-surface-container-low px-2 py-1.5",children:[e.jsx("option",{value:"off",children:a("cam.controls.off")}),e.jsx("option",{value:"50hz",children:"50 Hz"}),e.jsx("option",{value:"60hz",children:"60 Hz"})]})]}),e.jsxs("label",{className:"block",children:[a("cam.controls.maxAeFrame"),e.jsx("input",{type:"number",min:.01,max:1,step:.01,value:d.autoExposureMaxSeconds,onChange:t=>{S(n=>({...n,autoExposureMaxSeconds:N(Number(t.target.value)||1,.01,1)})),w(!0)},className:"mt-1 w-full rounded border border-outline-variant/30 bg-surface-container-low px-2 py-1.5"})]}),e.jsx(X,{label:"R Gain",value:d.whiteBalanceGainR,min:.1,max:3,step:.1,disabled:!gt,onChange:t=>{S(n=>({...n,whiteBalanceGainR:Number(t.toFixed(1))})),w(!0)}}),e.jsx(X,{label:"B Gain",value:d.whiteBalanceGainB,min:.1,max:3,step:.1,disabled:!gt,onChange:t=>{S(n=>({...n,whiteBalanceGainB:Number(t.toFixed(1))})),w(!0)}})]}),!gt&&e.jsx("p",{className:"mt-2 text-[11px] text-on-surface-variant",children:a("cam.controls.lockedByWb")}),e.jsx("div",{className:"mt-2",children:e.jsx("button",{type:"button",onClick:()=>void cr(),className:"w-full rounded border border-outline-variant/40 px-2 py-1.5",children:a("cam.controls.applyMode")})})]})]})]})]}),(me||xe)&&e.jsxs("div",{className:"fixed bottom-4 right-4 z-40 max-w-md space-y-2 text-xs",children:[me&&e.jsx("div",{className:"rounded border border-error/40 bg-error-container/20 px-3 py-2 text-on-error-container",children:me}),xe&&e.jsx("div",{className:"rounded border border-primary/30 bg-primary/10 px-3 py-2 text-on-surface",children:xe})]})]})}Br.createRoot(document.getElementById("root")).render(e.jsx(Pr.StrictMode,{children:e.jsx(Ir,{children:e.jsx(Gr,{children:e.jsx(rn,{})})})})); diff --git a/web/static/analysis-lab/assets/http-CaEmWEuL.js b/web/static/analysis-lab/assets/http-Dln9HPhZ.js similarity index 97% rename from web/static/analysis-lab/assets/http-CaEmWEuL.js rename to web/static/analysis-lab/assets/http-Dln9HPhZ.js index 6de129b..67f73f6 100644 --- a/web/static/analysis-lab/assets/http-CaEmWEuL.js +++ b/web/static/analysis-lab/assets/http-Dln9HPhZ.js @@ -1,4 +1,4 @@ -import{c as u}from"./index-BoSUzpvv.js";import{r as o,j as f}from"./client-D1ZVDB-N.js";/** +import{c as u}from"./index-BwAjnQW0.js";import{r as o,j as f}from"./client-D1ZVDB-N.js";/** * @license lucide-react v0.460.0 - ISC * * This source code is licensed under the ISC license. diff --git a/web/static/analysis-lab/assets/index-6doD6GKE.css b/web/static/analysis-lab/assets/index-6doD6GKE.css new file mode 100644 index 0000000..3706f60 --- /dev/null +++ b/web/static/analysis-lab/assets/index-6doD6GKE.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}.pointer-events-auto{pointer-events:auto}.collapse{visibility:collapse}.static{position:static}.fixed{position:fixed}.absolute{position:absolute}.relative{position:relative}.sticky{position:sticky}.inset-0{top:0;right:0;bottom:0;left:0}.inset-x-0{left:0;right:0}.inset-y-0{top:0;bottom:0}.bottom-0{bottom:0}.bottom-2{bottom:.5rem}.bottom-4{bottom:1rem}.bottom-6{bottom:1.5rem}.left-0{left:0}.left-1\/2{left:50%}.left-2{left:.5rem}.left-3{left:.75rem}.left-\[10px\]{left:10px}.left-\[110px\]{left:110px}.right-0{right:0}.right-2{right:.5rem}.right-3{right:.75rem}.right-4{right:1rem}.top-0{top:0}.top-1{top:.25rem}.top-1\/2{top:50%}.top-12{top:3rem}.top-2{top:.5rem}.top-3{top:.75rem}.top-8{top:2rem}.top-\[18px\]{top:18px}.top-\[88px\]{top:88px}.top-\[94px\]{top:94px}.z-10{z-index:10}.z-20{z-index:20}.z-30{z-index:30}.z-40{z-index:40}.z-50{z-index:50}.z-\[100\]{z-index:100}.z-\[1\]{z-index:1}.order-1{order:1}.order-3{order:3}.col-span-12{grid-column:span 12 / span 12}.col-span-2{grid-column:span 2 / span 2}.mx-auto{margin-left:auto;margin-right:auto}.mb-0\.5{margin-bottom:.125rem}.mb-1{margin-bottom:.25rem}.mb-2{margin-bottom:.5rem}.mb-3{margin-bottom:.75rem}.mb-4{margin-bottom:1rem}.mb-6{margin-bottom:1.5rem}.mb-8{margin-bottom:2rem}.ml-1{margin-left:.25rem}.ml-2{margin-left:.5rem}.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-5{margin-top:1.25rem}.mt-6{margin-top:1.5rem}.mt-auto{margin-top:auto}.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-0{height:0px}.h-1\.5{height:.375rem}.h-10{height:2.5rem}.h-11{height:2.75rem}.h-12{height:3rem}.h-2{height:.5rem}.h-24{height:6rem}.h-3{height:.75rem}.h-3\.5{height:.875rem}.h-4{height:1rem}.h-5{height:1.25rem}.h-7{height:1.75rem}.h-8{height:2rem}.h-9{height:2.25rem}.h-\[100px\]{height:100px}.h-\[112px\]{height:112px}.h-\[152px\]{height:152px}.h-\[188px\]{height:188px}.h-\[460px\]{height:460px}.h-\[88px\]{height:88px}.h-\[calc\(100vh-7rem\)\]{height:calc(100vh - 7rem)}.h-dvh{height:100dvh}.h-full{height:100%}.h-px{height:1px}.max-h-24{max-height:6rem}.max-h-32{max-height:8rem}.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-72{max-height:18rem}.max-h-96{max-height:24rem}.max-h-\[320px\]{max-height:320px}.max-h-\[420px\]{max-height:420px}.max-h-\[70vh\]{max-height:70vh}.max-h-\[min\(22rem\,55vh\)\]{max-height:min(22rem,55vh)}.max-h-\[min\(36vh\,18rem\)\]{max-height:min(36vh,18rem)}.max-h-\[min\(50vh\,28rem\)\]{max-height:min(50vh,28rem)}.max-h-dvh{max-height:100dvh}.min-h-0{min-height:0px}.min-h-14{min-height:3.5rem}.min-h-\[120px\]{min-height:120px}.min-h-\[200px\]{min-height:200px}.min-h-\[220px\]{min-height:220px}.min-h-\[480px\]{min-height:480px}.min-h-screen{min-height:100vh}.w-0{width:0px}.w-0\.5{width:.125rem}.w-10{width:2.5rem}.w-11{width:2.75rem}.w-16{width:4rem}.w-20{width:5rem}.w-24{width:6rem}.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-\[100px\]{width:100px}.w-\[200px\]{width:200px}.w-\[220px\]{width:220px}.w-\[360px\]{width:360px}.w-full{width:100%}.w-px{width:1px}.min-w-0{min-width:0px}.min-w-\[1\.25rem\]{min-width:1.25rem}.min-w-\[12rem\]{min-width:12rem}.min-w-\[15rem\]{min-width:15rem}.min-w-\[16rem\]{min-width:16rem}.min-w-\[200px\]{min-width:200px}.min-w-\[280px\]{min-width:280px}.max-w-2xl{max-width:42rem}.max-w-3xl{max-width:48rem}.max-w-5xl{max-width:64rem}.max-w-6xl{max-width:72rem}.max-w-7xl{max-width:80rem}.max-w-\[1880px\]{max-width:1880px}.max-w-\[220px\]{max-width:220px}.max-w-\[calc\(100\%-1\.5rem\)\]{max-width:calc(100% - 1.5rem)}.max-w-full{max-width:100%}.max-w-md{max-width:28rem}.max-w-sm{max-width:24rem}.max-w-xl{max-width:36rem}.max-w-xs{max-width:20rem}.flex-1{flex:1 1 0%}.shrink-0{flex-shrink:0}.border-separate{border-collapse:separate}.border-spacing-y-2{--tw-border-spacing-y: .5rem;border-spacing:var(--tw-border-spacing-x) var(--tw-border-spacing-y)}.origin-top-left{transform-origin:top left}.-translate-x-full{--tw-translate-x: -100%;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))}.-translate-x-px{--tw-translate-x: -1px;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))}.-translate-y-1\/2{--tw-translate-y: -50%;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))}.translate-x-0{--tw-translate-x: 0px;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))}.translate-x-full{--tw-translate-x: 100%;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))}.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-crosshair{cursor:crosshair}.cursor-pointer{cursor:pointer}.select-none{-webkit-user-select:none;-moz-user-select:none;user-select:none}.resize{resize:both}.scroll-mt-20{scroll-margin-top:5rem}.list-inside{list-style-position:inside}.list-decimal{list-style-type:decimal}.list-disc{list-style-type:disc}.list-none{list-style-type:none}.grid-cols-1{grid-template-columns:repeat(1,minmax(0,1fr))}.grid-cols-12{grid-template-columns:repeat(12,minmax(0,1fr))}.grid-cols-2{grid-template-columns:repeat(2,minmax(0,1fr))}.grid-cols-3{grid-template-columns:repeat(3,minmax(0,1fr))}.flex-col{flex-direction:column}.flex-wrap{flex-wrap:wrap}.items-start{align-items:flex-start}.items-end{align-items:flex-end}.items-center{align-items:center}.items-baseline{align-items:baseline}.justify-end{justify-content:flex-end}.justify-center{justify-content:center}.justify-between{justify-content:space-between}.gap-0\.5{gap:.125rem}.gap-1{gap:.25rem}.gap-1\.5{gap:.375rem}.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))}.space-y-3>:not([hidden])~:not([hidden]){--tw-space-y-reverse: 0;margin-top:calc(.75rem * calc(1 - var(--tw-space-y-reverse)));margin-bottom:calc(.75rem * var(--tw-space-y-reverse))}.space-y-4>:not([hidden])~:not([hidden]){--tw-space-y-reverse: 0;margin-top:calc(1rem * calc(1 - var(--tw-space-y-reverse)));margin-bottom:calc(1rem * var(--tw-space-y-reverse))}.space-y-6>:not([hidden])~:not([hidden]){--tw-space-y-reverse: 0;margin-top:calc(1.5rem * calc(1 - var(--tw-space-y-reverse)));margin-bottom:calc(1.5rem * var(--tw-space-y-reverse))}.self-start{align-self:flex-start}.self-center{align-self:center}.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}.whitespace-nowrap{white-space:nowrap}.break-words{overflow-wrap:break-word}.break-all{word-break:break-all}.rounded{border-radius:.25rem}.rounded-2xl{border-radius:1rem}.rounded-full{border-radius:9999px}.rounded-lg{border-radius:.5rem}.rounded-md{border-radius:.375rem}.rounded-sm{border-radius:.125rem}.rounded-xl{border-radius:.75rem}.border{border-width:1px}.border-2{border-width:2px}.border-x-\[9px\]{border-left-width:9px;border-right-width:9px}.border-b{border-bottom-width:1px}.border-b-2{border-bottom-width:2px}.border-b-\[12px\]{border-bottom-width:12px}.border-l{border-left-width:1px}.border-r{border-right-width:1px}.border-r-2{border-right-width:2px}.border-t{border-top-width:1px}.border-dashed{border-style:dashed}.border-amber-400\/40{border-color:#fbbf2466}.border-amber-500\/40{border-color:#f59e0b66}.border-emerald-400\/40{border-color:#34d39966}.border-emerald-500\/20{border-color:#10b98133}.border-emerald-600\/35{border-color:#05966959}.border-error\/35{border-color:#ffb4ab59}.border-error\/40{border-color:#ffb4ab66}.border-error\/50{border-color:#ffb4ab80}.border-error\/60{border-color:#ffb4ab99}.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-primary\/30{border-color:#adc6ff4d}.border-primary\/40{border-color:#adc6ff66}.border-primary\/45{border-color:#adc6ff73}.border-primary\/50{border-color:#adc6ff80}.border-primary\/70{border-color:#adc6ffb3}.border-red-500\/40{border-color:#ef444466}.border-rose-400\/40{border-color:#fb718566}.border-sky-500\/30{border-color:#0ea5e94d}.border-teal-400\/75{border-color:#2dd4bfbf}.border-teal-500\/25{border-color:#14b8a640}.border-white\/20{border-color:#fff3}.border-white\/30{border-color:#ffffff4d}.border-white\/5{border-color:#ffffff0d}.border-x-transparent{border-left-color:transparent;border-right-color:transparent}.border-b-amber-400{--tw-border-opacity: 1;border-bottom-color:rgb(251 191 36 / var(--tw-border-opacity, 1))}.bg-amber-400{--tw-bg-opacity: 1;background-color:rgb(251 191 36 / var(--tw-bg-opacity, 1))}.bg-amber-400\/10{background-color:#fbbf241a}.bg-amber-400\/95{background-color:#fbbf24f2}.bg-amber-500\/10{background-color:#f59e0b1a}.bg-amber-900\/55{background-color:#78350f8c}.bg-background{--tw-bg-opacity: 1;background-color:rgb(16 19 26 / var(--tw-bg-opacity, 1))}.bg-black{--tw-bg-opacity: 1;background-color:rgb(0 0 0 / var(--tw-bg-opacity, 1))}.bg-black\/20{background-color:#0003}.bg-black\/25{background-color:#00000040}.bg-black\/35{background-color:#00000059}.bg-black\/40{background-color:#0006}.bg-black\/55{background-color:#0000008c}.bg-black\/60{background-color:#0009}.bg-black\/70{background-color:#000000b3}.bg-black\/80{background-color:#000c}.bg-emerald-800\/90{background-color:#065f46e6}.bg-error-container\/15{background-color:#93000a26}.bg-error-container\/20{background-color:#93000a33}.bg-error-container\/95{background-color:#93000af2}.bg-error\/10{background-color:#ffb4ab1a}.bg-error\/80{background-color:#ffb4abcc}.bg-neutral-950{--tw-bg-opacity: 1;background-color:rgb(10 10 10 / var(--tw-bg-opacity, 1))}.bg-neutral-950\/80{background-color:#0a0a0acc}.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\/10{background-color:#4c8eff1a}.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-primary\/5{background-color:#adc6ff0d}.bg-red-500\/10{background-color:#ef44441a}.bg-secondary{--tw-bg-opacity: 1;background-color:rgb(64 229 108 / var(--tw-bg-opacity, 1))}.bg-secondary\/80{background-color:#40e56ccc}.bg-sky-300\/90{background-color:#7dd3fce6}.bg-sky-400{--tw-bg-opacity: 1;background-color:rgb(56 189 248 / var(--tw-bg-opacity, 1))}.bg-sky-400\/95{background-color:#38bdf8f2}.bg-sky-700\/80{background-color:#0369a1cc}.bg-sky-900\/50{background-color:#0c4a6e80}.bg-slate-500\/85{background-color:#64748bd9}.bg-slate-500\/90{background-color:#64748be6}.bg-slate-600\/40{background-color:#47556966}.bg-slate-600\/50{background-color:#47556980}.bg-slate-600\/60{background-color:#47556999}.bg-slate-900\/80{background-color:#0f172acc}.bg-slate-950\/50{background-color:#02061780}.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-high\/40{background-color:#272a3166}.bg-surface-container-high\/95{background-color:#272a31f2}.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-highest\/95{background-color:#32353cf2}.bg-surface-container-low{--tw-bg-opacity: 1;background-color:rgb(25 28 34 / var(--tw-bg-opacity, 1))}.bg-surface-container-low\/70{background-color:#191c22b3}.bg-surface-container-low\/80{background-color:#191c22cc}.bg-surface-container-low\/90{background-color:#191c22e6}.bg-surface-container-low\/95{background-color:#191c22f2}.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}.bg-surface-container\/50{background-color:#1d202680}.bg-surface-container\/60{background-color:#1d202699}.bg-surface-container\/80{background-color:#1d2026cc}.bg-surface-container\/90{background-color:#1d2026e6}.bg-violet-400{--tw-bg-opacity: 1;background-color:rgb(167 139 250 / var(--tw-bg-opacity, 1))}.bg-violet-400\/95{background-color:#a78bfaf2}.bg-violet-900\/45{background-color:#4c1d9573}.bg-white\/5{background-color:#ffffff0d}.bg-white\/80{background-color:#fffc}.bg-white\/90{background-color:#ffffffe6}.bg-gradient-to-b{background-image:linear-gradient(to bottom,var(--tw-gradient-stops))}.bg-gradient-to-br{background-image:linear-gradient(to bottom right,var(--tw-gradient-stops))}.bg-gradient-to-r{background-image:linear-gradient(to right,var(--tw-gradient-stops))}.from-amber-300\/95{--tw-gradient-from: rgb(252 211 77 / .95) var(--tw-gradient-from-position);--tw-gradient-to: rgb(252 211 77 / 0) var(--tw-gradient-to-position);--tw-gradient-stops: var(--tw-gradient-from), var(--tw-gradient-to)}.from-emerald-600{--tw-gradient-from: #059669 var(--tw-gradient-from-position);--tw-gradient-to: rgb(5 150 105 / 0) var(--tw-gradient-to-position);--tw-gradient-stops: var(--tw-gradient-from), var(--tw-gradient-to)}.from-slate-600\/90{--tw-gradient-from: rgb(71 85 105 / .9) var(--tw-gradient-from-position);--tw-gradient-to: rgb(71 85 105 / 0) var(--tw-gradient-to-position);--tw-gradient-stops: var(--tw-gradient-from), var(--tw-gradient-to)}.from-slate-900\/40{--tw-gradient-from: rgb(15 23 42 / .4) var(--tw-gradient-from-position);--tw-gradient-to: rgb(15 23 42 / 0) var(--tw-gradient-to-position);--tw-gradient-stops: var(--tw-gradient-from), var(--tw-gradient-to)}.from-slate-900\/90{--tw-gradient-from: rgb(15 23 42 / .9) var(--tw-gradient-from-position);--tw-gradient-to: rgb(15 23 42 / 0) var(--tw-gradient-to-position);--tw-gradient-stops: var(--tw-gradient-from), var(--tw-gradient-to)}.via-slate-800\/95{--tw-gradient-to: rgb(30 41 59 / 0) var(--tw-gradient-to-position);--tw-gradient-stops: var(--tw-gradient-from), rgb(30 41 59 / .95) var(--tw-gradient-via-position), var(--tw-gradient-to)}.via-slate-900\/70{--tw-gradient-to: rgb(15 23 42 / 0) var(--tw-gradient-to-position);--tw-gradient-stops: var(--tw-gradient-from), rgb(15 23 42 / .7) var(--tw-gradient-via-position), var(--tw-gradient-to)}.to-sky-400\/40{--tw-gradient-to: rgb(56 189 248 / .4) var(--tw-gradient-to-position)}.to-sky-500{--tw-gradient-to: #0ea5e9 var(--tw-gradient-to-position)}.to-slate-950{--tw-gradient-to: #020617 var(--tw-gradient-to-position)}.to-surface-container\/70{--tw-gradient-to: rgb(29 32 38 / .7) var(--tw-gradient-to-position)}.to-surface-container\/90{--tw-gradient-to: rgb(29 32 38 / .9) var(--tw-gradient-to-position)}.fill-current{fill:currentColor}.object-contain{-o-object-fit:contain;object-fit:contain}.object-cover{-o-object-fit:cover;object-fit:cover}.p-1{padding:.25rem}.p-1\.5{padding:.375rem}.p-2{padding:.5rem}.p-3{padding:.75rem}.p-4{padding:1rem}.p-5{padding:1.25rem}.p-6{padding:1.5rem}.px-1{padding-left:.25rem;padding-right:.25rem}.px-1\.5{padding-left:.375rem;padding-right:.375rem}.px-2{padding-left:.5rem;padding-right:.5rem}.px-2\.5{padding-left:.625rem;padding-right:.625rem}.px-3{padding-left:.75rem;padding-right:.75rem}.px-4{padding-left:1rem;padding-right:1rem}.px-6{padding-left:1.5rem;padding-right:1.5rem}.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}.py-3{padding-top:.75rem;padding-bottom:.75rem}.py-4{padding-top:1rem;padding-bottom:1rem}.py-5{padding-top:1.25rem;padding-bottom:1.25rem}.pb-0\.5{padding-bottom:.125rem}.pb-1{padding-bottom:.25rem}.pb-2{padding-bottom:.5rem}.pb-3{padding-bottom:.75rem}.pl-4{padding-left:1rem}.pl-8{padding-left:2rem}.pr-0\.5{padding-right:.125rem}.pr-1{padding-right:.25rem}.pr-2{padding-right:.5rem}.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}.text-right{text-align:right}.align-top{vertical-align:top}.font-body,.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-2xl{font-size:1.5rem;line-height:2rem}.text-3xl{font-size:1.875rem;line-height:2.25rem}.text-4xl{font-size:2.25rem;line-height:2.5rem}.text-\[10px\]{font-size:10px}.text-\[11px\]{font-size:11px}.text-\[13px\]{font-size:13px}.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-xl{font-size:1.25rem;line-height:1.75rem}.text-xs{font-size:.75rem;line-height:1rem}.font-black{font-weight:900}.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-\[0\.14em\]{letter-spacing:.14em}.tracking-\[0\.2em\]{letter-spacing:.2em}.tracking-tight{letter-spacing:-.025em}.tracking-wide{letter-spacing:.025em}.tracking-wider{letter-spacing:.05em}.tracking-widest{letter-spacing:.1em}.text-amber-100{--tw-text-opacity: 1;color:rgb(254 243 199 / var(--tw-text-opacity, 1))}.text-amber-200{--tw-text-opacity: 1;color:rgb(253 230 138 / var(--tw-text-opacity, 1))}.text-amber-200\/95{color:#fde68af2}.text-amber-300{--tw-text-opacity: 1;color:rgb(252 211 77 / var(--tw-text-opacity, 1))}.text-emerald-200{--tw-text-opacity: 1;color:rgb(167 243 208 / var(--tw-text-opacity, 1))}.text-emerald-50{--tw-text-opacity: 1;color:rgb(236 253 245 / var(--tw-text-opacity, 1))}.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\/70{color:#c2c6d6b3}.text-on-surface-variant\/80{color:#c2c6d6cc}.text-on-surface-variant\/85{color:#c2c6d6d9}.text-on-surface-variant\/90{color:#c2c6d6e6}.text-on-surface-variant\/95{color:#c2c6d6f2}.text-on-surface\/80{color:#e1e2ebcc}.text-primary{--tw-text-opacity: 1;color:rgb(173 198 255 / var(--tw-text-opacity, 1))}.text-primary\/80{color:#adc6ffcc}.text-red-200{--tw-text-opacity: 1;color:rgb(254 202 202 / var(--tw-text-opacity, 1))}.text-secondary{--tw-text-opacity: 1;color:rgb(64 229 108 / var(--tw-text-opacity, 1))}.text-sky-100{--tw-text-opacity: 1;color:rgb(224 242 254 / var(--tw-text-opacity, 1))}.text-sky-200{--tw-text-opacity: 1;color:rgb(186 230 253 / var(--tw-text-opacity, 1))}.text-sky-300\/90{color:#7dd3fce6}.text-slate-400{--tw-text-opacity: 1;color:rgb(148 163 184 / var(--tw-text-opacity, 1))}.text-teal-100{--tw-text-opacity: 1;color:rgb(204 251 241 / var(--tw-text-opacity, 1))}.text-teal-200{--tw-text-opacity: 1;color:rgb(153 246 228 / var(--tw-text-opacity, 1))}.text-teal-200\/95{color:#99f6e4f2}.text-violet-200{--tw-text-opacity: 1;color:rgb(221 214 254 / var(--tw-text-opacity, 1))}.text-white{--tw-text-opacity: 1;color:rgb(255 255 255 / var(--tw-text-opacity, 1))}.underline{text-decoration-line:underline}.antialiased{-webkit-font-smoothing:antialiased;-moz-osx-font-smoothing:grayscale}.accent-primary{accent-color:#adc6ff}.opacity-50{opacity:.5}.opacity-80{opacity:.8}.opacity-90{opacity:.9}.shadow-2xl{--tw-shadow: 0 25px 50px -12px rgb(0 0 0 / .25);--tw-shadow-colored: 0 25px 50px -12px var(--tw-shadow-color);box-shadow:var(--tw-ring-offset-shadow, 0 0 #0000),var(--tw-ring-shadow, 0 0 #0000),var(--tw-shadow)}.shadow-\[0_0_6px_rgba\(255\,255\,255\,0\.6\)\]{--tw-shadow: 0 0 6px rgba(255,255,255,.6);--tw-shadow-colored: 0 0 6px var(--tw-shadow-color);box-shadow:var(--tw-ring-offset-shadow, 0 0 #0000),var(--tw-ring-shadow, 0 0 #0000),var(--tw-shadow)}.shadow-\[0_20px_50px_rgba\(0\,0\,0\,0\.55\)\,inset_0_1px_0_rgba\(255\,255\,255\,0\.08\)\]{--tw-shadow: 0 20px 50px rgba(0,0,0,.55),inset 0 1px 0 rgba(255,255,255,.08);--tw-shadow-colored: 0 20px 50px var(--tw-shadow-color), inset 0 1px 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)}.shadow-lg{--tw-shadow: 0 10px 15px -3px rgb(0 0 0 / .1), 0 4px 6px -4px rgb(0 0 0 / .1);--tw-shadow-colored: 0 10px 15px -3px var(--tw-shadow-color), 0 4px 6px -4px var(--tw-shadow-color);box-shadow:var(--tw-ring-offset-shadow, 0 0 #0000),var(--tw-ring-shadow, 0 0 #0000),var(--tw-shadow)}.shadow-md{--tw-shadow: 0 4px 6px -1px rgb(0 0 0 / .1), 0 2px 4px -2px rgb(0 0 0 / .1);--tw-shadow-colored: 0 4px 6px -1px var(--tw-shadow-color), 0 2px 4px -2px var(--tw-shadow-color);box-shadow:var(--tw-ring-offset-shadow, 0 0 #0000),var(--tw-ring-shadow, 0 0 #0000),var(--tw-shadow)}.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)}.outline-none{outline:2px solid transparent;outline-offset:2px}.ring-1{--tw-ring-offset-shadow: var(--tw-ring-inset) 0 0 0 var(--tw-ring-offset-width) var(--tw-ring-offset-color);--tw-ring-shadow: var(--tw-ring-inset) 0 0 0 calc(1px + var(--tw-ring-offset-width)) var(--tw-ring-color);box-shadow:var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow, 0 0 #0000)}.ring-amber-200\/35{--tw-ring-color: rgb(253 230 138 / .35)}.ring-sky-200\/30{--tw-ring-color: rgb(186 230 253 / .3)}.ring-sky-500\/25{--tw-ring-color: rgb(14 165 233 / .25)}.ring-slate-600\/50{--tw-ring-color: rgb(71 85 105 / .5)}.ring-teal-500\/10{--tw-ring-color: rgb(20 184 166 / .1)}.ring-violet-200\/35{--tw-ring-color: rgb(221 214 254 / .35)}.ring-white\/5{--tw-ring-color: rgb(255 255 255 / .05)}.drop-shadow{--tw-drop-shadow: drop-shadow(0 1px 2px rgb(0 0 0 / .1)) drop-shadow(0 1px 1px rgb(0 0 0 / .06));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)}.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)}.backdrop-blur{--tw-backdrop-blur: blur(8px);-webkit-backdrop-filter:var(--tw-backdrop-blur) var(--tw-backdrop-brightness) var(--tw-backdrop-contrast) var(--tw-backdrop-grayscale) var(--tw-backdrop-hue-rotate) var(--tw-backdrop-invert) var(--tw-backdrop-opacity) var(--tw-backdrop-saturate) var(--tw-backdrop-sepia);backdrop-filter:var(--tw-backdrop-blur) var(--tw-backdrop-brightness) var(--tw-backdrop-contrast) var(--tw-backdrop-grayscale) var(--tw-backdrop-hue-rotate) var(--tw-backdrop-invert) var(--tw-backdrop-opacity) var(--tw-backdrop-saturate) var(--tw-backdrop-sepia)}.backdrop-blur-md{--tw-backdrop-blur: blur(12px);-webkit-backdrop-filter:var(--tw-backdrop-blur) var(--tw-backdrop-brightness) var(--tw-backdrop-contrast) var(--tw-backdrop-grayscale) var(--tw-backdrop-hue-rotate) var(--tw-backdrop-invert) var(--tw-backdrop-opacity) var(--tw-backdrop-saturate) var(--tw-backdrop-sepia);backdrop-filter:var(--tw-backdrop-blur) var(--tw-backdrop-brightness) var(--tw-backdrop-contrast) var(--tw-backdrop-grayscale) var(--tw-backdrop-hue-rotate) var(--tw-backdrop-invert) var(--tw-backdrop-opacity) var(--tw-backdrop-saturate) var(--tw-backdrop-sepia)}.backdrop-blur-xl{--tw-backdrop-blur: blur(24px);-webkit-backdrop-filter:var(--tw-backdrop-blur) var(--tw-backdrop-brightness) var(--tw-backdrop-contrast) var(--tw-backdrop-grayscale) var(--tw-backdrop-hue-rotate) var(--tw-backdrop-invert) var(--tw-backdrop-opacity) var(--tw-backdrop-saturate) var(--tw-backdrop-sepia);backdrop-filter:var(--tw-backdrop-blur) var(--tw-backdrop-brightness) var(--tw-backdrop-contrast) var(--tw-backdrop-grayscale) var(--tw-backdrop-hue-rotate) var(--tw-backdrop-invert) var(--tw-backdrop-opacity) var(--tw-backdrop-saturate) var(--tw-backdrop-sepia)}.transition{transition-property:color,background-color,border-color,text-decoration-color,fill,stroke,opacity,box-shadow,transform,filter,backdrop-filter;transition-timing-function:cubic-bezier(.4,0,.2,1);transition-duration:.15s}.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}.duration-150{transition-duration:.15s}.ease-out{transition-timing-function:cubic-bezier(0,0,.2,1)}.will-change-transform{will-change:transform}html,body,#root{height:100%}.og-scrollbar{scrollbar-width:thin;scrollbar-color:#414754 #191c22}.og-scrollbar::-webkit-scrollbar{width:7px;height:7px}.og-scrollbar::-webkit-scrollbar-track{background:#191c22;border-radius:4px}.og-scrollbar::-webkit-scrollbar-thumb{background:#414754;border-radius:4px}.og-scrollbar::-webkit-scrollbar-thumb:hover{background:#5c6070}.hover\:border-primary:hover{--tw-border-opacity: 1;border-color:rgb(173 198 255 / var(--tw-border-opacity, 1))}.hover\:bg-amber-500\/10:hover{background-color:#f59e0b1a}.hover\:bg-emerald-500\/10:hover{background-color:#10b9811a}.hover\:bg-emerald-700:hover{--tw-bg-opacity: 1;background-color:rgb(4 120 87 / var(--tw-bg-opacity, 1))}.hover\:bg-error-container\/20:hover{background-color:#93000a33}.hover\:bg-error-container\/30:hover{background-color:#93000a4d}.hover\:bg-error\/10:hover{background-color:#ffb4ab1a}.hover\:bg-rose-500\/10:hover{background-color:#f43f5e1a}.hover\:bg-sky-600:hover{--tw-bg-opacity: 1;background-color:rgb(2 132 199 / var(--tw-bg-opacity, 1))}.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\:bg-surface-container\/80:hover{background-color:#1d2026cc}.hover\:bg-white\/5:hover{background-color:#ffffff0d}.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}.hover\:opacity-90:hover{opacity:.9}.focus\:border-primary:focus{--tw-border-opacity: 1;border-color:rgb(173 198 255 / var(--tw-border-opacity, 1))}.disabled\:opacity-40:disabled{opacity:.4}.disabled\:opacity-50:disabled{opacity:.5}@media (min-width: 640px){.sm\:col-span-2{grid-column:span 2 / span 2}.sm\:inline{display:inline}.sm\:grid-cols-2{grid-template-columns:repeat(2,minmax(0,1fr))}.sm\:grid-cols-3{grid-template-columns:repeat(3,minmax(0,1fr))}.sm\:gap-4{gap:1rem}.sm\:text-xl{font-size:1.25rem;line-height:1.75rem}}@media (min-width: 768px){.md\:col-span-1{grid-column:span 1 / span 1}.md\:col-span-3{grid-column:span 3 / span 3}.md\:col-span-4{grid-column:span 4 / span 4}.md\:min-w-\[200px\]{min-width:200px}.md\:max-w-md{max-width:28rem}.md\:grid-cols-2{grid-template-columns:repeat(2,minmax(0,1fr))}.md\:grid-cols-3{grid-template-columns:repeat(3,minmax(0,1fr))}.md\:grid-cols-4{grid-template-columns:repeat(4,minmax(0,1fr))}.md\:flex-row{flex-direction:row}.md\:items-start{align-items:flex-start}.md\:justify-center{justify-content:center}.md\:gap-8{gap:2rem}}@media (min-width: 1024px){.lg\:col-span-3{grid-column:span 3 / span 3}.lg\:col-span-4{grid-column:span 4 / span 4}.lg\:col-span-8{grid-column:span 8 / span 8}.lg\:col-span-9{grid-column:span 9 / span 9}.lg\:ml-64{margin-left:16rem}.lg\:hidden{display:none}.lg\:max-w-lg{max-width:32rem}.lg\:translate-x-0{--tw-translate-x: 0px;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))}.lg\:grid-cols-4{grid-template-columns:repeat(4,minmax(0,1fr))}.lg\:p-6{padding:1.5rem}.lg\:px-8{padding-left:2rem;padding-right:2rem}}@media (min-width: 1280px){.xl\:max-w-\[340px\]{max-width:340px}.xl\:grid-cols-\[minmax\(0\,1\.25fr\)_minmax\(280px\,0\.75fr\)\]{grid-template-columns:minmax(0,1.25fr) minmax(280px,.75fr)}.xl\:flex-row{flex-direction:row}.xl\:items-start{align-items:flex-start}.xl\:justify-between{justify-content:space-between}.xl\:gap-8{gap:2rem}.xl\:self-start{align-self:flex-start}}@media (min-width: 1536px){.\32xl\:static{position:static}.\32xl\:order-1{order:1}.\32xl\:order-2{order:2}.\32xl\:col-span-10{grid-column:span 10 / span 10}.\32xl\:col-span-2{grid-column:span 2 / span 2}.\32xl\:col-span-3{grid-column:span 3 / span 3}.\32xl\:col-span-9{grid-column:span 9 / span 9}.\32xl\:hidden{display:none}.\32xl\:translate-x-0{--tw-translate-x: 0px;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))}.\32xl\:self-start{align-self:flex-start}.\32xl\:shadow-none{--tw-shadow: 0 0 #0000;--tw-shadow-colored: 0 0 #0000;box-shadow:var(--tw-ring-offset-shadow, 0 0 #0000),var(--tw-ring-shadow, 0 0 #0000),var(--tw-shadow)}}.\[\&\:\:-webkit-details-marker\]\:hidden::-webkit-details-marker{display:none} diff --git a/web/static/analysis-lab/assets/index-BoSUzpvv.js b/web/static/analysis-lab/assets/index-BoSUzpvv.js deleted file mode 100644 index 9fe2832..0000000 --- a/web/static/analysis-lab/assets/index-BoSUzpvv.js +++ /dev/null @@ -1,26 +0,0 @@ -import{r as a,j as f}from"./client-D1ZVDB-N.js";/** - * @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 v=e=>e.replace(/([a-z0-9])([A-Z])/g,"$1-$2").toLowerCase(),y=(...e)=>e.filter((s,t,o)=>!!s&&s.trim()!==""&&o.indexOf(s)===t).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 w={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 S=a.forwardRef(({color:e="currentColor",size:s=24,strokeWidth:t=2,absoluteStrokeWidth:o,className:i="",children:r,iconNode:m,...n},c)=>a.createElement("svg",{ref:c,...w,width:s,height:s,stroke:e,strokeWidth:o?Number(t)*24/Number(s):t,className:y("lucide",i),...n},[...m.map(([l,d])=>a.createElement(l,d)),...Array.isArray(r)?r:[r]]));/** - * @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 P=(e,s)=>{const t=a.forwardRef(({className:o,...i},r)=>a.createElement(S,{ref:r,iconNode:s,className:y(`lucide-${v(e)}`,o),...i}));return t.displayName=`${e}`,t};/** - * @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 R=P("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"}]]),k={"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.videoMjpegHint":"MJPEG slots are full: close live previews on other pages or retry later (default ~4; tune OGSCOPE_STREAM_MAX_MJPEG_CLIENTS in systemd).","lab.cameraStreamStatusFail":"Could not read MJPEG slot status. Please retry shortly.","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","nav.systemAdmin":"Back to System Admin","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.flowDownloadingPool":"Downloading imported file...","sidebar.flowWritingBuffer":"Writing to transcoder buffer...","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.layer.rejected":"Rejected (red cross)","lab.previewConfidence":"Confidence","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":"Tetra3 `Prob` is false-positive probability (lower is better). We use (1−Prob)×100% when representable; for extremely small Prob, IEEE doubles round to 100%, so we map −log10(Prob) to ~70–100% for differentiation.","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","lab.gateNextMs":"Retry in ~{ms} ms","lab.gate.skipRecording":"Recording is active: the camera is writing video, so live-frame solve is paused. Stop recording and try again.","lab.gate.skipBusyInFlight":"The previous solve is still running; please wait. If this repeats often, increase the realtime solve interval.","lab.gate.skipInterval":"Solve requests are arriving faster than allowed; the server is throttling. Wait for the countdown and retry.","lab.gate.skipBusyFallback":"Realtime solve could not start ({detail}).","lab.centroidQualityTitle":"Centroid quality","lab.centroidQualityMetrics":"In {in} → kept {out}; removed dense {dense}, collinear {line}","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","results.solveHistoryTitle":"Solve history","results.historySingle":"Single frame","results.historyBatch":"Batch ({count} runs)","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.solveIntervalIndependent":"Solve cadence is independent from sensor and preview frame rates.","params.solveIntervalBound":"Backend bounds: {min}-{max} ms, effective now: {effective} ms","params.centroidRejectionLevel":"Centroid rejection (1–5)","params.centroidRejectionLevelHelp":"1 keeps more stars; 5 aggressively rejects dense clusters and collinear false detections; default 3.","params.centroidRejectionScale":"Conservative ← → Aggressive","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.writingBuffer":"Writing to transcoder buffer...","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.","sys.shell.subtitle":"system debug console","sys.shell.nav.overview":"Overview","sys.shell.nav.network":"Network","sys.shell.nav.camera":"Camera Debug","sys.shell.nav.analysis":"Analysis Console","sys.shell.nav.sensors":"Sensors","sys.shell.nav.power":"Power","sys.shell.nav.hmi":"HMI","sys.shell.nav.config":"Config","sys.shell.workbench":"WORKBENCH / System workspace","sys.shell.node":"Node","sys.shell.top.overview":"System Status","sys.shell.top.network":"Network & WiFi","sys.shell.top.sensors":"Sensors","sys.shell.top.power":"Power Management","sys.shell.top.hmi":"HMI","sys.shell.top.config":"Config","sys.overview.breadcrumb.console":"Console","sys.overview.breadcrumb.module":"System Overview","sys.overview.title":"System Overview","sys.overview.subtitle":"Live system telemetry snapshot","sys.overview.metric.cpu":"CPU Usage","sys.overview.metric.mem":"Memory Usage","sys.overview.metric.temp":"Core Temperature","sys.overview.metric.wifi":"WiFi Link","sys.overview.metric.uptime":"Uptime","sys.overview.metric.load":"Load (1m)","sys.overview.metric.storage":"Storage","sys.overview.linkActive":"Link Active","sys.overview.tempState":"status: nominal","sys.overview.wifiSummary":"WiFi Summary","sys.overview.iface":"Interface","sys.overview.signal":"Signal","sys.overview.quality":"Quality","sys.overview.storageComingSoon":"Coming soon","sys.logs.title":"System Logs","sys.logs.kernel":"Kernel","sys.logs.liveToggle":"Live pull","sys.logs.refresh":"Refresh","sys.logs.liveOffHint":"Live pull is off (default)","sys.logs.loading":"Loading logs...","sys.logs.empty":"No logs","sys.placeholder.breadcrumb":"Console / Reserved Module","sys.placeholder.block":"PLANNED BLOCK","sys.placeholder.desc":"Shell layout is reserved and ready for real data integration.","sys.placeholder.status":"STATUS: RESERVED / Structured placeholder page.","sys.placeholder.sensors.title":"Sensors Console","sys.placeholder.sensors.desc":"Unified monitor for IMU, environment, power rails, and timeline sampling.","sys.placeholder.sensors.block1":"Realtime chart area","sys.placeholder.sensors.block2":"Device health cards","sys.placeholder.sensors.block3":"Threshold alerts","sys.sensors.title":"Sensors Console","sys.sensors.desc":"Visual compass (magnetometer) and gyro rate gauges; backed by dev debug APIs.","sys.sensors.mag.section":"Magnetometer (AK09911 / I²C)","sys.sensors.mag.note":"Defaults: bus 1, address 12 (0x0C with CAD to GND). No app-level GPIO mapping is required; enable I²C in firmware and load i2c-dev.","sys.sensors.mag.bus":"I²C bus","sys.sensors.mag.addr":"7-bit address (decimal, 12 = 0x0C)","sys.sensors.mag.i2cdetect":"Run i2cdetect","sys.sensors.mag.btnSelftest":"Run self-test","sys.sensors.mag.btnProbe":"Probe all buses","sys.sensors.mag.btnCalStart":"Start heading calibration","sys.sensors.mag.btnCalCommit":"Save & lock heading","sys.sensors.mag.btnCalReset":"Reset to auto mode","sys.sensors.mag.btnCalStatus":"Calibration status","sys.sensors.mag.running":"Running…","sys.sensors.mpu.section":"IMU (MPU-6050 / I²C)","sys.sensors.mpu.note":"Defaults: bus 1, address 104 (0x68 with AD0 low). Includes gyro angular-rate sampling (MPU integrates accel + gyro).","sys.sensors.mpu.addr":"7-bit address (decimal, 104 = 0x68)","sys.sensors.mpu.btnSelftest":"Run MPU self-test","sys.sensors.mpu.running":"Running…","sys.sensors.gyro.title":"Gyroscope (MPU-6050)","sys.sensors.gyro.subtitle":"Uses the same bus/address as above; one IMU read: 3-axis gyro, accel-derived roll/pitch, and a 3D board hint.","sys.sensors.gyro.btn":"Read angular rate","sys.sensors.gyro.loading":"Sampling…","sys.sensors.gyro.dps":"Angular rate","sys.sensors.gyro.raw":"Raw","sys.sensors.gyro.unitDps":"°/s, ±250 °/s range, default sensitivity","sys.sensors.gyro.unitRaw":"signed 16-bit","sys.sensors.gyro.hint":"Click “Read angular rate” to sample accel (0x3B) and gyro (0x43). Roll/pitch use gravity; ωz is yaw rate, not absolute heading.","sys.sensors.gyro.errUnknown":"Sample failed","sys.sensors.gyro.live":"Live update (~2 Hz)","sys.sensors.gyro.barsTitle":"Angular rate bars (±250 °/s full scale)","sys.sensors.gyro.rawBlock":"Raw register values (16-bit)","sys.sensors.gyro.att3dTitle":"3D attitude hint","sys.sensors.gyro.att3dDesc":"Roll/pitch from accelerometer (gravity); ωz is yaw rate (not magnetic heading). Body XYZ triad is fixed to the PCB and rotates in 3D.","sys.sensors.gyro.axisXLabel":"+X","sys.sensors.gyro.axisYLabel":"+Y","sys.sensors.gyro.axisZLabel":"+Z","sys.sensors.gyro.att3dBodyAxes":"Body frame on the PCB: +X amber, +Y sky, +Z violet (normal); short dashes mark negative directions; the whole frame follows roll/pitch.","sys.sensors.gyro.att3dRefTitle":"Orthogonal axes (fixed view)","sys.sensors.gyro.att3dRefDesc":"Same colors as left; use this to read XYZ directions at a glance.","sys.sensors.gyro.roll":"Roll","sys.sensors.gyro.pitch":"Pitch","sys.sensors.gyro.yawRate":"Yaw rate","sys.sensors.compass.tapeCaption":"Heading tape · center = current readout","sys.sensors.compass.title":"Magnetometer · North compass","sys.sensors.compass.desc":"Horizontal-plane heading for “roughly north” debugging; precise alignment needs calibration and a fixed mount definition.","sys.sensors.compass.btn":"Refresh compass","sys.sensors.compass.loading":"Measuring…","sys.sensors.compass.live":"Live update","sys.sensors.compass.headingLabel":"Horizontal heading","sys.sensors.compass.headingHint":"From atan2(Hx, Hy), degrees","sys.sensors.compass.cardN":"N","sys.sensors.compass.cardE":"E","sys.sensors.compass.cardS":"S","sys.sensors.compass.cardW":"W","sys.sensors.compass.err":"Sample failed","sys.sensors.compass.footnote":"The red tip tracks the horizontal XY field direction; it should move as you rotate the board. Use soft/hard-iron calibration for tight north lock.","sys.sensors.jsonToggle":"Raw JSON (troubleshooting)","sys.hmi.title":"HMI · SPI display","sys.hmi.desc":"ST7796 via hardware plane (default 320×320). Requires OGSCOPE_DISPLAY_ENABLED=true and SPI enabled (/dev/spidev0.0).","sys.hmi.status.section":"Display & hardware plane","sys.hmi.status.refresh":"Refresh","sys.hmi.status.displayEnabled":"DISPLAY_ENABLED","sys.hmi.status.spidev":"/dev/spidev0.0","sys.hmi.status.yes":"present","sys.hmi.status.no":"missing","sys.hmi.status.resolution":"Resolution / DC","sys.hmi.status.driver":"Driver handle","sys.hmi.status.open":"open","sys.hmi.status.closed":"closed","sys.hmi.status.screenOutput":"Logical output","sys.hmi.status.on":"on","sys.hmi.status.off":"off","sys.hmi.status.lastPattern":"Last pattern","sys.hmi.status.lastError":"Last error","sys.hmi.actions.section":"Output to panel","sys.hmi.actions.hint":"Uses hardware plane device.command → hmi; large frames use a longer RPC timeout.","sys.hmi.actions.smoke":"Test pattern (text + frame)","sys.hmi.actions.colorbars":"Color bars","sys.hmi.actions.fill":"Fill RGB","sys.hmi.actions.screenOn":"Enable panel output","sys.hmi.actions.screenOff":"Pause panel output","sys.hmi.actions.release":"Release driver (close SPI/GPIO)","sys.hmi.rawJson":"Raw JSON response","sys.placeholder.hmi.title":"HMI Console","sys.placeholder.hmi.desc":"Debug workspace for SPI display, key matrix, backlight, and UI replay.","sys.placeholder.hmi.block1":"Display preview","sys.placeholder.hmi.block2":"Input event stream","sys.placeholder.hmi.block3":"Mode controls","sys.placeholder.power.title":"Power Console","sys.placeholder.power.desc":"Debug workspace for battery, charging, power profile, and thermal policy.","sys.placeholder.power.block1":"Power dashboard","sys.placeholder.power.block2":"Power policy table","sys.placeholder.power.block3":"Recovery actions","cam.title":"Camera Debug Console","cam.subtitle":"Migrated to unified admin UI with key optimizations preserved","cam.btn.system":"System Admin","cam.btn.analysis":"Analysis Console","cam.btn.home":"Home","cam.btn.start":"Start Preview","cam.btn.starting":"Starting...","cam.btn.stop":"Stop Preview","cam.btn.stopping":"Stopping...","cam.btn.capture":"Capture","cam.btn.capturing":"Capturing...","cam.btn.recordStart":"Start Recording","cam.btn.recordStop":"Stop Recording","cam.btn.recordBusy":"Switching recording...","cam.btn.refresh":"Refresh Status","cam.preview.title":"Live Preview","cam.preview.state":"State","cam.preview.mode":"Exposure Mode","cam.preview.emptyTitle":"Preview is not started","cam.preview.emptyDesc":"Start the camera preview stream with the button below.","cam.focus.title":"Star Focus Calibration","cam.focus.intro":"Measures focus from multiple stars in raw frames. Rotate the lens slowly after starting; lower HFD is sharper, and the yellow ring marks the reference star.","cam.focus.start":"Start Focus Calibration","cam.focus.starting":"Preparing camera…","cam.focus.finish":"Finish Calibration","cam.focus.resetBest":"Reset Session Best","cam.focus.autoSelect":"Resume Auto Selection","cam.focus.active":"Measuring","cam.focus.idle":"Not started","cam.focus.currentHfd.multi":"Current Multi-star HFD","cam.focus.currentHfd.single":"Current Locked-star HFD","cam.focus.bestHfd":"Session Best","cam.focus.lowerBetter":"lower is better","cam.focus.stars":"Usable / Measured / Candidates","cam.focus.fwhm":"Median FWHM","cam.focus.concentration":"Core Concentration","cam.focus.selectedStar":"Reference Star","cam.focus.stability":"In-frame Spread","cam.focus.history":"Recent Focus Trend","cam.focus.historyEmpty":"Collecting focus samples…","cam.focus.clickHint":"Click the preview to lock a star","cam.focus.autoTarget":"Automatically selecting a high-SNR star","cam.focus.manualTarget":"Locked near the clicked position","cam.focus.confidence.high":"High confidence","cam.focus.confidence.medium":"Medium confidence","cam.focus.confidence.low":"Low confidence; find more stars","cam.focus.guidance.collecting":"Keep the device still while a baseline is collected…","cam.focus.guidance.noStars":"No usable stars found. Check sky direction, exposure, and the lens cap.","cam.focus.guidance.best":"Near the session best. Fine-tune, hold for a few seconds, then lock the lens when stable.","cam.focus.guidance.improving":"Focus is improving. Continue with a small turn in the same direction.","cam.focus.guidance.worsening":"Focus is getting worse. Reverse slightly and return to the session best.","cam.focus.guidance.steady":"The change is small. Use a finer adjustment and watch for a lower session best.","cam.focus.warning.saturated":"Some bright stars were saturated and excluded. Lower exposure or gain if too few stars remain.","cam.focus.warning.targetNotFound":"No usable star was found near that point. Click closer to a star center or resume automatic selection.","cam.focus.warning.undersampled":"Very sharp stars below about one pixel were detected. They remain usable and are evaluated with FWHM and concentration too.","cam.focus.warning.hotPixel":"The clicked target may be an isolated hot pixel. Confirm a real stellar halo in the preview or choose another star.","cam.focus.detectionSummary":"{detected} candidates, {measured} measured, {rejected} rejected by quality checks.","cam.focus.sessionLocked":"This session locks measured exposure and gain and disables denoise; finishing restores the previous settings.","cam.focus.step.start":"Point at the sky, start calibration, and wait for at least three usable stars.","cam.focus.step.adjust":"Rotate the lens slowly and wait for the curve to settle after each adjustment.","cam.focus.step.lock":"Return to the lowest HFD, confirm stability, and lock the lens.","cam.state.streaming":"streaming","cam.state.idle":"idle","cam.state.rec":"REC","cam.stats.requestFps":"Request FPS","cam.hint.mjpegSingleStream":"MJPEG concurrent streams are limited (default ~2). Beyond the cap, extra tabs may contend or get busy—reduce simultaneous live previews.","cam.stats.frameFps":"Display FPS","cam.stats.fpsMeasureNote":"Pixel deltas on a 32×32 sample over ~1s. Multiple previews, slow links, or a stream cap below the sensor target often read much lower.","cam.err.streamBusy":"Preview unavailable: MJPEG slot is already in use","cam.err.streamBusyHint":"Close the live preview in other tabs or in the Analysis console, then try again.","cam.err.streamProbeFailed":"Could not open the preview stream","cam.err.streamProbeDetail":"Check network and camera service, or retry in a moment.","cam.stats.targetFps":"Camera capture target FPS","cam.stats.streamPacingFps":"Shared stream / MJPEG cap FPS","cam.stats.streamPacingHint":"Matches OGSCOPE_SHARED_PREVIEW_FPS: shared grabber pacing and per-stream MJPEG min frame spacing.","cam.stats.captureFps":"Actual capture FPS","cam.stats.encodeMs":"Average JPEG encode","cam.stats.encoder":"Preview encoder","cam.stats.driver":"Camera driver","cam.stats.metadata":"Metadata","cam.stats.consumers":"Preview/analysis/record consumers","cam.stats.cameraMemory":"Process memory/swap","cam.stats.longExposureThrottle":"Auto exposure is about {exposure} s, so long exposure is limiting FPS; exposure is not shortened to preserve analysis quality.","cam.stats.uptime":"Stream Uptime","cam.controls.title":"Controls","cam.controls.core":"Core Settings","cam.controls.fps":"FPS","cam.controls.sensorFps":"Sensor target FPS","cam.controls.previewFps":"Preview target FPS","cam.controls.resolution":"Resolution","cam.controls.applyRes":"Apply resolution","cam.controls.sampling":"Sampling mode","cam.controls.exposure":"Exposure","cam.controls.gain":"Analog Gain","cam.controls.digitalGain":"Digital Gain","cam.controls.noiseReduction":"Noise Reduction","cam.controls.noiseReductionMode":"Noise Reduction","cam.controls.digitalGainReadOnly":"Digital gain is read-only on this driver","cam.controls.aeFlicker":"AE Flicker","cam.controls.maxAeFrame":"Max auto exposure (s)","cam.controls.contrast":"Contrast","cam.controls.brightness":"Brightness","cam.controls.saturation":"Saturation","cam.controls.sharpness":"Sharpness","cam.controls.applySettings":"Apply All Settings","cam.controls.applyAll":"Save Parameters","cam.controls.applyRuntime":"Save Runtime Settings","cam.controls.mode":"Mode Controls","cam.controls.autoExposure":"Exposure Mode","cam.controls.whiteBalance":"White Balance","cam.controls.colorMode":"Color Mode","cam.controls.auto":"Auto","cam.controls.manual":"Manual","cam.controls.night":"Night","cam.controls.off":"Off","cam.controls.nr.off":"Off","cam.controls.nr.fast":"Fast","cam.controls.nr.high_quality":"High quality","cam.controls.wb.auto":"Auto","cam.controls.wb.daylight":"Daylight","cam.controls.wb.cloudy":"Cloudy","cam.controls.wb.tungsten":"Tungsten","cam.controls.wb.fluorescent":"Fluorescent","cam.controls.wb.indoor":"Indoor","cam.controls.wb.manual":"Manual","cam.controls.wb.night":"Night","cam.controls.color":"Color","cam.controls.mono":"Mono","cam.controls.applyAe":"Apply Exposure Mode","cam.controls.applyWb":"Apply White Balance","cam.controls.applyColor":"Apply Color Mode","cam.controls.applyMode":"Apply Mode Settings","cam.controls.lockedByAe":"Exposure and gain are locked while auto exposure is enabled.","cam.controls.manualExposureFpsLimit":"At {exposure} s manual exposure, actual capture is limited to about {fps} FPS.","cam.controls.lockedByWb":"R/B gains are locked unless white balance is in manual mode.","cam.controls.pendingChanges":"You have unapplied parameter changes","cam.controls.tools":"Tools","cam.controls.nightPreset":"Night Preset","cam.controls.nightOn":"Enable Night Mode","cam.controls.nightOff":"Disable Night Mode","cam.controls.backup":"Backup Settings","cam.controls.restore":"Restore Settings","cam.controls.reset":"Reset Camera","cam.quick.title":"Quick Presets & Smart Tuning","cam.quick.daylight":"Daylight","cam.quick.night":"Night","cam.quick.nightPreset":"Apply Night Preset","cam.quick.deep-sky":"Deep Sky","cam.quick.planetary":"Planetary","cam.quick.autoAdjust":"Auto Adjust","cam.quick.nightHint":"Night preset is one-click tuning; night mode toggle is runtime on/off control.","cam.system.title":"System Monitor","cam.system.status":"Camera Ready","cam.system.stream":"Stream","cam.system.sensor":"Sensor","cam.system.quality":"Image Quality","cam.presets.title":"Preset Management","cam.presets.name":"Preset Name","cam.presets.desc":"Preset Description","cam.presets.save":"Save Preset","cam.presets.empty":"No presets","cam.presets.noDesc":"No description","cam.presets.apply":"Apply","cam.presets.delete":"Delete","cam.files.title":"File Management","cam.files.refresh":"Refresh File List","cam.files.empty":"No files","cam.files.download":"Download","cam.files.info":"Details","cam.files.delete":"Delete","cam.files.loadingInfo":"Loading file details...","cam.files.prev":"Prev","cam.files.next":"Next","cam.files.page":"Page {current}/{total}","cam.files.closeDetail":"Close details","cam.files.size":"Size","cam.files.type":"Type","cam.files.modified":"Modified","cam.hist.title":"Histogram","cam.hist.enabled":"Enable histogram","cam.hist.luminance":"Luminance","cam.hist.over":"Over-exposure","cam.hist.expand":"Expand Histogram","cam.hist.collapse":"Collapse Histogram","cam.notice.previewStart":"Preview started","cam.notice.previewStop":"Preview stopped","cam.notice.captureSaved":"Capture saved: {name}","cam.notice.recordStart":"Recording started: {name}","cam.notice.recordStop":"Recording stopped","cam.notice.fpsApplied":"FPS updated to {fps}","cam.notice.resApplied":"Resolution updated to {w}x{h}","cam.notice.samplingApplied":"Sampling mode updated: {mode}","cam.notice.settingsApplied":"Settings applied","cam.notice.aeApplied":"Exposure mode applied","cam.notice.wbApplied":"White balance applied","cam.notice.colorApplied":"Color mode applied","cam.notice.modeApplied":"Mode settings applied","cam.notice.runtimeApplied":"Runtime settings applied","cam.notice.rotationApplied":"Rotation set to {value}°","cam.mirror.hint":"Use with rotation when the camera is mounted inverted; preview, plate solve, and polar guide share one coordinate frame.","cam.mirror.horizontal":"Horizontal mirror","cam.mirror.vertical":"Vertical mirror","cam.notice.mirrorApplied":"Mirror settings updated","cam.notice.nightOn":"Night mode enabled","cam.notice.nightOff":"Night mode disabled","cam.notice.nightPreset":"Night preset applied","cam.notice.reset":"Camera settings reset","cam.notice.backup":"Settings backed up","cam.notice.restore":"Settings restored","cam.notice.presetSaved":"Preset saved: {name}","cam.notice.presetApplied":"Preset applied: {name}","cam.notice.presetDeleted":"Preset deleted: {name}","cam.notice.download":"Download started: {name}","cam.notice.downloadWithSidecar":"Downloaded: {name} (with sidecar {sidecar})","cam.notice.fileDeleted":"File deleted: {name}","cam.notice.quickPresetApplied":"Quick preset applied: {preset}","cam.notice.needPreview":"Start preview first","cam.notice.needManualForAutoAdjust":"Switch to manual exposure before auto-adjust","cam.notice.autoAdjusted":"Auto-adjust completed and applied","cam.confirm.reset":"Reset camera settings on the current device?","cam.confirm.deletePreset":"Delete preset {name}?","cam.confirm.deleteFile":"Delete file {name}?","sys.sensors.mag.calStatusPrefix":"Calibration mode:","sys.sensors.mag.calModeAuto":"Auto (not locked)","sys.sensors.mag.calModeRecording":"Recording (rotate device)","sys.sensors.mag.calModeLocked":"Locked (saved heading)","sys.sensors.mag.calSamplesPrefix":"Recorded samples:","sys.sensors.mag.calRecordingHint":"Tip: keep level and rotate slowly; save after 10+ samples.","sys.sensors.mag.calLockedHint":"Locked heading params:"},C={"app.title":"OGScope 星空解算控制台","nav.lab":"解算台","nav.labImage":"图片解算","nav.labVideo":"视频解算","nav.pool":"素材池","nav.history":"实验记录","nav.cameraDebug":"相机调试控制台","nav.home":"首页","nav.systemAdmin":"返回系统后台","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.flowDownloadingPool":"正在下载已导入文件…","sidebar.flowWritingBuffer":"正在写入转码缓冲区…","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.layer.rejected":"已剔除(红叉)","lab.previewConfidence":"置信度","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":"Tetra3 的 Prob 为假阳性概率(越低越可信)。一般使用 (1−Prob)×100%;当 Prob 极小时浮点数会舍入为 100%,此时用 −log₁₀(Prob) 映射到约 70–100% 以便区分。","lab.metric.probRawHelp":"Tetra3 返回的原始 Prob 字段,可能为对数似然等内部量;与上一行换算后的百分比对照查看即可。","lab.metric.radec":"RA / Dec","lab.metric.matches":"匹配","lab.metric.rmse":"RMSE","lab.metric.prob":"置信","lab.metric.status":"状态","lab.gateNextMs":"约 {ms} ms 后可重试","lab.gate.skipRecording":"录制中:相机正写入录像,暂无法从相机取帧解算;请先停止录制后再试。","lab.gate.skipBusyInFlight":"上一帧解算仍在处理中,请稍候。若频繁出现,可适当增大「实时解算间隔」。","lab.gate.skipInterval":"解算请求过于频繁,已按后端节流排队。","lab.gate.skipBusyFallback":"实时解算暂时无法开始({detail})。","lab.centroidQualityTitle":"质心质量","lab.centroidQualityMetrics":"输入 {in} → 保留 {out};剔除过密 {dense},共线 {line}","meta.exposure":"曝光","meta.gain":"增益","meta.fps":"帧率","meta.sensor":"传感器","meta.colorMode":"色彩","meta.outputResolution":"输出分辨率","meta.fileTime":"文件时间","meta.fileSize":"文件大小","results.viewRaw":"原始 JSON","results.hideRaw":"收起","results.solveHistoryTitle":"解算历史","results.historySingle":"单帧","results.historyBatch":"批量 {count} 组","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.solveIntervalIndependent":"解算间隔独立于相机采集和预览帧率。","params.solveIntervalBound":"后端限制范围:{min}-{max} ms,当前生效:{effective} ms","params.centroidRejectionLevel":"质心剔除强度 (1–5)","params.centroidRejectionLevelHelp":"1 最保守保留更多星点,5 更激进剔除过密与共线假星;默认 3。","params.centroidRejectionScale":"保守 ← → 激进","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.stopCameraPreview":"停止预览","lab.videoLiveIntro":"与调试控制台共用同一相机;此处可预览并解算实时帧,无需单独打开调试页。两页可同时使用。","lab.videoMjpegHint":"MJPEG 同时连接数已满:请关闭其它页面的实时预览或稍后重试(默认允许约 4 路,可在 systemd 调整 OGSCOPE_STREAM_MAX_MJPEG_CLIENTS)。","lab.cameraStreamStatusFail":"无法读取视频流名额状态,请稍后重试。","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.writingBuffer":"正在写入转码缓冲区…","lab.transcode.running":"正在转码 AVI -> MP4…","lab.transcode.packaging":"正在封装转码结果…","lab.transcode.uploading":"正在上传转码结果…","lab.transcode.done":"转码并替换完成,可继续预览与解算。","lab.transcode.failed":"转码或上传失败,请重试。","lab.transcode.fetchFailed":"无法读取服务器上的 AVI 文件。","sys.shell.subtitle":"系统调试控制台","sys.shell.nav.overview":"总览","sys.shell.nav.network":"网络","sys.shell.nav.camera":"相机调试","sys.shell.nav.analysis":"寻星控制台","sys.shell.nav.sensors":"传感器","sys.shell.nav.power":"电源","sys.shell.nav.hmi":"人机交互","sys.shell.nav.config":"配置管理","sys.shell.workbench":"WORKBENCH / 系统工作台","sys.shell.node":"节点","sys.shell.top.overview":"系统状态","sys.shell.top.network":"网络与 WiFi","sys.shell.top.sensors":"传感器","sys.shell.top.power":"电源管理","sys.shell.top.hmi":"人机交互","sys.shell.top.config":"配置管理","sys.overview.breadcrumb.console":"控制台","sys.overview.breadcrumb.module":"系统总览","sys.overview.title":"系统总览","sys.overview.subtitle":"实时系统运行健康状况","sys.overview.metric.cpu":"CPU 使用率","sys.overview.metric.mem":"内存占用","sys.overview.metric.temp":"核心温度","sys.overview.metric.wifi":"WiFi 链路","sys.overview.metric.uptime":"运行时长","sys.overview.metric.load":"1 分钟负载","sys.overview.metric.storage":"存储","sys.overview.linkActive":"链路在线","sys.overview.tempState":"状态: 正常","sys.overview.wifiSummary":"无线网络摘要","sys.overview.iface":"接口","sys.overview.signal":"信号","sys.overview.quality":"质量","sys.overview.storageComingSoon":"即将接入","sys.logs.title":"系统日志","sys.logs.kernel":"内核","sys.logs.liveToggle":"实时拉取","sys.logs.refresh":"刷新","sys.logs.liveOffHint":"实时拉取关闭(默认)","sys.logs.loading":"日志加载中...","sys.logs.empty":"暂无日志","sys.placeholder.breadcrumb":"控制台 / 预留模块","sys.placeholder.block":"预留模块","sys.placeholder.desc":"模块壳层已预留,后续将按统一组件体系接入真实数据。","sys.placeholder.status":"STATUS: RESERVED / 当前为结构化占位页面。","sys.placeholder.sensors.title":"传感器诊断台","sys.placeholder.sensors.desc":"用于 IMU、温湿度、电流电压、姿态与时序采样的统一监控面板。","sys.placeholder.sensors.block1":"实时曲线区","sys.placeholder.sensors.block2":"设备健康卡","sys.placeholder.sensors.block3":"阈值告警区","sys.sensors.title":"传感器诊断台","sys.sensors.desc":"磁力计罗盘指北与 MPU-6050 陀螺仪角速度可视化;底层仍为开发者调试 API。","sys.sensors.mag.section":"磁力计(AK09911 / I²C)","sys.sensors.mag.note":"默认总线 1、地址 12(0x0C,CAD 接 GND)。无需在应用层“定义 GPIO”;请确保固件已启用 I²C 且已加载 i2c-dev。","sys.sensors.mag.bus":"I²C 总线号","sys.sensors.mag.addr":"7-bit 地址(十进制,12=0x0C)","sys.sensors.mag.i2cdetect":"附带运行 i2cdetect","sys.sensors.mag.btnSelftest":"运行自检","sys.sensors.mag.btnProbe":"扫描各总线","sys.sensors.mag.btnCalStart":"开始方向校准","sys.sensors.mag.btnCalCommit":"保存并锁定方向","sys.sensors.mag.btnCalReset":"重置到自动模式","sys.sensors.mag.btnCalStatus":"查看校准状态","sys.sensors.mag.running":"执行中…","sys.sensors.mpu.section":"IMU(MPU-6050 / I²C)","sys.sensors.mpu.note":"默认总线 1、地址 104(0x68,AD0 接 GND)。含陀螺仪角速度采样(MPU 内集成加速度计与陀螺仪)。","sys.sensors.mpu.addr":"7-bit 地址(十进制,104=0x68)","sys.sensors.mpu.btnSelftest":"运行 MPU 自检","sys.sensors.mpu.running":"执行中…","sys.sensors.gyro.title":"陀螺仪(MPU-6050)","sys.sensors.gyro.subtitle":"使用上方同一总线/地址;一次读取加速度与陀螺仪,估算横滚/俯仰并显示 3D 板卡示意。","sys.sensors.gyro.btn":"读取角速度","sys.sensors.gyro.loading":"采样中…","sys.sensors.gyro.dps":"角速度","sys.sensors.gyro.raw":"原始值","sys.sensors.gyro.unitDps":"°/s,±250°/s 量程、默认灵敏度","sys.sensors.gyro.unitRaw":"16-bit 有符号","sys.sensors.gyro.hint":"点击「读取角速度」从 IMU 采样(加速度 0x3B + 陀螺仪 0x43)。横滚/俯仰来自重力;ωz 为绕竖直轴角速度,并非绝对航向。","sys.sensors.gyro.errUnknown":"采样失败","sys.sensors.gyro.live":"连续刷新(约 2 Hz)","sys.sensors.gyro.barsTitle":"角速度条(相对 ±250°/s 满偏)","sys.sensors.gyro.rawBlock":"寄存器原始值(16-bit)","sys.sensors.gyro.att3dTitle":"3D 姿态示意","sys.sensors.gyro.att3dDesc":"横滚/俯仰由加速度计重力估算;ωz 为陀螺仪绕竖直轴角速度(非磁航向)。机体系三轴与板卡固连,随姿态在三维空间中转动。","sys.sensors.gyro.axisXLabel":"+X","sys.sensors.gyro.axisYLabel":"+Y","sys.sensors.gyro.axisZLabel":"+Z","sys.sensors.gyro.att3dBodyAxes":"左侧为与电路板固连的机体系:琥珀 +X、天蓝 +Y、紫 +Z(法向);短划为负向参考,整体随横滚/俯仰旋转。","sys.sensors.gyro.att3dRefTitle":"正交轴参考(固定视角)","sys.sensors.gyro.att3dRefDesc":"与左侧同色对应,便于辨认三轴空间关系。","sys.sensors.gyro.roll":"横滚","sys.sensors.gyro.pitch":"俯仰","sys.sensors.gyro.yawRate":"偏航角速度","sys.sensors.compass.tapeCaption":"水平航向带 · 中心为当前读数","sys.sensors.compass.title":"磁力计 · 指北罗盘","sys.sensors.compass.desc":"根据水平面磁场分量估算方向角,用于调试「是否大致指向北方」;精对准需校准与固定安装姿态。","sys.sensors.compass.btn":"刷新罗盘","sys.sensors.compass.loading":"测量中…","sys.sensors.compass.live":"连续刷新","sys.sensors.compass.headingLabel":"水平航向角","sys.sensors.compass.headingHint":"由 atan2(Hx, Hy) 得到,单位度","sys.sensors.compass.cardN":"北","sys.sensors.compass.cardE":"东","sys.sensors.compass.cardS":"南","sys.sensors.compass.cardW":"西","sys.sensors.compass.err":"采样失败","sys.sensors.compass.footnote":"红色针尖表示 XY 平面内磁场水平分量方向;转动设备时指针应随之变化。精对准需软铁/硬铁校准。","sys.sensors.jsonToggle":"展开原始 JSON(排障用)","sys.hmi.title":"人机交互 · SPI 显示","sys.hmi.desc":"通过硬件平面驱动 ST7796(默认 320×320);需 OGSCOPE_DISPLAY_ENABLED=true 且系统已启用 SPI(/dev/spidev0.0)。","sys.hmi.status.section":"显示与硬件平面状态","sys.hmi.status.refresh":"刷新状态","sys.hmi.status.displayEnabled":"DISPLAY_ENABLED","sys.hmi.status.spidev":"/dev/spidev0.0","sys.hmi.status.yes":"存在","sys.hmi.status.no":"不存在","sys.hmi.status.resolution":"分辨率 / DC","sys.hmi.status.driver":"驱动句柄","sys.hmi.status.open":"已打开","sys.hmi.status.closed":"未打开","sys.hmi.status.screenOutput":"逻辑输出","sys.hmi.status.on":"开","sys.hmi.status.off":"关","sys.hmi.status.lastPattern":"上次图案","sys.hmi.status.lastError":"上次错误","sys.hmi.actions.section":"输出到屏幕","sys.hmi.actions.hint":"以下为硬件平面 device.command → hmi;大屏刷新使用较长 RPC 超时。","sys.hmi.actions.smoke":"测试画面(文字+边框)","sys.hmi.actions.colorbars":"彩条","sys.hmi.actions.fill":"填充 RGB","sys.hmi.actions.screenOn":"允许屏幕输出","sys.hmi.actions.screenOff":"暂停屏幕输出","sys.hmi.actions.release":"释放驱动(关闭 SPI/GPIO)","sys.hmi.rawJson":"原始 JSON 响应","sys.placeholder.hmi.title":"人机交互台","sys.placeholder.hmi.desc":"用于 SPI 显示、按键矩阵、背光/对比度与界面回放的调试工作区。","sys.placeholder.hmi.block1":"显示预览区","sys.placeholder.hmi.block2":"输入事件流","sys.placeholder.hmi.block3":"参数与模式控制","sys.placeholder.power.title":"电源与功耗台","sys.placeholder.power.desc":"用于电池、充电、瞬时功耗与热管理策略联动调试。","sys.placeholder.power.block1":"功耗看板","sys.placeholder.power.block2":"电源策略表","sys.placeholder.power.block3":"恢复与保护动作","cam.title":"相机调试控制台","cam.subtitle":"迁移到统一后台架构,保留关键性能优化","cam.btn.system":"系统后台","cam.btn.analysis":"寻星控制台","cam.btn.home":"首页","cam.btn.start":"启动预览","cam.btn.starting":"启动中...","cam.btn.stop":"停止预览","cam.btn.stopping":"停止中...","cam.btn.capture":"拍摄","cam.btn.capturing":"拍摄中...","cam.btn.recordStart":"开始录制","cam.btn.recordStop":"停止录制","cam.btn.recordBusy":"录制切换中...","cam.btn.refresh":"刷新状态","cam.preview.title":"实时预览","cam.preview.state":"状态","cam.preview.mode":"曝光模式","cam.preview.emptyTitle":"预览尚未启动","cam.preview.emptyDesc":"点击下方按钮启动相机预览流。","cam.focus.title":"星点焦点校准","cam.focus.intro":"用原始帧中的多颗星测量焦点质量。开始后缓慢旋转镜头;HFD 越小越清晰,黄色圆圈表示当前参考星。","cam.focus.start":"开始焦点校准","cam.focus.starting":"准备相机…","cam.focus.finish":"完成校准","cam.focus.resetBest":"重置本轮最佳","cam.focus.autoSelect":"恢复自动选星","cam.focus.active":"测量中","cam.focus.idle":"未开始","cam.focus.currentHfd.multi":"当前多星 HFD","cam.focus.currentHfd.single":"当前锁定星 HFD","cam.focus.bestHfd":"本轮最佳","cam.focus.lowerBetter":"越低越好","cam.focus.stars":"可用 / 已测量 / 候选","cam.focus.fwhm":"中位 FWHM","cam.focus.concentration":"核心能量集中度","cam.focus.selectedStar":"参考星","cam.focus.stability":"帧内离散度","cam.focus.history":"最近焦点趋势","cam.focus.historyEmpty":"正在收集焦点样本…","cam.focus.clickHint":"可点击预览锁定一颗星","cam.focus.autoTarget":"自动选择高信噪比星点","cam.focus.manualTarget":"已按点击位置锁定","cam.focus.confidence.high":"高置信度","cam.focus.confidence.medium":"中等置信度","cam.focus.confidence.low":"低置信度,请寻找更多星点","cam.focus.guidance.collecting":"保持设备稳定,正在建立基线…","cam.focus.guidance.noStars":"没有找到可用星点:请检查天空方向、曝光和镜头保护盖。","cam.focus.guidance.best":"已接近本轮最佳;微调并保持数秒,确认读数稳定后锁紧镜头。","cam.focus.guidance.improving":"正在变清晰,继续沿当前方向小幅旋转。","cam.focus.guidance.worsening":"清晰度变差,建议反向微调并回到本轮最佳。","cam.focus.guidance.steady":"变化很小;请用更小角度微调,观察本轮最佳是否下降。","cam.focus.warning.saturated":"部分亮星已饱和并被排除;若可用星过少,请降低曝光或增益。","cam.focus.warning.targetNotFound":"点击位置附近没有可用星点,请点击更靠近星点中心的位置,或恢复自动选星。","cam.focus.warning.undersampled":"检测到小于约 1 像素的锐利星点;工具会保留它们,并结合 FWHM 与能量集中度判断。","cam.focus.warning.hotPixel":"点击目标可能是孤立热像素;请确认它在预览中具有真实星点光晕,或换一颗星。","cam.focus.detectionSummary":"候选 {detected},已测量 {measured},质量筛除 {rejected}。","cam.focus.sessionLocked":"本轮已锁定当前实测曝光和增益、关闭降噪;结束校准时自动恢复。","cam.focus.step.start":"对准星空并开始校准,等待至少三颗可用星。","cam.focus.step.adjust":"缓慢旋转镜头,每次调整后等待曲线稳定。","cam.focus.step.lock":"回到本轮最低 HFD,保持稳定后锁紧镜头。","cam.state.streaming":"流已启动","cam.state.idle":"未启动","cam.state.rec":"录制中","cam.stats.requestFps":"请求 FPS","cam.hint.mjpegSingleStream":"MJPEG 同时连接数有限(默认约两路);超出上限时多标签页会争用或返回忙,请减少同时打开的实时预览。","cam.stats.frameFps":"画面 FPS","cam.stats.fpsMeasureNote":"统计最近约 1 秒内在 32×32 采样上的像素变化次数;多路同时预览、网络较慢、或下方「共享流上限」低于相机目标时,读数常明显偏低。","cam.err.streamBusy":"无法连接预览:MJPEG 名额已被占用","cam.err.streamBusyHint":"请关闭其它标签页或解算台中的设备实时预览,或稍后再试。","cam.err.streamProbeFailed":"无法建立预览流","cam.err.streamProbeDetail":"请检查网络与相机服务状态,或稍后重试。","cam.stats.targetFps":"相机采集目标 FPS","cam.stats.streamPacingFps":"共享流/MJPEG 上限 FPS","cam.stats.streamPacingHint":"与进程环境 OGSCOPE_SHARED_PREVIEW_FPS 一致,约束共享抓帧节奏与每路 MJPEG 的最小帧间隔。","cam.stats.captureFps":"实际采集 FPS","cam.stats.encodeMs":"JPEG 平均编码","cam.stats.encoder":"预览编码器","cam.stats.driver":"相机驱动","cam.stats.metadata":"元数据","cam.stats.consumers":"预览/分析/录像消费者","cam.stats.cameraMemory":"进程内存/Swap","cam.stats.longExposureThrottle":"自动曝光约 {exposure} s,实际帧率受长曝光限制;为保证分析效果未强制缩短曝光。","cam.stats.uptime":"流运行时长","cam.controls.title":"参数控制","cam.controls.core":"核心参数","cam.controls.fps":"帧率","cam.controls.sensorFps":"传感器目标 FPS","cam.controls.previewFps":"预览目标 FPS","cam.controls.resolution":"分辨率","cam.controls.applyRes":"应用分辨率","cam.controls.sampling":"采样模式","cam.controls.exposure":"曝光","cam.controls.gain":"模拟增益","cam.controls.digitalGain":"数字增益","cam.controls.noiseReduction":"降噪级别","cam.controls.noiseReductionMode":"降噪模式","cam.controls.digitalGainReadOnly":"当前驱动仅上报数字增益,不支持手动设置","cam.controls.aeFlicker":"AE 防闪烁","cam.controls.maxAeFrame":"最长自动曝光时间 (s)","cam.controls.contrast":"对比度","cam.controls.brightness":"亮度","cam.controls.saturation":"饱和度","cam.controls.sharpness":"锐度","cam.controls.applySettings":"应用全部设置","cam.controls.applyAll":"保存参数","cam.controls.applyRuntime":"保存运行参数","cam.controls.mode":"模式控制","cam.controls.autoExposure":"曝光模式","cam.controls.whiteBalance":"白平衡","cam.controls.colorMode":"颜色模式","cam.controls.auto":"自动","cam.controls.manual":"手动","cam.controls.night":"夜间","cam.controls.off":"关闭","cam.controls.nr.off":"关闭","cam.controls.nr.fast":"快速","cam.controls.nr.high_quality":"高质量","cam.controls.wb.auto":"自动","cam.controls.wb.daylight":"日光","cam.controls.wb.cloudy":"阴天","cam.controls.wb.tungsten":"钨丝灯","cam.controls.wb.fluorescent":"荧光灯","cam.controls.wb.indoor":"室内","cam.controls.wb.manual":"手动","cam.controls.wb.night":"夜间","cam.controls.color":"彩色","cam.controls.mono":"黑白","cam.controls.applyAe":"应用曝光模式","cam.controls.applyWb":"应用白平衡","cam.controls.applyColor":"应用颜色模式","cam.controls.applyMode":"应用模式设置","cam.controls.lockedByAe":"自动曝光开启时,曝光与增益参数锁定。","cam.controls.manualExposureFpsLimit":"手动曝光 {exposure} 秒时,实际采集帧率最高约 {fps} FPS。","cam.controls.lockedByWb":"当前非手动白平衡模式,R/B 增益参数锁定。","cam.controls.pendingChanges":"有未应用的参数修改","cam.controls.tools":"工具","cam.controls.nightPreset":"夜间预设","cam.controls.nightOn":"开启夜间模式","cam.controls.nightOff":"关闭夜间模式","cam.controls.backup":"备份设置","cam.controls.restore":"恢复设置","cam.controls.reset":"重置相机","cam.quick.title":"快速预设与智能调参","cam.quick.daylight":"白天模式","cam.quick.night":"夜间模式","cam.quick.nightPreset":"应用夜间预设","cam.quick.deep-sky":"深空模式","cam.quick.planetary":"行星模式","cam.quick.autoAdjust":"智能调整","cam.quick.nightHint":"夜间预设用于一键切换夜拍参数;夜间模式开关用于实时启停红外/夜景模式。","cam.system.title":"系统监控","cam.system.status":"相机就绪","cam.system.stream":"流状态","cam.system.sensor":"传感器","cam.system.quality":"图像质量","cam.presets.title":"预设管理","cam.presets.name":"预设名称","cam.presets.desc":"预设描述","cam.presets.save":"保存预设","cam.presets.empty":"暂无预设","cam.presets.noDesc":"无描述","cam.presets.apply":"应用","cam.presets.delete":"删除","cam.files.title":"文件管理","cam.files.refresh":"刷新文件列表","cam.files.empty":"暂无文件","cam.files.download":"下载","cam.files.info":"详情","cam.files.delete":"删除","cam.files.loadingInfo":"正在读取文件详情...","cam.files.prev":"上一页","cam.files.next":"下一页","cam.files.page":"第 {current}/{total} 页","cam.files.closeDetail":"关闭详情","cam.files.size":"文件大小","cam.files.type":"文件类型","cam.files.modified":"修改时间","cam.hist.title":"直方图","cam.hist.enabled":"启用直方图","cam.hist.luminance":"亮度通道","cam.hist.over":"过曝警告","cam.hist.expand":"展开直方图","cam.hist.collapse":"收起直方图","cam.notice.previewStart":"预览已启动","cam.notice.previewStop":"预览已停止","cam.notice.captureSaved":"拍摄完成: {name}","cam.notice.recordStart":"开始录制: {name}","cam.notice.recordStop":"录制已停止","cam.notice.fpsApplied":"帧率已设置为 {fps}","cam.notice.resApplied":"分辨率已设置为 {w}x{h}","cam.notice.samplingApplied":"采样模式已设置为 {mode}","cam.notice.settingsApplied":"参数已应用","cam.notice.aeApplied":"曝光模式已应用","cam.notice.wbApplied":"白平衡已应用","cam.notice.colorApplied":"颜色模式已应用","cam.notice.modeApplied":"模式设置已应用","cam.notice.runtimeApplied":"运行参数已应用","cam.notice.rotationApplied":"旋转已设置为 {value}°","cam.mirror.hint":"相机倒装时可配合旋转使用;镜像后预览与星点解算、极轴引导同坐标系。","cam.mirror.horizontal":"水平镜像","cam.mirror.vertical":"垂直镜像","cam.notice.mirrorApplied":"镜像设置已更新","cam.notice.nightOn":"夜间模式已开启","cam.notice.nightOff":"夜间模式已关闭","cam.notice.nightPreset":"夜间预设已应用","cam.notice.reset":"相机设置已重置","cam.notice.backup":"设置已备份","cam.notice.restore":"设置已恢复","cam.notice.presetSaved":"预设已保存: {name}","cam.notice.presetApplied":"预设已应用: {name}","cam.notice.presetDeleted":"预设已删除: {name}","cam.notice.download":"已开始下载: {name}","cam.notice.downloadWithSidecar":"已下载: {name}(含侧车 {sidecar})","cam.notice.fileDeleted":"文件已删除: {name}","cam.notice.quickPresetApplied":"已应用快速预设: {preset}","cam.notice.needPreview":"请先启动预览","cam.notice.needManualForAutoAdjust":"请先切换为手动曝光再进行智能调整","cam.notice.autoAdjusted":"智能调整已完成并生效","cam.confirm.reset":"将重置当前设备的相机设置。确认继续?","cam.confirm.deletePreset":"确定删除预设 {name} 吗?","cam.confirm.deleteFile":"确定删除文件 {name} 吗?","sys.sensors.mag.calStatusPrefix":"校准状态:","sys.sensors.mag.calModeAuto":"自动模式(未锁定)","sys.sensors.mag.calModeRecording":"录制中(请旋转设备)","sys.sensors.mag.calModeLocked":"已锁定(使用保存方向)","sys.sensors.mag.calSamplesPrefix":"录制样本:","sys.sensors.mag.calRecordingHint":"操作提示:保持水平,缓慢旋转设备,建议累计 10+ 样本后保存。","sys.sensors.mag.calLockedHint":"已锁定方向参数:"},g=a.createContext(null),p={zh:C,en:k},h="ogscope.analysis.locale";function u(){const e=window.localStorage.getItem(h);return e==="zh"||e==="en"?e:(navigator.language||"zh").toLowerCase().startsWith("en")?"en":"zh"}function x({children:e}){const[s,t]=a.useState(u),[o,i]=a.useState(p[u()]);a.useEffect(()=>{i(p[s]),window.localStorage.setItem(h,s),document.documentElement.lang=s==="en"?"en":"zh-CN"},[s]);const r=a.useMemo(()=>(n,c)=>{let l=o[n]??n;if(c)for(const[d,b]of Object.entries(c))l=l.replace(new RegExp(`\\{${d}\\}`,"g"),String(b));return l},[o]),m=a.useMemo(()=>({locale:s,setLocale:t,t:r}),[s,r]);return f.jsx(g.Provider,{value:m,children:e})}function A(){const e=a.useContext(g);if(!e)throw new Error("useI18n must be used within I18nProvider");return e}export{x as I,R as T,P as c,A as u}; diff --git a/web/static/analysis-lab/assets/index-BwAjnQW0.js b/web/static/analysis-lab/assets/index-BwAjnQW0.js new file mode 100644 index 0000000..92ad665 --- /dev/null +++ b/web/static/analysis-lab/assets/index-BwAjnQW0.js @@ -0,0 +1,26 @@ +import{r as a,j as f}from"./client-D1ZVDB-N.js";/** + * @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 v=e=>e.replace(/([a-z0-9])([A-Z])/g,"$1-$2").toLowerCase(),y=(...e)=>e.filter((s,t,o)=>!!s&&s.trim()!==""&&o.indexOf(s)===t).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 w={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 S=a.forwardRef(({color:e="currentColor",size:s=24,strokeWidth:t=2,absoluteStrokeWidth:o,className:i="",children:r,iconNode:m,...n},c)=>a.createElement("svg",{ref:c,...w,width:s,height:s,stroke:e,strokeWidth:o?Number(t)*24/Number(s):t,className:y("lucide",i),...n},[...m.map(([l,d])=>a.createElement(l,d)),...Array.isArray(r)?r:[r]]));/** + * @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 P=(e,s)=>{const t=a.forwardRef(({className:o,...i},r)=>a.createElement(S,{ref:r,iconNode:s,className:y(`lucide-${v(e)}`,o),...i}));return t.displayName=`${e}`,t};/** + * @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 x=P("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"}]]),k={"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.videoMjpegHint":"MJPEG slots are full: close live previews on other pages or retry later (default ~4; tune OGSCOPE_STREAM_MAX_MJPEG_CLIENTS in systemd).","lab.cameraStreamStatusFail":"Could not read MJPEG slot status. Please retry shortly.","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","nav.systemAdmin":"Back to System Admin","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.flowDownloadingPool":"Downloading imported file...","sidebar.flowWritingBuffer":"Writing to transcoder buffer...","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.layer.rejected":"Rejected (red cross)","lab.previewConfidence":"Confidence","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":"Tetra3 `Prob` is false-positive probability (lower is better). We use (1−Prob)×100% when representable; for extremely small Prob, IEEE doubles round to 100%, so we map −log10(Prob) to ~70–100% for differentiation.","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","lab.gateNextMs":"Retry in ~{ms} ms","lab.gate.skipRecording":"Recording is active: the camera is writing video, so live-frame solve is paused. Stop recording and try again.","lab.gate.skipBusyInFlight":"The previous solve is still running; please wait. If this repeats often, increase the realtime solve interval.","lab.gate.skipInterval":"Solve requests are arriving faster than allowed; the server is throttling. Wait for the countdown and retry.","lab.gate.skipBusyFallback":"Realtime solve could not start ({detail}).","lab.centroidQualityTitle":"Centroid quality","lab.centroidQualityMetrics":"In {in} → kept {out}; removed dense {dense}, collinear {line}","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","results.solveHistoryTitle":"Solve history","results.historySingle":"Single frame","results.historyBatch":"Batch ({count} runs)","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.solveIntervalIndependent":"Solve cadence is independent from sensor and preview frame rates.","params.solveIntervalBound":"Backend bounds: {min}-{max} ms, effective now: {effective} ms","params.centroidRejectionLevel":"Centroid rejection (1–5)","params.centroidRejectionLevelHelp":"1 keeps more stars; 5 aggressively rejects dense clusters and collinear false detections; default 3.","params.centroidRejectionScale":"Conservative ← → Aggressive","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.writingBuffer":"Writing to transcoder buffer...","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.","sys.shell.subtitle":"system debug console","sys.shell.nav.overview":"Overview","sys.shell.nav.network":"Network","sys.shell.nav.camera":"Camera Debug","sys.shell.nav.analysis":"Analysis Console","sys.shell.nav.sensors":"Sensors","sys.shell.nav.power":"Power","sys.shell.nav.hmi":"HMI","sys.shell.nav.config":"Config","sys.shell.workbench":"WORKBENCH / System workspace","sys.shell.node":"Node","sys.shell.top.overview":"System Status","sys.shell.top.network":"Network & WiFi","sys.shell.top.sensors":"Sensors","sys.shell.top.power":"Power Management","sys.shell.top.hmi":"HMI","sys.shell.top.config":"Config","sys.overview.breadcrumb.console":"Console","sys.overview.breadcrumb.module":"System Overview","sys.overview.title":"System Overview","sys.overview.subtitle":"Live system telemetry snapshot","sys.overview.metric.cpu":"CPU Usage","sys.overview.metric.mem":"Memory Usage","sys.overview.metric.temp":"Core Temperature","sys.overview.metric.wifi":"WiFi Link","sys.overview.metric.uptime":"Uptime","sys.overview.metric.load":"Load (1m)","sys.overview.metric.storage":"Storage","sys.overview.linkActive":"Link Active","sys.overview.tempState":"status: nominal","sys.overview.wifiSummary":"WiFi Summary","sys.overview.iface":"Interface","sys.overview.signal":"Signal","sys.overview.quality":"Quality","sys.overview.storageComingSoon":"Coming soon","sys.logs.title":"System Logs","sys.logs.kernel":"Kernel","sys.logs.liveToggle":"Live pull","sys.logs.refresh":"Refresh","sys.logs.liveOffHint":"Live pull is off (default)","sys.logs.loading":"Loading logs...","sys.logs.empty":"No logs","sys.placeholder.breadcrumb":"Console / Reserved Module","sys.placeholder.block":"PLANNED BLOCK","sys.placeholder.desc":"Shell layout is reserved and ready for real data integration.","sys.placeholder.status":"STATUS: RESERVED / Structured placeholder page.","sys.placeholder.sensors.title":"Sensors Console","sys.placeholder.sensors.desc":"Unified monitor for IMU, environment, power rails, and timeline sampling.","sys.placeholder.sensors.block1":"Realtime chart area","sys.placeholder.sensors.block2":"Device health cards","sys.placeholder.sensors.block3":"Threshold alerts","sys.sensors.title":"Sensors Console","sys.sensors.desc":"Visual compass (magnetometer) and gyro rate gauges; backed by dev debug APIs.","sys.sensors.mag.section":"Magnetometer (AK09911 / I²C)","sys.sensors.mag.note":"Defaults: bus 1, address 12 (0x0C with CAD to GND). No app-level GPIO mapping is required; enable I²C in firmware and load i2c-dev.","sys.sensors.mag.bus":"I²C bus","sys.sensors.mag.addr":"7-bit address (decimal, 12 = 0x0C)","sys.sensors.mag.i2cdetect":"Run i2cdetect","sys.sensors.mag.btnSelftest":"Run self-test","sys.sensors.mag.btnProbe":"Probe all buses","sys.sensors.mag.btnCalStart":"Start heading calibration","sys.sensors.mag.btnCalCommit":"Save & lock heading","sys.sensors.mag.btnCalReset":"Reset to auto mode","sys.sensors.mag.btnCalStatus":"Calibration status","sys.sensors.mag.running":"Running…","sys.sensors.mpu.section":"IMU (MPU-6050 / I²C)","sys.sensors.mpu.note":"Defaults: bus 1, address 104 (0x68 with AD0 low). Includes gyro angular-rate sampling (MPU integrates accel + gyro).","sys.sensors.mpu.addr":"7-bit address (decimal, 104 = 0x68)","sys.sensors.mpu.btnSelftest":"Run MPU self-test","sys.sensors.mpu.running":"Running…","sys.sensors.gyro.title":"Gyroscope (MPU-6050)","sys.sensors.gyro.subtitle":"Uses the same bus/address as above; one IMU read: 3-axis gyro, accel-derived roll/pitch, and a 3D board hint.","sys.sensors.gyro.btn":"Read angular rate","sys.sensors.gyro.loading":"Sampling…","sys.sensors.gyro.dps":"Angular rate","sys.sensors.gyro.raw":"Raw","sys.sensors.gyro.unitDps":"°/s, ±250 °/s range, default sensitivity","sys.sensors.gyro.unitRaw":"signed 16-bit","sys.sensors.gyro.hint":"Click “Read angular rate” to sample accel (0x3B) and gyro (0x43). Roll/pitch use gravity; ωz is yaw rate, not absolute heading.","sys.sensors.gyro.errUnknown":"Sample failed","sys.sensors.gyro.live":"Live update (~2 Hz)","sys.sensors.gyro.barsTitle":"Angular rate bars (±250 °/s full scale)","sys.sensors.gyro.rawBlock":"Raw register values (16-bit)","sys.sensors.gyro.att3dTitle":"3D attitude hint","sys.sensors.gyro.att3dDesc":"Roll/pitch from accelerometer (gravity); ωz is yaw rate (not magnetic heading). Body XYZ triad is fixed to the PCB and rotates in 3D.","sys.sensors.gyro.axisXLabel":"+X","sys.sensors.gyro.axisYLabel":"+Y","sys.sensors.gyro.axisZLabel":"+Z","sys.sensors.gyro.att3dBodyAxes":"Body frame on the PCB: +X amber, +Y sky, +Z violet (normal); short dashes mark negative directions; the whole frame follows roll/pitch.","sys.sensors.gyro.att3dRefTitle":"Orthogonal axes (fixed view)","sys.sensors.gyro.att3dRefDesc":"Same colors as left; use this to read XYZ directions at a glance.","sys.sensors.gyro.roll":"Roll","sys.sensors.gyro.pitch":"Pitch","sys.sensors.gyro.yawRate":"Yaw rate","sys.sensors.compass.tapeCaption":"Heading tape · center = current readout","sys.sensors.compass.title":"Magnetometer · North compass","sys.sensors.compass.desc":"Horizontal-plane heading for “roughly north” debugging; precise alignment needs calibration and a fixed mount definition.","sys.sensors.compass.btn":"Refresh compass","sys.sensors.compass.loading":"Measuring…","sys.sensors.compass.live":"Live update","sys.sensors.compass.headingLabel":"Horizontal heading","sys.sensors.compass.headingHint":"From atan2(Hx, Hy), degrees","sys.sensors.compass.cardN":"N","sys.sensors.compass.cardE":"E","sys.sensors.compass.cardS":"S","sys.sensors.compass.cardW":"W","sys.sensors.compass.err":"Sample failed","sys.sensors.compass.footnote":"The red tip tracks the horizontal XY field direction; it should move as you rotate the board. Use soft/hard-iron calibration for tight north lock.","sys.sensors.jsonToggle":"Raw JSON (troubleshooting)","sys.hmi.title":"HMI · SPI display","sys.hmi.desc":"ST7796 via hardware plane (default 320×320). Requires OGSCOPE_DISPLAY_ENABLED=true and SPI enabled (/dev/spidev0.0).","sys.hmi.status.section":"Display & hardware plane","sys.hmi.status.refresh":"Refresh","sys.hmi.status.displayEnabled":"DISPLAY_ENABLED","sys.hmi.status.spidev":"/dev/spidev0.0","sys.hmi.status.yes":"present","sys.hmi.status.no":"missing","sys.hmi.status.resolution":"Resolution / DC","sys.hmi.status.driver":"Driver handle","sys.hmi.status.open":"open","sys.hmi.status.closed":"closed","sys.hmi.status.screenOutput":"Logical output","sys.hmi.status.on":"on","sys.hmi.status.off":"off","sys.hmi.status.lastPattern":"Last pattern","sys.hmi.status.lastError":"Last error","sys.hmi.actions.section":"Output to panel","sys.hmi.actions.hint":"Uses hardware plane device.command → hmi; large frames use a longer RPC timeout.","sys.hmi.actions.smoke":"Test pattern (text + frame)","sys.hmi.actions.colorbars":"Color bars","sys.hmi.actions.fill":"Fill RGB","sys.hmi.actions.screenOn":"Enable panel output","sys.hmi.actions.screenOff":"Pause panel output","sys.hmi.actions.release":"Release driver (close SPI/GPIO)","sys.hmi.rawJson":"Raw JSON response","sys.placeholder.hmi.title":"HMI Console","sys.placeholder.hmi.desc":"Debug workspace for SPI display, key matrix, backlight, and UI replay.","sys.placeholder.hmi.block1":"Display preview","sys.placeholder.hmi.block2":"Input event stream","sys.placeholder.hmi.block3":"Mode controls","sys.placeholder.power.title":"Power Console","sys.placeholder.power.desc":"Debug workspace for battery, charging, power profile, and thermal policy.","sys.placeholder.power.block1":"Power dashboard","sys.placeholder.power.block2":"Power policy table","sys.placeholder.power.block3":"Recovery actions","cam.title":"Camera Debug Console","cam.subtitle":"Migrated to unified admin UI with key optimizations preserved","cam.btn.system":"System Admin","cam.btn.analysis":"Analysis Console","cam.btn.home":"Home","cam.btn.start":"Start Preview","cam.btn.starting":"Starting...","cam.btn.stop":"Stop Preview","cam.btn.stopping":"Stopping...","cam.btn.capture":"Capture","cam.btn.capturing":"Capturing...","cam.btn.recordStart":"Start Recording","cam.btn.recordStop":"Stop Recording","cam.btn.recordBusy":"Switching recording...","cam.btn.refresh":"Refresh Status","cam.preview.title":"Live Preview","cam.preview.state":"State","cam.preview.mode":"Exposure Mode","cam.preview.emptyTitle":"Preview is not started","cam.preview.emptyDesc":"Start the camera preview stream with the button below.","cam.focus.title":"Star Focus Calibration","cam.focus.intro":"Measures focus from multiple stars in raw frames. Rotate the lens slowly after starting; lower HFD is sharper, and the yellow ring marks the reference star.","cam.focus.start":"Start Focus Calibration","cam.focus.starting":"Preparing camera…","cam.focus.finish":"Finish Calibration","cam.focus.resetBest":"Reset Session Best","cam.focus.autoSelect":"Resume Auto Selection","cam.focus.active":"Measuring","cam.focus.idle":"Not started","cam.focus.currentHfd.multi":"Current Multi-star HFD","cam.focus.currentHfd.single":"Current Locked-star HFD","cam.focus.bestHfd":"Session Best","cam.focus.lowerBetter":"lower is better","cam.focus.stars":"Usable / Measured / Candidates","cam.focus.fwhm":"Median FWHM","cam.focus.concentration":"Core Concentration","cam.focus.selectedStar":"Reference Star","cam.focus.stability":"In-frame Spread","cam.focus.history":"Recent Focus Trend","cam.focus.historyEmpty":"Collecting focus samples…","cam.focus.clickHint":"Click the preview to lock a star","cam.focus.autoTarget":"Automatically selecting a high-SNR star","cam.focus.manualTarget":"Locked near the clicked position","cam.focus.confidence.high":"High confidence","cam.focus.confidence.medium":"Medium confidence","cam.focus.confidence.low":"Low confidence; find more stars","cam.focus.guidance.collecting":"Keep the device still while a baseline is collected…","cam.focus.guidance.noStars":"No usable stars found. Check sky direction, exposure, and the lens cap.","cam.focus.guidance.best":"Near the session best. Fine-tune, hold for a few seconds, then lock the lens when stable.","cam.focus.guidance.improving":"Focus is improving. Continue with a small turn in the same direction.","cam.focus.guidance.worsening":"Focus is getting worse. Reverse slightly and return to the session best.","cam.focus.guidance.steady":"The change is small. Use a finer adjustment and watch for a lower session best.","cam.focus.warning.saturated":"Some bright stars were saturated and excluded. Lower exposure or gain if too few stars remain.","cam.focus.warning.targetNotFound":"No usable star was found near that point. Click closer to a star center or resume automatic selection.","cam.focus.warning.undersampled":"Very sharp stars below about one pixel were detected. They remain usable and are evaluated with FWHM and concentration too.","cam.focus.warning.hotPixel":"The clicked target may be an isolated hot pixel. Confirm a real stellar halo in the preview or choose another star.","cam.focus.detectionSummary":"{detected} candidates, {measured} measured, {rejected} rejected by quality checks.","cam.focus.sessionLocked":"This session locks measured exposure and gain and disables denoise; finishing restores the previous settings.","cam.focus.step.start":"Point at the sky, start calibration, and wait for at least three usable stars.","cam.focus.step.adjust":"Rotate the lens slowly and wait for the curve to settle after each adjustment.","cam.focus.step.lock":"Return to the lowest HFD, confirm stability, and lock the lens.","cam.state.streaming":"streaming","cam.state.idle":"idle","cam.state.rec":"REC","cam.stats.requestFps":"Request FPS","cam.hint.mjpegSingleStream":"MJPEG concurrent streams are limited (default ~2). Beyond the cap, extra tabs may contend or get busy—reduce simultaneous live previews.","cam.stats.frameFps":"Display FPS","cam.stats.fpsMeasureNote":"Pixel deltas on a 32×32 sample over ~1s. Multiple previews, slow links, or a stream cap below the sensor target often read much lower.","cam.err.streamBusy":"Preview unavailable: MJPEG slot is already in use","cam.err.streamBusyHint":"Close the live preview in other tabs or in the Analysis console, then try again.","cam.err.streamProbeFailed":"Could not open the preview stream","cam.err.streamProbeDetail":"Check network and camera service, or retry in a moment.","cam.stats.targetFps":"Camera capture target FPS","cam.stats.streamPacingFps":"Shared stream / MJPEG cap FPS","cam.stats.streamPacingHint":"Matches OGSCOPE_SHARED_PREVIEW_FPS: shared grabber pacing and per-stream MJPEG min frame spacing.","cam.stats.captureFps":"Actual capture FPS","cam.stats.encodeMs":"Average JPEG encode","cam.stats.encoder":"Preview encoder","cam.stats.driver":"Camera driver","cam.stats.metadata":"Metadata","cam.stats.consumers":"Preview/analysis/record consumers","cam.stats.cameraMemory":"Process memory/swap","cam.stats.longExposureThrottle":"Auto exposure is about {exposure} s, so long exposure is limiting FPS; exposure is not shortened to preserve analysis quality.","cam.stats.uptime":"Stream Uptime","cam.controls.title":"Controls","cam.controls.core":"Core Settings","cam.controls.fps":"FPS","cam.controls.sensorFps":"Sensor target FPS","cam.controls.previewFps":"Preview target FPS","cam.controls.resolution":"Resolution","cam.controls.applyRes":"Apply resolution","cam.controls.sampling":"Sampling mode","cam.controls.exposure":"Exposure","cam.controls.gain":"Analog Gain","cam.controls.digitalGain":"Digital Gain","cam.controls.noiseReduction":"Noise Reduction","cam.controls.noiseReductionMode":"Noise Reduction","cam.controls.digitalGainReadOnly":"Digital gain is read-only on this driver","cam.controls.aeFlicker":"AE Flicker","cam.controls.maxAeFrame":"Max auto exposure (s)","cam.controls.contrast":"Contrast","cam.controls.brightness":"Brightness","cam.controls.saturation":"Saturation","cam.controls.sharpness":"Sharpness","cam.controls.applySettings":"Apply All Settings","cam.controls.applyAll":"Save Parameters","cam.controls.applyRuntime":"Save Runtime Settings","cam.controls.mode":"Mode Controls","cam.controls.autoExposure":"Exposure Mode","cam.controls.whiteBalance":"White Balance","cam.controls.colorMode":"Color Mode","cam.controls.auto":"Auto","cam.controls.manual":"Manual","cam.controls.night":"Night","cam.controls.off":"Off","cam.controls.nr.off":"Off","cam.controls.nr.fast":"Fast","cam.controls.nr.high_quality":"High quality","cam.controls.wb.auto":"Auto","cam.controls.wb.daylight":"Daylight","cam.controls.wb.cloudy":"Cloudy","cam.controls.wb.tungsten":"Tungsten","cam.controls.wb.fluorescent":"Fluorescent","cam.controls.wb.indoor":"Indoor","cam.controls.wb.manual":"Manual","cam.controls.wb.night":"Night","cam.controls.color":"Color","cam.controls.mono":"Mono","cam.controls.applyAe":"Apply Exposure Mode","cam.controls.applyWb":"Apply White Balance","cam.controls.applyColor":"Apply Color Mode","cam.controls.applyMode":"Apply Mode Settings","cam.controls.lockedByAe":"Exposure and gain are locked while auto exposure is enabled.","cam.controls.manualExposureFpsLimit":"At {exposure} s manual exposure, actual capture is limited to about {fps} FPS.","cam.controls.lockedByWb":"R/B gains are locked unless white balance is in manual mode.","cam.controls.pendingChanges":"You have unapplied parameter changes","cam.controls.tools":"Tools","cam.controls.nightPreset":"Night Preset","cam.controls.nightOn":"Enable Night Mode","cam.controls.nightOff":"Disable Night Mode","cam.controls.backup":"Backup Settings","cam.controls.restore":"Restore Settings","cam.controls.reset":"Reset Camera","cam.quick.title":"Quick Presets & Smart Tuning","cam.quick.daylight":"Daylight","cam.quick.night":"Night","cam.quick.nightPreset":"Apply Night Preset","cam.quick.deep-sky":"Deep Sky","cam.quick.planetary":"Planetary","cam.quick.autoAdjust":"Auto Adjust","cam.quick.nightHint":"Night preset is one-click tuning; night mode toggle is runtime on/off control.","cam.system.title":"System Monitor","cam.system.status":"Camera Ready","cam.system.stream":"Stream","cam.system.sensor":"Sensor","cam.system.quality":"Image Quality","cam.presets.title":"Preset Management","cam.presets.name":"Preset Name","cam.presets.desc":"Preset Description","cam.presets.save":"Save Preset","cam.presets.empty":"No presets","cam.presets.noDesc":"No description","cam.presets.apply":"Apply","cam.presets.delete":"Delete","cam.files.title":"File Management","cam.files.refresh":"Refresh File List","cam.files.exportAll":"Export All","cam.files.storagePersistent":"Captures are stored persistently on this device and survive restarts and routine deployments.","cam.files.storageTemporary":"Temporary storage is active. A device restart or power loss clears captures; export them promptly.","cam.files.empty":"No files","cam.files.download":"Download","cam.files.info":"Details","cam.files.delete":"Delete","cam.files.loadingInfo":"Loading file details...","cam.files.prev":"Prev","cam.files.next":"Next","cam.files.page":"Page {current}/{total}","cam.files.closeDetail":"Close details","cam.files.size":"Size","cam.files.type":"Type","cam.files.modified":"Modified","cam.hist.title":"Histogram","cam.hist.enabled":"Enable histogram","cam.hist.luminance":"Luminance","cam.hist.over":"Over-exposure","cam.hist.expand":"Expand Histogram","cam.hist.collapse":"Collapse Histogram","cam.notice.previewStart":"Preview started","cam.notice.previewStop":"Preview stopped","cam.notice.captureSaved":"Capture saved: {name}","cam.notice.recordStart":"Recording started: {name}","cam.notice.recordStop":"Recording stopped","cam.notice.fpsApplied":"FPS updated to {fps}","cam.notice.resApplied":"Resolution updated to {w}x{h}","cam.notice.samplingApplied":"Sampling mode updated: {mode}","cam.notice.settingsApplied":"Settings applied","cam.notice.aeApplied":"Exposure mode applied","cam.notice.wbApplied":"White balance applied","cam.notice.colorApplied":"Color mode applied","cam.notice.modeApplied":"Mode settings applied","cam.notice.runtimeApplied":"Runtime settings applied","cam.notice.rotationApplied":"Rotation set to {value}°","cam.mirror.hint":"Use with rotation when the camera is mounted inverted; preview, plate solve, and polar guide share one coordinate frame.","cam.mirror.horizontal":"Horizontal mirror","cam.mirror.vertical":"Vertical mirror","cam.notice.mirrorApplied":"Mirror settings updated","cam.notice.nightOn":"Night mode enabled","cam.notice.nightOff":"Night mode disabled","cam.notice.nightPreset":"Night preset applied","cam.notice.reset":"Camera settings reset","cam.notice.backup":"Settings backed up","cam.notice.restore":"Settings restored","cam.notice.presetSaved":"Preset saved: {name}","cam.notice.presetApplied":"Preset applied: {name}","cam.notice.presetDeleted":"Preset deleted: {name}","cam.notice.download":"Download started: {name}","cam.notice.downloadWithSidecar":"Downloaded: {name} (with sidecar {sidecar})","cam.notice.exportAll":"Export of all capture files started","cam.notice.fileDeleted":"File deleted: {name}","cam.notice.quickPresetApplied":"Quick preset applied: {preset}","cam.notice.needPreview":"Start preview first","cam.notice.needManualForAutoAdjust":"Switch to manual exposure before auto-adjust","cam.notice.autoAdjusted":"Auto-adjust completed and applied","cam.confirm.reset":"Reset camera settings on the current device?","cam.confirm.deletePreset":"Delete preset {name}?","cam.confirm.deleteFile":"Delete file {name}?","sys.sensors.mag.calStatusPrefix":"Calibration mode:","sys.sensors.mag.calModeAuto":"Auto (not locked)","sys.sensors.mag.calModeRecording":"Recording (rotate device)","sys.sensors.mag.calModeLocked":"Locked (saved heading)","sys.sensors.mag.calSamplesPrefix":"Recorded samples:","sys.sensors.mag.calRecordingHint":"Tip: keep level and rotate slowly; save after 10+ samples.","sys.sensors.mag.calLockedHint":"Locked heading params:"},C={"app.title":"OGScope 星空解算控制台","nav.lab":"解算台","nav.labImage":"图片解算","nav.labVideo":"视频解算","nav.pool":"素材池","nav.history":"实验记录","nav.cameraDebug":"相机调试控制台","nav.home":"首页","nav.systemAdmin":"返回系统后台","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.flowDownloadingPool":"正在下载已导入文件…","sidebar.flowWritingBuffer":"正在写入转码缓冲区…","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.layer.rejected":"已剔除(红叉)","lab.previewConfidence":"置信度","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":"Tetra3 的 Prob 为假阳性概率(越低越可信)。一般使用 (1−Prob)×100%;当 Prob 极小时浮点数会舍入为 100%,此时用 −log₁₀(Prob) 映射到约 70–100% 以便区分。","lab.metric.probRawHelp":"Tetra3 返回的原始 Prob 字段,可能为对数似然等内部量;与上一行换算后的百分比对照查看即可。","lab.metric.radec":"RA / Dec","lab.metric.matches":"匹配","lab.metric.rmse":"RMSE","lab.metric.prob":"置信","lab.metric.status":"状态","lab.gateNextMs":"约 {ms} ms 后可重试","lab.gate.skipRecording":"录制中:相机正写入录像,暂无法从相机取帧解算;请先停止录制后再试。","lab.gate.skipBusyInFlight":"上一帧解算仍在处理中,请稍候。若频繁出现,可适当增大「实时解算间隔」。","lab.gate.skipInterval":"解算请求过于频繁,已按后端节流排队。","lab.gate.skipBusyFallback":"实时解算暂时无法开始({detail})。","lab.centroidQualityTitle":"质心质量","lab.centroidQualityMetrics":"输入 {in} → 保留 {out};剔除过密 {dense},共线 {line}","meta.exposure":"曝光","meta.gain":"增益","meta.fps":"帧率","meta.sensor":"传感器","meta.colorMode":"色彩","meta.outputResolution":"输出分辨率","meta.fileTime":"文件时间","meta.fileSize":"文件大小","results.viewRaw":"原始 JSON","results.hideRaw":"收起","results.solveHistoryTitle":"解算历史","results.historySingle":"单帧","results.historyBatch":"批量 {count} 组","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.solveIntervalIndependent":"解算间隔独立于相机采集和预览帧率。","params.solveIntervalBound":"后端限制范围:{min}-{max} ms,当前生效:{effective} ms","params.centroidRejectionLevel":"质心剔除强度 (1–5)","params.centroidRejectionLevelHelp":"1 最保守保留更多星点,5 更激进剔除过密与共线假星;默认 3。","params.centroidRejectionScale":"保守 ← → 激进","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.stopCameraPreview":"停止预览","lab.videoLiveIntro":"与调试控制台共用同一相机;此处可预览并解算实时帧,无需单独打开调试页。两页可同时使用。","lab.videoMjpegHint":"MJPEG 同时连接数已满:请关闭其它页面的实时预览或稍后重试(默认允许约 4 路,可在 systemd 调整 OGSCOPE_STREAM_MAX_MJPEG_CLIENTS)。","lab.cameraStreamStatusFail":"无法读取视频流名额状态,请稍后重试。","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.writingBuffer":"正在写入转码缓冲区…","lab.transcode.running":"正在转码 AVI -> MP4…","lab.transcode.packaging":"正在封装转码结果…","lab.transcode.uploading":"正在上传转码结果…","lab.transcode.done":"转码并替换完成,可继续预览与解算。","lab.transcode.failed":"转码或上传失败,请重试。","lab.transcode.fetchFailed":"无法读取服务器上的 AVI 文件。","sys.shell.subtitle":"系统调试控制台","sys.shell.nav.overview":"总览","sys.shell.nav.network":"网络","sys.shell.nav.camera":"相机调试","sys.shell.nav.analysis":"寻星控制台","sys.shell.nav.sensors":"传感器","sys.shell.nav.power":"电源","sys.shell.nav.hmi":"人机交互","sys.shell.nav.config":"配置管理","sys.shell.workbench":"WORKBENCH / 系统工作台","sys.shell.node":"节点","sys.shell.top.overview":"系统状态","sys.shell.top.network":"网络与 WiFi","sys.shell.top.sensors":"传感器","sys.shell.top.power":"电源管理","sys.shell.top.hmi":"人机交互","sys.shell.top.config":"配置管理","sys.overview.breadcrumb.console":"控制台","sys.overview.breadcrumb.module":"系统总览","sys.overview.title":"系统总览","sys.overview.subtitle":"实时系统运行健康状况","sys.overview.metric.cpu":"CPU 使用率","sys.overview.metric.mem":"内存占用","sys.overview.metric.temp":"核心温度","sys.overview.metric.wifi":"WiFi 链路","sys.overview.metric.uptime":"运行时长","sys.overview.metric.load":"1 分钟负载","sys.overview.metric.storage":"存储","sys.overview.linkActive":"链路在线","sys.overview.tempState":"状态: 正常","sys.overview.wifiSummary":"无线网络摘要","sys.overview.iface":"接口","sys.overview.signal":"信号","sys.overview.quality":"质量","sys.overview.storageComingSoon":"即将接入","sys.logs.title":"系统日志","sys.logs.kernel":"内核","sys.logs.liveToggle":"实时拉取","sys.logs.refresh":"刷新","sys.logs.liveOffHint":"实时拉取关闭(默认)","sys.logs.loading":"日志加载中...","sys.logs.empty":"暂无日志","sys.placeholder.breadcrumb":"控制台 / 预留模块","sys.placeholder.block":"预留模块","sys.placeholder.desc":"模块壳层已预留,后续将按统一组件体系接入真实数据。","sys.placeholder.status":"STATUS: RESERVED / 当前为结构化占位页面。","sys.placeholder.sensors.title":"传感器诊断台","sys.placeholder.sensors.desc":"用于 IMU、温湿度、电流电压、姿态与时序采样的统一监控面板。","sys.placeholder.sensors.block1":"实时曲线区","sys.placeholder.sensors.block2":"设备健康卡","sys.placeholder.sensors.block3":"阈值告警区","sys.sensors.title":"传感器诊断台","sys.sensors.desc":"磁力计罗盘指北与 MPU-6050 陀螺仪角速度可视化;底层仍为开发者调试 API。","sys.sensors.mag.section":"磁力计(AK09911 / I²C)","sys.sensors.mag.note":"默认总线 1、地址 12(0x0C,CAD 接 GND)。无需在应用层“定义 GPIO”;请确保固件已启用 I²C 且已加载 i2c-dev。","sys.sensors.mag.bus":"I²C 总线号","sys.sensors.mag.addr":"7-bit 地址(十进制,12=0x0C)","sys.sensors.mag.i2cdetect":"附带运行 i2cdetect","sys.sensors.mag.btnSelftest":"运行自检","sys.sensors.mag.btnProbe":"扫描各总线","sys.sensors.mag.btnCalStart":"开始方向校准","sys.sensors.mag.btnCalCommit":"保存并锁定方向","sys.sensors.mag.btnCalReset":"重置到自动模式","sys.sensors.mag.btnCalStatus":"查看校准状态","sys.sensors.mag.running":"执行中…","sys.sensors.mpu.section":"IMU(MPU-6050 / I²C)","sys.sensors.mpu.note":"默认总线 1、地址 104(0x68,AD0 接 GND)。含陀螺仪角速度采样(MPU 内集成加速度计与陀螺仪)。","sys.sensors.mpu.addr":"7-bit 地址(十进制,104=0x68)","sys.sensors.mpu.btnSelftest":"运行 MPU 自检","sys.sensors.mpu.running":"执行中…","sys.sensors.gyro.title":"陀螺仪(MPU-6050)","sys.sensors.gyro.subtitle":"使用上方同一总线/地址;一次读取加速度与陀螺仪,估算横滚/俯仰并显示 3D 板卡示意。","sys.sensors.gyro.btn":"读取角速度","sys.sensors.gyro.loading":"采样中…","sys.sensors.gyro.dps":"角速度","sys.sensors.gyro.raw":"原始值","sys.sensors.gyro.unitDps":"°/s,±250°/s 量程、默认灵敏度","sys.sensors.gyro.unitRaw":"16-bit 有符号","sys.sensors.gyro.hint":"点击「读取角速度」从 IMU 采样(加速度 0x3B + 陀螺仪 0x43)。横滚/俯仰来自重力;ωz 为绕竖直轴角速度,并非绝对航向。","sys.sensors.gyro.errUnknown":"采样失败","sys.sensors.gyro.live":"连续刷新(约 2 Hz)","sys.sensors.gyro.barsTitle":"角速度条(相对 ±250°/s 满偏)","sys.sensors.gyro.rawBlock":"寄存器原始值(16-bit)","sys.sensors.gyro.att3dTitle":"3D 姿态示意","sys.sensors.gyro.att3dDesc":"横滚/俯仰由加速度计重力估算;ωz 为陀螺仪绕竖直轴角速度(非磁航向)。机体系三轴与板卡固连,随姿态在三维空间中转动。","sys.sensors.gyro.axisXLabel":"+X","sys.sensors.gyro.axisYLabel":"+Y","sys.sensors.gyro.axisZLabel":"+Z","sys.sensors.gyro.att3dBodyAxes":"左侧为与电路板固连的机体系:琥珀 +X、天蓝 +Y、紫 +Z(法向);短划为负向参考,整体随横滚/俯仰旋转。","sys.sensors.gyro.att3dRefTitle":"正交轴参考(固定视角)","sys.sensors.gyro.att3dRefDesc":"与左侧同色对应,便于辨认三轴空间关系。","sys.sensors.gyro.roll":"横滚","sys.sensors.gyro.pitch":"俯仰","sys.sensors.gyro.yawRate":"偏航角速度","sys.sensors.compass.tapeCaption":"水平航向带 · 中心为当前读数","sys.sensors.compass.title":"磁力计 · 指北罗盘","sys.sensors.compass.desc":"根据水平面磁场分量估算方向角,用于调试「是否大致指向北方」;精对准需校准与固定安装姿态。","sys.sensors.compass.btn":"刷新罗盘","sys.sensors.compass.loading":"测量中…","sys.sensors.compass.live":"连续刷新","sys.sensors.compass.headingLabel":"水平航向角","sys.sensors.compass.headingHint":"由 atan2(Hx, Hy) 得到,单位度","sys.sensors.compass.cardN":"北","sys.sensors.compass.cardE":"东","sys.sensors.compass.cardS":"南","sys.sensors.compass.cardW":"西","sys.sensors.compass.err":"采样失败","sys.sensors.compass.footnote":"红色针尖表示 XY 平面内磁场水平分量方向;转动设备时指针应随之变化。精对准需软铁/硬铁校准。","sys.sensors.jsonToggle":"展开原始 JSON(排障用)","sys.hmi.title":"人机交互 · SPI 显示","sys.hmi.desc":"通过硬件平面驱动 ST7796(默认 320×320);需 OGSCOPE_DISPLAY_ENABLED=true 且系统已启用 SPI(/dev/spidev0.0)。","sys.hmi.status.section":"显示与硬件平面状态","sys.hmi.status.refresh":"刷新状态","sys.hmi.status.displayEnabled":"DISPLAY_ENABLED","sys.hmi.status.spidev":"/dev/spidev0.0","sys.hmi.status.yes":"存在","sys.hmi.status.no":"不存在","sys.hmi.status.resolution":"分辨率 / DC","sys.hmi.status.driver":"驱动句柄","sys.hmi.status.open":"已打开","sys.hmi.status.closed":"未打开","sys.hmi.status.screenOutput":"逻辑输出","sys.hmi.status.on":"开","sys.hmi.status.off":"关","sys.hmi.status.lastPattern":"上次图案","sys.hmi.status.lastError":"上次错误","sys.hmi.actions.section":"输出到屏幕","sys.hmi.actions.hint":"以下为硬件平面 device.command → hmi;大屏刷新使用较长 RPC 超时。","sys.hmi.actions.smoke":"测试画面(文字+边框)","sys.hmi.actions.colorbars":"彩条","sys.hmi.actions.fill":"填充 RGB","sys.hmi.actions.screenOn":"允许屏幕输出","sys.hmi.actions.screenOff":"暂停屏幕输出","sys.hmi.actions.release":"释放驱动(关闭 SPI/GPIO)","sys.hmi.rawJson":"原始 JSON 响应","sys.placeholder.hmi.title":"人机交互台","sys.placeholder.hmi.desc":"用于 SPI 显示、按键矩阵、背光/对比度与界面回放的调试工作区。","sys.placeholder.hmi.block1":"显示预览区","sys.placeholder.hmi.block2":"输入事件流","sys.placeholder.hmi.block3":"参数与模式控制","sys.placeholder.power.title":"电源与功耗台","sys.placeholder.power.desc":"用于电池、充电、瞬时功耗与热管理策略联动调试。","sys.placeholder.power.block1":"功耗看板","sys.placeholder.power.block2":"电源策略表","sys.placeholder.power.block3":"恢复与保护动作","cam.title":"相机调试控制台","cam.subtitle":"迁移到统一后台架构,保留关键性能优化","cam.btn.system":"系统后台","cam.btn.analysis":"寻星控制台","cam.btn.home":"首页","cam.btn.start":"启动预览","cam.btn.starting":"启动中...","cam.btn.stop":"停止预览","cam.btn.stopping":"停止中...","cam.btn.capture":"拍摄","cam.btn.capturing":"拍摄中...","cam.btn.recordStart":"开始录制","cam.btn.recordStop":"停止录制","cam.btn.recordBusy":"录制切换中...","cam.btn.refresh":"刷新状态","cam.preview.title":"实时预览","cam.preview.state":"状态","cam.preview.mode":"曝光模式","cam.preview.emptyTitle":"预览尚未启动","cam.preview.emptyDesc":"点击下方按钮启动相机预览流。","cam.focus.title":"星点焦点校准","cam.focus.intro":"用原始帧中的多颗星测量焦点质量。开始后缓慢旋转镜头;HFD 越小越清晰,黄色圆圈表示当前参考星。","cam.focus.start":"开始焦点校准","cam.focus.starting":"准备相机…","cam.focus.finish":"完成校准","cam.focus.resetBest":"重置本轮最佳","cam.focus.autoSelect":"恢复自动选星","cam.focus.active":"测量中","cam.focus.idle":"未开始","cam.focus.currentHfd.multi":"当前多星 HFD","cam.focus.currentHfd.single":"当前锁定星 HFD","cam.focus.bestHfd":"本轮最佳","cam.focus.lowerBetter":"越低越好","cam.focus.stars":"可用 / 已测量 / 候选","cam.focus.fwhm":"中位 FWHM","cam.focus.concentration":"核心能量集中度","cam.focus.selectedStar":"参考星","cam.focus.stability":"帧内离散度","cam.focus.history":"最近焦点趋势","cam.focus.historyEmpty":"正在收集焦点样本…","cam.focus.clickHint":"可点击预览锁定一颗星","cam.focus.autoTarget":"自动选择高信噪比星点","cam.focus.manualTarget":"已按点击位置锁定","cam.focus.confidence.high":"高置信度","cam.focus.confidence.medium":"中等置信度","cam.focus.confidence.low":"低置信度,请寻找更多星点","cam.focus.guidance.collecting":"保持设备稳定,正在建立基线…","cam.focus.guidance.noStars":"没有找到可用星点:请检查天空方向、曝光和镜头保护盖。","cam.focus.guidance.best":"已接近本轮最佳;微调并保持数秒,确认读数稳定后锁紧镜头。","cam.focus.guidance.improving":"正在变清晰,继续沿当前方向小幅旋转。","cam.focus.guidance.worsening":"清晰度变差,建议反向微调并回到本轮最佳。","cam.focus.guidance.steady":"变化很小;请用更小角度微调,观察本轮最佳是否下降。","cam.focus.warning.saturated":"部分亮星已饱和并被排除;若可用星过少,请降低曝光或增益。","cam.focus.warning.targetNotFound":"点击位置附近没有可用星点,请点击更靠近星点中心的位置,或恢复自动选星。","cam.focus.warning.undersampled":"检测到小于约 1 像素的锐利星点;工具会保留它们,并结合 FWHM 与能量集中度判断。","cam.focus.warning.hotPixel":"点击目标可能是孤立热像素;请确认它在预览中具有真实星点光晕,或换一颗星。","cam.focus.detectionSummary":"候选 {detected},已测量 {measured},质量筛除 {rejected}。","cam.focus.sessionLocked":"本轮已锁定当前实测曝光和增益、关闭降噪;结束校准时自动恢复。","cam.focus.step.start":"对准星空并开始校准,等待至少三颗可用星。","cam.focus.step.adjust":"缓慢旋转镜头,每次调整后等待曲线稳定。","cam.focus.step.lock":"回到本轮最低 HFD,保持稳定后锁紧镜头。","cam.state.streaming":"流已启动","cam.state.idle":"未启动","cam.state.rec":"录制中","cam.stats.requestFps":"请求 FPS","cam.hint.mjpegSingleStream":"MJPEG 同时连接数有限(默认约两路);超出上限时多标签页会争用或返回忙,请减少同时打开的实时预览。","cam.stats.frameFps":"画面 FPS","cam.stats.fpsMeasureNote":"统计最近约 1 秒内在 32×32 采样上的像素变化次数;多路同时预览、网络较慢、或下方「共享流上限」低于相机目标时,读数常明显偏低。","cam.err.streamBusy":"无法连接预览:MJPEG 名额已被占用","cam.err.streamBusyHint":"请关闭其它标签页或解算台中的设备实时预览,或稍后再试。","cam.err.streamProbeFailed":"无法建立预览流","cam.err.streamProbeDetail":"请检查网络与相机服务状态,或稍后重试。","cam.stats.targetFps":"相机采集目标 FPS","cam.stats.streamPacingFps":"共享流/MJPEG 上限 FPS","cam.stats.streamPacingHint":"与进程环境 OGSCOPE_SHARED_PREVIEW_FPS 一致,约束共享抓帧节奏与每路 MJPEG 的最小帧间隔。","cam.stats.captureFps":"实际采集 FPS","cam.stats.encodeMs":"JPEG 平均编码","cam.stats.encoder":"预览编码器","cam.stats.driver":"相机驱动","cam.stats.metadata":"元数据","cam.stats.consumers":"预览/分析/录像消费者","cam.stats.cameraMemory":"进程内存/Swap","cam.stats.longExposureThrottle":"自动曝光约 {exposure} s,实际帧率受长曝光限制;为保证分析效果未强制缩短曝光。","cam.stats.uptime":"流运行时长","cam.controls.title":"参数控制","cam.controls.core":"核心参数","cam.controls.fps":"帧率","cam.controls.sensorFps":"传感器目标 FPS","cam.controls.previewFps":"预览目标 FPS","cam.controls.resolution":"分辨率","cam.controls.applyRes":"应用分辨率","cam.controls.sampling":"采样模式","cam.controls.exposure":"曝光","cam.controls.gain":"模拟增益","cam.controls.digitalGain":"数字增益","cam.controls.noiseReduction":"降噪级别","cam.controls.noiseReductionMode":"降噪模式","cam.controls.digitalGainReadOnly":"当前驱动仅上报数字增益,不支持手动设置","cam.controls.aeFlicker":"AE 防闪烁","cam.controls.maxAeFrame":"最长自动曝光时间 (s)","cam.controls.contrast":"对比度","cam.controls.brightness":"亮度","cam.controls.saturation":"饱和度","cam.controls.sharpness":"锐度","cam.controls.applySettings":"应用全部设置","cam.controls.applyAll":"保存参数","cam.controls.applyRuntime":"保存运行参数","cam.controls.mode":"模式控制","cam.controls.autoExposure":"曝光模式","cam.controls.whiteBalance":"白平衡","cam.controls.colorMode":"颜色模式","cam.controls.auto":"自动","cam.controls.manual":"手动","cam.controls.night":"夜间","cam.controls.off":"关闭","cam.controls.nr.off":"关闭","cam.controls.nr.fast":"快速","cam.controls.nr.high_quality":"高质量","cam.controls.wb.auto":"自动","cam.controls.wb.daylight":"日光","cam.controls.wb.cloudy":"阴天","cam.controls.wb.tungsten":"钨丝灯","cam.controls.wb.fluorescent":"荧光灯","cam.controls.wb.indoor":"室内","cam.controls.wb.manual":"手动","cam.controls.wb.night":"夜间","cam.controls.color":"彩色","cam.controls.mono":"黑白","cam.controls.applyAe":"应用曝光模式","cam.controls.applyWb":"应用白平衡","cam.controls.applyColor":"应用颜色模式","cam.controls.applyMode":"应用模式设置","cam.controls.lockedByAe":"自动曝光开启时,曝光与增益参数锁定。","cam.controls.manualExposureFpsLimit":"手动曝光 {exposure} 秒时,实际采集帧率最高约 {fps} FPS。","cam.controls.lockedByWb":"当前非手动白平衡模式,R/B 增益参数锁定。","cam.controls.pendingChanges":"有未应用的参数修改","cam.controls.tools":"工具","cam.controls.nightPreset":"夜间预设","cam.controls.nightOn":"开启夜间模式","cam.controls.nightOff":"关闭夜间模式","cam.controls.backup":"备份设置","cam.controls.restore":"恢复设置","cam.controls.reset":"重置相机","cam.quick.title":"快速预设与智能调参","cam.quick.daylight":"白天模式","cam.quick.night":"夜间模式","cam.quick.nightPreset":"应用夜间预设","cam.quick.deep-sky":"深空模式","cam.quick.planetary":"行星模式","cam.quick.autoAdjust":"智能调整","cam.quick.nightHint":"夜间预设用于一键切换夜拍参数;夜间模式开关用于实时启停红外/夜景模式。","cam.system.title":"系统监控","cam.system.status":"相机就绪","cam.system.stream":"流状态","cam.system.sensor":"传感器","cam.system.quality":"图像质量","cam.presets.title":"预设管理","cam.presets.name":"预设名称","cam.presets.desc":"预设描述","cam.presets.save":"保存预设","cam.presets.empty":"暂无预设","cam.presets.noDesc":"无描述","cam.presets.apply":"应用","cam.presets.delete":"删除","cam.files.title":"文件管理","cam.files.refresh":"刷新文件列表","cam.files.exportAll":"导出全部","cam.files.storagePersistent":"拍摄文件保存在设备持久存储中,重启和常规部署不会清除。","cam.files.storageTemporary":"当前使用临时存储,设备重启或断电会清除拍摄文件,请及时导出。","cam.files.empty":"暂无文件","cam.files.download":"下载","cam.files.info":"详情","cam.files.delete":"删除","cam.files.loadingInfo":"正在读取文件详情...","cam.files.prev":"上一页","cam.files.next":"下一页","cam.files.page":"第 {current}/{total} 页","cam.files.closeDetail":"关闭详情","cam.files.size":"文件大小","cam.files.type":"文件类型","cam.files.modified":"修改时间","cam.hist.title":"直方图","cam.hist.enabled":"启用直方图","cam.hist.luminance":"亮度通道","cam.hist.over":"过曝警告","cam.hist.expand":"展开直方图","cam.hist.collapse":"收起直方图","cam.notice.previewStart":"预览已启动","cam.notice.previewStop":"预览已停止","cam.notice.captureSaved":"拍摄完成: {name}","cam.notice.recordStart":"开始录制: {name}","cam.notice.recordStop":"录制已停止","cam.notice.fpsApplied":"帧率已设置为 {fps}","cam.notice.resApplied":"分辨率已设置为 {w}x{h}","cam.notice.samplingApplied":"采样模式已设置为 {mode}","cam.notice.settingsApplied":"参数已应用","cam.notice.aeApplied":"曝光模式已应用","cam.notice.wbApplied":"白平衡已应用","cam.notice.colorApplied":"颜色模式已应用","cam.notice.modeApplied":"模式设置已应用","cam.notice.runtimeApplied":"运行参数已应用","cam.notice.rotationApplied":"旋转已设置为 {value}°","cam.mirror.hint":"相机倒装时可配合旋转使用;镜像后预览与星点解算、极轴引导同坐标系。","cam.mirror.horizontal":"水平镜像","cam.mirror.vertical":"垂直镜像","cam.notice.mirrorApplied":"镜像设置已更新","cam.notice.nightOn":"夜间模式已开启","cam.notice.nightOff":"夜间模式已关闭","cam.notice.nightPreset":"夜间预设已应用","cam.notice.reset":"相机设置已重置","cam.notice.backup":"设置已备份","cam.notice.restore":"设置已恢复","cam.notice.presetSaved":"预设已保存: {name}","cam.notice.presetApplied":"预设已应用: {name}","cam.notice.presetDeleted":"预设已删除: {name}","cam.notice.download":"已开始下载: {name}","cam.notice.downloadWithSidecar":"已下载: {name}(含侧车 {sidecar})","cam.notice.exportAll":"已开始导出全部拍摄文件","cam.notice.fileDeleted":"文件已删除: {name}","cam.notice.quickPresetApplied":"已应用快速预设: {preset}","cam.notice.needPreview":"请先启动预览","cam.notice.needManualForAutoAdjust":"请先切换为手动曝光再进行智能调整","cam.notice.autoAdjusted":"智能调整已完成并生效","cam.confirm.reset":"将重置当前设备的相机设置。确认继续?","cam.confirm.deletePreset":"确定删除预设 {name} 吗?","cam.confirm.deleteFile":"确定删除文件 {name} 吗?","sys.sensors.mag.calStatusPrefix":"校准状态:","sys.sensors.mag.calModeAuto":"自动模式(未锁定)","sys.sensors.mag.calModeRecording":"录制中(请旋转设备)","sys.sensors.mag.calModeLocked":"已锁定(使用保存方向)","sys.sensors.mag.calSamplesPrefix":"录制样本:","sys.sensors.mag.calRecordingHint":"操作提示:保持水平,缓慢旋转设备,建议累计 10+ 样本后保存。","sys.sensors.mag.calLockedHint":"已锁定方向参数:"},g=a.createContext(null),p={zh:C,en:k},h="ogscope.analysis.locale";function u(){const e=window.localStorage.getItem(h);return e==="zh"||e==="en"?e:(navigator.language||"zh").toLowerCase().startsWith("en")?"en":"zh"}function R({children:e}){const[s,t]=a.useState(u),[o,i]=a.useState(p[u()]);a.useEffect(()=>{i(p[s]),window.localStorage.setItem(h,s),document.documentElement.lang=s==="en"?"en":"zh-CN"},[s]);const r=a.useMemo(()=>(n,c)=>{let l=o[n]??n;if(c)for(const[d,b]of Object.entries(c))l=l.replace(new RegExp(`\\{${d}\\}`,"g"),String(b));return l},[o]),m=a.useMemo(()=>({locale:s,setLocale:t,t:r}),[s,r]);return f.jsx(g.Provider,{value:m,children:e})}function A(){const e=a.useContext(g);if(!e)throw new Error("useI18n must be used within I18nProvider");return e}export{R as I,x as T,P as c,A as u}; diff --git a/web/static/analysis-lab/assets/index-Cm13xeJE.css b/web/static/analysis-lab/assets/index-Cm13xeJE.css deleted file mode 100644 index 45e5428..0000000 --- a/web/static/analysis-lab/assets/index-Cm13xeJE.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}.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}.pointer-events-auto{pointer-events:auto}.collapse{visibility:collapse}.static{position:static}.fixed{position:fixed}.absolute{position:absolute}.relative{position:relative}.sticky{position:sticky}.inset-0{top:0;right:0;bottom:0;left:0}.inset-x-0{left:0;right:0}.inset-y-0{top:0;bottom:0}.bottom-0{bottom:0}.bottom-2{bottom:.5rem}.bottom-4{bottom:1rem}.bottom-6{bottom:1.5rem}.left-0{left:0}.left-1\/2{left:50%}.left-2{left:.5rem}.left-3{left:.75rem}.left-\[10px\]{left:10px}.left-\[110px\]{left:110px}.right-0{right:0}.right-2{right:.5rem}.right-3{right:.75rem}.right-4{right:1rem}.top-0{top:0}.top-1{top:.25rem}.top-1\/2{top:50%}.top-12{top:3rem}.top-2{top:.5rem}.top-3{top:.75rem}.top-8{top:2rem}.top-\[18px\]{top:18px}.top-\[88px\]{top:88px}.top-\[94px\]{top:94px}.z-10{z-index:10}.z-20{z-index:20}.z-30{z-index:30}.z-40{z-index:40}.z-50{z-index:50}.z-\[100\]{z-index:100}.z-\[1\]{z-index:1}.order-1{order:1}.order-3{order:3}.col-span-12{grid-column:span 12 / span 12}.col-span-2{grid-column:span 2 / span 2}.mx-auto{margin-left:auto;margin-right:auto}.mb-0\.5{margin-bottom:.125rem}.mb-1{margin-bottom:.25rem}.mb-2{margin-bottom:.5rem}.mb-3{margin-bottom:.75rem}.mb-4{margin-bottom:1rem}.mb-6{margin-bottom:1.5rem}.mb-8{margin-bottom:2rem}.ml-1{margin-left:.25rem}.ml-2{margin-left:.5rem}.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-5{margin-top:1.25rem}.mt-6{margin-top:1.5rem}.mt-auto{margin-top:auto}.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-0{height:0px}.h-1\.5{height:.375rem}.h-10{height:2.5rem}.h-11{height:2.75rem}.h-12{height:3rem}.h-2{height:.5rem}.h-24{height:6rem}.h-3{height:.75rem}.h-3\.5{height:.875rem}.h-4{height:1rem}.h-5{height:1.25rem}.h-7{height:1.75rem}.h-8{height:2rem}.h-9{height:2.25rem}.h-\[100px\]{height:100px}.h-\[112px\]{height:112px}.h-\[152px\]{height:152px}.h-\[188px\]{height:188px}.h-\[460px\]{height:460px}.h-\[88px\]{height:88px}.h-\[calc\(100vh-7rem\)\]{height:calc(100vh - 7rem)}.h-dvh{height:100dvh}.h-full{height:100%}.h-px{height:1px}.max-h-24{max-height:6rem}.max-h-32{max-height:8rem}.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-72{max-height:18rem}.max-h-96{max-height:24rem}.max-h-\[320px\]{max-height:320px}.max-h-\[420px\]{max-height:420px}.max-h-\[70vh\]{max-height:70vh}.max-h-\[min\(22rem\,55vh\)\]{max-height:min(22rem,55vh)}.max-h-\[min\(36vh\,18rem\)\]{max-height:min(36vh,18rem)}.max-h-\[min\(50vh\,28rem\)\]{max-height:min(50vh,28rem)}.max-h-dvh{max-height:100dvh}.min-h-0{min-height:0px}.min-h-14{min-height:3.5rem}.min-h-\[120px\]{min-height:120px}.min-h-\[200px\]{min-height:200px}.min-h-\[220px\]{min-height:220px}.min-h-\[480px\]{min-height:480px}.min-h-screen{min-height:100vh}.w-0{width:0px}.w-0\.5{width:.125rem}.w-10{width:2.5rem}.w-11{width:2.75rem}.w-16{width:4rem}.w-20{width:5rem}.w-24{width:6rem}.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-\[100px\]{width:100px}.w-\[200px\]{width:200px}.w-\[220px\]{width:220px}.w-\[360px\]{width:360px}.w-full{width:100%}.w-px{width:1px}.min-w-0{min-width:0px}.min-w-\[1\.25rem\]{min-width:1.25rem}.min-w-\[12rem\]{min-width:12rem}.min-w-\[15rem\]{min-width:15rem}.min-w-\[16rem\]{min-width:16rem}.min-w-\[200px\]{min-width:200px}.min-w-\[280px\]{min-width:280px}.max-w-2xl{max-width:42rem}.max-w-3xl{max-width:48rem}.max-w-5xl{max-width:64rem}.max-w-6xl{max-width:72rem}.max-w-7xl{max-width:80rem}.max-w-\[1880px\]{max-width:1880px}.max-w-\[220px\]{max-width:220px}.max-w-\[calc\(100\%-1\.5rem\)\]{max-width:calc(100% - 1.5rem)}.max-w-full{max-width:100%}.max-w-md{max-width:28rem}.max-w-sm{max-width:24rem}.max-w-xl{max-width:36rem}.max-w-xs{max-width:20rem}.flex-1{flex:1 1 0%}.shrink-0{flex-shrink:0}.border-separate{border-collapse:separate}.border-spacing-y-2{--tw-border-spacing-y: .5rem;border-spacing:var(--tw-border-spacing-x) var(--tw-border-spacing-y)}.origin-top-left{transform-origin:top left}.-translate-x-full{--tw-translate-x: -100%;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))}.-translate-x-px{--tw-translate-x: -1px;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))}.-translate-y-1\/2{--tw-translate-y: -50%;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))}.translate-x-0{--tw-translate-x: 0px;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))}.translate-x-full{--tw-translate-x: 100%;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))}.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-crosshair{cursor:crosshair}.cursor-pointer{cursor:pointer}.select-none{-webkit-user-select:none;-moz-user-select:none;user-select:none}.resize{resize:both}.scroll-mt-20{scroll-margin-top:5rem}.list-inside{list-style-position:inside}.list-decimal{list-style-type:decimal}.list-disc{list-style-type:disc}.list-none{list-style-type:none}.grid-cols-1{grid-template-columns:repeat(1,minmax(0,1fr))}.grid-cols-12{grid-template-columns:repeat(12,minmax(0,1fr))}.grid-cols-2{grid-template-columns:repeat(2,minmax(0,1fr))}.grid-cols-3{grid-template-columns:repeat(3,minmax(0,1fr))}.flex-col{flex-direction:column}.flex-wrap{flex-wrap:wrap}.items-start{align-items:flex-start}.items-end{align-items:flex-end}.items-center{align-items:center}.items-baseline{align-items:baseline}.justify-end{justify-content:flex-end}.justify-center{justify-content:center}.justify-between{justify-content:space-between}.gap-0\.5{gap:.125rem}.gap-1{gap:.25rem}.gap-1\.5{gap:.375rem}.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))}.space-y-3>:not([hidden])~:not([hidden]){--tw-space-y-reverse: 0;margin-top:calc(.75rem * calc(1 - var(--tw-space-y-reverse)));margin-bottom:calc(.75rem * var(--tw-space-y-reverse))}.space-y-4>:not([hidden])~:not([hidden]){--tw-space-y-reverse: 0;margin-top:calc(1rem * calc(1 - var(--tw-space-y-reverse)));margin-bottom:calc(1rem * var(--tw-space-y-reverse))}.space-y-6>:not([hidden])~:not([hidden]){--tw-space-y-reverse: 0;margin-top:calc(1.5rem * calc(1 - var(--tw-space-y-reverse)));margin-bottom:calc(1.5rem * var(--tw-space-y-reverse))}.self-start{align-self:flex-start}.self-center{align-self:center}.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}.whitespace-nowrap{white-space:nowrap}.break-words{overflow-wrap:break-word}.break-all{word-break:break-all}.rounded{border-radius:.25rem}.rounded-2xl{border-radius:1rem}.rounded-full{border-radius:9999px}.rounded-lg{border-radius:.5rem}.rounded-md{border-radius:.375rem}.rounded-sm{border-radius:.125rem}.rounded-xl{border-radius:.75rem}.border{border-width:1px}.border-2{border-width:2px}.border-x-\[9px\]{border-left-width:9px;border-right-width:9px}.border-b{border-bottom-width:1px}.border-b-2{border-bottom-width:2px}.border-b-\[12px\]{border-bottom-width:12px}.border-l{border-left-width:1px}.border-r{border-right-width:1px}.border-r-2{border-right-width:2px}.border-t{border-top-width:1px}.border-dashed{border-style:dashed}.border-amber-400\/40{border-color:#fbbf2466}.border-amber-500\/40{border-color:#f59e0b66}.border-emerald-400\/40{border-color:#34d39966}.border-emerald-500\/20{border-color:#10b98133}.border-emerald-600\/35{border-color:#05966959}.border-error\/35{border-color:#ffb4ab59}.border-error\/40{border-color:#ffb4ab66}.border-error\/50{border-color:#ffb4ab80}.border-error\/60{border-color:#ffb4ab99}.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-primary\/30{border-color:#adc6ff4d}.border-primary\/40{border-color:#adc6ff66}.border-primary\/45{border-color:#adc6ff73}.border-primary\/50{border-color:#adc6ff80}.border-primary\/70{border-color:#adc6ffb3}.border-red-500\/40{border-color:#ef444466}.border-rose-400\/40{border-color:#fb718566}.border-sky-500\/30{border-color:#0ea5e94d}.border-teal-400\/75{border-color:#2dd4bfbf}.border-teal-500\/25{border-color:#14b8a640}.border-white\/20{border-color:#fff3}.border-white\/30{border-color:#ffffff4d}.border-white\/5{border-color:#ffffff0d}.border-x-transparent{border-left-color:transparent;border-right-color:transparent}.border-b-amber-400{--tw-border-opacity: 1;border-bottom-color:rgb(251 191 36 / var(--tw-border-opacity, 1))}.bg-amber-400{--tw-bg-opacity: 1;background-color:rgb(251 191 36 / var(--tw-bg-opacity, 1))}.bg-amber-400\/95{background-color:#fbbf24f2}.bg-amber-500\/10{background-color:#f59e0b1a}.bg-amber-900\/55{background-color:#78350f8c}.bg-background{--tw-bg-opacity: 1;background-color:rgb(16 19 26 / var(--tw-bg-opacity, 1))}.bg-black{--tw-bg-opacity: 1;background-color:rgb(0 0 0 / var(--tw-bg-opacity, 1))}.bg-black\/20{background-color:#0003}.bg-black\/25{background-color:#00000040}.bg-black\/35{background-color:#00000059}.bg-black\/40{background-color:#0006}.bg-black\/55{background-color:#0000008c}.bg-black\/60{background-color:#0009}.bg-black\/70{background-color:#000000b3}.bg-black\/80{background-color:#000c}.bg-emerald-800\/90{background-color:#065f46e6}.bg-error-container\/15{background-color:#93000a26}.bg-error-container\/20{background-color:#93000a33}.bg-error-container\/95{background-color:#93000af2}.bg-error\/10{background-color:#ffb4ab1a}.bg-error\/80{background-color:#ffb4abcc}.bg-neutral-950{--tw-bg-opacity: 1;background-color:rgb(10 10 10 / var(--tw-bg-opacity, 1))}.bg-neutral-950\/80{background-color:#0a0a0acc}.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\/10{background-color:#4c8eff1a}.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-primary\/5{background-color:#adc6ff0d}.bg-red-500\/10{background-color:#ef44441a}.bg-secondary{--tw-bg-opacity: 1;background-color:rgb(64 229 108 / var(--tw-bg-opacity, 1))}.bg-secondary\/80{background-color:#40e56ccc}.bg-sky-300\/90{background-color:#7dd3fce6}.bg-sky-400{--tw-bg-opacity: 1;background-color:rgb(56 189 248 / var(--tw-bg-opacity, 1))}.bg-sky-400\/95{background-color:#38bdf8f2}.bg-sky-700\/80{background-color:#0369a1cc}.bg-sky-900\/50{background-color:#0c4a6e80}.bg-slate-500\/85{background-color:#64748bd9}.bg-slate-500\/90{background-color:#64748be6}.bg-slate-600\/40{background-color:#47556966}.bg-slate-600\/50{background-color:#47556980}.bg-slate-600\/60{background-color:#47556999}.bg-slate-900\/80{background-color:#0f172acc}.bg-slate-950\/50{background-color:#02061780}.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-high\/40{background-color:#272a3166}.bg-surface-container-high\/95{background-color:#272a31f2}.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-highest\/95{background-color:#32353cf2}.bg-surface-container-low{--tw-bg-opacity: 1;background-color:rgb(25 28 34 / var(--tw-bg-opacity, 1))}.bg-surface-container-low\/70{background-color:#191c22b3}.bg-surface-container-low\/80{background-color:#191c22cc}.bg-surface-container-low\/90{background-color:#191c22e6}.bg-surface-container-low\/95{background-color:#191c22f2}.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}.bg-surface-container\/50{background-color:#1d202680}.bg-surface-container\/60{background-color:#1d202699}.bg-surface-container\/80{background-color:#1d2026cc}.bg-surface-container\/90{background-color:#1d2026e6}.bg-violet-400{--tw-bg-opacity: 1;background-color:rgb(167 139 250 / var(--tw-bg-opacity, 1))}.bg-violet-400\/95{background-color:#a78bfaf2}.bg-violet-900\/45{background-color:#4c1d9573}.bg-white\/5{background-color:#ffffff0d}.bg-white\/80{background-color:#fffc}.bg-white\/90{background-color:#ffffffe6}.bg-gradient-to-b{background-image:linear-gradient(to bottom,var(--tw-gradient-stops))}.bg-gradient-to-br{background-image:linear-gradient(to bottom right,var(--tw-gradient-stops))}.bg-gradient-to-r{background-image:linear-gradient(to right,var(--tw-gradient-stops))}.from-amber-300\/95{--tw-gradient-from: rgb(252 211 77 / .95) var(--tw-gradient-from-position);--tw-gradient-to: rgb(252 211 77 / 0) var(--tw-gradient-to-position);--tw-gradient-stops: var(--tw-gradient-from), var(--tw-gradient-to)}.from-emerald-600{--tw-gradient-from: #059669 var(--tw-gradient-from-position);--tw-gradient-to: rgb(5 150 105 / 0) var(--tw-gradient-to-position);--tw-gradient-stops: var(--tw-gradient-from), var(--tw-gradient-to)}.from-slate-600\/90{--tw-gradient-from: rgb(71 85 105 / .9) var(--tw-gradient-from-position);--tw-gradient-to: rgb(71 85 105 / 0) var(--tw-gradient-to-position);--tw-gradient-stops: var(--tw-gradient-from), var(--tw-gradient-to)}.from-slate-900\/40{--tw-gradient-from: rgb(15 23 42 / .4) var(--tw-gradient-from-position);--tw-gradient-to: rgb(15 23 42 / 0) var(--tw-gradient-to-position);--tw-gradient-stops: var(--tw-gradient-from), var(--tw-gradient-to)}.from-slate-900\/90{--tw-gradient-from: rgb(15 23 42 / .9) var(--tw-gradient-from-position);--tw-gradient-to: rgb(15 23 42 / 0) var(--tw-gradient-to-position);--tw-gradient-stops: var(--tw-gradient-from), var(--tw-gradient-to)}.via-slate-800\/95{--tw-gradient-to: rgb(30 41 59 / 0) var(--tw-gradient-to-position);--tw-gradient-stops: var(--tw-gradient-from), rgb(30 41 59 / .95) var(--tw-gradient-via-position), var(--tw-gradient-to)}.via-slate-900\/70{--tw-gradient-to: rgb(15 23 42 / 0) var(--tw-gradient-to-position);--tw-gradient-stops: var(--tw-gradient-from), rgb(15 23 42 / .7) var(--tw-gradient-via-position), var(--tw-gradient-to)}.to-sky-400\/40{--tw-gradient-to: rgb(56 189 248 / .4) var(--tw-gradient-to-position)}.to-sky-500{--tw-gradient-to: #0ea5e9 var(--tw-gradient-to-position)}.to-slate-950{--tw-gradient-to: #020617 var(--tw-gradient-to-position)}.to-surface-container\/70{--tw-gradient-to: rgb(29 32 38 / .7) var(--tw-gradient-to-position)}.to-surface-container\/90{--tw-gradient-to: rgb(29 32 38 / .9) var(--tw-gradient-to-position)}.fill-current{fill:currentColor}.object-contain{-o-object-fit:contain;object-fit:contain}.object-cover{-o-object-fit:cover;object-fit:cover}.p-1{padding:.25rem}.p-1\.5{padding:.375rem}.p-2{padding:.5rem}.p-3{padding:.75rem}.p-4{padding:1rem}.p-5{padding:1.25rem}.p-6{padding:1.5rem}.px-1{padding-left:.25rem;padding-right:.25rem}.px-1\.5{padding-left:.375rem;padding-right:.375rem}.px-2{padding-left:.5rem;padding-right:.5rem}.px-2\.5{padding-left:.625rem;padding-right:.625rem}.px-3{padding-left:.75rem;padding-right:.75rem}.px-4{padding-left:1rem;padding-right:1rem}.px-6{padding-left:1.5rem;padding-right:1.5rem}.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}.py-3{padding-top:.75rem;padding-bottom:.75rem}.py-4{padding-top:1rem;padding-bottom:1rem}.py-5{padding-top:1.25rem;padding-bottom:1.25rem}.pb-0\.5{padding-bottom:.125rem}.pb-1{padding-bottom:.25rem}.pb-2{padding-bottom:.5rem}.pb-3{padding-bottom:.75rem}.pl-4{padding-left:1rem}.pl-8{padding-left:2rem}.pr-0\.5{padding-right:.125rem}.pr-1{padding-right:.25rem}.pr-2{padding-right:.5rem}.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}.text-right{text-align:right}.align-top{vertical-align:top}.font-body,.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-2xl{font-size:1.5rem;line-height:2rem}.text-3xl{font-size:1.875rem;line-height:2.25rem}.text-4xl{font-size:2.25rem;line-height:2.5rem}.text-\[10px\]{font-size:10px}.text-\[11px\]{font-size:11px}.text-\[13px\]{font-size:13px}.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-xl{font-size:1.25rem;line-height:1.75rem}.text-xs{font-size:.75rem;line-height:1rem}.font-black{font-weight:900}.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-\[0\.14em\]{letter-spacing:.14em}.tracking-\[0\.2em\]{letter-spacing:.2em}.tracking-tight{letter-spacing:-.025em}.tracking-wide{letter-spacing:.025em}.tracking-wider{letter-spacing:.05em}.tracking-widest{letter-spacing:.1em}.text-amber-100{--tw-text-opacity: 1;color:rgb(254 243 199 / var(--tw-text-opacity, 1))}.text-amber-200{--tw-text-opacity: 1;color:rgb(253 230 138 / var(--tw-text-opacity, 1))}.text-amber-200\/95{color:#fde68af2}.text-amber-300{--tw-text-opacity: 1;color:rgb(252 211 77 / var(--tw-text-opacity, 1))}.text-emerald-200{--tw-text-opacity: 1;color:rgb(167 243 208 / var(--tw-text-opacity, 1))}.text-emerald-50{--tw-text-opacity: 1;color:rgb(236 253 245 / var(--tw-text-opacity, 1))}.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\/70{color:#c2c6d6b3}.text-on-surface-variant\/80{color:#c2c6d6cc}.text-on-surface-variant\/85{color:#c2c6d6d9}.text-on-surface-variant\/90{color:#c2c6d6e6}.text-on-surface-variant\/95{color:#c2c6d6f2}.text-on-surface\/80{color:#e1e2ebcc}.text-primary{--tw-text-opacity: 1;color:rgb(173 198 255 / var(--tw-text-opacity, 1))}.text-primary\/80{color:#adc6ffcc}.text-red-200{--tw-text-opacity: 1;color:rgb(254 202 202 / var(--tw-text-opacity, 1))}.text-secondary{--tw-text-opacity: 1;color:rgb(64 229 108 / var(--tw-text-opacity, 1))}.text-sky-100{--tw-text-opacity: 1;color:rgb(224 242 254 / var(--tw-text-opacity, 1))}.text-sky-200{--tw-text-opacity: 1;color:rgb(186 230 253 / var(--tw-text-opacity, 1))}.text-sky-300\/90{color:#7dd3fce6}.text-slate-400{--tw-text-opacity: 1;color:rgb(148 163 184 / var(--tw-text-opacity, 1))}.text-teal-100{--tw-text-opacity: 1;color:rgb(204 251 241 / var(--tw-text-opacity, 1))}.text-teal-200{--tw-text-opacity: 1;color:rgb(153 246 228 / var(--tw-text-opacity, 1))}.text-teal-200\/95{color:#99f6e4f2}.text-violet-200{--tw-text-opacity: 1;color:rgb(221 214 254 / var(--tw-text-opacity, 1))}.text-white{--tw-text-opacity: 1;color:rgb(255 255 255 / var(--tw-text-opacity, 1))}.underline{text-decoration-line:underline}.antialiased{-webkit-font-smoothing:antialiased;-moz-osx-font-smoothing:grayscale}.accent-primary{accent-color:#adc6ff}.opacity-50{opacity:.5}.opacity-90{opacity:.9}.shadow-2xl{--tw-shadow: 0 25px 50px -12px rgb(0 0 0 / .25);--tw-shadow-colored: 0 25px 50px -12px var(--tw-shadow-color);box-shadow:var(--tw-ring-offset-shadow, 0 0 #0000),var(--tw-ring-shadow, 0 0 #0000),var(--tw-shadow)}.shadow-\[0_0_6px_rgba\(255\,255\,255\,0\.6\)\]{--tw-shadow: 0 0 6px rgba(255,255,255,.6);--tw-shadow-colored: 0 0 6px var(--tw-shadow-color);box-shadow:var(--tw-ring-offset-shadow, 0 0 #0000),var(--tw-ring-shadow, 0 0 #0000),var(--tw-shadow)}.shadow-\[0_20px_50px_rgba\(0\,0\,0\,0\.55\)\,inset_0_1px_0_rgba\(255\,255\,255\,0\.08\)\]{--tw-shadow: 0 20px 50px rgba(0,0,0,.55),inset 0 1px 0 rgba(255,255,255,.08);--tw-shadow-colored: 0 20px 50px var(--tw-shadow-color), inset 0 1px 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)}.shadow-lg{--tw-shadow: 0 10px 15px -3px rgb(0 0 0 / .1), 0 4px 6px -4px rgb(0 0 0 / .1);--tw-shadow-colored: 0 10px 15px -3px var(--tw-shadow-color), 0 4px 6px -4px var(--tw-shadow-color);box-shadow:var(--tw-ring-offset-shadow, 0 0 #0000),var(--tw-ring-shadow, 0 0 #0000),var(--tw-shadow)}.shadow-md{--tw-shadow: 0 4px 6px -1px rgb(0 0 0 / .1), 0 2px 4px -2px rgb(0 0 0 / .1);--tw-shadow-colored: 0 4px 6px -1px var(--tw-shadow-color), 0 2px 4px -2px var(--tw-shadow-color);box-shadow:var(--tw-ring-offset-shadow, 0 0 #0000),var(--tw-ring-shadow, 0 0 #0000),var(--tw-shadow)}.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)}.outline-none{outline:2px solid transparent;outline-offset:2px}.ring-1{--tw-ring-offset-shadow: var(--tw-ring-inset) 0 0 0 var(--tw-ring-offset-width) var(--tw-ring-offset-color);--tw-ring-shadow: var(--tw-ring-inset) 0 0 0 calc(1px + var(--tw-ring-offset-width)) var(--tw-ring-color);box-shadow:var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow, 0 0 #0000)}.ring-amber-200\/35{--tw-ring-color: rgb(253 230 138 / .35)}.ring-sky-200\/30{--tw-ring-color: rgb(186 230 253 / .3)}.ring-sky-500\/25{--tw-ring-color: rgb(14 165 233 / .25)}.ring-slate-600\/50{--tw-ring-color: rgb(71 85 105 / .5)}.ring-teal-500\/10{--tw-ring-color: rgb(20 184 166 / .1)}.ring-violet-200\/35{--tw-ring-color: rgb(221 214 254 / .35)}.ring-white\/5{--tw-ring-color: rgb(255 255 255 / .05)}.drop-shadow{--tw-drop-shadow: drop-shadow(0 1px 2px rgb(0 0 0 / .1)) drop-shadow(0 1px 1px rgb(0 0 0 / .06));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)}.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)}.backdrop-blur{--tw-backdrop-blur: blur(8px);-webkit-backdrop-filter:var(--tw-backdrop-blur) var(--tw-backdrop-brightness) var(--tw-backdrop-contrast) var(--tw-backdrop-grayscale) var(--tw-backdrop-hue-rotate) var(--tw-backdrop-invert) var(--tw-backdrop-opacity) var(--tw-backdrop-saturate) var(--tw-backdrop-sepia);backdrop-filter:var(--tw-backdrop-blur) var(--tw-backdrop-brightness) var(--tw-backdrop-contrast) var(--tw-backdrop-grayscale) var(--tw-backdrop-hue-rotate) var(--tw-backdrop-invert) var(--tw-backdrop-opacity) var(--tw-backdrop-saturate) var(--tw-backdrop-sepia)}.backdrop-blur-md{--tw-backdrop-blur: blur(12px);-webkit-backdrop-filter:var(--tw-backdrop-blur) var(--tw-backdrop-brightness) var(--tw-backdrop-contrast) var(--tw-backdrop-grayscale) var(--tw-backdrop-hue-rotate) var(--tw-backdrop-invert) var(--tw-backdrop-opacity) var(--tw-backdrop-saturate) var(--tw-backdrop-sepia);backdrop-filter:var(--tw-backdrop-blur) var(--tw-backdrop-brightness) var(--tw-backdrop-contrast) var(--tw-backdrop-grayscale) var(--tw-backdrop-hue-rotate) var(--tw-backdrop-invert) var(--tw-backdrop-opacity) var(--tw-backdrop-saturate) var(--tw-backdrop-sepia)}.backdrop-blur-xl{--tw-backdrop-blur: blur(24px);-webkit-backdrop-filter:var(--tw-backdrop-blur) var(--tw-backdrop-brightness) var(--tw-backdrop-contrast) var(--tw-backdrop-grayscale) var(--tw-backdrop-hue-rotate) var(--tw-backdrop-invert) var(--tw-backdrop-opacity) var(--tw-backdrop-saturate) var(--tw-backdrop-sepia);backdrop-filter:var(--tw-backdrop-blur) var(--tw-backdrop-brightness) var(--tw-backdrop-contrast) var(--tw-backdrop-grayscale) var(--tw-backdrop-hue-rotate) var(--tw-backdrop-invert) var(--tw-backdrop-opacity) var(--tw-backdrop-saturate) var(--tw-backdrop-sepia)}.transition{transition-property:color,background-color,border-color,text-decoration-color,fill,stroke,opacity,box-shadow,transform,filter,backdrop-filter;transition-timing-function:cubic-bezier(.4,0,.2,1);transition-duration:.15s}.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}.duration-150{transition-duration:.15s}.ease-out{transition-timing-function:cubic-bezier(0,0,.2,1)}.will-change-transform{will-change:transform}html,body,#root{height:100%}.og-scrollbar{scrollbar-width:thin;scrollbar-color:#414754 #191c22}.og-scrollbar::-webkit-scrollbar{width:7px;height:7px}.og-scrollbar::-webkit-scrollbar-track{background:#191c22;border-radius:4px}.og-scrollbar::-webkit-scrollbar-thumb{background:#414754;border-radius:4px}.og-scrollbar::-webkit-scrollbar-thumb:hover{background:#5c6070}.hover\:border-primary:hover{--tw-border-opacity: 1;border-color:rgb(173 198 255 / var(--tw-border-opacity, 1))}.hover\:bg-amber-500\/10:hover{background-color:#f59e0b1a}.hover\:bg-emerald-500\/10:hover{background-color:#10b9811a}.hover\:bg-emerald-700:hover{--tw-bg-opacity: 1;background-color:rgb(4 120 87 / var(--tw-bg-opacity, 1))}.hover\:bg-error-container\/20:hover{background-color:#93000a33}.hover\:bg-error-container\/30:hover{background-color:#93000a4d}.hover\:bg-error\/10:hover{background-color:#ffb4ab1a}.hover\:bg-rose-500\/10:hover{background-color:#f43f5e1a}.hover\:bg-sky-600:hover{--tw-bg-opacity: 1;background-color:rgb(2 132 199 / var(--tw-bg-opacity, 1))}.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\:bg-surface-container\/80:hover{background-color:#1d2026cc}.hover\:bg-white\/5:hover{background-color:#ffffff0d}.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}.hover\:opacity-90:hover{opacity:.9}.focus\:border-primary:focus{--tw-border-opacity: 1;border-color:rgb(173 198 255 / var(--tw-border-opacity, 1))}.disabled\:opacity-40:disabled{opacity:.4}.disabled\:opacity-50:disabled{opacity:.5}@media (min-width: 640px){.sm\:col-span-2{grid-column:span 2 / span 2}.sm\:inline{display:inline}.sm\:grid-cols-2{grid-template-columns:repeat(2,minmax(0,1fr))}.sm\:grid-cols-3{grid-template-columns:repeat(3,minmax(0,1fr))}.sm\:gap-4{gap:1rem}.sm\:text-xl{font-size:1.25rem;line-height:1.75rem}}@media (min-width: 768px){.md\:col-span-1{grid-column:span 1 / span 1}.md\:col-span-3{grid-column:span 3 / span 3}.md\:col-span-4{grid-column:span 4 / span 4}.md\:min-w-\[200px\]{min-width:200px}.md\:max-w-md{max-width:28rem}.md\:grid-cols-2{grid-template-columns:repeat(2,minmax(0,1fr))}.md\:grid-cols-3{grid-template-columns:repeat(3,minmax(0,1fr))}.md\:grid-cols-4{grid-template-columns:repeat(4,minmax(0,1fr))}.md\:flex-row{flex-direction:row}.md\:items-start{align-items:flex-start}.md\:justify-center{justify-content:center}.md\:gap-8{gap:2rem}}@media (min-width: 1024px){.lg\:col-span-3{grid-column:span 3 / span 3}.lg\:col-span-4{grid-column:span 4 / span 4}.lg\:col-span-8{grid-column:span 8 / span 8}.lg\:col-span-9{grid-column:span 9 / span 9}.lg\:ml-64{margin-left:16rem}.lg\:hidden{display:none}.lg\:max-w-lg{max-width:32rem}.lg\:translate-x-0{--tw-translate-x: 0px;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))}.lg\:grid-cols-4{grid-template-columns:repeat(4,minmax(0,1fr))}.lg\:p-6{padding:1.5rem}.lg\:px-8{padding-left:2rem;padding-right:2rem}}@media (min-width: 1280px){.xl\:max-w-\[340px\]{max-width:340px}.xl\:grid-cols-\[minmax\(0\,1\.25fr\)_minmax\(280px\,0\.75fr\)\]{grid-template-columns:minmax(0,1.25fr) minmax(280px,.75fr)}.xl\:flex-row{flex-direction:row}.xl\:items-start{align-items:flex-start}.xl\:justify-between{justify-content:space-between}.xl\:gap-8{gap:2rem}.xl\:self-start{align-self:flex-start}}@media (min-width: 1536px){.\32xl\:static{position:static}.\32xl\:order-1{order:1}.\32xl\:order-2{order:2}.\32xl\:col-span-10{grid-column:span 10 / span 10}.\32xl\:col-span-2{grid-column:span 2 / span 2}.\32xl\:col-span-3{grid-column:span 3 / span 3}.\32xl\:col-span-9{grid-column:span 9 / span 9}.\32xl\:hidden{display:none}.\32xl\:translate-x-0{--tw-translate-x: 0px;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))}.\32xl\:self-start{align-self:flex-start}.\32xl\:shadow-none{--tw-shadow: 0 0 #0000;--tw-shadow-colored: 0 0 #0000;box-shadow:var(--tw-ring-offset-shadow, 0 0 #0000),var(--tw-ring-shadow, 0 0 #0000),var(--tw-shadow)}}.\[\&\:\:-webkit-details-marker\]\:hidden::-webkit-details-marker{display:none} diff --git a/web/static/analysis-lab/assets/refresh-cw-B1jE4hda.js b/web/static/analysis-lab/assets/refresh-cw-krpJBiQY.js similarity index 91% rename from web/static/analysis-lab/assets/refresh-cw-B1jE4hda.js rename to web/static/analysis-lab/assets/refresh-cw-krpJBiQY.js index 50abe47..50a930c 100644 --- a/web/static/analysis-lab/assets/refresh-cw-B1jE4hda.js +++ b/web/static/analysis-lab/assets/refresh-cw-krpJBiQY.js @@ -1,4 +1,4 @@ -import{c as e}from"./index-BoSUzpvv.js";/** +import{c as e}from"./index-BwAjnQW0.js";/** * @license lucide-react v0.460.0 - ISC * * This source code is licensed under the ISC license. diff --git a/web/static/analysis-lab/assets/rotate-ccw-D6okH6-S.js b/web/static/analysis-lab/assets/rotate-ccw-BE-GHC4e.js similarity index 88% rename from web/static/analysis-lab/assets/rotate-ccw-D6okH6-S.js rename to web/static/analysis-lab/assets/rotate-ccw-BE-GHC4e.js index da9f2b3..15d26df 100644 --- a/web/static/analysis-lab/assets/rotate-ccw-D6okH6-S.js +++ b/web/static/analysis-lab/assets/rotate-ccw-BE-GHC4e.js @@ -1,4 +1,4 @@ -import{c as t}from"./index-BoSUzpvv.js";/** +import{c as t}from"./index-BwAjnQW0.js";/** * @license lucide-react v0.460.0 - ISC * * This source code is licensed under the ISC license. diff --git a/web/static/analysis-lab/assets/system-C3fbtn0M.js b/web/static/analysis-lab/assets/system-Bcfh7l2_.js similarity index 99% rename from web/static/analysis-lab/assets/system-C3fbtn0M.js rename to web/static/analysis-lab/assets/system-Bcfh7l2_.js index aa8c7ab..ded3126 100644 --- a/web/static/analysis-lab/assets/system-C3fbtn0M.js +++ b/web/static/analysis-lab/assets/system-Bcfh7l2_.js @@ -1,4 +1,4 @@ -import{r,j as e,a as Re,R as Oe}from"./client-D1ZVDB-N.js";import{u as ye,X as Te,C as Ae,r as K,a as W,S as Fe,b as ze}from"./http-CaEmWEuL.js";import{c as U,u as V,T as Ie,I as De}from"./index-BoSUzpvv.js";import{R as ve}from"./refresh-cw-B1jE4hda.js";/** +import{r,j as e,a as Re,R as Oe}from"./client-D1ZVDB-N.js";import{u as ye,X as Te,C as Ae,r as K,a as W,S as Fe,b as ze}from"./http-Dln9HPhZ.js";import{c as U,u as V,T as Ie,I as De}from"./index-BwAjnQW0.js";import{R as ve}from"./refresh-cw-krpJBiQY.js";/** * @license lucide-react v0.460.0 - ISC * * This source code is licensed under the ISC license. diff --git a/web/static/analysis-lab/camera.html b/web/static/analysis-lab/camera.html index 794148c..ed92669 100644 --- a/web/static/analysis-lab/camera.html +++ b/web/static/analysis-lab/camera.html @@ -4,12 +4,12 @@ OGScope 相机调试控制台 - + - - - - + + + +
diff --git a/web/static/analysis-lab/index.html b/web/static/analysis-lab/index.html index cc7abc9..22a92de 100644 --- a/web/static/analysis-lab/index.html +++ b/web/static/analysis-lab/index.html @@ -4,12 +4,12 @@ OGScope 星空解算控制台 - + - - - - + + + +
diff --git a/web/static/analysis-lab/system.html b/web/static/analysis-lab/system.html index a1d8b12..539b6ca 100644 --- a/web/static/analysis-lab/system.html +++ b/web/static/analysis-lab/system.html @@ -4,12 +4,12 @@ OGScope 系统调试控制台 - + - - - - + + + +
diff --git a/web/static/i18n/analysis.en.json b/web/static/i18n/analysis.en.json index 7af0669..76ada5e 100644 --- a/web/static/i18n/analysis.en.json +++ b/web/static/i18n/analysis.en.json @@ -511,6 +511,9 @@ "cam.presets.delete": "Delete", "cam.files.title": "File Management", "cam.files.refresh": "Refresh File List", + "cam.files.exportAll": "Export All", + "cam.files.storagePersistent": "Captures are stored persistently on this device and survive restarts and routine deployments.", + "cam.files.storageTemporary": "Temporary storage is active. A device restart or power loss clears captures; export them promptly.", "cam.files.empty": "No files", "cam.files.download": "Download", "cam.files.info": "Details", @@ -559,6 +562,7 @@ "cam.notice.presetDeleted": "Preset deleted: {name}", "cam.notice.download": "Download started: {name}", "cam.notice.downloadWithSidecar": "Downloaded: {name} (with sidecar {sidecar})", + "cam.notice.exportAll": "Export of all capture files started", "cam.notice.fileDeleted": "File deleted: {name}", "cam.notice.quickPresetApplied": "Quick preset applied: {preset}", "cam.notice.needPreview": "Start preview first", diff --git a/web/static/i18n/analysis.zh.json b/web/static/i18n/analysis.zh.json index d8f0f22..41663b2 100644 --- a/web/static/i18n/analysis.zh.json +++ b/web/static/i18n/analysis.zh.json @@ -512,6 +512,9 @@ "cam.presets.delete": "删除", "cam.files.title": "文件管理", "cam.files.refresh": "刷新文件列表", + "cam.files.exportAll": "导出全部", + "cam.files.storagePersistent": "拍摄文件保存在设备持久存储中,重启和常规部署不会清除。", + "cam.files.storageTemporary": "当前使用临时存储,设备重启或断电会清除拍摄文件,请及时导出。", "cam.files.empty": "暂无文件", "cam.files.download": "下载", "cam.files.info": "详情", @@ -560,6 +563,7 @@ "cam.notice.presetDeleted": "预设已删除: {name}", "cam.notice.download": "已开始下载: {name}", "cam.notice.downloadWithSidecar": "已下载: {name}(含侧车 {sidecar})", + "cam.notice.exportAll": "已开始导出全部拍摄文件", "cam.notice.fileDeleted": "文件已删除: {name}", "cam.notice.quickPresetApplied": "已应用快速预设: {preset}", "cam.notice.needPreview": "请先启动预览", From abf41b29a12a0a5bb8f7c9e8de3e3a779f3fe93f Mon Sep 17 00:00:00 2001 From: "Richard.Lu" Date: Fri, 4 Sep 2026 16:42:15 +0800 Subject: [PATCH 4/5] =?UTF-8?q?test:=20=E7=BB=9F=E4=B8=80=E8=B0=83?= =?UTF-8?q?=E8=AF=95=E5=AD=98=E5=82=A8=E8=B7=A8=E5=B9=B3=E5=8F=B0=E8=AF=AD?= =?UTF-8?q?=E4=B9=89=20/=20align=20debug=20storage=20semantics=20across=20?= =?UTF-8?q?platforms?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 将系统临时目录明确标记为非持久化 / Mark system temporary directories as non-persistent - 修正 Linux 与 macOS 测试路径差异 / Fix Linux and macOS test path differences --- ogscope/domain/shared/filesystem.py | 7 ++++++- tests/unit/test_debug_files_api.py | 2 +- tests/unit/test_domain_shared_filesystem.py | 4 ++-- 3 files changed, 9 insertions(+), 4 deletions(-) diff --git a/ogscope/domain/shared/filesystem.py b/ogscope/domain/shared/filesystem.py index 432adbf..5af60c1 100644 --- a/ogscope/domain/shared/filesystem.py +++ b/ogscope/domain/shared/filesystem.py @@ -6,6 +6,7 @@ import os import shutil +import tempfile from collections.abc import Iterable from pathlib import Path, PurePath @@ -51,7 +52,11 @@ def ensure_safe_basename(filename: str) -> str: def dev_captures_storage_info(path: Path = DEV_CAPTURES_DIR) -> dict[str, object]: """返回调试拍摄目录的持久化语义 / Describe capture storage persistence.""" resolved = path.expanduser().resolve() - volatile_roots = (Path("/tmp").resolve(), Path("/run").resolve()) + volatile_roots = { + Path("/tmp").resolve(), + Path("/run").resolve(), + Path(tempfile.gettempdir()).resolve(), + } is_temporary = any( resolved == root or root in resolved.parents for root in volatile_roots ) diff --git a/tests/unit/test_debug_files_api.py b/tests/unit/test_debug_files_api.py index 8451fd4..6aeedcb 100644 --- a/tests/unit/test_debug_files_api.py +++ b/tests/unit/test_debug_files_api.py @@ -16,7 +16,7 @@ def test_debug_files_empty(client, temp_debug_dir): payload = response.json() assert payload["files"] == [] assert payload["storage"]["path"] == str(temp_debug_dir) - assert payload["storage"]["is_persistent"] is True + assert payload["storage"]["is_persistent"] is False @pytest.mark.unit diff --git a/tests/unit/test_domain_shared_filesystem.py b/tests/unit/test_domain_shared_filesystem.py index 9108546..9ae0ccb 100644 --- a/tests/unit/test_domain_shared_filesystem.py +++ b/tests/unit/test_domain_shared_filesystem.py @@ -75,8 +75,8 @@ def test_settings_accepts_explicit_debug_capture_directory(tmp_path: Path) -> No @pytest.mark.unit def test_storage_info_distinguishes_persistent_and_temporary(tmp_path: Path) -> None: - persistent = dev_captures_storage_info(tmp_path / "captures") - temporary = dev_captures_storage_info(Path("/tmp/dev_captures")) + persistent = dev_captures_storage_info(Path.cwd() / "data" / "dev_captures") + temporary = dev_captures_storage_info(tmp_path / "captures") assert persistent["is_persistent"] is True assert persistent["persistence"] == "persistent" From 2715d7d352785098bfdd97efaa326f84d60042ec Mon Sep 17 00:00:00 2001 From: "Richard.Lu" Date: Fri, 4 Sep 2026 16:54:06 +0800 Subject: [PATCH 5/5] =?UTF-8?q?fix:=20=E5=85=BC=E5=AE=B9Python=203.10?= =?UTF-8?q?=E5=AE=9E=E6=97=B6=E8=A7=A3=E7=AE=97=E6=97=B6=E9=97=B4=20/=20su?= =?UTF-8?q?pport=20Python=203.10=20realtime=20timestamps?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 使用timezone.utc替代Python 3.11新增的datetime.UTC,并同步更新测试。\nReplace Python 3.11-only datetime.UTC with timezone.utc and update the matching test. --- ogscope/core/realtime/service.py | 6 +++--- tests/unit/test_realtime_api.py | 4 ++-- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/ogscope/core/realtime/service.py b/ogscope/core/realtime/service.py index ca8084a..ded3b13 100644 --- a/ogscope/core/realtime/service.py +++ b/ogscope/core/realtime/service.py @@ -8,7 +8,7 @@ import json import time from dataclasses import dataclass -from datetime import UTC, datetime +from datetime import datetime, timezone from typing import Any from loguru import logger @@ -75,12 +75,12 @@ def _capture_time_payload( ) midpoint_ts = completed_ts - exposure_us / 2_000_000.0 completed_iso = ( - datetime.fromtimestamp(completed_ts, UTC) + datetime.fromtimestamp(completed_ts, timezone.utc) .isoformat() .replace("+00:00", "Z") ) midpoint_iso = ( - datetime.fromtimestamp(midpoint_ts, UTC) + datetime.fromtimestamp(midpoint_ts, timezone.utc) .isoformat() .replace("+00:00", "Z") ) diff --git a/tests/unit/test_realtime_api.py b/tests/unit/test_realtime_api.py index ea119ad..5a066b8 100644 --- a/tests/unit/test_realtime_api.py +++ b/tests/unit/test_realtime_api.py @@ -4,7 +4,7 @@ import asyncio from dataclasses import dataclass -from datetime import UTC, datetime +from datetime import datetime, timezone import numpy as np import pytest @@ -31,7 +31,7 @@ def test_capture_time_payload_uses_exposure_midpoint() -> None: """解算时刻取曝光中点而非解算完成时刻 / Use exposure midpoint, not solve completion.""" from ogscope.core.realtime.service import RealtimeSolveService - completed = datetime(2026, 9, 1, 15, 0, 1, tzinfo=UTC).timestamp() + completed = datetime(2026, 9, 1, 15, 0, 1, tzinfo=timezone.utc).timestamp() payload = RealtimeSolveService._capture_time_payload( completed, {"actual_exposure_us": 1_000_000},