From 73e0d1b0ebbdaa849f1c24a5bf9b659dba850f41 Mon Sep 17 00:00:00 2001 From: Josh Hawkins <32435876+hawkeye217@users.noreply.github.com> Date: Sat, 16 May 2026 12:17:46 -0500 Subject: [PATCH] Rename to Detectors and model, float Modified badge, use ConfigMessageBanner for mismatch --- .../settings/detectors-and-model.spec.ts | 10 +-- web/public/locales/en/views/settings.json | 10 +-- .../config-form/ConfigMessageBanner.tsx | 2 +- .../config-form/section-configs/types.ts | 2 + .../DetectorsAndModelSettingsView.tsx | 82 +++++++++---------- 5 files changed, 54 insertions(+), 52 deletions(-) diff --git a/web/e2e/specs/settings/detectors-and-model.spec.ts b/web/e2e/specs/settings/detectors-and-model.spec.ts index 4437aa0784..f697b2b2d6 100644 --- a/web/e2e/specs/settings/detectors-and-model.spec.ts +++ b/web/e2e/specs/settings/detectors-and-model.spec.ts @@ -1,19 +1,19 @@ /** - * Detectors & Model settings page tests -- HIGH tier. + * Detectors and model settings page tests -- HIGH tier. * * Tests rendering of the merged page and navigation from the Frigate+ page. */ import { test, expect } from "../../fixtures/frigate-test"; -test.describe("Detectors & Model Settings @high", () => { +test.describe("Detectors and model Settings @high", () => { test("page renders with detector and model cards", async ({ frigateApp }) => { await frigateApp.goto("/settings?page=systemDetectorsAndModel"); await frigateApp.page.waitForTimeout(2000); await expect(frigateApp.page.locator("#pageRoot")).toBeVisible(); const text = await frigateApp.page.textContent("#pageRoot"); - expect(text).toContain("Detectors & Model"); + expect(text).toContain("Detectors and model"); expect(text?.toLowerCase()).toContain("detector hardware"); expect(text?.toLowerCase()).toContain("detection model"); }); @@ -23,7 +23,7 @@ test.describe("Detectors & Model Settings @high", () => { await frigateApp.page.waitForTimeout(2000); const button = frigateApp.page.getByRole("button", { - name: /Change in Detectors & Model/, + name: /Change in Detectors and model/, }); // Button only appears when Frigate+ is enabled in the test config; skip @@ -32,7 +32,7 @@ test.describe("Detectors & Model Settings @high", () => { await button.first().click(); await frigateApp.page.waitForURL(/page=systemDetectorsAndModel/); await expect(frigateApp.page.locator("#pageRoot")).toContainText( - "Detectors & Model", + "Detectors and model", ); } else { test.skip( diff --git a/web/public/locales/en/views/settings.json b/web/public/locales/en/views/settings.json index d3de53b76e..fda1c27acb 100644 --- a/web/public/locales/en/views/settings.json +++ b/web/public/locales/en/views/settings.json @@ -12,7 +12,7 @@ "globalConfig": "Global Configuration - Frigate", "cameraConfig": "Camera Configuration - Frigate", "frigatePlus": "Frigate+ Settings - Frigate", - "detectorsAndModel": "Detectors & Model - Frigate", + "detectorsAndModel": "Detectors and model - Frigate", "notifications": "Notification Settings - Frigate", "maintenance": "Maintenance - Frigate", "profiles": "Profiles - Frigate" @@ -70,7 +70,7 @@ "systemTelemetry": "Telemetry", "systemBirdseye": "Birdseye", "systemFfmpeg": "FFmpeg", - "systemDetectorsAndModel": "Detectors & Model", + "systemDetectorsAndModel": "Detectors and model", "systemMqtt": "MQTT", "systemGo2rtcStreams": "go2rtc streams", "integrationSemanticSearch": "Semantic search", @@ -1146,7 +1146,7 @@ }, "modelSelect": "Your available models on Frigate+ can be selected here. Note that only models compatible with your current detector configuration can be selected." }, - "changeInDetectorsAndModel": "Change in Detectors & Model →", + "changeInDetectorsAndModel": "Change model", "unsavedChanges": "Unsaved Frigate+ settings changes", "restart_required": "Restart required (Frigate+ model changed)", "toast": { @@ -1155,7 +1155,7 @@ } }, "detectorsAndModel": { - "title": "Detectors & Model", + "title": "Detectors and model", "description": "Configure the detector backend that runs object detection and the model it uses. Changes are saved together so the detector and model stay in sync.", "cardTitles": { "detector": "Detector Hardware", @@ -1166,7 +1166,7 @@ "custom": "Custom Model" }, "mismatch": { - "warning": "The current Frigate+ model <0>{{model}} requires the <1>{{required}} detector. Pick a compatible model below or switch to Custom Model before saving." + "warning": "The current Frigate+ model \"{{model}}\" requires the {{required}} detector. Pick a compatible model below or switch to Custom Model before saving." }, "plusModel": { "requiresDetector": "Requires: {{detector}}", diff --git a/web/src/components/config-form/ConfigMessageBanner.tsx b/web/src/components/config-form/ConfigMessageBanner.tsx index f5b8280003..919d337c15 100644 --- a/web/src/components/config-form/ConfigMessageBanner.tsx +++ b/web/src/components/config-form/ConfigMessageBanner.tsx @@ -44,7 +44,7 @@ export function ConfigMessageBanner({ messages }: ConfigMessageBannerProps) { className="flex items-center [&>svg+div]:translate-y-0 [&>svg]:static [&>svg~*]:pl-2" > - {t(msg.messageKey)} + {t(msg.messageKey, msg.values)} ))} diff --git a/web/src/components/config-form/section-configs/types.ts b/web/src/components/config-form/section-configs/types.ts index 9efeb2b32f..e3a89ba264 100644 --- a/web/src/components/config-form/section-configs/types.ts +++ b/web/src/components/config-form/section-configs/types.ts @@ -24,6 +24,8 @@ export type ConditionalMessage = { severity: MessageSeverity; /** Function returning true when the message should be shown */ condition: (ctx: MessageConditionContext) => boolean; + /** Optional interpolation values passed to t() for {{var}} substitution */ + values?: Record; }; /** Field-level conditional message, adds field targeting */ diff --git a/web/src/views/settings/DetectorsAndModelSettingsView.tsx b/web/src/views/settings/DetectorsAndModelSettingsView.tsx index b740f19945..c1aac103b5 100644 --- a/web/src/views/settings/DetectorsAndModelSettingsView.tsx +++ b/web/src/views/settings/DetectorsAndModelSettingsView.tsx @@ -1,5 +1,5 @@ import { useCallback, useContext, useEffect, useMemo, useState } from "react"; -import { Trans, useTranslation } from "react-i18next"; +import { useTranslation } from "react-i18next"; import { Link } from "react-router-dom"; import { LuExternalLink, LuFilter } from "react-icons/lu"; import { toast } from "sonner"; @@ -39,6 +39,7 @@ import type { import type { ConfigSectionData } from "@/types/configForm"; import { SettingsGroupCard } from "@/components/card/SettingsGroupCard"; import { ConfigSectionTemplate } from "@/components/config-form/sections"; +import { ConfigMessageBanner } from "@/components/config-form/ConfigMessageBanner"; import { Tabs, TabsContent, TabsList, TabsTrigger } from "@/components/ui/tabs"; type ModelTab = "plus" | "custom"; @@ -358,35 +359,34 @@ export default function DetectorsAndModelSettingsView({ return (
-
-
-
- {t("detectorsAndModel.title")} -
- {t("detectorsAndModel.description")} -
-
- - {t("readTheDocumentation", { ns: "common" })} - - -
+
+
+ {t("detectorsAndModel.title")} +
+ {t("detectorsAndModel.description")}
- {isDirty && ( - + - {t("button.modified", { ns: "common", defaultValue: "Modified" })} - - )} + {t("readTheDocumentation", { ns: "common" })} + + +
- + {isDirty && ( + + {t("button.modified", { ns: "common", defaultValue: "Modified" })} + + )} +
+
{plusMismatch && selectedPlusModel && ( -
- , - 1: , - }} - /> -
+ true, + values: { + model: selectedPlusModel.name, + required: selectedPlusModel.supportedDetectors.join(", "), + }, + }, + ]} + /> )}