Add support for uploading images

This commit is contained in:
Nicolas Mowen 2024-11-26 08:40:32 -07:00
parent 41cb52c4cb
commit 3684c441a5
3 changed files with 51 additions and 33 deletions

View File

@ -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(

View File

@ -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<z.infer<typeof formSchema>>({
resolver: zodResolver(formSchema),
mode: "onChange",
});
const fileRef = form.register("file");
// upload handler
const onSubmit = useCallback(
(data: z.infer<typeof formSchema>) => {
if (!data["file"]) {
return;
}
onSave(data["file"]["0"]);
},
[onSave],
);
return (
<Dialog open={open} defaultOpen={false} onOpenChange={setOpen}>
@ -44,31 +58,30 @@ export default function UploadImageDialog({
{description && <DialogDescription>{description}</DialogDescription>}
</DialogHeader>
<Form {...form}>
<form>
<form onSubmit={form.handleSubmit(onSubmit)}>
<FormField
control={form.control}
name="image"
render={({ field }) => (
name="file"
render={() => (
<FormItem>
<FormControl>
{
// @ts-expect-error ignore
<Input
className="aspect-video h-40 w-full"
type="file"
{...field}
/>
}
<Input
className="aspect-video h-40 w-full"
type="file"
{...fileRef}
/>
</FormControl>
</FormItem>
)}
/>
<DialogFooter className="pt-4">
<Button onClick={() => setOpen(false)}>Cancel</Button>
<Button variant="select" type="submit">
Save
</Button>
</DialogFooter>
</form>
</Form>
<DialogFooter>
<Button onClick={() => setOpen(false)}>Cancel</Button>
<Button variant="select">Save</Button>
</DialogFooter>
</DialogContent>
</Dialog>
);

View File

@ -18,10 +18,6 @@ export default function FaceLibrary() {
const [pageToggle, setPageToggle] = useOptimisticState(page, setPage, 100);
const tabsRef = useRef<HTMLDivElement | null>(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 (
<div className="flex size-full flex-col p-2">
<Toaster />
@ -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}
/>
<div className="relative flex h-11 w-full items-center justify-between">