Add filterable artifact browser

This commit is contained in:
2026-06-30 15:41:58 +08:00
parent 90dccbea0e
commit 41cb0235cd
4 changed files with 148 additions and 11 deletions

View File

@@ -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<FileList | null>(null);
const [agentValidation, setAgentValidation] = useState<ValidationAgentPayload | null>(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() {
</div>
<BarChart3 size={22} />
</div>
<div className="resultList">
{results.map((item) => (
<a key={item.path} href={`${API_BASE}/api/artifacts/${item.relative_path}`} target="_blank" rel="noreferrer">
<span>{item.name}</span>
<small>{formatBytes(item.size)}</small>
</a>
))}
</div>
<ResultBrowser
results={filteredResults}
total={results.length}
familyOptions={resultFamilyOptions}
roleOptions={resultRoleOptions}
familyFilter={resultFamilyFilter}
roleFilter={resultRoleFilter}
onFamilyFilter={setResultFamilyFilter}
onRoleFilter={setResultRoleFilter}
/>
</div>
</section>
</section>
@@ -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 (
<div className="resultBrowser">
<div className="resultFilters">
<label>
<span>Family</span>
<select value={familyFilter} onChange={(event) => onFamilyFilter(event.target.value)} aria-label="result family">
{familyOptions.map((option) => (
<option key={option} value={option}>{resultFilterLabel(option)}</option>
))}
</select>
</label>
<label>
<span>Role</span>
<select value={roleFilter} onChange={(event) => onRoleFilter(event.target.value)} aria-label="result role">
{roleOptions.map((option) => (
<option key={option} value={option}>{resultFilterLabel(option)}</option>
))}
</select>
</label>
</div>
<div className="resultSummary">
<strong>{results.length}</strong>
<span>/ {total} artifacts</span>
</div>
<div className="resultList">
{results.map((item) => (
<a key={item.path} href={`${API_BASE}/api/artifacts/${item.relative_path}`} target="_blank" rel="noreferrer">
<span>{item.name}</span>
<small>{item.family ?? "artifact"} · {item.role ?? "artifact"} · {formatBytes(item.size)}</small>
</a>
))}
</div>
</div>
);
}
function DatasetQuality({ validation }: { validation: DatasetValidation }) {
return (
<div className="qualityBox">
@@ -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(
<React.StrictMode>
<App />