From ae7f174948a8a386aa176553d43317da2fb55128 Mon Sep 17 00:00:00 2001
From: Mert <101130780+mertalev@users.noreply.github.com>
Date: Mon, 29 Jan 2024 09:51:22 -0500
Subject: [PATCH] refactor: rename clip -> smart search (#6713)

---
 .../search_result_page_state.model.dart       |  14 +++---
 .../search_result_page.provider.dart          |  16 +++----
 .../search/services/search.service.dart       |   4 +-
 .../search/views/search_result_page.dart      |  12 ++---
 mobile/openapi/doc/SearchApi.md               | Bin 6700 -> 6805 bytes
 mobile/openapi/doc/ServerFeaturesDto.md       | Bin 732 -> 733 bytes
 mobile/openapi/lib/api/search_api.dart        | Bin 6591 -> 6818 bytes
 .../lib/model/server_features_dto.dart        | Bin 5485 -> 5497 bytes
 mobile/openapi/test/search_api_test.dart      | Bin 944 -> 956 bytes
 .../test/server_features_dto_test.dart        | Bin 1601 -> 1603 bytes
 open-api/immich-openapi-specs.json            |  18 ++++++--
 open-api/typescript-sdk/client/api.ts         |  41 ++++++++++++------
 server/e2e/api/specs/server-info.e2e-spec.ts  |   2 +-
 server/src/domain/job/job.constants.ts        |  12 ++---
 server/src/domain/job/job.service.spec.ts     |  14 +++---
 server/src/domain/job/job.service.ts          |   6 +--
 .../domain/repositories/asset.repository.ts   |   2 +-
 .../src/domain/repositories/job.repository.ts |   6 +--
 .../domain/repositories/search.repository.ts  |   2 +-
 server/src/domain/search/dto/search.dto.ts    |   6 +++
 .../src/domain/search/search.service.spec.ts  |  12 ++---
 server/src/domain/search/search.service.ts    |  13 +++---
 .../src/domain/server-info/server-info.dto.ts |   2 +-
 .../server-info/server-info.service.spec.ts   |   2 +-
 .../smart-info/smart-info.service.spec.ts     |   6 +--
 .../domain/smart-info/smart-info.service.ts   |   6 ++-
 .../system-config/system-config.core.ts       |   8 ++--
 .../infra/repositories/asset.repository.ts    |   2 +-
 server/src/microservices/app.service.ts       |   4 +-
 .../admin-page/jobs/jobs-panel.svelte         |   2 +-
 .../search-bar/search-bar.svelte              |   6 +--
 web/src/lib/constants.ts                      |   2 +-
 web/src/lib/stores/server-config.store.ts     |   2 +-
 web/src/routes/(user)/search/+page.svelte     |   2 +-
 34 files changed, 128 insertions(+), 96 deletions(-)

diff --git a/mobile/lib/modules/search/models/search_result_page_state.model.dart b/mobile/lib/modules/search/models/search_result_page_state.model.dart
index 51d557b8c3..f42f2e9cb6 100644
--- a/mobile/lib/modules/search/models/search_result_page_state.model.dart
+++ b/mobile/lib/modules/search/models/search_result_page_state.model.dart
@@ -5,14 +5,14 @@ class SearchResultPageState {
   final bool isLoading;
   final bool isSuccess;
   final bool isError;
-  final bool isClip;
+  final bool isSmart;
   final List<Asset> searchResult;
 
   SearchResultPageState({
     required this.isLoading,
     required this.isSuccess,
     required this.isError,
-    required this.isClip,
+    required this.isSmart,
     required this.searchResult,
   });
 
@@ -20,21 +20,21 @@ class SearchResultPageState {
     bool? isLoading,
     bool? isSuccess,
     bool? isError,
-    bool? isClip,
+    bool? isSmart,
     List<Asset>? searchResult,
   }) {
     return SearchResultPageState(
       isLoading: isLoading ?? this.isLoading,
       isSuccess: isSuccess ?? this.isSuccess,
       isError: isError ?? this.isError,
-      isClip: isClip ?? this.isClip,
+      isSmart: isSmart ?? this.isSmart,
       searchResult: searchResult ?? this.searchResult,
     );
   }
 
   @override
   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
@@ -46,7 +46,7 @@ class SearchResultPageState {
         other.isLoading == isLoading &&
         other.isSuccess == isSuccess &&
         other.isError == isError &&
-        other.isClip == isClip &&
+        other.isSmart == isSmart &&
         listEquals(other.searchResult, searchResult);
   }
 
@@ -55,7 +55,7 @@ class SearchResultPageState {
     return isLoading.hashCode ^
         isSuccess.hashCode ^
         isError.hashCode ^
-        isClip.hashCode ^
+        isSmart.hashCode ^
         searchResult.hashCode;
   }
 }
diff --git a/mobile/lib/modules/search/providers/search_result_page.provider.dart b/mobile/lib/modules/search/providers/search_result_page.provider.dart
index a481f291ce..e220cc69f0 100644
--- a/mobile/lib/modules/search/providers/search_result_page.provider.dart
+++ b/mobile/lib/modules/search/providers/search_result_page.provider.dart
@@ -14,13 +14,13 @@ class SearchResultPageNotifier extends StateNotifier<SearchResultPageState> {
             isError: false,
             isLoading: true,
             isSuccess: false,
-            isClip: false,
+            isSmart: false,
           ),
         );
 
   final SearchService _searchService;
 
-  Future<void> search(String searchTerm, {bool clipEnable = true}) async {
+  Future<void> search(String searchTerm, {bool smartSearch = true}) async {
     state = state.copyWith(
       searchResult: [],
       isError: false,
@@ -28,10 +28,8 @@ class SearchResultPageNotifier extends StateNotifier<SearchResultPageState> {
       isSuccess: false,
     );
 
-    List<Asset>? assets = await _searchService.searchAsset(
-      searchTerm,
-      clipEnable: clipEnable,
-    );
+    List<Asset>? assets =
+        await _searchService.searchAsset(searchTerm, smartSearch: smartSearch);
 
     if (assets != null) {
       state = state.copyWith(
@@ -39,7 +37,7 @@ class SearchResultPageNotifier extends StateNotifier<SearchResultPageState> {
         isError: false,
         isLoading: false,
         isSuccess: true,
-        isClip: clipEnable,
+        isSmart: smartSearch,
       );
     } else {
       state = state.copyWith(
@@ -47,7 +45,7 @@ class SearchResultPageNotifier extends StateNotifier<SearchResultPageState> {
         isError: true,
         isLoading: false,
         isSuccess: false,
-        isClip: clipEnable,
+        isSmart: smartSearch,
       );
     }
   }
@@ -63,7 +61,7 @@ final searchRenderListProvider = Provider((ref) {
   final result = ref.watch(searchResultPageProvider);
   return ref.watch(
     renderListProviderWithGrouping(
-      (result.searchResult, result.isClip ? GroupAssetsBy.none : null),
+      (result.searchResult, result.isSmart ? GroupAssetsBy.none : null),
     ),
   );
 });
diff --git a/mobile/lib/modules/search/services/search.service.dart b/mobile/lib/modules/search/services/search.service.dart
index 746bb455fa..35249dec5d 100644
--- a/mobile/lib/modules/search/services/search.service.dart
+++ b/mobile/lib/modules/search/services/search.service.dart
@@ -31,13 +31,13 @@ class SearchService {
 
   Future<List<Asset>?> searchAsset(
     String searchTerm, {
-    bool clipEnable = true,
+    bool smartSearch = true,
   }) async {
     // TODO search in local DB: 1. when offline, 2. to find local assets
     try {
       final SearchResponseDto? results = await _apiService.searchApi.search(
         query: searchTerm,
-        clip: clipEnable,
+        smart: smartSearch,
       );
       if (results == null) {
         return null;
diff --git a/mobile/lib/modules/search/views/search_result_page.dart b/mobile/lib/modules/search/views/search_result_page.dart
index 3645a81f1b..97df5f10c7 100644
--- a/mobile/lib/modules/search/views/search_result_page.dart
+++ b/mobile/lib/modules/search/views/search_result_page.dart
@@ -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';
 
 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;
 }
 
 SearchType _getSearchType(String searchTerm) {
   if (searchTerm.startsWith('m:')) {
-    return SearchType(isClip: false, searchTerm: searchTerm.substring(2));
+    return SearchType(isSmart: false, searchTerm: searchTerm.substring(2));
   } else {
-    return SearchType(isClip: true, searchTerm: searchTerm);
+    return SearchType(isSmart: true, searchTerm: searchTerm);
   }
 }
 
@@ -52,7 +52,7 @@ class SearchResultPage extends HookConsumerWidget {
           Duration.zero,
           () => ref
               .read(searchResultPageProvider.notifier)
-              .search(searchType.searchTerm, clipEnable: searchType.isClip),
+              .search(searchType.searchTerm, smartSearch: searchType.isSmart),
         );
         return () => searchFocusNode?.dispose();
       },
@@ -67,7 +67,7 @@ class SearchResultPage extends HookConsumerWidget {
       var searchType = _getSearchType(newSearchTerm);
       return ref
           .watch(searchResultPageProvider.notifier)
-          .search(searchType.searchTerm, clipEnable: searchType.isClip);
+          .search(searchType.searchTerm, smartSearch: searchType.isSmart);
     }
 
     buildTextField() {
diff --git a/mobile/openapi/doc/SearchApi.md b/mobile/openapi/doc/SearchApi.md
index 1cd5f60a7ce0d1b8c8a40266db281799df158e82..dcf453b55d2ec75e8ade4468355e7105fd2d58e8 100644
GIT binary patch
delta 83
zcmZ2uGSzeg7bi<`Zer19cFq76ZikfAf}+&q#FEsM$>F?{!7`K2^KOJFspVr~L{ZYn
TzlIg0xny#JfZS#+0VXa0wm}?Z

delta 41
zcmV+^0M`GNHLNtS3JJ3j2~7l(X$_N;8xEF}5eXBM%MR|7UJw$K)(@JqmJt65CzTHx

diff --git a/mobile/openapi/doc/ServerFeaturesDto.md b/mobile/openapi/doc/ServerFeaturesDto.md
index a149d8f4845eaeb2cb04667e179da0c5fa0e16f9..a1a5be40795fa816fb2365a5993d8f8a9ae57b14 100644
GIT binary patch
delta 29
lcmcb^dY5&=-Hm@08M%sc6N^fMQxl7lCnqq9PnKia2LQk63!DG|

delta 28
kcmcc1dWUtwUCx}$0@u9c{FKzmj*Ma(?<g@&mS@@r0I1#y+5i9m

diff --git a/mobile/openapi/lib/api/search_api.dart b/mobile/openapi/lib/api/search_api.dart
index d3ce301508063fb968af9492b3bce771c34b19b3..e2bde2a17f02a9cf58541d1c052d446eae80380f 100644
GIT binary patch
delta 175
zcmdmQyvTF|69=Dyf`UUzYC%zIa$-qp%H)UaVw3;z=5QzF=jX&K6z3)ul}wi7lbqbf
zF_{Y_W)Bvd#BZ|Mi}MH*D@4IME^{6*OI-&nz4<ryXGY>pHk!PIFCNA8`P^XRtRSY%
I;aBDY09v>{H~;_u

delta 70
zcmZ2vy5D#M6UXEZ4uQ$5_;Myc5aXC^#cwqE1xMoK8~lcoHAL(;ALDFg+PsYC0VA5K
Oi9i)|Ha7_<Z~*|Ujv8hF

diff --git a/mobile/openapi/lib/model/server_features_dto.dart b/mobile/openapi/lib/model/server_features_dto.dart
index e3fd530b173bf1cc6edbd2390ba219e032eb93c2..b3ff58f164152dae1cff2cce561106d6b9d3119d 100644
GIT binary patch
delta 257
zcmaE>^;2uZG{((a82K65igOc-N`fc%Gf7T9%p^AXEz?$Rh)`-`QF6xQ8O*JlomnCo
zrH}*_Y;6^gxRd)>^CnBM{YBBo%pN_tjQzF*k~&)zB+g`ij?B$ZIc6{-o1`AAV5?A)
zky)&Vq;hi}7tpOPJkCrg7EIukN9J#?;?rZByh~)UGLlwx9WDhRP%kM;EY1LPYOT3y
GxwrstzFYVJ

delta 243
zcmeyV^;T=cG)B&x%mUZE<ouM>$rG7GHcw>~V4Qq}NeoH&Jkx>6OPH(0;R+ROZ55K?
zyv+hE8H|$`uqKGYh4eBKi!+>oMrtZ(Z~n>3!Z=xq-4mhTRt2G3M`3aj`<=-V9O<%f
z?dq`#whAQ~nZ<etb(`07tYF;S#?_AO;EO!+d<Z4#nu<D`nR$GfHc#SHWaCB{r!#qt
K$g0Viq5=SjFjXV~

diff --git a/mobile/openapi/test/search_api_test.dart b/mobile/openapi/test/search_api_test.dart
index 50406e90d159b67366dc223e9b1e60fb1d00a5dc..769ad3194367ba51db104049ddf94ad3b663cdcf 100644
GIT binary patch
delta 22
dcmdnMzK4CoY9`L4{QMk+;@rfd%`2EV7y)2&2gU#Z

delta 12
TcmdnPzJYziYNpNGnOGSCAngQ7

diff --git a/mobile/openapi/test/server_features_dto_test.dart b/mobile/openapi/test/server_features_dto_test.dart
index ac92a105f7208f8c7b0b223ddee10f0f5f7b7b7f..67cc029d4d823e7c942c11edea42d4451ad33aba 100644
GIT binary patch
delta 43
vcmX@ebC_qtUB=BWOpBPgi*pl;N`g}pi;^=Y`!XrALHLvXSj{ItV~qv?W<?Lm

delta 44
ucmX@ibC74lT}IBF%mUZE<ouM>$+gUioXIfmWC14Q&370VGf#fO8Vvw<#Sne~

diff --git a/open-api/immich-openapi-specs.json b/open-api/immich-openapi-specs.json
index bd0b99b9c0..a593ac894a 100644
--- a/open-api/immich-openapi-specs.json
+++ b/open-api/immich-openapi-specs.json
@@ -4684,6 +4684,8 @@
             "name": "clip",
             "required": false,
             "in": "query",
+            "description": "@deprecated",
+            "deprecated": true,
             "schema": {
               "type": "boolean"
             }
@@ -4720,6 +4722,14 @@
               "type": "boolean"
             }
           },
+          {
+            "name": "smart",
+            "required": false,
+            "in": "query",
+            "schema": {
+              "type": "boolean"
+            }
+          },
           {
             "name": "type",
             "required": false,
@@ -8919,9 +8929,6 @@
       },
       "ServerFeaturesDto": {
         "properties": {
-          "clipEncode": {
-            "type": "boolean"
-          },
           "configFile": {
             "type": "boolean"
           },
@@ -8949,12 +8956,14 @@
           "sidecar": {
             "type": "boolean"
           },
+          "smartSearch": {
+            "type": "boolean"
+          },
           "trash": {
             "type": "boolean"
           }
         },
         "required": [
-          "clipEncode",
           "configFile",
           "facialRecognition",
           "map",
@@ -8964,6 +8973,7 @@
           "reverseGeocoding",
           "search",
           "sidecar",
+          "smartSearch",
           "trash"
         ],
         "type": "object"
diff --git a/open-api/typescript-sdk/client/api.ts b/open-api/typescript-sdk/client/api.ts
index 64531eb76b..595187df44 100644
--- a/open-api/typescript-sdk/client/api.ts
+++ b/open-api/typescript-sdk/client/api.ts
@@ -3069,12 +3069,6 @@ export interface ServerConfigDto {
  * @interface ServerFeaturesDto
  */
 export interface ServerFeaturesDto {
-    /**
-     * 
-     * @type {boolean}
-     * @memberof ServerFeaturesDto
-     */
-    'clipEncode': boolean;
     /**
      * 
      * @type {boolean}
@@ -3129,6 +3123,12 @@ export interface ServerFeaturesDto {
      * @memberof ServerFeaturesDto
      */
     'sidecar': boolean;
+    /**
+     * 
+     * @type {boolean}
+     * @memberof ServerFeaturesDto
+     */
+    'smartSearch': boolean;
     /**
      * 
      * @type {boolean}
@@ -15206,17 +15206,18 @@ export const SearchApiAxiosParamCreator = function (configuration?: Configuratio
         },
         /**
          * 
-         * @param {boolean} [clip] 
+         * @param {boolean} [clip] @deprecated
          * @param {boolean} [motion] 
          * @param {string} [q] 
          * @param {string} [query] 
          * @param {boolean} [recent] 
+         * @param {boolean} [smart] 
          * @param {SearchTypeEnum} [type] 
          * @param {boolean} [withArchived] 
          * @param {*} [options] Override http request option.
          * @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`;
             // use dummy base URL string because the URL constructor only accepts absolute URLs.
             const localVarUrlObj = new URL(localVarPath, DUMMY_BASE_URL);
@@ -15258,6 +15259,10 @@ export const SearchApiAxiosParamCreator = function (configuration?: Configuratio
                 localVarQueryParameter['recent'] = recent;
             }
 
+            if (smart !== undefined) {
+                localVarQueryParameter['smart'] = smart;
+            }
+
             if (type !== undefined) {
                 localVarQueryParameter['type'] = type;
             }
@@ -15350,18 +15355,19 @@ export const SearchApiFp = function(configuration?: Configuration) {
         },
         /**
          * 
-         * @param {boolean} [clip] 
+         * @param {boolean} [clip] @deprecated
          * @param {boolean} [motion] 
          * @param {string} [q] 
          * @param {string} [query] 
          * @param {boolean} [recent] 
+         * @param {boolean} [smart] 
          * @param {SearchTypeEnum} [type] 
          * @param {boolean} [withArchived] 
          * @param {*} [options] Override http request option.
          * @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>> {
-            const localVarAxiosArgs = await localVarAxiosParamCreator.search(clip, motion, q, query, recent, type, withArchived, options);
+        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, smart, type, withArchived, options);
             const index = configuration?.serverIndex ?? 0;
             const operationBasePath = operationServerMap['SearchApi.search']?.[index]?.url;
             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}
          */
         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 {
     /**
-     * 
+     * @deprecated
      * @type {boolean}
      * @memberof SearchApiSearch
      */
@@ -15459,6 +15465,13 @@ export interface SearchApiSearchRequest {
      */
     readonly recent?: boolean
 
+    /**
+     * 
+     * @type {boolean}
+     * @memberof SearchApiSearch
+     */
+    readonly smart?: boolean
+
     /**
      * 
      * @type {'IMAGE' | 'VIDEO' | 'AUDIO' | 'OTHER'}
@@ -15520,7 +15533,7 @@ export class SearchApi extends BaseAPI {
      * @memberof SearchApi
      */
     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));
     }
 
     /**
diff --git a/server/e2e/api/specs/server-info.e2e-spec.ts b/server/e2e/api/specs/server-info.e2e-spec.ts
index d142704346..f5664a11a3 100644
--- a/server/e2e/api/specs/server-info.e2e-spec.ts
+++ b/server/e2e/api/specs/server-info.e2e-spec.ts
@@ -73,7 +73,7 @@ describe(`${ServerInfoController.name} (e2e)`, () => {
       const { status, body } = await request(server).get('/server-info/features');
       expect(status).toBe(200);
       expect(body).toEqual({
-        clipEncode: true,
+        smartSearch: true,
         configFile: false,
         facialRecognition: true,
         map: true,
diff --git a/server/src/domain/job/job.constants.ts b/server/src/domain/job/job.constants.ts
index 8f8c0188f1..fe3c817278 100644
--- a/server/src/domain/job/job.constants.ts
+++ b/server/src/domain/job/job.constants.ts
@@ -80,9 +80,9 @@ export enum JobName {
   DELETE_FILES = 'delete-files',
   CLEAN_OLD_AUDIT_LOGS = 'clean-old-audit-logs',
 
-  // clip
-  QUEUE_ENCODE_CLIP = 'queue-clip-encode',
-  ENCODE_CLIP = 'clip-encode',
+  // smart search
+  QUEUE_SMART_SEARCH = 'queue-smart-search',
+  SMART_SEARCH = 'smart-search',
 
   // XMP sidecars
   QUEUE_SIDECAR = 'queue-sidecar',
@@ -135,9 +135,9 @@ export const JOBS_TO_QUEUE: Record<JobName, QueueName> = {
   [JobName.QUEUE_FACIAL_RECOGNITION]: QueueName.FACIAL_RECOGNITION,
   [JobName.FACIAL_RECOGNITION]: QueueName.FACIAL_RECOGNITION,
 
-  // clip
-  [JobName.QUEUE_ENCODE_CLIP]: QueueName.SMART_SEARCH,
-  [JobName.ENCODE_CLIP]: QueueName.SMART_SEARCH,
+  // smart search
+  [JobName.QUEUE_SMART_SEARCH]: QueueName.SMART_SEARCH,
+  [JobName.SMART_SEARCH]: QueueName.SMART_SEARCH,
 
   // XMP sidecars
   [JobName.QUEUE_SIDECAR]: QueueName.SIDECAR,
diff --git a/server/src/domain/job/job.service.spec.ts b/server/src/domain/job/job.service.spec.ts
index 56e0dfb9f5..4abeb309b6 100644
--- a/server/src/domain/job/job.service.spec.ts
+++ b/server/src/domain/job/job.service.spec.ts
@@ -159,12 +159,12 @@ describe(JobService.name, () => {
       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 });
 
       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 () => {
@@ -289,7 +289,7 @@ describe(JobService.name, () => {
         jobs: [
           JobName.GENERATE_WEBP_THUMBNAIL,
           JobName.GENERATE_THUMBHASH_THUMBNAIL,
-          JobName.ENCODE_CLIP,
+          JobName.SMART_SEARCH,
           JobName.FACE_DETECTION,
         ],
       },
@@ -298,7 +298,7 @@ describe(JobService.name, () => {
         jobs: [
           JobName.GENERATE_WEBP_THUMBNAIL,
           JobName.GENERATE_THUMBHASH_THUMBNAIL,
-          JobName.ENCODE_CLIP,
+          JobName.SMART_SEARCH,
           JobName.FACE_DETECTION,
           JobName.VIDEO_CONVERSION,
         ],
@@ -308,13 +308,13 @@ describe(JobService.name, () => {
         jobs: [
           JobName.GENERATE_WEBP_THUMBNAIL,
           JobName.GENERATE_THUMBHASH_THUMBNAIL,
-          JobName.ENCODE_CLIP,
+          JobName.SMART_SEARCH,
           JobName.FACE_DETECTION,
           JobName.VIDEO_CONVERSION,
         ],
       },
       {
-        item: { name: JobName.ENCODE_CLIP, data: { id: 'asset-1' } },
+        item: { name: JobName.SMART_SEARCH, data: { id: 'asset-1' } },
         jobs: [],
       },
       {
@@ -365,7 +365,7 @@ describe(JobService.name, () => {
     const featureTests: Array<{ queue: QueueName; feature: FeatureFlag; configKey: SystemConfigKey }> = [
       {
         queue: QueueName.SMART_SEARCH,
-        feature: FeatureFlag.CLIP_ENCODE,
+        feature: FeatureFlag.SMART_SEARCH,
         configKey: SystemConfigKey.MACHINE_LEARNING_CLIP_ENABLED,
       },
       {
diff --git a/server/src/domain/job/job.service.ts b/server/src/domain/job/job.service.ts
index 574bfffb77..a804cf658b 100644
--- a/server/src/domain/job/job.service.ts
+++ b/server/src/domain/job/job.service.ts
@@ -95,8 +95,8 @@ export class JobService {
         return this.jobRepository.queue({ name: JobName.QUEUE_MIGRATION });
 
       case QueueName.SMART_SEARCH:
-        await this.configCore.requireFeature(FeatureFlag.CLIP_ENCODE);
-        return this.jobRepository.queue({ name: JobName.QUEUE_ENCODE_CLIP, data: { force } });
+        await this.configCore.requireFeature(FeatureFlag.SMART_SEARCH);
+        return this.jobRepository.queue({ name: JobName.QUEUE_SMART_SEARCH, data: { force } });
 
       case QueueName.METADATA_EXTRACTION:
         return this.jobRepository.queue({ name: JobName.QUEUE_METADATA_EXTRACTION, data: { force } });
@@ -226,7 +226,7 @@ export class JobService {
         const jobs: JobItem[] = [
           { name: JobName.GENERATE_WEBP_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 },
         ];
 
diff --git a/server/src/domain/repositories/asset.repository.ts b/server/src/domain/repositories/asset.repository.ts
index 23d2106025..00a74f8293 100644
--- a/server/src/domain/repositories/asset.repository.ts
+++ b/server/src/domain/repositories/asset.repository.ts
@@ -93,7 +93,7 @@ export enum WithoutProperty {
   THUMBNAIL = 'thumbnail',
   ENCODED_VIDEO = 'encoded-video',
   EXIF = 'exif',
-  CLIP_ENCODING = 'clip-embedding',
+  SMART_SEARCH = 'smart-search',
   OBJECT_TAGS = 'object-tags',
   FACES = 'faces',
   PERSON = 'person',
diff --git a/server/src/domain/repositories/job.repository.ts b/server/src/domain/repositories/job.repository.ts
index 4a05fa83b2..0b52e14590 100644
--- a/server/src/domain/repositories/job.repository.ts
+++ b/server/src/domain/repositories/job.repository.ts
@@ -71,9 +71,9 @@ export type JobItem =
   | { name: JobName.FACIAL_RECOGNITION; data: IDeferrableJob }
   | { name: JobName.GENERATE_PERSON_THUMBNAIL; data: IEntityJob }
 
-  // Clip Embedding
-  | { name: JobName.QUEUE_ENCODE_CLIP; data: IBaseJob }
-  | { name: JobName.ENCODE_CLIP; data: IEntityJob }
+  // Smart Search
+  | { name: JobName.QUEUE_SMART_SEARCH; data: IBaseJob }
+  | { name: JobName.SMART_SEARCH; data: IEntityJob }
 
   // Filesystem
   | { name: JobName.DELETE_FILES; data: IDeleteFilesJob }
diff --git a/server/src/domain/repositories/search.repository.ts b/server/src/domain/repositories/search.repository.ts
index c2a0201efe..5c3497c8ef 100644
--- a/server/src/domain/repositories/search.repository.ts
+++ b/server/src/domain/repositories/search.repository.ts
@@ -1,7 +1,7 @@
 import { AssetType } from '@app/infra/entities';
 
 export enum SearchStrategy {
-  CLIP = 'CLIP',
+  SMART = 'SMART',
   TEXT = 'TEXT',
 }
 
diff --git a/server/src/domain/search/dto/search.dto.ts b/server/src/domain/search/dto/search.dto.ts
index acfb91f0cb..3ddcb3a32c 100644
--- a/server/src/domain/search/dto/search.dto.ts
+++ b/server/src/domain/search/dto/search.dto.ts
@@ -14,6 +14,12 @@ export class SearchDto {
   @Optional()
   query?: string;
 
+  @IsBoolean()
+  @Optional()
+  @Transform(toBoolean)
+  smart?: boolean;
+
+  /** @deprecated */
   @IsBoolean()
   @Optional()
   @Transform(toBoolean)
diff --git a/server/src/domain/search/search.service.spec.ts b/server/src/domain/search/search.service.spec.ts
index 7b182b9d30..86373ce2d2 100644
--- a/server/src/domain/search/search.service.spec.ts
+++ b/server/src/domain/search/search.service.spec.ts
@@ -180,14 +180,14 @@ describe(SearchService.name, () => {
       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 };
-      configMock.load
-        .mockResolvedValueOnce([{ key: SystemConfigKey.MACHINE_LEARNING_ENABLED, value: false }])
-        .mockResolvedValueOnce([{ key: SystemConfigKey.MACHINE_LEARNING_CLIP_ENABLED, value: false }]);
+      configMock.load.mockResolvedValue([{ key, value: false }]);
 
-      await expect(sut.search(authStub.user1, dto)).rejects.toThrow('CLIP is not enabled');
-      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');
     });
   });
 });
diff --git a/server/src/domain/search/search.service.ts b/server/src/domain/search/search.service.ts
index 2bd65daab9..8695c26e0d 100644
--- a/server/src/domain/search/search.service.ts
+++ b/server/src/domain/search/search.service.ts
@@ -56,23 +56,26 @@ export class SearchService {
   }
 
   async search(auth: AuthDto, dto: SearchDto): Promise<SearchResponseDto> {
+    await this.configCore.requireFeature(FeatureFlag.SEARCH);
     const { machineLearning } = await this.configCore.getConfig();
     const query = dto.q || dto.query;
     if (!query) {
       throw new Error('Missing query');
     }
-    const hasClip = machineLearning.enabled && machineLearning.clip.enabled;
-    if (dto.clip && !hasClip) {
-      throw new Error('CLIP is not enabled');
+
+    let strategy = SearchStrategy.TEXT;
+    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 withArchived = dto.withArchived || false;
 
     let assets: AssetEntity[] = [];
 
     switch (strategy) {
-      case SearchStrategy.CLIP:
+      case SearchStrategy.SMART:
         const embedding = await this.machineLearning.encodeText(
           machineLearning.url,
           { text: query },
diff --git a/server/src/domain/server-info/server-info.dto.ts b/server/src/domain/server-info/server-info.dto.ts
index 8470da8bcd..b631bdc7ef 100644
--- a/server/src/domain/server-info/server-info.dto.ts
+++ b/server/src/domain/server-info/server-info.dto.ts
@@ -93,7 +93,7 @@ export class ServerConfigDto {
 }
 
 export class ServerFeaturesDto implements FeatureFlags {
-  clipEncode!: boolean;
+  smartSearch!: boolean;
   configFile!: boolean;
   facialRecognition!: boolean;
   map!: boolean;
diff --git a/server/src/domain/server-info/server-info.service.spec.ts b/server/src/domain/server-info/server-info.service.spec.ts
index 4bc1b10443..1f1b51055b 100644
--- a/server/src/domain/server-info/server-info.service.spec.ts
+++ b/server/src/domain/server-info/server-info.service.spec.ts
@@ -174,7 +174,7 @@ describe(ServerInfoService.name, () => {
   describe('getFeatures', () => {
     it('should respond the server features', async () => {
       await expect(sut.getFeatures()).resolves.toEqual({
-        clipEncode: true,
+        smartSearch: true,
         facialRecognition: true,
         map: true,
         reverseGeocoding: true,
diff --git a/server/src/domain/smart-info/smart-info.service.spec.ts b/server/src/domain/smart-info/smart-info.service.spec.ts
index 7dd21d7a53..373f8da91c 100644
--- a/server/src/domain/smart-info/smart-info.service.spec.ts
+++ b/server/src/domain/smart-info/smart-info.service.spec.ts
@@ -69,8 +69,8 @@ describe(SmartInfoService.name, () => {
 
       await sut.handleQueueEncodeClip({ force: false });
 
-      expect(jobMock.queueAll).toHaveBeenCalledWith([{ name: JobName.ENCODE_CLIP, data: { id: assetStub.image.id } }]);
-      expect(assetMock.getWithout).toHaveBeenCalledWith({ skip: 0, take: 1000 }, WithoutProperty.CLIP_ENCODING);
+      expect(jobMock.queueAll).toHaveBeenCalledWith([{ name: JobName.SMART_SEARCH, data: { id: assetStub.image.id } }]);
+      expect(assetMock.getWithout).toHaveBeenCalledWith({ skip: 0, take: 1000 }, WithoutProperty.SMART_SEARCH);
     });
 
     it('should queue all the assets', async () => {
@@ -81,7 +81,7 @@ describe(SmartInfoService.name, () => {
 
       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();
     });
   });
diff --git a/server/src/domain/smart-info/smart-info.service.ts b/server/src/domain/smart-info/smart-info.service.ts
index d247fcf08c..55e4b7080f 100644
--- a/server/src/domain/smart-info/smart-info.service.ts
+++ b/server/src/domain/smart-info/smart-info.service.ts
@@ -53,11 +53,13 @@ export class SmartInfoService {
     const assetPagination = usePagination(JOBS_ASSET_PAGINATION_SIZE, (pagination) => {
       return force
         ? this.assetRepository.getAll(pagination)
-        : this.assetRepository.getWithout(pagination, WithoutProperty.CLIP_ENCODING);
+        : this.assetRepository.getWithout(pagination, WithoutProperty.SMART_SEARCH);
     });
 
     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;
diff --git a/server/src/domain/system-config/system-config.core.ts b/server/src/domain/system-config/system-config.core.ts
index 0d0af03df3..926703d0dd 100644
--- a/server/src/domain/system-config/system-config.core.ts
+++ b/server/src/domain/system-config/system-config.core.ts
@@ -136,7 +136,7 @@ export const defaults = Object.freeze<SystemConfig>({
 });
 
 export enum FeatureFlag {
-  CLIP_ENCODE = 'clipEncode',
+  SMART_SEARCH = 'smartSearch',
   FACIAL_RECOGNITION = 'facialRecognition',
   MAP = 'map',
   REVERSE_GEOCODING = 'reverseGeocoding',
@@ -178,8 +178,8 @@ export class SystemConfigCore {
     const hasFeature = await this.hasFeature(feature);
     if (!hasFeature) {
       switch (feature) {
-        case FeatureFlag.CLIP_ENCODE:
-          throw new BadRequestException('Clip encoding is not enabled');
+        case FeatureFlag.SMART_SEARCH:
+          throw new BadRequestException('Smart search is not enabled');
         case FeatureFlag.FACIAL_RECOGNITION:
           throw new BadRequestException('Facial recognition is not enabled');
         case FeatureFlag.SIDECAR:
@@ -208,7 +208,7 @@ export class SystemConfigCore {
     const mlEnabled = config.machineLearning.enabled;
 
     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.MAP]: config.map.enabled,
       [FeatureFlag.REVERSE_GEOCODING]: config.reverseGeocoding.enabled,
diff --git a/server/src/infra/repositories/asset.repository.ts b/server/src/infra/repositories/asset.repository.ts
index 715718f327..226803cfb9 100644
--- a/server/src/infra/repositories/asset.repository.ts
+++ b/server/src/infra/repositories/asset.repository.ts
@@ -501,7 +501,7 @@ export class AssetRepository implements IAssetRepository {
         };
         break;
 
-      case WithoutProperty.CLIP_ENCODING:
+      case WithoutProperty.SMART_SEARCH:
         relations = {
           smartSearch: true,
         };
diff --git a/server/src/microservices/app.service.ts b/server/src/microservices/app.service.ts
index 782f7bb73f..93870583ff 100644
--- a/server/src/microservices/app.service.ts
+++ b/server/src/microservices/app.service.ts
@@ -46,8 +46,8 @@ export class AppService {
       [JobName.USER_DELETE_CHECK]: () => this.userService.handleUserDeleteCheck(),
       [JobName.USER_DELETION]: (data) => this.userService.handleUserDelete(data),
       [JobName.USER_SYNC_USAGE]: () => this.userService.handleUserSyncUsage(),
-      [JobName.QUEUE_ENCODE_CLIP]: (data) => this.smartInfoService.handleQueueEncodeClip(data),
-      [JobName.ENCODE_CLIP]: (data) => this.smartInfoService.handleEncodeClip(data),
+      [JobName.QUEUE_SMART_SEARCH]: (data) => this.smartInfoService.handleQueueEncodeClip(data),
+      [JobName.SMART_SEARCH]: (data) => this.smartInfoService.handleEncodeClip(data),
       [JobName.STORAGE_TEMPLATE_MIGRATION]: () => this.storageTemplateService.handleMigration(),
       [JobName.STORAGE_TEMPLATE_MIGRATION_SINGLE]: (data) => this.storageTemplateService.handleMigrationSingle(data),
       [JobName.QUEUE_MIGRATION]: () => this.mediaService.handleQueueMigration(),
diff --git a/web/src/lib/components/admin-page/jobs/jobs-panel.svelte b/web/src/lib/components/admin-page/jobs/jobs-panel.svelte
index c5387c8958..34604e852c 100644
--- a/web/src/lib/components/admin-page/jobs/jobs-panel.svelte
+++ b/web/src/lib/components/admin-page/jobs/jobs-panel.svelte
@@ -85,7 +85,7 @@
       icon: mdiImageSearch,
       title: api.getJobName(JobName.SmartSearch),
       subtitle: 'Run machine learning on assets to support smart search',
-      disabled: !$featureFlags.clipEncode,
+      disabled: !$featureFlags.smartSearch,
     },
     [JobName.FaceDetection]: {
       icon: mdiFaceRecognition,
diff --git a/web/src/lib/components/shared-components/search-bar/search-bar.svelte b/web/src/lib/components/shared-components/search-bar/search-bar.svelte
index af36786ef2..1d26a368fc 100644
--- a/web/src/lib/components/shared-components/search-bar/search-bar.svelte
+++ b/web/src/lib/components/shared-components/search-bar/search-bar.svelte
@@ -15,11 +15,11 @@
   $: showClearIcon = value.length > 0;
 
   function onSearch() {
-    let clipSearch = 'true';
+    let smartSearch = 'true';
     let searchValue = value;
 
     if (value.slice(0, 2) == 'm:') {
-      clipSearch = 'false';
+      smartSearch = 'false';
       searchValue = value.slice(2);
     }
 
@@ -28,7 +28,7 @@
 
     const params = new URLSearchParams({
       q: searchValue,
-      clip: clipSearch,
+      smart: smartSearch,
     });
 
     showBigSearchBar = false;
diff --git a/web/src/lib/constants.ts b/web/src/lib/constants.ts
index bbbe245900..a1ac68c216 100644
--- a/web/src/lib/constants.ts
+++ b/web/src/lib/constants.ts
@@ -63,7 +63,7 @@ export const dateFormats = {
 export enum QueryParameter {
   ACTION = 'action',
   ASSET_INDEX = 'assetIndex',
-  CLIP = 'clip',
+  SMART_SEARCH = 'smartSearch',
   MEMORY_INDEX = 'memoryIndex',
   ONBOARDING_STEP = 'step',
   OPEN_SETTING = 'openSetting',
diff --git a/web/src/lib/stores/server-config.store.ts b/web/src/lib/stores/server-config.store.ts
index 2fc17595a2..3f9df3a458 100644
--- a/web/src/lib/stores/server-config.store.ts
+++ b/web/src/lib/stores/server-config.store.ts
@@ -5,7 +5,7 @@ export type FeatureFlags = ServerFeaturesDto & { loaded: boolean };
 
 export const featureFlags = writable<FeatureFlags>({
   loaded: false,
-  clipEncode: true,
+  smartSearch: true,
   facialRecognition: true,
   sidecar: true,
   map: true,
diff --git a/web/src/routes/(user)/search/+page.svelte b/web/src/routes/(user)/search/+page.svelte
index 5c34114451..f49c7a0c58 100644
--- a/web/src/routes/(user)/search/+page.svelte
+++ b/web/src/routes/(user)/search/+page.svelte
@@ -87,7 +87,7 @@
 
   $: 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 !== '') {
       term = `m:${term}`;
     }