Add caption, semantic description to search
This commit is contained in:
parent
e21ed7942b
commit
8a03ea8217
1
.vscode/settings.json
vendored
1
.vscode/settings.json
vendored
@ -21,6 +21,7 @@
|
||||
"headlessui",
|
||||
"hgetall",
|
||||
"hset",
|
||||
"ILIKE",
|
||||
"jpgs",
|
||||
"Lightbox",
|
||||
"Makernote",
|
||||
|
||||
@ -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',
|
||||
|
||||
@ -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}
|
||||
|
||||
@ -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>
|
||||
);
|
||||
|
||||
@ -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>
|
||||
);
|
||||
|
||||
@ -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();
|
||||
|
||||
@ -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());
|
||||
|
||||
@ -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)`);
|
||||
|
||||
@ -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),
|
||||
})),
|
||||
}]
|
||||
|
||||
Loading…
Reference in New Issue
Block a user