diff --git a/web/public/locales/en/views/classificationModel.json b/web/public/locales/en/views/classificationModel.json index 3a578c5d6..833240d54 100644 --- a/web/public/locales/en/views/classificationModel.json +++ b/web/public/locales/en/views/classificationModel.json @@ -65,6 +65,8 @@ "type": "Type", "typeState": "State", "typeObject": "Object", + "objectLabel": "Object Label", + "objectLabelPlaceholder": "Select object type...", "classificationType": "Classification Type", "classificationSubLabel": "Sub Label", "classificationAttribute": "Attribute", @@ -77,6 +79,7 @@ "classRequired": "At least 1 class is required", "classesUnique": "Class names must be unique", "stateRequiresTwoClasses": "State models require at least 2 classes", + "objectLabelRequired": "Please select an object label", "objectTypeRequired": "Please select a classification type" } }, diff --git a/web/src/components/classification/wizard/Step1NameAndDefine.tsx b/web/src/components/classification/wizard/Step1NameAndDefine.tsx index 28c077973..ce2386110 100644 --- a/web/src/components/classification/wizard/Step1NameAndDefine.tsx +++ b/web/src/components/classification/wizard/Step1NameAndDefine.tsx @@ -10,12 +10,23 @@ import { import { Input } from "@/components/ui/input"; import { Label } from "@/components/ui/label"; import { RadioGroup, RadioGroupItem } from "@/components/ui/radio-group"; +import { + Select, + SelectContent, + SelectItem, + SelectTrigger, + SelectValue, +} from "@/components/ui/select"; import { useForm } from "react-hook-form"; import { zodResolver } from "@hookform/resolvers/zod"; import { z } from "zod"; import { useTranslation } from "react-i18next"; +import { useMemo } from "react"; import { LuX } from "react-icons/lu"; import { MdAddBox } from "react-icons/md"; +import useSWR from "swr"; +import { FrigateConfig } from "@/types/frigateConfig"; +import { getTranslatedLabel } from "@/utils/i18n"; export type ModelType = "state" | "object"; export type ObjectClassificationType = "sub_label" | "attribute"; @@ -23,6 +34,7 @@ export type ObjectClassificationType = "sub_label" | "attribute"; export type Step1FormData = { modelName: string; modelType: ModelType; + objectLabel?: string; objectType?: ObjectClassificationType; classes: string[]; }; @@ -39,6 +51,27 @@ export default function Step1NameAndDefine({ onCancel, }: Step1NameAndDefineProps) { const { t } = useTranslation(["views/classificationModel"]); + const { data: config } = useSWR("config"); + + const objectLabels = useMemo(() => { + if (!config) return []; + + const labels = new Set(); + + Object.values(config.cameras).forEach((cameraConfig) => { + if (!cameraConfig.enabled || !cameraConfig.enabled_in_config) { + return; + } + + cameraConfig.objects.track.forEach((label) => { + if (!config.model.all_attributes.includes(label)) { + labels.add(label); + } + }); + }); + + return [...labels].sort(); + }, [config]); const step1FormData = z .object({ @@ -50,6 +83,7 @@ export default function Step1NameAndDefine({ message: t("wizard.step1.errors.nameOnlyNumbers"), }), modelType: z.enum(["state", "object"]), + objectLabel: z.string().optional(), objectType: z.enum(["sub_label", "attribute"]).optional(), classes: z .array(z.string()) @@ -86,7 +120,18 @@ export default function Step1NameAndDefine({ ) .refine( (data) => { - // Object models require objectType to be selected + if (data.modelType === "object") { + return data.objectLabel !== undefined && data.objectLabel !== ""; + } + return true; + }, + { + message: t("wizard.step1.errors.objectLabelRequired"), + path: ["objectLabel"], + }, + ) + .refine( + (data) => { if (data.modelType === "object") { return data.objectType !== undefined; } @@ -103,6 +148,7 @@ export default function Step1NameAndDefine({ defaultValues: { modelName: initialData?.modelName || "", modelType: initialData?.modelType || "state", + objectLabel: initialData?.objectLabel, objectType: initialData?.objectType || "sub_label", classes: initialData?.classes?.length ? initialData.classes : [""], }, @@ -209,52 +255,92 @@ export default function Step1NameAndDefine({ /> {watchedModelType === "object" && ( - ( - - {t("wizard.step1.classificationType")} - - + ( + + {t("wizard.step1.objectLabel")} + + + + )} + /> + + ( + + + {t("wizard.step1.classificationType")} + + + +
+ + +
+
+ + +
+
+
+ +
+ )} + /> + )}