diff --git a/frigate/api/classification.py b/frigate/api/classification.py index e142735be..fe54bebe9 100644 --- a/frigate/api/classification.py +++ b/frigate/api/classification.py @@ -2,10 +2,10 @@ import logging import os -from pathvalidate import sanitize_filename from fastapi import APIRouter, Request, UploadFile from fastapi.responses import JSONResponse +from pathvalidate import sanitize_filename from frigate.api.defs.tags import Tags from frigate.const import FACE_DIR @@ -30,15 +30,6 @@ def get_faces(): @router.post("/faces/{name}") async def register_face(request: Request, name: str, file: UploadFile): - # if not file.content_type.startswith("image"): - # return JSONResponse( - # status_code=400, - # content={ - # "success": False, - # "message": "Only an image can be used to register a face.", - # }, - # ) - context: EmbeddingsContext = request.app.embeddings context.register_face(name, await file.read()) return JSONResponse( diff --git a/web/src/components/overlay/dialog/UploadImageDialog.tsx b/web/src/components/overlay/dialog/UploadImageDialog.tsx index d3d317b44..b4fbd5065 100644 --- a/web/src/components/overlay/dialog/UploadImageDialog.tsx +++ b/web/src/components/overlay/dialog/UploadImageDialog.tsx @@ -10,6 +10,7 @@ import { import { Form, FormControl, FormField, FormItem } from "@/components/ui/form"; import { Input } from "@/components/ui/input"; import { zodResolver } from "@hookform/resolvers/zod"; +import { useCallback } from "react"; import { useForm } from "react-hook-form"; import { z } from "zod"; @@ -18,23 +19,36 @@ type UploadImageDialogProps = { title: string; description?: string; setOpen: (open: boolean) => void; + onSave: (file: File) => void; }; export default function UploadImageDialog({ open, title, description, setOpen, + onSave, }: UploadImageDialogProps) { const formSchema = z.object({ - image: z - .instanceof(File, { message: "Please select an image file." }) - .refine((file) => file.size < 1000000, "Image size is too large."), + file: z.instanceof(FileList, { message: "Please select an image file." }), }); const form = useForm>({ resolver: zodResolver(formSchema), - mode: "onChange", }); + const fileRef = form.register("file"); + + // upload handler + + const onSubmit = useCallback( + (data: z.infer) => { + if (!data["file"]) { + return; + } + + onSave(data["file"]["0"]); + }, + [onSave], + ); return ( @@ -44,31 +58,30 @@ export default function UploadImageDialog({ {description && {description}}
- + ( + name="file" + render={() => ( - { - // @ts-expect-error ignore - - } + )} /> + + + + - - - -
); diff --git a/web/src/pages/FaceLibrary.tsx b/web/src/pages/FaceLibrary.tsx index 0f4e5998e..e955a17de 100644 --- a/web/src/pages/FaceLibrary.tsx +++ b/web/src/pages/FaceLibrary.tsx @@ -18,10 +18,6 @@ export default function FaceLibrary() { const [pageToggle, setPageToggle] = useOptimisticState(page, setPage, 100); const tabsRef = useRef(null); - // upload - - const [upload, setUpload] = useState(false); - // face data const { data: faceData } = useSWR("faces"); @@ -43,6 +39,23 @@ export default function FaceLibrary() { // eslint-disable-next-line react-hooks/exhaustive-deps }, [faces]); + // upload + + const [upload, setUpload] = useState(false); + + const onUploadImage = useCallback( + (file: File) => { + const formData = new FormData(); + formData.append("file", file); + axios.post(`faces/${pageToggle}`, formData, { + headers: { + "Content-Type": "multipart/form-data", + }, + }); + }, + [pageToggle], + ); + return (
@@ -52,6 +65,7 @@ export default function FaceLibrary() { title="Upload Face Image" description={`Upload an image to scan for faces and include for ${pageToggle}`} setOpen={setUpload} + onSave={onUploadImage} />