diff --git a/web/apps/photos/src/components/PhotoViewer/FileInfo/index.tsx b/web/apps/photos/src/components/PhotoViewer/FileInfo/index.tsx
index 5db9e8d40a..158277ff23 100644
--- a/web/apps/photos/src/components/PhotoViewer/FileInfo/index.tsx
+++ b/web/apps/photos/src/components/PhotoViewer/FileInfo/index.tsx
@@ -332,7 +332,7 @@ export function FileInfo({
{isMLEnabled() && (
<>
{/* */}
-
+
>
)}
diff --git a/web/apps/photos/src/components/ml/PeopleList.tsx b/web/apps/photos/src/components/ml/PeopleList.tsx
index d0481ef3f7..cf7d6f54d2 100644
--- a/web/apps/photos/src/components/ml/PeopleList.tsx
+++ b/web/apps/photos/src/components/ml/PeopleList.tsx
@@ -1,4 +1,7 @@
-import { unidentifiedFaceIDs } from "@/new/photos/services/ml";
+import {
+ regenerateFaceCropsIfNeeded,
+ unidentifiedFaceIDs,
+} from "@/new/photos/services/ml";
import type { Person } from "@/new/photos/services/ml/people";
import { EnteFile } from "@/new/photos/types/file";
import { blobCache } from "@/next/blob-cache";
@@ -72,21 +75,37 @@ export function PhotoPeopleList() {
return <>>;
}
-export function UnidentifiedFaces({ file }: { file: EnteFile }) {
+interface UnidentifiedFacesProps {
+ enteFile: EnteFile;
+}
+/**
+ * Show the list of faces in the given file that are not linked to a specific
+ * person ("face cluster").
+ */
+export const UnidentifiedFaces: React.FC = ({
+ enteFile,
+}) => {
const [faceIDs, setFaceIDs] = useState([]);
+ const [, setDidRegen] = useState(false);
useEffect(() => {
let didCancel = false;
(async () => {
- const faceIDs = await unidentifiedFaceIDs(file);
+ const faceIDs = await unidentifiedFaceIDs(enteFile);
!didCancel && setFaceIDs(faceIDs);
+ // Don't block for the regeneration to happen. If anything got
+ // regenerated, the result will be true, which'll cause our state to
+ // change and us to be redrawn (and fetch the regenerated crops).
+ void regenerateFaceCropsIfNeeded(enteFile).then((r) =>
+ setDidRegen(r),
+ );
})();
return () => {
didCancel = true;
};
- }, [file]);
+ }, [enteFile]);
if (faceIDs.length == 0) return <>>;
@@ -104,12 +123,18 @@ export function UnidentifiedFaces({ file }: { file: EnteFile }) {
>
);
-}
+};
interface FaceCropImageViewProps {
faceID: string;
}
+/**
+ * An image view showing the face crop for the given {@link faceID}.
+ *
+ * The image is read from the "face-crops" {@link BlobCache}. While the image is
+ * being fetched, or if it doesn't exist, a placeholder is shown.
+ */
const FaceCropImageView: React.FC = ({ faceID }) => {
const [objectURL, setObjectURL] = useState();
@@ -119,11 +144,6 @@ const FaceCropImageView: React.FC = ({ faceID }) => {
blobCache("face-crops")
.then((cache) => cache.get(faceID))
.then((data) => {
- /*
- TODO(MR): regen if needed and get this to work on web too.
- cachedOrNew("face-crops", cacheKey, async () => {
- return regenerateFaceCrop(faceId);
- })*/
if (data) {
const blob = new Blob([data]);
if (!didCancel) setObjectURL(URL.createObjectURL(blob));
diff --git a/web/packages/new/photos/services/ml/index.ts b/web/packages/new/photos/services/ml/index.ts
index fb784755f4..97b6e09a72 100644
--- a/web/packages/new/photos/services/ml/index.ts
+++ b/web/packages/new/photos/services/ml/index.ts
@@ -240,17 +240,21 @@ export const unidentifiedFaceIDs = async (
* Check to see if any of the faces in the given file do not have a face crop
* present locally. If so, then regenerate the face crops for all the faces in
* the file (updating the "face-crops" {@link BlobCache}).
+ *
+ * @returns true if one or more face crops were regenerated; false otherwise.
*/
export const regenerateFaceCropsIfNeeded = async (enteFile: EnteFile) => {
const index = await faceIndex(enteFile.id);
- if (!index) return;
+ if (!index) return false;
const faceIDs = index.faceEmbedding.faces.map((f) => f.faceID);
const cache = await blobCache("face-crops");
for (const id of faceIDs) {
if (!(await cache.has(id))) {
await regenerateFaceCrops(enteFile, index);
- break;
+ return true;
}
}
+
+ return false;
};