diff --git a/web/packages/new/photos/components/PeopleList.tsx b/web/packages/new/photos/components/PeopleList.tsx index 3dabd06757..ab02c26158 100644 --- a/web/packages/new/photos/components/PeopleList.tsx +++ b/web/packages/new/photos/components/PeopleList.tsx @@ -1,7 +1,7 @@ import { useIsSmallWidth } from "@/base/hooks"; import { pt } from "@/base/i18n"; import { faceCrop, type AnnotatedFaceID } from "@/new/photos/services/ml"; -import type { Person } from "@/new/photos/services/ml/people"; +import type { Person, PreviewableFace } 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"; @@ -198,7 +198,7 @@ export interface SuggestionFaceListProps { * Faces, each annotated with the corresponding {@link EnteFile}, to show in * the list. */ - faces: { enteFile: EnteFile; faceID: string }[]; + faces: []; } /** @@ -228,14 +228,10 @@ const SuggestionFaceList_ = styled("div")` gap: 5px; `; -interface FaceCropImageViewProps { - /** The ID of the face to display. */ - faceID: string; - /** The {@link EnteFile} which contains this face. */ - enteFile: EnteFile; +type FaceCropImageViewProps = PreviewableFace & { /** Width and height for the placeholder. */ placeholderDimension: number; -} +}; /** * An image view showing the face crop for the given face. diff --git a/web/packages/new/photos/components/gallery/PeopleHeader.tsx b/web/packages/new/photos/components/gallery/PeopleHeader.tsx index 3bab0181a6..de9b2c4264 100644 --- a/web/packages/new/photos/components/gallery/PeopleHeader.tsx +++ b/web/packages/new/photos/components/gallery/PeopleHeader.tsx @@ -381,7 +381,6 @@ const SuggestionsDialog: React.FC = ({ const go = async () => { try { const suggestions = await suggestionsForPerson(person); - console.log("fetching"); dispatch({ type: "fetched", personID, suggestions }); } catch (e) { log.error("Failed to generate suggestions", e); @@ -426,8 +425,6 @@ const SuggestionsDialog: React.FC = ({ } }; - console.log({ f: "render", open, person, state }); - return ( = ({ }} key={suggestion.id} > - {`${suggestion.faces.length} faces ntaoheu naoehtu aosnehu asoenuh aoenuht`} + {`${suggestion.previewFaces.length} faces ntaoheu naoehtu aosnehu asoenuh aoenuht`} & { isBadFace: boolean; }; -export interface ClusterPreview { - clusterSize: number; - faces: ClusterPreviewFace[]; -} - -export interface ClusterPreviewFace { - face: ClusterFace; - cosineSimilarity: number; - wasMerged: boolean; -} - /** * Generates clusters from the given faces using a batched form of linear * clustering, with a bit of lookback (and a dollop of heuristics) to get the diff --git a/web/packages/new/photos/services/ml/people.ts b/web/packages/new/photos/services/ml/people.ts index 49e6eaaace..e6b1e4f3f1 100644 --- a/web/packages/new/photos/services/ml/people.ts +++ b/web/packages/new/photos/services/ml/people.ts @@ -1,3 +1,4 @@ +import { assertionFailed } from "@/base/assert"; import log from "@/base/log"; import type { EnteFile } from "../../types/file"; import { getLocalFiles } from "../files"; @@ -144,6 +145,19 @@ export type NamedPerson = Person & { name: string; }; +/** + * A face ID annotated with the {@link EnteFile} that contains it. + * + * Both these pieces of information are needed for a UI element to show the + * face. + */ +export interface PreviewableFace { + /** The ID of the face to display. */ + faceID: string; + /** The {@link EnteFile} which contains this face. */ + enteFile: EnteFile; +} + /** * Construct in-memory people using the data present locally, ignoring faces * belonging to deleted and hidden files. @@ -308,7 +322,23 @@ export const filterNamedPeople = (people: Person[]): NamedPerson[] => { return namedPeople; }; -export type PersonSuggestion = FaceCluster; +export interface PersonSuggestion { + /** + * The ID of the suggestion. This is the same as the cluster's ID, + * duplicated here for the UI's convenience. + */ + id: string; + /** + * The underlying {@link FaceCluster} that is being offered as the + * suggestion. + */ + cluster: FaceCluster; + /** + * A list of up to 3 "preview" faces for this cluster, each annotated with + * the corresponding {@link EnteFile} that contains them. + */ + previewFaces: PreviewableFace[]; +} /** * Returns suggestions for the given person. @@ -367,13 +397,41 @@ export const suggestionsForPerson = async (person: CGroupPerson) => { if (suggest) suggestedClusters.push(cluster); } - const result = suggestedClusters.sort( - (a, b) => b.faces.length - a.faces.length, - ); + suggestedClusters.sort((a, b) => b.faces.length - a.faces.length); + + // Annotate the clusters with the information that the UI needs to show its + // preview faces. + + const files = await getLocalFiles("normal"); + const fileByID = new Map(files.map((f) => [f.id, f])); + + const suggestions = suggestedClusters.map((cluster) => { + const previewFaces = cluster.faces + .slice(0, 3) + .map((faceID) => { + const fileID = fileIDFromFaceID(faceID); + if (!fileID) { + assertionFailed(); + return undefined; + } + const file = fileByID.get(fileID); + if (!file) { + // TODO-Cluster: This might be a hidden/trash file, so this + // assert is not appropriate, we instead need a "until 3". + // assertionFailed(); + return undefined; + } + return { enteFile: file, faceID }; + }) + .filter((f) => !!f); + + const id = cluster.id; + return { id, cluster, previewFaces }; + }); log.info( - `Generated ${result.length} suggestions for ${person.id} (${Date.now() - startTime} ms)`, + `Generated ${suggestions.length} suggestions for ${person.id} (${Date.now() - startTime} ms)`, ); - return result; + return suggestions; };