Expose real train artifacts in dashboard

This commit is contained in:
2026-06-30 23:38:03 +08:00
parent fb96c96d8b
commit 3e9e8ba6f5
4 changed files with 70 additions and 4 deletions

View File

@@ -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

View File

@@ -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)

View File

@@ -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<Catalog | null>(null);
const [gpus, setGpus] = useState<GpuPayload | null>(null);
@@ -1195,6 +1207,7 @@ function App() {
<span>{acceptance?.created_at ?? "尚未运行"} {acceptance?.run_id ? `#${acceptance.run_id}` : ""}</span>
<span>{realAcceptance?.created_at ?? "尚未运行"} {realAcceptance?.run_id ? `#${realAcceptance.run_id}` : ""} {realAcceptance?.checks?.filter((item) => item.passed).length ?? 0}/{realAcceptance?.checks?.length ?? 0}</span>
<span>{realTrainAcceptance?.created_at ?? "尚未运行"} {realTrainAcceptance?.run_id ? `#${realTrainAcceptance.run_id}` : ""} {realTrainAcceptance?.checks?.filter((item) => item.passed).length ?? 0}/{realTrainAcceptance?.checks?.length ?? 0}</span>
<AcceptanceArtifactLinks artifacts={realTrainAcceptance?.artifacts} />
<span>{deepAcceptance?.created_at ?? "尚未运行"} {deepAcceptance?.run_id ? `#${deepAcceptance.run_id}` : ""} {deepAcceptance?.checks?.filter((item) => item.passed).length ?? 0}/{deepAcceptance?.checks?.length ?? 0}</span>
<span> 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}</span>
</>
@@ -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 (
<div className="acceptanceArtifacts">
{links.map((item) => (
<a key={`${item.label}-${item.path}`} href={`${API_BASE}/api/artifacts/${item.path}`} target="_blank" rel="noreferrer" title={item.path}>
<span>{item.label}</span>
<small>{item.kind} · {fileName(item.path)}</small>
</a>
))}
</div>
);
}
function DatasetQuality({ validation }: { validation: DatasetValidation }) {
return (
<div className="qualityBox">

View File

@@ -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));