Implement deleting

This commit is contained in:
Nicolas Mowen 2024-11-25 09:56:27 -07:00
parent c3b8110a42
commit b7877ff3f9
3 changed files with 46 additions and 9 deletions

View File

@ -2,6 +2,7 @@
import logging import logging
import os import os
from pathvalidate import sanitize_filename
from fastapi import APIRouter, Request, UploadFile from fastapi import APIRouter, Request, UploadFile
from fastapi.responses import JSONResponse from fastapi.responses import JSONResponse
@ -46,8 +47,8 @@ async def register_face(request: Request, name: str, file: UploadFile):
) )
@router.delete("/faces") @router.post("/faces/{name}/delete")
def deregister_faces(request: Request, body: dict = None): def deregister_faces(request: Request, name: str, body: dict = None):
json: dict[str, any] = body or {} json: dict[str, any] = body or {}
list_of_ids = json.get("ids", "") list_of_ids = json.get("ids", "")
@ -58,7 +59,9 @@ def deregister_faces(request: Request, body: dict = None):
) )
context: EmbeddingsContext = request.app.embeddings 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( return JSONResponse(
content=({"success": True, "message": "Successfully deleted faces."}), content=({"success": True, "message": "Successfully deleted faces."}),
status_code=200, status_code=200,

View File

@ -14,7 +14,7 @@ from setproctitle import setproctitle
from frigate.comms.embeddings_updater import EmbeddingsRequestEnum, EmbeddingsRequestor from frigate.comms.embeddings_updater import EmbeddingsRequestEnum, EmbeddingsRequestor
from frigate.config import FrigateConfig 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.db.sqlitevecq import SqliteVecQueueDatabase
from frigate.models import Event from frigate.models import Event
from frigate.util.builtin import serialize from frigate.util.builtin import serialize
@ -209,8 +209,13 @@ class EmbeddingsContext:
return self.db.execute_sql(sql_query).fetchall() return self.db.execute_sql(sql_query).fetchall()
def delete_face_ids(self, ids: list[str]) -> None: def delete_face_ids(self, face: str, ids: list[str]) -> None:
self.db.delete_embeddings_face(ids) 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: def update_description(self, event_id: str, description: str) -> None:
self.requestor.send_data( self.requestor.send_data(

View File

@ -3,13 +3,16 @@ import Chip from "@/components/indicators/Chip";
import { Form, FormControl, FormField, FormItem } from "@/components/ui/form"; import { Form, FormControl, FormField, FormItem } from "@/components/ui/form";
import { Input } from "@/components/ui/input"; import { Input } from "@/components/ui/input";
import { ScrollArea, ScrollBar } from "@/components/ui/scroll-area"; import { ScrollArea, ScrollBar } from "@/components/ui/scroll-area";
import { Toaster } from "@/components/ui/sonner";
import { ToggleGroup, ToggleGroupItem } from "@/components/ui/toggle-group"; import { ToggleGroup, ToggleGroupItem } from "@/components/ui/toggle-group";
import useOptimisticState from "@/hooks/use-optimistic-state"; import useOptimisticState from "@/hooks/use-optimistic-state";
import { zodResolver } from "@hookform/resolvers/zod"; 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 { isDesktop } from "react-device-detect";
import { useForm } from "react-hook-form"; import { useForm } from "react-hook-form";
import { LuTrash } from "react-icons/lu"; import { LuTrash } from "react-icons/lu";
import { toast } from "sonner";
import useSWR from "swr"; import useSWR from "swr";
import { z } from "zod"; import { z } from "zod";
@ -60,6 +63,8 @@ export default function FaceLibrary() {
return ( return (
<div className="flex size-full flex-col p-2"> <div className="flex size-full flex-col p-2">
<div className="relative flex h-11 w-full items-center justify-between"> <div className="relative flex h-11 w-full items-center justify-between">
<Toaster />
<ScrollArea className="w-full whitespace-nowrap"> <ScrollArea className="w-full whitespace-nowrap">
<div ref={tabsRef} className="flex flex-row"> <div ref={tabsRef} className="flex flex-row">
<ToggleGroup <ToggleGroup
@ -108,7 +113,7 @@ export default function FaceLibrary() {
</Form> </Form>
</div> </div>
{pageToggle && ( {pageToggle && (
<div className="flex gap-2"> <div className="flex flex-wrap gap-2">
{faceImages.map((image: string) => ( {faceImages.map((image: string) => (
<FaceImage name={pageToggle} image={image} /> <FaceImage name={pageToggle} image={image} />
))} ))}
@ -125,6 +130,27 @@ type FaceImageProps = {
function FaceImage({ name, image }: FaceImageProps) { function FaceImage({ name, image }: FaceImageProps) {
const [hovered, setHovered] = useState(false); const [hovered, setHovered] = useState(false);
const onDelete = useCallback(() => {
axios
.post(`/faces/${name}/delete`, { ids: [image] })
.then((resp) => {
if (resp.status == 200) {
toast.error(`Successfully deleted face.`, { position: "top-center" });
}
})
.catch((error) => {
if (error.response?.data?.message) {
toast.error(`Failed to delete: ${error.response.data.message}`, {
position: "top-center",
});
} else {
toast.error(`Failed to delete: ${error.message}`, {
position: "top-center",
});
}
});
}, [name, image]);
return ( return (
<div <div
className="relative h-40" className="relative h-40"
@ -134,7 +160,10 @@ function FaceImage({ name, image }: FaceImageProps) {
> >
{hovered && ( {hovered && (
<div className="absolute right-1 top-1"> <div className="absolute right-1 top-1">
<Chip className="cursor-pointer rounded-md bg-gray-500 bg-gradient-to-br from-gray-400 to-gray-500"> <Chip
className="cursor-pointer rounded-md bg-gray-500 bg-gradient-to-br from-gray-400 to-gray-500"
onClick={() => onDelete()}
>
<LuTrash className="size-4 fill-destructive text-destructive" /> <LuTrash className="size-4 fill-destructive text-destructive" />
</Chip> </Chip>
</div> </div>