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; };