From e7f047e91595dbed0c25b42433ec4b0ea23de7c8 Mon Sep 17 00:00:00 2001 From: Josh Hawkins <32435876+hawkeye217@users.noreply.github.com> Date: Sat, 16 May 2026 08:20:22 -0500 Subject: [PATCH] add Frigate+ model selector with filter popover to merged page --- .../DetectorsAndModelSettingsView.tsx | 239 +++++++++++++++++- 1 file changed, 234 insertions(+), 5 deletions(-) diff --git a/web/src/views/settings/DetectorsAndModelSettingsView.tsx b/web/src/views/settings/DetectorsAndModelSettingsView.tsx index cb4de4a2b3..da93371c3c 100644 --- a/web/src/views/settings/DetectorsAndModelSettingsView.tsx +++ b/web/src/views/settings/DetectorsAndModelSettingsView.tsx @@ -1,7 +1,8 @@ import { useCallback, useContext, useEffect, useMemo, useState } from "react"; import { useTranslation } from "react-i18next"; import { Link } from "react-router-dom"; -import { LuExternalLink } from "react-icons/lu"; +import { LuExternalLink, LuFilter } from "react-icons/lu"; +import axios from "axios"; import useSWR from "swr"; import { cn } from "@/lib/utils"; import { useDocDomain } from "@/hooks/use-doc-domain"; @@ -11,6 +12,20 @@ import Heading from "@/components/ui/heading"; import { Badge } from "@/components/ui/badge"; import { Button } from "@/components/ui/button"; import { Toaster } from "@/components/ui/sonner"; +import { Label } from "@/components/ui/label"; +import { Switch } from "@/components/ui/switch"; +import { + Popover, + PopoverContent, + PopoverTrigger, +} from "@/components/ui/popover"; +import { + Select, + SelectContent, + SelectGroup, + SelectItem, + SelectTrigger, +} from "@/components/ui/select"; import type { FrigateConfig } from "@/types/frigateConfig"; import type { SectionStatus, @@ -30,6 +45,18 @@ type PageState = { customModel: ConfigSectionData; }; +type FrigatePlusModel = { + id: string; + type: string; + name: string; + isBaseModel: boolean; + supportedDetectors: string[]; + trainDate: string; + baseModel: string; + width: number; + height: number; +}; + const STATUS_BAR_KEY = "detectors_and_model"; const deriveInitialState = (config: FrigateConfig): PageState => { @@ -45,6 +72,10 @@ const deriveInitialState = (config: FrigateConfig): PageState => { } else { modelTab = "custom"; } + // Fallback: if Plus is not enabled, prefer Custom regardless of saved path + if (!plusEnabled && modelTab === "plus") { + modelTab = "custom"; + } const plusModelId = config.model?.plus?.id; const { plus: _plus, ...modelWithoutPlus } = (config.model ?? {}) as Record< @@ -85,6 +116,53 @@ export default function DetectorsAndModelSettingsView( hasValidationErrors: false, }); + const [showBaseModels, setShowBaseModels] = useState(true); + const [showFineTunedModels, setShowFineTunedModels] = useState(true); + + const plusEnabled = Boolean(config?.plus?.enabled); + + const { data: availableModels = {}, isLoading: isLoadingModels } = useSWR< + Record + >(plusEnabled ? "/plus/models" : null, { + fallbackData: {}, + fetcher: async (url) => { + const res = await axios.get(url, { withCredentials: true }); + return res.data.reduce( + (obj: Record, model: FrigatePlusModel) => { + obj[model.id] = model; + return obj; + }, + {}, + ); + }, + }); + + const filteredModelEntries = useMemo( + () => + Object.entries(availableModels || {}).filter(([, model]) => + model.isBaseModel ? showBaseModels : showFineTunedModels, + ), + [availableModels, showBaseModels, showFineTunedModels], + ); + + const isFilterActive = !showBaseModels || !showFineTunedModels; + + const currentDetectorType = useMemo(() => { + if (!state) return undefined; + const values = Object.values(state.detectors ?? {}); + if (values.length === 0) return undefined; + const first = values[0] as { type?: string } | undefined; + return first?.type; + }, [state]); + + const isModelCompatible = useCallback( + (model: FrigatePlusModel) => + currentDetectorType + ? model.supportedDetectors.includes(currentDetectorType) + : true, + [currentDetectorType], + ); + const handleChildPendingChange = useCallback( ( sectionKey: string, @@ -238,7 +316,7 @@ export default function DetectorsAndModelSettingsView( } > - + {t("detectorsAndModel.tabs.plus")} @@ -247,9 +325,160 @@ export default function DetectorsAndModelSettingsView( -
- Frigate+ model selector — added in Task 7. -
+ {!plusEnabled ? ( +

+ {t("detectorsAndModel.plusModel.plusDisabled")} +

+ ) : ( +
+ + + + + + +
+
+ {t("frigatePlus.modelInfo.filter.ariaLabel")} +
+
+ + +
+
+ + +
+
+
+
+
+ )}