Refine photo sync status checks

This commit is contained in:
Sam Becker 2025-04-19 23:25:01 -05:00
parent fa94b707de
commit eea8f94eea
7 changed files with 35 additions and 26 deletions

View File

@ -2,7 +2,6 @@
import { Photo } from '@/photo';
import AdminPhotosTable from '@/admin/AdminPhotosTable';
import LoaderButton from '@/components/primitives/LoaderButton';
import IconGrSync from '@/components/icons/IconGrSync';
import Note from '@/components/Note';
import AdminChildPage from '@/components/AdminChildPage';
@ -12,6 +11,7 @@ import { syncPhotosAction } from '@/photo/actions';
import { useRouter } from 'next/navigation';
import ResponsiveText from '@/components/primitives/ResponsiveText';
import { LiaBroomSolid } from 'react-icons/lia';
import ProgressButton from '@/components/primitives/ProgressButton';
const UPDATE_BATCH_SIZE_MAX = 4;
@ -37,7 +37,7 @@ export default function AdminPhotosSyncClient({
breadcrumb={<ResponsiveText shortText="Needs Sync">
Needs Sync ({photos.length})
</ResponsiveText>}
accessory={<LoaderButton
accessory={<ProgressButton
primary
icon={<IconGrSync className="translate-y-[1px]" />}
hideTextOnMobile={false}
@ -68,7 +68,7 @@ export default function AdminPhotosSyncClient({
{arePhotoIdsSyncing
? 'Syncing'
: 'Sync All'}
</LoaderButton>}
</ProgressButton>}
>
<div className="space-y-6">
<Note

View File

@ -93,11 +93,7 @@ export default function AdminPhotosTable({
'text-dim',
)}>
{<>
<PhotoDate {...{
photo,
dateType,
timezone,
}} />
<PhotoDate {...{ photo, dateType, timezone }} />
{photoHasSyncStatusText(photo) &&
<Tooltip
content={photoSyncStatusText(photo)}

View File

@ -10,7 +10,7 @@ export default function Tooltip({
}) {
return (
<TooltipPrimitive {...rest}>
{children ?? <IoInformationCircleOutline size={19} />}
{children ?? <IoInformationCircleOutline size={18} />}
</TooltipPrimitive>
);
}

View File

@ -414,7 +414,7 @@ export const syncPhotoAction = async (photoId: string) =>
semanticDescription: aiSemanticDescription,
} = await generateAiImageQueries(
imageResizedBase64,
AI_TEXT_AUTO_GENERATED_FIELDS,
photo.syncStatus.missingAiTextFields,
);
const formDataFromPhoto = convertPhotoToFormData(photo);

View File

@ -599,7 +599,7 @@ const needsAiTextWhereClauses = (
switch (field) {
case 'title': return `(title <> '') IS NOT TRUE`;
case 'caption': return `(caption <> '') IS NOT TRUE`;
case 'tags': return `array_length(tags, 1) = 0`;
case 'tags': return `(tags IS NULL OR array_length(tags, 1) = 0)`;
case 'semantic': return `(semantic_description <> '') IS NOT TRUE`;
}
})

View File

@ -97,7 +97,7 @@ export interface PhotoDb extends
updatedAt: Date
createdAt: Date
takenAt: Date
tags?: string[]
tags: string[] | null
}
// Parsed db response

View File

@ -1,10 +1,11 @@
import { MAKE_FUJIFILM } from '@/platforms/fujifilm';
import { Photo, PhotoDb } from '.';
import { AI_TEXT_AUTO_GENERATED_FIELDS } from '@/app/config';
import { AiAutoGeneratedField } from './ai';
export interface PhotoSyncStatus {
isOutdated: boolean;
isMissingAiText: boolean;
missingAiTextFields: AiAutoGeneratedField[];
}
export const SYNC_QUERY_LIMIT = 1000;
@ -19,32 +20,44 @@ const isPhotoOutdated = (photo: PhotoDb) =>
photo.make === MAKE_FUJIFILM
);
const doesPhotoNeedAiText = ({
const getMissingAiTextFields = ({
title,
caption,
tags = [],
tags,
semanticDescription,
}: PhotoDb) =>
(AI_TEXT_AUTO_GENERATED_FIELDS.includes('title') && !title) ||
(AI_TEXT_AUTO_GENERATED_FIELDS.includes('caption') && !caption) ||
(AI_TEXT_AUTO_GENERATED_FIELDS.includes('tags') && tags.length === 0) ||
(AI_TEXT_AUTO_GENERATED_FIELDS.includes('semantic') && !semanticDescription);
}: PhotoDb | Photo): AiAutoGeneratedField[] =>
AI_TEXT_AUTO_GENERATED_FIELDS.reduce((fields, field) => {
switch (field) {
case 'title':
return !title ? [...fields, 'title'] : fields;
case 'caption':
return !caption ? [...fields, 'caption'] : fields;
case 'tags':
return (tags ?? []).length === 0 ? [...fields, 'tags'] : fields;
case 'semantic':
return !semanticDescription ? [...fields, 'semantic'] : fields;
}
}, [] as AiAutoGeneratedField[]);
export const generatePhotoSyncStatus = (photo: PhotoDb): PhotoSyncStatus => ({
isOutdated: isPhotoOutdated(photo),
isMissingAiText: doesPhotoNeedAiText(photo),
missingAiTextFields: getMissingAiTextFields(photo),
});
export const photoHasSyncStatusText = (photo: Photo) =>
photo.syncStatus.isOutdated || photo.syncStatus.isMissingAiText;
photo.syncStatus.isOutdated ||
photo.syncStatus.missingAiTextFields.length > 0;
export const photoSyncStatusText = (photo: Photo) => {
const { isOutdated, isMissingAiText } = photo.syncStatus;
const { isOutdated, missingAiTextFields } = photo.syncStatus;
const text: string[] = [];
if (isOutdated) {
text.push('Outdated');
} else if (isMissingAiText) {
text.push('Missing AI Text');
text.push('Outdated Data');
} else if (missingAiTextFields.length > 0) {
const missingFieldsText = missingAiTextFields
.map(field => field.toLocaleUpperCase())
.join(', ');
text.push(`Missing AI Text (${missingFieldsText})`);
}
return text.join(' and ');
};