mirror of
https://github.com/immich-app/immich.git
synced 2025-01-01 08:31:59 +00:00
fix(server): search duplicates of the same asset type (#9747)
* search by type * make sql --------- Co-authored-by: Alex <alex.tran1502@gmail.com>
This commit is contained in:
parent
50f9b2d44e
commit
e7c8501930
5 changed files with 16 additions and 6 deletions
|
@ -155,8 +155,9 @@ export interface FaceEmbeddingSearch extends SearchEmbeddingOptions {
|
||||||
export interface AssetDuplicateSearch {
|
export interface AssetDuplicateSearch {
|
||||||
assetId: string;
|
assetId: string;
|
||||||
embedding: Embedding;
|
embedding: Embedding;
|
||||||
userIds: string[];
|
|
||||||
maxDistance?: number;
|
maxDistance?: number;
|
||||||
|
type: AssetType;
|
||||||
|
userIds: string[];
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface FaceSearchResult {
|
export interface FaceSearchResult {
|
||||||
|
|
|
@ -204,6 +204,7 @@ WITH
|
||||||
"asset"."ownerId" IN ($2)
|
"asset"."ownerId" IN ($2)
|
||||||
AND "asset"."id" != $3
|
AND "asset"."id" != $3
|
||||||
AND "asset"."isVisible" = $4
|
AND "asset"."isVisible" = $4
|
||||||
|
AND "asset"."type" = $5
|
||||||
)
|
)
|
||||||
AND ("asset"."deletedAt" IS NULL)
|
AND ("asset"."deletedAt" IS NULL)
|
||||||
ORDER BY
|
ORDER BY
|
||||||
|
@ -216,7 +217,7 @@ SELECT
|
||||||
FROM
|
FROM
|
||||||
"cte" "res"
|
"cte" "res"
|
||||||
WHERE
|
WHERE
|
||||||
res.distance <= $5
|
res.distance <= $6
|
||||||
|
|
||||||
-- SearchRepository.searchFaces
|
-- SearchRepository.searchFaces
|
||||||
START TRANSACTION
|
START TRANSACTION
|
||||||
|
|
|
@ -160,6 +160,7 @@ export class SearchRepository implements ISearchRepository {
|
||||||
assetId,
|
assetId,
|
||||||
embedding,
|
embedding,
|
||||||
maxDistance,
|
maxDistance,
|
||||||
|
type,
|
||||||
userIds,
|
userIds,
|
||||||
}: AssetDuplicateSearch): Promise<AssetDuplicateResult[]> {
|
}: AssetDuplicateSearch): Promise<AssetDuplicateResult[]> {
|
||||||
const cte = this.assetRepository.createQueryBuilder('asset');
|
const cte = this.assetRepository.createQueryBuilder('asset');
|
||||||
|
@ -171,18 +172,22 @@ export class SearchRepository implements ISearchRepository {
|
||||||
.where('asset.ownerId IN (:...userIds )')
|
.where('asset.ownerId IN (:...userIds )')
|
||||||
.andWhere('asset.id != :assetId')
|
.andWhere('asset.id != :assetId')
|
||||||
.andWhere('asset.isVisible = :isVisible')
|
.andWhere('asset.isVisible = :isVisible')
|
||||||
|
.andWhere('asset.type = :type')
|
||||||
.orderBy('search.embedding <=> :embedding')
|
.orderBy('search.embedding <=> :embedding')
|
||||||
.limit(64)
|
.limit(64)
|
||||||
.setParameters({ assetId, embedding: asVector(embedding), isVisible: true, userIds });
|
.setParameters({ assetId, embedding: asVector(embedding), isVisible: true, type, userIds });
|
||||||
|
|
||||||
const builder = this.assetRepository.manager
|
const builder = this.assetRepository.manager
|
||||||
.createQueryBuilder()
|
.createQueryBuilder()
|
||||||
.addCommonTableExpression(cte, 'cte')
|
.addCommonTableExpression(cte, 'cte')
|
||||||
.from('cte', 'res')
|
.from('cte', 'res')
|
||||||
.select('res.*')
|
.select('res.*');
|
||||||
.where('res.distance <= :maxDistance', { maxDistance });
|
|
||||||
|
|
||||||
return builder.getRawMany() as any as Promise<AssetDuplicateResult[]>;
|
if (maxDistance) {
|
||||||
|
builder.where('res.distance <= :maxDistance', { maxDistance });
|
||||||
|
}
|
||||||
|
|
||||||
|
return builder.getRawMany() as Promise<AssetDuplicateResult[]>;
|
||||||
}
|
}
|
||||||
|
|
||||||
@GenerateSql({
|
@GenerateSql({
|
||||||
|
|
|
@ -215,6 +215,7 @@ describe(SearchService.name, () => {
|
||||||
assetId: assetStub.hasEmbedding.id,
|
assetId: assetStub.hasEmbedding.id,
|
||||||
embedding: assetStub.hasEmbedding.smartSearch!.embedding,
|
embedding: assetStub.hasEmbedding.smartSearch!.embedding,
|
||||||
maxDistance: 0.03,
|
maxDistance: 0.03,
|
||||||
|
type: assetStub.hasEmbedding.type,
|
||||||
userIds: [assetStub.hasEmbedding.ownerId],
|
userIds: [assetStub.hasEmbedding.ownerId],
|
||||||
});
|
});
|
||||||
expect(assetMock.updateDuplicates).toHaveBeenCalledWith({
|
expect(assetMock.updateDuplicates).toHaveBeenCalledWith({
|
||||||
|
@ -240,6 +241,7 @@ describe(SearchService.name, () => {
|
||||||
assetId: assetStub.hasEmbedding.id,
|
assetId: assetStub.hasEmbedding.id,
|
||||||
embedding: assetStub.hasEmbedding.smartSearch!.embedding,
|
embedding: assetStub.hasEmbedding.smartSearch!.embedding,
|
||||||
maxDistance: 0.03,
|
maxDistance: 0.03,
|
||||||
|
type: assetStub.hasEmbedding.type,
|
||||||
userIds: [assetStub.hasEmbedding.ownerId],
|
userIds: [assetStub.hasEmbedding.ownerId],
|
||||||
});
|
});
|
||||||
expect(assetMock.updateDuplicates).toHaveBeenCalledWith({
|
expect(assetMock.updateDuplicates).toHaveBeenCalledWith({
|
||||||
|
|
|
@ -94,6 +94,7 @@ export class DuplicateService {
|
||||||
assetId: asset.id,
|
assetId: asset.id,
|
||||||
embedding: asset.smartSearch.embedding,
|
embedding: asset.smartSearch.embedding,
|
||||||
maxDistance: machineLearning.duplicateDetection.maxDistance,
|
maxDistance: machineLearning.duplicateDetection.maxDistance,
|
||||||
|
type: asset.type,
|
||||||
userIds: [asset.ownerId],
|
userIds: [asset.ownerId],
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
Loading…
Reference in a new issue