From d776777a9f1a4c8c6f712675f88559ab3f7e6dc0 Mon Sep 17 00:00:00 2001 From: Josh Hawkins <32435876+hawkeye217@users.noreply.github.com> Date: Sat, 16 May 2026 07:24:37 -0500 Subject: [PATCH] add detection model card with tabs and custom model embed --- .../DetectorsAndModelSettingsView.tsx | 83 +++++++++++++++++-- 1 file changed, 74 insertions(+), 9 deletions(-) diff --git a/web/src/views/settings/DetectorsAndModelSettingsView.tsx b/web/src/views/settings/DetectorsAndModelSettingsView.tsx index 82bc0da1bc..cb4de4a2b3 100644 --- a/web/src/views/settings/DetectorsAndModelSettingsView.tsx +++ b/web/src/views/settings/DetectorsAndModelSettingsView.tsx @@ -12,11 +12,14 @@ import { Badge } from "@/components/ui/badge"; import { Button } from "@/components/ui/button"; import { Toaster } from "@/components/ui/sonner"; import type { FrigateConfig } from "@/types/frigateConfig"; -import type { SettingsPageProps } from "@/views/settings/SingleSectionPage"; -import type { SectionStatus } from "@/views/settings/SingleSectionPage"; +import type { + SectionStatus, + SettingsPageProps, +} from "@/views/settings/SingleSectionPage"; import type { ConfigSectionData } from "@/types/configForm"; import { SettingsGroupCard } from "@/components/card/SettingsGroupCard"; import { ConfigSectionTemplate } from "@/components/config-form/sections"; +import { Tabs, TabsContent, TabsList, TabsTrigger } from "@/components/ui/tabs"; type ModelTab = "plus" | "custom"; @@ -76,6 +79,11 @@ export default function DetectorsAndModelSettingsView( isOverridden: false, hasValidationErrors: false, }); + const [modelStatus, setModelStatus] = useState({ + hasChanges: false, + isOverridden: false, + hasValidationErrors: false, + }); const handleChildPendingChange = useCallback( ( @@ -95,9 +103,19 @@ export default function DetectorsAndModelSettingsView( [], ); + const handleDetectorStatusChange = useCallback( + (status: SectionStatus) => setDetectorStatus(status), + [], + ); + + const handleModelStatusChange = useCallback( + (status: SectionStatus) => setModelStatus(status), + [], + ); + useEffect(() => { const detectorsPending = childPending["detectors"]; - if (detectorsPending && state) { + if (detectorsPending) { setState((prev) => prev ? { ...prev, detectors: detectorsPending } : prev, ); @@ -105,6 +123,16 @@ export default function DetectorsAndModelSettingsView( // eslint-disable-next-line react-hooks/exhaustive-deps }, [childPending["detectors"]]); + useEffect(() => { + const modelPending = childPending["model"]; + if (modelPending) { + setState((prev) => + prev ? { ...prev, customModel: modelPending } : prev, + ); + } + // eslint-disable-next-line react-hooks/exhaustive-deps + }, [childPending["model"]]); + useEffect(() => { if (!config || snapshot !== null) return; const initial = deriveInitialState(config); @@ -150,7 +178,10 @@ export default function DetectorsAndModelSettingsView( } const saveDisabled = - !isDirty || isSaving || detectorStatus.hasValidationErrors; + !isDirty || + isSaving || + detectorStatus.hasValidationErrors || + (state.modelTab === "custom" && modelStatus.hasValidationErrors); return (
@@ -194,13 +225,47 @@ export default function DetectorsAndModelSettingsView( embedded pendingDataBySection={childPending} onPendingDataChange={handleChildPendingChange} - onStatusChange={(status) => setDetectorStatus(status)} + onStatusChange={handleDetectorStatusChange} /> -
- {t("detectorsAndModel.cardTitles.model")} — placeholder, filled in - Tasks 6–8. -
+ + + setState((prev) => + prev ? { ...prev, modelTab: value as ModelTab } : prev, + ) + } + > + + + {t("detectorsAndModel.tabs.plus")} + + + {t("detectorsAndModel.tabs.custom")} + + + + +
+ Frigate+ model selector — added in Task 7. +
+
+ + + + +
+