Add ability to rename faces

This commit is contained in:
Weitheng Haw 2025-01-28 09:41:54 +00:00
parent b84201c030
commit a91768d480
2 changed files with 115 additions and 2 deletions

View File

@ -422,6 +422,49 @@ class FaceProcessor(RealTimeProcessorApi):
"message": "Internal server error", "message": "Internal server error",
"success": False, "success": False,
} }
elif topic == "rename_face":
old_name = request_data.get("old_name")
new_name = request_data.get("new_name")
if not self.__validate_face_name(new_name):
return {
"message": "Invalid new face name",
"success": False,
}
try:
old_folder = os.path.join(FACE_DIR, old_name)
new_folder = os.path.join(FACE_DIR, new_name)
if not os.path.exists(old_folder):
return {
"message": f"Face '{old_name}' not found",
"success": False,
}
if os.path.exists(new_folder):
return {
"message": f"Face name '{new_name}' already exists",
"success": False,
}
# Rename the directory
os.rename(old_folder, new_folder)
# Clear and rebuild classifier with new names
self.__clear_classifier()
self.__build_classifier()
return {
"message": "Successfully renamed face",
"success": True,
}
except Exception as e:
logger.error(f"Failed to rename face: {str(e)}")
return {
"message": f"Failed to rename face: {str(e)}",
"success": False,
}
def expire_object(self, object_id: str): def expire_object(self, object_id: str):
if object_id in self.detected_faces: if object_id in self.detected_faces:

View File

@ -23,7 +23,7 @@ import { cn } from "@/lib/utils";
import { FrigateConfig } from "@/types/frigateConfig"; import { FrigateConfig } from "@/types/frigateConfig";
import axios from "axios"; import axios from "axios";
import { useCallback, useEffect, useMemo, useRef, useState } from "react"; import { useCallback, useEffect, useMemo, useRef, useState } from "react";
import { LuImagePlus, LuTrash2, LuUserPlus } from "react-icons/lu"; import { LuImagePlus, LuTrash2, LuUserPlus, LuEdit } from "react-icons/lu";
import { toast } from "sonner"; import { toast } from "sonner";
import useSWR from "swr"; import useSWR from "swr";
import { Input } from "@/components/ui/input"; import { Input } from "@/components/ui/input";
@ -150,6 +150,37 @@ export default function FaceLibrary() {
} }
}, [newFaceName, refreshFaces]); }, [newFaceName, refreshFaces]);
const [renameDialog, setRenameDialog] = useState(false);
const [isRenaming, setIsRenaming] = useState(false);
const [renameData, setRenameData] = useState<{ oldName: string; newName: string }>({ oldName: '', newName: '' });
const renameFace = useCallback(async () => {
if (!renameData.newName.trim()) {
toast.error("Face name cannot be empty", { position: "top-center" });
return;
}
setIsRenaming(true);
try {
const resp = await axios.post(`/faces/${renameData.oldName}/rename`, {
new_name: renameData.newName
});
if (resp.status === 200) {
setRenameDialog(false);
setRenameData({ oldName: '', newName: '' });
refreshFaces();
toast.success("Successfully renamed face", { position: "top-center" });
}
} catch (error) {
toast.error(
`Failed to rename face: ${error.response?.data?.message || error.message}`,
{ position: "top-center" }
);
} finally {
setIsRenaming(false);
}
}, [renameData, refreshFaces]);
if (!config) { if (!config) {
return <ActivityIndicator />; return <ActivityIndicator />;
} }
@ -178,6 +209,26 @@ export default function FaceLibrary() {
</DialogContent> </DialogContent>
</Dialog> </Dialog>
<Dialog open={renameDialog} onOpenChange={setRenameDialog}>
<DialogContent>
<DialogHeader>
<DialogTitle>Rename Face</DialogTitle>
</DialogHeader>
<div className="flex flex-col gap-4">
<Input
placeholder="Enter new name"
value={renameData.newName}
onChange={(e) => setRenameData(prev => ({ ...prev, newName: e.target.value }))}
onKeyPress={(e) => e.key === 'Enter' && renameFace()}
disabled={isRenaming}
/>
<Button onClick={renameFace} disabled={isRenaming}>
{isRenaming ? "Renaming..." : "Rename"}
</Button>
</div>
</DialogContent>
</Dialog>
<UploadImageDialog <UploadImageDialog
open={upload} open={upload}
title="Upload Face Image" title="Upload Face Image"
@ -217,7 +268,9 @@ export default function FaceLibrary() {
{Object.values(faces).map((item) => ( {Object.values(faces).map((item) => (
<ToggleGroupItem <ToggleGroupItem
key={item} key={item}
className={`flex scroll-mx-10 items-center justify-between gap-2 ${pageToggle == item ? "" : "*:text-muted-foreground"}`} className={`flex scroll-mx-10 items-center justify-between gap-2 ${
pageToggle == item ? "" : "*:text-muted-foreground"
}`}
value={item} value={item}
data-nav-item={item} data-nav-item={item}
aria-label={`Select ${item}`} aria-label={`Select ${item}`}
@ -225,6 +278,23 @@ export default function FaceLibrary() {
<div className="capitalize"> <div className="capitalize">
{item} ({faceData[item].length}) {item} ({faceData[item].length})
</div> </div>
<Tooltip>
<TooltipTrigger asChild>
<Button
variant="ghost"
size="icon"
className="h-4 w-4"
onClick={(e) => {
e.stopPropagation();
setRenameData({ oldName: item, newName: item });
setRenameDialog(true);
}}
>
<LuEdit className="h-3 w-3" />
</Button>
</TooltipTrigger>
<TooltipContent>Rename Face</TooltipContent>
</Tooltip>
</ToggleGroupItem> </ToggleGroupItem>
))} ))}
</ToggleGroup> </ToggleGroup>