2023-12-08 17:15:46 +01:00
|
|
|
import { AssetEntity } from '@app/infra/entities';
|
2023-08-25 06:15:03 +02:00
|
|
|
import { Inject, Injectable, Logger } from '@nestjs/common';
|
2023-05-28 03:56:17 +02:00
|
|
|
import { AssetResponseDto, mapAsset } from '../asset';
|
2023-03-03 03:47:08 +01:00
|
|
|
import { AuthUserDto } from '../auth';
|
2023-12-08 17:15:46 +01:00
|
|
|
import { PersonResponseDto } from '../person';
|
2023-03-18 14:44:42 +01:00
|
|
|
import {
|
2023-10-09 16:25:03 +02:00
|
|
|
IAssetRepository,
|
|
|
|
IMachineLearningRepository,
|
|
|
|
IPersonRepository,
|
2023-12-08 17:15:46 +01:00
|
|
|
ISmartInfoRepository,
|
2023-10-09 16:25:03 +02:00
|
|
|
ISystemConfigRepository,
|
2023-03-18 14:44:42 +01:00
|
|
|
SearchExploreItem,
|
|
|
|
SearchStrategy,
|
2023-10-09 16:25:03 +02:00
|
|
|
} from '../repositories';
|
|
|
|
import { FeatureFlag, SystemConfigCore } from '../system-config';
|
2023-10-10 16:34:25 +02:00
|
|
|
import { SearchDto, SearchPeopleDto } from './dto';
|
2023-10-09 16:25:03 +02:00
|
|
|
import { SearchResponseDto } from './response-dto';
|
2023-03-18 14:44:42 +01:00
|
|
|
|
2023-03-03 03:47:08 +01:00
|
|
|
@Injectable()
|
|
|
|
export class SearchService {
|
|
|
|
private logger = new Logger(SearchService.name);
|
2023-08-25 06:15:03 +02:00
|
|
|
private configCore: SystemConfigCore;
|
2023-03-18 14:44:42 +01:00
|
|
|
|
2023-03-03 03:47:08 +01:00
|
|
|
constructor(
|
2023-12-08 17:15:46 +01:00
|
|
|
@Inject(ISystemConfigRepository) configRepository: ISystemConfigRepository,
|
2023-03-18 14:44:42 +01:00
|
|
|
@Inject(IMachineLearningRepository) private machineLearning: IMachineLearningRepository,
|
2023-09-27 22:46:46 +02:00
|
|
|
@Inject(IPersonRepository) private personRepository: IPersonRepository,
|
2023-12-08 17:15:46 +01:00
|
|
|
@Inject(ISmartInfoRepository) private smartInfoRepository: ISmartInfoRepository,
|
|
|
|
@Inject(IAssetRepository) private assetRepository: IAssetRepository,
|
2023-03-03 03:47:08 +01:00
|
|
|
) {
|
2023-10-09 02:51:03 +02:00
|
|
|
this.configCore = SystemConfigCore.create(configRepository);
|
2023-03-18 14:44:42 +01:00
|
|
|
}
|
|
|
|
|
2023-12-08 17:15:46 +01:00
|
|
|
async searchPerson(authUser: AuthUserDto, dto: SearchPeopleDto): Promise<PersonResponseDto[]> {
|
|
|
|
return this.personRepository.getByName(authUser.id, dto.name, { withHidden: dto.withHidden });
|
2023-03-03 03:47:08 +01:00
|
|
|
}
|
|
|
|
|
2023-05-28 03:56:17 +02:00
|
|
|
async getExploreData(authUser: AuthUserDto): Promise<SearchExploreItem<AssetResponseDto>[]> {
|
2023-08-25 06:15:03 +02:00
|
|
|
await this.configCore.requireFeature(FeatureFlag.SEARCH);
|
2023-12-08 17:15:46 +01:00
|
|
|
const options = { maxFields: 12, minAssetsPerField: 5 };
|
|
|
|
const results = await Promise.all([
|
|
|
|
this.assetRepository.getAssetIdByCity(authUser.id, options),
|
|
|
|
this.assetRepository.getAssetIdByTag(authUser.id, options),
|
|
|
|
]);
|
|
|
|
const assetIds = new Set<string>(results.flatMap((field) => field.items.map((item) => item.data)));
|
|
|
|
const assets = await this.assetRepository.getByIds(Array.from(assetIds));
|
|
|
|
const assetMap = new Map<string, AssetResponseDto>(assets.map((asset) => [asset.id, mapAsset(asset)]));
|
2023-05-30 19:55:06 +02:00
|
|
|
|
2023-05-28 03:56:17 +02:00
|
|
|
return results.map(({ fieldName, items }) => ({
|
|
|
|
fieldName,
|
2023-12-08 17:15:46 +01:00
|
|
|
items: items.map(({ value, data }) => ({ value, data: assetMap.get(data) as AssetResponseDto })),
|
2023-05-28 03:56:17 +02:00
|
|
|
}));
|
2023-03-05 21:44:31 +01:00
|
|
|
}
|
|
|
|
|
2023-03-03 03:47:08 +01:00
|
|
|
async search(authUser: AuthUserDto, dto: SearchDto): Promise<SearchResponseDto> {
|
2023-08-25 06:15:03 +02:00
|
|
|
const { machineLearning } = await this.configCore.getConfig();
|
2023-12-08 17:15:46 +01:00
|
|
|
const query = dto.q || dto.query;
|
|
|
|
if (!query) {
|
|
|
|
throw new Error('Missing query');
|
|
|
|
}
|
2023-08-29 15:58:00 +02:00
|
|
|
const hasClip = machineLearning.enabled && machineLearning.clip.enabled;
|
2023-12-08 17:15:46 +01:00
|
|
|
if (dto.clip && !hasClip) {
|
|
|
|
throw new Error('CLIP is not enabled');
|
|
|
|
}
|
|
|
|
const strategy = dto.clip ? SearchStrategy.CLIP : SearchStrategy.TEXT;
|
|
|
|
|
|
|
|
let assets: AssetEntity[] = [];
|
2023-03-18 14:44:42 +01:00
|
|
|
|
|
|
|
switch (strategy) {
|
|
|
|
case SearchStrategy.CLIP:
|
2023-12-08 17:15:46 +01:00
|
|
|
const embedding = await this.machineLearning.encodeText(
|
|
|
|
machineLearning.url,
|
|
|
|
{ text: query },
|
|
|
|
machineLearning.clip,
|
|
|
|
);
|
|
|
|
assets = await this.smartInfoRepository.searchCLIP({ ownerId: authUser.id, embedding, numResults: 100 });
|
2023-03-19 14:20:23 +01:00
|
|
|
break;
|
2023-03-18 22:30:48 +01:00
|
|
|
case SearchStrategy.TEXT:
|
2023-12-08 17:15:46 +01:00
|
|
|
assets = await this.assetRepository.searchMetadata(query, authUser.id, { numResults: 250 });
|
2023-03-18 22:30:48 +01:00
|
|
|
default:
|
|
|
|
break;
|
2023-03-18 14:44:42 +01:00
|
|
|
}
|
|
|
|
|
2023-03-03 03:47:08 +01:00
|
|
|
return {
|
2023-12-08 17:15:46 +01:00
|
|
|
albums: {
|
|
|
|
total: 0,
|
|
|
|
count: 0,
|
|
|
|
items: [],
|
|
|
|
facets: [],
|
|
|
|
},
|
2023-05-30 19:55:06 +02:00
|
|
|
assets: {
|
2023-12-08 17:15:46 +01:00
|
|
|
total: assets.length,
|
|
|
|
count: assets.length,
|
|
|
|
items: assets.map((asset) => mapAsset(asset)),
|
|
|
|
facets: [],
|
2023-05-30 19:55:06 +02:00
|
|
|
},
|
2023-03-03 03:47:08 +01:00
|
|
|
};
|
|
|
|
}
|
|
|
|
}
|