mirror of
https://github.com/blakeblackshear/frigate.git
synced 2026-07-13 07:21:14 +03:00
add state motion and interval configs to edit dialog
This commit is contained in:
parent
d14932c0af
commit
25087007d6
@ -69,9 +69,15 @@
|
|||||||
"enabledDesc": "Run this model. When disabled, it stops running and no longer classifies.",
|
"enabledDesc": "Run this model. When disabled, it stops running and no longer classifies.",
|
||||||
"saveAttempts": "Save Attempts",
|
"saveAttempts": "Save Attempts",
|
||||||
"saveAttemptsDesc": "Number of classification attempt images to keep for the recent classifications UI.",
|
"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.",
|
"stateClassesInfo": "Model updated. Retrain the model for the class changes to take effect.",
|
||||||
"errors": {
|
"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": {
|
"deleteDatasetImages": {
|
||||||
|
|||||||
@ -61,6 +61,8 @@ type ObjectFormData = {
|
|||||||
type StateFormData = {
|
type StateFormData = {
|
||||||
enabled: boolean;
|
enabled: boolean;
|
||||||
saveAttempts: number;
|
saveAttempts: number;
|
||||||
|
motion: boolean;
|
||||||
|
interval?: number;
|
||||||
classes: string[];
|
classes: string[];
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -128,6 +130,16 @@ export default function ClassificationModelEditDialog({
|
|||||||
// State model
|
// State model
|
||||||
return z.object({
|
return z.object({
|
||||||
...sharedFields,
|
...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
|
classes: z
|
||||||
.array(z.string())
|
.array(z.string())
|
||||||
.min(1, t("wizard.step1.errors.classRequired"))
|
.min(1, t("wizard.step1.errors.classRequired"))
|
||||||
@ -164,6 +176,8 @@ export default function ClassificationModelEditDialog({
|
|||||||
: ({
|
: ({
|
||||||
enabled: model.enabled,
|
enabled: model.enabled,
|
||||||
saveAttempts: model.save_attempts ?? defaultSaveAttempts,
|
saveAttempts: model.save_attempts ?? defaultSaveAttempts,
|
||||||
|
motion: model.state_config?.motion ?? false,
|
||||||
|
interval: model.state_config?.interval,
|
||||||
classes: [""], // Will be populated from dataset
|
classes: [""], // Will be populated from dataset
|
||||||
} as StateFormData),
|
} as StateFormData),
|
||||||
mode: "onChange",
|
mode: "onChange",
|
||||||
@ -191,6 +205,8 @@ export default function ClassificationModelEditDialog({
|
|||||||
form.reset({
|
form.reset({
|
||||||
enabled: model.enabled,
|
enabled: model.enabled,
|
||||||
saveAttempts: model.save_attempts ?? defaultSaveAttempts,
|
saveAttempts: model.save_attempts ?? defaultSaveAttempts,
|
||||||
|
motion: model.state_config?.motion ?? false,
|
||||||
|
interval: model.state_config?.interval,
|
||||||
classes: [""],
|
classes: [""],
|
||||||
} as StateFormData);
|
} as StateFormData);
|
||||||
}
|
}
|
||||||
@ -274,6 +290,7 @@ export default function ClassificationModelEditDialog({
|
|||||||
setIsSaving(true);
|
setIsSaving(true);
|
||||||
try {
|
try {
|
||||||
if (isObjectModel) {
|
if (isObjectModel) {
|
||||||
|
// object model save
|
||||||
const objectData = data as ObjectFormData;
|
const objectData = data as ObjectFormData;
|
||||||
|
|
||||||
// Update the config
|
// Update the config
|
||||||
@ -302,8 +319,18 @@ export default function ClassificationModelEditDialog({
|
|||||||
position: "top-center",
|
position: "top-center",
|
||||||
});
|
});
|
||||||
} else {
|
} else {
|
||||||
|
// state model save
|
||||||
const stateData = data as StateFormData;
|
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", {
|
await axios.put("/config/set", {
|
||||||
requires_restart: 0,
|
requires_restart: 0,
|
||||||
update_topic: `config/classification/custom/${model.name}`,
|
update_topic: `config/classification/custom/${model.name}`,
|
||||||
@ -313,6 +340,7 @@ export default function ClassificationModelEditDialog({
|
|||||||
[model.name]: {
|
[model.name]: {
|
||||||
enabled: stateData.enabled,
|
enabled: stateData.enabled,
|
||||||
save_attempts: stateData.saveAttempts,
|
save_attempts: stateData.saveAttempts,
|
||||||
|
state_config: stateConfig,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
@ -601,6 +629,58 @@ export default function ClassificationModelEditDialog({
|
|||||||
</div>
|
</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
|
<FormField
|
||||||
control={form.control}
|
control={form.control}
|
||||||
name="saveAttempts"
|
name="saveAttempts"
|
||||||
|
|||||||
@ -370,6 +370,7 @@ export type CustomClassificationModelConfig = {
|
|||||||
};
|
};
|
||||||
};
|
};
|
||||||
motion: boolean;
|
motion: boolean;
|
||||||
|
interval?: number;
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user