import React, { useEffect, useMemo, useRef, useState } from "react"; import { createRoot } from "react-dom/client"; import { Activity, BarChart3, Bot, Boxes, ClipboardCheck, Cpu, Database, FileImage, FileSearch, Gauge, HardDrive, Layers3, Play, RefreshCcw, ShieldCheck, Square, Terminal, UploadCloud, Wand2, Zap } from "lucide-react"; import "./styles.css"; const API_BASE = import.meta.env.VITE_API_BASE ?? "http://localhost:8010"; type JobProgress = { percent: number | null; label: string; stage: string; current: number | null; total: number | null; unit: string | null; source: string; }; type Job = { id: string; type: string; status: string; description: string; command?: string[]; cwd?: string; pid?: number | null; exit_code?: number | null; error?: string | null; created_at: string; started_at?: string; finished_at?: string; log_path?: string; log_tail?: string; log_size?: number; params: Record; progress?: JobProgress; }; type Catalog = { task_types: string[]; task_defaults: Record>; segmodel_architectures: string[]; yolo_models: string[]; mmseg_algorithms: string[]; datasets: Array<{ name: string; path: string; source: string }>; weights: { count: number; total_bytes: number; updated_at?: string }; }; type UploadedDataset = { name: string; description?: string; absolute_layout?: Record<"images" | "labels" | "masks", string>; layout?: Record<"images" | "labels" | "masks", string>; counts: { images: number; labels: number; masks: number }; samples: Record>; }; type DatasetValidation = { dataset: string; counts: { images: number; labels: number; masks: number; annotations: number }; pairs: { image_label: number; image_mask: number; images_without_labels: string[]; labels_without_images: string[]; images_without_masks: string[]; masks_without_images: string[]; }; classes: number[]; checks: Array<{ name: string; passed: boolean; count?: number; labels?: number; masks?: number; errors?: unknown[] }>; ready: { yolo: boolean; mask: boolean; any: boolean }; }; type ResultItem = { name: string; path: string; relative_path: string; size: number; modified: number; kind: string; family?: string; role?: string; previewable?: boolean; }; type CurveSeries = { name: string; points: Array<{ x: number; y: number }>; last: number; min: number; max: number; }; type TrainingCurve = { name: string; file_name: string; relative_path: string; modified: number; family: string; x_key: string; row_count: number; series: CurveSeries[]; }; type DatasetYoloOutputsPayload = { bestWeight?: ResultItem; artifacts: ResultItem[]; curves: TrainingCurve[]; predictions: ResultItem[]; heatmaps: ResultItem[]; }; type WeightEntry = { source_path: string; stored_path: string; size: number; family: string; role: string; sha256?: string; }; type WeightManifest = { generated_at?: string | null; updated_at?: string | null; source_root?: string; count: number; total_bytes: number; files: WeightEntry[]; }; type WeightVerifyPayload = { count: number; ok_count: number; items: Array<{ stored_path: string; exists: boolean; size_ok: boolean; hash_ok?: boolean | null; ok: boolean }>; }; type CoveragePayload = { scripts_total: number; user_scripts_total: number; mapped_user_scripts: number; unmapped_user_scripts: string[]; task_build_passed: boolean; task_build_checks: Array<{ task: string; passed: boolean; script_exists?: boolean; error?: string }>; }; type AcceptancePayload = { available?: boolean; passed?: boolean; run_id?: string; created_at?: string; checks?: Array<{ name: string; passed: boolean }>; model_family_readiness?: { passed: boolean; warnings: Array<{ name: string; passed: boolean }>; checks: Array<{ name: string; passed: boolean; required: boolean }>; }; }; type DeepAcceptancePayload = { available?: boolean; passed?: boolean; run_id?: string; created_at?: string; checks?: Array<{ name: string; passed: boolean }>; }; type GpuPayload = { available: boolean; gpus: Array<{ index: number; name: string; memory_total_mb: number; memory_used_mb: number; memory_free_mb: number; utilization_gpu_percent: number; temperature_c: number; }>; }; type CondaEnvPayload = { available: boolean; envs: Array<{ name: string; path: string; active?: boolean }>; task_default?: string; mmseg_default?: string; }; type RuntimeCheck = { module: string; package?: string; passed: boolean; version?: string | null; error?: string; }; type RuntimeEnv = { role: string; name: string; label: string; env_file: string; path?: string; exists: boolean; passed: boolean; checks: RuntimeCheck[]; extra: Record; }; type RuntimeReadinessPayload = { available: boolean; passed: boolean; generated_at: string; cached: boolean; cache_seconds: number; envs: RuntimeEnv[]; specs: { bootstrap_script: string; verify_script: string; env_files: string[]; task_default: string; mmseg_default: string; }; }; type CapabilityDomain = { id: string; label: string; ready: boolean; tasks: { total: number; required: number; required_ready: number; examples: string[]; missing_required: string[]; unbuildable_required: string[]; }; runtime: Array<{ role: string; name: string; passed: boolean }>; evidence: { count: number; artifacts: Array<{ name: string; relative_path: string; role?: string; family?: string }>; curves: Array<{ name: string; relative_path: string; family?: string; row_count?: number }>; }; gaps: string[]; }; type CapabilityPayload = { passed: boolean; generated_at: string; summary: { ready_domains: number; total_domains: number; mapped_user_scripts: number; user_scripts_total: number; uploaded_datasets: number; artifacts: number; curves: number; weights: number; }; requirements: Array<{ id: string; label: string; passed: boolean; detail: string }>; domains: CapabilityDomain[]; }; type AgentCheck = { name: string; passed: boolean; detail?: unknown; details?: unknown; }; type EvaluationAgentPayload = { agent: string; score: number; checks: AgentCheck[]; suggestions: string[]; }; type ValidationAgentPayload = { agent: string; passed: boolean; checks: AgentCheck[]; }; async function api(path: string, init?: RequestInit): Promise { const res = await fetch(`${API_BASE}${path}`, { headers: { "Content-Type": "application/json" }, ...init }); if (!res.ok) throw new Error(await res.text()); return res.json(); } const defaultParams: Record> = { "mock.echo": { message: "hello from Seg Data Server" }, "dataset.rename": { image_dir: "../DataSet_Own/ORI", label_dir: "../DataSet_Own/Label" }, "dataset.to_png": { input_dir: "../DataSet_Own/ORI", output_dir: "../DataSet_Own/ORI_PNG" }, "dataset.resize": { image_dir: "../DataSet_Own/ORI", label_dir: "../DataSet_Own/Label", width: 1920, height: 1080 }, "dataset.pair": { image_dir: "../DataSet_Own/ORI", label_dir: "../DataSet_Own/Label" }, "dataset.rebuild_labels": { label_dir: "../DataSet_Own/Label" }, "dataset.stack": { image_dir: "../DataSet_Own/ORI", label_dir: "../DataSet_Own/Label", result_dir: "../DataSet_Own/stacked", alpha: 0.3 }, "dataset.stitch": { image_dir: "../DataSet_Own/ORI", label_dir: "../DataSet_Own/Label", result_dir: "../DataSet_Own/stitch" }, "dataset.video_frames": { video: "../Seg_Predict_Own_Video_V2/LC_Video_1.mp4", interval: 0.5, resize: "1920x1080" }, "segmodel.train": { architecture: "Unet" }, "segmodel.predict": { architecture: "Unet", run_choice: 1 }, "yolo.train": { model: "YOLOv8n-seg" }, "yolo.train_custom": { model: "YOLO11n-seg", data: "var/uploads/datasets/example/dataset.yaml", epochs: 10, imgsz: 640, batch: 1, workers: 0, device: "cpu", exist_ok: true }, "yolo.predict": { model: "YOLOv8n-seg", pt_name: "best.pt", conf: 0.2, run_choice: 1 }, "yolo.predict_custom": { weights: "var/custom_yolo_runs/example/weights/best.pt", source: "var/uploads/datasets/example/images", imgsz: 640, conf: 0.25, device: "cpu", name: "example_predict", exist_ok: true }, "yolo.heatmap": { model: "YOLOv8n-seg", cam_method: "All", pt_name: "best.pt", run_choice: 1 }, "yolo.heatmap_custom": { weights: "var/custom_yolo_runs/example/weights/best.pt", source: "var/uploads/datasets/example/images", model_key: "YOLO11n-seg", cam_method: "GradCAM", target_layers: "model.model.model[9]", limit: 3, name: "example_heatmap" }, "mmseg.generate_alg": { dataset_choice: 1, gpu_count: 1, gpu_ids: [0], schedule_mode: 2, max_epochs: 300, algorithm_choice: 1 }, "mmseg.train": { config: "configs/example.py", work_dir: "../DataSet_Public_outputs/example" }, "mmseg.metrics": { input_dir: "../Hardisk", output_dir: "../BestMode_Predict_Results_DataSet_Public", dataset_choice: 1, algorithm_choice: 0 }, "mmseg.flops_fps": { input_dir: "../Hardisk", output_dir: "../BestMode_Predict_Results_DataSet_Public", repeat_times: 3, dataset_choice: 1, algorithm_choice: 0 }, "analysis.all": { input_dir: "../BestMode_Predict_Results_DataSet_Public", output_dir: "./", dataset_choice: 1 } }; const taskLabels: Record = { "dataset.rename": "重命名", "dataset.to_png": "转 PNG", "dataset.resize": "Resize", "dataset.pair": "图片/Label 配对", "dataset.rebuild_labels": "重建 Label", "dataset.stack": "透明叠加", "dataset.stitch": "拼接检查", "dataset.video_frames": "视频抽帧", "dataset.yolo_check_pairs": "YOLO 配对", "dataset.yolo_stack": "YOLO 叠加", "dataset.yolo_rebuild_labels": "YOLO Label", "dataset.yolo_txt_sort": "生成 TXT", "dataset.yolo_convert_png": "批量 PNG", "dataset.yolo_resize": "批量缩放" }; function formatBytes(value?: number) { if (!value) return "0 B"; const units = ["B", "KB", "MB", "GB", "TB"]; let next = value; let unit = 0; while (next >= 1024 && unit < units.length - 1) { next /= 1024; unit += 1; } return `${next.toFixed(unit > 1 ? 2 : 0)} ${units[unit]}`; } function useData() { const [catalog, setCatalog] = useState(null); const [gpus, setGpus] = useState(null); const [condaEnvs, setCondaEnvs] = useState(null); const [jobs, setJobs] = useState([]); const [results, setResults] = useState([]); const [curves, setCurves] = useState([]); const [weightManifest, setWeightManifest] = useState(null); const [datasets, setDatasets] = useState([]); const [datasetValidations, setDatasetValidations] = useState>({}); const [coverage, setCoverage] = useState(null); const [acceptance, setAcceptance] = useState(null); const [deepAcceptance, setDeepAcceptance] = useState(null); const [runtimeReadiness, setRuntimeReadiness] = useState(null); const [capabilities, setCapabilities] = useState(null); const [agentEvaluation, setAgentEvaluation] = useState(null); const [error, setError] = useState(""); async function refresh() { try { const [catalogNext, gpusNext, envsNext, readinessNext, capabilitiesNext, jobsNext, resultsNext, curvesNext, weightsNext, datasetsNext, coverageNext, acceptanceNext, deepAcceptanceNext, agentEvaluationNext] = await Promise.all([ api("/api/catalog"), api("/api/system/gpus"), api("/api/system/envs"), api("/api/system/readiness"), api("/api/capabilities"), api("/api/jobs"), api("/api/results?limit=1000"), api("/api/results/curves?limit=100"), api("/api/weights"), api("/api/datasets"), api("/api/coverage"), api("/api/acceptance/latest"), api("/api/acceptance/deep/latest"), api("/api/agents/evaluate") ]); setCatalog(catalogNext); setGpus(gpusNext); setCondaEnvs(envsNext); setRuntimeReadiness(readinessNext); setCapabilities(capabilitiesNext); setJobs(jobsNext); setResults(resultsNext); setCurves(curvesNext); setWeightManifest(weightsNext); setDatasets(datasetsNext); const validationEntries: Array<[string, DatasetValidation]> = []; await Promise.all( datasetsNext.map(async (dataset) => { try { const validation = await api(`/api/datasets/${encodeURIComponent(dataset.name)}/validate`); validationEntries.push([dataset.name, validation]); } catch { // Dataset validation is advisory; upload and job controls should remain usable. } }) ); setDatasetValidations(Object.fromEntries(validationEntries)); setCoverage(coverageNext); setAcceptance(acceptanceNext); setDeepAcceptance(deepAcceptanceNext); setAgentEvaluation(agentEvaluationNext); setError(""); } catch (err) { setError(String(err)); } } useEffect(() => { refresh(); const timer = window.setInterval(refresh, 5000); return () => window.clearInterval(timer); }, []); return { catalog, gpus, condaEnvs, runtimeReadiness, capabilities, agentEvaluation, jobs, results, curves, weightManifest, datasets, datasetValidations, coverage, acceptance, deepAcceptance, error, refresh }; } function StatusPill({ status }: { status: string }) { return {status}; } function JobProgressBar({ progress }: { progress?: JobProgress }) { const percent = typeof progress?.percent === "number" ? Math.max(0, Math.min(100, progress.percent)) : 0; return (
{progress?.label ?? "等待日志"} {typeof progress?.percent === "number" ? `${progress.percent.toFixed(progress.percent % 1 ? 1 : 0)}%` : "..."}
); } function App() { const { catalog, gpus, condaEnvs, runtimeReadiness, capabilities, agentEvaluation, jobs, results, curves, weightManifest, datasets, datasetValidations, coverage, acceptance, deepAcceptance, error, refresh } = useData(); const [taskType, setTaskType] = useState("mock.echo"); const [params, setParams] = useState(JSON.stringify(defaultParams["mock.echo"], null, 2)); const [selectedJob, setSelectedJob] = useState(null); const [log, setLog] = useState(""); const [busy, setBusy] = useState(false); const [datasetName, setDatasetName] = useState("demo_dataset"); const [datasetDescription, setDatasetDescription] = useState(""); const [selectedDatasetName, setSelectedDatasetName] = useState(""); const [selectedCurvePath, setSelectedCurvePath] = useState(""); const [resultFamilyFilter, setResultFamilyFilter] = useState("all"); const [resultRoleFilter, setResultRoleFilter] = useState("all"); const [selectedGpuIds, setSelectedGpuIds] = useState([]); const [selectedCondaEnv, setSelectedCondaEnv] = useState("auto"); const [uploadKind, setUploadKind] = useState<"images" | "labels" | "masks">("images"); const [uploadFiles, setUploadFiles] = useState(null); const [agentValidation, setAgentValidation] = useState(null); const [weightVerification, setWeightVerification] = useState(null); const [agentBusy, setAgentBusy] = useState(false); const eventSourceRef = useRef(null); useEffect(() => () => { eventSourceRef.current?.close(); }, []); const runningCount = jobs.filter((job) => job.status === "running").length; const successCount = jobs.filter((job) => job.status === "success").length; const failedCount = jobs.filter((job) => job.status === "failed").length; const taskGroups = useMemo>(() => { const items = catalog?.task_types ?? []; return { dataset: items.filter((task) => task.startsWith("dataset.")), segmodel: items.filter((task) => task.startsWith("segmodel.")), yolo: items.filter((task) => task.startsWith("yolo.")), visual: items.filter((task) => task.startsWith("visual.")), mmseg: items.filter((task) => task.startsWith("mmseg.")), analysis: items.filter((task) => task.startsWith("analysis.") || task.startsWith("system.") || task.startsWith("mock.")) }; }, [catalog]); const datasetOps = taskGroups.dataset.filter((task) => task in taskLabels); const selectedDataset = useMemo( () => datasets.find((dataset) => dataset.name === selectedDatasetName) ?? datasets.find((dataset) => dataset.name === datasetName), [datasetName, datasets, selectedDatasetName] ); const selectedValidation = selectedDataset ? datasetValidations[selectedDataset.name] : undefined; const selectedCurve = curves.find((curve) => curve.relative_path === selectedCurvePath) ?? curves[0]; const selectedYoloOutputs = useMemo(() => { if (!selectedDataset) { return { artifacts: [], curves: [], predictions: [], heatmaps: [] }; } const prefixes = [ `var/custom_yolo_runs/${selectedDataset.name}`, `var/custom_yolo_runs/${selectedDataset.name}_predict`, `var/custom_yolo_runs/${selectedDataset.name}_heatmap` ]; const matches = (relativePath: string) => prefixes.some((prefix) => relativePath === prefix || relativePath.startsWith(`${prefix}/`)); const artifacts = results.filter((item) => matches(item.relative_path)); return { bestWeight: artifacts.find((item) => item.relative_path.endsWith("/weights/best.pt")), artifacts, curves: curves.filter((curve) => matches(curve.relative_path)), predictions: artifacts.filter((item) => item.role === "segmentation" && item.previewable), heatmaps: artifacts.filter((item) => item.role === "heatmap" && item.previewable) }; }, [curves, results, selectedDataset]); const selectedYoloWeightReady = Boolean(selectedYoloOutputs.bestWeight); const availableGpus = gpus?.gpus ?? []; const condaEnvOptions = useMemo(() => ["auto", ...Array.from(new Set((condaEnvs?.envs ?? []).map((item) => item.name))).sort()], [condaEnvs]); const selectedGpuDevice = selectedGpuIds.length ? selectedGpuIds.map((_, index) => index).join(",") : "cpu"; const resultFamilyOptions = useMemo(() => ["all", ...Array.from(new Set(results.map((item) => item.family ?? "artifact"))).sort()], [results]); const resultRoleOptions = useMemo(() => ["all", ...Array.from(new Set(results.map((item) => item.role ?? "artifact"))).sort()], [results]); const filteredResults = useMemo( () => results.filter((item) => (resultFamilyFilter === "all" || (item.family ?? "artifact") === resultFamilyFilter) && (resultRoleFilter === "all" || (item.role ?? "artifact") === resultRoleFilter) ), [resultFamilyFilter, resultRoleFilter, results] ); function pickTask(next: string) { setTaskType(next); setParams(JSON.stringify(catalog?.task_defaults?.[next] ?? defaultParams[next] ?? {}, null, 2)); } function pickDatasetTask(next: string) { setTaskType(next); setParams(JSON.stringify(datasetParamsForTask(next), null, 2)); window.location.hash = "jobs"; } function toggleGpu(index: number) { setSelectedGpuIds((current) => current.includes(index) ? current.filter((item) => item !== index) : [...current, index].sort((a, b) => a - b)); } function paramsWithGpuSelection(type: string, rawParams: Record) { const next = { ...rawParams }; if (!selectedGpuIds.length) return next; if (type.startsWith("yolo.") || type.startsWith("visual.")) { next.device = selectedGpuDevice; } if (type.startsWith("mmseg.generate_alg")) { next.gpu_count = selectedGpuIds.length; next.gpu_ids = selectedGpuIds; } return next; } function jobPayload(type: string, rawParams: Record) { const payload: { type: string; params: Record; gpus?: number[]; conda_env?: string } = { type, params: paramsWithGpuSelection(type, rawParams) }; if (selectedGpuIds.length) payload.gpus = selectedGpuIds; if (selectedCondaEnv !== "auto") payload.conda_env = selectedCondaEnv; return payload; } function datasetParamsForTask(next: string): Record { const base = { ...(catalog?.task_defaults?.[next] ?? defaultParams[next] ?? {}) }; const layout = selectedDataset?.absolute_layout; if (!layout) return base; const resultDir = `${layout.images.replace(/\/images$/, "")}/results/${next.replace(".", "_")}`; if (["dataset.pair", "dataset.resize", "dataset.stack", "dataset.stitch", "dataset.yolo_check_pairs", "dataset.yolo_stack"].includes(next)) { return { ...base, image_dir: layout.images, label_dir: layout.labels, result_dir: resultDir }; } if (["dataset.rebuild_labels", "dataset.yolo_rebuild_labels", "dataset.yolo_txt_sort"].includes(next)) { return { ...base, label_dir: layout.labels, folder: layout.labels }; } if (["dataset.to_png", "dataset.yolo_convert_png", "dataset.yolo_resize"].includes(next)) { return { ...base, input_dir: layout.images, output_dir: resultDir, folder: layout.images }; } return base; } async function createJob() { setBusy(true); try { const job = await api("/api/jobs", { method: "POST", body: JSON.stringify(jobPayload(taskType, JSON.parse(params) as Record)) }); await inspectJob(job); await refresh(); } finally { setBusy(false); } } async function syncWeights() { setBusy(true); try { await api("/api/weights/sync", { method: "POST", body: JSON.stringify({ mode: "copy", hash_files: true, skip_existing: true }) }); setWeightVerification(null); await refresh(); } finally { setBusy(false); } } async function verifyWeights() { setBusy(true); try { const result = await api("/api/weights/verify", { method: "POST" }); setWeightVerification(result); } finally { setBusy(false); } } async function runAcceptanceSmoke() { setBusy(true); try { await api("/api/acceptance/smoke", { method: "POST" }); await refresh(); } finally { setBusy(false); } } async function runDeepAcceptance() { setBusy(true); try { await api("/api/acceptance/deep", { method: "POST" }); await refresh(); } finally { setBusy(false); } } async function runAgentValidation() { setAgentBusy(true); try { const result = await api("/api/agents/validate?run_build=false&run_acceptance=false&run_deep=false"); setAgentValidation(result); } finally { setAgentBusy(false); } } async function createDataset() { setBusy(true); try { await api("/api/datasets", { method: "POST", body: JSON.stringify({ name: datasetName, description: datasetDescription }) }); setSelectedDatasetName(datasetName); await refresh(); } finally { setBusy(false); } } async function uploadDatasetFiles() { if (!uploadFiles || uploadFiles.length === 0) return; setBusy(true); try { const body = new FormData(); Array.from(uploadFiles).forEach((file) => body.append("files", file)); const res = await fetch(`${API_BASE}/api/datasets/${encodeURIComponent(datasetName)}/upload/${uploadKind}`, { method: "POST", body }); if (!res.ok) throw new Error(await res.text()); await refresh(); } finally { setBusy(false); } } function customYoloWeightPath(dataset: UploadedDataset) { const expected = `var/custom_yolo_runs/${dataset.name}/weights/best.pt`; return selectedYoloOutputs.bestWeight?.relative_path ?? results.find((item) => item.relative_path === expected || item.relative_path.endsWith(`/custom_yolo_runs/${dataset.name}/weights/best.pt`))?.relative_path ?? expected; } async function createSelectedYoloYaml() { if (!selectedDataset) return; const classNames = selectedValidation?.classes.map((classId) => `class_${classId}`) ?? undefined; return api<{ relative_path: string; path: string }>(`/api/datasets/${encodeURIComponent(selectedDataset.name)}/yolo-yaml`, { method: "POST", body: JSON.stringify({ class_names: classNames }) }); } async function generateSelectedYoloYaml() { if (!selectedDataset) return; setBusy(true); try { const generated = await createSelectedYoloYaml(); if (!generated) return; setTaskType("yolo.train_custom"); setParams(JSON.stringify(paramsWithGpuSelection("yolo.train_custom", { model: "YOLO11n-seg", data: generated.path, epochs: 10, imgsz: 640, batch: 1, workers: 0, device: selectedGpuDevice, project: "var/custom_yolo_runs", name: selectedDataset.name, exist_ok: true }), null, 2)); await refresh(); } finally { setBusy(false); } } async function startSelectedYoloTrain() { if (!selectedDataset) return; setBusy(true); try { const generated = await createSelectedYoloYaml(); if (!generated) return; const job = await api("/api/jobs", { method: "POST", body: JSON.stringify(jobPayload("yolo.train_custom", { model: "YOLO11n-seg", data: generated.path, epochs: 10, imgsz: 640, batch: 1, workers: 0, device: selectedGpuDevice, project: "var/custom_yolo_runs", name: selectedDataset.name, exist_ok: true })) }); await inspectJob(job); window.location.hash = "jobs"; await refresh(); } finally { setBusy(false); } } async function startSelectedYoloPredict() { if (!selectedDataset?.absolute_layout) return; setBusy(true); try { const job = await api("/api/jobs", { method: "POST", body: JSON.stringify(jobPayload("yolo.predict_custom", { weights: customYoloWeightPath(selectedDataset), source: selectedDataset.absolute_layout.images, imgsz: 640, conf: 0.25, device: selectedGpuDevice, project: "var/custom_yolo_runs", name: `${selectedDataset.name}_predict`, exist_ok: true })) }); await inspectJob(job); window.location.hash = "jobs"; await refresh(); } finally { setBusy(false); } } async function startSelectedYoloHeatmap() { if (!selectedDataset?.absolute_layout) return; setBusy(true); try { const job = await api("/api/jobs", { method: "POST", body: JSON.stringify(jobPayload("yolo.heatmap_custom", { weights: customYoloWeightPath(selectedDataset), source: selectedDataset.absolute_layout.images, model_key: "YOLO11n-seg", cam_method: "GradCAM", target_layers: "model.model.model[9]", limit: 3, device: selectedGpuDevice, project: "var/custom_yolo_runs", name: `${selectedDataset.name}_heatmap` })) }); await inspectJob(job); window.location.hash = "jobs"; await refresh(); } finally { setBusy(false); } } async function inspectJob(job: Job) { eventSourceRef.current?.close(); const detail = await api(`/api/jobs/${job.id}`); setSelectedJob(detail); setLog(detail.log_tail ?? ""); const source = new EventSource(`${API_BASE}/api/jobs/${job.id}/events?offset=${detail.log_size ?? 0}`); eventSourceRef.current = source; source.onmessage = (event) => { const payload = JSON.parse(event.data); if (payload.chunk) setLog((prev) => `${prev}${payload.chunk}`); setSelectedJob(payload.job); if (["success", "failed", "cancelled"].includes(payload.job.status)) { source.close(); if (eventSourceRef.current === source) eventSourceRef.current = null; } }; source.onerror = () => { source.close(); if (eventSourceRef.current === source) eventSourceRef.current = null; }; } async function cancelSelectedJob() { if (!selectedJob) return; await api(`/api/jobs/${selectedJob.id}/cancel`, { method: "POST" }); await refresh(); } return (

Segmentation Operations

训练、预测、分析与权重资产控制台

{error &&
{error}
}
运行中 {runningCount}
成功 {successCount}
失败 {failedCount}
上传集 {datasets.length}
任务 {catalog?.task_types.length ?? 0}

Capability Matrix

全功能矩阵

{capabilities?.summary.ready_domains ?? 0}/{capabilities?.summary.total_domains ?? 0}
脚本{capabilities?.summary.mapped_user_scripts ?? 0}/{capabilities?.summary.user_scripts_total ?? 0}
数据集{capabilities?.summary.uploaded_datasets ?? 0}
结果{capabilities?.summary.artifacts ?? 0}
曲线{capabilities?.summary.curves ?? 0}
权重{capabilities?.summary.weights ?? 0}
{(capabilities?.domains ?? []).map((domain) => (
{domain.label} {domain.ready ? "READY" : "CHECK"}
{domain.tasks.required_ready}/{domain.tasks.required} required {domain.evidence.count} evidence
{domain.tasks.examples.slice(0, 4).map((task) => {task})}
))}
{(capabilities?.requirements ?? []).map((item) => ( {item.label} ))}

Job Builder

创建任务

{Object.entries(taskGroups).map(([group, values]) => (
{group} {values.map((task) => ( ))}
))}
GPU 设备 {selectedGpuIds.length ? selectedGpuIds.map((item) => `GPU ${item}`).join(", ") : "CPU"}
{availableGpus.map((gpu) => ( ))}