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