diff --git a/web/public/locales/en/views/classificationModel.json b/web/public/locales/en/views/classificationModel.json new file mode 100644 index 000000000..f76bf7ce1 --- /dev/null +++ b/web/public/locales/en/views/classificationModel.json @@ -0,0 +1,5 @@ +{ + "button": { + "deleteClassificationAttempts": "Delete Classification Images" + } +} diff --git a/web/src/views/classification/ModelTrainingView.tsx b/web/src/views/classification/ModelTrainingView.tsx index 5602a598c..c7bd0e18e 100644 --- a/web/src/views/classification/ModelTrainingView.tsx +++ b/web/src/views/classification/ModelTrainingView.tsx @@ -1,3 +1,4 @@ +import { baseUrl } from "@/api/baseUrl"; import TextEntryDialog from "@/components/overlay/dialog/TextEntryDialog"; import { Button } from "@/components/ui/button"; import { @@ -20,10 +21,12 @@ import { TooltipTrigger, } from "@/components/ui/tooltip"; import useOptimisticState from "@/hooks/use-optimistic-state"; +import { cn } from "@/lib/utils"; import { CustomClassificationModelConfig } from "@/types/frigateConfig"; import { TooltipPortal } from "@radix-ui/react-tooltip"; import axios from "axios"; -import { useCallback, useState } from "react"; +import { useCallback, useMemo, useState } from "react"; +import { isMobile } from "react-device-detect"; import { useTranslation } from "react-i18next"; import { LuPencil, LuTrash2 } from "react-icons/lu"; import useSWR from "swr"; @@ -47,7 +50,7 @@ export default function ModelTrainingView({ model }: ModelTrainingViewProps) { }, [model]); return ( -
+
{}} onRename={() => {}} /> - +
+ {pageToggle == "train" ? ( + {}} + onDelete={() => {}} + /> + ) : ( + + )}
); } @@ -154,7 +166,13 @@ function LibrarySelector({ @@ -239,3 +257,139 @@ function LibrarySelector({ ); } + +type DatasetGridProps = { + name: string; + images: string[]; + onDelete: (name: string, ids: string[]) => void; +}; +function DatasetGrid({ name, images, onDelete }: DatasetGridProps) { + const { t } = useTranslation(["views/classificationModel"]); + + return ( +
+
+ +
+
+
+
+
{name}
+
+
+ + + { + e.stopPropagation(); + onDelete(name, images); + }} + /> + + {t("button.deleteFaceAttempts")} + +
+
+
+
+ ); +} + +type TrainGridProps = { + model: CustomClassificationModelConfig; + trainImages: string[]; + selected: boolean; + onClickImages: (images: string[], ctrl: boolean) => void; + onDelete: (name: string, ids: string[]) => void; +}; +function TrainGrid({ + model, + trainImages, + selected, + onClickImages, + onDelete, +}: TrainGridProps) { + const { t } = useTranslation(["views/classificationModel"]); + + const trainData = useMemo( + () => + trainImages.map((raw) => { + const parts = raw.replaceAll(".webp", "").split("-"); + return { + raw, + timestamp: parts[0], + label: parts[1], + score: Number.parseFloat(parts[2]) * 100, + }; + }), + [trainImages], + ); + + return ( +
+ {trainData.map((data) => ( +
{ + e.stopPropagation(); + onClickImages([data.raw], e.ctrlKey || e.metaKey); + }} + > +
+ +
+
+
+
+
{data.label}
+
{data.score}%
+
+
+ + + { + e.stopPropagation(); + onDelete("train", [data.raw]); + }} + /> + + + {t("button.deleteClassificationAttempts")} + + +
+
+
+
+ ))} +
+ ); +}