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"