From 25087007d64c9be84b1ec50cc8f38e444e4c4c3b Mon Sep 17 00:00:00 2001 From: Josh Hawkins <32435876+hawkeye217@users.noreply.github.com> Date: Sun, 12 Jul 2026 08:42:16 -0500 Subject: [PATCH] add state motion and interval configs to edit dialog --- .../locales/en/views/classificationModel.json | 8 +- .../ClassificationModelEditDialog.tsx | 80 +++++++++++++++++++ web/src/types/frigateConfig.ts | 1 + 3 files changed, 88 insertions(+), 1 deletion(-) diff --git a/web/public/locales/en/views/classificationModel.json b/web/public/locales/en/views/classificationModel.json index de0e2f920b..3e881ae345 100644 --- a/web/public/locales/en/views/classificationModel.json +++ b/web/public/locales/en/views/classificationModel.json @@ -69,9 +69,15 @@ "enabledDesc": "Run this model. When disabled, it stops running and no longer classifies.", "saveAttempts": "Save Attempts", "saveAttemptsDesc": "Number of classification attempt images to keep for the recent classifications UI.", + "motion": "Run on Motion", + "motionDesc": "Run classification when motion is detected within the configured crop.", + "interval": "Interval", + "intervalDesc": "Seconds between periodic classification runs. Leave empty to run only on motion.", + "intervalPlaceholder": "No interval", "stateClassesInfo": "Model updated. Retrain the model for the class changes to take effect.", "errors": { - "saveAttemptsInvalid": "Save attempts must be a whole number of 0 or greater" + "saveAttemptsInvalid": "Save attempts must be a whole number of 0 or greater", + "intervalInvalid": "Interval must be a whole number greater than 0" } }, "deleteDatasetImages": { diff --git a/web/src/components/classification/ClassificationModelEditDialog.tsx b/web/src/components/classification/ClassificationModelEditDialog.tsx index 4db4fc0833..4be2e04f95 100644 --- a/web/src/components/classification/ClassificationModelEditDialog.tsx +++ b/web/src/components/classification/ClassificationModelEditDialog.tsx @@ -61,6 +61,8 @@ type ObjectFormData = { type StateFormData = { enabled: boolean; saveAttempts: number; + motion: boolean; + interval?: number; classes: string[]; }; @@ -128,6 +130,16 @@ export default function ClassificationModelEditDialog({ // State model return z.object({ ...sharedFields, + motion: z.boolean(), + interval: z.preprocess( + (val) => + val === "" || val === null || val === undefined ? undefined : val, + z.coerce + .number({ message: t("edit.errors.intervalInvalid") }) + .int(t("edit.errors.intervalInvalid")) + .positive(t("edit.errors.intervalInvalid")) + .optional(), + ), classes: z .array(z.string()) .min(1, t("wizard.step1.errors.classRequired")) @@ -164,6 +176,8 @@ export default function ClassificationModelEditDialog({ : ({ enabled: model.enabled, saveAttempts: model.save_attempts ?? defaultSaveAttempts, + motion: model.state_config?.motion ?? false, + interval: model.state_config?.interval, classes: [""], // Will be populated from dataset } as StateFormData), mode: "onChange", @@ -191,6 +205,8 @@ export default function ClassificationModelEditDialog({ form.reset({ enabled: model.enabled, saveAttempts: model.save_attempts ?? defaultSaveAttempts, + motion: model.state_config?.motion ?? false, + interval: model.state_config?.interval, classes: [""], } as StateFormData); } @@ -274,6 +290,7 @@ export default function ClassificationModelEditDialog({ setIsSaving(true); try { if (isObjectModel) { + // object model save const objectData = data as ObjectFormData; // Update the config @@ -302,8 +319,18 @@ export default function ClassificationModelEditDialog({ position: "top-center", }); } else { + // state model save const stateData = data as StateFormData; + const stateConfig: { motion: boolean; interval?: number | null } = { + motion: stateData.motion, + }; + if (stateData.interval != null) { + stateConfig.interval = stateData.interval; + } else if (model.state_config?.interval != null) { + stateConfig.interval = null; + } + await axios.put("/config/set", { requires_restart: 0, update_topic: `config/classification/custom/${model.name}`, @@ -313,6 +340,7 @@ export default function ClassificationModelEditDialog({ [model.name]: { enabled: stateData.enabled, save_attempts: stateData.saveAttempts, + state_config: stateConfig, }, }, }, @@ -601,6 +629,58 @@ export default function ClassificationModelEditDialog({ )} + {isStateModel && ( + <> + ( + +
+ + {t("edit.motion")} + + + {t("edit.motionDesc")} + +
+ + + +
+ )} + /> + + ( + + + {t("edit.interval")} + + + + + + {t("edit.intervalDesc")} + + + + )} + /> + + )} +