import { baseUrl } from "@/api/baseUrl"; import { CamerasFilterButton, GeneralFilterContent, } from "@/components/filter/ReviewFilterGroup"; import { Button } from "@/components/ui/button"; import { Dialog, DialogContent, DialogDescription, DialogFooter, DialogHeader, DialogTitle, } from "@/components/ui/dialog"; import { Drawer, DrawerContent, DrawerTrigger } from "@/components/ui/drawer"; import { DropdownMenu, DropdownMenuContent, DropdownMenuSeparator, DropdownMenuTrigger, } from "@/components/ui/dropdown-menu"; import { Input } from "@/components/ui/input"; import { Label } from "@/components/ui/label"; import { RadioGroup, RadioGroupItem } from "@/components/ui/radio-group"; import { DualThumbSlider } from "@/components/ui/slider"; import { Event } from "@/types/event"; import { ATTRIBUTE_LABELS, FrigateConfig } from "@/types/frigateConfig"; import axios from "axios"; import { useCallback, useEffect, useMemo, useState } from "react"; import { isMobile } from "react-device-detect"; import { FaList, FaSort, FaSortAmountDown, FaSortAmountUp, } from "react-icons/fa"; import { PiSlidersHorizontalFill } from "react-icons/pi"; import useSWR from "swr"; export default function SubmitPlus() { const { data: config } = useSWR("config"); useEffect(() => { document.title = "Plus - Frigate"; }, []); // filters const [selectedCameras, setSelectedCameras] = useState(); const [selectedLabels, setSelectedLabels] = useState(); const [scoreRange, setScoreRange] = useState(); // sort const [sort, setSort] = useState(); // data const { data: events, mutate: refresh } = useSWR([ "events", { limit: 100, in_progress: 0, is_submitted: 0, cameras: selectedCameras ? selectedCameras.join(",") : null, labels: selectedLabels ? selectedLabels.join(",") : null, min_score: scoreRange ? scoreRange[0] : null, max_score: scoreRange ? scoreRange[1] : null, sort: sort ? sort : null, }, ]); const [upload, setUpload] = useState(); const grow = useMemo(() => { if (!config || !upload) { return ""; } const camera = config.cameras[upload.camera]; if (!camera) { return ""; } if (camera.detect.width / camera.detect.height < 16 / 9) { return "aspect-video object-contain"; } return ""; }, [config, upload]); const onSubmitToPlus = useCallback( async (falsePositive: boolean) => { if (!upload) { return; } falsePositive ? axios.put(`events/${upload.id}/false_positive`) : axios.post(`events/${upload.id}/plus`, { include_annotation: 1, }); refresh( (data: Event[] | undefined) => { if (!data) { return data; } const index = data.findIndex((e) => e.id == upload.id); if (index == -1) { return data; } return [...data.slice(0, index), ...data.slice(index + 1)]; }, { revalidate: false, populateCache: true }, ); setUpload(undefined); }, [refresh, upload], ); return (
(!open ? setUpload(undefined) : null)} > Submit To Frigate+ Objects in locations you want to avoid are not false positives. Submitting them as false positives will confuse the model. {`${upload?.label}`} {events?.map((event) => { if (event.data.type != "object") { return; } return (
setUpload(event)} >
); })}
); } type PlusFilterGroupProps = { selectedCameras: string[] | undefined; selectedLabels: string[] | undefined; selectedScoreRange: number[] | undefined; setSelectedCameras: (cameras: string[] | undefined) => void; setSelectedLabels: (cameras: string[] | undefined) => void; setSelectedScoreRange: (range: number[] | undefined) => void; }; function PlusFilterGroup({ selectedCameras, selectedLabels, selectedScoreRange, setSelectedCameras, setSelectedLabels, setSelectedScoreRange, }: PlusFilterGroupProps) { const { data: config } = useSWR("config"); const allCameras = useMemo(() => { if (!config) { return []; } return Object.keys(config.cameras); }, [config]); const allLabels = useMemo(() => { if (!config) { return []; } const labels = new Set(); const cameras = selectedCameras || Object.keys(config.cameras); cameras.forEach((camera) => { const cameraConfig = config.cameras[camera]; cameraConfig.objects.track.forEach((label) => { if (!ATTRIBUTE_LABELS.includes(label)) { labels.add(label); } }); }); return [...labels].sort(); }, [config, selectedCameras]); const [open, setOpen] = useState<"none" | "camera" | "label" | "score">( "none", ); const [currentLabels, setCurrentLabels] = useState( undefined, ); const [currentScoreRange, setCurrentScoreRange] = useState< number[] | undefined >(undefined); const Menu = isMobile ? Drawer : DropdownMenu; const Trigger = isMobile ? DrawerTrigger : DropdownMenuTrigger; const Content = isMobile ? DrawerContent : DropdownMenuContent; return (
{ if (!open) { setCurrentLabels(selectedLabels); } setOpen(open ? "label" : "none"); }} > setOpen("none")} /> { setOpen(open ? "score" : "none"); }} >
setCurrentScoreRange([ parseInt(e.target.value) / 100.0, currentScoreRange?.at(1) ?? 1.0, ]) } /> setCurrentScoreRange([ currentScoreRange?.at(0) ?? 0.5, parseInt(e.target.value) / 100.0, ]) } />
); } type PlusSortSelectorProps = { selectedSort?: string; setSelectedSort: (sort: string | undefined) => void; }; function PlusSortSelector({ selectedSort, setSelectedSort, }: PlusSortSelectorProps) { // menu state const [open, setOpen] = useState(false); // sort const [currentSort, setCurrentSort] = useState(); const [currentDir, setCurrentDir] = useState("desc"); // components const Sort = selectedSort ? selectedSort.split("_")[1] == "desc" ? FaSortAmountDown : FaSortAmountUp : FaSort; const Menu = isMobile ? Drawer : DropdownMenu; const Trigger = isMobile ? DrawerTrigger : DropdownMenuTrigger; const Content = isMobile ? DrawerContent : DropdownMenuContent; return (
{ setOpen(open); if (!open) { const parts = selectedSort?.split("_"); if (parts?.length == 2) { setCurrentSort(parts[0]); setCurrentDir(parts[1]); } } }} > setCurrentSort(value)} >
{currentSort == "date" ? ( currentDir == "desc" ? ( setCurrentDir("asc")} /> ) : ( setCurrentDir("desc")} /> ) ) : (
)}
{currentSort == "score" ? ( currentDir == "desc" ? ( setCurrentDir("asc")} /> ) : ( setCurrentDir("desc")} /> ) ) : (
)}
); }