diff --git a/cli/src/api/open-api/api.ts b/cli/src/api/open-api/api.ts index f8b8188ac..28e2b2e2e 100644 --- a/cli/src/api/open-api/api.ts +++ b/cli/src/api/open-api/api.ts @@ -14565,22 +14565,12 @@ export const SearchApiAxiosParamCreator = function (configuration?: Configuratio * @param {string} [query] * @param {boolean} [clip] * @param {'IMAGE' | 'VIDEO' | 'AUDIO' | 'OTHER'} [type] - * @param {boolean} [isFavorite] - * @param {boolean} [isArchived] - * @param {string} [exifInfoCity] - * @param {string} [exifInfoState] - * @param {string} [exifInfoCountry] - * @param {string} [exifInfoMake] - * @param {string} [exifInfoModel] - * @param {string} [exifInfoProjectionType] - * @param {Array} [smartInfoObjects] - * @param {Array} [smartInfoTags] * @param {boolean} [recent] * @param {boolean} [motion] * @param {*} [options] Override http request option. * @throws {RequiredError} */ - search: async (q?: string, query?: string, clip?: boolean, type?: 'IMAGE' | 'VIDEO' | 'AUDIO' | 'OTHER', isFavorite?: boolean, isArchived?: boolean, exifInfoCity?: string, exifInfoState?: string, exifInfoCountry?: string, exifInfoMake?: string, exifInfoModel?: string, exifInfoProjectionType?: string, smartInfoObjects?: Array, smartInfoTags?: Array, recent?: boolean, motion?: boolean, options: AxiosRequestConfig = {}): Promise => { + search: async (q?: string, query?: string, clip?: boolean, type?: 'IMAGE' | 'VIDEO' | 'AUDIO' | 'OTHER', recent?: boolean, motion?: boolean, options: AxiosRequestConfig = {}): Promise => { const localVarPath = `/search`; // use dummy base URL string because the URL constructor only accepts absolute URLs. const localVarUrlObj = new URL(localVarPath, DUMMY_BASE_URL); @@ -14618,46 +14608,6 @@ export const SearchApiAxiosParamCreator = function (configuration?: Configuratio localVarQueryParameter['type'] = type; } - if (isFavorite !== undefined) { - localVarQueryParameter['isFavorite'] = isFavorite; - } - - if (isArchived !== undefined) { - localVarQueryParameter['isArchived'] = isArchived; - } - - if (exifInfoCity !== undefined) { - localVarQueryParameter['exifInfo.city'] = exifInfoCity; - } - - if (exifInfoState !== undefined) { - localVarQueryParameter['exifInfo.state'] = exifInfoState; - } - - if (exifInfoCountry !== undefined) { - localVarQueryParameter['exifInfo.country'] = exifInfoCountry; - } - - if (exifInfoMake !== undefined) { - localVarQueryParameter['exifInfo.make'] = exifInfoMake; - } - - if (exifInfoModel !== undefined) { - localVarQueryParameter['exifInfo.model'] = exifInfoModel; - } - - if (exifInfoProjectionType !== undefined) { - localVarQueryParameter['exifInfo.projectionType'] = exifInfoProjectionType; - } - - if (smartInfoObjects) { - localVarQueryParameter['smartInfo.objects'] = smartInfoObjects; - } - - if (smartInfoTags) { - localVarQueryParameter['smartInfo.tags'] = smartInfoTags; - } - if (recent !== undefined) { localVarQueryParameter['recent'] = recent; } @@ -14752,23 +14702,13 @@ export const SearchApiFp = function(configuration?: Configuration) { * @param {string} [query] * @param {boolean} [clip] * @param {'IMAGE' | 'VIDEO' | 'AUDIO' | 'OTHER'} [type] - * @param {boolean} [isFavorite] - * @param {boolean} [isArchived] - * @param {string} [exifInfoCity] - * @param {string} [exifInfoState] - * @param {string} [exifInfoCountry] - * @param {string} [exifInfoMake] - * @param {string} [exifInfoModel] - * @param {string} [exifInfoProjectionType] - * @param {Array} [smartInfoObjects] - * @param {Array} [smartInfoTags] * @param {boolean} [recent] * @param {boolean} [motion] * @param {*} [options] Override http request option. * @throws {RequiredError} */ - async search(q?: string, query?: string, clip?: boolean, type?: 'IMAGE' | 'VIDEO' | 'AUDIO' | 'OTHER', isFavorite?: boolean, isArchived?: boolean, exifInfoCity?: string, exifInfoState?: string, exifInfoCountry?: string, exifInfoMake?: string, exifInfoModel?: string, exifInfoProjectionType?: string, smartInfoObjects?: Array, smartInfoTags?: Array, recent?: boolean, motion?: boolean, options?: AxiosRequestConfig): Promise<(axios?: AxiosInstance, basePath?: string) => AxiosPromise> { - const localVarAxiosArgs = await localVarAxiosParamCreator.search(q, query, clip, type, isFavorite, isArchived, exifInfoCity, exifInfoState, exifInfoCountry, exifInfoMake, exifInfoModel, exifInfoProjectionType, smartInfoObjects, smartInfoTags, recent, motion, options); + async search(q?: string, query?: string, clip?: boolean, type?: 'IMAGE' | 'VIDEO' | 'AUDIO' | 'OTHER', recent?: boolean, motion?: boolean, options?: AxiosRequestConfig): Promise<(axios?: AxiosInstance, basePath?: string) => AxiosPromise> { + const localVarAxiosArgs = await localVarAxiosParamCreator.search(q, query, clip, type, recent, motion, options); return createRequestFunction(localVarAxiosArgs, globalAxios, BASE_PATH, configuration); }, /** @@ -14807,7 +14747,7 @@ export const SearchApiFactory = function (configuration?: Configuration, basePat * @throws {RequiredError} */ search(requestParameters: SearchApiSearchRequest = {}, options?: AxiosRequestConfig): AxiosPromise { - return localVarFp.search(requestParameters.q, requestParameters.query, requestParameters.clip, requestParameters.type, requestParameters.isFavorite, requestParameters.isArchived, requestParameters.exifInfoCity, requestParameters.exifInfoState, requestParameters.exifInfoCountry, requestParameters.exifInfoMake, requestParameters.exifInfoModel, requestParameters.exifInfoProjectionType, requestParameters.smartInfoObjects, requestParameters.smartInfoTags, requestParameters.recent, requestParameters.motion, options).then((request) => request(axios, basePath)); + return localVarFp.search(requestParameters.q, requestParameters.query, requestParameters.clip, requestParameters.type, requestParameters.recent, requestParameters.motion, options).then((request) => request(axios, basePath)); }, /** * @@ -14855,76 +14795,6 @@ export interface SearchApiSearchRequest { */ readonly type?: 'IMAGE' | 'VIDEO' | 'AUDIO' | 'OTHER' - /** - * - * @type {boolean} - * @memberof SearchApiSearch - */ - readonly isFavorite?: boolean - - /** - * - * @type {boolean} - * @memberof SearchApiSearch - */ - readonly isArchived?: boolean - - /** - * - * @type {string} - * @memberof SearchApiSearch - */ - readonly exifInfoCity?: string - - /** - * - * @type {string} - * @memberof SearchApiSearch - */ - readonly exifInfoState?: string - - /** - * - * @type {string} - * @memberof SearchApiSearch - */ - readonly exifInfoCountry?: string - - /** - * - * @type {string} - * @memberof SearchApiSearch - */ - readonly exifInfoMake?: string - - /** - * - * @type {string} - * @memberof SearchApiSearch - */ - readonly exifInfoModel?: string - - /** - * - * @type {string} - * @memberof SearchApiSearch - */ - readonly exifInfoProjectionType?: string - - /** - * - * @type {Array} - * @memberof SearchApiSearch - */ - readonly smartInfoObjects?: Array - - /** - * - * @type {Array} - * @memberof SearchApiSearch - */ - readonly smartInfoTags?: Array - /** * * @type {boolean} @@ -14986,7 +14856,7 @@ export class SearchApi extends BaseAPI { * @memberof SearchApi */ public search(requestParameters: SearchApiSearchRequest = {}, options?: AxiosRequestConfig) { - return SearchApiFp(this.configuration).search(requestParameters.q, requestParameters.query, requestParameters.clip, requestParameters.type, requestParameters.isFavorite, requestParameters.isArchived, requestParameters.exifInfoCity, requestParameters.exifInfoState, requestParameters.exifInfoCountry, requestParameters.exifInfoMake, requestParameters.exifInfoModel, requestParameters.exifInfoProjectionType, requestParameters.smartInfoObjects, requestParameters.smartInfoTags, requestParameters.recent, requestParameters.motion, options).then((request) => request(this.axios, this.basePath)); + return SearchApiFp(this.configuration).search(requestParameters.q, requestParameters.query, requestParameters.clip, requestParameters.type, requestParameters.recent, requestParameters.motion, options).then((request) => request(this.axios, this.basePath)); } /** diff --git a/server/src/domain/asset/response-dto/asset-response.dto.ts b/server/src/domain/asset/response-dto/asset-response.dto.ts index 45b86b282..c3a7491cf 100644 --- a/server/src/domain/asset/response-dto/asset-response.dto.ts +++ b/server/src/domain/asset/response-dto/asset-response.dto.ts @@ -1,4 +1,4 @@ -import { AssetEntity, AssetType } from '@app/infra/entities'; +import { AssetEntity, AssetFaceEntity, AssetType } from '@app/infra/entities'; import { ApiProperty } from '@nestjs/swagger'; import { PersonWithFacesResponseDto } from '../../person/person.dto'; import { TagResponseDto, mapTag } from '../../tag'; diff --git a/server/src/domain/person/person.dto.ts b/server/src/domain/person/person.dto.ts index 52777da75..2d8b142d7 100644 --- a/server/src/domain/person/person.dto.ts +++ b/server/src/domain/person/person.dto.ts @@ -153,6 +153,6 @@ export function mapFace(face: AssetFaceEntity, authUser: AuthUserDto): AssetFace boundingBoxX2: face.boundingBoxX2, boundingBoxY1: face.boundingBoxY1, boundingBoxY2: face.boundingBoxY2, - person: face.person?.ownerId === authUser.id ? mapPerson(face.person) : null, + person: face.person && face.person.ownerId === authUser.id ? mapPerson(face.person) : null, }; } diff --git a/server/src/domain/person/person.service.spec.ts b/server/src/domain/person/person.service.spec.ts index 60fe8693f..9730b1668 100644 --- a/server/src/domain/person/person.service.spec.ts +++ b/server/src/domain/person/person.service.spec.ts @@ -31,7 +31,7 @@ import { ISystemConfigRepository, WithoutProperty, } from '../repositories'; -import { PersonResponseDto, mapFace } from './person.dto'; +import { PersonResponseDto, mapFace, mapPerson } from './person.dto'; import { PersonService } from './person.service'; const responseDto: PersonResponseDto = { @@ -848,12 +848,15 @@ describe(PersonService.name, () => { describe('mapFace', () => { it('should map a face', () => { - expect(mapFace(faceStub.face1, authStub.user1)).toEqual({ - id: 'person-1', - name: 'Person 1', - birthDate: null, - thumbnailPath: '/path/to/thumbnail.jpg', - isHidden: false, + expect(mapFace(faceStub.face1, personStub.withName.owner)).toEqual({ + boundingBoxX1: 0, + boundingBoxX2: 1, + boundingBoxY1: 0, + boundingBoxY2: 1, + id: 'assetFaceId', + imageHeight: 1024, + imageWidth: 1024, + person: mapPerson(personStub.withName), }); }); diff --git a/server/src/domain/person/person.service.ts b/server/src/domain/person/person.service.ts index dc4f3e4a1..62732e4b5 100644 --- a/server/src/domain/person/person.service.ts +++ b/server/src/domain/person/person.service.ts @@ -9,7 +9,6 @@ import { usePagination } from '../domain.util'; import { IBaseJob, IEntityJob, JOBS_ASSET_PAGINATION_SIZE, JobName } from '../job'; import { FACE_THUMBNAIL_SIZE } from '../media'; import { - AssetFaceId, CropOptions, IAccessRepository, IAssetRepository, diff --git a/server/src/domain/smart-info/smart-info.constant.ts b/server/src/domain/smart-info/smart-info.constant.ts index 5710c637e..6fd863495 100644 --- a/server/src/domain/smart-info/smart-info.constant.ts +++ b/server/src/domain/smart-info/smart-info.constant.ts @@ -92,9 +92,9 @@ export const CLIP_MODEL_INFO: Record = { }, }; -function cleanModelName(modelName: string): string { +export function cleanModelName(modelName: string): string { const tokens = modelName.split('/'); - return tokens[tokens.length - 1].replace(':', '_'); + return tokens[tokens.length - 1].replace(/:/g, '_'); } export function getCLIPModelInfo(modelName: string): ModelInfo { 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 2abd66c3e..e3b5acce3 100644 --- a/server/src/domain/smart-info/smart-info.service.spec.ts +++ b/server/src/domain/smart-info/smart-info.service.spec.ts @@ -16,6 +16,7 @@ import { ISystemConfigRepository, WithoutProperty, } from '../repositories'; +import { cleanModelName, getCLIPModelInfo } from './smart-info.constant'; import { SmartInfoService } from './smart-info.service'; const asset = { @@ -203,4 +204,21 @@ describe(SmartInfoService.name, () => { ); }); }); + + describe('cleanModelName', () => { + it('should clean name', () => { + expect(cleanModelName('ViT-B-32::openai')).toEqual('ViT-B-32__openai'); + expect(cleanModelName('M-CLIP/XLM-Roberta-Large-Vit-L-14')).toEqual('XLM-Roberta-Large-Vit-L-14'); + }); + }); + + describe('getCLIPModelInfo', () => { + it('should return the model info', () => { + expect(getCLIPModelInfo('ViT-B-32__openai')).toEqual({ dimSize: 512 }); + }); + + it('should throw an error if the model is not present', () => { + expect(() => getCLIPModelInfo('test-model')).toThrow('Unknown CLIP model: test-model'); + }); + }); }); diff --git a/server/src/domain/system-config/system-config.service.spec.ts b/server/src/domain/system-config/system-config.service.spec.ts index 6ff4ac5c4..30e216a7d 100644 --- a/server/src/domain/system-config/system-config.service.spec.ts +++ b/server/src/domain/system-config/system-config.service.spec.ts @@ -13,7 +13,12 @@ import { import { BadRequestException } from '@nestjs/common'; import { newCommunicationRepositoryMock, newJobRepositoryMock, newSystemConfigRepositoryMock } from '@test'; import { JobName, QueueName } from '../job'; -import { ICommunicationRepository, IJobRepository, ISystemConfigRepository } from '../repositories'; +import { + ICommunicationRepository, + IJobRepository, + ISmartInfoRepository, + ISystemConfigRepository, +} from '../repositories'; import { defaults, SystemConfigValidator } from './system-config.core'; import { SystemConfigService } from './system-config.service'; @@ -133,13 +138,14 @@ describe(SystemConfigService.name, () => { let configMock: jest.Mocked; let communicationMock: jest.Mocked; let jobMock: jest.Mocked; + let smartInfoMock: jest.Mocked; beforeEach(async () => { delete process.env.IMMICH_CONFIG_FILE; configMock = newSystemConfigRepositoryMock(); communicationMock = newCommunicationRepositoryMock(); jobMock = newJobRepositoryMock(); - sut = new SystemConfigService(configMock, communicationMock, jobMock); + sut = new SystemConfigService(configMock, communicationMock, jobMock, smartInfoMock); }); it('should work', () => { diff --git a/web/src/api/open-api/api.ts b/web/src/api/open-api/api.ts index f8b8188ac..28e2b2e2e 100644 --- a/web/src/api/open-api/api.ts +++ b/web/src/api/open-api/api.ts @@ -14565,22 +14565,12 @@ export const SearchApiAxiosParamCreator = function (configuration?: Configuratio * @param {string} [query] * @param {boolean} [clip] * @param {'IMAGE' | 'VIDEO' | 'AUDIO' | 'OTHER'} [type] - * @param {boolean} [isFavorite] - * @param {boolean} [isArchived] - * @param {string} [exifInfoCity] - * @param {string} [exifInfoState] - * @param {string} [exifInfoCountry] - * @param {string} [exifInfoMake] - * @param {string} [exifInfoModel] - * @param {string} [exifInfoProjectionType] - * @param {Array} [smartInfoObjects] - * @param {Array} [smartInfoTags] * @param {boolean} [recent] * @param {boolean} [motion] * @param {*} [options] Override http request option. * @throws {RequiredError} */ - search: async (q?: string, query?: string, clip?: boolean, type?: 'IMAGE' | 'VIDEO' | 'AUDIO' | 'OTHER', isFavorite?: boolean, isArchived?: boolean, exifInfoCity?: string, exifInfoState?: string, exifInfoCountry?: string, exifInfoMake?: string, exifInfoModel?: string, exifInfoProjectionType?: string, smartInfoObjects?: Array, smartInfoTags?: Array, recent?: boolean, motion?: boolean, options: AxiosRequestConfig = {}): Promise => { + search: async (q?: string, query?: string, clip?: boolean, type?: 'IMAGE' | 'VIDEO' | 'AUDIO' | 'OTHER', recent?: boolean, motion?: boolean, options: AxiosRequestConfig = {}): Promise => { const localVarPath = `/search`; // use dummy base URL string because the URL constructor only accepts absolute URLs. const localVarUrlObj = new URL(localVarPath, DUMMY_BASE_URL); @@ -14618,46 +14608,6 @@ export const SearchApiAxiosParamCreator = function (configuration?: Configuratio localVarQueryParameter['type'] = type; } - if (isFavorite !== undefined) { - localVarQueryParameter['isFavorite'] = isFavorite; - } - - if (isArchived !== undefined) { - localVarQueryParameter['isArchived'] = isArchived; - } - - if (exifInfoCity !== undefined) { - localVarQueryParameter['exifInfo.city'] = exifInfoCity; - } - - if (exifInfoState !== undefined) { - localVarQueryParameter['exifInfo.state'] = exifInfoState; - } - - if (exifInfoCountry !== undefined) { - localVarQueryParameter['exifInfo.country'] = exifInfoCountry; - } - - if (exifInfoMake !== undefined) { - localVarQueryParameter['exifInfo.make'] = exifInfoMake; - } - - if (exifInfoModel !== undefined) { - localVarQueryParameter['exifInfo.model'] = exifInfoModel; - } - - if (exifInfoProjectionType !== undefined) { - localVarQueryParameter['exifInfo.projectionType'] = exifInfoProjectionType; - } - - if (smartInfoObjects) { - localVarQueryParameter['smartInfo.objects'] = smartInfoObjects; - } - - if (smartInfoTags) { - localVarQueryParameter['smartInfo.tags'] = smartInfoTags; - } - if (recent !== undefined) { localVarQueryParameter['recent'] = recent; } @@ -14752,23 +14702,13 @@ export const SearchApiFp = function(configuration?: Configuration) { * @param {string} [query] * @param {boolean} [clip] * @param {'IMAGE' | 'VIDEO' | 'AUDIO' | 'OTHER'} [type] - * @param {boolean} [isFavorite] - * @param {boolean} [isArchived] - * @param {string} [exifInfoCity] - * @param {string} [exifInfoState] - * @param {string} [exifInfoCountry] - * @param {string} [exifInfoMake] - * @param {string} [exifInfoModel] - * @param {string} [exifInfoProjectionType] - * @param {Array} [smartInfoObjects] - * @param {Array} [smartInfoTags] * @param {boolean} [recent] * @param {boolean} [motion] * @param {*} [options] Override http request option. * @throws {RequiredError} */ - async search(q?: string, query?: string, clip?: boolean, type?: 'IMAGE' | 'VIDEO' | 'AUDIO' | 'OTHER', isFavorite?: boolean, isArchived?: boolean, exifInfoCity?: string, exifInfoState?: string, exifInfoCountry?: string, exifInfoMake?: string, exifInfoModel?: string, exifInfoProjectionType?: string, smartInfoObjects?: Array, smartInfoTags?: Array, recent?: boolean, motion?: boolean, options?: AxiosRequestConfig): Promise<(axios?: AxiosInstance, basePath?: string) => AxiosPromise> { - const localVarAxiosArgs = await localVarAxiosParamCreator.search(q, query, clip, type, isFavorite, isArchived, exifInfoCity, exifInfoState, exifInfoCountry, exifInfoMake, exifInfoModel, exifInfoProjectionType, smartInfoObjects, smartInfoTags, recent, motion, options); + async search(q?: string, query?: string, clip?: boolean, type?: 'IMAGE' | 'VIDEO' | 'AUDIO' | 'OTHER', recent?: boolean, motion?: boolean, options?: AxiosRequestConfig): Promise<(axios?: AxiosInstance, basePath?: string) => AxiosPromise> { + const localVarAxiosArgs = await localVarAxiosParamCreator.search(q, query, clip, type, recent, motion, options); return createRequestFunction(localVarAxiosArgs, globalAxios, BASE_PATH, configuration); }, /** @@ -14807,7 +14747,7 @@ export const SearchApiFactory = function (configuration?: Configuration, basePat * @throws {RequiredError} */ search(requestParameters: SearchApiSearchRequest = {}, options?: AxiosRequestConfig): AxiosPromise { - return localVarFp.search(requestParameters.q, requestParameters.query, requestParameters.clip, requestParameters.type, requestParameters.isFavorite, requestParameters.isArchived, requestParameters.exifInfoCity, requestParameters.exifInfoState, requestParameters.exifInfoCountry, requestParameters.exifInfoMake, requestParameters.exifInfoModel, requestParameters.exifInfoProjectionType, requestParameters.smartInfoObjects, requestParameters.smartInfoTags, requestParameters.recent, requestParameters.motion, options).then((request) => request(axios, basePath)); + return localVarFp.search(requestParameters.q, requestParameters.query, requestParameters.clip, requestParameters.type, requestParameters.recent, requestParameters.motion, options).then((request) => request(axios, basePath)); }, /** * @@ -14855,76 +14795,6 @@ export interface SearchApiSearchRequest { */ readonly type?: 'IMAGE' | 'VIDEO' | 'AUDIO' | 'OTHER' - /** - * - * @type {boolean} - * @memberof SearchApiSearch - */ - readonly isFavorite?: boolean - - /** - * - * @type {boolean} - * @memberof SearchApiSearch - */ - readonly isArchived?: boolean - - /** - * - * @type {string} - * @memberof SearchApiSearch - */ - readonly exifInfoCity?: string - - /** - * - * @type {string} - * @memberof SearchApiSearch - */ - readonly exifInfoState?: string - - /** - * - * @type {string} - * @memberof SearchApiSearch - */ - readonly exifInfoCountry?: string - - /** - * - * @type {string} - * @memberof SearchApiSearch - */ - readonly exifInfoMake?: string - - /** - * - * @type {string} - * @memberof SearchApiSearch - */ - readonly exifInfoModel?: string - - /** - * - * @type {string} - * @memberof SearchApiSearch - */ - readonly exifInfoProjectionType?: string - - /** - * - * @type {Array} - * @memberof SearchApiSearch - */ - readonly smartInfoObjects?: Array - - /** - * - * @type {Array} - * @memberof SearchApiSearch - */ - readonly smartInfoTags?: Array - /** * * @type {boolean} @@ -14986,7 +14856,7 @@ export class SearchApi extends BaseAPI { * @memberof SearchApi */ public search(requestParameters: SearchApiSearchRequest = {}, options?: AxiosRequestConfig) { - return SearchApiFp(this.configuration).search(requestParameters.q, requestParameters.query, requestParameters.clip, requestParameters.type, requestParameters.isFavorite, requestParameters.isArchived, requestParameters.exifInfoCity, requestParameters.exifInfoState, requestParameters.exifInfoCountry, requestParameters.exifInfoMake, requestParameters.exifInfoModel, requestParameters.exifInfoProjectionType, requestParameters.smartInfoObjects, requestParameters.smartInfoTags, requestParameters.recent, requestParameters.motion, options).then((request) => request(this.axios, this.basePath)); + return SearchApiFp(this.configuration).search(requestParameters.q, requestParameters.query, requestParameters.clip, requestParameters.type, requestParameters.recent, requestParameters.motion, options).then((request) => request(this.axios, this.basePath)); } /** diff --git a/web/src/routes/(user)/explore/+page.svelte b/web/src/routes/(user)/explore/+page.svelte index c85b30881..2b694eea3 100644 --- a/web/src/routes/(user)/explore/+page.svelte +++ b/web/src/routes/(user)/explore/+page.svelte @@ -3,16 +3,8 @@ import Thumbnail from '$lib/components/assets/thumbnail/thumbnail.svelte'; import UserPageLayout from '$lib/components/layouts/user-page-layout.svelte'; import { AppRoute } from '$lib/constants'; - import { AssetTypeEnum, SearchExploreResponseDto, api } from '@api'; - import Icon from '$lib/components/elements/icon.svelte'; + import { SearchExploreResponseDto, api } from '@api'; import type { PageData } from './$types'; - import { - mdiHeartMultipleOutline, - mdiClockOutline, - mdiPlayCircleOutline, - mdiMotionPlayOutline, - mdiRotate360, - } from '@mdi/js'; export let data: PageData;