Add Image Generation Settings Endpoint and Update Frontend Modal: Implement a new API endpoint to fetch image generation settings, enhance the ImageQueueModal to display progress and status, and integrate the settings into the image generation workflow.

This commit is contained in:
Desktop
2025-11-12 03:50:34 +05:00
parent e89eaab0f2
commit 27ec18727c
6 changed files with 590 additions and 286 deletions

View File

@@ -10,10 +10,12 @@ import {
ContentImagesGroup,
ContentImagesResponse,
generateImages,
fetchImageGenerationSettings,
} from '../../services/api';
import { useToast } from '../../components/ui/toast/ToastContainer';
import { FileIcon, DownloadIcon, BoltIcon } from '../../icons';
import { createImagesPageConfig } from '../../config/pages/images.config';
import ImageQueueModal, { ImageQueueItem } from '../../components/common/ImageQueueModal';
export default function Images() {
const toast = useToast();
@@ -38,6 +40,11 @@ export default function Images() {
const [sortDirection, setSortDirection] = useState<'asc' | 'desc'>('asc');
const [showContent, setShowContent] = useState(false);
// Image queue modal state
const [isQueueModalOpen, setIsQueueModalOpen] = useState(false);
const [imageQueue, setImageQueue] = useState<ImageQueueItem[]>([]);
const [currentContentId, setCurrentContentId] = useState<number | null>(null);
// Load images - wrapped in useCallback
const loadImages = useCallback(async () => {
setLoading(true);
@@ -137,87 +144,105 @@ export default function Images() {
toast.info(`Bulk action "${action}" for ${ids.length} items`);
}, [toast]);
// Generate images handler
// Build image queue structure
const buildImageQueue = useCallback((contentId: number, maxInArticleImages: number) => {
const contentImages = images.find(g => g.content_id === contentId);
if (!contentImages) return [];
const queue: ImageQueueItem[] = [];
let queueIndex = 1;
// Featured image (always first)
if (contentImages.featured_image?.status === 'pending' &&
contentImages.featured_image?.prompt) {
queue.push({
imageId: contentImages.featured_image.id || null,
index: queueIndex++,
label: 'Featured Image',
type: 'featured',
contentTitle: contentImages.content_title || `Content #${contentId}`,
status: 'pending',
progress: 0,
imageUrl: null,
error: null,
});
}
// In-article images (up to max_in_article_images)
const pendingInArticle = contentImages.in_article_images
.filter(img => img.status === 'pending' && img.prompt)
.slice(0, maxInArticleImages)
.sort((a, b) => (a.position || 0) - (b.position || 0));
pendingInArticle.forEach((img, idx) => {
queue.push({
imageId: img.id || null,
index: queueIndex++,
label: `In-Article Image ${img.position || idx + 1}`,
type: 'in_article',
position: img.position || idx + 1,
contentTitle: contentImages.content_title || `Content #${contentId}`,
status: 'pending',
progress: 0,
imageUrl: null,
error: null,
});
});
return queue;
}, [images]);
// Generate images handler - Stage 1: Open modal immediately
const handleGenerateImages = useCallback(async (contentId: number) => {
try {
// Get all pending images for this content
// Get content images
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);
// Fetch image generation settings to get max_in_article_images
let maxInArticleImages = 2; // Default
try {
const settings = await fetchImageGenerationSettings();
if (settings.success && settings.config) {
maxInArticleImages = settings.config.max_in_article_images || 2;
}
});
} catch (error) {
console.warn('Failed to fetch image settings, using default:', error);
}
if (imageIds.length === 0) {
// Build image queue
const queue = buildImageQueue(contentId, maxInArticleImages);
if (queue.length === 0) {
toast.info('No pending images with prompts found for this content');
return;
}
console.log('[Generate Images] Request:', { imageIds, count: imageIds.length });
console.log('[Generate Images] Endpoint: /v1/writer/images/generate_images/');
// STAGE 1: Open modal immediately with all progress bars
setImageQueue(queue);
setCurrentContentId(contentId);
setIsQueueModalOpen(true);
const result = await generateImages(imageIds);
// Collect image IDs for API call (will be used in Stage 2)
const imageIds: number[] = queue
.map(item => item.imageId)
.filter((id): id is number => id !== null);
console.log('[Generate Images] Full Response:', result);
console.log('[Generate Images] Response Keys:', Object.keys(result));
console.log('[Generate Images] Stage 1 Complete: Modal opened with', queue.length, 'images');
console.log('[Generate Images] Image IDs to generate:', imageIds);
console.log('[Generate Images] Max in-article images from settings:', maxInArticleImages);
// TODO: Stage 2 - Start actual generation
// This will be implemented in Stage 2
if (result.success) {
// Log queued prompts if available (TEST MODE)
if (result.queued_prompts && result.queued_prompts.length > 0) {
console.log('[Generate Images] Queued Prompts (TEST MODE - NOT sent to AI):', result.queued_prompts);
console.log(`[Generate Images] Provider: ${result.provider}, Model: ${result.model}`);
result.queued_prompts.forEach((qp: any, idx: number) => {
console.log(`[Generate Images] Prompt ${idx + 1}/${result.queued_prompts.length}:`);
console.log(` - Image Type: ${qp.image_type}`);
console.log(` - Content: ${qp.content_title}`);
console.log(` - Prompt Length: ${qp.prompt_length} chars`);
console.log(` - Full Prompt:`, qp.formatted_prompt);
if (qp.negative_prompt) {
console.log(` - Negative Prompt:`, qp.negative_prompt);
}
});
}
// Show toast message (no progress modal)
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 if (failed > 0) {
toast.error(`Image generation failed: ${failed} image${failed !== 1 ? 's' : ''} failed`);
} else {
toast.success('Image generation completed');
}
loadImages(); // Reload to show new images
} else {
console.error('[Generate Images] Error:', result.error);
console.error('[Generate Images] Full Error Response:', result);
toast.error(result.error || 'Failed to generate images');
}
} catch (error: any) {
console.error('[Generate Images] Exception:', error);
console.error('[Generate Images] Error Details:', {
message: error.message,
stack: error.stack,
response: error.response,
status: error.status,
statusText: error.statusText
});
toast.error(`Failed to generate images: ${error.message}`);
toast.error(`Failed to initialize image generation: ${error.message}`);
}
}, [toast, loadImages, images]);
}, [toast, images, buildImageQueue]);
// Get max in-article images from the data (to determine column count)
const maxInArticleImages = useMemo(() => {
@@ -303,6 +328,19 @@ export default function Images() {
setCurrentPage(1);
}}
/>
<ImageQueueModal
isOpen={isQueueModalOpen}
onClose={() => {
setIsQueueModalOpen(false);
setImageQueue([]);
setCurrentContentId(null);
// Reload images after closing if generation completed
loadImages();
}}
queue={imageQueue}
totalImages={imageQueue.length}
onUpdateQueue={setImageQueue}
/>
</>
);
}