diff --git a/web/apps/photos/src/components/PhotoViewer/FileInfo/index.tsx b/web/apps/photos/src/components/PhotoViewer/FileInfo/index.tsx index a8c5957ba9..bfd86e0db3 100644 --- a/web/apps/photos/src/components/PhotoViewer/FileInfo/index.tsx +++ b/web/apps/photos/src/components/PhotoViewer/FileInfo/index.tsx @@ -16,7 +16,7 @@ import { UnidentifiedFaces } from "@/new/photos/components/PeopleList"; import { PhotoDateTimePicker } from "@/new/photos/components/PhotoDateTimePicker"; import { photoSwipeZIndex } from "@/new/photos/components/PhotoViewer"; import { tagNumericValue, type RawExifTags } from "@/new/photos/services/exif"; -import { annotatedFaceIDsForFile, isMLEnabled } from "@/new/photos/services/ml"; +import { annotatedFaceIDsForFile, AnnotatedFacesForFile, getAnnotatedFacesForFile, getFacesForFile, isMLEnabled } from "@/new/photos/services/ml"; import { EnteFile } from "@/new/photos/types/file"; import { formattedByteSize } from "@/new/photos/utils/units"; import CopyButton from "@ente/shared/components/CodeBlock/CopyButton"; @@ -98,6 +98,7 @@ export const FileInfo: React.FC = ({ const [exifInfo, setExifInfo] = useState(); const [openRawExif, setOpenRawExif] = useState(false); + const [annotatedFaces, setAnnotatedFaces] = useState(); const location = useMemo(() => { if (file) { @@ -107,23 +108,19 @@ export const FileInfo: React.FC = ({ return exif?.parsed?.location; }, [file, exif]); - const [annotatedPeopleFaceIDs, otherFaceIDs] = - useMemoSingleThreaded(async () => { - if (!file) return [[], []]; - const annotatedFaceIDs = await annotatedFaceIDsForFile(file); - return annotatedFaceIDs.reduce( - ([people, other], item) => { - if (item[1]) { - people.push(item); - } else { - other.push(item[0]); - } - return [people, other]; - }, - [[], []], - ); + + useEffect(() => { + let didCancel = false; + + void (async () => { + const result = await getAnnotatedFacesForFile(file); + !didCancel && setAnnotatedFaces(result); + })(); + + return () => { didCancel = true;} }, [file]); + useEffect(() => { setExifInfo(parseExifInfo(exif)); }, [exif]); @@ -287,7 +284,8 @@ export const FileInfo: React.FC = ({ {isMLEnabled() && ( <> - {/* TODO-Cluster */} + {annotatedFaces?.annotatedFaceIDs.length && + // {/* TODO-Cluster */} )} diff --git a/web/packages/new/photos/components/PeopleList.tsx b/web/packages/new/photos/components/PeopleList.tsx index 07157b5622..3553ed78aa 100644 --- a/web/packages/new/photos/components/PeopleList.tsx +++ b/web/packages/new/photos/components/PeopleList.tsx @@ -1,12 +1,11 @@ import { useIsMobileWidth } from "@/base/hooks"; -import { faceCrop, unidentifiedFaceIDs } from "@/new/photos/services/ml"; +import { faceCrop, type AnnotatedFaceID } from "@/new/photos/services/ml"; import type { Person } from "@/new/photos/services/ml/people"; import type { EnteFile } from "@/new/photos/types/file"; import { Skeleton, Typography, styled } from "@mui/material"; import { t } from "i18next"; import React, { useEffect, useState } from "react"; import { UnstyledButton } from "./mui-custom"; -import type { CGroup } from "../services/user-entity"; export interface SearchPeopleListProps { people: Person[]; @@ -67,28 +66,21 @@ const SearchPeopleButton = styled(UnstyledButton)( `, ); -export interface CGroupPeopleListProps { +export interface AnnotatedFacePeopleListProps { + annotatedFaceIDs: AnnotatedFaceID[]; /** - * List of cgroup people to show. - * - * The current types don't reflect this, but these are all guaranteed to be - * {@link Person}s with type "cgroup" + * Called when the user selects a face in the list. */ - people: Person[]; - /** - * Called when the user selects a person in the list. - */ - onSelectPerson: (person: Person) => void; + onSelectFace: (annotatedFaceID: AnnotatedFaceID) => void; } /** - * Show the list of faces in the given file that are not linked to a a specific - * cgroup ("people"). + * Show the list of faces in the given file that are associated with a specific + * person. */ -export const CGroupPeopleList: React.FC = ({ - people, - onSelectPerson, -}) => { +export const AnnotatedFacePeopleList: React.FC< + AnnotatedFacePeopleListProps +> = ({ annotatedFaceIDs, onSelectFace }) => { const isMobileWidth = useIsMobileWidth(); return ( = ({ }) => { const [faceIDs, setFaceIDs] = useState([]); - useEffect(() => { - let didCancel = false; - - const go = async () => { - const faceIDs = await unidentifiedFaceIDs(enteFile); - !didCancel && setFaceIDs(faceIDs); - }; - - void go(); - - return () => { - didCancel = true; - }; - }, [enteFile]); - if (faceIDs.length == 0) return <>; return ( diff --git a/web/packages/new/photos/services/ml/index.ts b/web/packages/new/photos/services/ml/index.ts index 70d6762349..bc7db78572 100644 --- a/web/packages/new/photos/services/ml/index.ts +++ b/web/packages/new/photos/services/ml/index.ts @@ -592,18 +592,39 @@ export const clipMatches = ( ): Promise => worker().then((w) => w.clipMatches(searchPhrase)); +/** A face ID annotated with the ID of the person to which it is associated. */ +export interface AnnotatedFaceID { + faceID: string; + personID: string; +} + +/** + * List of faces found in a file + * + * It is actually a pair of lists, one annotated by the person ids, and one with + * just the face ids. + */ +export interface AnnotatedFacesForFile { + /** + * A list of {@link AnnotatedFaceID}s for all faces in the file that are + * also associated with a {@link Person}. + */ + annotatedFaceIDs: AnnotatedFaceID[]; + /* A list of the remaining face (ids). */ + otherFaceIDs: string[]; +} + /** * Return the list of faces found in the given {@link enteFile}. - * - * Each item is returned as a (faceID, personID) tuple, where the faceID is the - * ID of the face, and the personID is the id of the corresponding person that - * this face is associated to (if any). */ -export const annotatedFaceIDsForFile = async ( +export const getAnnotatedFacesForFile = async ( enteFile: EnteFile, -): Promise<[string, string | undefined][]> => { +): Promise => { + const annotatedFaceIDs: AnnotatedFaceID[] = []; + const otherFaceIDs: string[] = []; + const index = await getFaceIndex(enteFile.id); - if (!index) return []; + if (!index) return { annotatedFaceIDs, otherFaceIDs }; const people = _state.peopleSnapshot ?? []; @@ -620,10 +641,16 @@ export const annotatedFaceIDsForFile = async ( } } - return index.faces.map(({ faceID }) => [ - faceID, - faceIDToPersonID.get(faceID), - ]); + for (const { faceID } of index.faces) { + const personID = faceIDToPersonID.get(faceID); + if (personID) { + annotatedFaceIDs.push({ faceID, personID }); + } else { + otherFaceIDs.push(faceID); + } + } + + return { annotatedFaceIDs, otherFaceIDs }; }; /**