import ActivityIndicator from "@/components/indicators/activity-indicator"; import type { FrigatePlusModel } from "@/components/config-form/theme/fields/ModelSourcePicker"; import { Button } from "@/components/ui/button"; import { RadioGroup, RadioGroupItem } from "@/components/ui/radio-group"; import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue, } from "@/components/ui/select"; import { useDocDomain } from "@/hooks/use-doc-domain"; import type { FrigateConfig } from "@/types/frigateConfig"; import type { DetectionHardware } from "@/types/hardware"; import { recommendedDetectorCount } from "@/utils/detectionHardware"; import axios from "axios"; import { useCallback, useMemo, useState } from "react"; import { useTranslation } from "react-i18next"; import { LuExternalLink } from "react-icons/lu"; import { toast } from "sonner"; import useSWR from "swr"; // detectors that ship no default model; configuring them without one leaves // the detector unable to start const MODEL_REQUIRED_DETECTORS = ["onnx", "tensorrt"]; const CPU_FALLBACK: DetectionHardware[] = [ { key: "cpu", detector: "cpu", name: "CPU", units: [{ device: "cpu", label: "CPU" }], count: 1, unlimited: true, }, ]; type SetupDetectorProps = { cameraCount: number; onNext: (hardwareKey: string) => void; onBack: () => void; onSkip: (hardwareKey?: string) => void; }; export default function SetupDetector({ cameraCount, onNext, onBack, onSkip, }: SetupDetectorProps) { const { t } = useTranslation(["views/setup", "common"]); const { getLocaleDocUrl } = useDocDomain(); const { data: hardware, isLoading, error: probeError, } = useSWR("hardware/probe", { revalidateOnFocus: false, }); const { data: config } = useSWR("config", { revalidateOnFocus: false, }); const plusEnabled = Boolean(config?.plus?.enabled); // the cpu is always probed, so an empty or failed probe leaves a fallback const options = useMemo( () => (hardware && hardware.length > 0 ? hardware : CPU_FALLBACK), [hardware], ); // the prober orders accelerators ahead of the cpu const recommendedKey = options[0].key; const [selectedKey, setSelectedKey] = useState(); const selected = options.find((entry) => entry.key === (selectedKey ?? recommendedKey)) ?? options[0]; const needsModel = MODEL_REQUIRED_DETECTORS.includes(selected.detector); const { data: plusModels } = useSWR( plusEnabled && needsModel ? "/plus/models" : null, { fetcher: async (url) => { const res = await axios.get(url, { withCredentials: true }); return res.data; }, }, ); const [plusModelId, setPlusModelId] = useState(""); const compatiblePlusModels = useMemo( () => (plusModels ?? []).filter((model) => model.supportedDetectors.includes(selected.detector), ), [plusModels, selected.detector], ); const [saving, setSaving] = useState(false); const buildDevices = useCallback( (entry: DetectionHardware): string[] => { const first = entry.units[0]?.device; if (!first) { return []; } if (!entry.unlimited) { return [first]; } // repeating a device runs an extra inference process on it const count = recommendedDetectorCount(Math.max(cameraCount, 1)); return Array.from({ length: count }, () => first); }, [cameraCount], ); const handleSave = useCallback(async () => { if (needsModel && !plusModelId) { // no model to run on this hardware yet, so leave the config untouched onSkip(selected.key); return; } setSaving(true); try { const model: Record = { devices: buildDevices(selected), }; if (needsModel) { model.path = `plus://${plusModelId}`; } await axios.put("config/set", { config_data: { models: [model], detect: { enabled: true }, }, requires_restart: 1, }); onNext(selected.key); } catch { toast.error(t("setupWizard.errors.saveFailed")); } finally { setSaving(false); } }, [needsModel, plusModelId, selected, buildDevices, onNext, onSkip, t]); if (isLoading) { return (

{t("setupWizard.detector.detecting")}

); } return (

{t("setupWizard.detector.title")}

{t("setupWizard.detector.description")}

{probeError && (

{t("setupWizard.detector.probeFailed")}

)} { setSelectedKey(value); setPlusModelId(""); }} > {options.map((entry) => (
))}
{needsModel && (

{t("setupWizard.detector.modelRequired", { name: selected.name })}

{plusEnabled ? ( ) : ( {t("readTheDocumentation", { ns: "common" })} )}
)}
); }