import { useEffect, useMemo, useState } from "react"; import { useOutletContext } from "react-router-dom"; import { api, type CatalogReport, type JobRecord, type PendingReport } from "../api/client"; import { useToast } from "../components/Toast"; import { LANE_DATA_VIZ_ENABLED } from "../config/featureFlags"; type Ctx = { refreshMeta: () => void }; type IterAction = "train_dms" | "train_lane" | "eval_dms" | "eval_lane" | "visualize_dms" | "visualize_lane"; const ITERATE_ACTIONS = new Set([ "train_dms", "train_lane", "eval_dms", "eval_lane", "visualize_dms", "visualize_lane", ]); const ACTION_META: Record = { train_dms: { label: "DMS 训练", project: "DMS", kind: "训练" }, train_lane: { label: "Lane 训练", project: "Lane", kind: "训练" }, eval_dms: { label: "DMS 评估", project: "DMS", kind: "评估" }, eval_lane: { label: "Lane 评估", project: "Lane", kind: "评估" }, visualize_dms: { label: "DMS 可视化", project: "DMS", kind: "可视化" }, visualize_lane: { label: "Lane 可视化", project: "Lane", kind: "可视化" }, }; function statusBadgeClass(status: string): string { if (status === "succeeded") return "badge-promoted"; if (status === "running") return "badge-training"; if (status === "failed") return "badge-pending"; return "badge-idle"; } function fmtTime(ts?: string): string { if (!ts) return "—"; const d = new Date(ts); if (Number.isNaN(d.getTime())) return ts; return d.toLocaleString("zh-CN", { hour12: false }); } function extractWeight(job: JobRecord): string | null { const res = (job.result || {}) as Record; const params = (job.params || {}) as Record; const candidates = [res.best_weights, res.candidate, res.model_path, res.weights, params.weights, params.model_path]; for (const v of candidates) { if (typeof v === "string" && v.trim()) return v; } return null; } export function IteratePage() { const { refreshMeta } = useOutletContext(); const toast = useToast(); const [pending, setPending] = useState(null); const [cat, setCat] = useState(null); const [jobs, setJobs] = useState([]); const [dmsTask, setDmsTask] = useState("dam"); const [dmsTrack, setDmsTrack] = useState<"platform" | "local">("platform"); const [dmsWeights, setDmsWeights] = useState(""); const [laneTrack, setLaneTrack] = useState<"platform" | "local">("platform"); const [laneModelPath, setLaneModelPath] = useState(""); const [laneDataRoot, setLaneDataRoot] = useState(""); const [laneTestList, setLaneTestList] = useState("list/test_gt.txt"); const [selectedJobId, setSelectedJobId] = useState(null); const loadAll = async () => { const [p, c, j] = await Promise.all([api.pending(), api.catalog(), api.listJobs()]); setPending(p); setCat(c); setJobs(j.items || []); }; useEffect(() => { loadAll(); }, []); useEffect(() => { const tasks = Object.keys(pending?.projects?.dms?.task_defs || {}); if (tasks.length > 0 && !tasks.includes(dmsTask)) { setDmsTask(tasks[0]); } }, [pending]); const submit = async (action: string, params: Record) => { try { await api.submitApproval(action, params); toast(`已提交 ${action}`); refreshMeta(); await loadAll(); } catch (e) { toast(String(e), true); } }; const packRows = [ ...(pending?.projects?.dms?.active_packs || []).map((n) => ({ project: "dms", name: n, enabled: true })), ...(pending?.projects?.dms?.not_enabled || []).map((n) => ({ project: "dms", name: n, enabled: false })), ...Object.entries(cat?.lane || {}).map(([n, info]) => ({ project: "lane", name: n, enabled: info.enabled })), ]; const iterJobs = useMemo(() => { return jobs.filter((j) => ITERATE_ACTIONS.has(j.action as IterAction)).slice(0, 30); }, [jobs]); const milestones = useMemo(() => [...iterJobs].reverse(), [iterJobs]); const selectedJob = useMemo( () => milestones.find((j) => j.id === selectedJobId) || milestones[milestones.length - 1] || null, [milestones, selectedJobId] ); useEffect(() => { if (!selectedJobId && milestones.length > 0) { setSelectedJobId(milestones[milestones.length - 1].id); } }, [milestones, selectedJobId]); const dmsTasks = Object.keys(pending?.projects?.dms?.task_defs || {}); return ( <>

DMS(YOLO)一键训练/评估/可视化

Lane(UFLD)一键训练/评估{LANE_DATA_VIZ_ENABLED ? "/可视化" : ""}

{LANE_DATA_VIZ_ENABLED && ( )}

训练数据包状态

{packRows.map((p) => (
{p.project}/{p.name} {p.enabled ? "已启用" : "未启用"} python as.py enable {p.project} {p.name}
))}

训练里程碑节点(可追溯)

{milestones.length === 0 ? (
暂无训练/评估节点
) : (
{milestones.map((j) => { const action = j.action as IterAction; const meta = ACTION_META[action]; const weight = extractWeight(j); return ( ); })}
{selectedJob && (

{ACTION_META[selectedJob.action as IterAction]?.label || selectedJob.action}

{selectedJob.status}
Job ID
{selectedJob.id}
创建时间
{fmtTime(selectedJob.created_at)}
开始时间
{fmtTime(selectedJob.started_at)}
结束时间
{fmtTime(selectedJob.finished_at)}
关键权重
{extractWeight(selectedJob) || "—"}
执行参数
{JSON.stringify(selectedJob.params || {}, null, 2)}
执行结果
{JSON.stringify(selectedJob.result || {}, null, 2)}
)}
)}
); }