diff --git a/.vscode/settings.json b/.vscode/settings.json index f3ea8edd..1e20aba9 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -21,6 +21,7 @@ "headlessui", "hgetall", "hset", + "ILIKE", "jpgs", "Lightbox", "Makernote", diff --git a/src/components/CommandKClient.tsx b/src/components/CommandKClient.tsx index 105b1049..d0588f29 100644 --- a/src/components/CommandKClient.tsx +++ b/src/components/CommandKClient.tsx @@ -27,6 +27,7 @@ export type CommandKSection = { accessory?: ReactNode items: { label: string + keywords?: string[] annotation?: ReactNode annotationAria?: string accessory?: ReactNode @@ -157,8 +158,13 @@ export default function CommandKClient({ open={isOpen} onOpenChange={setIsOpen} label="Global Command Menu" - filter={(value, search) => - value.toLowerCase().includes(search.toLowerCase()) ? 1 : 0} + filter={(value, search, keywords) => { + const searchFormatted = search.trim().toLocaleLowerCase(); + return ( + value.toLocaleLowerCase().includes(searchFormatted) || + keywords?.includes(searchFormatted) + ) ? 1 : 0 ; + }} loop > {items.map(({ - accessory, label, + keywords, annotation, annotationAria, + accessory, path, action, }) => ); diff --git a/src/photo/PhotoTiny.tsx b/src/photo/PhotoTiny.tsx index 98eed075..f7c54af5 100644 --- a/src/photo/PhotoTiny.tsx +++ b/src/photo/PhotoTiny.tsx @@ -1,4 +1,4 @@ -import { Photo, titleForPhoto } from '.'; +import { Photo, altTextForPhoto } from '.'; import ImageTiny from '@/components/ImageTiny'; import Link from 'next/link'; import { clsx } from 'clsx/lite'; @@ -31,7 +31,7 @@ export default function PhotoTiny({ src={photo.url} aspectRatio={photo.aspectRatio} blurData={photo.blurData} - alt={titleForPhoto(photo)} + alt={altTextForPhoto(photo)} /> ); diff --git a/src/photo/ai/useAiImageQueries.ts b/src/photo/ai/useAiImageQueries.ts index 2cc369ae..cdff41f7 100644 --- a/src/photo/ai/useAiImageQueries.ts +++ b/src/photo/ai/useAiImageQueries.ts @@ -47,7 +47,9 @@ export default function useAiImageQueries( const hasRunAllQueriesOnce = useRef(false); const request = useCallback(async () => { - console.log('RUNNING ALL AI QUERIES'); + if (process.env.NODE_ENV === 'development') { + console.log('RUNNING ALL AI QUERIES'); + } hasRunAllQueriesOnce.current = true; requestTitleCaption(); requestTags(); diff --git a/src/photo/index.ts b/src/photo/index.ts index 89094cff..88059b4f 100644 --- a/src/photo/index.ts +++ b/src/photo/index.ts @@ -168,6 +168,9 @@ export const translatePhotoId = (id: string) => export const titleForPhoto = (photo: Photo) => photo.title || 'Untitled'; +export const altTextForPhoto = (photo: Photo) => + photo.semanticDescription || titleForPhoto(photo); + export const photoLabelForCount = (count: number) => count === 1 ? 'Photo' : 'Photos'; @@ -247,3 +250,9 @@ export const shouldShowCameraDataForPhoto = (photo: Photo) => export const shouldShowExifDataForPhoto = (photo: Photo) => SHOW_EXIF_DATA && photoHasExifData(photo); + +export const getKeywordsForPhoto = (photo: Photo) => + (photo.caption ?? '').split(' ') + .concat((photo.semanticDescription ?? '').split(' ')) + .filter(Boolean) + .map(keyword => keyword.toLocaleLowerCase()); diff --git a/src/services/vercel-postgres.ts b/src/services/vercel-postgres.ts index 8516c62e..a0b67f6b 100644 --- a/src/services/vercel-postgres.ts +++ b/src/services/vercel-postgres.ts @@ -294,7 +294,7 @@ export type GetPhotosOptions = { sortBy?: 'createdAt' | 'takenAt' | 'priority' limit?: number offset?: number - title?: string + query?: string tag?: string camera?: Camera simulation?: FilmSimulation @@ -344,7 +344,7 @@ export const getPhotos = async (options: GetPhotosOptions = {}) => { sortBy = PRIORITY_ORDER_ENABLED ? 'priority' : 'takenAt', limit = PHOTO_DEFAULT_LIMIT, offset = 0, - title, + query, tag, camera, simulation, @@ -370,9 +370,10 @@ export const getPhotos = async (options: GetPhotosOptions = {}) => { wheres.push(`taken_at <= $${valueIndex++}`); values.push(takenAfterInclusive.toISOString()); } - if (title) { - wheres.push(`LOWER(title) LIKE $${valueIndex++}`); - values.push(`%${title.toLowerCase()}%`); + if (query) { + // eslint-disable-next-line max-len + wheres.push(`CONCAT(title, ' ', caption, ' ', semantic_description) ILIKE $${valueIndex++}`); + values.push(`%${query.toLocaleLowerCase()}%`); } if (tag) { wheres.push(`$${valueIndex++}=ANY(tags)`); diff --git a/src/site/CommandK.tsx b/src/site/CommandK.tsx index ced26023..523207bf 100644 --- a/src/site/CommandK.tsx +++ b/src/site/CommandK.tsx @@ -19,7 +19,7 @@ import { import { formatCameraText } from '@/camera'; import { authCached } from '@/auth/cache'; import { getPhotos } from '@/services/vercel-postgres'; -import { photoQuantityText, titleForPhoto } from '@/photo'; +import { getKeywordsForPhoto, photoQuantityText, titleForPhoto } from '@/photo'; import PhotoTiny from '@/photo/PhotoTiny'; import { formatDate } from '@/utility/date'; import { formatCount, formatCountDescriptive } from '@/utility/string'; @@ -139,15 +139,14 @@ export default async function CommandK() { ]} onQueryChange={async (query) => { 'use server'; - const photos = (await getPhotos({ title: query, limit: 10 })) - .filter(({ title }) => Boolean(title)); + const photos = (await getPhotos({ query, limit: 10 })); return photos.length > 0 ? [{ heading: 'Photos', accessory: , items: photos.map(photo => ({ - accessory: , label: titleForPhoto(photo), + keywords: getKeywordsForPhoto(photo), annotation: <> {formatDate(photo.takenAt)} @@ -156,6 +155,7 @@ export default async function CommandK() { {formatDate(photo.takenAt, true)} , + accessory: , path: pathForPhoto(photo), })), }]