From 32f6114573999fb7150530739b6413a5e2dde5b2 Mon Sep 17 00:00:00 2001 From: Josh Hawkins <32435876+hawkeye217@users.noreply.github.com> Date: Thu, 15 Jan 2026 20:41:39 -0600 Subject: [PATCH 1/9] misc triggers tweaks i18n fixes fix toaster color fix clicking on labels selecting incorrect checkbox --- web/public/locales/en/common.json | 1 + web/src/components/overlay/CreateTriggerDialog.tsx | 13 ++++++++----- .../trigger/wizard/Step3ThresholdAndActions.tsx | 11 +++++++---- web/src/views/settings/TriggerView.tsx | 11 +++++++---- 4 files changed, 23 insertions(+), 13 deletions(-) diff --git a/web/public/locales/en/common.json b/web/public/locales/en/common.json index 2ae6297a1..8bf13ca61 100644 --- a/web/public/locales/en/common.json +++ b/web/public/locales/en/common.json @@ -3,6 +3,7 @@ "untilForTime": "Until {{time}}", "untilForRestart": "Until Frigate restarts.", "untilRestart": "Until restart", + "never": "Never", "ago": "{{timeAgo}} ago", "justNow": "Just now", "today": "Today", diff --git a/web/src/components/overlay/CreateTriggerDialog.tsx b/web/src/components/overlay/CreateTriggerDialog.tsx index 11734acaf..ef30c649d 100644 --- a/web/src/components/overlay/CreateTriggerDialog.tsx +++ b/web/src/components/overlay/CreateTriggerDialog.tsx @@ -268,7 +268,7 @@ export default function CreateTriggerDialog({
- {t("enabled", { ns: "common" })} + {t("button.enabled", { ns: "common" })}
{t("triggers.dialog.form.enabled.description")} @@ -394,7 +394,10 @@ export default function CreateTriggerDialog({
{availableActions.map((action) => ( -
+
+ + ))}
diff --git a/web/src/components/trigger/wizard/Step3ThresholdAndActions.tsx b/web/src/components/trigger/wizard/Step3ThresholdAndActions.tsx index 33568dca7..db82febb4 100644 --- a/web/src/components/trigger/wizard/Step3ThresholdAndActions.tsx +++ b/web/src/components/trigger/wizard/Step3ThresholdAndActions.tsx @@ -142,7 +142,10 @@ export default function Step3ThresholdAndActions({ {t("triggers.dialog.form.actions.title")}
{availableActions.map((action) => ( -
+
+ + ))}
diff --git a/web/src/views/settings/TriggerView.tsx b/web/src/views/settings/TriggerView.tsx index 7448a210b..28aa148fe 100644 --- a/web/src/views/settings/TriggerView.tsx +++ b/web/src/views/settings/TriggerView.tsx @@ -1,6 +1,7 @@ import { useCallback, useEffect, useMemo, useState } from "react"; import { Trans, useTranslation } from "react-i18next"; -import { Toaster, toast } from "sonner"; +import { toast } from "sonner"; +import { Toaster } from "@/components/ui/sonner"; import useSWR from "swr"; import axios from "axios"; import { Button } from "@/components/ui/button"; @@ -598,7 +599,7 @@ export default function TriggerView({ date_style: "medium", }, ) - : "Never"} + : t("never", { ns: "common" })} {trigger_status?.triggers[trigger.name] ?.triggering_event_id && ( @@ -663,7 +664,9 @@ export default function TriggerView({ - {t("name", { ns: "common" })} + + {t("name", { ns: "triggers.table.name" })} + {t("triggers.table.type")} {t("triggers.table.lastTriggered")} @@ -759,7 +762,7 @@ export default function TriggerView({ date_style: "medium", }, ) - : "Never"} + : t("time.never", { ns: "common" })} {trigger_status?.triggers[trigger.name] ?.triggering_event_id && ( From f428a64948d89f3bd4b546cde71170547ba7756c Mon Sep 17 00:00:00 2001 From: Josh Hawkins <32435876+hawkeye217@users.noreply.github.com> Date: Thu, 15 Jan 2026 20:42:24 -0600 Subject: [PATCH 2/9] update copilot instructions --- .github/copilot-instructions.md | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/.github/copilot-instructions.md b/.github/copilot-instructions.md index 6c14bb166..89acd8a9b 100644 --- a/.github/copilot-instructions.md +++ b/.github/copilot-instructions.md @@ -1,2 +1,3 @@ -Never write strings in the frontend directly, always write to and reference the relevant translations file. -Always conform new and refactored code to the existing coding style in the project. +- For Frigate NVR, never write strings in the frontend directly. Since the project uses `react-i18next`, use `t()` and write the English string in the relevant translations file in `web/public/locales/en`. +- Always conform new and refactored code to the existing coding style in the project. +- Always have a way to test your work and confirm your changes. When running backend tests, use `python3 -u -m unittest`. From dcff06906c43decc444d483c71c1c80c8e543e4c Mon Sep 17 00:00:00 2001 From: Josh Hawkins <32435876+hawkeye217@users.noreply.github.com> Date: Fri, 16 Jan 2026 06:57:44 -0600 Subject: [PATCH 3/9] lpr docs tweaks --- docs/docs/configuration/license_plate_recognition.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/docs/docs/configuration/license_plate_recognition.md b/docs/docs/configuration/license_plate_recognition.md index a18c822f5..5f70dd9a0 100644 --- a/docs/docs/configuration/license_plate_recognition.md +++ b/docs/docs/configuration/license_plate_recognition.md @@ -68,8 +68,8 @@ Fine-tune the LPR feature using these optional parameters at the global level of - Default: `1000` pixels. Note: this is intentionally set very low as it is an _area_ measurement (length x width). For reference, 1000 pixels represents a ~32x32 pixel square in your camera image. - Depending on the resolution of your camera's `detect` stream, you can increase this value to ignore small or distant plates. - **`device`**: Device to use to run license plate detection _and_ recognition models. - - Default: `CPU` - - This can be `CPU`, `GPU`, or the GPU's device number. For users without a model that detects license plates natively, using a GPU may increase performance of the YOLOv9 license plate detector model. See the [Hardware Accelerated Enrichments](/configuration/hardware_acceleration_enrichments.md) documentation. However, for users who run a model that detects `license_plate` natively, there is little to no performance gain reported with running LPR on GPU compared to the CPU. + - Default: `None` + - This is auto-selected by Frigate and can be `CPU`, `GPU`, or the GPU's device number. For users without a model that detects license plates natively, using a GPU may increase performance of the YOLOv9 license plate detector model. See the [Hardware Accelerated Enrichments](/configuration/hardware_acceleration_enrichments.md) documentation. However, for users who run a model that detects `license_plate` natively, there is little to no performance gain reported with running LPR on GPU compared to the CPU. - **`model_size`**: The size of the model used to identify regions of text on plates. - Default: `small` - This can be `small` or `large`. @@ -432,6 +432,6 @@ If you are using a model that natively detects `license_plate`, add an _object m If you are not using a model that natively detects `license_plate` or you are using dedicated LPR camera mode, only a _motion mask_ over your text is required. -### I see "Error running ... model" in my logs. How can I fix this? +### I see "Error running ... model" in my logs, or my inference time is very high. How can I fix this? This usually happens when your GPU is unable to compile or use one of the LPR models. Set your `device` to `CPU` and try again. GPU acceleration only provides a slight performance increase, and the models are lightweight enough to run without issue on most CPUs. From 4fcf4675075773341542174bd038e41786a67e12 Mon Sep 17 00:00:00 2001 From: Josh Hawkins <32435876+hawkeye217@users.noreply.github.com> Date: Fri, 16 Jan 2026 10:08:11 -0600 Subject: [PATCH 4/9] add retry params to gemini --- frigate/genai/gemini.py | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/frigate/genai/gemini.py b/frigate/genai/gemini.py index 36e708594..83bd3340d 100644 --- a/frigate/genai/gemini.py +++ b/frigate/genai/gemini.py @@ -24,6 +24,14 @@ class GeminiClient(GenAIClient): http_options_dict = { "api_version": "v1", "timeout": int(self.timeout * 1000), # requires milliseconds + "retry_options": types.HttpRetryOptions( + attempts=3, + initial_delay=1.0, + max_delay=60.0, + exp_base=2.0, + jitter=1.0, + http_status_codes=[429, 500, 502, 503, 504], + ), } if isinstance(self.genai_config.provider_options, dict): From c78d020cca85f4c77ef9fc6ebff2e73a04ddc3e4 Mon Sep 17 00:00:00 2001 From: Josh Hawkins <32435876+hawkeye217@users.noreply.github.com> Date: Sat, 17 Jan 2026 07:16:44 -0600 Subject: [PATCH 5/9] i18n fix --- .../components/trigger/wizard/Step3ThresholdAndActions.tsx | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/web/src/components/trigger/wizard/Step3ThresholdAndActions.tsx b/web/src/components/trigger/wizard/Step3ThresholdAndActions.tsx index db82febb4..2b560ded3 100644 --- a/web/src/components/trigger/wizard/Step3ThresholdAndActions.tsx +++ b/web/src/components/trigger/wizard/Step3ThresholdAndActions.tsx @@ -200,9 +200,7 @@ export default function Step3ThresholdAndActions({ {isLoading && } {isLoading ? t("button.saving", { ns: "common" }) - : t("triggers.dialog.form.save", { - defaultValue: "Save Trigger", - })} + : t("button.save", { ns: "common" })}
From af942fb64ee2015dbba15579c14f29106e4f09ea Mon Sep 17 00:00:00 2001 From: Josh Hawkins <32435876+hawkeye217@users.noreply.github.com> Date: Sat, 17 Jan 2026 14:56:18 -0600 Subject: [PATCH 6/9] ensure users only see recognized plates from accessible cameras in explore --- frigate/api/app.py | 17 ++++++++++++++--- 1 file changed, 14 insertions(+), 3 deletions(-) diff --git a/frigate/api/app.py b/frigate/api/app.py index c87e929a6..440adfce4 100644 --- a/frigate/api/app.py +++ b/frigate/api/app.py @@ -23,7 +23,12 @@ from markupsafe import escape from peewee import SQL, fn, operator from pydantic import ValidationError -from frigate.api.auth import allow_any_authenticated, allow_public, require_role +from frigate.api.auth import ( + allow_any_authenticated, + allow_public, + get_allowed_cameras_for_filter, + require_role, +) from frigate.api.defs.query.app_query_parameters import AppTimelineHourlyQueryParameters from frigate.api.defs.request.app_body import AppConfigSetBody from frigate.api.defs.tags import Tags @@ -687,13 +692,19 @@ def plusModels(request: Request, filterByCurrentModelDetector: bool = False): @router.get( "/recognized_license_plates", dependencies=[Depends(allow_any_authenticated())] ) -def get_recognized_license_plates(split_joined: Optional[int] = None): +def get_recognized_license_plates( + split_joined: Optional[int] = None, + allowed_cameras: List[str] = Depends(get_allowed_cameras_for_filter), +): try: query = ( Event.select( SQL("json_extract(data, '$.recognized_license_plate') AS plate") ) - .where(SQL("json_extract(data, '$.recognized_license_plate') IS NOT NULL")) + .where( + (SQL("json_extract(data, '$.recognized_license_plate') IS NOT NULL")) + & (Event.camera << allowed_cameras) + ) .distinct() ) recognized_license_plates = [row[0] for row in query.tuples()] From 208a83cf79047e96c9dd0be3edb867baec946813 Mon Sep 17 00:00:00 2001 From: Josh Hawkins <32435876+hawkeye217@users.noreply.github.com> Date: Sat, 17 Jan 2026 15:32:49 -0600 Subject: [PATCH 7/9] ensure all zone filters are converted to pixels zone-level filters were never converted from percentage area to pixels. RuntimeFilterConfig was only applied to filters at the camera level, not zone.filters. Fixes https://github.com/blakeblackshear/frigate/discussions/21694 --- frigate/config/config.py | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/frigate/config/config.py b/frigate/config/config.py index a9c54976e..a26d4c50e 100644 --- a/frigate/config/config.py +++ b/frigate/config/config.py @@ -662,6 +662,13 @@ class FrigateConfig(FrigateBaseModel): # generate zone contours if len(camera_config.zones) > 0: for zone in camera_config.zones.values(): + if zone.filters: + for object_name, filter_config in zone.filters.items(): + zone.filters[object_name] = RuntimeFilterConfig( + frame_shape=camera_config.frame_shape, + **filter_config.model_dump(exclude_unset=True), + ) + zone.generate_contour(camera_config.frame_shape) # Set live view stream if none is set From 4dd2567848f3c5b17a2b3f54dc46f243f67717e9 Mon Sep 17 00:00:00 2001 From: Josh Hawkins <32435876+hawkeye217@users.noreply.github.com> Date: Sat, 17 Jan 2026 15:33:07 -0600 Subject: [PATCH 8/9] add test for percentage based zone filters --- frigate/test/test_config.py | 43 +++++++++++++++++++++++++++++++++++++ 1 file changed, 43 insertions(+) diff --git a/frigate/test/test_config.py b/frigate/test/test_config.py index 4bafe7369..afe577f2f 100644 --- a/frigate/test/test_config.py +++ b/frigate/test/test_config.py @@ -632,6 +632,49 @@ class TestConfig(unittest.TestCase): ) assert frigate_config.cameras["back"].zones["test"].color != (0, 0, 0) + def test_zone_filter_area_percent_converts_to_pixels(self): + config = { + "mqtt": {"host": "mqtt"}, + "record": { + "alerts": { + "retain": { + "days": 20, + } + } + }, + "cameras": { + "back": { + "ffmpeg": { + "inputs": [ + {"path": "rtsp://10.0.0.1:554/video", "roles": ["detect"]} + ] + }, + "detect": { + "height": 1080, + "width": 1920, + "fps": 5, + }, + "zones": { + "notification": { + "coordinates": "0.03,1,0.025,0,0.626,0,0.643,1", + "objects": ["person"], + "filters": {"person": {"min_area": 0.1}}, + } + }, + } + }, + } + + frigate_config = FrigateConfig(**config) + expected_min_area = int(1080 * 1920 * 0.1) + assert ( + frigate_config.cameras["back"] + .zones["notification"] + .filters["person"] + .min_area + == expected_min_area + ) + def test_zone_relative_matches_explicit(self): config = { "mqtt": {"host": "mqtt"}, From bfe46d9f4af501c240a38c42743a52930e3c073f Mon Sep 17 00:00:00 2001 From: Josh Hawkins <32435876+hawkeye217@users.noreply.github.com> Date: Sat, 17 Jan 2026 16:18:43 -0600 Subject: [PATCH 9/9] use export id for key instead of name --- web/src/pages/Exports.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/web/src/pages/Exports.tsx b/web/src/pages/Exports.tsx index 69e9dc0e7..26a75801a 100644 --- a/web/src/pages/Exports.tsx +++ b/web/src/pages/Exports.tsx @@ -206,7 +206,7 @@ function Exports() { > {Object.values(exports).map((item) => (