Consolidate outdated/needs AI text sync statuses
This commit is contained in:
parent
00932b6687
commit
f22d5f85a8
@ -1,11 +1,10 @@
|
||||
import { getStoragePhotoUrlsNoStore } from '@/platforms/storage/cache';
|
||||
import { getPhotos } from '@/photo/db/query';
|
||||
import { getPhotos, getPhotosInNeedOfSyncCount } from '@/photo/db/query';
|
||||
import { getPhotosMetaCached } from '@/photo/cache';
|
||||
import AdminPhotosClient from '@/admin/AdminPhotosClient';
|
||||
import { revalidatePath } from 'next/cache';
|
||||
import { cookies } from 'next/headers';
|
||||
import { TIMEZONE_COOKIE_NAME } from '@/utility/timezone';
|
||||
import { getOutdatedPhotosCount } from '@/photo/db/query';
|
||||
import {
|
||||
AI_TEXT_GENERATION_ENABLED,
|
||||
PRESERVE_ORIGINAL_UPLOADS,
|
||||
@ -35,7 +34,7 @@ export default async function AdminPhotosPage() {
|
||||
getPhotosMetaCached({ hidden: 'include'})
|
||||
.then(({ count }) => count)
|
||||
.catch(() => 0),
|
||||
getOutdatedPhotosCount()
|
||||
getPhotosInNeedOfSyncCount()
|
||||
.catch(() => 0),
|
||||
DEBUG_PHOTO_BLOBS
|
||||
? getStoragePhotoUrlsNoStore()
|
||||
|
||||
@ -1,11 +1,11 @@
|
||||
import AdminPhotosSyncClient from '@/admin/AdminPhotosSyncClient';
|
||||
import { AI_TEXT_GENERATION_ENABLED } from '@/app/config';
|
||||
import { getOutdatedPhotos } from '@/photo/db/query';
|
||||
import { getPhotosInNeedOfSync } from '@/photo/db/query';
|
||||
|
||||
export const maxDuration = 60;
|
||||
|
||||
export default async function AdminSyncPage() {
|
||||
const photos = await getOutdatedPhotos()
|
||||
const photos = await getPhotosInNeedOfSync()
|
||||
.catch(() => []);
|
||||
|
||||
return (
|
||||
|
||||
@ -78,7 +78,7 @@ export default function AdminPhotoMenu({
|
||||
label: 'Sync',
|
||||
labelComplex: <span className="inline-flex items-center gap-2">
|
||||
<span>Sync</span>
|
||||
{photo.needsSync &&
|
||||
{(photo.syncStatus.isOutdated || photo.syncStatus.isMissingAiText) &&
|
||||
<InsightsIndicatorDot
|
||||
colorOverride="blue"
|
||||
className="translate-y-[1.5px]"
|
||||
|
||||
@ -13,6 +13,7 @@ import AdminUploadsTable from './AdminUploadsTable';
|
||||
import { Timezone } from '@/utility/timezone';
|
||||
import { useAppState } from '@/state/AppState';
|
||||
import PhotoUploadWithStatus from '@/photo/PhotoUploadWithStatus';
|
||||
import { pluralize } from '@/utility/string';
|
||||
|
||||
export default function AdminPhotosClient({
|
||||
photos,
|
||||
@ -58,9 +59,10 @@ export default function AdminPhotosClient({
|
||||
size={18}
|
||||
className="translate-y-[-1px]"
|
||||
/>}
|
||||
// TODO: Add tooltip
|
||||
// TODO: Use LinkWithStatus
|
||||
title={`${photosCountOutdated} Outdated Photos`}
|
||||
tooltip={(
|
||||
pluralize(photosCountOutdated, 'photo') +
|
||||
' needs sync'
|
||||
)}
|
||||
className={clsx(
|
||||
'text-blue-600 dark:text-blue-400',
|
||||
'border border-blue-200 dark:border-blue-800/60',
|
||||
|
||||
@ -67,9 +67,7 @@ export default function AdminPhotosSyncClient({
|
||||
>
|
||||
{arePhotoIdsSyncing
|
||||
? 'Syncing'
|
||||
: <ResponsiveText shortText={`Sync Next ${updateBatchSize}`}>
|
||||
Sync Next {updateBatchSize} Photos
|
||||
</ResponsiveText>}
|
||||
: 'Sync All'}
|
||||
</LoaderButton>}
|
||||
>
|
||||
<div className="space-y-6">
|
||||
|
||||
@ -9,13 +9,13 @@ import {
|
||||
} from '@/photo/db/query';
|
||||
import AdminAppInsightsClient from './AdminAppInsightsClient';
|
||||
import { getAllInsights, getGitHubMetaForCurrentApp } from '.';
|
||||
import { getOutdatedPhotosCount } from '@/photo/db/query';
|
||||
import { getPhotosInNeedOfSyncCount } from '@/photo/db/query';
|
||||
|
||||
export default async function AdminAppInsights() {
|
||||
const [
|
||||
{ count: photosCount, dateRange },
|
||||
{ count: photosCountHidden },
|
||||
photosCountOutdated,
|
||||
photosCountNeedSync,
|
||||
{ count: photosCountPortrait },
|
||||
codeMeta,
|
||||
cameras,
|
||||
@ -27,7 +27,7 @@ export default async function AdminAppInsights() {
|
||||
] = await Promise.all([
|
||||
getPhotosMeta({ hidden: 'include' }),
|
||||
getPhotosMeta({ hidden: 'only' }),
|
||||
getOutdatedPhotosCount(),
|
||||
getPhotosInNeedOfSyncCount(),
|
||||
getPhotosMeta({ maximumAspectRatio: 0.9 }),
|
||||
getGitHubMetaForCurrentApp(),
|
||||
getUniqueCameras(),
|
||||
@ -44,14 +44,14 @@ export default async function AdminAppInsights() {
|
||||
insights={getAllInsights({
|
||||
codeMeta,
|
||||
photosCount,
|
||||
photosCountOutdated,
|
||||
photosCountNeedSync,
|
||||
photosCountPortrait,
|
||||
tagsCount: tags.length,
|
||||
})}
|
||||
photoStats={{
|
||||
photosCount,
|
||||
photosCountHidden,
|
||||
photosCountOutdated,
|
||||
photosCountNeedSync,
|
||||
camerasCount: cameras.length,
|
||||
lensesCount: lenses.length,
|
||||
tagsCount: tags.length,
|
||||
|
||||
@ -50,7 +50,7 @@ import { HiOutlineDocumentText } from 'react-icons/hi';
|
||||
const DEBUG_COMMIT_SHA = '4cd29ed';
|
||||
const DEBUG_COMMIT_MESSAGE = 'Long commit message for debugging purposes';
|
||||
const DEBUG_BEHIND_BY = 9;
|
||||
const DEBUG_PHOTOS_COUNT_OUTDATED = 7;
|
||||
const DEBUG_PHOTOS_NEED_SYNC_COUNT = 7;
|
||||
|
||||
const TEXT_COLOR_WARNING = 'text-amber-600 dark:text-amber-500';
|
||||
const TEXT_COLOR_BLUE = 'text-blue-600 dark:text-blue-500';
|
||||
@ -91,7 +91,7 @@ export default function AdminAppInsightsClient({
|
||||
photoStats: {
|
||||
photosCount,
|
||||
photosCountHidden,
|
||||
photosCountOutdated,
|
||||
photosCountNeedSync,
|
||||
camerasCount,
|
||||
lensesCount,
|
||||
tagsCount,
|
||||
@ -114,7 +114,7 @@ export default function AdminAppInsightsClient({
|
||||
noAiRateLimiting,
|
||||
noConfiguredDomain,
|
||||
noConfiguredMeta,
|
||||
outdatedPhotos,
|
||||
photosNeedSync,
|
||||
photoMatting,
|
||||
camerasFirst,
|
||||
gridFirst,
|
||||
@ -417,7 +417,7 @@ export default function AdminAppInsightsClient({
|
||||
</AdminEmptyState>}
|
||||
</ScoreCard>
|
||||
<ScoreCard title="Library Stats">
|
||||
{(outdatedPhotos || debug) && <ScoreCardRow
|
||||
{(photosNeedSync || debug) && <ScoreCardRow
|
||||
icon={<LiaBroomSolid
|
||||
size={19}
|
||||
className={clsx(
|
||||
@ -427,7 +427,7 @@ export default function AdminAppInsightsClient({
|
||||
/>}
|
||||
content={renderHighlightText(
|
||||
pluralize(
|
||||
photosCountOutdated || DEBUG_PHOTOS_COUNT_OUTDATED,
|
||||
photosCountNeedSync || DEBUG_PHOTOS_NEED_SYNC_COUNT,
|
||||
'photo',
|
||||
) + ' need to be synced',
|
||||
'blue',
|
||||
|
||||
@ -39,7 +39,7 @@ const _INSIGHTS_TEMPLATE = [
|
||||
type AdminAppInsightRecommendation = typeof _INSIGHTS_TEMPLATE[number];
|
||||
|
||||
const _INSIGHTS_LIBRARY = [
|
||||
'outdatedPhotos',
|
||||
'photosNeedSync',
|
||||
] as const;
|
||||
type AdminAppInsightLibrary = typeof _INSIGHTS_LIBRARY[number];
|
||||
|
||||
@ -58,7 +58,7 @@ export const hasTemplateRecommendations = (insights: AdminAppInsights) =>
|
||||
export interface PhotoStats {
|
||||
photosCount: number
|
||||
photosCountHidden: number
|
||||
photosCountOutdated: number
|
||||
photosCountNeedSync: number
|
||||
camerasCount: number
|
||||
lensesCount: number
|
||||
tagsCount: number
|
||||
@ -80,10 +80,10 @@ export const getGitHubMetaForCurrentApp = () =>
|
||||
|
||||
export const getSignificantInsights = ({
|
||||
codeMeta,
|
||||
photosCountOutdated,
|
||||
photosCountNeedSync,
|
||||
}: {
|
||||
codeMeta: Awaited<ReturnType<typeof getGitHubMetaForCurrentApp>>
|
||||
photosCountOutdated: number
|
||||
photosCountNeedSync: number
|
||||
}) => {
|
||||
const {
|
||||
isAiTextGenerationEnabled,
|
||||
@ -95,7 +95,7 @@ export const getSignificantInsights = ({
|
||||
forkBehind: Boolean(codeMeta?.isBehind),
|
||||
noAiRateLimiting: isAiTextGenerationEnabled && !hasRedisStorage,
|
||||
noConfiguredDomain: !hasDomain,
|
||||
outdatedPhotos: Boolean(photosCountOutdated),
|
||||
photosNeedSync: Boolean(photosCountNeedSync),
|
||||
};
|
||||
};
|
||||
|
||||
@ -106,19 +106,19 @@ export const indicatorStatusForSignificantInsights = (
|
||||
forkBehind,
|
||||
noAiRateLimiting,
|
||||
noConfiguredDomain,
|
||||
outdatedPhotos,
|
||||
photosNeedSync,
|
||||
} = insights;
|
||||
|
||||
if (noAiRateLimiting || noConfiguredDomain) {
|
||||
return 'yellow';
|
||||
} else if (forkBehind || outdatedPhotos) {
|
||||
} else if (forkBehind || photosNeedSync) {
|
||||
return 'blue';
|
||||
}
|
||||
};
|
||||
|
||||
export const getAllInsights = ({
|
||||
codeMeta,
|
||||
photosCountOutdated,
|
||||
photosCountNeedSync,
|
||||
photosCount,
|
||||
photosCountPortrait,
|
||||
tagsCount,
|
||||
@ -127,7 +127,7 @@ export const getAllInsights = ({
|
||||
photosCountPortrait: number
|
||||
tagsCount: number
|
||||
}) => ({
|
||||
...getSignificantInsights({ codeMeta, photosCountOutdated }),
|
||||
...getSignificantInsights({ codeMeta, photosCountNeedSync }),
|
||||
noFork: !codeMeta?.isForkedFromBase && !codeMeta?.isBaseRepo,
|
||||
noAi: !AI_TEXT_GENERATION_ENABLED,
|
||||
noConfiguredMeta:
|
||||
|
||||
@ -1,4 +1,4 @@
|
||||
import { getOutdatedPhotosCount } from '@/photo/db/query';
|
||||
import { getPhotosInNeedOfSyncCount } from '@/photo/db/query';
|
||||
import {
|
||||
getSignificantInsights,
|
||||
indicatorStatusForSignificantInsights,
|
||||
@ -8,15 +8,15 @@ import { getGitHubMetaForCurrentApp } from '.';
|
||||
export const getInsightsIndicatorStatus = async () => {
|
||||
const [
|
||||
codeMeta,
|
||||
photosCountOutdated,
|
||||
photosCountNeedSync,
|
||||
] = await Promise.all([
|
||||
getGitHubMetaForCurrentApp(),
|
||||
getOutdatedPhotosCount(),
|
||||
getPhotosInNeedOfSyncCount(),
|
||||
]);
|
||||
|
||||
const significantInsights = getSignificantInsights({
|
||||
codeMeta,
|
||||
photosCountOutdated,
|
||||
photosCountNeedSync,
|
||||
});
|
||||
|
||||
return indicatorStatusForSignificantInsights(significantInsights);
|
||||
|
||||
@ -2,7 +2,6 @@ import { BiCopy } from 'react-icons/bi';
|
||||
import LoaderButton from './primitives/LoaderButton';
|
||||
import clsx from 'clsx/lite';
|
||||
import { toastSuccess } from '@/toast';
|
||||
import Tooltip from './Tooltip';
|
||||
import { ComponentProps } from 'react';
|
||||
|
||||
export default function CopyButton({
|
||||
@ -10,20 +9,18 @@ export default function CopyButton({
|
||||
text,
|
||||
subtle,
|
||||
iconSize = 15,
|
||||
tooltip,
|
||||
tooltipColor,
|
||||
className,
|
||||
...props
|
||||
}: {
|
||||
label: string
|
||||
text?: string,
|
||||
subtle?: boolean
|
||||
iconSize?: number
|
||||
tooltip?: string
|
||||
tooltipColor?: ComponentProps<typeof Tooltip>['color']
|
||||
className?: string
|
||||
}) {
|
||||
const button =
|
||||
} & ComponentProps<typeof LoaderButton>) {
|
||||
return (
|
||||
<LoaderButton
|
||||
{...props}
|
||||
icon={<BiCopy size={iconSize} />}
|
||||
className={clsx(
|
||||
subtle && 'text-gray-300 dark:text-gray-700',
|
||||
@ -37,13 +34,6 @@ export default function CopyButton({
|
||||
: undefined}
|
||||
styleAs="link"
|
||||
disabled={!text}
|
||||
/>;
|
||||
|
||||
return (
|
||||
tooltip
|
||||
? <Tooltip content={tooltip} color={tooltipColor}>
|
||||
{button}
|
||||
</Tooltip>
|
||||
: button
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
@ -2,21 +2,10 @@
|
||||
|
||||
import Spinner, { SpinnerColor } from '@/components/Spinner';
|
||||
import { clsx } from 'clsx/lite';
|
||||
import { ButtonHTMLAttributes, ReactNode } from 'react';
|
||||
import { ButtonHTMLAttributes, ComponentProps, ReactNode } from 'react';
|
||||
import Tooltip from '../Tooltip';
|
||||
|
||||
export default function LoaderButton(props: {
|
||||
isLoading?: boolean
|
||||
icon?: ReactNode
|
||||
spinnerColor?: SpinnerColor
|
||||
spinnerClassName?: string
|
||||
styleAs?: 'button' | 'link' | 'link-without-hover'
|
||||
hideTextOnMobile?: boolean
|
||||
confirmText?: string
|
||||
shouldPreventDefault?: boolean
|
||||
primary?: boolean
|
||||
hideFocusOutline?: boolean
|
||||
} & ButtonHTMLAttributes<HTMLButtonElement>) {
|
||||
const {
|
||||
export default function LoaderButton({
|
||||
children,
|
||||
isLoading,
|
||||
icon,
|
||||
@ -32,10 +21,24 @@ export default function LoaderButton(props: {
|
||||
onClick,
|
||||
disabled,
|
||||
className,
|
||||
tooltip,
|
||||
tooltipColor,
|
||||
...rest
|
||||
} = props;
|
||||
|
||||
return (
|
||||
}: {
|
||||
isLoading?: boolean
|
||||
icon?: ReactNode
|
||||
spinnerColor?: SpinnerColor
|
||||
spinnerClassName?: string
|
||||
styleAs?: 'button' | 'link' | 'link-without-hover'
|
||||
hideTextOnMobile?: boolean
|
||||
confirmText?: string
|
||||
shouldPreventDefault?: boolean
|
||||
primary?: boolean
|
||||
hideFocusOutline?: boolean
|
||||
tooltip?: string
|
||||
tooltipColor?: ComponentProps<typeof Tooltip>['color']
|
||||
} & ButtonHTMLAttributes<HTMLButtonElement>) {
|
||||
const button =
|
||||
<button
|
||||
{...rest}
|
||||
type={type}
|
||||
@ -86,6 +89,13 @@ export default function LoaderButton(props: {
|
||||
)}>
|
||||
{children}
|
||||
</span>}
|
||||
</button>
|
||||
</button>;
|
||||
|
||||
return (
|
||||
tooltip
|
||||
? <Tooltip content={tooltip} color={tooltipColor}>
|
||||
{button}
|
||||
</Tooltip>
|
||||
: button
|
||||
);
|
||||
}
|
||||
|
||||
@ -579,44 +579,22 @@ export const getPhoto = async (
|
||||
|
||||
// Sync queries
|
||||
|
||||
const outdatedWhereClause =
|
||||
`WHERE updated_at < $1 OR (updated_at < $2 AND make = $3)`;
|
||||
const outdatedWhereClauses = [
|
||||
`updated_at < $1`,
|
||||
`(updated_at < $2 AND make = $3)`,
|
||||
];
|
||||
|
||||
const outdatedValues = [
|
||||
const outdatedWhereValues = [
|
||||
UPDATED_BEFORE_01.toISOString(),
|
||||
UPDATED_BEFORE_02.toISOString(),
|
||||
MAKE_FUJIFILM,
|
||||
];
|
||||
|
||||
export const getOutdatedPhotos = () => safelyQueryPhotos(
|
||||
() => query(`
|
||||
SELECT * FROM photos
|
||||
${outdatedWhereClause}
|
||||
ORDER BY created_at DESC
|
||||
LIMIT ${SYNC_QUERY_LIMIT}
|
||||
`,
|
||||
outdatedValues,
|
||||
)
|
||||
.then(({ rows }) => rows.map(parsePhotoFromDb)),
|
||||
'getOutdatedPhotos',
|
||||
);
|
||||
|
||||
export const getOutdatedPhotosCount = () => safelyQueryPhotos(
|
||||
() => query(`
|
||||
SELECT COUNT(*) FROM photos
|
||||
${outdatedWhereClause}
|
||||
`,
|
||||
outdatedValues,
|
||||
)
|
||||
.then(({ rows }) => parseInt(rows[0].count, 10)),
|
||||
'getOutdatedPhotosCount',
|
||||
);
|
||||
|
||||
const photosThatNeedAiTextWhereClause = (
|
||||
const needsAiTextWhereClauses = (
|
||||
AI_TEXT_GENERATION_ENABLED &&
|
||||
AI_TEXT_AUTO_GENERATED_FIELDS.length
|
||||
)
|
||||
? 'WHERE ' + AI_TEXT_AUTO_GENERATED_FIELDS
|
||||
? AI_TEXT_AUTO_GENERATED_FIELDS
|
||||
.map(field => {
|
||||
switch (field) {
|
||||
case 'title': return `(title <> '') IS NOT TRUE`;
|
||||
@ -624,29 +602,32 @@ const photosThatNeedAiTextWhereClause = (
|
||||
case 'tags': return `array_length(tags, 1) = 0`;
|
||||
case 'semantic': return `(semantic_description <> '') IS NOT TRUE`;
|
||||
}
|
||||
}).join(' OR ')
|
||||
: undefined;
|
||||
})
|
||||
: [];
|
||||
|
||||
export const getPhotosThatNeedAiText = () => safelyQueryPhotos(
|
||||
async () => photosThatNeedAiTextWhereClause
|
||||
? query(`
|
||||
const needsSyncWhereStatement =
|
||||
`WHERE ${outdatedWhereClauses.concat(needsAiTextWhereClauses).join(' OR ')}`;
|
||||
|
||||
export const getPhotosInNeedOfSync = () => safelyQueryPhotos(
|
||||
() => query(`
|
||||
SELECT * FROM photos
|
||||
${photosThatNeedAiTextWhereClause}
|
||||
${needsSyncWhereStatement}
|
||||
ORDER BY created_at DESC
|
||||
LIMIT ${SYNC_QUERY_LIMIT}
|
||||
`)
|
||||
.then(({ rows }) => rows.map(parsePhotoFromDb))
|
||||
: [] as Photo[],
|
||||
'getPhotosThatNeedAiText',
|
||||
`,
|
||||
outdatedWhereValues,
|
||||
)
|
||||
.then(({ rows }) => rows.map(parsePhotoFromDb)),
|
||||
'getPhotosInNeedOfSync',
|
||||
);
|
||||
|
||||
export const getPhotosThatNeedAiTextCount = () => safelyQueryPhotos(
|
||||
async () => photosThatNeedAiTextWhereClause
|
||||
? query(`
|
||||
export const getPhotosInNeedOfSyncCount = () => safelyQueryPhotos(
|
||||
() => query(`
|
||||
SELECT COUNT(*) FROM photos
|
||||
${photosThatNeedAiTextWhereClause}
|
||||
`)
|
||||
.then(({ rows }) => parseInt(rows[0].count, 10))
|
||||
: 0,
|
||||
'getPhotosThatNeedAiTextCount',
|
||||
${needsSyncWhereStatement}
|
||||
`,
|
||||
outdatedWhereValues,
|
||||
)
|
||||
.then(({ rows }) => parseInt(rows[0].count, 10)),
|
||||
'getPhotosInNeedOfSyncCount',
|
||||
);
|
||||
|
||||
@ -23,7 +23,7 @@ import { isBefore } from 'date-fns';
|
||||
import type { Metadata } from 'next';
|
||||
import { FujifilmRecipe } from '@/platforms/fujifilm/recipe';
|
||||
import { FujifilmSimulation } from '@/platforms/fujifilm/simulation';
|
||||
import { doesPhotoNeedSync } from './sync';
|
||||
import { PhotoSyncStatus, generatePhotoSyncStatus } from './sync';
|
||||
|
||||
// INFINITE SCROLL: FEED
|
||||
export const INFINITE_SCROLL_FEED_INITIAL =
|
||||
@ -97,7 +97,7 @@ export interface PhotoDb extends
|
||||
updatedAt: Date
|
||||
createdAt: Date
|
||||
takenAt: Date
|
||||
tags: string[]
|
||||
tags?: string[]
|
||||
}
|
||||
|
||||
// Parsed db response
|
||||
@ -109,15 +109,16 @@ export interface Photo extends Omit<PhotoDb, 'recipeData'> {
|
||||
exposureTimeFormatted?: string
|
||||
exposureCompensationFormatted?: string
|
||||
takenAtNaiveFormatted: string
|
||||
tags: string[]
|
||||
recipeData?: FujifilmRecipe
|
||||
needsSync?: boolean
|
||||
syncStatus: PhotoSyncStatus
|
||||
}
|
||||
|
||||
export const parsePhotoFromDb = (photoDbRaw: PhotoDb): Photo => {
|
||||
const photoDb = camelcaseKeys(
|
||||
photoDbRaw as unknown as Record<string, unknown>,
|
||||
) as unknown as PhotoDb;
|
||||
const photo: Photo ={
|
||||
return {
|
||||
...photoDb,
|
||||
tags: photoDb.tags ?? [],
|
||||
focalLengthFormatted:
|
||||
@ -140,9 +141,8 @@ export const parsePhotoFromDb = (photoDbRaw: PhotoDb): Photo => {
|
||||
? JSON.parse(photoDb.recipeData)
|
||||
: photoDb.recipeData
|
||||
: undefined,
|
||||
};
|
||||
photo.needsSync = doesPhotoNeedSync(photo);
|
||||
return photo;
|
||||
syncStatus: generatePhotoSyncStatus(photoDb),
|
||||
} as Photo;
|
||||
};
|
||||
|
||||
export const parseCachedPhotoDates = (photo: Photo) => ({
|
||||
|
||||
@ -1,14 +1,19 @@
|
||||
import { MAKE_FUJIFILM } from '@/platforms/fujifilm';
|
||||
import { Photo } from '.';
|
||||
import { PhotoDb } from '.';
|
||||
import { AI_TEXT_AUTO_GENERATED_FIELDS } from '@/app/config';
|
||||
|
||||
export interface PhotoSyncStatus {
|
||||
isOutdated: boolean;
|
||||
isMissingAiText: boolean;
|
||||
}
|
||||
|
||||
export const SYNC_QUERY_LIMIT = 1000;
|
||||
|
||||
export const UPDATED_BEFORE_01 = new Date('2024-06-16');
|
||||
// UTC 2025-02-24 05:30:00
|
||||
export const UPDATED_BEFORE_02 = new Date(Date.UTC(2025, 1, 24, 5, 30, 0));
|
||||
|
||||
const isPhotoOutdated = (photo: Photo) =>
|
||||
const isPhotoOutdated = (photo: PhotoDb) =>
|
||||
photo.updatedAt < UPDATED_BEFORE_01 || (
|
||||
photo.updatedAt < UPDATED_BEFORE_02 &&
|
||||
photo.make === MAKE_FUJIFILM
|
||||
@ -19,12 +24,13 @@ const doesPhotoNeedAiText = ({
|
||||
caption,
|
||||
tags = [],
|
||||
semanticDescription,
|
||||
}: Photo) =>
|
||||
}: 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);
|
||||
|
||||
export const doesPhotoNeedSync = (photo: Photo) =>
|
||||
isPhotoOutdated(photo) ||
|
||||
doesPhotoNeedAiText(photo);
|
||||
export const generatePhotoSyncStatus = (photo: PhotoDb): PhotoSyncStatus => ({
|
||||
isOutdated: isPhotoOutdated(photo),
|
||||
isMissingAiText: doesPhotoNeedAiText(photo),
|
||||
});
|
||||
|
||||
Loading…
Reference in New Issue
Block a user