diff --git a/web/packages/new/photos/components/PeopleList.tsx b/web/packages/new/photos/components/PeopleList.tsx index 1f9862f308..07157b5622 100644 --- a/web/packages/new/photos/components/PeopleList.tsx +++ b/web/packages/new/photos/components/PeopleList.tsx @@ -6,6 +6,7 @@ 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[]; @@ -66,6 +67,75 @@ const SearchPeopleButton = styled(UnstyledButton)( `, ); +export interface CGroupPeopleListProps { + /** + * 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" + */ + people: Person[]; + /** + * Called when the user selects a person in the list. + */ + onSelectPerson: (person: Person) => void; +} + +/** + * Show the list of faces in the given file that are not linked to a a specific + * cgroup ("people"). + */ +export const CGroupPeopleList: React.FC = ({ + people, + onSelectPerson, +}) => { + const isMobileWidth = useIsMobileWidth(); + return ( + 3 ? "center" : "start" }} + > + {people.slice(0, isMobileWidth ? 6 : 7).map((person) => ( + onSelectPerson(person)} + > + + + ))} + + ); +}; + +const SearchPeopleContainer = styled("div")` + display: flex; + flex-wrap: wrap; + align-items: center; + gap: 5px; + margin-block-start: 12px; + margin-block-end: 15px; +`; + +const SearchPeopleButton = styled(UnstyledButton)( + ({ theme }) => ` + width: 87px; + height: 87px; + border-radius: 50%; + overflow: hidden; + & > img { + width: 100%; + height: 100%; + } + :hover { + outline: 1px solid ${theme.colors.stroke.faint}; + outline-offset: 2px; + } +`, +); + const FaceChipContainer = styled("div")` display: flex; flex-wrap: wrap; diff --git a/web/packages/new/photos/services/ml/index.ts b/web/packages/new/photos/services/ml/index.ts index f7cc7a9a17..9330cf1e4c 100644 --- a/web/packages/new/photos/services/ml/index.ts +++ b/web/packages/new/photos/services/ml/index.ts @@ -593,14 +593,37 @@ export const clipMatches = ( worker().then((w) => w.clipMatches(searchPhrase)); /** - * Return the IDs of all the faces in the given {@link enteFile} that are not - * associated with a person cluster. + * 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 unidentifiedFaceIDs = async ( +export const peopleIDsAndOtherFaceIDsInFile = async ( enteFile: EnteFile, -): Promise => { +): Promise<[string, string | undefined][]> => { const index = await getFaceIndex(enteFile.id); - return index?.faces.map((f) => f.faceID) ?? []; + if (!index) return []; + + const people = _state.peopleSnapshot ?? []; + + const faceIDToPersonID = new Map(); + for (const person of people) { + let faceIDs: string[]; + if (person.type == "cgroup") { + faceIDs = person.cgroup.data.assigned.map((c) => c.faces).flat(); + } else { + faceIDs = person.cluster.faces; + } + for (const faceID of faceIDs) { + faceIDToPersonID.set(faceID, person.id); + } + } + + return index.faces.map(({ faceID }) => [ + faceID, + faceIDToPersonID.get(faceID), + ]); }; /**