1
0
Fork 0
mirror of https://github.com/immich-app/immich.git synced 2025-01-16 00:36:47 +01:00

feat: filter people when using smart search (#7521)

This commit is contained in:
Michel Heusschen 2024-02-29 22:14:48 +01:00 committed by GitHub
parent 15a4a4aaaa
commit c89d91e006
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
8 changed files with 29 additions and 14 deletions

Binary file not shown.

Binary file not shown.

Binary file not shown.

View file

@ -9539,6 +9539,12 @@
"page": {
"type": "number"
},
"personIds": {
"items": {
"type": "string"
},
"type": "array"
},
"query": {
"type": "string"
},

View file

@ -671,6 +671,7 @@ export type SmartSearchDto = {
make?: string;
model?: string;
page?: number;
personIds?: string[];
query: string;
size?: number;
state?: string;

View file

@ -122,6 +122,9 @@ class BaseSearchDto {
@QueryBoolean({ optional: true })
isNotInAlbum?: boolean;
@Optional()
personIds?: string[];
}
export class MetadataSearchDto extends BaseSearchDto {
@ -173,9 +176,6 @@ export class MetadataSearchDto extends BaseSearchDto {
@Optional()
@ApiProperty({ enumName: 'AssetOrder', enum: AssetOrder })
order?: AssetOrder;
@Optional()
personIds?: string[];
}
export class SmartSearchDto extends BaseSearchDto {

View file

@ -22,7 +22,7 @@ import {
import { ImmichLogger } from '@app/infra/logger';
import { Injectable } from '@nestjs/common';
import { InjectRepository } from '@nestjs/typeorm';
import { Repository } from 'typeorm';
import { Repository, SelectQueryBuilder } from 'typeorm';
import { vectorExt } from '../database.config';
import { DummyValue, GenerateSql } from '../infra.util';
import { asVector, isValidInteger, paginatedBuilder, searchAssetBuilder } from '../infra.utils';
@ -81,6 +81,14 @@ export class SearchRepository implements ISearchRepository {
});
}
private createPersonFilter(builder: SelectQueryBuilder<AssetFaceEntity>, personIds: string[]) {
return builder
.select(`${builder.alias}."assetId"`)
.where(`${builder.alias}."personId" IN (:...personIds)`, { personIds })
.groupBy(`${builder.alias}."assetId"`)
.having(`COUNT(DISTINCT ${builder.alias}."personId") = :personCount`, { personCount: personIds.length });
}
@GenerateSql({
params: [
{ page: 1, size: 100 },
@ -96,12 +104,21 @@ export class SearchRepository implements ISearchRepository {
})
async searchSmart(
pagination: SearchPaginationOptions,
{ embedding, userIds, ...options }: SmartSearchOptions,
{ embedding, userIds, personIds, ...options }: SmartSearchOptions,
): Paginated<AssetEntity> {
let results: PaginationResult<AssetEntity> = { items: [], hasNextPage: false };
await this.assetRepository.manager.transaction(async (manager) => {
let builder = manager.createQueryBuilder(AssetEntity, 'asset');
if (personIds?.length) {
const assetFaceBuilder = manager.createQueryBuilder(AssetFaceEntity, 'asset_face');
const cte = this.createPersonFilter(assetFaceBuilder, personIds);
builder
.addCommonTableExpression(cte, 'asset_face_ids')
.innerJoin('asset_face_ids', 'a', 'a."assetId" = asset.id');
}
builder = searchAssetBuilder(builder, options);
builder
.innerJoin('asset.smartSearch', 'search')

View file

@ -22,7 +22,6 @@
<script lang="ts">
import Button from '$lib/components/elements/buttons/button.svelte';
import { handleError } from '$lib/utils/handle-error';
import { AssetTypeEnum, type SmartSearchDto, type MetadataSearchDto } from '@immich/sdk';
import { createEventDispatcher } from 'svelte';
import { fly } from 'svelte/transition';
@ -83,14 +82,6 @@
};
const search = () => {
if (filter.context && filter.personIds.size > 0) {
handleError(
new Error('Context search does not support people filter'),
'Context search does not support people filter',
);
return;
}
let type: AssetTypeEnum | undefined = undefined;
if (filter.mediaType === MediaType.Image) {
type = AssetTypeEnum.Image;