Add filterable artifact browser
This commit is contained in:
@@ -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 />
|
||||
|
||||
Reference in New Issue
Block a user