feat: use pgvecto.rs (#3605)
This commit is contained in:
@@ -1,3 +1,4 @@
|
||||
import { SearchExploreItem } from '@app/domain';
|
||||
import { AssetEntity, AssetJobStatusEntity, AssetType, ExifEntity } from '@app/infra/entities';
|
||||
import { FindOptionsRelations } from 'typeorm';
|
||||
import { Paginated, PaginationOptions } from '../domain.util';
|
||||
@@ -105,8 +106,7 @@ export enum TimeBucketSize {
|
||||
MONTH = 'MONTH',
|
||||
}
|
||||
|
||||
export interface TimeBucketOptions {
|
||||
size: TimeBucketSize;
|
||||
export interface AssetBuilderOptions {
|
||||
isArchived?: boolean;
|
||||
isFavorite?: boolean;
|
||||
isTrashed?: boolean;
|
||||
@@ -114,6 +114,12 @@ export interface TimeBucketOptions {
|
||||
personId?: string;
|
||||
userIds?: string[];
|
||||
withStacked?: boolean;
|
||||
exifInfo?: boolean;
|
||||
assetType?: AssetType;
|
||||
}
|
||||
|
||||
export interface TimeBucketOptions extends AssetBuilderOptions {
|
||||
size: TimeBucketSize;
|
||||
}
|
||||
|
||||
export interface TimeBucketItem {
|
||||
@@ -142,6 +148,21 @@ export interface MonthDay {
|
||||
month: number;
|
||||
}
|
||||
|
||||
export interface AssetExploreFieldOptions {
|
||||
maxFields: number;
|
||||
minAssetsPerField: number;
|
||||
}
|
||||
|
||||
export interface AssetExploreOptions extends AssetExploreFieldOptions {
|
||||
relation: keyof AssetEntity;
|
||||
relatedField: string;
|
||||
unnest?: boolean;
|
||||
}
|
||||
|
||||
export interface MetadataSearchOptions {
|
||||
numResults: number;
|
||||
}
|
||||
|
||||
export const IAssetRepository = 'IAssetRepository';
|
||||
|
||||
export interface IAssetRepository {
|
||||
@@ -152,7 +173,7 @@ export interface IAssetRepository {
|
||||
getByChecksum(userId: string, checksum: Buffer): Promise<AssetEntity | null>;
|
||||
getByAlbumId(pagination: PaginationOptions, albumId: string): Paginated<AssetEntity>;
|
||||
getByUserId(pagination: PaginationOptions, userId: string, options?: AssetSearchOptions): Paginated<AssetEntity>;
|
||||
getById(id: string): Promise<AssetEntity | null>;
|
||||
getById(id: string, relations?: FindOptionsRelations<AssetEntity>): Promise<AssetEntity | null>;
|
||||
getWithout(pagination: PaginationOptions, property: WithoutProperty): Paginated<AssetEntity>;
|
||||
getWith(pagination: PaginationOptions, property: WithProperty, libraryId?: string): Paginated<AssetEntity>;
|
||||
getRandom(userId: string, count: number): Promise<AssetEntity[]>;
|
||||
@@ -176,4 +197,7 @@ export interface IAssetRepository {
|
||||
upsertExif(exif: Partial<ExifEntity>): Promise<void>;
|
||||
upsertJobStatus(jobStatus: Partial<AssetJobStatusEntity>): Promise<void>;
|
||||
search(options: AssetSearchOptions): Promise<AssetEntity[]>;
|
||||
getAssetIdByCity(userId: string, options: AssetExploreFieldOptions): Promise<SearchExploreItem<string>>;
|
||||
getAssetIdByTag(userId: string, options: AssetExploreFieldOptions): Promise<SearchExploreItem<string>>;
|
||||
searchMetadata(query: string, userId: string, options: MetadataSearchOptions): Promise<AssetEntity[]>;
|
||||
}
|
||||
|
||||
@@ -2,9 +2,7 @@ import { JobName, QueueName } from '../job/job.constants';
|
||||
|
||||
import {
|
||||
IAssetDeletionJob,
|
||||
IAssetFaceJob,
|
||||
IBaseJob,
|
||||
IBulkEntityJob,
|
||||
IDeleteFilesJob,
|
||||
IEntityJob,
|
||||
ILibraryFileJob,
|
||||
@@ -96,18 +94,7 @@ export type JobItem =
|
||||
| { name: JobName.LIBRARY_REMOVE_OFFLINE; data: IEntityJob }
|
||||
| { name: JobName.LIBRARY_DELETE; data: IEntityJob }
|
||||
| { name: JobName.LIBRARY_QUEUE_SCAN_ALL; data: IBaseJob }
|
||||
| { name: JobName.LIBRARY_QUEUE_CLEANUP; data: IBaseJob }
|
||||
|
||||
// Search
|
||||
| { name: JobName.SEARCH_INDEX_ASSETS; data?: IBaseJob }
|
||||
| { name: JobName.SEARCH_INDEX_ASSET; data: IBulkEntityJob }
|
||||
| { name: JobName.SEARCH_INDEX_FACES; data?: IBaseJob }
|
||||
| { name: JobName.SEARCH_INDEX_FACE; data: IAssetFaceJob }
|
||||
| { name: JobName.SEARCH_INDEX_ALBUMS; data?: IBaseJob }
|
||||
| { name: JobName.SEARCH_INDEX_ALBUM; data: IBulkEntityJob }
|
||||
| { name: JobName.SEARCH_REMOVE_ASSET; data: IBulkEntityJob }
|
||||
| { name: JobName.SEARCH_REMOVE_ALBUM; data: IBulkEntityJob }
|
||||
| { name: JobName.SEARCH_REMOVE_FACE; data: IAssetFaceJob };
|
||||
| { name: JobName.LIBRARY_QUEUE_CLEANUP; data: IBaseJob };
|
||||
|
||||
export type JobHandler<T = any> = (data: T) => boolean | Promise<boolean>;
|
||||
export type JobItemHandler = (item: JobItem) => Promise<void>;
|
||||
|
||||
@@ -41,9 +41,7 @@ export interface IPersonRepository {
|
||||
update(entity: Partial<PersonEntity>): Promise<PersonEntity>;
|
||||
delete(entity: PersonEntity): Promise<PersonEntity | null>;
|
||||
deleteAll(): Promise<number>;
|
||||
|
||||
getStatistics(personId: string): Promise<PersonStatistics>;
|
||||
|
||||
getAllFaces(): Promise<AssetFaceEntity[]>;
|
||||
getFacesByIds(ids: AssetFaceId[]): Promise<AssetFaceEntity[]>;
|
||||
getRandomFace(personId: string): Promise<AssetFaceEntity | null>;
|
||||
|
||||
@@ -1,20 +1,10 @@
|
||||
import { AlbumEntity, AssetEntity, AssetFaceEntity, AssetType } from '@app/infra/entities';
|
||||
|
||||
export enum SearchCollection {
|
||||
ASSETS = 'assets',
|
||||
ALBUMS = 'albums',
|
||||
FACES = 'faces',
|
||||
}
|
||||
import { AssetType } from '@app/infra/entities';
|
||||
|
||||
export enum SearchStrategy {
|
||||
CLIP = 'CLIP',
|
||||
TEXT = 'TEXT',
|
||||
}
|
||||
|
||||
export interface SearchFaceFilter {
|
||||
ownerId: string;
|
||||
}
|
||||
|
||||
export interface SearchFilter {
|
||||
id?: string;
|
||||
userId: string;
|
||||
@@ -55,43 +45,12 @@ export interface SearchFacet {
|
||||
}>;
|
||||
}
|
||||
|
||||
export type SearchExploreItemSet<T> = Array<{
|
||||
value: string;
|
||||
data: T;
|
||||
}>;
|
||||
|
||||
export interface SearchExploreItem<T> {
|
||||
fieldName: string;
|
||||
items: Array<{
|
||||
value: string;
|
||||
data: T;
|
||||
}>;
|
||||
}
|
||||
|
||||
export type OwnedFaceEntity = Pick<AssetFaceEntity, 'assetId' | 'personId' | 'embedding'> & {
|
||||
/** computed as assetId|personId */
|
||||
id: string;
|
||||
/** copied from asset.id */
|
||||
ownerId: string;
|
||||
};
|
||||
|
||||
export type SearchCollectionIndexStatus = Record<SearchCollection, boolean>;
|
||||
|
||||
export const ISearchRepository = 'ISearchRepository';
|
||||
|
||||
export interface ISearchRepository {
|
||||
setup(): Promise<void>;
|
||||
checkMigrationStatus(): Promise<SearchCollectionIndexStatus>;
|
||||
|
||||
importAlbums(items: AlbumEntity[], done: boolean): Promise<void>;
|
||||
importAssets(items: AssetEntity[], done: boolean): Promise<void>;
|
||||
importFaces(items: OwnedFaceEntity[], done: boolean): Promise<void>;
|
||||
|
||||
deleteAlbums(ids: string[]): Promise<void>;
|
||||
deleteAssets(ids: string[]): Promise<void>;
|
||||
deleteFaces(ids: string[]): Promise<void>;
|
||||
deleteAllFaces(): Promise<number>;
|
||||
updateCLIPField(num_dim: number): Promise<void>;
|
||||
|
||||
searchAlbums(query: string, filters: SearchFilter): Promise<SearchResult<AlbumEntity>>;
|
||||
searchAssets(query: string, filters: SearchFilter): Promise<SearchResult<AssetEntity>>;
|
||||
vectorSearch(query: number[], filters: SearchFilter): Promise<SearchResult<AssetEntity>>;
|
||||
searchFaces(query: number[], filters: SearchFaceFilter): Promise<SearchResult<AssetFaceEntity>>;
|
||||
|
||||
explore(userId: string): Promise<SearchExploreItem<AssetEntity>[]>;
|
||||
items: SearchExploreItemSet<T>;
|
||||
}
|
||||
|
||||
@@ -1,7 +1,19 @@
|
||||
import { SmartInfoEntity } from '@app/infra/entities';
|
||||
import { AssetEntity, AssetFaceEntity, SmartInfoEntity } from '@app/infra/entities';
|
||||
|
||||
export const ISmartInfoRepository = 'ISmartInfoRepository';
|
||||
|
||||
export interface ISmartInfoRepository {
|
||||
upsert(info: Partial<SmartInfoEntity>): Promise<void>;
|
||||
export type Embedding = number[];
|
||||
|
||||
export interface EmbeddingSearch {
|
||||
ownerId: string;
|
||||
embedding: Embedding;
|
||||
numResults: number;
|
||||
maxDistance?: number;
|
||||
}
|
||||
|
||||
export interface ISmartInfoRepository {
|
||||
init(modelName: string): Promise<void>;
|
||||
searchCLIP(search: EmbeddingSearch): Promise<AssetEntity[]>;
|
||||
searchFaces(search: EmbeddingSearch): Promise<AssetFaceEntity[]>;
|
||||
upsert(smartInfo: Partial<SmartInfoEntity>, embedding?: Embedding): Promise<void>;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user