diff --git a/backend/igny8_core/ai/functions/generate_images_from_prompts.py b/backend/igny8_core/ai/functions/generate_images_from_prompts.py index 90bea7e5..2a54202e 100644 --- a/backend/igny8_core/ai/functions/generate_images_from_prompts.py +++ b/backend/igny8_core/ai/functions/generate_images_from_prompts.py @@ -89,26 +89,89 @@ class GenerateImagesFromPromptsFunction(BaseAIFunction): if not images: raise ValueError("No pending images found with prompts") - # Get image generation settings + # Get image generation settings - CHECK IF ENABLED image_settings = {} + image_generation_enabled = False if account: try: from igny8_core.modules.system.models import IntegrationSettings integration = IntegrationSettings.objects.get( account=account, - integration_type='image_generation', - is_active=True + integration_type='image_generation' ) + image_generation_enabled = integration.is_active image_settings = integration.config or {} + logger.info(f"[generate_images_from_prompts] Image generation settings: enabled={image_generation_enabled}, config_keys={list(image_settings.keys())}") + except IntegrationSettings.DoesNotExist: + logger.warning(f"[generate_images_from_prompts] Image generation integration not found for account {account.id}") + raise ValueError("Image generation integration not configured") except Exception as e: - logger.warning(f"Failed to load image generation settings: {e}") + logger.error(f"[generate_images_from_prompts] Failed to load image generation settings: {e}") + raise ValueError(f"Failed to load image generation settings: {str(e)}") - # Extract settings with defaults + if not image_generation_enabled: + raise ValueError("Image generation is not enabled in settings") + + # Get provider from image_generation settings provider = image_settings.get('provider') or image_settings.get('service', 'openai') - if provider == 'runware': + logger.info(f"[generate_images_from_prompts] Provider from settings: {provider}") + + # Get provider-specific settings (OpenAI or Runware) - CHECK IF ENABLED + provider_api_key = None + provider_enabled = False + provider_model = None + + if provider == 'openai': + try: + openai_settings = IntegrationSettings.objects.get( + account=account, + integration_type='openai' + ) + provider_enabled = openai_settings.is_active + provider_api_key = openai_settings.config.get('apiKey') if openai_settings.config else None + provider_model = openai_settings.config.get('model') if openai_settings.config else None + logger.info(f"[generate_images_from_prompts] OpenAI settings: enabled={provider_enabled}, has_key={bool(provider_api_key)}, model={provider_model}") + except IntegrationSettings.DoesNotExist: + logger.error(f"[generate_images_from_prompts] OpenAI integration not found") + raise ValueError("OpenAI integration not configured") + except Exception as e: + logger.error(f"[generate_images_from_prompts] Error getting OpenAI settings: {e}") + raise ValueError(f"Failed to load OpenAI settings: {str(e)}") + elif provider == 'runware': + try: + runware_settings = IntegrationSettings.objects.get( + account=account, + integration_type='runware' + ) + provider_enabled = runware_settings.is_active + provider_api_key = runware_settings.config.get('apiKey') if runware_settings.config else None + provider_model = runware_settings.config.get('model') if runware_settings.config else None + logger.info(f"[generate_images_from_prompts] Runware settings: enabled={provider_enabled}, has_key={bool(provider_api_key)}, model={provider_model}") + except IntegrationSettings.DoesNotExist: + logger.error(f"[generate_images_from_prompts] Runware integration not found") + raise ValueError("Runware integration not configured") + except Exception as e: + logger.error(f"[generate_images_from_prompts] Error getting Runware settings: {e}") + raise ValueError(f"Failed to load Runware settings: {str(e)}") + else: + raise ValueError(f"Invalid provider: {provider}") + + # Validate provider is enabled and has API key + if not provider_enabled: + raise ValueError(f"{provider.capitalize()} integration is not enabled") + + if not provider_api_key: + raise ValueError(f"{provider.capitalize()} API key not configured") + + # Determine model: from provider settings, or image_generation settings, or default + if provider_model: + model = provider_model + elif provider == 'runware': model = image_settings.get('model') or image_settings.get('runwareModel', 'runware:97@1') else: - model = image_settings.get('model', 'dall-e-3') + model = image_settings.get('model') or image_settings.get('imageModel', 'dall-e-3') + + logger.info(f"[generate_images_from_prompts] Final settings: provider={provider}, model={model}, enabled={provider_enabled}, has_api_key={bool(provider_api_key)}") # Get prompt templates image_prompt_template = PromptRegistry.get_image_prompt_template(account) @@ -119,6 +182,7 @@ class GenerateImagesFromPromptsFunction(BaseAIFunction): 'account': account, 'provider': provider, 'model': model, + 'api_key': provider_api_key, # Include API key 'image_type': image_settings.get('image_type', 'realistic'), 'image_format': image_settings.get('image_format', 'webp'), 'image_prompt_template': image_prompt_template, @@ -167,10 +231,18 @@ class GenerateImagesFromPromptsFunction(BaseAIFunction): provider = original_data.get('provider', 'openai') model = original_data.get('model', 'dall-e-3') + api_key = original_data.get('api_key') # Get API key from prepare image_type = original_data.get('image_type', 'realistic') image_prompt_template = original_data.get('image_prompt_template', '') negative_prompt = original_data.get('negative_prompt', '') + # Validate API key is present + if not api_key: + error_msg = f"[{function_name}] API key not found for provider {provider}" + if console_tracker: + console_tracker.error('ConfigurationError', error_msg) + raise ValueError(error_msg) + ai_core = AICore(account=account or original_data.get('account')) total_images = len(images) @@ -212,6 +284,10 @@ class GenerateImagesFromPromptsFunction(BaseAIFunction): if console_tracker: console_tracker.prep(f"[{function_name}] Image queue initialized with {total_images} image{'s' if total_images != 1 else ''}") + console_tracker.prep(f"[{function_name}] Provider: {provider}, Model: {model}, API Key: {'***' + api_key[-4:] if api_key and len(api_key) > 4 else 'NOT SET'}") + + # Queue all prompts first (TEST MODE - don't send to AI) + queued_prompts = [] # Process each image sequentially for index, image in enumerate(images, 1): @@ -283,114 +359,60 @@ class GenerateImagesFromPromptsFunction(BaseAIFunction): meta['image_queue'] = image_queue progress_tracker.update("AI_CALL", progress_pct, ai_msg, meta=meta) - # Generate image (this is the actual API call) - # Frontend will simulate smooth progress from 50% to 95% while waiting - if console_tracker: - console_tracker.ai_call(f"[{function_name}] Calling {provider}/{model} API for image {index}/{total_images}") - - result = ai_core.generate_image( - prompt=formatted_prompt, - provider=provider, - model=model, - size='1024x1024', - negative_prompt=negative_prompt if provider == 'runware' else None, - function_name='generate_images_from_prompts' - ) - - # Update progress to 90% (API call completed, processing response) - queue_item['progress'] = 90 - if progress_tracker and step_tracker: - meta = step_tracker.get_meta() - meta['image_queue'] = image_queue - progress_tracker.update("AI_CALL", progress_pct, ai_msg, meta=meta) - - if result.get('error'): - # Mark as failed - queue_item['status'] = 'failed' - queue_item['progress'] = 100 - queue_item['error'] = result['error'] - with transaction.atomic(): - image.status = 'failed' - image.save(update_fields=['status', 'updated_at']) - - error_msg = f"[{function_name}] Image {index}/{total_images} failed: {result['error']}" - errors.append(error_msg) - images_failed += 1 - logger.error(f"Image generation failed for image {image.id}: {result['error']}") - - if console_tracker: - console_tracker.error('ImageGenerationError', error_msg) - - if progress_tracker and step_tracker: - parse_msg = f"Image {index} failed: {result['error']}" - step_tracker.add_response_step("PARSE", "error", parse_msg) - meta = step_tracker.get_meta() - meta['image_queue'] = image_queue - progress_pct = 70 + int((index - 1) / total_images * 15) # 70-85% for PARSE - progress_tracker.update("PARSE", progress_pct, parse_msg, meta=meta) - - continue - - image_url = result.get('url') - if not image_url: - # Mark as failed - queue_item['status'] = 'failed' - queue_item['progress'] = 100 - queue_item['error'] = 'No URL returned' - with transaction.atomic(): - image.status = 'failed' - image.save(update_fields=['status', 'updated_at']) - - error_msg = f"[{function_name}] Image {index}/{total_images} failed: No URL returned" - errors.append(error_msg) - images_failed += 1 - logger.error(f"No image URL returned for image {image.id}") - - if console_tracker: - console_tracker.error('ImageGenerationError', error_msg) - - if progress_tracker and step_tracker: - parse_msg = f"Image {index} failed: No URL returned" - step_tracker.add_response_step("PARSE", "error", parse_msg) - meta = step_tracker.get_meta() - meta['image_queue'] = image_queue - progress_pct = 70 + int((index - 1) / total_images * 15) - progress_tracker.update("PARSE", progress_pct, parse_msg, meta=meta) - - continue - - # Update progress: PARSE phase (90%) - queue_item['progress'] = 90 - if progress_tracker and step_tracker: - parse_msg = f"Image {index} of {total_images} generated successfully" - step_tracker.add_response_step("PARSE", "success", parse_msg) - meta = step_tracker.get_meta() - meta['image_queue'] = image_queue - progress_pct = 70 + int((index - 1) / total_images * 15) # 70-85% for PARSE - progress_tracker.update("PARSE", progress_pct, parse_msg, meta=meta) + # Queue the complete prompt (TEST MODE - don't send to AI yet) + queued_prompts.append({ + 'image_id': image.id, + 'index': index, + 'image_type': image.image_type, + 'content_title': content_title, + 'provider': provider, + 'model': model, + 'formatted_prompt': formatted_prompt, + 'negative_prompt': negative_prompt if provider == 'runware' else None, + 'prompt_length': len(formatted_prompt) + }) if console_tracker: - console_tracker.parse(f"[{function_name}] Image {index}/{total_images} generated successfully: {image_url[:50]}...") + console_tracker.ai_call(f"[{function_name}] [TEST MODE] Queued prompt {index}/{total_images}: {image.image_type} for '{content_title}'") + console_tracker.ai_call(f"[{function_name}] [TEST MODE] Provider: {provider}, Model: {model}") + console_tracker.ai_call(f"[{function_name}] [TEST MODE] Prompt length: {len(formatted_prompt)} chars") + console_tracker.ai_call(f"[{function_name}] [TEST MODE] Prompt preview: {formatted_prompt[:150]}...") - # Update image record - with transaction.atomic(): - image.image_url = image_url - image.status = 'generated' - image.save(update_fields=['image_url', 'status', 'updated_at']) + # TEMPORARY: Simulate result for testing (don't actually call AI) + result = { + 'url': None, + 'error': None, + 'test_mode': True, + 'queued': True + } - # Mark queue item as completed + # ACTUAL AI CALL (COMMENTED OUT FOR TESTING) + # if console_tracker: + # console_tracker.ai_call(f"[{function_name}] Calling {provider}/{model} API for image {index}/{total_images}") + # + # result = ai_core.generate_image( + # prompt=formatted_prompt, + # provider=provider, + # model=model, + # size='1024x1024', + # n=1, + # api_key=api_key, # Pass API key explicitly + # negative_prompt=negative_prompt if provider == 'runware' else None, + # function_name='generate_images_from_prompts' + # ) + + # TEST MODE: Mark as queued (not actually generated) queue_item['status'] = 'completed' queue_item['progress'] = 100 - queue_item['image_url'] = image_url - images_generated += 1 - logger.info(f"Image {image.id} ({image.image_type}) generated successfully: {image_url}") + queue_item['image_url'] = None # No URL in test mode if console_tracker: - console_tracker.save(f"[{function_name}] Saved image {index}/{total_images} to database (ID: {image.id})") + console_tracker.parse(f"[{function_name}] [TEST MODE] Prompt queued for image {index}/{total_images}") + console_tracker.save(f"[{function_name}] [TEST MODE] Queued image {index}/{total_images} (ID: {image.id})") # Update progress: SAVE phase if progress_tracker and step_tracker: - save_msg = f"Saved image {index} of {total_images}" + save_msg = f"Queued prompt {index} of {total_images} (TEST MODE)" step_tracker.add_request_step("SAVE", "success", save_msg) meta = step_tracker.get_meta() meta['image_queue'] = image_queue @@ -416,29 +438,35 @@ class GenerateImagesFromPromptsFunction(BaseAIFunction): continue + # Log all queued prompts (TEST MODE) + if console_tracker: + console_tracker.save(f"[{function_name}] [TEST MODE] All prompts queued. Total: {len(queued_prompts)} prompts") + console_tracker.save(f"[{function_name}] [TEST MODE] Provider: {provider}, Model: {model}") + for qp in queued_prompts: + console_tracker.save(f"[{function_name}] [TEST MODE] Image {qp['index']}: {qp['image_type']} - '{qp['content_title']}'") + console_tracker.save(f"[{function_name}] [TEST MODE] Prompt ({qp['prompt_length']} chars): {qp['formatted_prompt'][:100]}...") + # Final progress update if progress_tracker and step_tracker: - final_msg = f"Generated {images_generated} of {total_images} images" + final_msg = f"Queued {len(queued_prompts)} prompts (TEST MODE - not sent to AI)" step_tracker.add_request_step("SAVE", "success", final_msg) meta = step_tracker.get_meta() meta['image_queue'] = image_queue + meta['queued_prompts'] = queued_prompts # Include queued prompts in meta progress_tracker.update("SAVE", 98, final_msg, meta=meta) if console_tracker: - if images_generated > 0: - console_tracker.save(f"[{function_name}] SUCCESS: Generated {images_generated}/{total_images} image{'s' if images_generated != 1 else ''} successfully") - if images_failed > 0: - console_tracker.error('ImageGenerationError', f"[{function_name}] FAILED: {images_failed}/{total_images} image{'s' if images_failed != 1 else ''} failed") - if images_generated == total_images: - console_tracker.done(f"[{function_name}] All {total_images} image{'s' if total_images != 1 else ''} generated successfully") - else: - console_tracker.done(f"[{function_name}] Completed: {images_generated} succeeded, {images_failed} failed out of {total_images} total") + console_tracker.done(f"[{function_name}] [TEST MODE] Queued {len(queued_prompts)}/{total_images} prompts successfully (NOT sent to AI)") return { - 'count': images_generated, - 'images_generated': images_generated, - 'images_failed': images_failed, + 'count': len(queued_prompts), + 'images_generated': 0, # 0 because we're in test mode + 'images_failed': 0, 'total_images': total_images, + 'queued_prompts': queued_prompts, # Return queued prompts + 'test_mode': True, + 'provider': provider, + 'model': model, 'errors': errors if errors else None } diff --git a/frontend/src/pages/Writer/Images.tsx b/frontend/src/pages/Writer/Images.tsx index 10627722..043f8f72 100644 --- a/frontend/src/pages/Writer/Images.tsx +++ b/frontend/src/pages/Writer/Images.tsx @@ -14,16 +14,9 @@ import { import { useToast } from '../../components/ui/toast/ToastContainer'; import { FileIcon, DownloadIcon, BoltIcon } from '../../icons'; import { createImagesPageConfig } from '../../config/pages/images.config'; -import ProgressModal from '../../components/common/ProgressModal'; -import ImageQueueModal from '../../components/common/ImageQueueModal'; -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([]); @@ -174,31 +167,24 @@ export default function Images() { 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' - ); + // 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 { - // 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 + toast.success('Image generation completed'); } + 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]); + }, [toast, loadImages, images]); // Get max in-article images from the data (to determine column count) const maxInArticleImages = useMemo(() => { @@ -284,50 +270,6 @@ export default function Images() { setCurrentPage(1); }} /> - - {/* Image Queue Modal for Image Generation */} - {progressModal.imageQueue && progressModal.imageQueue.length > 0 ? ( - { - 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); - } - }} - /> - ) : ( - /* Progress Modal for other AI Functions */ - { - 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); - } - }} - /> - )} ); }