Miscellaneous Fixes (#20848)

* Fix filtering for classification

* Adjust prompt to account for response tokens

* Correctly return response for reprocess

* Use API response to update data instead of trying to re-parse all of the values

* Implement rename class api

* Fix model deletion / rename dialog

* Remove camera spatial context

* Catch error
This commit is contained in:
Nicolas Mowen
2025-11-08 13:13:40 -07:00
committed by GitHub
parent c99ada8f6a
commit d41ee4ff88
11 changed files with 255 additions and 117 deletions

View File

@@ -622,7 +622,15 @@ type TrainingGridProps = {
faceNames: string[];
selectedFaces: string[];
onClickFaces: (images: string[], ctrl: boolean) => void;
onRefresh: () => void;
onRefresh: (
data?:
| FaceLibraryData
| Promise<FaceLibraryData>
| ((
currentData: FaceLibraryData | undefined,
) => FaceLibraryData | undefined),
opts?: boolean | { revalidate?: boolean },
) => Promise<FaceLibraryData | undefined>;
};
function TrainingGrid({
config,
@@ -726,7 +734,15 @@ type FaceAttemptGroupProps = {
faceNames: string[];
selectedFaces: string[];
onClickFaces: (image: string[], ctrl: boolean) => void;
onRefresh: () => void;
onRefresh: (
data?:
| FaceLibraryData
| Promise<FaceLibraryData>
| ((
currentData: FaceLibraryData | undefined,
) => FaceLibraryData | undefined),
opts?: boolean | { revalidate?: boolean },
) => Promise<FaceLibraryData | undefined>;
};
function FaceAttemptGroup({
config,
@@ -814,11 +830,44 @@ function FaceAttemptGroup({
axios
.post(`/faces/reprocess`, { training_file: data.filename })
.then((resp) => {
if (resp.status == 200) {
toast.success(t("toast.success.updatedFaceScore"), {
position: "top-center",
});
onRefresh();
if (resp.status == 200 && resp.data?.success) {
const { face_name, score } = resp.data;
const oldFilename = data.filename;
const parts = oldFilename.split("-");
const newFilename = `${parts[0]}-${parts[1]}-${parts[2]}-${face_name}-${score}.webp`;
onRefresh(
(currentData: FaceLibraryData | undefined) => {
if (!currentData?.train) return currentData;
return {
...currentData,
train: currentData.train.map((filename: string) =>
filename === oldFilename ? newFilename : filename,
),
};
},
{ revalidate: true },
);
toast.success(
t("toast.success.updatedFaceScore", {
name: face_name,
score: score.toFixed(2),
}),
{
position: "top-center",
},
);
} else if (resp.data?.success === false) {
// Handle case where API returns success: false
const errorMessage = resp.data?.message || "Unknown error";
toast.error(
t("toast.error.updateFaceScoreFailed", { errorMessage }),
{
position: "top-center",
},
);
}
})
.catch((error) => {

View File

@@ -187,6 +187,37 @@ export default function ModelTrainingView({ model }: ModelTrainingViewProps) {
null,
);
const onRename = useCallback(
(old_name: string, new_name: string) => {
axios
.put(`/classification/${model.name}/dataset/${old_name}/rename`, {
new_category: new_name,
})
.then((resp) => {
if (resp.status == 200) {
toast.success(
t("toast.success.renamedCategory", { name: new_name }),
{
position: "top-center",
},
);
setPageToggle(new_name);
refreshDataset();
}
})
.catch((error) => {
const errorMessage =
error.response?.data?.message ||
error.response?.data?.detail ||
"Unknown error";
toast.error(t("toast.error.renameCategoryFailed", { errorMessage }), {
position: "top-center",
});
});
},
[model, setPageToggle, refreshDataset, t],
);
const onDelete = useCallback(
(ids: string[], isName: boolean = false, category?: string) => {
const targetCategory = category || pageToggle;
@@ -354,7 +385,7 @@ export default function ModelTrainingView({ model }: ModelTrainingViewProps) {
trainImages={trainImages || []}
setPageToggle={setPageToggle}
onDelete={onDelete}
onRename={() => {}}
onRename={onRename}
/>
</div>
)}
@@ -534,7 +565,7 @@ function LibrarySelector({
regexErrorMessage={t("description.invalidName")}
/>
<DropdownMenu>
<DropdownMenu modal={false}>
<DropdownMenuTrigger asChild>
<Button className="flex justify-between smart-capitalize">
{pageTitle}
@@ -585,48 +616,50 @@ function LibrarySelector({
({dataset?.[id].length})
</span>
</div>
<div className="flex gap-0.5">
<Tooltip>
<TooltipTrigger asChild>
<Button
variant="ghost"
size="icon"
className="size-7 lg:opacity-0 lg:transition-opacity lg:group-hover:opacity-100"
onClick={(e) => {
e.stopPropagation();
setRenameClass(id);
}}
>
<LuPencil className="size-4 text-primary" />
</Button>
</TooltipTrigger>
<TooltipPortal>
<TooltipContent>
{t("button.renameCategory")}
</TooltipContent>
</TooltipPortal>
</Tooltip>
<Tooltip>
<TooltipTrigger asChild>
<Button
variant="ghost"
size="icon"
className="size-7 lg:opacity-0 lg:transition-opacity lg:group-hover:opacity-100"
onClick={(e) => {
e.stopPropagation();
setConfirmDelete(id);
}}
>
<LuTrash2 className="size-4 text-destructive" />
</Button>
</TooltipTrigger>
<TooltipPortal>
<TooltipContent>
{t("button.deleteCategory")}
</TooltipContent>
</TooltipPortal>
</Tooltip>
</div>
{id != "none" && (
<div className="flex gap-0.5">
<Tooltip>
<TooltipTrigger asChild>
<Button
variant="ghost"
size="icon"
className="size-7 lg:opacity-0 lg:transition-opacity lg:group-hover:opacity-100"
onClick={(e) => {
e.stopPropagation();
setRenameClass(id);
}}
>
<LuPencil className="size-4 text-primary" />
</Button>
</TooltipTrigger>
<TooltipPortal>
<TooltipContent>
{t("button.renameCategory")}
</TooltipContent>
</TooltipPortal>
</Tooltip>
<Tooltip>
<TooltipTrigger asChild>
<Button
variant="ghost"
size="icon"
className="size-7 lg:opacity-0 lg:transition-opacity lg:group-hover:opacity-100"
onClick={(e) => {
e.stopPropagation();
setConfirmDelete(id);
}}
>
<LuTrash2 className="size-4 text-destructive" />
</Button>
</TooltipTrigger>
<TooltipPortal>
<TooltipContent>
{t("button.deleteCategory")}
</TooltipContent>
</TooltipPortal>
</Tooltip>
</div>
)}
</DropdownMenuItem>
))}
</DropdownMenuContent>
@@ -745,17 +778,11 @@ function TrainGrid({
return false;
}
if (
trainFilter.min_score &&
trainFilter.min_score > data.score / 100.0
) {
if (trainFilter.min_score && trainFilter.min_score > data.score) {
return false;
}
if (
trainFilter.max_score &&
trainFilter.max_score < data.score / 100.0
) {
if (trainFilter.max_score && trainFilter.max_score < data.score) {
return false;
}