diff --git a/web/packages/base/components/mui/ActivityIndicator.tsx b/web/packages/base/components/mui/ActivityIndicator.tsx
index e7633d71f9..b52980012d 100644
--- a/web/packages/base/components/mui/ActivityIndicator.tsx
+++ b/web/packages/base/components/mui/ActivityIndicator.tsx
@@ -1,9 +1,11 @@
+import ErrorOutline from "@mui/icons-material/ErrorOutline";
import {
CircularProgress,
Stack,
Typography,
type CircularProgressProps,
} from "@mui/material";
+import { t } from "i18next";
import type React from "react";
/**
@@ -27,3 +29,21 @@ export const ActivityIndicator: React.FC<
) : (
);
+
+/**
+ * An error message indicator, styled to complement {@link ActivityIndicator}.
+ *
+ * If a child is provided, it is used as the error message to show (after being
+ * wrapped in a suitable {@link Typography}). Otherwise the default generic
+ * error message is shown.
+ */
+export const ErrorIndicator: React.FC = ({
+ children,
+}) => (
+
+
+
+ {children ?? t("generic_error")}
+
+
+);
diff --git a/web/packages/new/photos/components/gallery/PeopleHeader.tsx b/web/packages/new/photos/components/gallery/PeopleHeader.tsx
index 09ca85c1da..6c53e054e0 100644
--- a/web/packages/new/photos/components/gallery/PeopleHeader.tsx
+++ b/web/packages/new/photos/components/gallery/PeopleHeader.tsx
@@ -1,4 +1,7 @@
-import { ActivityIndicator } from "@/base/components/mui/ActivityIndicator";
+import {
+ ActivityIndicator,
+ ErrorIndicator,
+} from "@/base/components/mui/ActivityIndicator";
import { CenteredBox } from "@/base/components/mui/Container";
import { FocusVisibleButton } from "@/base/components/mui/FocusVisibleButton";
import { LoadingButton } from "@/base/components/mui/LoadingButton";
@@ -250,10 +253,10 @@ const SuggestionsDialog: React.FC = ({
onClose,
person,
}) => {
- const { showMiniDialog } = useAppContext();
+ const { showMiniDialog, onGenericError } = useAppContext();
const [phase, setPhase] = useState<
- "loading" | "failed" | "saving" | undefined
+ "loading" | "fetch-failed" | "saving" | undefined
>();
const [suggestions, setSuggestions] = useState([]);
const [unsavedChanges /*, setUnsavedChanges */] = useState(false);
@@ -292,7 +295,7 @@ const SuggestionsDialog: React.FC = ({
resetPhaseAndClose();
} catch (e) {
log.error("Failed to save suggestion review", e);
- setPhase("failed");
+ onGenericError(e);
}
};
@@ -302,14 +305,28 @@ const SuggestionsDialog: React.FC = ({
setPhase("loading");
let ignore = false;
- void suggestionsForPerson(person).then(async (suggestions) => {
- setPhase(undefined);
- if (!ignore) setSuggestions(suggestions);
- });
+
+ const go = async () => {
+ try {
+ const suggestions = await suggestionsForPerson(person);
+ if (!ignore) {
+ setPhase(undefined);
+ setSuggestions(suggestions);
+ }
+ } catch (e) {
+ log.error("Failed to generate suggestions", e);
+ if (!ignore) {
+ setPhase("fetch-failed");
+ }
+ }
+ };
+
+ void go();
+
return () => {
ignore = true;
};
- }, [open, person]);
+ }, [open, person, onGenericError]);
console.log({ open, person });
@@ -332,6 +349,10 @@ const SuggestionsDialog: React.FC = ({
{pt("Finding similar faces...")}
+ ) : phase == "fetch-failed" ? (
+
+
+
) : suggestions.length == 0 ? (