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:
IGNY8 VPS (Salman)
2025-11-11 20:49:11 +00:00
parent 5f11da03e4
commit 5638ea78df
11 changed files with 1511 additions and 129 deletions

View File

@@ -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);
}
}}
/>
</>
);
}