From 3e9e8ba6f54b84e52ff542b47f82c6279edc438a Mon Sep 17 00:00:00 2001 From: admin <572701190@qq.com> Date: Tue, 30 Jun 2026 23:38:03 +0800 Subject: [PATCH] Expose real train artifacts in dashboard --- README.md | 8 +++--- backend/app/agents/evaluation_agent.py | 7 +++++- frontend/src/main.tsx | 34 ++++++++++++++++++++++++++ frontend/src/styles.css | 25 +++++++++++++++++++ 4 files changed, 70 insertions(+), 4 deletions(-) diff --git a/README.md b/README.md index d462de5..b5cc66e 100644 --- a/README.md +++ b/README.md @@ -279,9 +279,11 @@ image/txt pair, generates `dataset.yaml`, runs one CPU epoch through `yolo.train_custom`, verifies `results.csv` and `best.pt`, then uses that trained checkpoint for prediction and GradCAM heatmap jobs. The latest report is available from `GET /api/acceptance/real-train/latest` and is shown in the -coverage panel as `真实短训`. This is heavier than the real-data predict -acceptance, so run it when you want proof that real uploaded data can create -loss curves, trained weights, segmentation previews, and heatmap artifacts. +coverage panel as `真实短训`, including direct links to the generated `best.pt`, +`results.csv`, prediction preview, and heatmap images. This is heavier than +the real-data predict acceptance, so run it when you want proof that real +uploaded data can create loss curves, trained weights, segmentation previews, +and heatmap artifacts. For stronger runtime proof, `POST /api/acceptance/deep` runs minimal training loops for the three model families: one SegModel optimizer step, one YOLO diff --git a/backend/app/agents/evaluation_agent.py b/backend/app/agents/evaluation_agent.py index 1af07ca..2701687 100644 --- a/backend/app/agents/evaluation_agent.py +++ b/backend/app/agents/evaluation_agent.py @@ -114,6 +114,11 @@ def evaluate_project() -> dict: and "real_train_yolo_one_epoch_job_runner" in acceptance_text and "real_train_trained_weight_predict_job_runner" in acceptance_text and "real_train_trained_weight_heatmap_job_runner" in acceptance_text, + "real_train_artifact_links_ui": "AcceptanceArtifactLinks" in frontend_text + and "acceptanceArtifacts" in frontend_text + and "best_weight" in frontend_text + and "results_csv" in frontend_text + and "heatmap_outputs" in frontend_text, "agent_api": "/api/agents/evaluate" in backend_text and "/api/agents/validate" in backend_text, "agent_panel_ui": "runAgentValidation" in frontend_text and "评价建议" in frontend_text and "Validation Agent" in frontend_text, "coverage_api": "/api/coverage" in backend_text and coverage["task_build_passed"], @@ -138,7 +143,7 @@ def evaluate_project() -> dict: if coverage["unmapped_user_scripts"]: suggestions.append(f"Map remaining user-facing scripts: {len(coverage['unmapped_user_scripts'])}") if not suggestions: - suggestions.append("Current platform covers the requested control-plane features, uploaded YOLO dataset train/predict/heatmap actions, live uploaded-data YOLO predict/heatmap acceptance, real workspace data acceptance, real short-train acceptance, and synthetic deep training acceptance; next focus is a longer operator-run task on a full dataset.") + suggestions.append("Current platform covers the requested control-plane features, uploaded YOLO dataset train/predict/heatmap actions, live uploaded-data YOLO predict/heatmap acceptance, real workspace data acceptance, real short-train acceptance with artifact links, and synthetic deep training acceptance; next focus is a longer operator-run task on a full dataset.") passed_count = sum(1 for item in checks if item["passed"]) total_count = max(len(checks), 1) diff --git a/frontend/src/main.tsx b/frontend/src/main.tsx index 56caaf4..034e15f 100644 --- a/frontend/src/main.tsx +++ b/frontend/src/main.tsx @@ -169,6 +169,13 @@ type AcceptancePayload = { run_id?: string; created_at?: string; checks?: Array<{ name: string; passed: boolean }>; + artifacts?: { + train_root?: string; + best_weight?: string | null; + results_csv?: string | null; + predict_outputs?: string[]; + heatmap_outputs?: string[]; + }; model_family_readiness?: { passed: boolean; warnings: Array<{ name: string; passed: boolean }>; @@ -367,6 +374,11 @@ function formatBytes(value?: number) { return `${next.toFixed(unit > 1 ? 2 : 0)} ${units[unit]}`; } +function fileName(path?: string | null) { + if (!path) return ""; + return path.split("/").filter(Boolean).pop() ?? path; +} + function useData() { const [catalog, setCatalog] = useState(null); const [gpus, setGpus] = useState(null); @@ -1195,6 +1207,7 @@ function App() { 最近验收:{acceptance?.created_at ?? "尚未运行"} {acceptance?.run_id ? `#${acceptance.run_id}` : ""} 真实数据:{realAcceptance?.created_at ?? "尚未运行"} {realAcceptance?.run_id ? `#${realAcceptance.run_id}` : ""},通过 {realAcceptance?.checks?.filter((item) => item.passed).length ?? 0}/{realAcceptance?.checks?.length ?? 0} 真实短训:{realTrainAcceptance?.created_at ?? "尚未运行"} {realTrainAcceptance?.run_id ? `#${realTrainAcceptance.run_id}` : ""},通过 {realTrainAcceptance?.checks?.filter((item) => item.passed).length ?? 0}/{realTrainAcceptance?.checks?.length ?? 0} + 深度验收:{deepAcceptance?.created_at ?? "尚未运行"} {deepAcceptance?.run_id ? `#${deepAcceptance.run_id}` : ""},通过 {deepAcceptance?.checks?.filter((item) => item.passed).length ?? 0}/{deepAcceptance?.checks?.length ?? 0} 模型族 readiness:{acceptance?.model_family_readiness?.checks?.filter((item) => item.passed).length ?? 0}/{acceptance?.model_family_readiness?.checks?.length ?? 0},warnings {acceptance?.model_family_readiness?.warnings?.length ?? 0} @@ -1595,6 +1608,27 @@ function ResultBrowser({ ); } +function AcceptanceArtifactLinks({ artifacts }: { artifacts?: AcceptancePayload["artifacts"] }) { + if (!artifacts) return null; + const links = [ + artifacts.best_weight && { label: "best.pt", path: artifacts.best_weight, kind: "weight" }, + artifacts.results_csv && { label: "loss csv", path: artifacts.results_csv, kind: "curve" }, + ...(artifacts.predict_outputs ?? []).slice(0, 2).map((path, index) => ({ label: `predict ${index + 1}`, path, kind: "segmentation" })), + ...(artifacts.heatmap_outputs ?? []).slice(0, 4).map((path, index) => ({ label: `heatmap ${index + 1}`, path, kind: "heatmap" })) + ].filter(Boolean) as Array<{ label: string; path: string; kind: string }>; + if (!links.length) return null; + return ( +
+ {links.map((item) => ( + + {item.label} + {item.kind} · {fileName(item.path)} + + ))} +
+ ); +} + function DatasetQuality({ validation }: { validation: DatasetValidation }) { return (
diff --git a/frontend/src/styles.css b/frontend/src/styles.css index 4c73715..949d1f8 100644 --- a/frontend/src/styles.css +++ b/frontend/src/styles.css @@ -681,6 +681,31 @@ textarea { overflow-wrap: anywhere; } +.acceptanceArtifacts { + display: grid; + grid-template-columns: repeat(2, minmax(0, 1fr)); + gap: 8px; +} + +.acceptanceArtifacts a { + min-width: 0; + display: grid; + gap: 3px; + padding: 8px; + border-radius: 6px; + border: 1px solid var(--line); + background: #101310; + color: var(--ink); + text-decoration: none; +} + +.acceptanceArtifacts span, +.acceptanceArtifacts small { + overflow: hidden; + text-overflow: ellipsis; + white-space: nowrap; +} + .taskCheckList { display: grid; grid-template-columns: repeat(2, minmax(0, 1fr));