feat(web): add full-path search mode to UI (#26758)

Co-authored-by: mws-weekend-projects <mws-weekend-projects@users.noreply.github.com>
Co-authored-by: Mert <101130780+mertalev@users.noreply.github.com>
Co-authored-by: Daniel Dietzler <mail@ddietzler.dev>
Co-authored-by: Daniel Dietzler <36593685+danieldietzler@users.noreply.github.com>
pull/25579/head
mws-weekend-projects 2026-05-06 15:45:40 +02:00 committed by GitHub
parent 6580394cfe
commit 90a69e2ba6
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
8 changed files with 46 additions and 3 deletions

View File

@ -18,6 +18,7 @@ You can search the following types of content:
| People | Faces that are recognized in your photos/videos. |
| Contextual | Content of the photos and videos. |
| File name or extension | Full or partial file's name, or file's extension |
| Full path or folder | Full or partial folder names from the original path. |
| Description | Description added to assets. |
| Optical Character Recognition (OCR) | Text in images |
| Locations | Cities, states, and countries from reverse geocoding. |
@ -30,6 +31,12 @@ You can search the following types of content:
<img src={require('./img/advanced-search-filters.webp').default} width="70%" title='Advanced search filters' />
### Full path or folder
Use this mode when you know a folder name or part of the original asset path.
Example: for /John/Projects/3D_Printing/2026-07-01/IMG_0001.jpg, searches like Projects, 3D, Printing, or 2026 match the asset.
## Configuration
Navigating to `Administration > Settings > Machine Learning Settings > Smart Search` will show the options available.

View File

@ -1240,6 +1240,7 @@
"free_up_space_description": "Move backed-up photos and videos to your device's trash to free up space. Your copies on the server remain safe.",
"free_up_space_settings_subtitle": "Free up device storage",
"full_path": "Full path: {path}",
"full_path_or_folder": "Full path or folder",
"gcast_enabled": "Google Cast",
"gcast_enabled_description": "This feature loads external resources from Google in order to work.",
"general": "General",
@ -1943,6 +1944,8 @@
"search_by_description_example": "Hiking day in Sapa",
"search_by_filename": "Search by file name or extension",
"search_by_filename_example": "i.e. IMG_1234.JPG or PNG",
"search_by_full_path": "Search by full path or folder",
"search_by_full_path_example": "/John/Projects/3D_Printing/2026-07-01 - you can search for Projects, 3D, Printing, 2026 etc.",
"search_by_ocr": "Search by OCR",
"search_by_ocr_example": "Latte",
"search_camera_lens_model": "Search lens model...",

View File

@ -88,6 +88,10 @@
case 'description': {
return { description: term };
}
case 'fullPath': {
const normalizedTerm = term.trim();
return normalizedTerm ? { originalPath: normalizedTerm } : {};
}
case 'ocr': {
return { ocr: term };
}
@ -198,6 +202,7 @@
case 'smart':
case 'metadata':
case 'description':
case 'fullPath':
case 'ocr': {
currentSearchType = searchType;
return searchType;
@ -220,6 +225,9 @@
case 'description': {
return $t('description');
}
case 'fullPath': {
return $t('full_path_or_folder');
}
case 'ocr': {
return $t('ocr');
}
@ -237,6 +245,7 @@
{ value: 'smart', label: () => $t('context') },
{ value: 'metadata', label: () => $t('filename') },
{ value: 'description', label: () => $t('description') },
{ value: 'fullPath', label: () => $t('full_path_or_folder') },
{ value: 'ocr', label: () => $t('ocr') },
] as const;
</script>

View File

@ -6,7 +6,7 @@
interface Props {
query: string | undefined;
queryType?: 'smart' | 'metadata' | 'description' | 'ocr';
queryType?: 'smart' | 'metadata' | 'description' | 'fullPath' | 'ocr';
}
let { query = $bindable(), queryType = $bindable('smart') }: Props = $props();
@ -33,6 +33,13 @@
bind:group={queryType}
value="description"
/>
<RadioButton
name="query-type"
id="full-path-radio"
label={$t('full_path_or_folder')}
bind:group={queryType}
value="fullPath"
/>
{#if featureFlagsManager.value.ocr}
<RadioButton name="query-type" id="ocr-radio" label={$t('ocr')} bind:group={queryType} value="ocr" />
{/if}
@ -51,6 +58,10 @@
<Field label={$t('search_by_description')}>
<Input type="text" placeholder={$t('search_by_description_example')} bind:value={query} />
</Field>
{:else if queryType === 'fullPath'}
<Field label={$t('search_by_full_path')}>
<Input type="text" placeholder={$t('search_by_full_path_example')} bind:value={query} />
</Field>
{:else if queryType === 'ocr'}
<Field label={$t('search_by_ocr')}>
<Input type="text" placeholder={$t('search_by_ocr_example')} bind:value={query} />

View File

@ -87,10 +87,17 @@ export enum QueryType {
SMART = 'smart',
METADATA = 'metadata',
DESCRIPTION = 'description',
FULL_PATH = 'fullPath',
OCR = 'ocr',
}
export const validQueryTypes = new Set([QueryType.SMART, QueryType.METADATA, QueryType.DESCRIPTION, QueryType.OCR]);
export const validQueryTypes = new Set([
QueryType.SMART,
QueryType.METADATA,
QueryType.DESCRIPTION,
QueryType.FULL_PATH,
QueryType.OCR,
]);
export const locales = [
{ code: 'af-ZA', name: 'Afrikaans (South Africa)' },

View File

@ -54,6 +54,10 @@
query = searchQuery.originalFileName;
}
if ('originalPath' in searchQuery && searchQuery.originalPath) {
query = searchQuery.originalPath;
}
return {
query,
ocr: searchQuery.ocr,
@ -133,6 +137,7 @@
ocr: filter.queryType === 'ocr' ? query : undefined,
originalFileName: filter.queryType === 'metadata' ? query : undefined,
description: filter.queryType === 'description' ? query : undefined,
originalPath: filter.queryType === 'fullPath' ? filter.query.trim() || undefined : undefined,
country: filter.location.country,
state: filter.location.state,
city: filter.location.city,

View File

@ -80,7 +80,7 @@ export type SearchLocationFilter = {
export type SearchFilter = {
query: string;
ocr?: string;
queryType: 'smart' | 'metadata' | 'description' | 'ocr';
queryType: 'smart' | 'metadata' | 'description' | 'fullPath' | 'ocr';
personIds: SvelteSet<string>;
tagIds: SvelteSet<string> | null;
location: SearchLocationFilter;

View File

@ -184,6 +184,7 @@
personIds: $t('people'),
tagIds: $t('tags'),
originalFileName: $t('file_name_text'),
originalPath: $t('full_path_or_folder'),
description: $t('description'),
queryAssetId: $t('query_asset_id'),
ocr: $t('ocr'),