diff --git a/frigate/types.py b/frigate/types.py
index a9e27ba90..f342d27cd 100644
--- a/frigate/types.py
+++ b/frigate/types.py
@@ -23,6 +23,7 @@ class ModelStatusTypesEnum(str, Enum):
error = "error"
training = "training"
complete = "complete"
+ failed = "failed"
class TrackedObjectUpdateTypesEnum(str, Enum):
diff --git a/frigate/util/classification.py b/frigate/util/classification.py
index fe8e6069c..a74094c32 100644
--- a/frigate/util/classification.py
+++ b/frigate/util/classification.py
@@ -306,13 +306,13 @@ def kickoff_model_training(
logger.error(
f"Training subprocess failed for {model_name} (exit code: {training_process.exitcode})"
)
- # mark training as complete (not failed) so UI doesn't stay in training state
- # but don't reload the model since it failed
+ # mark training as failed so UI shows error state
+ # don't reload the model since it failed
requestor.send_data(
UPDATE_MODEL_STATE,
{
"model": model_name,
- "state": ModelStatusTypesEnum.complete,
+ "state": ModelStatusTypesEnum.failed,
},
)
diff --git a/web/public/locales/en/views/classificationModel.json b/web/public/locales/en/views/classificationModel.json
index 3a68b82f1..2bae0c0ce 100644
--- a/web/public/locales/en/views/classificationModel.json
+++ b/web/public/locales/en/views/classificationModel.json
@@ -35,7 +35,8 @@
"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}}",
+ "trainingFailed": "Model training failed. Check Frigate logs for details.",
+ "trainingFailedToStart": "Failed to start model training: {{errorMessage}}",
"updateModelFailed": "Failed to update model: {{errorMessage}}",
"renameCategoryFailed": "Failed to rename class: {{errorMessage}}"
}
diff --git a/web/src/types/ws.ts b/web/src/types/ws.ts
index 1120aec67..1d98b7b01 100644
--- a/web/src/types/ws.ts
+++ b/web/src/types/ws.ts
@@ -87,7 +87,8 @@ export type ModelState =
| "downloaded"
| "error"
| "training"
- | "complete";
+ | "complete"
+ | "failed";
export type EmbeddingsReindexProgressType = {
thumbnails: number;
diff --git a/web/src/views/classification/ModelTrainingView.tsx b/web/src/views/classification/ModelTrainingView.tsx
index 35b5584d7..6a3e680f9 100644
--- a/web/src/views/classification/ModelTrainingView.tsx
+++ b/web/src/views/classification/ModelTrainingView.tsx
@@ -103,6 +103,11 @@ export default function ModelTrainingView({ model }: ModelTrainingViewProps) {
});
setWasTraining(false);
refreshDataset();
+ } else if (modelState == "failed") {
+ toast.error(t("toast.error.trainingFailed"), {
+ position: "top-center",
+ });
+ setWasTraining(false);
}
// only refresh when modelState changes
// eslint-disable-next-line react-hooks/exhaustive-deps
@@ -188,7 +193,7 @@ export default function ModelTrainingView({ model }: ModelTrainingViewProps) {
error.response?.data?.detail ||
"Unknown error";
- toast.error(t("toast.error.trainingFailed", { errorMessage }), {
+ toast.error(t("toast.error.trainingFailedToStart", { errorMessage }), {
position: "top-center",
});
});
@@ -437,9 +442,9 @@ export default function ModelTrainingView({ model }: ModelTrainingViewProps) {
{((trainingMetadata?.new_images_count ?? 0) === 0 ||
- modelState != "complete") && (
+ (modelState != "complete" && modelState != "failed")) && (
{modelState == "training"