wip checkpoint
This commit is contained in:
@@ -17,7 +17,7 @@ import {
|
||||
import { wipHasSwitchedOnceCmpAndSet } from "@/new/photos/services/ml";
|
||||
import {
|
||||
filterSearchableFiles,
|
||||
setSearchableCollectionsAndFiles,
|
||||
setSearchCollectionsAndFiles,
|
||||
} from "@/new/photos/services/search";
|
||||
import type { SearchOption } from "@/new/photos/services/search/types";
|
||||
import { EnteFile } from "@/new/photos/types/file";
|
||||
@@ -411,7 +411,7 @@ export default function Gallery() {
|
||||
|
||||
useEffect(
|
||||
() =>
|
||||
setSearchableCollectionsAndFiles({
|
||||
setSearchCollectionsAndFiles({
|
||||
collections: collections ?? [],
|
||||
files: getUniqueFiles(files ?? []),
|
||||
}),
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import { useIsMobileWidth } from "@/base/hooks";
|
||||
import type { Person } from "@/new/photos/services/ml";
|
||||
import { faceCrop, unidentifiedFaceIDs } from "@/new/photos/services/ml";
|
||||
import type { Person } from "@/new/photos/services/ml/cgroups";
|
||||
import type { EnteFile } from "@/new/photos/types/file";
|
||||
import { Skeleton, Typography, styled } from "@mui/material";
|
||||
import { t } from "i18next";
|
||||
|
||||
@@ -2,7 +2,6 @@ import { masterKeyFromSession } from "@/base/session-store";
|
||||
import { fileIDFromFaceID, wipClusterEnable } from ".";
|
||||
import type { EnteFile } from "../../types/file";
|
||||
import { getLocalFiles } from "../files";
|
||||
import { setCGroups } from "../search";
|
||||
import { pullCGroups } from "../user-entity";
|
||||
import type { FaceCluster } from "./cluster";
|
||||
import { getClusterGroups, getFaceIndexes } from "./db";
|
||||
@@ -78,6 +77,47 @@ export interface CGroup {
|
||||
avatarFaceID: string | undefined;
|
||||
}
|
||||
|
||||
/**
|
||||
* A massaged version of {@link CGroup} suitable for being shown in the UI.
|
||||
*
|
||||
* The cgroups synced with remote do not directly correspond to "people".
|
||||
* CGroups represent both positive and negative feedback, where the negations
|
||||
* are specifically feedback meant so that we do not show the corresponding
|
||||
* cluster in the UI.
|
||||
*
|
||||
* So while each person has an underlying cgroups, not all cgroups have a
|
||||
* corresponding person.
|
||||
*
|
||||
* Beyond this semantic difference, there is also data massaging: a
|
||||
* {@link Person} has data converted into a format that the UI can directly and
|
||||
* efficiently use, as compared to a {@link CGroup}, which is tailored for
|
||||
* transmission and storage.
|
||||
*/
|
||||
export interface Person {
|
||||
/**
|
||||
* Nanoid of the underlying {@link CGroup}.
|
||||
*/
|
||||
id: string;
|
||||
/**
|
||||
* The name of the person.
|
||||
*/
|
||||
name: string;
|
||||
/**
|
||||
* IDs of the (unique) files in which this face occurs.
|
||||
*/
|
||||
fileIDs: number[];
|
||||
/**
|
||||
* The face that should be used as the "cover" face to represent this
|
||||
* {@link Person} in the UI.
|
||||
*/
|
||||
displayFaceID: string;
|
||||
/**
|
||||
* The {@link EnteFile} which contains the display face.
|
||||
*/
|
||||
displayFaceFile: EnteFile;
|
||||
}
|
||||
|
||||
// TODO-Cluster remove me
|
||||
/**
|
||||
* A {@link CGroup} annotated with various in-memory state to make it easier for
|
||||
* the upper layers of our code to directly use it.
|
||||
@@ -110,10 +150,13 @@ export const syncCGroups = async () => {
|
||||
* This function is meant to run after files, cgroups and faces have been synced
|
||||
* with remote. It then uses all the information in the local DBs to construct
|
||||
* an in-memory list of {@link Person}s on which the UI will operate.
|
||||
*
|
||||
* @return A list of {@link Person}s, sorted by the number of files that they
|
||||
* reference.
|
||||
*/
|
||||
export const updatePeople = async () => {
|
||||
if (!process.env.NEXT_PUBLIC_ENTE_WIP_CL) return;
|
||||
if (!(await wipClusterEnable())) return;
|
||||
export const updatedPeople = async () => {
|
||||
if (!process.env.NEXT_PUBLIC_ENTE_WIP_CL) return [];
|
||||
if (!(await wipClusterEnable())) return [];
|
||||
|
||||
// Ignore faces belonging to deleted (incl Trash) and hidden files.
|
||||
//
|
||||
@@ -144,7 +187,7 @@ export const updatePeople = async () => {
|
||||
|
||||
// Convert cgroups to people.
|
||||
const cgroups = await getClusterGroups();
|
||||
const people = cgroups
|
||||
return cgroups
|
||||
.map((cgroup) => {
|
||||
// Hidden cgroups are clusters specifically marked so as to not be shown
|
||||
// in the UI.
|
||||
@@ -196,15 +239,6 @@ export const updatePeople = async () => {
|
||||
|
||||
return { id, name, fileIDs, displayFaceID, displayFaceFile };
|
||||
})
|
||||
.filter((c) => !!c);
|
||||
|
||||
// Read all the latest cluster groups, locally present files, local faces
|
||||
|
||||
// Re-read the cgroups across which we should search from the local ML DB.
|
||||
//
|
||||
// This DB read can happen in the search worker too, but that would cause
|
||||
// another handle to the DB to be opened (from the search worker's context).
|
||||
// Making the DB call here avoids that (not that we noticed any issues, but
|
||||
// preemptively trying not to poke IndexedDB too much lest it be flaky).
|
||||
await setCGroups();
|
||||
.filter((c) => !!c)
|
||||
.sort((a, b) => b.fileIDs.length - a.fileIDs.length);
|
||||
};
|
||||
|
||||
@@ -596,46 +596,6 @@ const setInterimScheduledStatus = () => {
|
||||
|
||||
const workerDidProcessFileOrIdle = throttled(updateMLStatusSnapshot, 2000);
|
||||
|
||||
/**
|
||||
* A massaged version of {@link CGroup} suitable for being shown in the UI.
|
||||
*
|
||||
* The cgroups synced with remote do not directly correspond to "people".
|
||||
* CGroups represent both positive and negative feedback, where the negations
|
||||
* are specifically feedback meant so that we do not show the corresponding
|
||||
* cluster in the UI.
|
||||
*
|
||||
* So while each person has an underlying cgroups, not all cgroups have a
|
||||
* corresponding person.
|
||||
*
|
||||
* Beyond this semantic difference, there is also data massaging: a
|
||||
* {@link Person} has data converted into a format that the UI can directly and
|
||||
* efficiently use, as compared to a {@link CGroup}, which is tailored for
|
||||
* transmission and storage.
|
||||
*/
|
||||
export interface Person {
|
||||
/**
|
||||
* Nanoid of the underlying {@link CGroup}.
|
||||
*/
|
||||
id: string;
|
||||
/**
|
||||
* The name of the person.
|
||||
*/
|
||||
name: string;
|
||||
/**
|
||||
* IDs of the (unique) files in which this face occurs.
|
||||
*/
|
||||
fileIDs: number[];
|
||||
/**
|
||||
* The face that should be used as the "cover" face to represent this
|
||||
* {@link Person} in the UI.
|
||||
*/
|
||||
displayFaceID: string;
|
||||
/**
|
||||
* The {@link EnteFile} which contains the display face.
|
||||
*/
|
||||
displayFaceFile: EnteFile;
|
||||
}
|
||||
|
||||
/**
|
||||
* A function that can be used to subscribe to updates to {@link Person}s.
|
||||
*
|
||||
|
||||
@@ -4,12 +4,12 @@ import { ComlinkWorker } from "@/base/worker/comlink-worker";
|
||||
import { FileType } from "@/media/file-type";
|
||||
import i18n, { t } from "i18next";
|
||||
import { clipMatches, isMLEnabled, isMLSupported } from "../ml";
|
||||
import type { CGroup } from "../ml/cgroups";
|
||||
import type { Person } from "../ml/cgroups";
|
||||
import type {
|
||||
LabelledFileType,
|
||||
LabelledSearchDateComponents,
|
||||
LocalizedSearchData,
|
||||
SearchableCollectionsAndFiles,
|
||||
SearchCollectionsAndFiles,
|
||||
SearchSuggestion,
|
||||
} from "./types";
|
||||
import type { SearchWorker } from "./worker";
|
||||
@@ -54,15 +54,14 @@ export const searchDataSync = () =>
|
||||
/**
|
||||
* Set the collections and files over which we should search.
|
||||
*/
|
||||
export const setSearchableCollectionsAndFiles = (
|
||||
data: SearchableCollectionsAndFiles,
|
||||
) => void worker().then((w) => w.setSearchableCollectionsAndFiles(data));
|
||||
export const setSearchCollectionsAndFiles = (cf: SearchCollectionsAndFiles) =>
|
||||
void worker().then((w) => w.setCollectionsAndFiles(cf));
|
||||
|
||||
/**
|
||||
* Set the cgroups that we should search across.
|
||||
* Set the people that we should search across.
|
||||
*/
|
||||
export const setCGroups = (data: CGroup[]) =>
|
||||
worker().then((w) => w.setCGroups(data));
|
||||
export const setPeople = (people: Person[]) =>
|
||||
void worker().then((w) => w.setPeople(people));
|
||||
|
||||
/**
|
||||
* Convert a search string into (annotated) suggestions that can be shown in the
|
||||
@@ -121,7 +120,7 @@ const suggestionsToOptions = (suggestions: SearchSuggestion[]) =>
|
||||
|
||||
/**
|
||||
* Return the list of {@link EnteFile}s (from amongst the previously set
|
||||
* {@link SearchableCollectionsAndFiles}) that match the given search {@link suggestion}.
|
||||
* {@link SearchCollectionsAndFiles}) that match the given search {@link suggestion}.
|
||||
*/
|
||||
export const filterSearchableFiles = async (suggestion: SearchSuggestion) =>
|
||||
worker().then((w) => w.filterSearchableFiles(suggestion));
|
||||
|
||||
@@ -6,7 +6,7 @@
|
||||
import type { Location } from "@/base/types";
|
||||
import type { Collection } from "@/media/collection";
|
||||
import { FileType } from "@/media/file-type";
|
||||
import type { Person } from "@/new/photos/services/ml";
|
||||
import type { Person } from "@/new/photos/services/ml/cgroups";
|
||||
import type { EnteFile } from "@/new/photos/types/file";
|
||||
import type { LocationTag } from "../user-entity";
|
||||
|
||||
@@ -45,9 +45,9 @@ export interface SearchOption {
|
||||
}
|
||||
|
||||
/**
|
||||
* The collections and files which we should search.
|
||||
* The collections and files over which we should search.
|
||||
*/
|
||||
export interface SearchableCollectionsAndFiles {
|
||||
export interface SearchCollectionsAndFiles {
|
||||
collections: Collection[];
|
||||
files: EnteFile[];
|
||||
}
|
||||
|
||||
@@ -1,9 +1,8 @@
|
||||
import { HTTPError } from "@/base/http";
|
||||
import log from "@/base/log";
|
||||
import type { Location } from "@/base/types";
|
||||
import type { Collection } from "@/media/collection";
|
||||
import { fileCreationPhotoDate, fileLocation } from "@/media/file-metadata";
|
||||
import type { CGroup } from "@/new/photos/services/ml/cgroups";
|
||||
import type { Person } from "@/new/photos/services/ml/cgroups";
|
||||
import type { EnteFile } from "@/new/photos/types/file";
|
||||
import { ensure } from "@/utils/ensure";
|
||||
import { nullToUndefined } from "@/utils/transform";
|
||||
@@ -23,13 +22,11 @@ import type {
|
||||
LabelledSearchDateComponents,
|
||||
LocalizedSearchData,
|
||||
Searchable,
|
||||
SearchableCollectionsAndFiles,
|
||||
SearchCollectionsAndFiles,
|
||||
SearchDateComponents,
|
||||
SearchSuggestion,
|
||||
} from "./types";
|
||||
|
||||
type SearchableCGroup = Searchable<Omit<CGroup, "name"> & { name: string }>;
|
||||
|
||||
/**
|
||||
* A web worker that runs the search asynchronously so that the main thread
|
||||
* remains responsive.
|
||||
@@ -37,11 +34,11 @@ type SearchableCGroup = Searchable<Omit<CGroup, "name"> & { name: string }>;
|
||||
export class SearchWorker {
|
||||
private locationTags: Searchable<LocationTag>[] = [];
|
||||
private cities: Searchable<City>[] = [];
|
||||
private searchableCollectionsAndFiles: SearchableCollectionsAndFiles = {
|
||||
private collectionsAndFiles: SearchCollectionsAndFiles = {
|
||||
collections: [],
|
||||
files: [],
|
||||
};
|
||||
private searchableCGroups: SearchableCGroup[] = [];
|
||||
private searchablePeople: Searchable<Person>[] = [];
|
||||
|
||||
/**
|
||||
* Fetch any state we might need when the actual search happens.
|
||||
@@ -71,23 +68,17 @@ export class SearchWorker {
|
||||
/**
|
||||
* Set the collections and files that we should search across.
|
||||
*/
|
||||
setSearchableCollectionsAndFiles(data: SearchableCollectionsAndFiles) {
|
||||
this.searchableCollectionsAndFiles = data;
|
||||
setCollectionsAndFiles(cf: SearchCollectionsAndFiles) {
|
||||
this.collectionsAndFiles = cf;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the cgroups that we should search across.
|
||||
* Set the people that we should search across.
|
||||
*/
|
||||
setCGroups(cgroups: CGroup[]) {
|
||||
this.searchableCGroups = cgroups
|
||||
.map((cgroup) => {
|
||||
const name = cgroup.name;
|
||||
if (!name) return undefined;
|
||||
if (cgroup.isHidden) return undefined;
|
||||
return { ...cgroup, name, lowercasedName: name.toLowerCase() };
|
||||
})
|
||||
.filter((c) => !!c);
|
||||
log.debug(() => ["searchableCGroups", this.searchableCGroups]);
|
||||
setPeople(people: Person[]) {
|
||||
this.searchablePeople = people.map((person) => {
|
||||
return { ...person, lowercasedName: person.name.toLowerCase() };
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -101,8 +92,8 @@ export class SearchWorker {
|
||||
return suggestionsForString(
|
||||
s,
|
||||
searchString,
|
||||
this.searchableCollectionsAndFiles,
|
||||
this.searchableCGroups,
|
||||
this.collectionsAndFiles,
|
||||
this.searchablePeople,
|
||||
localizedSearchData,
|
||||
this.locationTags,
|
||||
this.cities,
|
||||
@@ -114,7 +105,7 @@ export class SearchWorker {
|
||||
*/
|
||||
filterSearchableFiles(suggestion: SearchSuggestion) {
|
||||
return filterSearchableFiles(
|
||||
this.searchableCollectionsAndFiles.files,
|
||||
this.collectionsAndFiles.files,
|
||||
suggestion,
|
||||
);
|
||||
}
|
||||
@@ -123,7 +114,7 @@ export class SearchWorker {
|
||||
* Batched variant of {@link filterSearchableFiles}.
|
||||
*/
|
||||
filterSearchableFilesMulti(suggestions: SearchSuggestion[]) {
|
||||
const files = this.searchableCollectionsAndFiles.files;
|
||||
const files = this.collectionsAndFiles.files;
|
||||
return suggestions
|
||||
.map((sg) => [filterSearchableFiles(files, sg), sg] as const)
|
||||
.filter(([files]) => files.length);
|
||||
@@ -139,15 +130,15 @@ expose(SearchWorker);
|
||||
const suggestionsForString = (
|
||||
s: string,
|
||||
searchString: string,
|
||||
{ collections, files }: SearchableCollectionsAndFiles,
|
||||
searchableCGroups: SearchableCGroup[],
|
||||
{ collections, files }: SearchCollectionsAndFiles,
|
||||
searchablePeople: Searchable<Person>[],
|
||||
{ locale, holidays, labelledFileTypes }: LocalizedSearchData,
|
||||
locationTags: Searchable<LocationTag>[],
|
||||
cities: Searchable<City>[],
|
||||
): SearchSuggestion[] =>
|
||||
[
|
||||
// . <-- clip suggestions will be inserted here by our caller.
|
||||
peopleSuggestions(s, searchableCGroups),
|
||||
peopleSuggestions(s, searchablePeople),
|
||||
fileTypeSuggestions(s, labelledFileTypes),
|
||||
dateSuggestions(s, locale, holidays),
|
||||
locationSuggestions(s, locationTags, cities),
|
||||
@@ -216,21 +207,11 @@ const fileCaptionSuggestion = (
|
||||
|
||||
const peopleSuggestions = (
|
||||
s: string,
|
||||
searchableCGroups: SearchableCGroup[],
|
||||
): SearchSuggestion[] => {
|
||||
// Suppress the unused warning during WIP
|
||||
if (process.env.NEXT_PUBLIC_ENTE_WIP_CL) {
|
||||
console.log({ s, searchableCGroups });
|
||||
}
|
||||
return [];
|
||||
// searchableCGroups
|
||||
// .filter(({ lowercasedName }) => lowercasedName.startsWith(s))
|
||||
// .map((scgroup) => ({
|
||||
// type: "person",
|
||||
// person: scgroup,
|
||||
// label: scgroup.name,
|
||||
// }));
|
||||
};
|
||||
searchablePeople: Searchable<Person>[],
|
||||
): SearchSuggestion[] =>
|
||||
searchablePeople
|
||||
.filter(({ lowercasedName }) => lowercasedName.startsWith(s))
|
||||
.map((person) => ({ type: "person", person, label: person.name }));
|
||||
|
||||
const dateSuggestions = (
|
||||
s: string,
|
||||
|
||||
Reference in New Issue
Block a user