diff --git a/frigate/config/camera/updater.py b/frigate/config/camera/updater.py index 91ca8257a..756e370db 100644 --- a/frigate/config/camera/updater.py +++ b/frigate/config/camera/updater.py @@ -107,6 +107,8 @@ class CameraConfigUpdateSubscriber: config.record = updated_config elif update_type == CameraConfigUpdateEnum.review: config.review = updated_config + elif update_type == CameraConfigUpdateEnum.semantic_search: + config.semantic_search = updated_config elif update_type == CameraConfigUpdateEnum.snapshots: config.snapshots = updated_config elif update_type == CameraConfigUpdateEnum.zones: diff --git a/frigate/config/classification.py b/frigate/config/classification.py index bf101e963..1f019e817 100644 --- a/frigate/config/classification.py +++ b/frigate/config/classification.py @@ -125,10 +125,11 @@ class SemanticSearchConfig(FrigateBaseModel): class TriggerConfig(FrigateBaseModel): + enabled: bool = Field(default=True, title="Enable this trigger") type: TriggerType = Field(default=TriggerType.DESCRIPTION, title="Type of trigger") data: str = Field(title="Trigger content (text phrase or image ID)") threshold: float = Field( - title="Confidence score required to run the trigger.", + title="Confidence score required to run the trigger", default=0.8, gt=0.0, le=1.0, diff --git a/frigate/data_processing/post/semantic_trigger.py b/frigate/data_processing/post/semantic_trigger.py index faf37ecec..14fc479be 100644 --- a/frigate/data_processing/post/semantic_trigger.py +++ b/frigate/data_processing/post/semantic_trigger.py @@ -80,6 +80,16 @@ class SemanticTriggerProcessor(PostProcessorApi): ) for trigger in triggers: + if ( + not self.config.cameras[camera] + .semantic_search.triggers[trigger["name"]] + .enabled + ): + logger.debug( + f"Trigger {trigger['name']} is disabled for camera {camera}" + ) + continue + logger.debug( f"Processing {trigger['type']} trigger for {event_id} on {trigger['camera']}: {trigger['name']}" ) @@ -171,6 +181,7 @@ class SemanticTriggerProcessor(PostProcessorApi): .actions ): # TODO: handle actions for the trigger + # notifications already handled by webpush pass if WRITE_DEBUG_IMAGES: diff --git a/frigate/embeddings/maintainer.py b/frigate/embeddings/maintainer.py index 8fd3e853e..53935100d 100644 --- a/frigate/embeddings/maintainer.py +++ b/frigate/embeddings/maintainer.py @@ -97,7 +97,11 @@ class EmbeddingMaintainer(threading.Thread): self.config_updater = CameraConfigUpdateSubscriber( self.config, self.config.cameras, - [CameraConfigUpdateEnum.add, CameraConfigUpdateEnum.remove], + [ + CameraConfigUpdateEnum.add, + CameraConfigUpdateEnum.remove, + CameraConfigUpdateEnum.semantic_search, + ], ) # Configure Frigate DB diff --git a/web/public/locales/en/views/settings.json b/web/public/locales/en/views/settings.json index 9df1a2c38..9061aa74b 100644 --- a/web/public/locales/en/views/settings.json +++ b/web/public/locales/en/views/settings.json @@ -693,6 +693,9 @@ "alreadyExists": "A trigger with this name already exists for this camera." } }, + "enabled": { + "description": "Enable or disable this trigger" + }, "type": { "title": "Trigger Type", "placeholder": "Select trigger type" diff --git a/web/src/components/overlay/CreateTriggerDialog.tsx b/web/src/components/overlay/CreateTriggerDialog.tsx index 73f3b785e..ec8572281 100644 --- a/web/src/components/overlay/CreateTriggerDialog.tsx +++ b/web/src/components/overlay/CreateTriggerDialog.tsx @@ -34,12 +34,14 @@ import ActivityIndicator from "@/components/indicators/activity-indicator"; import { FrigateConfig } from "@/types/frigateConfig"; import ImagePicker from "@/components/overlay/ImagePicker"; import { Trigger, TriggerAction, TriggerType } from "@/types/trigger"; +import { Switch } from "@/components/ui/switch"; type CreateTriggerDialogProps = { show: boolean; trigger: Trigger | null; selectedCamera: string; onCreate: ( + enabled: boolean, name: string, type: TriggerType, data: string, @@ -74,6 +76,7 @@ export default function CreateTriggerDialog({ }, [config, selectedCamera]); const formSchema = z.object({ + enabled: z.boolean(), name: z .string() .min(2, t("triggers.dialog.form.name.error.minLength")) @@ -101,6 +104,7 @@ export default function CreateTriggerDialog({ resolver: zodResolver(formSchema), mode: "onChange", defaultValues: { + enabled: trigger?.enabled ?? true, name: trigger?.name ?? "", type: trigger?.type ?? "description", data: trigger?.data ?? "", @@ -115,6 +119,7 @@ export default function CreateTriggerDialog({ onEdit({ ...values }); } else { onCreate( + values.enabled, values.name, values.type, values.data, @@ -128,6 +133,7 @@ export default function CreateTriggerDialog({ useEffect(() => { if (!show) { form.reset({ + enabled: true, name: "", type: "description", data: "", @@ -136,6 +142,7 @@ export default function CreateTriggerDialog({ }); } else if (trigger) { form.reset({ + enabled: trigger.enabled, name: trigger.name, type: trigger.type, data: trigger.data, @@ -194,6 +201,29 @@ export default function CreateTriggerDialog({ )} /> + ( + +
+ + {t("enabled", { ns: "common" })} + +
+ {t("triggers.dialog.form.enabled.description")} +
+
+ + + +
+ )} + /> + ({ + enabled: trigger.enabled, name, type: trigger.type, data: trigger.data, @@ -99,7 +101,7 @@ export default function TriggerView({ const saveToConfig = useCallback( (trigger: Trigger, isEdit: boolean) => { setIsLoading(true); - const { name, type, data, threshold, actions } = trigger; + const { enabled, name, type, data, threshold, actions } = trigger; const embeddingBody: TriggerEmbeddingBody = { type, data, threshold }; const embeddingUrl = isEdit ? `/trigger/embedding/${selectedCamera}/${name}` @@ -117,6 +119,7 @@ export default function TriggerView({ semantic_search: { triggers: { [name]: { + enabled, type, data, threshold, @@ -172,6 +175,7 @@ export default function TriggerView({ const onCreate = useCallback( ( + enabled: boolean, name: string, type: TriggerType, data: string, @@ -179,7 +183,7 @@ export default function TriggerView({ actions: TriggerAction[], ) => { setUnsavedChanges(true); - saveToConfig({ name, type, data, threshold, actions }, false); + saveToConfig({ enabled, name, type, data, threshold, actions }, false); setShowCreate(false); }, [saveToConfig, setUnsavedChanges],