feat: view similar photos (#21108)

* Enable filteing by example

* Drop `@GenerateSql` for `getEmbedding`?

* Improve error message

* PR Feedback

* Sort en.json

* Add SQL

* Fix lint

* Drop test that is no longer valid

* Fix i18n file sorting

* Fix TS error

* Add a `requireAccess` before pulling the embedding

* Fix decorators

* Run `make open-api`

---------

Co-authored-by: Alex <alex.tran1502@gmail.com>
This commit is contained in:
Arthur Normand
2025-09-04 10:22:09 -04:00
committed by GitHub
parent bf6211776f
commit 37a79292c0
12 changed files with 105 additions and 29 deletions

View File

@@ -22,6 +22,7 @@
import ButtonContextMenu from '$lib/components/shared-components/context-menu/button-context-menu.svelte';
import MenuOption from '$lib/components/shared-components/context-menu/menu-option.svelte';
import { AppRoute } from '$lib/constants';
import { featureFlags } from '$lib/stores/server-config.store';
import { user } from '$lib/stores/user.store';
import { photoZoomState } from '$lib/stores/zoom-image.store';
import { getAssetJobName, getSharedLink } from '$lib/utils';
@@ -41,6 +42,7 @@
import {
mdiAlertOutline,
mdiCogRefreshOutline,
mdiCompare,
mdiContentCopy,
mdiDatabaseRefreshOutline,
mdiDotsVertical,
@@ -98,6 +100,7 @@
let isOwner = $derived($user && asset.ownerId === $user?.id);
let showDownloadButton = $derived(sharedLink ? sharedLink.allowDownload : !asset.isOffline);
let isLocked = $derived(asset.visibility === AssetVisibility.Locked);
let smartSearchEnabled = $derived($featureFlags.loaded && $featureFlags.smartSearch);
// $: showEditorButton =
// isOwner &&
@@ -225,6 +228,13 @@
text={$t('view_in_timeline')}
/>
{/if}
{#if !asset.isArchived && !asset.isTrashed && smartSearchEnabled}
<MenuOption
icon={mdiCompare}
onClick={() => goto(`${AppRoute.SEARCH}?query={"queryAssetId":"${stack?.primaryAssetId ?? asset.id}"}`)}
text={$t('view_similar_photos')}
/>
{/if}
{/if}
{#if !asset.isTrashed}

View File

@@ -64,8 +64,16 @@
return validQueryTypes.has(storedQueryType) ? storedQueryType : QueryType.SMART;
}
let query = '';
if ('query' in searchQuery && searchQuery.query) {
query = searchQuery.query;
}
if ('originalFileName' in searchQuery && searchQuery.originalFileName) {
query = searchQuery.originalFileName;
}
let filter: SearchFilter = $state({
query: 'query' in searchQuery ? searchQuery.query : searchQuery.originalFileName || '',
query,
queryType: defaultQueryType(),
personIds: new SvelteSet('personIds' in searchQuery ? searchQuery.personIds : []),
tagIds:

View File

@@ -68,7 +68,7 @@
const assetInteraction = new AssetInteraction();
type SearchTerms = MetadataSearchDto & Pick<SmartSearchDto, 'query'>;
type SearchTerms = MetadataSearchDto & Pick<SmartSearchDto, 'query' | 'queryAssetId'>;
let searchQuery = $derived(page.url.searchParams.get(QueryParameter.QUERY));
let smartSearchEnabled = $derived($featureFlags.loaded && $featureFlags.smartSearch);
let terms = $derived(searchQuery ? JSON.parse(searchQuery) : {});
@@ -164,7 +164,7 @@
try {
const { albums, assets } =
'query' in searchDto && smartSearchEnabled
('query' in searchDto || 'queryAssetId' in searchDto) && smartSearchEnabled
? await searchSmart({ smartSearchDto: searchDto })
: await searchAssets({ metadataSearchDto: searchDto });
@@ -210,6 +210,7 @@
tagIds: $t('tags'),
originalFileName: $t('file_name'),
description: $t('description'),
queryAssetId: $t('query_asset_id'),
};
return keyMap[key] || key;
}