Expose real train artifacts in dashboard
This commit is contained in:
@@ -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
|
`yolo.train_custom`, verifies `results.csv` and `best.pt`, then uses that
|
||||||
trained checkpoint for prediction and GradCAM heatmap jobs. The latest report
|
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
|
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
|
coverage panel as `真实短训`, including direct links to the generated `best.pt`,
|
||||||
acceptance, so run it when you want proof that real uploaded data can create
|
`results.csv`, prediction preview, and heatmap images. This is heavier than
|
||||||
loss curves, trained weights, segmentation previews, and heatmap artifacts.
|
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
|
For stronger runtime proof, `POST /api/acceptance/deep` runs minimal training
|
||||||
loops for the three model families: one SegModel optimizer step, one YOLO
|
loops for the three model families: one SegModel optimizer step, one YOLO
|
||||||
|
|||||||
@@ -114,6 +114,11 @@ def evaluate_project() -> dict:
|
|||||||
and "real_train_yolo_one_epoch_job_runner" in acceptance_text
|
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_predict_job_runner" in acceptance_text
|
||||||
and "real_train_trained_weight_heatmap_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_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,
|
"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"],
|
"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"]:
|
if coverage["unmapped_user_scripts"]:
|
||||||
suggestions.append(f"Map remaining user-facing scripts: {len(coverage['unmapped_user_scripts'])}")
|
suggestions.append(f"Map remaining user-facing scripts: {len(coverage['unmapped_user_scripts'])}")
|
||||||
if not suggestions:
|
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"])
|
passed_count = sum(1 for item in checks if item["passed"])
|
||||||
total_count = max(len(checks), 1)
|
total_count = max(len(checks), 1)
|
||||||
|
|||||||
@@ -169,6 +169,13 @@ type AcceptancePayload = {
|
|||||||
run_id?: string;
|
run_id?: string;
|
||||||
created_at?: string;
|
created_at?: string;
|
||||||
checks?: Array<{ name: string; passed: boolean }>;
|
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?: {
|
model_family_readiness?: {
|
||||||
passed: boolean;
|
passed: boolean;
|
||||||
warnings: Array<{ name: string; 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]}`;
|
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() {
|
function useData() {
|
||||||
const [catalog, setCatalog] = useState<Catalog | null>(null);
|
const [catalog, setCatalog] = useState<Catalog | null>(null);
|
||||||
const [gpus, setGpus] = useState<GpuPayload | 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>最近验收:{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>真实数据:{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>
|
<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>深度验收:{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>
|
<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 }) {
|
function DatasetQuality({ validation }: { validation: DatasetValidation }) {
|
||||||
return (
|
return (
|
||||||
<div className="qualityBox">
|
<div className="qualityBox">
|
||||||
|
|||||||
@@ -681,6 +681,31 @@ textarea {
|
|||||||
overflow-wrap: anywhere;
|
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 {
|
.taskCheckList {
|
||||||
display: grid;
|
display: grid;
|
||||||
grid-template-columns: repeat(2, minmax(0, 1fr));
|
grid-template-columns: repeat(2, minmax(0, 1fr));
|
||||||
|
|||||||
Reference in New Issue
Block a user