This commit is contained in:
Manav Rathi
2024-09-11 18:08:48 +05:30
parent c809d572f7
commit ace2e5bb27
3 changed files with 172 additions and 175 deletions

View File

@@ -7,6 +7,7 @@ import {
mlStatusSnapshot,
mlStatusSubscribe,
} from "@/new/photos/services/ml";
import { getAutoCompleteSuggestions } from "@/new/photos/services/search";
import type {
City,
SearchDateComponents,
@@ -65,7 +66,6 @@ import {
type StylesConfig,
} from "react-select";
import AsyncSelect from "react-select/async";
import { getAutoCompleteSuggestions } from "services/searchService";
interface SearchBarProps {
/**

View File

@@ -1,174 +0,0 @@
import log from "@/base/log";
import type { Collection } from "@/media/collection";
import { FileType } from "@/media/file-type";
import { createSearchQuery, search } from "@/new/photos/services/search";
import type {
SearchDateComponents,
SearchPerson,
} from "@/new/photos/services/search/types";
import {
City,
ClipSearchScores,
SearchOption,
SearchQuery,
Suggestion,
SuggestionType,
} from "@/new/photos/services/search/types";
import type { LocationTag } from "@/new/photos/services/user-entity";
import { type EnteFile } from "@/new/photos/types/file";
// Suggestions shown in the search dropdown when the user has typed something.
export const getAutoCompleteSuggestions =
(files: EnteFile[], collections: Collection[]) =>
async (searchPhrase: string): Promise<SearchOption[]> => {
log.debug(() => ["getAutoCompleteSuggestions", { searchPhrase }]);
try {
const searchPhrase2 = searchPhrase.trim().toLowerCase();
if (!searchPhrase2?.length) {
return [];
}
const suggestions: Suggestion[] = [
// The following functionality has moved to createSearchQuery
// - getClipSuggestion(searchPhrase)
// - getDateSuggestion(searchPhrase),
// - getLocationSuggestion(searchPhrase),
// - getFileTypeSuggestion(searchPhrase),
...(await createSearchQuery(searchPhrase)),
...getCollectionSuggestion(searchPhrase2, collections),
getFileNameSuggestion(searchPhrase2, files),
getFileCaptionSuggestion(searchPhrase2, files),
].filter((suggestion) => !!suggestion);
return convertSuggestionsToOptions(suggestions);
} catch (e) {
log.error("getAutoCompleteSuggestions failed", e);
return [];
}
};
async function convertSuggestionsToOptions(
suggestions: Suggestion[],
): Promise<SearchOption[]> {
const previewImageAppendedOptions: SearchOption[] = [];
for (const suggestion of suggestions) {
const searchQuery = convertSuggestionToSearchQuery(suggestion);
const resultFiles = await search(searchQuery);
if (searchQuery?.clip) {
resultFiles.sort((a, b) => {
const aScore = searchQuery.clip.get(a.id);
const bScore = searchQuery.clip.get(b.id);
return bScore - aScore;
});
}
if (resultFiles.length) {
previewImageAppendedOptions.push({
...suggestion,
fileCount: resultFiles.length,
previewFiles: resultFiles.slice(0, 3),
});
}
}
return previewImageAppendedOptions;
}
function getCollectionSuggestion(
searchPhrase: string,
collections: Collection[],
): Suggestion[] {
const collectionResults = searchCollection(searchPhrase, collections);
return collectionResults.map(
(searchResult) =>
({
type: SuggestionType.COLLECTION,
value: searchResult.id,
label: searchResult.name,
}) as Suggestion,
);
}
function getFileNameSuggestion(
searchPhrase: string,
files: EnteFile[],
): Suggestion {
const matchedFiles = searchFilesByName(searchPhrase, files);
return {
type: SuggestionType.FILE_NAME,
value: matchedFiles.map((file) => file.id),
label: searchPhrase,
};
}
function getFileCaptionSuggestion(
searchPhrase: string,
files: EnteFile[],
): Suggestion {
const matchedFiles = searchFilesByCaption(searchPhrase, files);
return {
type: SuggestionType.FILE_CAPTION,
value: matchedFiles.map((file) => file.id),
label: searchPhrase,
};
}
function searchCollection(
searchPhrase: string,
collections: Collection[],
): Collection[] {
return collections.filter((collection) =>
collection.name.toLowerCase().includes(searchPhrase),
);
}
function searchFilesByName(searchPhrase: string, files: EnteFile[]) {
return files.filter(
(file) =>
file.id.toString().includes(searchPhrase) ||
file.metadata.title.toLowerCase().includes(searchPhrase),
);
}
function searchFilesByCaption(searchPhrase: string, files: EnteFile[]) {
return files.filter(
(file) =>
file.pubMagicMetadata &&
file.pubMagicMetadata.data.caption
?.toLowerCase()
.includes(searchPhrase),
);
}
function convertSuggestionToSearchQuery(option: Suggestion): SearchQuery {
switch (option.type) {
case SuggestionType.DATE:
return {
date: option.value as SearchDateComponents,
};
case SuggestionType.LOCATION:
return {
location: option.value as LocationTag,
};
case SuggestionType.CITY:
return { city: option.value as City };
case SuggestionType.COLLECTION:
return { collection: option.value as number };
case SuggestionType.FILE_NAME:
return { files: option.value as number[] };
case SuggestionType.FILE_CAPTION:
return { files: option.value as number[] };
case SuggestionType.PERSON:
return { person: option.value as SearchPerson };
case SuggestionType.FILE_TYPE:
return { fileType: option.value as FileType };
case SuggestionType.CLIP:
return { clip: option.value as ClipSearchScores };
}
}

View File

@@ -1,16 +1,25 @@
import { isDesktop } from "@/base/app";
import log from "@/base/log";
import { masterKeyFromSession } from "@/base/session-store";
import { ComlinkWorker } from "@/base/worker/comlink-worker";
import type { Collection } from "@/media/collection";
import { FileType } from "@/media/file-type";
import type { LocationTag } from "@/new/photos/services/user-entity";
import i18n, { t } from "i18next";
import type { EnteFile } from "../../types/file";
import { clipMatches, isMLEnabled } from "../ml";
import {
SuggestionType,
type City,
type ClipSearchScores,
type DateSearchResult,
type LabelledFileType,
type LocalizedSearchData,
type SearchDateComponents,
type SearchOption,
type SearchPerson,
type SearchQuery,
type Suggestion,
} from "./types";
import type { SearchWorker } from "./worker";
@@ -149,3 +158,165 @@ const labelledFileTypes = (): LabelledFileType[] => [
{ fileType: FileType.video, label: t("VIDEO") },
{ fileType: FileType.livePhoto, label: t("LIVE_PHOTO") },
];
// TODO-Cluster -- AUDIT BELOW THIS
// Suggestions shown in the search dropdown when the user has typed something.
export const getAutoCompleteSuggestions =
(files: EnteFile[], collections: Collection[]) =>
async (searchPhrase: string): Promise<SearchOption[]> => {
log.debug(() => ["getAutoCompleteSuggestions", { searchPhrase }]);
try {
const searchPhrase2 = searchPhrase.trim().toLowerCase();
// eslint-disable-next-line @typescript-eslint/no-unnecessary-condition
if (!searchPhrase2?.length) {
return [];
}
const suggestions: Suggestion[] = [
// The following functionality has moved to createSearchQuery
// - getClipSuggestion(searchPhrase)
// - getDateSuggestion(searchPhrase),
// - getLocationSuggestion(searchPhrase),
// - getFileTypeSuggestion(searchPhrase),
...(await createSearchQuery(searchPhrase)),
...getCollectionSuggestion(searchPhrase2, collections),
getFileNameSuggestion(searchPhrase2, files),
getFileCaptionSuggestion(searchPhrase2, files),
// eslint-disable-next-line @typescript-eslint/no-unnecessary-condition
].filter((suggestion) => !!suggestion);
return convertSuggestionsToOptions(suggestions);
} catch (e) {
log.error("getAutoCompleteSuggestions failed", e);
return [];
}
};
async function convertSuggestionsToOptions(
suggestions: Suggestion[],
): Promise<SearchOption[]> {
const previewImageAppendedOptions: SearchOption[] = [];
for (const suggestion of suggestions) {
const searchQuery = convertSuggestionToSearchQuery(suggestion);
const resultFiles = await search(searchQuery);
// eslint-disable-next-line @typescript-eslint/no-unnecessary-condition
if (searchQuery?.clip) {
resultFiles.sort((a, b) => {
const aScore = searchQuery.clip?.get(a.id) ?? 0;
const bScore = searchQuery.clip?.get(b.id) ?? 0;
return bScore - aScore;
});
}
if (resultFiles.length) {
previewImageAppendedOptions.push({
...suggestion,
fileCount: resultFiles.length,
previewFiles: resultFiles.slice(0, 3),
});
}
}
return previewImageAppendedOptions;
}
function getCollectionSuggestion(
searchPhrase: string,
collections: Collection[],
): Suggestion[] {
const collectionResults = searchCollection(searchPhrase, collections);
return collectionResults.map(
(searchResult) =>
({
type: SuggestionType.COLLECTION,
value: searchResult.id,
label: searchResult.name,
}) as Suggestion,
);
}
function getFileNameSuggestion(
searchPhrase: string,
files: EnteFile[],
): Suggestion {
const matchedFiles = searchFilesByName(searchPhrase, files);
return {
type: SuggestionType.FILE_NAME,
value: matchedFiles.map((file) => file.id),
label: searchPhrase,
};
}
function getFileCaptionSuggestion(
searchPhrase: string,
files: EnteFile[],
): Suggestion {
const matchedFiles = searchFilesByCaption(searchPhrase, files);
return {
type: SuggestionType.FILE_CAPTION,
value: matchedFiles.map((file) => file.id),
label: searchPhrase,
};
}
function searchCollection(
searchPhrase: string,
collections: Collection[],
): Collection[] {
return collections.filter((collection) =>
collection.name.toLowerCase().includes(searchPhrase),
);
}
function searchFilesByName(searchPhrase: string, files: EnteFile[]) {
return files.filter(
(file) =>
file.id.toString().includes(searchPhrase) ||
file.metadata.title.toLowerCase().includes(searchPhrase),
);
}
function searchFilesByCaption(searchPhrase: string, files: EnteFile[]) {
return files.filter(
(file) =>
// eslint-disable-next-line @typescript-eslint/prefer-optional-chain
file.pubMagicMetadata &&
file.pubMagicMetadata.data.caption
?.toLowerCase()
.includes(searchPhrase),
);
}
function convertSuggestionToSearchQuery(option: Suggestion): SearchQuery {
switch (option.type) {
case SuggestionType.DATE:
return {
date: option.value as SearchDateComponents,
};
case SuggestionType.LOCATION:
return {
location: option.value as LocationTag,
};
case SuggestionType.CITY:
return { city: option.value as City };
case SuggestionType.COLLECTION:
return { collection: option.value as number };
case SuggestionType.FILE_NAME:
return { files: option.value as number[] };
case SuggestionType.FILE_CAPTION:
return { files: option.value as number[] };
case SuggestionType.PERSON:
return { person: option.value as SearchPerson };
case SuggestionType.FILE_TYPE:
return { fileType: option.value as FileType };
case SuggestionType.CLIP:
return { clip: option.value as ClipSearchScores };
}
}