diff --git a/src/admin/AdminPhotosSyncClient.tsx b/src/admin/AdminPhotosSyncClient.tsx
index b55ee092..94479816 100644
--- a/src/admin/AdminPhotosSyncClient.tsx
+++ b/src/admin/AdminPhotosSyncClient.tsx
@@ -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={
Needs Sync ({photos.length})
}
- accessory={}
hideTextOnMobile={false}
@@ -68,7 +68,7 @@ export default function AdminPhotosSyncClient({
{arePhotoIdsSyncing
? 'Syncing'
: 'Sync All'}
- }
+ }
>
{<>
-
+
{photoHasSyncStatusText(photo) &&
- {children ?? }
+ {children ?? }
);
}
diff --git a/src/photo/actions.ts b/src/photo/actions.ts
index 6513ba29..3ec326bd 100644
--- a/src/photo/actions.ts
+++ b/src/photo/actions.ts
@@ -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);
diff --git a/src/photo/db/query.ts b/src/photo/db/query.ts
index 246befa3..07e73777 100644
--- a/src/photo/db/query.ts
+++ b/src/photo/db/query.ts
@@ -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`;
}
})
diff --git a/src/photo/index.ts b/src/photo/index.ts
index fba70537..a93c3675 100644
--- a/src/photo/index.ts
+++ b/src/photo/index.ts
@@ -97,7 +97,7 @@ export interface PhotoDb extends
updatedAt: Date
createdAt: Date
takenAt: Date
- tags?: string[]
+ tags: string[] | null
}
// Parsed db response
diff --git a/src/photo/sync.ts b/src/photo/sync.ts
index d28dcbce..657bce5c 100644
--- a/src/photo/sync.ts
+++ b/src/photo/sync.ts
@@ -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 ');
};