mirror of
https://github.com/immich-app/immich.git
synced 2024-11-15 09:59:00 -07:00
refactor: rename clip -> smart search (#6713)
This commit is contained in:
parent
e5a70329c9
commit
ae7f174948
@ -5,14 +5,14 @@ class SearchResultPageState {
|
|||||||
final bool isLoading;
|
final bool isLoading;
|
||||||
final bool isSuccess;
|
final bool isSuccess;
|
||||||
final bool isError;
|
final bool isError;
|
||||||
final bool isClip;
|
final bool isSmart;
|
||||||
final List<Asset> searchResult;
|
final List<Asset> searchResult;
|
||||||
|
|
||||||
SearchResultPageState({
|
SearchResultPageState({
|
||||||
required this.isLoading,
|
required this.isLoading,
|
||||||
required this.isSuccess,
|
required this.isSuccess,
|
||||||
required this.isError,
|
required this.isError,
|
||||||
required this.isClip,
|
required this.isSmart,
|
||||||
required this.searchResult,
|
required this.searchResult,
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -20,21 +20,21 @@ class SearchResultPageState {
|
|||||||
bool? isLoading,
|
bool? isLoading,
|
||||||
bool? isSuccess,
|
bool? isSuccess,
|
||||||
bool? isError,
|
bool? isError,
|
||||||
bool? isClip,
|
bool? isSmart,
|
||||||
List<Asset>? searchResult,
|
List<Asset>? searchResult,
|
||||||
}) {
|
}) {
|
||||||
return SearchResultPageState(
|
return SearchResultPageState(
|
||||||
isLoading: isLoading ?? this.isLoading,
|
isLoading: isLoading ?? this.isLoading,
|
||||||
isSuccess: isSuccess ?? this.isSuccess,
|
isSuccess: isSuccess ?? this.isSuccess,
|
||||||
isError: isError ?? this.isError,
|
isError: isError ?? this.isError,
|
||||||
isClip: isClip ?? this.isClip,
|
isSmart: isSmart ?? this.isSmart,
|
||||||
searchResult: searchResult ?? this.searchResult,
|
searchResult: searchResult ?? this.searchResult,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
String toString() {
|
String toString() {
|
||||||
return 'SearchresultPageState(isLoading: $isLoading, isSuccess: $isSuccess, isError: $isError, isClip: $isClip, searchResult: $searchResult)';
|
return 'SearchresultPageState(isLoading: $isLoading, isSuccess: $isSuccess, isError: $isError, isSmart: $isSmart, searchResult: $searchResult)';
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
@ -46,7 +46,7 @@ class SearchResultPageState {
|
|||||||
other.isLoading == isLoading &&
|
other.isLoading == isLoading &&
|
||||||
other.isSuccess == isSuccess &&
|
other.isSuccess == isSuccess &&
|
||||||
other.isError == isError &&
|
other.isError == isError &&
|
||||||
other.isClip == isClip &&
|
other.isSmart == isSmart &&
|
||||||
listEquals(other.searchResult, searchResult);
|
listEquals(other.searchResult, searchResult);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -55,7 +55,7 @@ class SearchResultPageState {
|
|||||||
return isLoading.hashCode ^
|
return isLoading.hashCode ^
|
||||||
isSuccess.hashCode ^
|
isSuccess.hashCode ^
|
||||||
isError.hashCode ^
|
isError.hashCode ^
|
||||||
isClip.hashCode ^
|
isSmart.hashCode ^
|
||||||
searchResult.hashCode;
|
searchResult.hashCode;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -14,13 +14,13 @@ class SearchResultPageNotifier extends StateNotifier<SearchResultPageState> {
|
|||||||
isError: false,
|
isError: false,
|
||||||
isLoading: true,
|
isLoading: true,
|
||||||
isSuccess: false,
|
isSuccess: false,
|
||||||
isClip: false,
|
isSmart: false,
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
|
|
||||||
final SearchService _searchService;
|
final SearchService _searchService;
|
||||||
|
|
||||||
Future<void> search(String searchTerm, {bool clipEnable = true}) async {
|
Future<void> search(String searchTerm, {bool smartSearch = true}) async {
|
||||||
state = state.copyWith(
|
state = state.copyWith(
|
||||||
searchResult: [],
|
searchResult: [],
|
||||||
isError: false,
|
isError: false,
|
||||||
@ -28,10 +28,8 @@ class SearchResultPageNotifier extends StateNotifier<SearchResultPageState> {
|
|||||||
isSuccess: false,
|
isSuccess: false,
|
||||||
);
|
);
|
||||||
|
|
||||||
List<Asset>? assets = await _searchService.searchAsset(
|
List<Asset>? assets =
|
||||||
searchTerm,
|
await _searchService.searchAsset(searchTerm, smartSearch: smartSearch);
|
||||||
clipEnable: clipEnable,
|
|
||||||
);
|
|
||||||
|
|
||||||
if (assets != null) {
|
if (assets != null) {
|
||||||
state = state.copyWith(
|
state = state.copyWith(
|
||||||
@ -39,7 +37,7 @@ class SearchResultPageNotifier extends StateNotifier<SearchResultPageState> {
|
|||||||
isError: false,
|
isError: false,
|
||||||
isLoading: false,
|
isLoading: false,
|
||||||
isSuccess: true,
|
isSuccess: true,
|
||||||
isClip: clipEnable,
|
isSmart: smartSearch,
|
||||||
);
|
);
|
||||||
} else {
|
} else {
|
||||||
state = state.copyWith(
|
state = state.copyWith(
|
||||||
@ -47,7 +45,7 @@ class SearchResultPageNotifier extends StateNotifier<SearchResultPageState> {
|
|||||||
isError: true,
|
isError: true,
|
||||||
isLoading: false,
|
isLoading: false,
|
||||||
isSuccess: false,
|
isSuccess: false,
|
||||||
isClip: clipEnable,
|
isSmart: smartSearch,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -63,7 +61,7 @@ final searchRenderListProvider = Provider((ref) {
|
|||||||
final result = ref.watch(searchResultPageProvider);
|
final result = ref.watch(searchResultPageProvider);
|
||||||
return ref.watch(
|
return ref.watch(
|
||||||
renderListProviderWithGrouping(
|
renderListProviderWithGrouping(
|
||||||
(result.searchResult, result.isClip ? GroupAssetsBy.none : null),
|
(result.searchResult, result.isSmart ? GroupAssetsBy.none : null),
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
@ -31,13 +31,13 @@ class SearchService {
|
|||||||
|
|
||||||
Future<List<Asset>?> searchAsset(
|
Future<List<Asset>?> searchAsset(
|
||||||
String searchTerm, {
|
String searchTerm, {
|
||||||
bool clipEnable = true,
|
bool smartSearch = true,
|
||||||
}) async {
|
}) async {
|
||||||
// TODO search in local DB: 1. when offline, 2. to find local assets
|
// TODO search in local DB: 1. when offline, 2. to find local assets
|
||||||
try {
|
try {
|
||||||
final SearchResponseDto? results = await _apiService.searchApi.search(
|
final SearchResponseDto? results = await _apiService.searchApi.search(
|
||||||
query: searchTerm,
|
query: searchTerm,
|
||||||
clip: clipEnable,
|
smart: smartSearch,
|
||||||
);
|
);
|
||||||
if (results == null) {
|
if (results == null) {
|
||||||
return null;
|
return null;
|
||||||
|
@ -11,17 +11,17 @@ import 'package:immich_mobile/shared/ui/asset_grid/multiselect_grid.dart';
|
|||||||
import 'package:immich_mobile/shared/ui/immich_loading_indicator.dart';
|
import 'package:immich_mobile/shared/ui/immich_loading_indicator.dart';
|
||||||
|
|
||||||
class SearchType {
|
class SearchType {
|
||||||
SearchType({required this.isClip, required this.searchTerm});
|
SearchType({required this.isSmart, required this.searchTerm});
|
||||||
|
|
||||||
final bool isClip;
|
final bool isSmart;
|
||||||
final String searchTerm;
|
final String searchTerm;
|
||||||
}
|
}
|
||||||
|
|
||||||
SearchType _getSearchType(String searchTerm) {
|
SearchType _getSearchType(String searchTerm) {
|
||||||
if (searchTerm.startsWith('m:')) {
|
if (searchTerm.startsWith('m:')) {
|
||||||
return SearchType(isClip: false, searchTerm: searchTerm.substring(2));
|
return SearchType(isSmart: false, searchTerm: searchTerm.substring(2));
|
||||||
} else {
|
} else {
|
||||||
return SearchType(isClip: true, searchTerm: searchTerm);
|
return SearchType(isSmart: true, searchTerm: searchTerm);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -52,7 +52,7 @@ class SearchResultPage extends HookConsumerWidget {
|
|||||||
Duration.zero,
|
Duration.zero,
|
||||||
() => ref
|
() => ref
|
||||||
.read(searchResultPageProvider.notifier)
|
.read(searchResultPageProvider.notifier)
|
||||||
.search(searchType.searchTerm, clipEnable: searchType.isClip),
|
.search(searchType.searchTerm, smartSearch: searchType.isSmart),
|
||||||
);
|
);
|
||||||
return () => searchFocusNode?.dispose();
|
return () => searchFocusNode?.dispose();
|
||||||
},
|
},
|
||||||
@ -67,7 +67,7 @@ class SearchResultPage extends HookConsumerWidget {
|
|||||||
var searchType = _getSearchType(newSearchTerm);
|
var searchType = _getSearchType(newSearchTerm);
|
||||||
return ref
|
return ref
|
||||||
.watch(searchResultPageProvider.notifier)
|
.watch(searchResultPageProvider.notifier)
|
||||||
.search(searchType.searchTerm, clipEnable: searchType.isClip);
|
.search(searchType.searchTerm, smartSearch: searchType.isSmart);
|
||||||
}
|
}
|
||||||
|
|
||||||
buildTextField() {
|
buildTextField() {
|
||||||
|
10
mobile/openapi/doc/SearchApi.md
generated
10
mobile/openapi/doc/SearchApi.md
generated
@ -66,7 +66,7 @@ This endpoint does not need any parameter.
|
|||||||
[[Back to top]](#) [[Back to API list]](../README.md#documentation-for-api-endpoints) [[Back to Model list]](../README.md#documentation-for-models) [[Back to README]](../README.md)
|
[[Back to top]](#) [[Back to API list]](../README.md#documentation-for-api-endpoints) [[Back to Model list]](../README.md#documentation-for-models) [[Back to README]](../README.md)
|
||||||
|
|
||||||
# **search**
|
# **search**
|
||||||
> SearchResponseDto search(clip, motion, q, query, recent, type, withArchived)
|
> SearchResponseDto search(clip, motion, q, query, recent, smart, type, withArchived)
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
@ -89,16 +89,17 @@ import 'package:openapi/api.dart';
|
|||||||
//defaultApiClient.getAuthentication<HttpBearerAuth>('bearer').setAccessToken(yourTokenGeneratorFunction);
|
//defaultApiClient.getAuthentication<HttpBearerAuth>('bearer').setAccessToken(yourTokenGeneratorFunction);
|
||||||
|
|
||||||
final api_instance = SearchApi();
|
final api_instance = SearchApi();
|
||||||
final clip = true; // bool |
|
final clip = true; // bool | @deprecated
|
||||||
final motion = true; // bool |
|
final motion = true; // bool |
|
||||||
final q = q_example; // String |
|
final q = q_example; // String |
|
||||||
final query = query_example; // String |
|
final query = query_example; // String |
|
||||||
final recent = true; // bool |
|
final recent = true; // bool |
|
||||||
|
final smart = true; // bool |
|
||||||
final type = type_example; // String |
|
final type = type_example; // String |
|
||||||
final withArchived = true; // bool |
|
final withArchived = true; // bool |
|
||||||
|
|
||||||
try {
|
try {
|
||||||
final result = api_instance.search(clip, motion, q, query, recent, type, withArchived);
|
final result = api_instance.search(clip, motion, q, query, recent, smart, type, withArchived);
|
||||||
print(result);
|
print(result);
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
print('Exception when calling SearchApi->search: $e\n');
|
print('Exception when calling SearchApi->search: $e\n');
|
||||||
@ -109,11 +110,12 @@ try {
|
|||||||
|
|
||||||
Name | Type | Description | Notes
|
Name | Type | Description | Notes
|
||||||
------------- | ------------- | ------------- | -------------
|
------------- | ------------- | ------------- | -------------
|
||||||
**clip** | **bool**| | [optional]
|
**clip** | **bool**| @deprecated | [optional]
|
||||||
**motion** | **bool**| | [optional]
|
**motion** | **bool**| | [optional]
|
||||||
**q** | **String**| | [optional]
|
**q** | **String**| | [optional]
|
||||||
**query** | **String**| | [optional]
|
**query** | **String**| | [optional]
|
||||||
**recent** | **bool**| | [optional]
|
**recent** | **bool**| | [optional]
|
||||||
|
**smart** | **bool**| | [optional]
|
||||||
**type** | **String**| | [optional]
|
**type** | **String**| | [optional]
|
||||||
**withArchived** | **bool**| | [optional]
|
**withArchived** | **bool**| | [optional]
|
||||||
|
|
||||||
|
2
mobile/openapi/doc/ServerFeaturesDto.md
generated
2
mobile/openapi/doc/ServerFeaturesDto.md
generated
@ -8,7 +8,6 @@ import 'package:openapi/api.dart';
|
|||||||
## Properties
|
## Properties
|
||||||
Name | Type | Description | Notes
|
Name | Type | Description | Notes
|
||||||
------------ | ------------- | ------------- | -------------
|
------------ | ------------- | ------------- | -------------
|
||||||
**clipEncode** | **bool** | |
|
|
||||||
**configFile** | **bool** | |
|
**configFile** | **bool** | |
|
||||||
**facialRecognition** | **bool** | |
|
**facialRecognition** | **bool** | |
|
||||||
**map** | **bool** | |
|
**map** | **bool** | |
|
||||||
@ -18,6 +17,7 @@ Name | Type | Description | Notes
|
|||||||
**reverseGeocoding** | **bool** | |
|
**reverseGeocoding** | **bool** | |
|
||||||
**search** | **bool** | |
|
**search** | **bool** | |
|
||||||
**sidecar** | **bool** | |
|
**sidecar** | **bool** | |
|
||||||
|
**smartSearch** | **bool** | |
|
||||||
**trash** | **bool** | |
|
**trash** | **bool** | |
|
||||||
|
|
||||||
[[Back to Model list]](../README.md#documentation-for-models) [[Back to API list]](../README.md#documentation-for-api-endpoints) [[Back to README]](../README.md)
|
[[Back to Model list]](../README.md#documentation-for-models) [[Back to API list]](../README.md#documentation-for-api-endpoints) [[Back to README]](../README.md)
|
||||||
|
15
mobile/openapi/lib/api/search_api.dart
generated
15
mobile/openapi/lib/api/search_api.dart
generated
@ -64,6 +64,7 @@ class SearchApi {
|
|||||||
/// Parameters:
|
/// Parameters:
|
||||||
///
|
///
|
||||||
/// * [bool] clip:
|
/// * [bool] clip:
|
||||||
|
/// @deprecated
|
||||||
///
|
///
|
||||||
/// * [bool] motion:
|
/// * [bool] motion:
|
||||||
///
|
///
|
||||||
@ -73,10 +74,12 @@ class SearchApi {
|
|||||||
///
|
///
|
||||||
/// * [bool] recent:
|
/// * [bool] recent:
|
||||||
///
|
///
|
||||||
|
/// * [bool] smart:
|
||||||
|
///
|
||||||
/// * [String] type:
|
/// * [String] type:
|
||||||
///
|
///
|
||||||
/// * [bool] withArchived:
|
/// * [bool] withArchived:
|
||||||
Future<Response> searchWithHttpInfo({ bool? clip, bool? motion, String? q, String? query, bool? recent, String? type, bool? withArchived, }) async {
|
Future<Response> searchWithHttpInfo({ bool? clip, bool? motion, String? q, String? query, bool? recent, bool? smart, String? type, bool? withArchived, }) async {
|
||||||
// ignore: prefer_const_declarations
|
// ignore: prefer_const_declarations
|
||||||
final path = r'/search';
|
final path = r'/search';
|
||||||
|
|
||||||
@ -102,6 +105,9 @@ class SearchApi {
|
|||||||
if (recent != null) {
|
if (recent != null) {
|
||||||
queryParams.addAll(_queryParams('', 'recent', recent));
|
queryParams.addAll(_queryParams('', 'recent', recent));
|
||||||
}
|
}
|
||||||
|
if (smart != null) {
|
||||||
|
queryParams.addAll(_queryParams('', 'smart', smart));
|
||||||
|
}
|
||||||
if (type != null) {
|
if (type != null) {
|
||||||
queryParams.addAll(_queryParams('', 'type', type));
|
queryParams.addAll(_queryParams('', 'type', type));
|
||||||
}
|
}
|
||||||
@ -126,6 +132,7 @@ class SearchApi {
|
|||||||
/// Parameters:
|
/// Parameters:
|
||||||
///
|
///
|
||||||
/// * [bool] clip:
|
/// * [bool] clip:
|
||||||
|
/// @deprecated
|
||||||
///
|
///
|
||||||
/// * [bool] motion:
|
/// * [bool] motion:
|
||||||
///
|
///
|
||||||
@ -135,11 +142,13 @@ class SearchApi {
|
|||||||
///
|
///
|
||||||
/// * [bool] recent:
|
/// * [bool] recent:
|
||||||
///
|
///
|
||||||
|
/// * [bool] smart:
|
||||||
|
///
|
||||||
/// * [String] type:
|
/// * [String] type:
|
||||||
///
|
///
|
||||||
/// * [bool] withArchived:
|
/// * [bool] withArchived:
|
||||||
Future<SearchResponseDto?> search({ bool? clip, bool? motion, String? q, String? query, bool? recent, String? type, bool? withArchived, }) async {
|
Future<SearchResponseDto?> search({ bool? clip, bool? motion, String? q, String? query, bool? recent, bool? smart, String? type, bool? withArchived, }) async {
|
||||||
final response = await searchWithHttpInfo( clip: clip, motion: motion, q: q, query: query, recent: recent, type: type, withArchived: withArchived, );
|
final response = await searchWithHttpInfo( clip: clip, motion: motion, q: q, query: query, recent: recent, smart: smart, type: type, withArchived: withArchived, );
|
||||||
if (response.statusCode >= HttpStatus.badRequest) {
|
if (response.statusCode >= HttpStatus.badRequest) {
|
||||||
throw ApiException(response.statusCode, await _decodeBodyBytes(response));
|
throw ApiException(response.statusCode, await _decodeBodyBytes(response));
|
||||||
}
|
}
|
||||||
|
18
mobile/openapi/lib/model/server_features_dto.dart
generated
18
mobile/openapi/lib/model/server_features_dto.dart
generated
@ -13,7 +13,6 @@ part of openapi.api;
|
|||||||
class ServerFeaturesDto {
|
class ServerFeaturesDto {
|
||||||
/// Returns a new [ServerFeaturesDto] instance.
|
/// Returns a new [ServerFeaturesDto] instance.
|
||||||
ServerFeaturesDto({
|
ServerFeaturesDto({
|
||||||
required this.clipEncode,
|
|
||||||
required this.configFile,
|
required this.configFile,
|
||||||
required this.facialRecognition,
|
required this.facialRecognition,
|
||||||
required this.map,
|
required this.map,
|
||||||
@ -23,11 +22,10 @@ class ServerFeaturesDto {
|
|||||||
required this.reverseGeocoding,
|
required this.reverseGeocoding,
|
||||||
required this.search,
|
required this.search,
|
||||||
required this.sidecar,
|
required this.sidecar,
|
||||||
|
required this.smartSearch,
|
||||||
required this.trash,
|
required this.trash,
|
||||||
});
|
});
|
||||||
|
|
||||||
bool clipEncode;
|
|
||||||
|
|
||||||
bool configFile;
|
bool configFile;
|
||||||
|
|
||||||
bool facialRecognition;
|
bool facialRecognition;
|
||||||
@ -46,11 +44,12 @@ class ServerFeaturesDto {
|
|||||||
|
|
||||||
bool sidecar;
|
bool sidecar;
|
||||||
|
|
||||||
|
bool smartSearch;
|
||||||
|
|
||||||
bool trash;
|
bool trash;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
bool operator ==(Object other) => identical(this, other) || other is ServerFeaturesDto &&
|
bool operator ==(Object other) => identical(this, other) || other is ServerFeaturesDto &&
|
||||||
other.clipEncode == clipEncode &&
|
|
||||||
other.configFile == configFile &&
|
other.configFile == configFile &&
|
||||||
other.facialRecognition == facialRecognition &&
|
other.facialRecognition == facialRecognition &&
|
||||||
other.map == map &&
|
other.map == map &&
|
||||||
@ -60,12 +59,12 @@ class ServerFeaturesDto {
|
|||||||
other.reverseGeocoding == reverseGeocoding &&
|
other.reverseGeocoding == reverseGeocoding &&
|
||||||
other.search == search &&
|
other.search == search &&
|
||||||
other.sidecar == sidecar &&
|
other.sidecar == sidecar &&
|
||||||
|
other.smartSearch == smartSearch &&
|
||||||
other.trash == trash;
|
other.trash == trash;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
int get hashCode =>
|
int get hashCode =>
|
||||||
// ignore: unnecessary_parenthesis
|
// ignore: unnecessary_parenthesis
|
||||||
(clipEncode.hashCode) +
|
|
||||||
(configFile.hashCode) +
|
(configFile.hashCode) +
|
||||||
(facialRecognition.hashCode) +
|
(facialRecognition.hashCode) +
|
||||||
(map.hashCode) +
|
(map.hashCode) +
|
||||||
@ -75,14 +74,14 @@ class ServerFeaturesDto {
|
|||||||
(reverseGeocoding.hashCode) +
|
(reverseGeocoding.hashCode) +
|
||||||
(search.hashCode) +
|
(search.hashCode) +
|
||||||
(sidecar.hashCode) +
|
(sidecar.hashCode) +
|
||||||
|
(smartSearch.hashCode) +
|
||||||
(trash.hashCode);
|
(trash.hashCode);
|
||||||
|
|
||||||
@override
|
@override
|
||||||
String toString() => 'ServerFeaturesDto[clipEncode=$clipEncode, configFile=$configFile, facialRecognition=$facialRecognition, map=$map, oauth=$oauth, oauthAutoLaunch=$oauthAutoLaunch, passwordLogin=$passwordLogin, reverseGeocoding=$reverseGeocoding, search=$search, sidecar=$sidecar, trash=$trash]';
|
String toString() => 'ServerFeaturesDto[configFile=$configFile, facialRecognition=$facialRecognition, map=$map, oauth=$oauth, oauthAutoLaunch=$oauthAutoLaunch, passwordLogin=$passwordLogin, reverseGeocoding=$reverseGeocoding, search=$search, sidecar=$sidecar, smartSearch=$smartSearch, trash=$trash]';
|
||||||
|
|
||||||
Map<String, dynamic> toJson() {
|
Map<String, dynamic> toJson() {
|
||||||
final json = <String, dynamic>{};
|
final json = <String, dynamic>{};
|
||||||
json[r'clipEncode'] = this.clipEncode;
|
|
||||||
json[r'configFile'] = this.configFile;
|
json[r'configFile'] = this.configFile;
|
||||||
json[r'facialRecognition'] = this.facialRecognition;
|
json[r'facialRecognition'] = this.facialRecognition;
|
||||||
json[r'map'] = this.map;
|
json[r'map'] = this.map;
|
||||||
@ -92,6 +91,7 @@ class ServerFeaturesDto {
|
|||||||
json[r'reverseGeocoding'] = this.reverseGeocoding;
|
json[r'reverseGeocoding'] = this.reverseGeocoding;
|
||||||
json[r'search'] = this.search;
|
json[r'search'] = this.search;
|
||||||
json[r'sidecar'] = this.sidecar;
|
json[r'sidecar'] = this.sidecar;
|
||||||
|
json[r'smartSearch'] = this.smartSearch;
|
||||||
json[r'trash'] = this.trash;
|
json[r'trash'] = this.trash;
|
||||||
return json;
|
return json;
|
||||||
}
|
}
|
||||||
@ -104,7 +104,6 @@ class ServerFeaturesDto {
|
|||||||
final json = value.cast<String, dynamic>();
|
final json = value.cast<String, dynamic>();
|
||||||
|
|
||||||
return ServerFeaturesDto(
|
return ServerFeaturesDto(
|
||||||
clipEncode: mapValueOfType<bool>(json, r'clipEncode')!,
|
|
||||||
configFile: mapValueOfType<bool>(json, r'configFile')!,
|
configFile: mapValueOfType<bool>(json, r'configFile')!,
|
||||||
facialRecognition: mapValueOfType<bool>(json, r'facialRecognition')!,
|
facialRecognition: mapValueOfType<bool>(json, r'facialRecognition')!,
|
||||||
map: mapValueOfType<bool>(json, r'map')!,
|
map: mapValueOfType<bool>(json, r'map')!,
|
||||||
@ -114,6 +113,7 @@ class ServerFeaturesDto {
|
|||||||
reverseGeocoding: mapValueOfType<bool>(json, r'reverseGeocoding')!,
|
reverseGeocoding: mapValueOfType<bool>(json, r'reverseGeocoding')!,
|
||||||
search: mapValueOfType<bool>(json, r'search')!,
|
search: mapValueOfType<bool>(json, r'search')!,
|
||||||
sidecar: mapValueOfType<bool>(json, r'sidecar')!,
|
sidecar: mapValueOfType<bool>(json, r'sidecar')!,
|
||||||
|
smartSearch: mapValueOfType<bool>(json, r'smartSearch')!,
|
||||||
trash: mapValueOfType<bool>(json, r'trash')!,
|
trash: mapValueOfType<bool>(json, r'trash')!,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@ -162,7 +162,6 @@ class ServerFeaturesDto {
|
|||||||
|
|
||||||
/// The list of required keys that must be present in a JSON.
|
/// The list of required keys that must be present in a JSON.
|
||||||
static const requiredKeys = <String>{
|
static const requiredKeys = <String>{
|
||||||
'clipEncode',
|
|
||||||
'configFile',
|
'configFile',
|
||||||
'facialRecognition',
|
'facialRecognition',
|
||||||
'map',
|
'map',
|
||||||
@ -172,6 +171,7 @@ class ServerFeaturesDto {
|
|||||||
'reverseGeocoding',
|
'reverseGeocoding',
|
||||||
'search',
|
'search',
|
||||||
'sidecar',
|
'sidecar',
|
||||||
|
'smartSearch',
|
||||||
'trash',
|
'trash',
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
2
mobile/openapi/test/search_api_test.dart
generated
2
mobile/openapi/test/search_api_test.dart
generated
@ -22,7 +22,7 @@ void main() {
|
|||||||
// TODO
|
// TODO
|
||||||
});
|
});
|
||||||
|
|
||||||
//Future<SearchResponseDto> search({ bool clip, bool motion, String q, String query, bool recent, String type, bool withArchived }) async
|
//Future<SearchResponseDto> search({ bool clip, bool motion, String q, String query, bool recent, bool smart, String type, bool withArchived }) async
|
||||||
test('test search', () async {
|
test('test search', () async {
|
||||||
// TODO
|
// TODO
|
||||||
});
|
});
|
||||||
|
10
mobile/openapi/test/server_features_dto_test.dart
generated
10
mobile/openapi/test/server_features_dto_test.dart
generated
@ -16,11 +16,6 @@ void main() {
|
|||||||
// final instance = ServerFeaturesDto();
|
// final instance = ServerFeaturesDto();
|
||||||
|
|
||||||
group('test ServerFeaturesDto', () {
|
group('test ServerFeaturesDto', () {
|
||||||
// bool clipEncode
|
|
||||||
test('to test the property `clipEncode`', () async {
|
|
||||||
// TODO
|
|
||||||
});
|
|
||||||
|
|
||||||
// bool configFile
|
// bool configFile
|
||||||
test('to test the property `configFile`', () async {
|
test('to test the property `configFile`', () async {
|
||||||
// TODO
|
// TODO
|
||||||
@ -66,6 +61,11 @@ void main() {
|
|||||||
// TODO
|
// TODO
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// bool smartSearch
|
||||||
|
test('to test the property `smartSearch`', () async {
|
||||||
|
// TODO
|
||||||
|
});
|
||||||
|
|
||||||
// bool trash
|
// bool trash
|
||||||
test('to test the property `trash`', () async {
|
test('to test the property `trash`', () async {
|
||||||
// TODO
|
// TODO
|
||||||
|
@ -4684,6 +4684,8 @@
|
|||||||
"name": "clip",
|
"name": "clip",
|
||||||
"required": false,
|
"required": false,
|
||||||
"in": "query",
|
"in": "query",
|
||||||
|
"description": "@deprecated",
|
||||||
|
"deprecated": true,
|
||||||
"schema": {
|
"schema": {
|
||||||
"type": "boolean"
|
"type": "boolean"
|
||||||
}
|
}
|
||||||
@ -4720,6 +4722,14 @@
|
|||||||
"type": "boolean"
|
"type": "boolean"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"name": "smart",
|
||||||
|
"required": false,
|
||||||
|
"in": "query",
|
||||||
|
"schema": {
|
||||||
|
"type": "boolean"
|
||||||
|
}
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"name": "type",
|
"name": "type",
|
||||||
"required": false,
|
"required": false,
|
||||||
@ -8919,9 +8929,6 @@
|
|||||||
},
|
},
|
||||||
"ServerFeaturesDto": {
|
"ServerFeaturesDto": {
|
||||||
"properties": {
|
"properties": {
|
||||||
"clipEncode": {
|
|
||||||
"type": "boolean"
|
|
||||||
},
|
|
||||||
"configFile": {
|
"configFile": {
|
||||||
"type": "boolean"
|
"type": "boolean"
|
||||||
},
|
},
|
||||||
@ -8949,12 +8956,14 @@
|
|||||||
"sidecar": {
|
"sidecar": {
|
||||||
"type": "boolean"
|
"type": "boolean"
|
||||||
},
|
},
|
||||||
|
"smartSearch": {
|
||||||
|
"type": "boolean"
|
||||||
|
},
|
||||||
"trash": {
|
"trash": {
|
||||||
"type": "boolean"
|
"type": "boolean"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"required": [
|
"required": [
|
||||||
"clipEncode",
|
|
||||||
"configFile",
|
"configFile",
|
||||||
"facialRecognition",
|
"facialRecognition",
|
||||||
"map",
|
"map",
|
||||||
@ -8964,6 +8973,7 @@
|
|||||||
"reverseGeocoding",
|
"reverseGeocoding",
|
||||||
"search",
|
"search",
|
||||||
"sidecar",
|
"sidecar",
|
||||||
|
"smartSearch",
|
||||||
"trash"
|
"trash"
|
||||||
],
|
],
|
||||||
"type": "object"
|
"type": "object"
|
||||||
|
41
open-api/typescript-sdk/client/api.ts
generated
41
open-api/typescript-sdk/client/api.ts
generated
@ -3069,12 +3069,6 @@ export interface ServerConfigDto {
|
|||||||
* @interface ServerFeaturesDto
|
* @interface ServerFeaturesDto
|
||||||
*/
|
*/
|
||||||
export interface ServerFeaturesDto {
|
export interface ServerFeaturesDto {
|
||||||
/**
|
|
||||||
*
|
|
||||||
* @type {boolean}
|
|
||||||
* @memberof ServerFeaturesDto
|
|
||||||
*/
|
|
||||||
'clipEncode': boolean;
|
|
||||||
/**
|
/**
|
||||||
*
|
*
|
||||||
* @type {boolean}
|
* @type {boolean}
|
||||||
@ -3129,6 +3123,12 @@ export interface ServerFeaturesDto {
|
|||||||
* @memberof ServerFeaturesDto
|
* @memberof ServerFeaturesDto
|
||||||
*/
|
*/
|
||||||
'sidecar': boolean;
|
'sidecar': boolean;
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
* @type {boolean}
|
||||||
|
* @memberof ServerFeaturesDto
|
||||||
|
*/
|
||||||
|
'smartSearch': boolean;
|
||||||
/**
|
/**
|
||||||
*
|
*
|
||||||
* @type {boolean}
|
* @type {boolean}
|
||||||
@ -15206,17 +15206,18 @@ export const SearchApiAxiosParamCreator = function (configuration?: Configuratio
|
|||||||
},
|
},
|
||||||
/**
|
/**
|
||||||
*
|
*
|
||||||
* @param {boolean} [clip]
|
* @param {boolean} [clip] @deprecated
|
||||||
* @param {boolean} [motion]
|
* @param {boolean} [motion]
|
||||||
* @param {string} [q]
|
* @param {string} [q]
|
||||||
* @param {string} [query]
|
* @param {string} [query]
|
||||||
* @param {boolean} [recent]
|
* @param {boolean} [recent]
|
||||||
|
* @param {boolean} [smart]
|
||||||
* @param {SearchTypeEnum} [type]
|
* @param {SearchTypeEnum} [type]
|
||||||
* @param {boolean} [withArchived]
|
* @param {boolean} [withArchived]
|
||||||
* @param {*} [options] Override http request option.
|
* @param {*} [options] Override http request option.
|
||||||
* @throws {RequiredError}
|
* @throws {RequiredError}
|
||||||
*/
|
*/
|
||||||
search: async (clip?: boolean, motion?: boolean, q?: string, query?: string, recent?: boolean, type?: SearchTypeEnum, withArchived?: boolean, options: RawAxiosRequestConfig = {}): Promise<RequestArgs> => {
|
search: async (clip?: boolean, motion?: boolean, q?: string, query?: string, recent?: boolean, smart?: boolean, type?: SearchTypeEnum, withArchived?: boolean, options: RawAxiosRequestConfig = {}): Promise<RequestArgs> => {
|
||||||
const localVarPath = `/search`;
|
const localVarPath = `/search`;
|
||||||
// use dummy base URL string because the URL constructor only accepts absolute URLs.
|
// use dummy base URL string because the URL constructor only accepts absolute URLs.
|
||||||
const localVarUrlObj = new URL(localVarPath, DUMMY_BASE_URL);
|
const localVarUrlObj = new URL(localVarPath, DUMMY_BASE_URL);
|
||||||
@ -15258,6 +15259,10 @@ export const SearchApiAxiosParamCreator = function (configuration?: Configuratio
|
|||||||
localVarQueryParameter['recent'] = recent;
|
localVarQueryParameter['recent'] = recent;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (smart !== undefined) {
|
||||||
|
localVarQueryParameter['smart'] = smart;
|
||||||
|
}
|
||||||
|
|
||||||
if (type !== undefined) {
|
if (type !== undefined) {
|
||||||
localVarQueryParameter['type'] = type;
|
localVarQueryParameter['type'] = type;
|
||||||
}
|
}
|
||||||
@ -15350,18 +15355,19 @@ export const SearchApiFp = function(configuration?: Configuration) {
|
|||||||
},
|
},
|
||||||
/**
|
/**
|
||||||
*
|
*
|
||||||
* @param {boolean} [clip]
|
* @param {boolean} [clip] @deprecated
|
||||||
* @param {boolean} [motion]
|
* @param {boolean} [motion]
|
||||||
* @param {string} [q]
|
* @param {string} [q]
|
||||||
* @param {string} [query]
|
* @param {string} [query]
|
||||||
* @param {boolean} [recent]
|
* @param {boolean} [recent]
|
||||||
|
* @param {boolean} [smart]
|
||||||
* @param {SearchTypeEnum} [type]
|
* @param {SearchTypeEnum} [type]
|
||||||
* @param {boolean} [withArchived]
|
* @param {boolean} [withArchived]
|
||||||
* @param {*} [options] Override http request option.
|
* @param {*} [options] Override http request option.
|
||||||
* @throws {RequiredError}
|
* @throws {RequiredError}
|
||||||
*/
|
*/
|
||||||
async search(clip?: boolean, motion?: boolean, q?: string, query?: string, recent?: boolean, type?: SearchTypeEnum, withArchived?: boolean, options?: RawAxiosRequestConfig): Promise<(axios?: AxiosInstance, basePath?: string) => AxiosPromise<SearchResponseDto>> {
|
async search(clip?: boolean, motion?: boolean, q?: string, query?: string, recent?: boolean, smart?: boolean, type?: SearchTypeEnum, withArchived?: boolean, options?: RawAxiosRequestConfig): Promise<(axios?: AxiosInstance, basePath?: string) => AxiosPromise<SearchResponseDto>> {
|
||||||
const localVarAxiosArgs = await localVarAxiosParamCreator.search(clip, motion, q, query, recent, type, withArchived, options);
|
const localVarAxiosArgs = await localVarAxiosParamCreator.search(clip, motion, q, query, recent, smart, type, withArchived, options);
|
||||||
const index = configuration?.serverIndex ?? 0;
|
const index = configuration?.serverIndex ?? 0;
|
||||||
const operationBasePath = operationServerMap['SearchApi.search']?.[index]?.url;
|
const operationBasePath = operationServerMap['SearchApi.search']?.[index]?.url;
|
||||||
return (axios, basePath) => createRequestFunction(localVarAxiosArgs, globalAxios, BASE_PATH, configuration)(axios, operationBasePath || basePath);
|
return (axios, basePath) => createRequestFunction(localVarAxiosArgs, globalAxios, BASE_PATH, configuration)(axios, operationBasePath || basePath);
|
||||||
@ -15404,7 +15410,7 @@ export const SearchApiFactory = function (configuration?: Configuration, basePat
|
|||||||
* @throws {RequiredError}
|
* @throws {RequiredError}
|
||||||
*/
|
*/
|
||||||
search(requestParameters: SearchApiSearchRequest = {}, options?: RawAxiosRequestConfig): AxiosPromise<SearchResponseDto> {
|
search(requestParameters: SearchApiSearchRequest = {}, options?: RawAxiosRequestConfig): AxiosPromise<SearchResponseDto> {
|
||||||
return localVarFp.search(requestParameters.clip, requestParameters.motion, requestParameters.q, requestParameters.query, requestParameters.recent, requestParameters.type, requestParameters.withArchived, options).then((request) => request(axios, basePath));
|
return localVarFp.search(requestParameters.clip, requestParameters.motion, requestParameters.q, requestParameters.query, requestParameters.recent, requestParameters.smart, requestParameters.type, requestParameters.withArchived, options).then((request) => request(axios, basePath));
|
||||||
},
|
},
|
||||||
/**
|
/**
|
||||||
*
|
*
|
||||||
@ -15425,7 +15431,7 @@ export const SearchApiFactory = function (configuration?: Configuration, basePat
|
|||||||
*/
|
*/
|
||||||
export interface SearchApiSearchRequest {
|
export interface SearchApiSearchRequest {
|
||||||
/**
|
/**
|
||||||
*
|
* @deprecated
|
||||||
* @type {boolean}
|
* @type {boolean}
|
||||||
* @memberof SearchApiSearch
|
* @memberof SearchApiSearch
|
||||||
*/
|
*/
|
||||||
@ -15459,6 +15465,13 @@ export interface SearchApiSearchRequest {
|
|||||||
*/
|
*/
|
||||||
readonly recent?: boolean
|
readonly recent?: boolean
|
||||||
|
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
* @type {boolean}
|
||||||
|
* @memberof SearchApiSearch
|
||||||
|
*/
|
||||||
|
readonly smart?: boolean
|
||||||
|
|
||||||
/**
|
/**
|
||||||
*
|
*
|
||||||
* @type {'IMAGE' | 'VIDEO' | 'AUDIO' | 'OTHER'}
|
* @type {'IMAGE' | 'VIDEO' | 'AUDIO' | 'OTHER'}
|
||||||
@ -15520,7 +15533,7 @@ export class SearchApi extends BaseAPI {
|
|||||||
* @memberof SearchApi
|
* @memberof SearchApi
|
||||||
*/
|
*/
|
||||||
public search(requestParameters: SearchApiSearchRequest = {}, options?: RawAxiosRequestConfig) {
|
public search(requestParameters: SearchApiSearchRequest = {}, options?: RawAxiosRequestConfig) {
|
||||||
return SearchApiFp(this.configuration).search(requestParameters.clip, requestParameters.motion, requestParameters.q, requestParameters.query, requestParameters.recent, requestParameters.type, requestParameters.withArchived, options).then((request) => request(this.axios, this.basePath));
|
return SearchApiFp(this.configuration).search(requestParameters.clip, requestParameters.motion, requestParameters.q, requestParameters.query, requestParameters.recent, requestParameters.smart, requestParameters.type, requestParameters.withArchived, options).then((request) => request(this.axios, this.basePath));
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -73,7 +73,7 @@ describe(`${ServerInfoController.name} (e2e)`, () => {
|
|||||||
const { status, body } = await request(server).get('/server-info/features');
|
const { status, body } = await request(server).get('/server-info/features');
|
||||||
expect(status).toBe(200);
|
expect(status).toBe(200);
|
||||||
expect(body).toEqual({
|
expect(body).toEqual({
|
||||||
clipEncode: true,
|
smartSearch: true,
|
||||||
configFile: false,
|
configFile: false,
|
||||||
facialRecognition: true,
|
facialRecognition: true,
|
||||||
map: true,
|
map: true,
|
||||||
|
@ -80,9 +80,9 @@ export enum JobName {
|
|||||||
DELETE_FILES = 'delete-files',
|
DELETE_FILES = 'delete-files',
|
||||||
CLEAN_OLD_AUDIT_LOGS = 'clean-old-audit-logs',
|
CLEAN_OLD_AUDIT_LOGS = 'clean-old-audit-logs',
|
||||||
|
|
||||||
// clip
|
// smart search
|
||||||
QUEUE_ENCODE_CLIP = 'queue-clip-encode',
|
QUEUE_SMART_SEARCH = 'queue-smart-search',
|
||||||
ENCODE_CLIP = 'clip-encode',
|
SMART_SEARCH = 'smart-search',
|
||||||
|
|
||||||
// XMP sidecars
|
// XMP sidecars
|
||||||
QUEUE_SIDECAR = 'queue-sidecar',
|
QUEUE_SIDECAR = 'queue-sidecar',
|
||||||
@ -135,9 +135,9 @@ export const JOBS_TO_QUEUE: Record<JobName, QueueName> = {
|
|||||||
[JobName.QUEUE_FACIAL_RECOGNITION]: QueueName.FACIAL_RECOGNITION,
|
[JobName.QUEUE_FACIAL_RECOGNITION]: QueueName.FACIAL_RECOGNITION,
|
||||||
[JobName.FACIAL_RECOGNITION]: QueueName.FACIAL_RECOGNITION,
|
[JobName.FACIAL_RECOGNITION]: QueueName.FACIAL_RECOGNITION,
|
||||||
|
|
||||||
// clip
|
// smart search
|
||||||
[JobName.QUEUE_ENCODE_CLIP]: QueueName.SMART_SEARCH,
|
[JobName.QUEUE_SMART_SEARCH]: QueueName.SMART_SEARCH,
|
||||||
[JobName.ENCODE_CLIP]: QueueName.SMART_SEARCH,
|
[JobName.SMART_SEARCH]: QueueName.SMART_SEARCH,
|
||||||
|
|
||||||
// XMP sidecars
|
// XMP sidecars
|
||||||
[JobName.QUEUE_SIDECAR]: QueueName.SIDECAR,
|
[JobName.QUEUE_SIDECAR]: QueueName.SIDECAR,
|
||||||
|
@ -159,12 +159,12 @@ describe(JobService.name, () => {
|
|||||||
expect(jobMock.queue).toHaveBeenCalledWith({ name: JobName.STORAGE_TEMPLATE_MIGRATION });
|
expect(jobMock.queue).toHaveBeenCalledWith({ name: JobName.STORAGE_TEMPLATE_MIGRATION });
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should handle a start clip encoding command', async () => {
|
it('should handle a start smart search command', async () => {
|
||||||
jobMock.getQueueStatus.mockResolvedValue({ isActive: false, isPaused: false });
|
jobMock.getQueueStatus.mockResolvedValue({ isActive: false, isPaused: false });
|
||||||
|
|
||||||
await sut.handleCommand(QueueName.SMART_SEARCH, { command: JobCommand.START, force: false });
|
await sut.handleCommand(QueueName.SMART_SEARCH, { command: JobCommand.START, force: false });
|
||||||
|
|
||||||
expect(jobMock.queue).toHaveBeenCalledWith({ name: JobName.QUEUE_ENCODE_CLIP, data: { force: false } });
|
expect(jobMock.queue).toHaveBeenCalledWith({ name: JobName.QUEUE_SMART_SEARCH, data: { force: false } });
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should handle a start metadata extraction command', async () => {
|
it('should handle a start metadata extraction command', async () => {
|
||||||
@ -289,7 +289,7 @@ describe(JobService.name, () => {
|
|||||||
jobs: [
|
jobs: [
|
||||||
JobName.GENERATE_WEBP_THUMBNAIL,
|
JobName.GENERATE_WEBP_THUMBNAIL,
|
||||||
JobName.GENERATE_THUMBHASH_THUMBNAIL,
|
JobName.GENERATE_THUMBHASH_THUMBNAIL,
|
||||||
JobName.ENCODE_CLIP,
|
JobName.SMART_SEARCH,
|
||||||
JobName.FACE_DETECTION,
|
JobName.FACE_DETECTION,
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
@ -298,7 +298,7 @@ describe(JobService.name, () => {
|
|||||||
jobs: [
|
jobs: [
|
||||||
JobName.GENERATE_WEBP_THUMBNAIL,
|
JobName.GENERATE_WEBP_THUMBNAIL,
|
||||||
JobName.GENERATE_THUMBHASH_THUMBNAIL,
|
JobName.GENERATE_THUMBHASH_THUMBNAIL,
|
||||||
JobName.ENCODE_CLIP,
|
JobName.SMART_SEARCH,
|
||||||
JobName.FACE_DETECTION,
|
JobName.FACE_DETECTION,
|
||||||
JobName.VIDEO_CONVERSION,
|
JobName.VIDEO_CONVERSION,
|
||||||
],
|
],
|
||||||
@ -308,13 +308,13 @@ describe(JobService.name, () => {
|
|||||||
jobs: [
|
jobs: [
|
||||||
JobName.GENERATE_WEBP_THUMBNAIL,
|
JobName.GENERATE_WEBP_THUMBNAIL,
|
||||||
JobName.GENERATE_THUMBHASH_THUMBNAIL,
|
JobName.GENERATE_THUMBHASH_THUMBNAIL,
|
||||||
JobName.ENCODE_CLIP,
|
JobName.SMART_SEARCH,
|
||||||
JobName.FACE_DETECTION,
|
JobName.FACE_DETECTION,
|
||||||
JobName.VIDEO_CONVERSION,
|
JobName.VIDEO_CONVERSION,
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
item: { name: JobName.ENCODE_CLIP, data: { id: 'asset-1' } },
|
item: { name: JobName.SMART_SEARCH, data: { id: 'asset-1' } },
|
||||||
jobs: [],
|
jobs: [],
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@ -365,7 +365,7 @@ describe(JobService.name, () => {
|
|||||||
const featureTests: Array<{ queue: QueueName; feature: FeatureFlag; configKey: SystemConfigKey }> = [
|
const featureTests: Array<{ queue: QueueName; feature: FeatureFlag; configKey: SystemConfigKey }> = [
|
||||||
{
|
{
|
||||||
queue: QueueName.SMART_SEARCH,
|
queue: QueueName.SMART_SEARCH,
|
||||||
feature: FeatureFlag.CLIP_ENCODE,
|
feature: FeatureFlag.SMART_SEARCH,
|
||||||
configKey: SystemConfigKey.MACHINE_LEARNING_CLIP_ENABLED,
|
configKey: SystemConfigKey.MACHINE_LEARNING_CLIP_ENABLED,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
@ -95,8 +95,8 @@ export class JobService {
|
|||||||
return this.jobRepository.queue({ name: JobName.QUEUE_MIGRATION });
|
return this.jobRepository.queue({ name: JobName.QUEUE_MIGRATION });
|
||||||
|
|
||||||
case QueueName.SMART_SEARCH:
|
case QueueName.SMART_SEARCH:
|
||||||
await this.configCore.requireFeature(FeatureFlag.CLIP_ENCODE);
|
await this.configCore.requireFeature(FeatureFlag.SMART_SEARCH);
|
||||||
return this.jobRepository.queue({ name: JobName.QUEUE_ENCODE_CLIP, data: { force } });
|
return this.jobRepository.queue({ name: JobName.QUEUE_SMART_SEARCH, data: { force } });
|
||||||
|
|
||||||
case QueueName.METADATA_EXTRACTION:
|
case QueueName.METADATA_EXTRACTION:
|
||||||
return this.jobRepository.queue({ name: JobName.QUEUE_METADATA_EXTRACTION, data: { force } });
|
return this.jobRepository.queue({ name: JobName.QUEUE_METADATA_EXTRACTION, data: { force } });
|
||||||
@ -226,7 +226,7 @@ export class JobService {
|
|||||||
const jobs: JobItem[] = [
|
const jobs: JobItem[] = [
|
||||||
{ name: JobName.GENERATE_WEBP_THUMBNAIL, data: item.data },
|
{ name: JobName.GENERATE_WEBP_THUMBNAIL, data: item.data },
|
||||||
{ name: JobName.GENERATE_THUMBHASH_THUMBNAIL, data: item.data },
|
{ name: JobName.GENERATE_THUMBHASH_THUMBNAIL, data: item.data },
|
||||||
{ name: JobName.ENCODE_CLIP, data: item.data },
|
{ name: JobName.SMART_SEARCH, data: item.data },
|
||||||
{ name: JobName.FACE_DETECTION, data: item.data },
|
{ name: JobName.FACE_DETECTION, data: item.data },
|
||||||
];
|
];
|
||||||
|
|
||||||
|
@ -93,7 +93,7 @@ export enum WithoutProperty {
|
|||||||
THUMBNAIL = 'thumbnail',
|
THUMBNAIL = 'thumbnail',
|
||||||
ENCODED_VIDEO = 'encoded-video',
|
ENCODED_VIDEO = 'encoded-video',
|
||||||
EXIF = 'exif',
|
EXIF = 'exif',
|
||||||
CLIP_ENCODING = 'clip-embedding',
|
SMART_SEARCH = 'smart-search',
|
||||||
OBJECT_TAGS = 'object-tags',
|
OBJECT_TAGS = 'object-tags',
|
||||||
FACES = 'faces',
|
FACES = 'faces',
|
||||||
PERSON = 'person',
|
PERSON = 'person',
|
||||||
|
@ -71,9 +71,9 @@ export type JobItem =
|
|||||||
| { name: JobName.FACIAL_RECOGNITION; data: IDeferrableJob }
|
| { name: JobName.FACIAL_RECOGNITION; data: IDeferrableJob }
|
||||||
| { name: JobName.GENERATE_PERSON_THUMBNAIL; data: IEntityJob }
|
| { name: JobName.GENERATE_PERSON_THUMBNAIL; data: IEntityJob }
|
||||||
|
|
||||||
// Clip Embedding
|
// Smart Search
|
||||||
| { name: JobName.QUEUE_ENCODE_CLIP; data: IBaseJob }
|
| { name: JobName.QUEUE_SMART_SEARCH; data: IBaseJob }
|
||||||
| { name: JobName.ENCODE_CLIP; data: IEntityJob }
|
| { name: JobName.SMART_SEARCH; data: IEntityJob }
|
||||||
|
|
||||||
// Filesystem
|
// Filesystem
|
||||||
| { name: JobName.DELETE_FILES; data: IDeleteFilesJob }
|
| { name: JobName.DELETE_FILES; data: IDeleteFilesJob }
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
import { AssetType } from '@app/infra/entities';
|
import { AssetType } from '@app/infra/entities';
|
||||||
|
|
||||||
export enum SearchStrategy {
|
export enum SearchStrategy {
|
||||||
CLIP = 'CLIP',
|
SMART = 'SMART',
|
||||||
TEXT = 'TEXT',
|
TEXT = 'TEXT',
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -14,6 +14,12 @@ export class SearchDto {
|
|||||||
@Optional()
|
@Optional()
|
||||||
query?: string;
|
query?: string;
|
||||||
|
|
||||||
|
@IsBoolean()
|
||||||
|
@Optional()
|
||||||
|
@Transform(toBoolean)
|
||||||
|
smart?: boolean;
|
||||||
|
|
||||||
|
/** @deprecated */
|
||||||
@IsBoolean()
|
@IsBoolean()
|
||||||
@Optional()
|
@Optional()
|
||||||
@Transform(toBoolean)
|
@Transform(toBoolean)
|
||||||
|
@ -180,14 +180,14 @@ describe(SearchService.name, () => {
|
|||||||
expect(assetMock.searchMetadata).not.toHaveBeenCalled();
|
expect(assetMock.searchMetadata).not.toHaveBeenCalled();
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should throw an error if clip is requested but disabled', async () => {
|
it.each([
|
||||||
|
{ key: SystemConfigKey.MACHINE_LEARNING_ENABLED },
|
||||||
|
{ key: SystemConfigKey.MACHINE_LEARNING_CLIP_ENABLED },
|
||||||
|
])('should throw an error if clip is requested but disabled', async ({ key }) => {
|
||||||
const dto: SearchDto = { q: 'test query', clip: true };
|
const dto: SearchDto = { q: 'test query', clip: true };
|
||||||
configMock.load
|
configMock.load.mockResolvedValue([{ key, value: false }]);
|
||||||
.mockResolvedValueOnce([{ key: SystemConfigKey.MACHINE_LEARNING_ENABLED, value: false }])
|
|
||||||
.mockResolvedValueOnce([{ key: SystemConfigKey.MACHINE_LEARNING_CLIP_ENABLED, value: false }]);
|
|
||||||
|
|
||||||
await expect(sut.search(authStub.user1, dto)).rejects.toThrow('CLIP is not enabled');
|
await expect(sut.search(authStub.user1, dto)).rejects.toThrow('Smart search is not enabled');
|
||||||
await expect(sut.search(authStub.user1, dto)).rejects.toThrow('CLIP is not enabled');
|
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
@ -56,23 +56,26 @@ export class SearchService {
|
|||||||
}
|
}
|
||||||
|
|
||||||
async search(auth: AuthDto, dto: SearchDto): Promise<SearchResponseDto> {
|
async search(auth: AuthDto, dto: SearchDto): Promise<SearchResponseDto> {
|
||||||
|
await this.configCore.requireFeature(FeatureFlag.SEARCH);
|
||||||
const { machineLearning } = await this.configCore.getConfig();
|
const { machineLearning } = await this.configCore.getConfig();
|
||||||
const query = dto.q || dto.query;
|
const query = dto.q || dto.query;
|
||||||
if (!query) {
|
if (!query) {
|
||||||
throw new Error('Missing query');
|
throw new Error('Missing query');
|
||||||
}
|
}
|
||||||
const hasClip = machineLearning.enabled && machineLearning.clip.enabled;
|
|
||||||
if (dto.clip && !hasClip) {
|
let strategy = SearchStrategy.TEXT;
|
||||||
throw new Error('CLIP is not enabled');
|
if (dto.smart || dto.clip) {
|
||||||
|
await this.configCore.requireFeature(FeatureFlag.SMART_SEARCH);
|
||||||
|
strategy = SearchStrategy.SMART;
|
||||||
}
|
}
|
||||||
const strategy = dto.clip ? SearchStrategy.CLIP : SearchStrategy.TEXT;
|
|
||||||
const userIds = await this.getUserIdsToSearch(auth);
|
const userIds = await this.getUserIdsToSearch(auth);
|
||||||
const withArchived = dto.withArchived || false;
|
const withArchived = dto.withArchived || false;
|
||||||
|
|
||||||
let assets: AssetEntity[] = [];
|
let assets: AssetEntity[] = [];
|
||||||
|
|
||||||
switch (strategy) {
|
switch (strategy) {
|
||||||
case SearchStrategy.CLIP:
|
case SearchStrategy.SMART:
|
||||||
const embedding = await this.machineLearning.encodeText(
|
const embedding = await this.machineLearning.encodeText(
|
||||||
machineLearning.url,
|
machineLearning.url,
|
||||||
{ text: query },
|
{ text: query },
|
||||||
|
@ -93,7 +93,7 @@ export class ServerConfigDto {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export class ServerFeaturesDto implements FeatureFlags {
|
export class ServerFeaturesDto implements FeatureFlags {
|
||||||
clipEncode!: boolean;
|
smartSearch!: boolean;
|
||||||
configFile!: boolean;
|
configFile!: boolean;
|
||||||
facialRecognition!: boolean;
|
facialRecognition!: boolean;
|
||||||
map!: boolean;
|
map!: boolean;
|
||||||
|
@ -174,7 +174,7 @@ describe(ServerInfoService.name, () => {
|
|||||||
describe('getFeatures', () => {
|
describe('getFeatures', () => {
|
||||||
it('should respond the server features', async () => {
|
it('should respond the server features', async () => {
|
||||||
await expect(sut.getFeatures()).resolves.toEqual({
|
await expect(sut.getFeatures()).resolves.toEqual({
|
||||||
clipEncode: true,
|
smartSearch: true,
|
||||||
facialRecognition: true,
|
facialRecognition: true,
|
||||||
map: true,
|
map: true,
|
||||||
reverseGeocoding: true,
|
reverseGeocoding: true,
|
||||||
|
@ -69,8 +69,8 @@ describe(SmartInfoService.name, () => {
|
|||||||
|
|
||||||
await sut.handleQueueEncodeClip({ force: false });
|
await sut.handleQueueEncodeClip({ force: false });
|
||||||
|
|
||||||
expect(jobMock.queueAll).toHaveBeenCalledWith([{ name: JobName.ENCODE_CLIP, data: { id: assetStub.image.id } }]);
|
expect(jobMock.queueAll).toHaveBeenCalledWith([{ name: JobName.SMART_SEARCH, data: { id: assetStub.image.id } }]);
|
||||||
expect(assetMock.getWithout).toHaveBeenCalledWith({ skip: 0, take: 1000 }, WithoutProperty.CLIP_ENCODING);
|
expect(assetMock.getWithout).toHaveBeenCalledWith({ skip: 0, take: 1000 }, WithoutProperty.SMART_SEARCH);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should queue all the assets', async () => {
|
it('should queue all the assets', async () => {
|
||||||
@ -81,7 +81,7 @@ describe(SmartInfoService.name, () => {
|
|||||||
|
|
||||||
await sut.handleQueueEncodeClip({ force: true });
|
await sut.handleQueueEncodeClip({ force: true });
|
||||||
|
|
||||||
expect(jobMock.queueAll).toHaveBeenCalledWith([{ name: JobName.ENCODE_CLIP, data: { id: assetStub.image.id } }]);
|
expect(jobMock.queueAll).toHaveBeenCalledWith([{ name: JobName.SMART_SEARCH, data: { id: assetStub.image.id } }]);
|
||||||
expect(assetMock.getAll).toHaveBeenCalled();
|
expect(assetMock.getAll).toHaveBeenCalled();
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
@ -53,11 +53,13 @@ export class SmartInfoService {
|
|||||||
const assetPagination = usePagination(JOBS_ASSET_PAGINATION_SIZE, (pagination) => {
|
const assetPagination = usePagination(JOBS_ASSET_PAGINATION_SIZE, (pagination) => {
|
||||||
return force
|
return force
|
||||||
? this.assetRepository.getAll(pagination)
|
? this.assetRepository.getAll(pagination)
|
||||||
: this.assetRepository.getWithout(pagination, WithoutProperty.CLIP_ENCODING);
|
: this.assetRepository.getWithout(pagination, WithoutProperty.SMART_SEARCH);
|
||||||
});
|
});
|
||||||
|
|
||||||
for await (const assets of assetPagination) {
|
for await (const assets of assetPagination) {
|
||||||
await this.jobRepository.queueAll(assets.map((asset) => ({ name: JobName.ENCODE_CLIP, data: { id: asset.id } })));
|
await this.jobRepository.queueAll(
|
||||||
|
assets.map((asset) => ({ name: JobName.SMART_SEARCH, data: { id: asset.id } })),
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
|
@ -136,7 +136,7 @@ export const defaults = Object.freeze<SystemConfig>({
|
|||||||
});
|
});
|
||||||
|
|
||||||
export enum FeatureFlag {
|
export enum FeatureFlag {
|
||||||
CLIP_ENCODE = 'clipEncode',
|
SMART_SEARCH = 'smartSearch',
|
||||||
FACIAL_RECOGNITION = 'facialRecognition',
|
FACIAL_RECOGNITION = 'facialRecognition',
|
||||||
MAP = 'map',
|
MAP = 'map',
|
||||||
REVERSE_GEOCODING = 'reverseGeocoding',
|
REVERSE_GEOCODING = 'reverseGeocoding',
|
||||||
@ -178,8 +178,8 @@ export class SystemConfigCore {
|
|||||||
const hasFeature = await this.hasFeature(feature);
|
const hasFeature = await this.hasFeature(feature);
|
||||||
if (!hasFeature) {
|
if (!hasFeature) {
|
||||||
switch (feature) {
|
switch (feature) {
|
||||||
case FeatureFlag.CLIP_ENCODE:
|
case FeatureFlag.SMART_SEARCH:
|
||||||
throw new BadRequestException('Clip encoding is not enabled');
|
throw new BadRequestException('Smart search is not enabled');
|
||||||
case FeatureFlag.FACIAL_RECOGNITION:
|
case FeatureFlag.FACIAL_RECOGNITION:
|
||||||
throw new BadRequestException('Facial recognition is not enabled');
|
throw new BadRequestException('Facial recognition is not enabled');
|
||||||
case FeatureFlag.SIDECAR:
|
case FeatureFlag.SIDECAR:
|
||||||
@ -208,7 +208,7 @@ export class SystemConfigCore {
|
|||||||
const mlEnabled = config.machineLearning.enabled;
|
const mlEnabled = config.machineLearning.enabled;
|
||||||
|
|
||||||
return {
|
return {
|
||||||
[FeatureFlag.CLIP_ENCODE]: mlEnabled && config.machineLearning.clip.enabled,
|
[FeatureFlag.SMART_SEARCH]: mlEnabled && config.machineLearning.clip.enabled,
|
||||||
[FeatureFlag.FACIAL_RECOGNITION]: mlEnabled && config.machineLearning.facialRecognition.enabled,
|
[FeatureFlag.FACIAL_RECOGNITION]: mlEnabled && config.machineLearning.facialRecognition.enabled,
|
||||||
[FeatureFlag.MAP]: config.map.enabled,
|
[FeatureFlag.MAP]: config.map.enabled,
|
||||||
[FeatureFlag.REVERSE_GEOCODING]: config.reverseGeocoding.enabled,
|
[FeatureFlag.REVERSE_GEOCODING]: config.reverseGeocoding.enabled,
|
||||||
|
@ -501,7 +501,7 @@ export class AssetRepository implements IAssetRepository {
|
|||||||
};
|
};
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case WithoutProperty.CLIP_ENCODING:
|
case WithoutProperty.SMART_SEARCH:
|
||||||
relations = {
|
relations = {
|
||||||
smartSearch: true,
|
smartSearch: true,
|
||||||
};
|
};
|
||||||
|
@ -46,8 +46,8 @@ export class AppService {
|
|||||||
[JobName.USER_DELETE_CHECK]: () => this.userService.handleUserDeleteCheck(),
|
[JobName.USER_DELETE_CHECK]: () => this.userService.handleUserDeleteCheck(),
|
||||||
[JobName.USER_DELETION]: (data) => this.userService.handleUserDelete(data),
|
[JobName.USER_DELETION]: (data) => this.userService.handleUserDelete(data),
|
||||||
[JobName.USER_SYNC_USAGE]: () => this.userService.handleUserSyncUsage(),
|
[JobName.USER_SYNC_USAGE]: () => this.userService.handleUserSyncUsage(),
|
||||||
[JobName.QUEUE_ENCODE_CLIP]: (data) => this.smartInfoService.handleQueueEncodeClip(data),
|
[JobName.QUEUE_SMART_SEARCH]: (data) => this.smartInfoService.handleQueueEncodeClip(data),
|
||||||
[JobName.ENCODE_CLIP]: (data) => this.smartInfoService.handleEncodeClip(data),
|
[JobName.SMART_SEARCH]: (data) => this.smartInfoService.handleEncodeClip(data),
|
||||||
[JobName.STORAGE_TEMPLATE_MIGRATION]: () => this.storageTemplateService.handleMigration(),
|
[JobName.STORAGE_TEMPLATE_MIGRATION]: () => this.storageTemplateService.handleMigration(),
|
||||||
[JobName.STORAGE_TEMPLATE_MIGRATION_SINGLE]: (data) => this.storageTemplateService.handleMigrationSingle(data),
|
[JobName.STORAGE_TEMPLATE_MIGRATION_SINGLE]: (data) => this.storageTemplateService.handleMigrationSingle(data),
|
||||||
[JobName.QUEUE_MIGRATION]: () => this.mediaService.handleQueueMigration(),
|
[JobName.QUEUE_MIGRATION]: () => this.mediaService.handleQueueMigration(),
|
||||||
|
@ -85,7 +85,7 @@
|
|||||||
icon: mdiImageSearch,
|
icon: mdiImageSearch,
|
||||||
title: api.getJobName(JobName.SmartSearch),
|
title: api.getJobName(JobName.SmartSearch),
|
||||||
subtitle: 'Run machine learning on assets to support smart search',
|
subtitle: 'Run machine learning on assets to support smart search',
|
||||||
disabled: !$featureFlags.clipEncode,
|
disabled: !$featureFlags.smartSearch,
|
||||||
},
|
},
|
||||||
[JobName.FaceDetection]: {
|
[JobName.FaceDetection]: {
|
||||||
icon: mdiFaceRecognition,
|
icon: mdiFaceRecognition,
|
||||||
|
@ -15,11 +15,11 @@
|
|||||||
$: showClearIcon = value.length > 0;
|
$: showClearIcon = value.length > 0;
|
||||||
|
|
||||||
function onSearch() {
|
function onSearch() {
|
||||||
let clipSearch = 'true';
|
let smartSearch = 'true';
|
||||||
let searchValue = value;
|
let searchValue = value;
|
||||||
|
|
||||||
if (value.slice(0, 2) == 'm:') {
|
if (value.slice(0, 2) == 'm:') {
|
||||||
clipSearch = 'false';
|
smartSearch = 'false';
|
||||||
searchValue = value.slice(2);
|
searchValue = value.slice(2);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -28,7 +28,7 @@
|
|||||||
|
|
||||||
const params = new URLSearchParams({
|
const params = new URLSearchParams({
|
||||||
q: searchValue,
|
q: searchValue,
|
||||||
clip: clipSearch,
|
smart: smartSearch,
|
||||||
});
|
});
|
||||||
|
|
||||||
showBigSearchBar = false;
|
showBigSearchBar = false;
|
||||||
|
@ -63,7 +63,7 @@ export const dateFormats = {
|
|||||||
export enum QueryParameter {
|
export enum QueryParameter {
|
||||||
ACTION = 'action',
|
ACTION = 'action',
|
||||||
ASSET_INDEX = 'assetIndex',
|
ASSET_INDEX = 'assetIndex',
|
||||||
CLIP = 'clip',
|
SMART_SEARCH = 'smartSearch',
|
||||||
MEMORY_INDEX = 'memoryIndex',
|
MEMORY_INDEX = 'memoryIndex',
|
||||||
ONBOARDING_STEP = 'step',
|
ONBOARDING_STEP = 'step',
|
||||||
OPEN_SETTING = 'openSetting',
|
OPEN_SETTING = 'openSetting',
|
||||||
|
@ -5,7 +5,7 @@ export type FeatureFlags = ServerFeaturesDto & { loaded: boolean };
|
|||||||
|
|
||||||
export const featureFlags = writable<FeatureFlags>({
|
export const featureFlags = writable<FeatureFlags>({
|
||||||
loaded: false,
|
loaded: false,
|
||||||
clipEncode: true,
|
smartSearch: true,
|
||||||
facialRecognition: true,
|
facialRecognition: true,
|
||||||
sidecar: true,
|
sidecar: true,
|
||||||
map: true,
|
map: true,
|
||||||
|
@ -87,7 +87,7 @@
|
|||||||
|
|
||||||
$: term = (() => {
|
$: term = (() => {
|
||||||
let term = $page.url.searchParams.get(QueryParameter.SEARCH_TERM) || data.term || '';
|
let term = $page.url.searchParams.get(QueryParameter.SEARCH_TERM) || data.term || '';
|
||||||
const isMetadataSearch = $page.url.searchParams.get(QueryParameter.CLIP) === 'false';
|
const isMetadataSearch = $page.url.searchParams.get(QueryParameter.SMART_SEARCH) === 'false';
|
||||||
if (isMetadataSearch && term !== '') {
|
if (isMetadataSearch && term !== '') {
|
||||||
term = `m:${term}`;
|
term = `m:${term}`;
|
||||||
}
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user