diff --git a/frigate/api/classification.py b/frigate/api/classification.py index 9cd3e0db1..e142735be 100644 --- a/frigate/api/classification.py +++ b/frigate/api/classification.py @@ -2,6 +2,7 @@ import logging import os +from pathvalidate import sanitize_filename from fastapi import APIRouter, Request, UploadFile from fastapi.responses import JSONResponse @@ -46,8 +47,8 @@ async def register_face(request: Request, name: str, file: UploadFile): ) -@router.delete("/faces") -def deregister_faces(request: Request, body: dict = None): +@router.post("/faces/{name}/delete") +def deregister_faces(request: Request, name: str, body: dict = None): json: dict[str, any] = body or {} list_of_ids = json.get("ids", "") @@ -58,7 +59,9 @@ def deregister_faces(request: Request, body: dict = None): ) context: EmbeddingsContext = request.app.embeddings - context.delete_face_ids(list_of_ids) + context.delete_face_ids( + name, map(lambda file: sanitize_filename(file), list_of_ids) + ) return JSONResponse( content=({"success": True, "message": "Successfully deleted faces."}), status_code=200, diff --git a/frigate/embeddings/__init__.py b/frigate/embeddings/__init__.py index 235b15df3..9836ae28e 100644 --- a/frigate/embeddings/__init__.py +++ b/frigate/embeddings/__init__.py @@ -14,7 +14,7 @@ from setproctitle import setproctitle from frigate.comms.embeddings_updater import EmbeddingsRequestEnum, EmbeddingsRequestor from frigate.config import FrigateConfig -from frigate.const import CONFIG_DIR +from frigate.const import CONFIG_DIR, FACE_DIR from frigate.db.sqlitevecq import SqliteVecQueueDatabase from frigate.models import Event from frigate.util.builtin import serialize @@ -209,8 +209,13 @@ class EmbeddingsContext: return self.db.execute_sql(sql_query).fetchall() - def delete_face_ids(self, ids: list[str]) -> None: - self.db.delete_embeddings_face(ids) + def delete_face_ids(self, face: str, ids: list[str]) -> None: + folder = os.path.join(FACE_DIR, face) + for id in ids: + file_path = os.path.join(folder, id) + + if os.path.isfile(file_path): + os.unlink(file_path) def update_description(self, event_id: str, description: str) -> None: self.requestor.send_data( diff --git a/web/src/pages/FaceLibrary.tsx b/web/src/pages/FaceLibrary.tsx index 381c2f14d..fd6d50223 100644 --- a/web/src/pages/FaceLibrary.tsx +++ b/web/src/pages/FaceLibrary.tsx @@ -3,13 +3,16 @@ import Chip from "@/components/indicators/Chip"; import { Form, FormControl, FormField, FormItem } from "@/components/ui/form"; import { Input } from "@/components/ui/input"; import { ScrollArea, ScrollBar } from "@/components/ui/scroll-area"; +import { Toaster } from "@/components/ui/sonner"; import { ToggleGroup, ToggleGroupItem } from "@/components/ui/toggle-group"; import useOptimisticState from "@/hooks/use-optimistic-state"; import { zodResolver } from "@hookform/resolvers/zod"; -import { useEffect, useMemo, useRef, useState } from "react"; +import axios from "axios"; +import { useCallback, useEffect, useMemo, useRef, useState } from "react"; import { isDesktop } from "react-device-detect"; import { useForm } from "react-hook-form"; import { LuTrash } from "react-icons/lu"; +import { toast } from "sonner"; import useSWR from "swr"; import { z } from "zod"; @@ -60,6 +63,8 @@ export default function FaceLibrary() { return (