mirror of
https://github.com/blakeblackshear/frigate.git
synced 2026-03-07 02:18:07 +01:00
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:
@@ -67,9 +67,6 @@
|
||||
},
|
||||
"activity_context_prompt": {
|
||||
"label": "Custom activity context prompt defining normal activity patterns for this property."
|
||||
},
|
||||
"camera_context": {
|
||||
"label": "Spatial context about the camera's field of view to help with descriptive accuracy. Should describe physical features and locations outside the frame. This is for spatial reference only and should NOT include subjective assessments."
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -22,7 +22,8 @@
|
||||
"categorizedImage": "Successfully Classified Image",
|
||||
"trainedModel": "Successfully trained model.",
|
||||
"trainingModel": "Successfully started model training.",
|
||||
"updatedModel": "Successfully updated model configuration"
|
||||
"updatedModel": "Successfully updated model configuration",
|
||||
"renamedCategory": "Successfully renamed class to {{name}}"
|
||||
},
|
||||
"error": {
|
||||
"deleteImageFailed": "Failed to delete: {{errorMessage}}",
|
||||
@@ -30,7 +31,8 @@
|
||||
"deleteModelFailed": "Failed to delete model: {{errorMessage}}",
|
||||
"categorizeFailed": "Failed to categorize image: {{errorMessage}}",
|
||||
"trainingFailed": "Failed to start model training: {{errorMessage}}",
|
||||
"updateModelFailed": "Failed to update model: {{errorMessage}}"
|
||||
"updateModelFailed": "Failed to update model: {{errorMessage}}",
|
||||
"renameCategoryFailed": "Failed to rename class: {{errorMessage}}"
|
||||
}
|
||||
},
|
||||
"deleteCategory": {
|
||||
|
||||
@@ -75,7 +75,7 @@
|
||||
"deletedName_other": "{{count}} faces have been successfully deleted.",
|
||||
"renamedFace": "Successfully renamed face to {{name}}",
|
||||
"trainedFace": "Successfully trained face.",
|
||||
"updatedFaceScore": "Successfully updated face score."
|
||||
"updatedFaceScore": "Successfully updated face score to {{name}} ({{score}})."
|
||||
},
|
||||
"error": {
|
||||
"uploadingImageFailed": "Failed to upload image: {{errorMessage}}",
|
||||
|
||||
@@ -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) => {
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user