diff --git a/web/src/pages/FaceLibrary.tsx b/web/src/pages/FaceLibrary.tsx
index 167dd9325..cbcb7c7de 100644
--- a/web/src/pages/FaceLibrary.tsx
+++ b/web/src/pages/FaceLibrary.tsx
@@ -416,6 +416,86 @@ export default function FaceLibrary() {
>
)}
+ {pageToggle === "train" && (
+ {
+ // Batch train all selected faces
+ let successCount = 0;
+ let failCount = 0;
+ const totalCount = selectedFaces.length;
+
+ selectedFaces.forEach((filename, index) => {
+ axios
+ .post(`/faces/train/${name}/classify`, {
+ training_file: filename,
+ })
+ .then((resp) => {
+ if (resp.status == 200) {
+ successCount++;
+ } else {
+ failCount++;
+ }
+
+ // Show final toast after all requests complete
+ if (index === totalCount - 1) {
+ if (successCount === totalCount) {
+ toast.success(
+ t("toast.success.batchTrainedFaces", {
+ count: successCount,
+ }),
+ {
+ position: "top-center",
+ },
+ );
+ } else if (successCount > 0) {
+ toast.warning(
+ t("toast.warning.partialBatchTrained", {
+ success: successCount,
+ total: totalCount,
+ }),
+ {
+ position: "top-center",
+ },
+ );
+ } else {
+ toast.error(
+ t("toast.error.batchTrainFailed", {
+ count: totalCount,
+ }),
+ {
+ position: "top-center",
+ },
+ );
+ }
+ setSelectedFaces([]);
+ refreshFaces();
+ }
+ })
+ .catch(() => {
+ failCount++;
+ if (index === totalCount - 1) {
+ toast.error(
+ t("toast.error.batchTrainFailed", {
+ count: totalCount,
+ }),
+ {
+ position: "top-center",
+ },
+ );
+ setSelectedFaces([]);
+ refreshFaces();
+ }
+ });
+ });
+ }}
+ >
+
+
+ )}