Add caption, semantic description to search

This commit is contained in:
Sam Becker 2024-03-21 22:37:04 -05:00
parent e21ed7942b
commit 8a03ea8217
9 changed files with 40 additions and 18 deletions

View File

@ -21,6 +21,7 @@
"headlessui",
"hgetall",
"hset",
"ILIKE",
"jpgs",
"Lightbox",
"Makernote",

View File

@ -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
>
<Modal
@ -223,16 +229,18 @@ export default function CommandKClient({
)}
>
{items.map(({
accessory,
label,
keywords,
annotation,
annotationAria,
accessory,
path,
action,
}) =>
<Command.Item
key={`${heading} ${label}`}
value={`${heading} ${label}`}
keywords={keywords}
className={clsx(
'px-2',
accessory ? 'py-2' : 'py-1',

View File

@ -1,5 +1,6 @@
import {
Photo,
altTextForPhoto,
shouldShowCameraDataForPhoto,
shouldShowExifDataForPhoto,
titleForPhoto,
@ -54,7 +55,7 @@ export default function PhotoLarge({
contentMain={
<ImageLarge
className="w-full"
alt={titleForPhoto(photo)}
alt={altTextForPhoto(photo)}
href={pathForPhoto(photo, primaryTag)}
src={photo.url}
aspectRatio={photo.aspectRatio}

View File

@ -1,4 +1,4 @@
import { Photo, titleForPhoto } from '.';
import { Photo, altTextForPhoto } from '.';
import ImageSmall from '@/components/ImageSmall';
import Link from 'next/link';
import { clsx } from 'clsx/lite';
@ -34,7 +34,7 @@ export default function PhotoSmall({
aspectRatio={photo.aspectRatio}
blurData={photo.blurData}
className="w-full"
alt={titleForPhoto(photo)}
alt={altTextForPhoto(photo)}
/>
</Link>
);

View File

@ -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)}
/>
</Link>
);

View File

@ -47,7 +47,9 @@ export default function useAiImageQueries(
const hasRunAllQueriesOnce = useRef(false);
const request = useCallback(async () => {
if (process.env.NODE_ENV === 'development') {
console.log('RUNNING ALL AI QUERIES');
}
hasRunAllQueriesOnce.current = true;
requestTitleCaption();
requestTags();

View File

@ -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());

View File

@ -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)`);

View File

@ -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: <TbPhoto size={14} />,
items: photos.map(photo => ({
accessory: <PhotoTiny photo={photo} />,
label: titleForPhoto(photo),
keywords: getKeywordsForPhoto(photo),
annotation: <>
<span className="hidden sm:inline-block">
{formatDate(photo.takenAt)}
@ -156,6 +155,7 @@ export default async function CommandK() {
{formatDate(photo.takenAt, true)}
</span>
</>,
accessory: <PhotoTiny photo={photo} />,
path: pathForPhoto(photo),
})),
}]