diff --git a/backend/igny8_core/ai/tasks.py b/backend/igny8_core/ai/tasks.py index 0964d04e..def80225 100644 --- a/backend/igny8_core/ai/tasks.py +++ b/backend/igny8_core/ai/tasks.py @@ -182,29 +182,25 @@ def process_image_generation_queue(self, image_ids: list, account_id: int = None logger.error("Image generation settings not found") return {'success': False, 'error': 'Image generation settings not found'} - # Get provider API key + # Get provider API key (using same approach as test image generation) + # Note: API key is stored as 'apiKey' (camelCase) in IntegrationSettings.config try: - if provider == 'openai': - provider_settings = IntegrationSettings.objects.get( - account=account, - integration_type='openai', - is_active=True - ) - api_key = provider_settings.config.get('api_key') if provider_settings.config else None - elif provider == 'runware': - provider_settings = IntegrationSettings.objects.get( - account=account, - integration_type='runware', - is_active=True - ) - api_key = provider_settings.config.get('api_key') if provider_settings.config else None - else: - return {'success': False, 'error': f'Unknown provider: {provider}'} - + provider_settings = IntegrationSettings.objects.get( + account=account, + integration_type=provider, # 'openai' or 'runware' + is_active=True + ) + api_key = provider_settings.config.get('apiKey') if provider_settings.config else None if not api_key: - return {'success': False, 'error': f'{provider} API key not configured'} + logger.error(f"[process_image_generation_queue] {provider.upper()} API key not found in config") + return {'success': False, 'error': f'{provider.upper()} API key not configured'} + logger.info(f"[process_image_generation_queue] {provider.upper()} API key retrieved successfully") except IntegrationSettings.DoesNotExist: - return {'success': False, 'error': f'{provider} integration not found'} + logger.error(f"[process_image_generation_queue] {provider.upper()} integration settings not found") + return {'success': False, 'error': f'{provider.upper()} integration not found or not active'} + except Exception as e: + logger.error(f"[process_image_generation_queue] Error getting {provider} API key: {e}", exc_info=True) + return {'success': False, 'error': f'Error retrieving {provider} API key: {str(e)}'} # Get image prompt template (has placeholders: {image_type}, {post_title}, {image_prompt}) try: @@ -291,8 +287,12 @@ def process_image_generation_queue(self, image_ids: list, account_id: int = None logger.warning(f"Prompt template formatting failed: {e}, using image prompt directly") formatted_prompt = image.prompt - # Generate image - logger.info(f"Generating image {index}/{total_images} (ID: {image_id})") + # Generate image (using same approach as test image generation) + logger.info(f"[process_image_generation_queue] Generating image {index}/{total_images} (ID: {image_id})") + logger.info(f"[process_image_generation_queue] Provider: {provider}, Model: {model}") + logger.info(f"[process_image_generation_queue] Prompt length: {len(formatted_prompt)}") + logger.info(f"[process_image_generation_queue] Image type: {image_type}") + result = ai_core.generate_image( prompt=formatted_prompt, provider=provider, @@ -303,6 +303,8 @@ def process_image_generation_queue(self, image_ids: list, account_id: int = None function_name='generate_images_from_prompts' ) + logger.info(f"[process_image_generation_queue] Image generation result: has_url={bool(result.get('url'))}, has_error={bool(result.get('error'))}") + # Check for errors if result.get('error'): logger.error(f"Image generation failed for {image_id}: {result.get('error')}") diff --git a/backend/igny8_core/modules/system/integration_views.py b/backend/igny8_core/modules/system/integration_views.py index fe95a791..23a61871 100644 --- a/backend/igny8_core/modules/system/integration_views.py +++ b/backend/igny8_core/modules/system/integration_views.py @@ -437,6 +437,7 @@ class IntegrationSettingsViewSet(viewsets.ViewSet): model = request.data.get('model', 'dall-e-3') logger.info(f"[generate_image] Request parameters: provider={provider}, model={model}, image_type={image_type}, image_size={image_size}, prompt_length={len(prompt)}") + logger.info(f"[generate_image] IMPORTANT: Using ONLY {provider.upper()} provider for this request. NOT using both providers.") if not prompt: logger.error("[generate_image] ERROR: Prompt is empty") @@ -445,79 +446,50 @@ class IntegrationSettingsViewSet(viewsets.ViewSet): 'error': 'Prompt is required' }, status=status.HTTP_400_BAD_REQUEST) - # Get API keys from saved settings - logger.info("[generate_image] Step 3: Getting API keys from saved settings") + # Get API key from saved settings for the specified provider only + logger.info(f"[generate_image] Step 3: Getting API key for provider: {provider}") from .models import IntegrationSettings - # Get OpenAI settings - openai_api_key = None - openai_enabled = False - try: - openai_settings = IntegrationSettings.objects.get( - integration_type='openai', - account=account - ) - openai_api_key = openai_settings.config.get('apiKey') - openai_enabled = openai_settings.is_active - logger.info(f"[generate_image] OpenAI settings found: enabled={openai_enabled}, has_key={bool(openai_api_key)}") - except IntegrationSettings.DoesNotExist: - logger.warning("[generate_image] OpenAI settings not found in database") - openai_api_key = None - openai_enabled = False - except Exception as e: - logger.error(f"[generate_image] Error getting OpenAI settings: {e}") - openai_api_key = None - openai_enabled = False + # Only fetch settings for the specified provider + api_key = None + integration_enabled = False + integration_type = provider # 'openai' or 'runware' - # Get Runware settings - runware_api_key = None - runware_enabled = False try: - runware_settings = IntegrationSettings.objects.get( - integration_type='runware', + integration_settings = IntegrationSettings.objects.get( + integration_type=integration_type, account=account ) - runware_api_key = runware_settings.config.get('apiKey') - runware_enabled = runware_settings.is_active - logger.info(f"[generate_image] Runware settings found: enabled={runware_enabled}, has_key={bool(runware_api_key)}") + api_key = integration_settings.config.get('apiKey') + integration_enabled = integration_settings.is_active + logger.info(f"[generate_image] {integration_type.upper()} settings found: enabled={integration_enabled}, has_key={bool(api_key)}") except IntegrationSettings.DoesNotExist: - logger.warning("[generate_image] Runware settings not found in database") - runware_api_key = None - runware_enabled = False + logger.warning(f"[generate_image] {integration_type.upper()} settings not found in database") + api_key = None + integration_enabled = False except Exception as e: - logger.error(f"[generate_image] Error getting Runware settings: {e}") - runware_api_key = None - runware_enabled = False + logger.error(f"[generate_image] Error getting {integration_type.upper()} settings: {e}") + api_key = None + integration_enabled = False # Validate provider and API key - logger.info("[generate_image] Step 4: Validating provider and API key") - if provider == 'openai': - logger.info(f"[generate_image] Provider is OpenAI, checking API key: has_key={bool(openai_api_key)}, enabled={openai_enabled}") - if not openai_api_key or not openai_enabled: - logger.error("[generate_image] ERROR: OpenAI API key not configured or integration not enabled") - return Response({ - 'success': False, - 'error': 'OpenAI API key not configured or integration not enabled' - }, status=status.HTTP_400_BAD_REQUEST) - api_key = openai_api_key - logger.info("[generate_image] OpenAI API key validated") - elif provider == 'runware': - logger.info(f"[generate_image] Provider is Runware, checking API key: has_key={bool(runware_api_key)}, enabled={runware_enabled}") - if not runware_api_key or not runware_enabled: - logger.error("[generate_image] ERROR: Runware API key not configured or integration not enabled") - return Response({ - 'success': False, - 'error': 'Runware API key not configured or integration not enabled' - }, status=status.HTTP_400_BAD_REQUEST) - api_key = runware_api_key - logger.info("[generate_image] Runware API key validated") - else: + logger.info(f"[generate_image] Step 4: Validating {provider} provider and API key") + if provider not in ['openai', 'runware']: logger.error(f"[generate_image] ERROR: Invalid provider: {provider}") return Response({ 'success': False, - 'error': f'Invalid provider: {provider}' + 'error': f'Invalid provider: {provider}. Must be "openai" or "runware"' }, status=status.HTTP_400_BAD_REQUEST) + if not api_key or not integration_enabled: + logger.error(f"[generate_image] ERROR: {provider.upper()} API key not configured or integration not enabled") + return Response({ + 'success': False, + 'error': f'{provider.upper()} API key not configured or integration not enabled' + }, status=status.HTTP_400_BAD_REQUEST) + + logger.info(f"[generate_image] {provider.upper()} API key validated successfully") + # Generate image using AIProcessor logger.info("[generate_image] Step 5: Creating AIProcessor and generating image") try: diff --git a/frontend/src/components/common/ImageQueueModal.tsx b/frontend/src/components/common/ImageQueueModal.tsx index 97bfed82..15dde80b 100644 --- a/frontend/src/components/common/ImageQueueModal.tsx +++ b/frontend/src/components/common/ImageQueueModal.tsx @@ -55,9 +55,23 @@ export default function ImageQueueModal({ useEffect(() => { if (!isOpen || !taskId) return; + let pollAttempts = 0; + const maxPollAttempts = 300; // 5 minutes max (300 * 1 second) + const pollInterval = setInterval(async () => { + pollAttempts++; + + // Stop polling after max attempts + if (pollAttempts > maxPollAttempts) { + console.warn('Polling timeout reached, stopping'); + clearInterval(pollInterval); + return; + } + try { + console.log(`[ImageQueueModal] Polling task status (attempt ${pollAttempts}):`, taskId); const data = await fetchAPI(`/v1/system/settings/task_progress/${taskId}/`); + console.log(`[ImageQueueModal] Task status response:`, data); // Check if data is valid (not HTML error page) if (!data || typeof data !== 'object') { @@ -67,14 +81,18 @@ export default function ImageQueueModal({ // Check state (task_progress returns 'state', not 'status') const taskState = data.state || data.status; + console.log(`[ImageQueueModal] Task state:`, taskState); if (taskState === 'SUCCESS' || taskState === 'FAILURE') { + console.log(`[ImageQueueModal] Task completed with state:`, taskState); clearInterval(pollInterval); // Update final state if (taskState === 'SUCCESS' && data.result) { + console.log(`[ImageQueueModal] Updating queue from result:`, data.result); updateQueueFromTaskResult(data.result); } else if (taskState === 'SUCCESS' && data.meta && data.meta.result) { // Some responses have result in meta + console.log(`[ImageQueueModal] Updating queue from meta result:`, data.meta.result); updateQueueFromTaskResult(data.meta.result); } return; @@ -82,7 +100,10 @@ export default function ImageQueueModal({ // Update progress from task meta if (data.meta) { + console.log(`[ImageQueueModal] Updating queue from meta:`, data.meta); updateQueueFromTaskMeta(data.meta); + } else { + console.log(`[ImageQueueModal] No meta data in response`); } } catch (error: any) { // Check if it's a JSON parse error (HTML response) or API error diff --git a/frontend/src/pages/Writer/Images.tsx b/frontend/src/pages/Writer/Images.tsx index 36bb3305..19f53878 100644 --- a/frontend/src/pages/Writer/Images.tsx +++ b/frontend/src/pages/Writer/Images.tsx @@ -16,10 +16,38 @@ 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'; +import { useResourceDebug } from '../../hooks/useResourceDebug'; export default function Images() { const toast = useToast(); + // Resource Debug toggle - controls AI Function Logs + const resourceDebugEnabled = useResourceDebug(); + + // AI Function Logs state + const [aiLogs, setAiLogs] = useState>([]); + + // Helper function to add log entry (only if Resource Debug is enabled) + const addAiLog = useCallback((log: { + timestamp: string; + type: 'request' | 'success' | 'error' | 'step'; + action: string; + data: any; + stepName?: string; + percentage?: number; + }) => { + if (resourceDebugEnabled) { + setAiLogs(prev => [...prev, log]); + } + }, [resourceDebugEnabled]); + // Data state const [images, setImages] = useState([]); const [loading, setLoading] = useState(true); @@ -237,16 +265,35 @@ export default function Images() { console.log('[Generate Images] Max in-article images from settings:', maxInArticleImages); // STAGE 2: Start actual generation + addAiLog({ + timestamp: new Date().toISOString(), + type: 'request', + action: 'generate_images', + data: { imageIds, contentId, totalImages: imageIds.length } + }); + const result = await generateImages(imageIds, contentId); if (result.success && result.task_id) { // Task started successfully - polling will be handled by ImageQueueModal setTaskId(result.task_id); console.log('[Generate Images] Stage 2: Task started with ID:', result.task_id); + addAiLog({ + timestamp: new Date().toISOString(), + type: 'success', + action: 'generate_images', + data: { task_id: result.task_id, message: 'Task queued successfully' } + }); } else { toast.error(result.error || 'Failed to start image generation'); setIsQueueModalOpen(false); setTaskId(null); + addAiLog({ + timestamp: new Date().toISOString(), + type: 'error', + action: 'generate_images', + data: { error: result.error || 'Failed to start image generation' } + }); } } catch (error: any) { @@ -354,6 +401,74 @@ export default function Images() { taskId={taskId} onUpdateQueue={setImageQueue} /> + + {/* AI Function Logs - Display below table (only when Resource Debug is enabled) */} + {resourceDebugEnabled && aiLogs.length > 0 && ( +
+
+

+ AI Function Logs +

+ +
+
+ {aiLogs.slice().reverse().map((log, index) => ( +
+
+
+ + [{log.type.toUpperCase()}] + + + {log.action} + + {log.stepName && ( + + {log.stepName} + + )} + {log.percentage !== undefined && ( + + {log.percentage}% + + )} +
+ + {new Date(log.timestamp).toLocaleTimeString()} + +
+
+                  {JSON.stringify(log.data, null, 2)}
+                
+
+ ))} +
+
+ )} ); }