Load full artifact scan in dashboard
This commit is contained in:
12
README.md
12
README.md
@@ -57,9 +57,9 @@ written under `var/custom_yolo_runs` and are scanned by the results dashboard.
|
|||||||
When a dataset is selected, the dataset panel shows its custom YOLO `best.pt`,
|
When a dataset is selected, the dataset panel shows its custom YOLO `best.pt`,
|
||||||
prediction previews, heatmap previews, and inline training curve previews.
|
prediction previews, heatmap previews, and inline training curve previews.
|
||||||
Segmentation previews, YOLO heatmaps, and loss/metric artifacts are grouped on
|
Segmentation previews, YOLO heatmaps, and loss/metric artifacts are grouped on
|
||||||
the results dashboard. The artifact browser can filter by model family and
|
the results dashboard. The artifact browser loads the full result scan, can
|
||||||
artifact role, and YOLO-style `results.csv` files are parsed into lightweight
|
filter by model family and artifact role, and YOLO-style `results.csv` files
|
||||||
training curves.
|
are parsed into lightweight training curves.
|
||||||
Job APIs and the SSE log stream also expose structured progress parsed from
|
Job APIs and the SSE log stream also expose structured progress parsed from
|
||||||
YOLO, MMSeg/MMEngine, SegModel-style epoch logs, and generic tqdm percentages,
|
YOLO, MMSeg/MMEngine, SegModel-style epoch logs, and generic tqdm percentages,
|
||||||
so the queue and live log panel can show stage, epoch/iteration, and percent
|
so the queue and live log panel can show stage, epoch/iteration, and percent
|
||||||
@@ -163,8 +163,10 @@ Use `GET /api/coverage` to inspect script-to-task coverage and task
|
|||||||
buildability.
|
buildability.
|
||||||
Use `GET /api/capabilities` to inspect the grouped full-function readiness
|
Use `GET /api/capabilities` to inspect the grouped full-function readiness
|
||||||
matrix used by the web dashboard and agents.
|
matrix used by the web dashboard and agents.
|
||||||
Use `GET /api/results/curves` to inspect parsed training curves discovered
|
Use `GET /api/results?limit=1000` to inspect browsable artifacts and
|
||||||
from YOLO, SegModel, MMSeg, visual-tool, and analysis output directories.
|
`GET /api/results/curves?limit=100` to inspect parsed training curves
|
||||||
|
discovered from YOLO, SegModel, MMSeg, visual-tool, and analysis output
|
||||||
|
directories.
|
||||||
Use `GET /api/agents/evaluate` and `GET /api/agents/validate` to surface the
|
Use `GET /api/agents/evaluate` and `GET /api/agents/validate` to surface the
|
||||||
same evaluation and validation feedback shown in the web dashboard Agent panel.
|
same evaluation and validation feedback shown in the web dashboard Agent panel.
|
||||||
|
|
||||||
|
|||||||
@@ -54,7 +54,10 @@ def evaluate_project() -> dict:
|
|||||||
and "resultFamilyFilter" in frontend_text
|
and "resultFamilyFilter" in frontend_text
|
||||||
and "resultRoleFilter" in frontend_text
|
and "resultRoleFilter" in frontend_text
|
||||||
and "familyOptions" in frontend_text
|
and "familyOptions" in frontend_text
|
||||||
and "roleOptions" in frontend_text,
|
and "roleOptions" in frontend_text
|
||||||
|
and "/api/results?limit=1000" in frontend_text
|
||||||
|
and "setResults(resultsNext)" in frontend_text
|
||||||
|
and "slice(0, 240)" not in frontend_text,
|
||||||
"job_progress_ui": "JobProgressBar" in frontend_text and "progressTrack" in frontend_text,
|
"job_progress_ui": "JobProgressBar" in frontend_text and "progressTrack" in frontend_text,
|
||||||
"runtime_readiness_ui": "runtimeReadiness" in frontend_text and "环境就绪" in frontend_text,
|
"runtime_readiness_ui": "runtimeReadiness" in frontend_text and "环境就绪" in frontend_text,
|
||||||
"capability_matrix_ui": "capabilities" in frontend_text and "全功能矩阵" in frontend_text,
|
"capability_matrix_ui": "capabilities" in frontend_text and "全功能矩阵" in frontend_text,
|
||||||
|
|||||||
@@ -4,7 +4,7 @@ import asyncio
|
|||||||
import json
|
import json
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
|
|
||||||
from fastapi import FastAPI, File, HTTPException, UploadFile
|
from fastapi import FastAPI, File, HTTPException, Query, UploadFile
|
||||||
from fastapi.middleware.cors import CORSMiddleware
|
from fastapi.middleware.cors import CORSMiddleware
|
||||||
from fastapi.responses import FileResponse, StreamingResponse
|
from fastapi.responses import FileResponse, StreamingResponse
|
||||||
|
|
||||||
@@ -211,12 +211,12 @@ async def api_job_events(job_id: str):
|
|||||||
|
|
||||||
|
|
||||||
@app.get("/api/results")
|
@app.get("/api/results")
|
||||||
def api_results() -> list[dict]:
|
def api_results(limit: int = Query(1000, ge=1, le=5000)) -> list[dict]:
|
||||||
return scan_results()
|
return scan_results(limit=limit)
|
||||||
|
|
||||||
|
|
||||||
@app.get("/api/results/curves")
|
@app.get("/api/results/curves")
|
||||||
def api_result_curves(limit: int = 100) -> list[dict]:
|
def api_result_curves(limit: int = Query(100, ge=1, le=500)) -> list[dict]:
|
||||||
return scan_training_curves(limit=limit)
|
return scan_training_curves(limit=limit)
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -1,5 +1,9 @@
|
|||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
|
|
||||||
|
from fastapi.testclient import TestClient
|
||||||
|
|
||||||
|
from app import main
|
||||||
|
from app.main import app
|
||||||
from app.modules.results.service import parse_training_curve
|
from app.modules.results.service import parse_training_curve
|
||||||
|
|
||||||
|
|
||||||
@@ -25,3 +29,31 @@ def test_parse_yolo_results_curve(tmp_path: Path):
|
|||||||
assert "train/seg_loss" in names
|
assert "train/seg_loss" in names
|
||||||
assert "metrics/mAP50(M)" in names
|
assert "metrics/mAP50(M)" in names
|
||||||
assert all("lr/" not in name for name in names)
|
assert all("lr/" not in name for name in names)
|
||||||
|
|
||||||
|
|
||||||
|
def test_results_api_honors_limit(monkeypatch):
|
||||||
|
calls = {}
|
||||||
|
|
||||||
|
def fake_scan_results(limit: int = 1000):
|
||||||
|
calls["limit"] = limit
|
||||||
|
return [{"name": "a.png", "relative_path": "var/a.png"}]
|
||||||
|
|
||||||
|
monkeypatch.setattr(main, "scan_results", fake_scan_results)
|
||||||
|
response = TestClient(app).get("/api/results?limit=123")
|
||||||
|
|
||||||
|
assert response.status_code == 200
|
||||||
|
assert calls["limit"] == 123
|
||||||
|
|
||||||
|
|
||||||
|
def test_results_curve_api_honors_limit(monkeypatch):
|
||||||
|
calls = {}
|
||||||
|
|
||||||
|
def fake_scan_training_curves(limit: int = 100):
|
||||||
|
calls["limit"] = limit
|
||||||
|
return [{"name": "results", "relative_path": "var/results.csv"}]
|
||||||
|
|
||||||
|
monkeypatch.setattr(main, "scan_training_curves", fake_scan_training_curves)
|
||||||
|
response = TestClient(app).get("/api/results/curves?limit=77")
|
||||||
|
|
||||||
|
assert response.status_code == 200
|
||||||
|
assert calls["limit"] == 77
|
||||||
|
|||||||
@@ -347,8 +347,8 @@ function useData() {
|
|||||||
api<RuntimeReadinessPayload>("/api/system/readiness"),
|
api<RuntimeReadinessPayload>("/api/system/readiness"),
|
||||||
api<CapabilityPayload>("/api/capabilities"),
|
api<CapabilityPayload>("/api/capabilities"),
|
||||||
api<Job[]>("/api/jobs"),
|
api<Job[]>("/api/jobs"),
|
||||||
api<ResultItem[]>("/api/results"),
|
api<ResultItem[]>("/api/results?limit=1000"),
|
||||||
api<TrainingCurve[]>("/api/results/curves"),
|
api<TrainingCurve[]>("/api/results/curves?limit=100"),
|
||||||
api<UploadedDataset[]>("/api/datasets"),
|
api<UploadedDataset[]>("/api/datasets"),
|
||||||
api<CoveragePayload>("/api/coverage"),
|
api<CoveragePayload>("/api/coverage"),
|
||||||
api<AcceptancePayload>("/api/acceptance/latest"),
|
api<AcceptancePayload>("/api/acceptance/latest"),
|
||||||
@@ -360,8 +360,8 @@ function useData() {
|
|||||||
setRuntimeReadiness(readinessNext);
|
setRuntimeReadiness(readinessNext);
|
||||||
setCapabilities(capabilitiesNext);
|
setCapabilities(capabilitiesNext);
|
||||||
setJobs(jobsNext);
|
setJobs(jobsNext);
|
||||||
setResults(resultsNext.slice(0, 240));
|
setResults(resultsNext);
|
||||||
setCurves(curvesNext.slice(0, 12));
|
setCurves(curvesNext);
|
||||||
setDatasets(datasetsNext);
|
setDatasets(datasetsNext);
|
||||||
const validationEntries: Array<[string, DatasetValidation]> = [];
|
const validationEntries: Array<[string, DatasetValidation]> = [];
|
||||||
await Promise.all(
|
await Promise.all(
|
||||||
@@ -1319,12 +1319,16 @@ function ResultBrowser({
|
|||||||
<span>/ {total} artifacts</span>
|
<span>/ {total} artifacts</span>
|
||||||
</div>
|
</div>
|
||||||
<div className="resultList">
|
<div className="resultList">
|
||||||
{results.map((item) => (
|
{results.length ? (
|
||||||
|
results.map((item) => (
|
||||||
<a key={item.path} href={`${API_BASE}/api/artifacts/${item.relative_path}`} target="_blank" rel="noreferrer">
|
<a key={item.path} href={`${API_BASE}/api/artifacts/${item.relative_path}`} target="_blank" rel="noreferrer">
|
||||||
<span>{item.name}</span>
|
<span>{item.name}</span>
|
||||||
<small>{item.family ?? "artifact"} · {item.role ?? "artifact"} · {formatBytes(item.size)}</small>
|
<small>{item.family ?? "artifact"} · {item.role ?? "artifact"} · {formatBytes(item.size)}</small>
|
||||||
</a>
|
</a>
|
||||||
))}
|
))
|
||||||
|
) : (
|
||||||
|
<p className="muted">没有匹配产物</p>
|
||||||
|
)}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
|||||||
Reference in New Issue
Block a user