no smart search cursor

This commit is contained in:
timonrieger
2026-07-25 18:33:45 +02:00
parent 8bcf23789d
commit 34c25b43fc
6 changed files with 6 additions and 38 deletions
+1 -18
View File
@@ -18,7 +18,6 @@ class SmartSearchDto {
this.country = const Optional.absent(),
this.createdAfter = const Optional.absent(),
this.createdBefore = const Optional.absent(),
this.cursor = const Optional.absent(),
this.filter = const Optional.absent(),
this.isEncoded = const Optional.absent(),
this.isFavorite = const Optional.absent(),
@@ -78,15 +77,6 @@ class SmartSearchDto {
///
Optional<DateTime?> createdBefore;
/// Cursor for the next page of results
///
/// Please note: This property should have been non-nullable! Since the specification file
/// does not include a default value (using the "default:" property), however, the generated
/// source code must fall back to having a nullable type.
/// Consider adding a "default:" property in the specification file to hide this note.
///
Optional<String?> cursor;
///
/// Please note: This property should have been non-nullable! Since the specification file
/// does not include a default value (using the "default:" property), however, the generated
@@ -316,7 +306,6 @@ class SmartSearchDto {
other.country == country &&
other.createdAfter == createdAfter &&
other.createdBefore == createdBefore &&
other.cursor == cursor &&
other.filter == filter &&
other.isEncoded == isEncoded &&
other.isFavorite == isFavorite &&
@@ -356,7 +345,6 @@ class SmartSearchDto {
(country == null ? 0 : country!.hashCode) +
(createdAfter == null ? 0 : createdAfter!.hashCode) +
(createdBefore == null ? 0 : createdBefore!.hashCode) +
(cursor == null ? 0 : cursor!.hashCode) +
(filter == null ? 0 : filter!.hashCode) +
(isEncoded == null ? 0 : isEncoded!.hashCode) +
(isFavorite == null ? 0 : isFavorite!.hashCode) +
@@ -389,7 +377,7 @@ class SmartSearchDto {
(withExif == null ? 0 : withExif!.hashCode);
@override
String toString() => 'SmartSearchDto[albumIds=$albumIds, city=$city, country=$country, createdAfter=$createdAfter, createdBefore=$createdBefore, cursor=$cursor, filter=$filter, isEncoded=$isEncoded, isFavorite=$isFavorite, isMotion=$isMotion, isNotInAlbum=$isNotInAlbum, isOffline=$isOffline, language=$language, lensModel=$lensModel, libraryId=$libraryId, make=$make, model=$model, ocr=$ocr, page=$page, personIds=$personIds, query=$query, queryAssetId=$queryAssetId, rating=$rating, size=$size, state=$state, tagIds=$tagIds, takenAfter=$takenAfter, takenBefore=$takenBefore, trashedAfter=$trashedAfter, trashedBefore=$trashedBefore, type=$type, updatedAfter=$updatedAfter, updatedBefore=$updatedBefore, visibility=$visibility, withDeleted=$withDeleted, withExif=$withExif]';
String toString() => 'SmartSearchDto[albumIds=$albumIds, city=$city, country=$country, createdAfter=$createdAfter, createdBefore=$createdBefore, filter=$filter, isEncoded=$isEncoded, isFavorite=$isFavorite, isMotion=$isMotion, isNotInAlbum=$isNotInAlbum, isOffline=$isOffline, language=$language, lensModel=$lensModel, libraryId=$libraryId, make=$make, model=$model, ocr=$ocr, page=$page, personIds=$personIds, query=$query, queryAssetId=$queryAssetId, rating=$rating, size=$size, state=$state, tagIds=$tagIds, takenAfter=$takenAfter, takenBefore=$takenBefore, trashedAfter=$trashedAfter, trashedBefore=$trashedBefore, type=$type, updatedAfter=$updatedAfter, updatedBefore=$updatedBefore, visibility=$visibility, withDeleted=$withDeleted, withExif=$withExif]';
Map<String, dynamic> toJson() {
final json = <String, dynamic>{};
@@ -417,10 +405,6 @@ class SmartSearchDto {
? value.millisecondsSinceEpoch
: value.toUtc().toIso8601String());
}
if (this.cursor.isPresent) {
final value = this.cursor.value;
json[r'cursor'] = value;
}
if (this.filter.isPresent) {
final value = this.filter.value;
json[r'filter'] = value;
@@ -572,7 +556,6 @@ class SmartSearchDto {
country: json.containsKey(r'country') ? Optional.present(mapValueOfType<String>(json, r'country')) : const Optional.absent(),
createdAfter: json.containsKey(r'createdAfter') ? Optional.present(mapDateTime(json, r'createdAfter', r'/^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d(?::[0-5]\\d(?:\\.\\d+)?)?(?:Z|([+-](?:[01]\\d|2[0-3]):[0-5]\\d)))$/')) : const Optional.absent(),
createdBefore: json.containsKey(r'createdBefore') ? Optional.present(mapDateTime(json, r'createdBefore', r'/^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d(?::[0-5]\\d(?:\\.\\d+)?)?(?:Z|([+-](?:[01]\\d|2[0-3]):[0-5]\\d)))$/')) : const Optional.absent(),
cursor: json.containsKey(r'cursor') ? Optional.present(mapValueOfType<String>(json, r'cursor')) : const Optional.absent(),
filter: json.containsKey(r'filter') ? Optional.present(SearchFilter.fromJson(json[r'filter'])) : const Optional.absent(),
isEncoded: json.containsKey(r'isEncoded') ? Optional.present(mapValueOfType<bool>(json, r'isEncoded')) : const Optional.absent(),
isFavorite: json.containsKey(r'isFavorite') ? Optional.present(mapValueOfType<bool>(json, r'isFavorite')) : const Optional.absent(),
-11
View File
@@ -25456,17 +25456,6 @@
],
"x-immich-state": "Deprecated"
},
"cursor": {
"description": "Cursor for the next page of results",
"minLength": 1,
"type": "string",
"x-immich-history": [
{
"version": "v3.1.0",
"state": "Added"
}
]
},
"filter": {
"$ref": "#/components/schemas/SearchFilter",
"x-immich-history": [
-2
View File
@@ -1990,8 +1990,6 @@ export type SmartSearchDto = {
createdAfter?: string;
/** Filter by creation date (before) */
createdBefore?: string;
/** Cursor for the next page of results */
cursor?: string;
filter?: SearchFilter;
/** Filter by encoded status */
isEncoded?: boolean;
-1
View File
@@ -387,7 +387,6 @@ const SmartSearchSchema = withShapeExclusivity(
language: z.string().optional().describe('Search language code'),
page: z.int().min(1).optional().describe('Page number').meta(DEPRECATED_FLAT_FIELD),
filter: filterField,
cursor: cursorField,
}).strict(),
).meta({ id: 'SmartSearchDto' });
+1 -1
View File
@@ -244,7 +244,7 @@ describe(SearchService.name, () => {
mocks.machineLearning.encodeText.mockResolvedValue('[1, 2, 3]');
await sut.searchSmart(auth, { size: 100, filter: {}, query: 'test' });
expect(mocks.search.searchSmartV3).toHaveBeenCalledWith(
{ size: 100, offset: 0 },
{ size: 100 },
expect.objectContaining({ embedding: '[1, 2, 3]' }),
expect.objectContaining({ lockedOwnerId: expect.any(String) }),
);
+4 -5
View File
@@ -285,15 +285,14 @@ export class SearchService extends BaseService {
this.resolveEmbedding(auth, dto, machineLearning),
]);
const { offset } = decodeSearchCursor(dto.cursor);
const size = dto.size;
const { hasNextPage, items } = await this.searchRepository.searchSmartV3(
{ size, offset },
// no cursor until a rank-aware pagination strategy for smart search is decided
const { items } = await this.searchRepository.searchSmartV3(
{ size: dto.size },
{ filter, withExif: dto.withExif, embedding },
scope,
);
return this.mapResponse(items, { auth }, { nextCursor: hasNextPage ? encodeSearchCursor(offset + size) : null });
return this.mapResponse(items, { auth });
}
private async resolveSearchScopeV3(