Add Image Generation from Prompts: Implement new functionality to generate images from prompts, including backend processing, API integration, and frontend handling with progress modal. Update settings and registry for new AI function.
This commit is contained in:
@@ -3,19 +3,26 @@
|
||||
* Shows content images grouped by content - one row per content
|
||||
*/
|
||||
|
||||
import { useState, useEffect, useMemo, useCallback } from 'react';
|
||||
import { useState, useEffect, useMemo, useCallback, useRef } from 'react';
|
||||
import TablePageTemplate from '../../templates/TablePageTemplate';
|
||||
import {
|
||||
fetchContentImages,
|
||||
ContentImagesGroup,
|
||||
ContentImagesResponse,
|
||||
generateImages,
|
||||
} from '../../services/api';
|
||||
import { useToast } from '../../components/ui/toast/ToastContainer';
|
||||
import { FileIcon, DownloadIcon } from '../../icons';
|
||||
import { FileIcon, DownloadIcon, BoltIcon } from '../../icons';
|
||||
import { createImagesPageConfig } from '../../config/pages/images.config';
|
||||
import ProgressModal from '../../components/common/ProgressModal';
|
||||
import { useProgressModal } from '../../hooks/useProgressModal';
|
||||
|
||||
export default function Images() {
|
||||
const toast = useToast();
|
||||
|
||||
// Progress modal for AI functions
|
||||
const progressModal = useProgressModal();
|
||||
const hasReloadedRef = useRef(false);
|
||||
|
||||
// Data state
|
||||
const [images, setImages] = useState<ContentImagesGroup[]>([]);
|
||||
@@ -136,6 +143,62 @@ export default function Images() {
|
||||
toast.info(`Bulk action "${action}" for ${ids.length} items`);
|
||||
}, [toast]);
|
||||
|
||||
// Generate images handler
|
||||
const handleGenerateImages = useCallback(async (contentId: number) => {
|
||||
try {
|
||||
// Get all pending images for this content
|
||||
const contentImages = images.find(g => g.content_id === contentId);
|
||||
if (!contentImages) {
|
||||
toast.error('Content not found');
|
||||
return;
|
||||
}
|
||||
|
||||
// Collect all image IDs with prompts and pending status
|
||||
const imageIds: number[] = [];
|
||||
if (contentImages.featured_image?.id &&
|
||||
contentImages.featured_image.status === 'pending' &&
|
||||
contentImages.featured_image.prompt) {
|
||||
imageIds.push(contentImages.featured_image.id);
|
||||
}
|
||||
contentImages.in_article_images.forEach(img => {
|
||||
if (img.id && img.status === 'pending' && img.prompt) {
|
||||
imageIds.push(img.id);
|
||||
}
|
||||
});
|
||||
|
||||
if (imageIds.length === 0) {
|
||||
toast.info('No pending images with prompts found for this content');
|
||||
return;
|
||||
}
|
||||
|
||||
const result = await generateImages(imageIds);
|
||||
if (result.success) {
|
||||
if (result.task_id) {
|
||||
// Open progress modal for async task
|
||||
progressModal.openModal(
|
||||
result.task_id,
|
||||
'Generate Images',
|
||||
'ai-generate-images-from-prompts-01-desktop'
|
||||
);
|
||||
} else {
|
||||
// Synchronous completion
|
||||
const generated = result.images_generated || 0;
|
||||
const failed = result.images_failed || 0;
|
||||
if (generated > 0) {
|
||||
toast.success(`Images generated: ${generated} image${generated !== 1 ? 's' : ''} created${failed > 0 ? `, ${failed} failed` : ''}`);
|
||||
} else {
|
||||
toast.error(`Image generation failed: ${failed} image${failed !== 1 ? 's' : ''} failed`);
|
||||
}
|
||||
loadImages(); // Reload to show new images
|
||||
}
|
||||
} else {
|
||||
toast.error(result.error || 'Failed to generate images');
|
||||
}
|
||||
} catch (error: any) {
|
||||
toast.error(`Failed to generate images: ${error.message}`);
|
||||
}
|
||||
}, [toast, progressModal, loadImages, images]);
|
||||
|
||||
// Get max in-article images from the data (to determine column count)
|
||||
const maxInArticleImages = useMemo(() => {
|
||||
if (images.length === 0) return 5; // Default
|
||||
@@ -152,8 +215,9 @@ export default function Images() {
|
||||
setStatusFilter,
|
||||
setCurrentPage,
|
||||
maxInArticleImages,
|
||||
onGenerateImages: handleGenerateImages,
|
||||
});
|
||||
}, [searchTerm, statusFilter, maxInArticleImages]);
|
||||
}, [searchTerm, statusFilter, maxInArticleImages, handleGenerateImages]);
|
||||
|
||||
// Calculate header metrics
|
||||
const headerMetrics = useMemo(() => {
|
||||
@@ -166,6 +230,7 @@ export default function Images() {
|
||||
}, [pageConfig?.headerMetrics, images, totalCount]);
|
||||
|
||||
return (
|
||||
<>
|
||||
<TablePageTemplate
|
||||
title="Content Images"
|
||||
titleIcon={<FileIcon className="text-purple-500 size-5" />}
|
||||
@@ -218,5 +283,30 @@ export default function Images() {
|
||||
setCurrentPage(1);
|
||||
}}
|
||||
/>
|
||||
|
||||
{/* Progress Modal for AI Functions */}
|
||||
<ProgressModal
|
||||
isOpen={progressModal.isOpen}
|
||||
title={progressModal.title}
|
||||
percentage={progressModal.progress.percentage}
|
||||
status={progressModal.progress.status}
|
||||
message={progressModal.progress.message}
|
||||
details={progressModal.progress.details}
|
||||
taskId={progressModal.taskId || undefined}
|
||||
functionId={progressModal.functionId}
|
||||
onClose={() => {
|
||||
const wasCompleted = progressModal.progress.status === 'completed';
|
||||
progressModal.closeModal();
|
||||
// Reload data after modal closes (if completed)
|
||||
if (wasCompleted && !hasReloadedRef.current) {
|
||||
hasReloadedRef.current = true;
|
||||
loadImages();
|
||||
setTimeout(() => {
|
||||
hasReloadedRef.current = false;
|
||||
}, 1000);
|
||||
}
|
||||
}}
|
||||
/>
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user