This commit is contained in:
Desktop
2025-11-12 05:13:56 +05:00
parent 84111f5ad6
commit b0e2888b09
4 changed files with 131 additions and 45 deletions

View File

@@ -488,8 +488,18 @@ class AICore:
# CRITICAL: Truncate prompt to OpenAI's 1000 character limit BEFORE any processing # CRITICAL: Truncate prompt to OpenAI's 1000 character limit BEFORE any processing
if len(prompt) > 1000: if len(prompt) > 1000:
print(f"[AI][{function_name}][Warning] Prompt too long ({len(prompt)} chars), truncating to 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)}") 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 api_key = api_key or self._openai_api_key
if not api_key: if not api_key:

View File

@@ -275,57 +275,110 @@ def process_image_generation_queue(self, image_ids: list, account_id: int = None
continue continue
# Format template with image prompt from database # 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 # CRITICAL: OpenAI has strict 1000 character limit for prompts
try: image_prompt = image.prompt or ""
# Get template length to calculate available space
template_placeholder_length = len(image_prompt_template.replace('{image_type}', '').replace('{post_title}', '').replace('{image_prompt}', ''))
# Truncate post_title aggressively (max 100 chars to leave room) if model == 'dall-e-2':
post_title = content.title or content.meta_title or f"Content #{content.id}" # DALL-E 2: Use image prompt directly, no template
if len(post_title) > 100: logger.info(f"[process_image_generation_queue] Using DALL-E 2 - skipping template, using image prompt directly")
post_title = post_title[:97] + "..." formatted_prompt = image_prompt
# Calculate max image_prompt length: 1000 - template_text - post_title - safety margin # Truncate to 1000 chars if needed
# 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
if len(formatted_prompt) > 1000: if len(formatted_prompt) > 1000:
logger.warning(f"Formatted prompt too long ({len(formatted_prompt)} chars), truncating to 1000") logger.warning(f"DALL-E 2 prompt too long ({len(formatted_prompt)} chars), truncating to 1000")
formatted_prompt = formatted_prompt[:997].rsplit(' ', 1)[0] + "..." 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] + "..."
# Double-check after truncation # Calculate actual template length with placeholders filled
if len(formatted_prompt) > 1000: # Format template with dummy values to measure actual length
logger.error(f"Prompt still too long after truncation ({len(formatted_prompt)} chars), forcing hard truncate") template_with_dummies = image_prompt_template.format(
formatted_prompt = formatted_prompt[:1000] 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)
except Exception as e: # Calculate max image_prompt length: 1000 - template_overhead - safety margin (20)
# Fallback if template formatting fails max_image_prompt_length = 1000 - template_overhead - 20
logger.warning(f"Prompt template formatting failed: {e}, using image prompt directly") if max_image_prompt_length < 50:
formatted_prompt = image.prompt or "" # If template is too long, use minimum 50 chars for image_prompt
# CRITICAL: Truncate to 1000 chars even in fallback max_image_prompt_length = 50
if len(formatted_prompt) > 1000: logger.warning(f"Template is very long ({template_overhead} chars), limiting image_prompt to {max_image_prompt_length}")
logger.warning(f"Fallback prompt too long ({len(formatted_prompt)} chars), truncating to 1000")
formatted_prompt = formatted_prompt[:997].rsplit(' ', 1)[0] + "..." logger.info(f"[process_image_generation_queue] Template overhead: {template_overhead} chars, max image_prompt: {max_image_prompt_length} chars")
# Final hard truncate if still too long
if len(formatted_prompt) > 1000: # Truncate image_prompt to calculated max
formatted_prompt = formatted_prompt[:1000] 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) # 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] 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] 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}") logger.info(f"[process_image_generation_queue] Image type: {image_type}")
result = ai_core.generate_image( result = ai_core.generate_image(

View File

@@ -28,6 +28,8 @@ interface ImageQueueModalProps {
queue: ImageQueueItem[]; queue: ImageQueueItem[];
totalImages: number; totalImages: number;
taskId?: string | null; taskId?: string | null;
model?: string;
provider?: string;
onUpdateQueue?: (queue: ImageQueueItem[]) => void; onUpdateQueue?: (queue: ImageQueueItem[]) => void;
onLog?: (log: { onLog?: (log: {
timestamp: string; timestamp: string;
@@ -45,6 +47,8 @@ export default function ImageQueueModal({
queue, queue,
totalImages, totalImages,
taskId, taskId,
model,
provider,
onUpdateQueue, onUpdateQueue,
onLog, onLog,
}: ImageQueueModalProps) { }: ImageQueueModalProps) {
@@ -306,6 +310,11 @@ export default function ImageQueueModal({
<p className="text-sm text-gray-500 dark:text-gray-400 mt-1"> <p className="text-sm text-gray-500 dark:text-gray-400 mt-1">
Total: {totalImages} image{totalImages !== 1 ? 's' : ''} in queue Total: {totalImages} image{totalImages !== 1 ? 's' : ''} in queue
</p> </p>
{model && (
<p className="text-xs text-gray-400 dark:text-gray-500 mt-1">
Model: {provider === 'openai' ? 'OpenAI' : provider === 'runware' ? 'Runware' : provider || 'Unknown'} {model === 'dall-e-2' ? 'DALL·E 2' : model === 'dall-e-3' ? 'DALL·E 3' : model}
</p>
)}
</div> </div>
</div> </div>
</div> </div>

View File

@@ -73,6 +73,8 @@ export default function Images() {
const [imageQueue, setImageQueue] = useState<ImageQueueItem[]>([]); const [imageQueue, setImageQueue] = useState<ImageQueueItem[]>([]);
const [currentContentId, setCurrentContentId] = useState<number | null>(null); const [currentContentId, setCurrentContentId] = useState<number | null>(null);
const [taskId, setTaskId] = useState<string | null>(null); const [taskId, setTaskId] = useState<string | null>(null);
const [imageModel, setImageModel] = useState<string | null>(null);
const [imageProvider, setImageProvider] = useState<string | null>(null);
// Load images - wrapped in useCallback // Load images - wrapped in useCallback
const loadImages = useCallback(async () => { const loadImages = useCallback(async () => {
@@ -231,17 +233,25 @@ export default function Images() {
return; 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 maxInArticleImages = 2; // Default
let model = null;
let provider = null;
try { try {
const settings = await fetchImageGenerationSettings(); const settings = await fetchImageGenerationSettings();
if (settings.success && settings.config) { if (settings.success && settings.config) {
maxInArticleImages = settings.config.max_in_article_images || 2; maxInArticleImages = settings.config.max_in_article_images || 2;
model = settings.config.model || null;
provider = settings.config.provider || null;
} }
} catch (error) { } catch (error) {
console.warn('Failed to fetch image settings, using default:', 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 // Build image queue
const queue = buildImageQueue(contentId, maxInArticleImages); const queue = buildImageQueue(contentId, maxInArticleImages);
@@ -394,12 +404,16 @@ export default function Images() {
setImageQueue([]); setImageQueue([]);
setCurrentContentId(null); setCurrentContentId(null);
setTaskId(null); setTaskId(null);
setImageModel(null);
setImageProvider(null);
// Reload images after closing if generation completed // Reload images after closing if generation completed
loadImages(); loadImages();
}} }}
queue={imageQueue} queue={imageQueue}
totalImages={imageQueue.length} totalImages={imageQueue.length}
taskId={taskId} taskId={taskId}
model={imageModel || undefined}
provider={imageProvider || undefined}
onUpdateQueue={setImageQueue} onUpdateQueue={setImageQueue}
onLog={addAiLog} onLog={addAiLog}
/> />