add enabled switch

This commit is contained in:
Josh Hawkins 2025-07-02 13:08:02 -05:00
parent 38a5797681
commit aca9e15790
9 changed files with 61 additions and 4 deletions

View File

@ -107,6 +107,8 @@ class CameraConfigUpdateSubscriber:
config.record = updated_config config.record = updated_config
elif update_type == CameraConfigUpdateEnum.review: elif update_type == CameraConfigUpdateEnum.review:
config.review = updated_config config.review = updated_config
elif update_type == CameraConfigUpdateEnum.semantic_search:
config.semantic_search = updated_config
elif update_type == CameraConfigUpdateEnum.snapshots: elif update_type == CameraConfigUpdateEnum.snapshots:
config.snapshots = updated_config config.snapshots = updated_config
elif update_type == CameraConfigUpdateEnum.zones: elif update_type == CameraConfigUpdateEnum.zones:

View File

@ -125,10 +125,11 @@ class SemanticSearchConfig(FrigateBaseModel):
class TriggerConfig(FrigateBaseModel): class TriggerConfig(FrigateBaseModel):
enabled: bool = Field(default=True, title="Enable this trigger")
type: TriggerType = Field(default=TriggerType.DESCRIPTION, title="Type of trigger") type: TriggerType = Field(default=TriggerType.DESCRIPTION, title="Type of trigger")
data: str = Field(title="Trigger content (text phrase or image ID)") data: str = Field(title="Trigger content (text phrase or image ID)")
threshold: float = Field( threshold: float = Field(
title="Confidence score required to run the trigger.", title="Confidence score required to run the trigger",
default=0.8, default=0.8,
gt=0.0, gt=0.0,
le=1.0, le=1.0,

View File

@ -80,6 +80,16 @@ class SemanticTriggerProcessor(PostProcessorApi):
) )
for trigger in triggers: 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( logger.debug(
f"Processing {trigger['type']} trigger for {event_id} on {trigger['camera']}: {trigger['name']}" f"Processing {trigger['type']} trigger for {event_id} on {trigger['camera']}: {trigger['name']}"
) )
@ -171,6 +181,7 @@ class SemanticTriggerProcessor(PostProcessorApi):
.actions .actions
): ):
# TODO: handle actions for the trigger # TODO: handle actions for the trigger
# notifications already handled by webpush
pass pass
if WRITE_DEBUG_IMAGES: if WRITE_DEBUG_IMAGES:

View File

@ -97,7 +97,11 @@ class EmbeddingMaintainer(threading.Thread):
self.config_updater = CameraConfigUpdateSubscriber( self.config_updater = CameraConfigUpdateSubscriber(
self.config, self.config,
self.config.cameras, self.config.cameras,
[CameraConfigUpdateEnum.add, CameraConfigUpdateEnum.remove], [
CameraConfigUpdateEnum.add,
CameraConfigUpdateEnum.remove,
CameraConfigUpdateEnum.semantic_search,
],
) )
# Configure Frigate DB # Configure Frigate DB

View File

@ -693,6 +693,9 @@
"alreadyExists": "A trigger with this name already exists for this camera." "alreadyExists": "A trigger with this name already exists for this camera."
} }
}, },
"enabled": {
"description": "Enable or disable this trigger"
},
"type": { "type": {
"title": "Trigger Type", "title": "Trigger Type",
"placeholder": "Select trigger type" "placeholder": "Select trigger type"

View File

@ -34,12 +34,14 @@ import ActivityIndicator from "@/components/indicators/activity-indicator";
import { FrigateConfig } from "@/types/frigateConfig"; import { FrigateConfig } from "@/types/frigateConfig";
import ImagePicker from "@/components/overlay/ImagePicker"; import ImagePicker from "@/components/overlay/ImagePicker";
import { Trigger, TriggerAction, TriggerType } from "@/types/trigger"; import { Trigger, TriggerAction, TriggerType } from "@/types/trigger";
import { Switch } from "@/components/ui/switch";
type CreateTriggerDialogProps = { type CreateTriggerDialogProps = {
show: boolean; show: boolean;
trigger: Trigger | null; trigger: Trigger | null;
selectedCamera: string; selectedCamera: string;
onCreate: ( onCreate: (
enabled: boolean,
name: string, name: string,
type: TriggerType, type: TriggerType,
data: string, data: string,
@ -74,6 +76,7 @@ export default function CreateTriggerDialog({
}, [config, selectedCamera]); }, [config, selectedCamera]);
const formSchema = z.object({ const formSchema = z.object({
enabled: z.boolean(),
name: z name: z
.string() .string()
.min(2, t("triggers.dialog.form.name.error.minLength")) .min(2, t("triggers.dialog.form.name.error.minLength"))
@ -101,6 +104,7 @@ export default function CreateTriggerDialog({
resolver: zodResolver(formSchema), resolver: zodResolver(formSchema),
mode: "onChange", mode: "onChange",
defaultValues: { defaultValues: {
enabled: trigger?.enabled ?? true,
name: trigger?.name ?? "", name: trigger?.name ?? "",
type: trigger?.type ?? "description", type: trigger?.type ?? "description",
data: trigger?.data ?? "", data: trigger?.data ?? "",
@ -115,6 +119,7 @@ export default function CreateTriggerDialog({
onEdit({ ...values }); onEdit({ ...values });
} else { } else {
onCreate( onCreate(
values.enabled,
values.name, values.name,
values.type, values.type,
values.data, values.data,
@ -128,6 +133,7 @@ export default function CreateTriggerDialog({
useEffect(() => { useEffect(() => {
if (!show) { if (!show) {
form.reset({ form.reset({
enabled: true,
name: "", name: "",
type: "description", type: "description",
data: "", data: "",
@ -136,6 +142,7 @@ export default function CreateTriggerDialog({
}); });
} else if (trigger) { } else if (trigger) {
form.reset({ form.reset({
enabled: trigger.enabled,
name: trigger.name, name: trigger.name,
type: trigger.type, type: trigger.type,
data: trigger.data, data: trigger.data,
@ -194,6 +201,29 @@ export default function CreateTriggerDialog({
)} )}
/> />
<FormField
control={form.control}
name="enabled"
render={({ field }) => (
<FormItem className="flex flex-row items-center justify-between">
<div className="space-y-0.5">
<FormLabel className="text-base">
{t("enabled", { ns: "common" })}
</FormLabel>
<div className="text-sm text-muted-foreground">
{t("triggers.dialog.form.enabled.description")}
</div>
</div>
<FormControl>
<Switch
checked={field.value}
onCheckedChange={field.onChange}
/>
</FormControl>
</FormItem>
)}
/>
<FormField <FormField
control={form.control} control={form.control}
name="type" name="type"

View File

@ -224,6 +224,7 @@ export interface CameraConfig {
semantic_search: { semantic_search: {
triggers: { triggers: {
[triggerName: string]: { [triggerName: string]: {
enabled: boolean;
type: TriggerType; type: TriggerType;
data: string; data: string;
threshold: number; threshold: number;

View File

@ -2,6 +2,7 @@ export type TriggerType = "thumbnail" | "description";
export type TriggerAction = "alert" | "notification"; export type TriggerAction = "alert" | "notification";
export type Trigger = { export type Trigger = {
enabled: boolean;
name: string; name: string;
type: TriggerType; type: TriggerType;
data: string; data: string;

View File

@ -36,6 +36,7 @@ type ConfigSetBody = {
triggers?: { triggers?: {
[key: string]: [key: string]:
| { | {
enabled: boolean;
type: string; type: string;
data: string; data: string;
threshold: number; threshold: number;
@ -84,6 +85,7 @@ export default function TriggerView({
return Object.entries( return Object.entries(
config.cameras[selectedCamera].semantic_search.triggers, config.cameras[selectedCamera].semantic_search.triggers,
).map(([name, trigger]) => ({ ).map(([name, trigger]) => ({
enabled: trigger.enabled,
name, name,
type: trigger.type, type: trigger.type,
data: trigger.data, data: trigger.data,
@ -99,7 +101,7 @@ export default function TriggerView({
const saveToConfig = useCallback( const saveToConfig = useCallback(
(trigger: Trigger, isEdit: boolean) => { (trigger: Trigger, isEdit: boolean) => {
setIsLoading(true); setIsLoading(true);
const { name, type, data, threshold, actions } = trigger; const { enabled, name, type, data, threshold, actions } = trigger;
const embeddingBody: TriggerEmbeddingBody = { type, data, threshold }; const embeddingBody: TriggerEmbeddingBody = { type, data, threshold };
const embeddingUrl = isEdit const embeddingUrl = isEdit
? `/trigger/embedding/${selectedCamera}/${name}` ? `/trigger/embedding/${selectedCamera}/${name}`
@ -117,6 +119,7 @@ export default function TriggerView({
semantic_search: { semantic_search: {
triggers: { triggers: {
[name]: { [name]: {
enabled,
type, type,
data, data,
threshold, threshold,
@ -172,6 +175,7 @@ export default function TriggerView({
const onCreate = useCallback( const onCreate = useCallback(
( (
enabled: boolean,
name: string, name: string,
type: TriggerType, type: TriggerType,
data: string, data: string,
@ -179,7 +183,7 @@ export default function TriggerView({
actions: TriggerAction[], actions: TriggerAction[],
) => { ) => {
setUnsavedChanges(true); setUnsavedChanges(true);
saveToConfig({ name, type, data, threshold, actions }, false); saveToConfig({ enabled, name, type, data, threshold, actions }, false);
setShowCreate(false); setShowCreate(false);
}, },
[saveToConfig, setUnsavedChanges], [saveToConfig, setUnsavedChanges],