import { baseUrl } from "@/api/baseUrl"; import ClassificationModelWizardDialog from "@/components/classification/ClassificationModelWizardDialog"; import ActivityIndicator from "@/components/indicators/activity-indicator"; import { ImageShadowOverlay } from "@/components/overlay/ImageShadowOverlay"; import { Button, buttonVariants } from "@/components/ui/button"; import { ToggleGroup, ToggleGroupItem } from "@/components/ui/toggle-group"; import useOptimisticState from "@/hooks/use-optimistic-state"; import { cn } from "@/lib/utils"; import { CustomClassificationModelConfig, FrigateConfig, } from "@/types/frigateConfig"; import { useCallback, useEffect, useMemo, useState } from "react"; import { useTranslation } from "react-i18next"; import { FaFolderPlus } from "react-icons/fa"; import { MdModelTraining } from "react-icons/md"; import { LuTrash2 } from "react-icons/lu"; import { FiMoreVertical } from "react-icons/fi"; import useSWR from "swr"; import Heading from "@/components/ui/heading"; import { useOverlayState } from "@/hooks/use-overlay-state"; import axios from "axios"; import { toast } from "sonner"; import { DropdownMenu, DropdownMenuContent, DropdownMenuItem, DropdownMenuTrigger, } from "@/components/ui/dropdown-menu"; import { AlertDialog, AlertDialogAction, AlertDialogCancel, AlertDialogContent, AlertDialogDescription, AlertDialogFooter, AlertDialogHeader, AlertDialogTitle, } from "@/components/ui/alert-dialog"; import BlurredIconButton from "@/components/button/BlurredIconButton"; const allModelTypes = ["objects", "states"] as const; type ModelType = (typeof allModelTypes)[number]; type ModelSelectionViewProps = { onClick: (model: CustomClassificationModelConfig) => void; }; export default function ModelSelectionView({ onClick, }: ModelSelectionViewProps) { const { t } = useTranslation(["views/classificationModel"]); const [page, setPage] = useOverlayState("objects", "objects"); const [pageToggle, setPageToggle] = useOptimisticState( page || "objects", setPage, 100, ); const { data: config, mutate: refreshConfig } = useSWR( "config", { revalidateOnFocus: false, }, ); // title useEffect(() => { document.title = t("documentTitle"); }, [t]); // data const classificationConfigs = useMemo(() => { if (!config) { return []; } return Object.values(config.classification.custom); }, [config]); const selectedClassificationConfigs = useMemo(() => { return classificationConfigs.filter((model) => { if (pageToggle == "objects" && model.object_config != undefined) { return true; } if (pageToggle == "states" && model.state_config != undefined) { return true; } return false; }); }, [classificationConfigs, pageToggle]); // new model wizard const [newModel, setNewModel] = useState(false); if (!config) { return ; } return (
{ setNewModel(false); refreshConfig(); }} />
{ if (value) { setPageToggle(value); } }} > {allModelTypes.map((item) => (
{t("menu." + item)}
))}
{selectedClassificationConfigs.length === 0 ? ( setNewModel(true)} modelType={pageToggle} /> ) : (
{selectedClassificationConfigs.map((config) => ( onClick(config)} onDelete={() => refreshConfig()} /> ))}
)}
); } function NoModelsView({ onCreateModel, modelType, }: { onCreateModel: () => void; modelType: ModelType; }) { const { t } = useTranslation(["views/classificationModel"]); const typeKey = modelType === "objects" ? "object" : "state"; return (
{t(`noModels.${typeKey}.title`)}
{t(`noModels.${typeKey}.description`)}
); } type ModelCardProps = { config: CustomClassificationModelConfig; onClick: () => void; onDelete: () => void; }; function ModelCard({ config, onClick, onDelete }: ModelCardProps) { const { t } = useTranslation(["views/classificationModel"]); const { data: dataset } = useSWR<{ [id: string]: string[]; }>(`classification/${config.name}/dataset`, { revalidateOnFocus: false }); const [deleteDialogOpen, setDeleteDialogOpen] = useState(false); const handleDelete = useCallback(async () => { try { await axios.delete(`classification/${config.name}`); await axios.put("/config/set", { requires_restart: 0, update_topic: `config/classification/custom/${config.name}`, config_data: { classification: { custom: { [config.name]: "", }, }, }, }); toast.success(t("toast.success.deletedModel", { count: 1 }), { position: "top-center", }); onDelete(); } catch (err) { const error = err as { response?: { data?: { message?: string; detail?: string } }; }; const errorMessage = error.response?.data?.message || error.response?.data?.detail || "Unknown error"; toast.error(t("toast.error.deleteModelFailed", { errorMessage }), { position: "top-center", }); } }, [config, onDelete, t]); const handleDeleteClick = useCallback((e: React.MouseEvent) => { e.stopPropagation(); setDeleteDialogOpen(true); }, []); const coverImage = useMemo(() => { if (!dataset) { return undefined; } const keys = Object.keys(dataset).filter((key) => key != "none"); const selectedKey = keys[0]; if (!dataset[selectedKey]) { return undefined; } return { name: selectedKey, img: dataset[selectedKey][0], }; }, [dataset]); return ( <> setDeleteDialogOpen(!deleteDialogOpen)} > {t("deleteModel.title")} {t("deleteModel.single", { name: config.name })} {t("button.cancel", { ns: "common" })} {t("button.delete", { ns: "common" })}
{config.name}
e.stopPropagation()}> e.stopPropagation()} > {t("button.delete", { ns: "common" })}
); }