From 41cb0235cd78e9ac3d6f1cb2cf175e0cd2412a6a Mon Sep 17 00:00:00 2001 From: admin <572701190@qq.com> Date: Tue, 30 Jun 2026 15:41:58 +0800 Subject: [PATCH] Add filterable artifact browser --- README.md | 5 +- backend/app/agents/evaluation_agent.py | 5 ++ frontend/src/main.tsx | 88 +++++++++++++++++++++++--- frontend/src/styles.css | 61 +++++++++++++++++- 4 files changed, 148 insertions(+), 11 deletions(-) diff --git a/README.md b/README.md index 5884b24..2d72e93 100644 --- a/README.md +++ b/README.md @@ -57,8 +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`, prediction previews, heatmap previews, and inline training curve previews. Segmentation previews, YOLO heatmaps, and loss/metric artifacts are grouped on -the results dashboard, and YOLO-style `results.csv` files are parsed into -lightweight training curves. +the results dashboard. The artifact browser can filter by model family and +artifact role, and YOLO-style `results.csv` files are parsed into lightweight +training curves. Job APIs and the SSE log stream also expose structured progress parsed from 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 diff --git a/backend/app/agents/evaluation_agent.py b/backend/app/agents/evaluation_agent.py index d65241d..6ef59d1 100644 --- a/backend/app/agents/evaluation_agent.py +++ b/backend/app/agents/evaluation_agent.py @@ -50,6 +50,11 @@ def evaluate_project() -> dict: and "datasetOutputCurve" in frontend_text and "MiniCurvePlot" in frontend_text, "loss_result_ui": "loss" in frontend_text.lower() and "heatmap" in frontend_text.lower() and "CurvePanel" in frontend_text, + "result_browser_filter_ui": "ResultBrowser" in frontend_text + and "resultFamilyFilter" in frontend_text + and "resultRoleFilter" in frontend_text + and "familyOptions" in frontend_text + and "roleOptions" 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, "capability_matrix_ui": "capabilities" in frontend_text and "全功能矩阵" in frontend_text, diff --git a/frontend/src/main.tsx b/frontend/src/main.tsx index 77a23a1..5ec75ce 100644 --- a/frontend/src/main.tsx +++ b/frontend/src/main.tsx @@ -424,6 +424,8 @@ function App() { const [datasetDescription, setDatasetDescription] = useState(""); const [selectedDatasetName, setSelectedDatasetName] = useState(""); const [selectedCurvePath, setSelectedCurvePath] = useState(""); + const [resultFamilyFilter, setResultFamilyFilter] = useState("all"); + const [resultRoleFilter, setResultRoleFilter] = useState("all"); const [uploadKind, setUploadKind] = useState<"images" | "labels" | "masks">("images"); const [uploadFiles, setUploadFiles] = useState(null); const [agentValidation, setAgentValidation] = useState(null); @@ -472,6 +474,15 @@ function App() { }; }, [curves, results, selectedDataset]); const selectedYoloWeightReady = Boolean(selectedYoloOutputs.bestWeight); + const resultFamilyOptions = useMemo(() => ["all", ...Array.from(new Set(results.map((item) => item.family ?? "artifact"))).sort()], [results]); + const resultRoleOptions = useMemo(() => ["all", ...Array.from(new Set(results.map((item) => item.role ?? "artifact"))).sort()], [results]); + const filteredResults = useMemo( + () => results.filter((item) => + (resultFamilyFilter === "all" || (item.family ?? "artifact") === resultFamilyFilter) && + (resultRoleFilter === "all" || (item.role ?? "artifact") === resultRoleFilter) + ), + [resultFamilyFilter, resultRoleFilter, results] + ); function pickTask(next: string) { setTaskType(next); @@ -1215,14 +1226,16 @@ function App() { -
- {results.map((item) => ( - - {item.name} - {formatBytes(item.size)} - - ))} -
+ @@ -1262,6 +1275,61 @@ function ResultPreview({ results }: { results: ResultItem[] }) { ); } +function ResultBrowser({ + results, + total, + familyOptions, + roleOptions, + familyFilter, + roleFilter, + onFamilyFilter, + onRoleFilter +}: { + results: ResultItem[]; + total: number; + familyOptions: string[]; + roleOptions: string[]; + familyFilter: string; + roleFilter: string; + onFamilyFilter: (value: string) => void; + onRoleFilter: (value: string) => void; +}) { + return ( +
+
+ + +
+
+ {results.length} + / {total} artifacts +
+ +
+ ); +} + function DatasetQuality({ validation }: { validation: DatasetValidation }) { return (
@@ -1416,6 +1484,10 @@ function curveColor(index: number) { return ["#9de26f", "#73d2de", "#d3b35b", "#7aa2ff", "#f07167"][index % 5]; } +function resultFilterLabel(value: string) { + return value === "all" ? "All" : value; +} + createRoot(document.getElementById("root")!).render( diff --git a/frontend/src/styles.css b/frontend/src/styles.css index 225811f..81bdbd9 100644 --- a/frontend/src/styles.css +++ b/frontend/src/styles.css @@ -936,8 +936,9 @@ textarea { } .jobRow, .resultList a { + min-width: 0; display: grid; - grid-template-columns: 1fr auto; + grid-template-columns: minmax(0, 1fr) minmax(0, auto); gap: 8px; text-align: left; padding: 10px; @@ -952,6 +953,64 @@ textarea { grid-template-columns: 1fr; } +.resultList span, +.resultList small { + min-width: 0; + overflow: hidden; + text-overflow: ellipsis; + white-space: nowrap; +} + +.resultList small { + color: var(--muted); +} + +.resultBrowser { + display: grid; + gap: 10px; +} + +.resultFilters { + display: grid; + grid-template-columns: repeat(2, minmax(0, 1fr)); + gap: 8px; +} + +.resultFilters label { + min-width: 0; + display: grid; + gap: 6px; +} + +.resultFilters span { + color: var(--muted); + font-size: 11px; +} + +.resultFilters select { + width: 100%; + height: 36px; + padding: 0 9px; + border-radius: 6px; + border: 1px solid var(--line); + background: var(--field); + color: var(--ink); +} + +.resultSummary { + display: flex; + align-items: baseline; + gap: 5px; + color: var(--muted); + font-size: 12px; +} + +.resultSummary strong { + color: var(--green); + font-size: 24px; + line-height: 1; +} + .jobRowTop { min-width: 0; display: grid;