add state motion and interval configs to edit dialog

This commit is contained in:
Josh Hawkins 2026-07-12 08:42:16 -05:00
parent d14932c0af
commit 25087007d6
3 changed files with 88 additions and 1 deletions

View File

@ -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": {

View File

@ -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({
</div>
)}
{isStateModel && (
<>
<FormField
control={form.control}
name="motion"
render={({ field }) => (
<FormItem className="flex flex-row items-center justify-between gap-4">
<div className="space-y-0.5">
<FormLabel className="text-primary-variant">
{t("edit.motion")}
</FormLabel>
<FormDescription className="text-xs">
{t("edit.motionDesc")}
</FormDescription>
</div>
<FormControl>
<Switch
checked={field.value}
onCheckedChange={field.onChange}
/>
</FormControl>
</FormItem>
)}
/>
<FormField
control={form.control}
name="interval"
render={({ field }) => (
<FormItem>
<FormLabel className="text-primary-variant">
{t("edit.interval")}
</FormLabel>
<FormControl>
<Input
className="h-8"
inputMode="numeric"
placeholder={t("edit.intervalPlaceholder")}
{...field}
value={field.value ?? ""}
/>
</FormControl>
<FormDescription className="text-xs">
{t("edit.intervalDesc")}
</FormDescription>
<FormMessage />
</FormItem>
)}
/>
</>
)}
<FormField
control={form.control}
name="saveAttempts"

View File

@ -370,6 +370,7 @@ export type CustomClassificationModelConfig = {
};
};
motion: boolean;
interval?: number;
};
};