diff --git a/web/src/views/settings/FrigatePlusSettingsView.tsx b/web/src/views/settings/FrigatePlusSettingsView.tsx index 7bc81fae22..806bc61839 100644 --- a/web/src/views/settings/FrigatePlusSettingsView.tsx +++ b/web/src/views/settings/FrigatePlusSettingsView.tsx @@ -1,234 +1,28 @@ -import Heading from "@/components/ui/heading"; -import { useCallback, useContext, useEffect, useMemo, useState } from "react"; +import { useEffect } from "react"; +import { Trans, useTranslation } from "react-i18next"; +import { Link, useNavigate } from "react-router-dom"; +import useSWR from "swr"; +import { CheckCircle2, XCircle } from "lucide-react"; +import { LuExternalLink } from "react-icons/lu"; import { Toaster } from "@/components/ui/sonner"; import ActivityIndicator from "@/components/indicators/activity-indicator"; -import { toast } from "sonner"; -import useSWR from "swr"; -import axios from "axios"; -import { FrigateConfig } from "@/types/frigateConfig"; -import { CheckCircle2, XCircle } from "lucide-react"; -import { Trans, useTranslation } from "react-i18next"; import { Button } from "@/components/ui/button"; -import { Link } from "react-router-dom"; -import { LuExternalLink, LuFilter } from "react-icons/lu"; -import { StatusBarMessagesContext } from "@/context/statusbar-provider"; -import { - Select, - SelectContent, - SelectGroup, - SelectItem, - SelectTrigger, -} from "@/components/ui/select"; -import { - Popover, - PopoverContent, - PopoverTrigger, -} from "@/components/ui/popover"; -import { Switch } from "@/components/ui/switch"; -import { Label } from "@/components/ui/label"; -import { cn } from "@/lib/utils"; -import { useDocDomain } from "@/hooks/use-doc-domain"; -import { CameraNameLabel } from "@/components/camera/FriendlyNameLabel"; +import Heading from "@/components/ui/heading"; import { SettingsGroupCard, SplitCardRow, } from "@/components/card/SettingsGroupCard"; import FrigatePlusCurrentModelSummary from "@/views/settings/components/FrigatePlusCurrentModelSummary"; -import { useRestart } from "@/api/ws"; -import RestartDialog from "@/components/overlay/dialog/RestartDialog"; +import { useDocDomain } from "@/hooks/use-doc-domain"; +import { CameraNameLabel } from "@/components/camera/FriendlyNameLabel"; +import { FrigateConfig } from "@/types/frigateConfig"; +import type { SettingsPageProps } from "@/views/settings/SingleSectionPage"; -type FrigatePlusModel = { - id: string; - type: string; - name: string; - isBaseModel: boolean; - supportedDetectors: string[]; - trainDate: string; - baseModel: string; - width: number; - height: number; -}; - -type FrigatePlusSettings = { - model: { - id?: string; - }; -}; - -type FrigateSettingsViewProps = { - setUnsavedChanges: React.Dispatch>; -}; - -export default function FrigatePlusSettingsView({ - setUnsavedChanges, -}: FrigateSettingsViewProps) { +export default function FrigatePlusSettingsView(_props: SettingsPageProps) { const { t } = useTranslation("views/settings"); const { getLocaleDocUrl } = useDocDomain(); - const { data: config, mutate: updateConfig } = - useSWR("config"); - const [changedValue, setChangedValue] = useState(false); - const [isLoading, setIsLoading] = useState(false); - const [restartDialogOpen, setRestartDialogOpen] = useState(false); - const { send: sendRestart } = useRestart(); - - const { addMessage, removeMessage } = useContext(StatusBarMessagesContext)!; - - const [frigatePlusSettings, setFrigatePlusSettings] = - useState({ - model: { - id: undefined, - }, - }); - - const [origPlusSettings, setOrigPlusSettings] = useState( - { - model: { - id: undefined, - }, - }, - ); - - const { data: availableModels = {}, isLoading: isLoadingModels } = useSWR< - Record - >("/plus/models", { - 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 [showBaseModels, setShowBaseModels] = useState(true); - const [showFineTunedModels, setShowFineTunedModels] = useState(true); - - const filteredModelEntries = useMemo( - () => - Object.entries(availableModels || {}).filter(([, model]) => - model.isBaseModel ? showBaseModels : showFineTunedModels, - ), - [availableModels, showBaseModels, showFineTunedModels], - ); - - const isFilterActive = !showBaseModels || !showFineTunedModels; - - useEffect(() => { - if (config) { - if (frigatePlusSettings?.model.id == undefined) { - setFrigatePlusSettings({ - model: { - id: config.model.plus?.id, - }, - }); - } - - setOrigPlusSettings({ - model: { - id: config.model.plus?.id, - }, - }); - } - // we know that these deps are correct - // eslint-disable-next-line react-hooks/exhaustive-deps - }, [config]); - - const handleFrigatePlusConfigChange = ( - newConfig: Partial, - ) => { - setFrigatePlusSettings((prevConfig) => ({ - model: { - ...prevConfig.model, - ...newConfig.model, - }, - })); - setUnsavedChanges(true); - setChangedValue(true); - }; - - const saveToConfig = useCallback(async () => { - setIsLoading(true); - - try { - // Clear the existing model section so only the new path remains - await axios.put("config/set", { - requires_restart: 0, - config_data: { model: null }, - }); - const res = await axios.put("config/set", { - requires_restart: 0, - config_data: { - model: { path: `plus://${frigatePlusSettings.model.id}` }, - }, - }); - - if (res.status === 200) { - toast.success(t("frigatePlus.toast.success"), { - position: "top-center", - action: ( - setRestartDialogOpen(true)}> - - - ), - }); - setChangedValue(false); - updateConfig(); - } else { - toast.error( - t("frigatePlus.toast.error", { errorMessage: res.statusText }), - { - position: "top-center", - }, - ); - } - } catch (error) { - const err = error as { - response?: { data?: { message?: string; detail?: string } }; - }; - const errorMessage = - err.response?.data?.message || - err.response?.data?.detail || - "Unknown error"; - toast.error(t("toast.save.error.title", { errorMessage, ns: "common" }), { - position: "top-center", - }); - } finally { - addMessage( - "plus_restart", - t("frigatePlus.restart_required"), - undefined, - "plus_restart", - ); - setIsLoading(false); - } - }, [updateConfig, addMessage, frigatePlusSettings, t]); - - const onCancel = useCallback(() => { - setFrigatePlusSettings(origPlusSettings); - setChangedValue(false); - removeMessage("plus_settings", "plus_settings"); - }, [origPlusSettings, removeMessage]); - - useEffect(() => { - if (changedValue) { - addMessage( - "plus_settings", - t("frigatePlus.unsavedChanges"), - undefined, - "plus_settings", - ); - } else { - removeMessage("plus_settings", "plus_settings"); - } - // we know that these deps are correct - // eslint-disable-next-line react-hooks/exhaustive-deps - }, [changedValue]); + const { data: config } = useSWR("config"); + const navigate = useNavigate(); useEffect(() => { document.title = t("documentTitle.frigatePlus"); @@ -246,7 +40,6 @@ export default function FrigatePlusSettingsView({ {t("frigatePlus.title")} -

{t("frigatePlus.description")}

@@ -292,170 +85,20 @@ export default function FrigatePlusSettingsView({ {config?.plus?.enabled && ( - - )} - - {config?.plus?.enabled && ( - - - frigatePlus.modelInfo.modelSelect - - } - content={ -
- - - - - - -
-
- {t("frigatePlus.modelInfo.filter.ariaLabel")} -
-
- - -
-
- - -
-
-
-
-
- } - /> -
+ + navigate("/settings?page=systemDetectorsAndModel") + } + > + {t("frigatePlus.changeInDetectorsAndModel")} + + } + /> )} @@ -524,55 +167,6 @@ export default function FrigatePlusSettingsView({ - -
-
- {changedValue && ( -
- - {t("unsavedChanges")} - -
- )} -
- {changedValue && ( - - )} - -
-
-
- setRestartDialogOpen(false)} - onRestart={() => sendRestart("restart")} - /> ); } diff --git a/web/src/views/settings/SystemDetectionModelSettingsView.tsx b/web/src/views/settings/SystemDetectionModelSettingsView.tsx deleted file mode 100644 index 7038378a20..0000000000 --- a/web/src/views/settings/SystemDetectionModelSettingsView.tsx +++ /dev/null @@ -1,88 +0,0 @@ -import ActivityIndicator from "@/components/indicators/activity-indicator"; -import Heading from "@/components/ui/heading"; -import { Button } from "@/components/ui/button"; -import { useState } from "react"; -import { useNavigate } from "react-router-dom"; -import useSWR from "swr"; -import type { FrigateConfig } from "@/types/frigateConfig"; -import { - SettingsGroupCard, - SplitCardRow, -} from "@/components/card/SettingsGroupCard"; -import { - SingleSectionPage, - type SettingsPageProps, -} from "@/views/settings/SingleSectionPage"; -import FrigatePlusCurrentModelSummary from "@/views/settings/components/FrigatePlusCurrentModelSummary"; -import { useTranslation } from "react-i18next"; - -export default function SystemDetectionModelSettingsView( - props: SettingsPageProps, -) { - const { t } = useTranslation(["config/global", "views/settings"]); - const { data: config } = useSWR("config"); - const [showModelForm, setShowModelForm] = useState(false); - const navigate = useNavigate(); - - if (!config) { - return ; - } - - const isPlusModelActive = Boolean(config?.model?.plus?.id); - - if (!isPlusModelActive || showModelForm) { - return ; - } - - return ( -
-
-
- {t("model.label", { ns: "config/global" })} -
- {t("model.description", { ns: "config/global" })} -
-
-
- -
- - - - -
- } - /> - - - -
- - ); -}