mirror of
https://github.com/blakeblackshear/frigate.git
synced 2026-07-28 14:49:01 +03:00
Reclassification (#22603)
* add ability to reclassify images * add ability to reclassify faces * work around radix pointer events issue again
This commit is contained in:
@@ -34,7 +34,11 @@ type ClassificationSelectionDialogProps = {
|
||||
classes: string[];
|
||||
modelName: string;
|
||||
image: string;
|
||||
onRefresh: () => void;
|
||||
onRefresh?: () => void;
|
||||
onCategorize?: (category: string) => void;
|
||||
excludeCategory?: string;
|
||||
dialogLabel?: string;
|
||||
tooltipLabel?: string;
|
||||
children: ReactNode;
|
||||
};
|
||||
export default function ClassificationSelectionDialog({
|
||||
@@ -43,12 +47,21 @@ export default function ClassificationSelectionDialog({
|
||||
modelName,
|
||||
image,
|
||||
onRefresh,
|
||||
onCategorize,
|
||||
excludeCategory,
|
||||
dialogLabel,
|
||||
tooltipLabel,
|
||||
children,
|
||||
}: ClassificationSelectionDialogProps) {
|
||||
const { t } = useTranslation(["views/classificationModel"]);
|
||||
|
||||
const onCategorizeImage = useCallback(
|
||||
(category: string) => {
|
||||
if (onCategorize) {
|
||||
onCategorize(category);
|
||||
return;
|
||||
}
|
||||
|
||||
axios
|
||||
.post(`/classification/${modelName}/dataset/categorize`, {
|
||||
category,
|
||||
@@ -59,7 +72,7 @@ export default function ClassificationSelectionDialog({
|
||||
toast.success(t("toast.success.categorizedImage"), {
|
||||
position: "top-center",
|
||||
});
|
||||
onRefresh();
|
||||
onRefresh?.();
|
||||
}
|
||||
})
|
||||
.catch((error) => {
|
||||
@@ -72,7 +85,13 @@ export default function ClassificationSelectionDialog({
|
||||
});
|
||||
});
|
||||
},
|
||||
[modelName, image, onRefresh, t],
|
||||
[modelName, image, onRefresh, onCategorize, t],
|
||||
);
|
||||
|
||||
const filteredClasses = useMemo(
|
||||
() =>
|
||||
excludeCategory ? classes.filter((c) => c !== excludeCategory) : classes,
|
||||
[classes, excludeCategory],
|
||||
);
|
||||
|
||||
const isChildButton = useMemo(
|
||||
@@ -111,6 +130,7 @@ export default function ClassificationSelectionDialog({
|
||||
</SelectorTrigger>
|
||||
<SelectorContent
|
||||
className={cn("", isMobile && "mx-1 gap-2 rounded-t-2xl px-4")}
|
||||
onCloseAutoFocus={(e) => e.preventDefault()}
|
||||
>
|
||||
{isMobile && (
|
||||
<DrawerHeader className="sr-only">
|
||||
@@ -118,14 +138,16 @@ export default function ClassificationSelectionDialog({
|
||||
<DrawerDescription>Details</DrawerDescription>
|
||||
</DrawerHeader>
|
||||
)}
|
||||
<DropdownMenuLabel>{t("categorizeImageAs")}</DropdownMenuLabel>
|
||||
<DropdownMenuLabel>
|
||||
{dialogLabel ?? t("categorizeImageAs")}
|
||||
</DropdownMenuLabel>
|
||||
<div
|
||||
className={cn(
|
||||
"flex max-h-[40dvh] flex-col overflow-y-auto",
|
||||
isMobile && "gap-2 pb-4",
|
||||
)}
|
||||
>
|
||||
{classes
|
||||
{filteredClasses
|
||||
.sort((a, b) => {
|
||||
if (a === "none") return 1;
|
||||
if (b === "none") return -1;
|
||||
@@ -152,7 +174,7 @@ export default function ClassificationSelectionDialog({
|
||||
</div>
|
||||
</SelectorContent>
|
||||
</Selector>
|
||||
<TooltipContent>{t("categorizeImage")}</TooltipContent>
|
||||
<TooltipContent>{tooltipLabel ?? t("categorizeImage")}</TooltipContent>
|
||||
</Tooltip>
|
||||
</div>
|
||||
);
|
||||
|
||||
@@ -30,17 +30,29 @@ import { Button } from "../ui/button";
|
||||
type FaceSelectionDialogProps = {
|
||||
className?: string;
|
||||
faceNames: string[];
|
||||
excludeName?: string;
|
||||
dialogLabel?: string;
|
||||
tooltipLabel?: string;
|
||||
onTrainAttempt: (name: string) => void;
|
||||
children: ReactNode;
|
||||
};
|
||||
export default function FaceSelectionDialog({
|
||||
className,
|
||||
faceNames,
|
||||
excludeName,
|
||||
dialogLabel,
|
||||
tooltipLabel,
|
||||
onTrainAttempt,
|
||||
children,
|
||||
}: FaceSelectionDialogProps) {
|
||||
const { t } = useTranslation(["views/faceLibrary"]);
|
||||
|
||||
const filteredNames = useMemo(
|
||||
() =>
|
||||
excludeName ? faceNames.filter((n) => n !== excludeName) : faceNames,
|
||||
[faceNames, excludeName],
|
||||
);
|
||||
|
||||
const isChildButton = useMemo(
|
||||
() => React.isValidElement(children) && children.type === Button,
|
||||
[children],
|
||||
@@ -79,6 +91,7 @@ export default function FaceSelectionDialog({
|
||||
</SelectorTrigger>
|
||||
<SelectorContent
|
||||
className={cn("", isMobile && "mx-1 gap-2 rounded-t-2xl px-4")}
|
||||
onCloseAutoFocus={(e) => e.preventDefault()}
|
||||
>
|
||||
{isMobile && (
|
||||
<DrawerHeader className="sr-only">
|
||||
@@ -86,14 +99,16 @@ export default function FaceSelectionDialog({
|
||||
<DrawerDescription>Details</DrawerDescription>
|
||||
</DrawerHeader>
|
||||
)}
|
||||
<DropdownMenuLabel>{t("trainFaceAs")}</DropdownMenuLabel>
|
||||
<DropdownMenuLabel>
|
||||
{dialogLabel ?? t("trainFaceAs")}
|
||||
</DropdownMenuLabel>
|
||||
<div
|
||||
className={cn(
|
||||
"flex max-h-[40dvh] flex-col overflow-y-auto overflow-x-hidden",
|
||||
isMobile && "gap-2 pb-4",
|
||||
)}
|
||||
>
|
||||
{faceNames.sort().map((faceName) => (
|
||||
{filteredNames.sort().map((faceName) => (
|
||||
<SelectorItem
|
||||
key={faceName}
|
||||
className="flex cursor-pointer gap-2 smart-capitalize"
|
||||
@@ -112,7 +127,7 @@ export default function FaceSelectionDialog({
|
||||
</div>
|
||||
</SelectorContent>
|
||||
</Selector>
|
||||
<TooltipContent>{t("trainFace")}</TooltipContent>
|
||||
<TooltipContent>{tooltipLabel ?? t("trainFace")}</TooltipContent>
|
||||
</Tooltip>
|
||||
</div>
|
||||
);
|
||||
|
||||
Reference in New Issue
Block a user