diff --git a/frigate/api/classification.py b/frigate/api/classification.py
index e9052097a..1b91afeea 100644
--- a/frigate/api/classification.py
+++ b/frigate/api/classification.py
@@ -804,3 +804,42 @@ async def generate_object_examples(request: Request, body: GenerateObjectExample
content={"success": True, "message": "Example generation completed"},
status_code=200,
)
+
+
+@router.delete(
+ "/classification/{name}",
+ response_model=GenericResponse,
+ dependencies=[Depends(require_role(["admin"]))],
+ summary="Delete a classification model",
+ description="""Deletes a specific classification model and all its associated data.
+ The name must exist in the classification models. Returns a success message or an error if the name is invalid.""",
+)
+def delete_classification_model(request: Request, name: str):
+ config: FrigateConfig = request.app.frigate_config
+
+ if name not in config.classification.custom:
+ return JSONResponse(
+ content=(
+ {
+ "success": False,
+ "message": f"{name} is not a known classification model.",
+ }
+ ),
+ status_code=404,
+ )
+
+ # Delete the classification model's data directory
+ model_dir = os.path.join(CLIPS_DIR, sanitize_filename(name))
+
+ if os.path.exists(model_dir):
+ shutil.rmtree(model_dir)
+
+ return JSONResponse(
+ content=(
+ {
+ "success": True,
+ "message": f"Successfully deleted classification model {name}.",
+ }
+ ),
+ status_code=200,
+ )
diff --git a/frigate/genai/openai.py b/frigate/genai/openai.py
index 2a21b8c2e..631cb3480 100644
--- a/frigate/genai/openai.py
+++ b/frigate/genai/openai.py
@@ -91,7 +91,7 @@ class OpenAIClient(GenAIClient):
# Default to 128K for ChatGPT models, 8K for others
model_name = self.genai_config.model.lower()
- if "gpt-4o" in model_name:
+ if "gpt" in model_name:
self.context_size = 128000
else:
self.context_size = 8192
diff --git a/web/public/locales/en/views/classificationModel.json b/web/public/locales/en/views/classificationModel.json
index fc49c2ff4..ff0fab291 100644
--- a/web/public/locales/en/views/classificationModel.json
+++ b/web/public/locales/en/views/classificationModel.json
@@ -5,12 +5,15 @@
"renameCategory": "Rename Class",
"deleteCategory": "Delete Class",
"deleteImages": "Delete Images",
- "trainModel": "Train Model"
+ "trainModel": "Train Model",
+ "addClassification": "Add Classification",
+ "deleteModels": "Delete Models"
},
"toast": {
"success": {
"deletedCategory": "Deleted Class",
"deletedImage": "Deleted Images",
+ "deletedModel": "Successfully deleted {{count}} model(s)",
"categorizedImage": "Successfully Classified Image",
"trainedModel": "Successfully trained model.",
"trainingModel": "Successfully started model training."
@@ -18,6 +21,7 @@
"error": {
"deleteImageFailed": "Failed to delete: {{errorMessage}}",
"deleteCategoryFailed": "Failed to delete class: {{errorMessage}}",
+ "deleteModelFailed": "Failed to delete model: {{errorMessage}}",
"categorizeFailed": "Failed to categorize image: {{errorMessage}}",
"trainingFailed": "Failed to start model training: {{errorMessage}}"
}
@@ -26,6 +30,11 @@
"title": "Delete Class",
"desc": "Are you sure you want to delete the class {{name}}? This will permanently delete all associated images and require re-training the model."
},
+ "deleteModel": {
+ "title": "Delete Classification Model",
+ "single": "Are you sure you want to delete {{name}}? This will permanently delete all associated data including images and training data. This action cannot be undone.",
+ "desc": "Are you sure you want to delete {{count}} model(s)? This will permanently delete all associated data including images and training data. This action cannot be undone."
+ },
"deleteDatasetImages": {
"title": "Delete Dataset Images",
"desc": "Are you sure you want to delete {{count}} images from {{dataset}}? This action cannot be undone and will require re-training the model."
@@ -52,6 +61,10 @@
},
"categorizeImageAs": "Classify Image As:",
"categorizeImage": "Classify Image",
+ "menu": {
+ "objects": "Objects",
+ "states": "States"
+ },
"noModels": {
"object": {
"title": "No Object Classification Models",
diff --git a/web/src/views/classification/ModelSelectionView.tsx b/web/src/views/classification/ModelSelectionView.tsx
index 275c3f5b8..95a221258 100644
--- a/web/src/views/classification/ModelSelectionView.tsx
+++ b/web/src/views/classification/ModelSelectionView.tsx
@@ -2,7 +2,7 @@ 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 } from "@/components/ui/button";
+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";
@@ -10,13 +10,35 @@ import {
CustomClassificationModelConfig,
FrigateConfig,
} from "@/types/frigateConfig";
-import { useEffect, useMemo, useState } from "react";
+import { useCallback, useEffect, useMemo, useRef, 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 useKeyboardListener from "@/hooks/use-keyboard-listener";
+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];
@@ -126,7 +148,7 @@ export default function ModelSelectionView({
onClick={() => setNewModel(true)}
>