From c9e320c3138208e93a7aa84c5cc54cae28d5e596 Mon Sep 17 00:00:00 2001 From: Josh Hawkins <32435876+hawkeye217@users.noreply.github.com> Date: Wed, 2 Jul 2025 18:53:10 -0500 Subject: [PATCH] renaming and deletion fixes --- frigate/api/event.py | 47 ++++++++----------- frigate/comms/webpush.py | 1 + .../data_processing/post/semantic_trigger.py | 4 +- web/public/locales/en/views/settings.json | 2 +- .../overlay/CreateTriggerDialog.tsx | 7 ++- .../overlay/DeleteTriggerDialog.tsx | 16 ++++++- .../components/overlay/DeleteUserDialog.tsx | 2 +- web/src/pages/FaceLibrary.tsx | 1 + .../classification/ModelTrainingView.tsx | 1 + web/src/views/settings/TriggerView.tsx | 40 ++++++++++++---- 10 files changed, 76 insertions(+), 45 deletions(-) diff --git a/frigate/api/event.py b/frigate/api/event.py index 2766f0814..3b4b413be 100644 --- a/frigate/api/event.py +++ b/frigate/api/event.py @@ -1647,24 +1647,14 @@ def update_trigger_embedding( status_code=400, ) - old = list( - Trigger.select(Trigger.camera, Trigger.name, Trigger.data) - .where(Trigger.camera == camera, Trigger.name == name) - .dicts() - .iterator() - ) - if not old: - return JSONResponse( - content={ - "success": False, - "message": f"Trigger {camera}:{name} not found", - }, - status_code=404, - ) + # Check if trigger exists for upsert + trigger = Trigger.get_or_none(Trigger.camera == camera, Trigger.name == name) - context.delete_trigger_thumbnail(camera, old[0]["data"]) + if trigger: + # Update existing trigger + if trigger.data != body.data: # Delete old thumbnail only if data changes + context.delete_trigger_thumbnail(camera, trigger.data) - updated = ( Trigger.update( data=body.data, model=request.app.frigate_config.semantic_search.model, @@ -1672,18 +1662,19 @@ def update_trigger_embedding( threshold=body.threshold, triggering_event_id="", last_triggered=None, - ) - .where(Trigger.camera == camera, Trigger.name == name) - .execute() - ) - - if updated == 0: - return JSONResponse( - content={ - "success": False, - "message": f"Trigger {camera}:{name} not found", - }, - status_code=404, + ).where(Trigger.camera == camera, Trigger.name == name).execute() + else: + # Create new trigger (for rename case) + Trigger.create( + camera=camera, + name=name, + type=body.type, + data=body.data, + threshold=body.threshold, + model=request.app.frigate_config.semantic_search.model, + embedding=np.array(embedding, dtype=np.float32).tobytes(), + triggering_event_id="", + last_triggered=None, ) if body.type == "thumbnail": diff --git a/frigate/comms/webpush.py b/frigate/comms/webpush.py index 99f2ff970..302140c10 100644 --- a/frigate/comms/webpush.py +++ b/frigate/comms/webpush.py @@ -196,6 +196,7 @@ class WebPushClient(Communicator): # type: ignore[misc] # notification action enabled if ( not self.config.cameras[camera].notifications.enabled + or name not in self.config.cameras[camera].semantic_search.triggers or "notification" not in self.config.cameras[camera] .semantic_search.triggers[name] diff --git a/frigate/data_processing/post/semantic_trigger.py b/frigate/data_processing/post/semantic_trigger.py index 14fc479be..b56ca4a56 100644 --- a/frigate/data_processing/post/semantic_trigger.py +++ b/frigate/data_processing/post/semantic_trigger.py @@ -81,7 +81,9 @@ class SemanticTriggerProcessor(PostProcessorApi): for trigger in triggers: if ( - not self.config.cameras[camera] + trigger["name"] + not in self.config.cameras[camera].semantic_search.triggers + or not self.config.cameras[camera] .semantic_search.triggers[trigger["name"]] .enabled ): diff --git a/web/public/locales/en/views/settings.json b/web/public/locales/en/views/settings.json index 6713186df..91eefd5c3 100644 --- a/web/public/locales/en/views/settings.json +++ b/web/public/locales/en/views/settings.json @@ -681,7 +681,7 @@ }, "deleteTrigger": { "title": "Delete Trigger", - "desc": "Are you sure you want to delete the trigger {{triggerName}} from camera {{camera}}? This action cannot be undone." + "desc": "Are you sure you want to delete the trigger {{triggerName}}? This action cannot be undone." }, "form": { "name": { diff --git a/web/src/components/overlay/CreateTriggerDialog.tsx b/web/src/components/overlay/CreateTriggerDialog.tsx index b74da75f6..243dd8502 100644 --- a/web/src/components/overlay/CreateTriggerDialog.tsx +++ b/web/src/components/overlay/CreateTriggerDialog.tsx @@ -1,4 +1,4 @@ -import { useEffect, useState, useMemo } from "react"; +import { useEffect, useMemo } from "react"; import { useTranslation } from "react-i18next"; import { useForm } from "react-hook-form"; import { zodResolver } from "@hookform/resolvers/zod"; @@ -40,6 +40,7 @@ type CreateTriggerDialogProps = { show: boolean; trigger: Trigger | null; selectedCamera: string; + isLoading: boolean; onCreate: ( enabled: boolean, name: string, @@ -56,12 +57,12 @@ export default function CreateTriggerDialog({ show, trigger, selectedCamera, + isLoading, onCreate, onEdit, onCancel, }: CreateTriggerDialogProps) { const { t } = useTranslation("views/settings"); - const [isLoading, setIsLoading] = useState(false); const { data: config } = useSWR("config"); const existingTriggerNames = useMemo(() => { @@ -112,7 +113,6 @@ export default function CreateTriggerDialog({ }); const onSubmit = async (values: z.infer) => { - setIsLoading(true); if (trigger) { onEdit({ ...values }); } else { @@ -125,7 +125,6 @@ export default function CreateTriggerDialog({ values.actions, ); } - setIsLoading(false); }; useEffect(() => { diff --git a/web/src/components/overlay/DeleteTriggerDialog.tsx b/web/src/components/overlay/DeleteTriggerDialog.tsx index cf249e282..79752817d 100644 --- a/web/src/components/overlay/DeleteTriggerDialog.tsx +++ b/web/src/components/overlay/DeleteTriggerDialog.tsx @@ -9,10 +9,12 @@ import { } from "@/components/ui/dialog"; import { Button } from "@/components/ui/button"; import { Trans } from "react-i18next"; +import ActivityIndicator from "@/components/indicators/activity-indicator"; type DeleteTriggerDialogProps = { show: boolean; triggerName: string; + isLoading: boolean; onCancel: () => void; onDelete: () => void; }; @@ -20,6 +22,7 @@ type DeleteTriggerDialogProps = { export default function DeleteTriggerDialog({ show, triggerName, + isLoading, onCancel, onDelete, }: DeleteTriggerDialogProps) { @@ -48,16 +51,25 @@ export default function DeleteTriggerDialog({ aria-label={t("button.cancel", { ns: "common" })} onClick={onCancel} type="button" + disabled={isLoading} > {t("button.cancel", { ns: "common" })} diff --git a/web/src/components/overlay/DeleteUserDialog.tsx b/web/src/components/overlay/DeleteUserDialog.tsx index 677047a11..162ef2241 100644 --- a/web/src/components/overlay/DeleteUserDialog.tsx +++ b/web/src/components/overlay/DeleteUserDialog.tsx @@ -60,7 +60,7 @@ export default function DeleteUserDialog({