From b0e2888b09489dbb67815e97773506595df8db7b Mon Sep 17 00:00:00 2001 From: Desktop Date: Wed, 12 Nov 2025 05:13:56 +0500 Subject: [PATCH] d --- backend/igny8_core/ai/ai_core.py | 12 +- backend/igny8_core/ai/tasks.py | 139 ++++++++++++------ .../src/components/common/ImageQueueModal.tsx | 9 ++ frontend/src/pages/Writer/Images.tsx | 16 +- 4 files changed, 131 insertions(+), 45 deletions(-) diff --git a/backend/igny8_core/ai/ai_core.py b/backend/igny8_core/ai/ai_core.py index f0756feb..41ee7680 100644 --- a/backend/igny8_core/ai/ai_core.py +++ b/backend/igny8_core/ai/ai_core.py @@ -488,8 +488,18 @@ class AICore: # CRITICAL: Truncate prompt to OpenAI's 1000 character limit BEFORE any processing if len(prompt) > 1000: print(f"[AI][{function_name}][Warning] Prompt too long ({len(prompt)} chars), truncating to 1000") - prompt = prompt[:997].rsplit(' ', 1)[0] + "..." + # Try word-aware truncation, but fallback to hard truncate if no space found + truncated = prompt[:997] + last_space = truncated.rfind(' ') + if last_space > 900: # Only use word-aware if we have a reasonable space + prompt = truncated[:last_space] + "..." + else: + prompt = prompt[:1000] # Hard truncate if no good space found print(f"[AI][{function_name}] Truncated prompt length: {len(prompt)}") + # Final safety check + if len(prompt) > 1000: + prompt = prompt[:1000] + print(f"[AI][{function_name}][Error] Had to hard truncate to exactly 1000 chars") api_key = api_key or self._openai_api_key if not api_key: diff --git a/backend/igny8_core/ai/tasks.py b/backend/igny8_core/ai/tasks.py index 346ef3f0..f8a6a76d 100644 --- a/backend/igny8_core/ai/tasks.py +++ b/backend/igny8_core/ai/tasks.py @@ -275,57 +275,110 @@ def process_image_generation_queue(self, image_ids: list, account_id: int = None continue # Format template with image prompt from database - # Template has placeholders: {image_type}, {post_title}, {image_prompt} + # For DALL-E 2: Use image prompt directly (no template) + # For DALL-E 3 and others: Use template with placeholders # CRITICAL: OpenAI has strict 1000 character limit for prompts - try: - # Get template length to calculate available space - template_placeholder_length = len(image_prompt_template.replace('{image_type}', '').replace('{post_title}', '').replace('{image_prompt}', '')) + image_prompt = image.prompt or "" + + if model == 'dall-e-2': + # DALL-E 2: Use image prompt directly, no template + logger.info(f"[process_image_generation_queue] Using DALL-E 2 - skipping template, using image prompt directly") + formatted_prompt = image_prompt - # Truncate post_title aggressively (max 100 chars to leave room) - post_title = content.title or content.meta_title or f"Content #{content.id}" - if len(post_title) > 100: - post_title = post_title[:97] + "..." - - # Calculate max image_prompt length: 1000 - template_text - post_title - safety margin - # Assume template adds ~200 chars, post_title max 100, safety margin 50 = ~650 chars for image_prompt - image_prompt = image.prompt or "" - max_image_prompt_length = 650 - if len(image_prompt) > max_image_prompt_length: - logger.warning(f"Image prompt too long ({len(image_prompt)} chars), truncating to {max_image_prompt_length}") - image_prompt = image_prompt[:max_image_prompt_length].rsplit(' ', 1)[0] + "..." - - formatted_prompt = image_prompt_template.format( - image_type=image_type, - post_title=post_title, - image_prompt=image_prompt - ) - - # CRITICAL: Final safety check - ALWAYS truncate to 1000 chars max + # Truncate to 1000 chars if needed if len(formatted_prompt) > 1000: - logger.warning(f"Formatted prompt too long ({len(formatted_prompt)} chars), truncating to 1000") - formatted_prompt = formatted_prompt[:997].rsplit(' ', 1)[0] + "..." - - # Double-check after truncation - if len(formatted_prompt) > 1000: - logger.error(f"Prompt still too long after truncation ({len(formatted_prompt)} chars), forcing hard truncate") - formatted_prompt = formatted_prompt[:1000] + logger.warning(f"DALL-E 2 prompt too long ({len(formatted_prompt)} chars), truncating to 1000") + truncated = formatted_prompt[:997] + last_space = truncated.rfind(' ') + if last_space > 900: + formatted_prompt = truncated[:last_space] + "..." + else: + formatted_prompt = formatted_prompt[:1000] + else: + # DALL-E 3 and others: Use template + try: + # Truncate post_title aggressively (max 80 chars to leave more room for image_prompt) + post_title = content.title or content.meta_title or f"Content #{content.id}" + if len(post_title) > 80: + post_title = post_title[:77] + "..." - except Exception as e: - # Fallback if template formatting fails - logger.warning(f"Prompt template formatting failed: {e}, using image prompt directly") - formatted_prompt = image.prompt or "" - # CRITICAL: Truncate to 1000 chars even in fallback - if len(formatted_prompt) > 1000: - logger.warning(f"Fallback prompt too long ({len(formatted_prompt)} chars), truncating to 1000") - formatted_prompt = formatted_prompt[:997].rsplit(' ', 1)[0] + "..." - # Final hard truncate if still too long - if len(formatted_prompt) > 1000: - formatted_prompt = formatted_prompt[:1000] + # Calculate actual template length with placeholders filled + # Format template with dummy values to measure actual length + template_with_dummies = image_prompt_template.format( + image_type=image_type, + post_title='X' * len(post_title), # Use same length as actual post_title + image_prompt='' # Empty to measure template overhead + ) + template_overhead = len(template_with_dummies) + + # Calculate max image_prompt length: 1000 - template_overhead - safety margin (20) + max_image_prompt_length = 1000 - template_overhead - 20 + if max_image_prompt_length < 50: + # If template is too long, use minimum 50 chars for image_prompt + max_image_prompt_length = 50 + logger.warning(f"Template is very long ({template_overhead} chars), limiting image_prompt to {max_image_prompt_length}") + + logger.info(f"[process_image_generation_queue] Template overhead: {template_overhead} chars, max image_prompt: {max_image_prompt_length} chars") + + # Truncate image_prompt to calculated max + if len(image_prompt) > max_image_prompt_length: + logger.warning(f"Image prompt too long ({len(image_prompt)} chars), truncating to {max_image_prompt_length}") + # Word-aware truncation + truncated = image_prompt[:max_image_prompt_length - 3] + last_space = truncated.rfind(' ') + if last_space > max_image_prompt_length * 0.8: # Only if we have a reasonable space + image_prompt = truncated[:last_space] + "..." + else: + image_prompt = image_prompt[:max_image_prompt_length - 3] + "..." + + formatted_prompt = image_prompt_template.format( + image_type=image_type, + post_title=post_title, + image_prompt=image_prompt + ) + + # CRITICAL: Final safety check - ALWAYS truncate to 1000 chars max + if len(formatted_prompt) > 1000: + logger.warning(f"Formatted prompt too long ({len(formatted_prompt)} chars), truncating to 1000") + # Try word-aware truncation + truncated = formatted_prompt[:997] + last_space = truncated.rfind(' ') + if last_space > 900: # Only use word-aware if we have a reasonable space + formatted_prompt = truncated[:last_space] + "..." + else: + formatted_prompt = formatted_prompt[:1000] # Hard truncate + + # Double-check after truncation - MUST be <= 1000 + if len(formatted_prompt) > 1000: + logger.error(f"Prompt still too long after truncation ({len(formatted_prompt)} chars), forcing hard truncate") + formatted_prompt = formatted_prompt[:1000] + + except Exception as e: + # Fallback if template formatting fails + logger.warning(f"Prompt template formatting failed: {e}, using image prompt directly") + formatted_prompt = image_prompt + # CRITICAL: Truncate to 1000 chars even in fallback + if len(formatted_prompt) > 1000: + logger.warning(f"Fallback prompt too long ({len(formatted_prompt)} chars), truncating to 1000") + # Try word-aware truncation + truncated = formatted_prompt[:997] + last_space = truncated.rfind(' ') + if last_space > 900: + formatted_prompt = truncated[:last_space] + "..." + else: + formatted_prompt = formatted_prompt[:1000] # Hard truncate + # Final hard truncate if still too long - MUST be <= 1000 + if len(formatted_prompt) > 1000: + formatted_prompt = formatted_prompt[:1000] # 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] Prompt length: {len(formatted_prompt)} (MUST be <= 1000)") + if len(formatted_prompt) > 1000: + logger.error(f"[process_image_generation_queue] ERROR: Prompt is {len(formatted_prompt)} chars, truncating NOW!") + formatted_prompt = formatted_prompt[:1000] + logger.info(f"[process_image_generation_queue] Final prompt length: {len(formatted_prompt)}") logger.info(f"[process_image_generation_queue] Image type: {image_type}") result = ai_core.generate_image( diff --git a/frontend/src/components/common/ImageQueueModal.tsx b/frontend/src/components/common/ImageQueueModal.tsx index 13e245d9..a32ad89e 100644 --- a/frontend/src/components/common/ImageQueueModal.tsx +++ b/frontend/src/components/common/ImageQueueModal.tsx @@ -28,6 +28,8 @@ interface ImageQueueModalProps { queue: ImageQueueItem[]; totalImages: number; taskId?: string | null; + model?: string; + provider?: string; onUpdateQueue?: (queue: ImageQueueItem[]) => void; onLog?: (log: { timestamp: string; @@ -45,6 +47,8 @@ export default function ImageQueueModal({ queue, totalImages, taskId, + model, + provider, onUpdateQueue, onLog, }: ImageQueueModalProps) { @@ -306,6 +310,11 @@ export default function ImageQueueModal({

Total: {totalImages} image{totalImages !== 1 ? 's' : ''} in queue

+ {model && ( +

+ Model: {provider === 'openai' ? 'OpenAI' : provider === 'runware' ? 'Runware' : provider || 'Unknown'} {model === 'dall-e-2' ? 'DALL·E 2' : model === 'dall-e-3' ? 'DALL·E 3' : model} +

+ )} diff --git a/frontend/src/pages/Writer/Images.tsx b/frontend/src/pages/Writer/Images.tsx index edba45bd..78ebe6b1 100644 --- a/frontend/src/pages/Writer/Images.tsx +++ b/frontend/src/pages/Writer/Images.tsx @@ -73,6 +73,8 @@ export default function Images() { const [imageQueue, setImageQueue] = useState([]); const [currentContentId, setCurrentContentId] = useState(null); const [taskId, setTaskId] = useState(null); + const [imageModel, setImageModel] = useState(null); + const [imageProvider, setImageProvider] = useState(null); // Load images - wrapped in useCallback const loadImages = useCallback(async () => { @@ -231,17 +233,25 @@ export default function Images() { return; } - // Fetch image generation settings to get max_in_article_images + // Fetch image generation settings to get max_in_article_images, model, and provider let maxInArticleImages = 2; // Default + let model = null; + let provider = null; try { const settings = await fetchImageGenerationSettings(); if (settings.success && settings.config) { maxInArticleImages = settings.config.max_in_article_images || 2; + model = settings.config.model || null; + provider = settings.config.provider || null; } } catch (error) { console.warn('Failed to fetch image settings, using default:', error); } + // Store model and provider for modal display + setImageModel(model); + setImageProvider(provider); + // Build image queue const queue = buildImageQueue(contentId, maxInArticleImages); @@ -394,12 +404,16 @@ export default function Images() { setImageQueue([]); setCurrentContentId(null); setTaskId(null); + setImageModel(null); + setImageProvider(null); // Reload images after closing if generation completed loadImages(); }} queue={imageQueue} totalImages={imageQueue.length} taskId={taskId} + model={imageModel || undefined} + provider={imageProvider || undefined} onUpdateQueue={setImageQueue} onLog={addAiLog} />