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

+
+
+
+
+
+
+
+ {
+ 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")}
+
+
+
+
+
+
+ ))}
+
+ );
+}