image s imsages images model fixes new model see dream
This commit is contained in:
@@ -1028,9 +1028,18 @@ class AICore:
|
||||
print(f"[AI][{function_name}] Using Nano Banana config: resolution=1k (no width/height)")
|
||||
elif runware_model.startswith('bytedance:'):
|
||||
# Seedream 4.5 (bytedance:seedream@4.5) - High quality ByteDance model
|
||||
# Uses basic format with just width/height - no steps, CFGScale, or special providerSettings needed
|
||||
# Just use the base inference_task as-is
|
||||
print(f"[AI][{function_name}] Using Seedream 4.5 config: basic format, width={width}, height={height}")
|
||||
# Uses basic format - no steps, CFGScale, negativePrompt, or special providerSettings needed
|
||||
# Remove negativePrompt as it's not supported
|
||||
if 'negativePrompt' in inference_task:
|
||||
del inference_task['negativePrompt']
|
||||
# Enforce minimum size for Seedream (min 3,686,400 pixels ~ 1920x1920)
|
||||
current_pixels = width * height
|
||||
if current_pixels < 3686400:
|
||||
# Use default Seedream square size
|
||||
inference_task['width'] = 2048
|
||||
inference_task['height'] = 2048
|
||||
width, height = 2048, 2048
|
||||
print(f"[AI][{function_name}] Using Seedream 4.5 config: basic format (no negativePrompt), width={inference_task['width']}, height={inference_task['height']}")
|
||||
elif runware_model.startswith('runware:'):
|
||||
# Hi Dream Full (runware:97@1) - General diffusion, steps 20, CFGScale 7
|
||||
inference_task['steps'] = 20
|
||||
|
||||
@@ -187,22 +187,42 @@ def process_image_generation_queue(self, image_ids: list, account_id: int = None
|
||||
logger.info("[process_image_generation_queue] Step 1: Loading image generation settings")
|
||||
from igny8_core.modules.system.ai_settings import AISettings
|
||||
from igny8_core.ai.model_registry import ModelRegistry
|
||||
from igny8_core.business.billing.models import AIModelConfig
|
||||
|
||||
# Get effective settings
|
||||
image_type = AISettings.get_effective_image_style(account)
|
||||
image_format = 'webp' # Default format
|
||||
|
||||
# Get default image model from database
|
||||
# Get user's selected quality tier (from account settings)
|
||||
quality_tier = AISettings.get_effective_quality_tier(account)
|
||||
logger.info(f"[process_image_generation_queue] User's quality tier: {quality_tier}")
|
||||
|
||||
# Find image model based on quality tier (DYNAMIC from database)
|
||||
model_config = None
|
||||
if quality_tier:
|
||||
model_config = AIModelConfig.objects.filter(
|
||||
model_type='image',
|
||||
quality_tier=quality_tier,
|
||||
is_active=True
|
||||
).first()
|
||||
|
||||
# Fallback to default image model if no tier match
|
||||
if not model_config:
|
||||
default_model = ModelRegistry.get_default_model('image')
|
||||
if default_model:
|
||||
model_config = ModelRegistry.get_model(default_model)
|
||||
provider = model_config.provider if model_config else 'openai'
|
||||
model = default_model
|
||||
|
||||
# Set provider and model from database config
|
||||
if model_config:
|
||||
provider = model_config.provider or 'openai'
|
||||
model = model_config.model_name
|
||||
else:
|
||||
# Ultimate fallback (should never happen if database is configured)
|
||||
logger.warning("[process_image_generation_queue] No image model found in database, using fallback")
|
||||
provider = 'openai'
|
||||
model = 'dall-e-3'
|
||||
|
||||
logger.info(f"[process_image_generation_queue] Using PROVIDER: {provider}, MODEL: {model} from settings")
|
||||
logger.info(f"[process_image_generation_queue] Using PROVIDER: {provider}, MODEL: {model} from AIModelConfig")
|
||||
|
||||
# Style to prompt enhancement mapping
|
||||
# These style descriptors are added to the image prompt for better results
|
||||
@@ -225,25 +245,23 @@ def process_image_generation_queue(self, image_ids: list, account_id: int = None
|
||||
style_description = STYLE_PROMPT_MAP.get(image_type, STYLE_PROMPT_MAP.get('photorealistic'))
|
||||
logger.info(f"[process_image_generation_queue] Style: {image_type} -> prompt enhancement: {style_description[:50]}...")
|
||||
|
||||
# Model-specific landscape sizes (square is always 1024x1024)
|
||||
# For Runware models - based on Runware documentation for optimal results per model
|
||||
# For OpenAI DALL-E 3 - uses 1792x1024 for landscape
|
||||
MODEL_LANDSCAPE_SIZES = {
|
||||
'runware:97@1': '1280x768', # Hi Dream Full landscape
|
||||
'bria:10@1': '1344x768', # Bria 3.2 landscape (16:9)
|
||||
'google:4@2': '1376x768', # Nano Banana landscape (16:9)
|
||||
'dall-e-3': '1792x1024', # DALL-E 3 landscape
|
||||
'dall-e-2': '1024x1024', # DALL-E 2 only supports square
|
||||
}
|
||||
DEFAULT_SQUARE_SIZE = '1024x1024'
|
||||
|
||||
# Get model-specific landscape size for featured images
|
||||
model_landscape_size = MODEL_LANDSCAPE_SIZES.get(model, '1792x1024' if provider == 'openai' else '1280x768')
|
||||
# Load image sizes from AIModelConfig (DYNAMIC from database)
|
||||
# model_config was loaded above based on quality tier
|
||||
if model_config:
|
||||
# Get sizes from database (single source of truth)
|
||||
model_landscape_size = model_config.landscape_size or '1792x1024'
|
||||
model_square_size = model_config.square_size or '1024x1024'
|
||||
logger.info(f"[process_image_generation_queue] Loaded sizes from AIModelConfig: landscape={model_landscape_size}, square={model_square_size}")
|
||||
else:
|
||||
# Fallback sizes if no model config (should never happen)
|
||||
model_landscape_size = '1792x1024'
|
||||
model_square_size = '1024x1024'
|
||||
logger.warning(f"[process_image_generation_queue] No model config, using fallback sizes")
|
||||
|
||||
# Featured image always uses model-specific landscape size
|
||||
featured_image_size = model_landscape_size
|
||||
# In-article images: alternating square/landscape based on position (handled in image loop)
|
||||
in_article_square_size = DEFAULT_SQUARE_SIZE
|
||||
in_article_square_size = model_square_size
|
||||
in_article_landscape_size = model_landscape_size
|
||||
|
||||
logger.info(f"[process_image_generation_queue] Settings loaded:")
|
||||
|
||||
@@ -0,0 +1,38 @@
|
||||
# Generated by Django 5.2.10 on 2026-01-10 12:46
|
||||
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('billing', '0031_add_seedream_model'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AddField(
|
||||
model_name='historicalaimodelconfig',
|
||||
name='landscape_size',
|
||||
field=models.CharField(blank=True, help_text="Landscape image size for this model (e.g., '1792x1024', '1280x768')", max_length=20, null=True),
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='historicalaimodelconfig',
|
||||
name='square_size',
|
||||
field=models.CharField(blank=True, default='1024x1024', help_text="Square image size for this model (e.g., '1024x1024')", max_length=20),
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='historicalaimodelconfig',
|
||||
name='valid_sizes',
|
||||
field=models.JSONField(blank=True, default=list, help_text="List of valid sizes for this model (e.g., ['1024x1024', '1792x1024'])"),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='aimodelconfig',
|
||||
name='quality_tier',
|
||||
field=models.CharField(blank=True, choices=[('basic', 'Basic'), ('quality', 'Quality'), ('quality_option2', 'Quality-Option2'), ('premium', 'Premium')], help_text='basic / quality / premium - for image models', max_length=20, null=True),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='historicalaimodelconfig',
|
||||
name='quality_tier',
|
||||
field=models.CharField(blank=True, choices=[('basic', 'Basic'), ('quality', 'Quality'), ('quality_option2', 'Quality-Option2'), ('premium', 'Premium')], help_text='basic / quality / premium - for image models', max_length=20, null=True),
|
||||
),
|
||||
]
|
||||
@@ -194,7 +194,7 @@ class SystemAISettings(models.Model):
|
||||
|
||||
@classmethod
|
||||
def get_effective_quality_tier(cls, account=None) -> str:
|
||||
"""Get quality_tier, checking account override first (from ai_settings key)"""
|
||||
"""Get quality_tier, checking account override first, then default image model"""
|
||||
if account:
|
||||
# Check consolidated ai_settings first
|
||||
try:
|
||||
@@ -214,6 +214,21 @@ class SystemAISettings(models.Model):
|
||||
override = cls._get_account_override(account, 'ai.quality_tier')
|
||||
if override is not None:
|
||||
return str(override)
|
||||
|
||||
# No account override - get tier from DEFAULT image model (is_default=True)
|
||||
try:
|
||||
from igny8_core.business.billing.models import AIModelConfig
|
||||
default_model = AIModelConfig.objects.filter(
|
||||
model_type='image',
|
||||
is_default=True,
|
||||
is_active=True
|
||||
).first()
|
||||
if default_model and default_model.quality_tier:
|
||||
return default_model.quality_tier
|
||||
except Exception as e:
|
||||
logger.debug(f"Could not get default image model tier: {e}")
|
||||
|
||||
# Ultimate fallback
|
||||
return cls.get_instance().default_quality_tier
|
||||
|
||||
@staticmethod
|
||||
|
||||
@@ -793,23 +793,38 @@ class IntegrationSettingsViewSet(viewsets.ViewSet):
|
||||
}
|
||||
|
||||
elif integration_type == 'image_generation':
|
||||
# Model-specific landscape sizes
|
||||
MODEL_LANDSCAPE_SIZES = {
|
||||
'runware:97@1': '1280x768',
|
||||
'bria:10@1': '1344x768',
|
||||
'google:4@2': '1376x768',
|
||||
}
|
||||
from igny8_core.business.billing.models import AIModelConfig
|
||||
from igny8_core.modules.system.ai_settings import AISettings
|
||||
|
||||
# Get default image model from AIModelConfig
|
||||
# Get user's quality tier (from account settings)
|
||||
quality_tier = AISettings.get_effective_quality_tier(account)
|
||||
|
||||
# Find image model based on quality tier (DYNAMIC from database)
|
||||
model_config = None
|
||||
if quality_tier:
|
||||
model_config = AIModelConfig.objects.filter(
|
||||
model_type='image',
|
||||
quality_tier=quality_tier,
|
||||
is_active=True
|
||||
).first()
|
||||
|
||||
# Fallback to default image model
|
||||
if not model_config:
|
||||
default_model = ModelRegistry.get_default_model('image')
|
||||
if default_model:
|
||||
model_config = ModelRegistry.get_model(default_model)
|
||||
default_service = model_config.provider if model_config else 'openai'
|
||||
|
||||
# Extract settings from model config
|
||||
if model_config:
|
||||
default_service = model_config.provider or 'openai'
|
||||
default_model = model_config.model_name
|
||||
model_landscape_size = model_config.landscape_size or '1792x1024'
|
||||
model_square_size = model_config.square_size or '1024x1024'
|
||||
else:
|
||||
default_service = 'openai'
|
||||
default_model = 'dall-e-3'
|
||||
|
||||
model_landscape_size = MODEL_LANDSCAPE_SIZES.get(default_model, '1280x768')
|
||||
model_landscape_size = '1792x1024'
|
||||
model_square_size = '1024x1024'
|
||||
|
||||
response_data = {
|
||||
'id': 'image_generation',
|
||||
@@ -826,8 +841,11 @@ class IntegrationSettingsViewSet(viewsets.ViewSet):
|
||||
'image_format': 'webp',
|
||||
'desktop_enabled': True,
|
||||
'featured_image_size': model_landscape_size,
|
||||
'in_article_landscape_size': model_landscape_size,
|
||||
'in_article_square_size': model_square_size,
|
||||
'desktop_image_size': SystemAISettings.get_effective_image_size(account),
|
||||
'using_global': True,
|
||||
'quality_tier': quality_tier,
|
||||
}
|
||||
else:
|
||||
# Other integration types - return empty
|
||||
@@ -856,9 +874,11 @@ class IntegrationSettingsViewSet(viewsets.ViewSet):
|
||||
1. SystemAISettings (singleton) provides system-wide defaults
|
||||
2. AccountSettings (key-value) provides per-account overrides
|
||||
3. API keys come from IntegrationProvider (accounts cannot override API keys)
|
||||
4. Model config (sizes, etc.) from AIModelConfig (DYNAMIC, single source of truth)
|
||||
"""
|
||||
from igny8_core.modules.system.ai_settings import SystemAISettings
|
||||
from igny8_core.modules.system.ai_settings import SystemAISettings, AISettings
|
||||
from igny8_core.ai.model_registry import ModelRegistry
|
||||
from igny8_core.business.billing.models import AIModelConfig
|
||||
|
||||
account = getattr(request, 'account', None)
|
||||
|
||||
@@ -868,29 +888,36 @@ class IntegrationSettingsViewSet(viewsets.ViewSet):
|
||||
if user and hasattr(user, 'is_authenticated') and user.is_authenticated:
|
||||
account = getattr(user, 'account', None)
|
||||
|
||||
# Model-specific landscape sizes
|
||||
MODEL_LANDSCAPE_SIZES = {
|
||||
'runware:97@1': '1280x768', # Hi Dream Full landscape
|
||||
'bria:10@1': '1344x768', # Bria 3.2 landscape (16:9)
|
||||
'google:4@2': '1376x768', # Nano Banana landscape (16:9)
|
||||
'dall-e-3': '1792x1024', # DALL-E 3 landscape
|
||||
'dall-e-2': '1024x1024', # DALL-E 2 square only
|
||||
}
|
||||
|
||||
try:
|
||||
# Get default image model from AIModelConfig
|
||||
# Get user's quality tier from account settings (DYNAMIC)
|
||||
quality_tier = AISettings.get_effective_quality_tier(account)
|
||||
|
||||
# Find image model based on quality tier (DYNAMIC from database)
|
||||
model_config = None
|
||||
if quality_tier:
|
||||
model_config = AIModelConfig.objects.filter(
|
||||
model_type='image',
|
||||
quality_tier=quality_tier,
|
||||
is_active=True
|
||||
).first()
|
||||
|
||||
# Fallback to default image model
|
||||
if not model_config:
|
||||
default_model = ModelRegistry.get_default_model('image')
|
||||
if default_model:
|
||||
model_config = ModelRegistry.get_model(default_model)
|
||||
provider = model_config.provider if model_config else 'openai'
|
||||
model = default_model
|
||||
|
||||
# Extract settings from model config (SINGLE SOURCE OF TRUTH)
|
||||
if model_config:
|
||||
provider = model_config.provider or 'openai'
|
||||
model = model_config.model_name
|
||||
model_landscape_size = model_config.landscape_size or '1792x1024'
|
||||
model_square_size = model_config.square_size or '1024x1024'
|
||||
else:
|
||||
provider = 'openai'
|
||||
model = 'dall-e-3'
|
||||
|
||||
# Get model-specific landscape size
|
||||
model_landscape_size = MODEL_LANDSCAPE_SIZES.get(model, '1280x768')
|
||||
default_featured_size = model_landscape_size if provider == 'runware' else '1792x1024'
|
||||
model_landscape_size = '1792x1024'
|
||||
model_square_size = '1024x1024'
|
||||
|
||||
# Get image style from SystemAISettings with AccountSettings overrides
|
||||
image_style = SystemAISettings.get_effective_image_style(account)
|
||||
@@ -915,7 +942,7 @@ class IntegrationSettingsViewSet(viewsets.ViewSet):
|
||||
if not image_style or image_style in ['natural', 'vivid']:
|
||||
image_style = 'photorealistic'
|
||||
|
||||
logger.info(f"[get_image_generation_settings] Returning: provider={provider}, model={model}, image_style={image_style}")
|
||||
logger.info(f"[get_image_generation_settings] Returning: provider={provider}, model={model}, image_style={image_style}, quality_tier={quality_tier}")
|
||||
|
||||
return success_response(
|
||||
data={
|
||||
@@ -927,8 +954,11 @@ class IntegrationSettingsViewSet(viewsets.ViewSet):
|
||||
'max_in_article_images': SystemAISettings.get_effective_max_images(account),
|
||||
'image_format': 'webp',
|
||||
'desktop_enabled': True,
|
||||
'featured_image_size': default_featured_size,
|
||||
'featured_image_size': model_landscape_size,
|
||||
'in_article_landscape_size': model_landscape_size,
|
||||
'in_article_square_size': model_square_size,
|
||||
'desktop_image_size': SystemAISettings.get_effective_image_size(account),
|
||||
'quality_tier': quality_tier,
|
||||
}
|
||||
},
|
||||
request=request
|
||||
|
||||
@@ -0,0 +1,19 @@
|
||||
# Generated by Django 5.2.10 on 2026-01-10 12:46
|
||||
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('system', '0023_systemaisettings_max_allowed_images_and_more'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AlterField(
|
||||
model_name='systemaisettings',
|
||||
name='default_quality_tier',
|
||||
field=models.CharField(choices=[('basic', 'Basic'), ('quality', 'Quality'), ('quality_option2', 'Quality-Option2'), ('premium', 'Premium')], default='basic', help_text='Default quality tier for image generation', max_length=20),
|
||||
),
|
||||
# Removed DeleteModel for AccountIntegrationOverride - table doesn't exist
|
||||
]
|
||||
@@ -621,15 +621,20 @@ class ContentGenerationSettingsViewSet(viewsets.ViewSet):
|
||||
# Get system defaults
|
||||
system_defaults = SystemAISettings.get_instance()
|
||||
|
||||
# Get default image model (is_default=True) - SINGLE SOURCE OF TRUTH
|
||||
default_image_model = AIModelConfig.get_default_image_model()
|
||||
|
||||
# Determine default tier from default model (not hardcoded)
|
||||
default_tier = default_image_model.quality_tier if default_image_model else 'basic'
|
||||
|
||||
# Apply account overrides or use system defaults
|
||||
temperature = account_settings.get('temperature', system_defaults.temperature)
|
||||
max_tokens = account_settings.get('max_tokens', system_defaults.max_tokens)
|
||||
image_style = account_settings.get('image_style', system_defaults.image_style)
|
||||
max_images = account_settings.get('max_images', system_defaults.max_images_per_article)
|
||||
selected_tier = account_settings.get('quality_tier', system_defaults.default_quality_tier)
|
||||
|
||||
# Get default image model (or model for selected tier)
|
||||
default_image_model = AIModelConfig.get_default_image_model()
|
||||
# Get selected tier: account override > default model's tier
|
||||
selected_tier = account_settings.get('quality_tier') or default_tier
|
||||
|
||||
# Try to find model matching selected tier
|
||||
selected_model = AIModelConfig.objects.filter(
|
||||
|
||||
@@ -869,8 +869,17 @@ Make sure each prompt is detailed enough for image generation, describing the vi
|
||||
del inference_task['height']
|
||||
inference_task['resolution'] = '1k'
|
||||
elif runware_model.startswith('bytedance:'):
|
||||
# Seedream models use basic format - no steps, CFGScale needed
|
||||
pass
|
||||
# Seedream models use basic format - no steps, CFGScale, negativePrompt needed
|
||||
# Also require minimum 3,686,400 pixels (roughly 1920x1920)
|
||||
if 'negativePrompt' in inference_task:
|
||||
del inference_task['negativePrompt']
|
||||
# Enforce minimum size for Seedream (min ~1920x1920, use 2048x2048 for square)
|
||||
current_pixels = width * height
|
||||
if current_pixels < 3686400:
|
||||
# Use default Seedream square size
|
||||
inference_task['width'] = 2048
|
||||
inference_task['height'] = 2048
|
||||
logger.info(f"[AIProcessor.generate_image] Seedream requires min 3.6M pixels, adjusted to 2048x2048")
|
||||
elif runware_model.startswith('runware:'):
|
||||
# Hi Dream Full - needs steps and CFGScale
|
||||
inference_task['steps'] = 30
|
||||
|
||||
373
docs/90-REFERENCE/IMAGE-GENERATION-GAPS.md
Normal file
373
docs/90-REFERENCE/IMAGE-GENERATION-GAPS.md
Normal file
@@ -0,0 +1,373 @@
|
||||
# Image Generation System - Comprehensive Gap Analysis
|
||||
|
||||
**Date:** January 2026
|
||||
**Status:** Audit Complete
|
||||
**Reviewer:** System Audit
|
||||
|
||||
## Executive Summary
|
||||
|
||||
This document provides a comprehensive audit of the image generation system, analyzing the flow from model configuration to image delivery, both for manual and automation workflows.
|
||||
|
||||
---
|
||||
|
||||
## 1. System Architecture Overview
|
||||
|
||||
### Current Flow
|
||||
```
|
||||
User Selects Quality Tier (basic/quality/quality_option2/premium)
|
||||
↓
|
||||
AIModelConfig (database) → provider, model_name, landscape_size, square_size
|
||||
↓
|
||||
process_image_generation_queue (Celery task)
|
||||
↓
|
||||
ai_core.generate_image() → provider-specific handler
|
||||
↓
|
||||
_generate_image_openai() / _generate_image_runware()
|
||||
↓
|
||||
Downloaded to /frontend/public/images/ai-images/
|
||||
↓
|
||||
Image record updated (Images model)
|
||||
```
|
||||
|
||||
### Image Count Determination
|
||||
```
|
||||
User sets max_images (1-8) in Site Settings
|
||||
↓
|
||||
AISettings.get_effective_max_images(account)
|
||||
↓
|
||||
generate_image_prompts.py: 1 featured + max_images in_article
|
||||
↓
|
||||
Images created with positions: featured(0) + in_article(0,1,2,3...)
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 2. RESOLVED Issues (Previously Fixed)
|
||||
|
||||
### ✅ 2.1 Quality Tier Fallback
|
||||
- **Issue:** `get_effective_quality_tier()` was returning hardcoded 'basic' instead of default model's tier
|
||||
- **Fixed in:** `ai_settings.py` lines 196-232
|
||||
- **Solution:** Now falls back to `AIModelConfig.is_default=True` model's `quality_tier`
|
||||
|
||||
### ✅ 2.2 Image Sizes from Database
|
||||
- **Issue:** `tasks.py` had hardcoded `MODEL_LANDSCAPE_SIZES` dict
|
||||
- **Fixed in:** `tasks.py` lines 242-260
|
||||
- **Solution:** Now loads `landscape_size` and `square_size` from `AIModelConfig`
|
||||
|
||||
### ✅ 2.3 Settings Endpoint Default Tier
|
||||
- **Issue:** `settings_views.py` returned hardcoded 'basic' as default tier
|
||||
- **Fixed in:** `settings_views.py` lines 616-640
|
||||
- **Solution:** Gets `default_tier` from `default_image_model.quality_tier`
|
||||
|
||||
### ✅ 2.4 Integration Views Dynamic Sizes
|
||||
- **Issue:** Two endpoints in `integration_views.py` had hardcoded size lookup
|
||||
- **Fixed:** Both endpoints now load from `AIModelConfig`
|
||||
|
||||
---
|
||||
|
||||
## 3. REMAINING Gaps (Action Required)
|
||||
|
||||
### 🔴 GAP-1: Hardcoded Size Constants in global_settings_models.py
|
||||
|
||||
**Location:** `backend/igny8_core/modules/system/global_settings_models.py` lines 180-188
|
||||
|
||||
**Code:**
|
||||
```python
|
||||
# Model-specific landscape sizes (square is always 1024x1024 for all models)
|
||||
MODEL_LANDSCAPE_SIZES = {
|
||||
'runware:97@1': '1280x768', # Hi Dream Full landscape
|
||||
'bria:10@1': '1344x768', # Bria 3.2 landscape (16:9)
|
||||
'google:4@2': '1376x768', # Nano Banana landscape (16:9)
|
||||
}
|
||||
|
||||
# Default square size (universal across all models)
|
||||
DEFAULT_SQUARE_SIZE = '1024x1024'
|
||||
```
|
||||
|
||||
**Impact:** These constants are UNUSED but could cause confusion. They're legacy from before the AIModelConfig migration.
|
||||
|
||||
**Recommendation:**
|
||||
- [ ] Remove unused `MODEL_LANDSCAPE_SIZES` dict
|
||||
- [ ] Remove unused `DEFAULT_SQUARE_SIZE` constant
|
||||
- [ ] Add deprecation comment if keeping for reference
|
||||
|
||||
---
|
||||
|
||||
### 🔴 GAP-2: Hardcoded Size Fallbacks in Tasks
|
||||
|
||||
**Location:** `backend/igny8_core/ai/tasks.py` lines 254-260
|
||||
|
||||
**Code:**
|
||||
```python
|
||||
else:
|
||||
# Fallback sizes if no model config (should never happen)
|
||||
model_landscape_size = '1792x1024'
|
||||
model_square_size = '1024x1024'
|
||||
logger.warning(f"[process_image_generation_queue] No model config, using fallback sizes")
|
||||
```
|
||||
|
||||
**Impact:** LOW - Fallback only triggers if database is misconfigured. But the hardcoded sizes may not match actual model requirements.
|
||||
|
||||
**Recommendation:**
|
||||
- [ ] Consider failing gracefully with clear error instead of using fallback
|
||||
- [ ] Or: Load fallback from a system default in database
|
||||
|
||||
---
|
||||
|
||||
### 🔴 GAP-3: Frontend VALID_SIZES_BY_MODEL Hardcoded
|
||||
|
||||
**Location:** `frontend/src/components/common/ImageGenerationCard.tsx` lines 52-55
|
||||
|
||||
**Code:**
|
||||
```tsx
|
||||
const VALID_SIZES_BY_MODEL: Record<string, string[]> = {
|
||||
'dall-e-3': ['1024x1024', '1024x1792', '1792x1024'],
|
||||
'dall-e-2': ['256x256', '512x512', '1024x1024'],
|
||||
};
|
||||
```
|
||||
|
||||
**Impact:** MEDIUM - Test image generation card only shows OpenAI sizes, not Runware/Bytedance sizes.
|
||||
|
||||
**Recommendation:**
|
||||
- [ ] Fetch valid_sizes from AIModelConfig via API
|
||||
- [ ] Or: Pass sizes from backend settings endpoint
|
||||
|
||||
---
|
||||
|
||||
### 🔴 GAP-4: Backend VALID_SIZES_BY_MODEL Hardcoded
|
||||
|
||||
**Location:** `backend/igny8_core/ai/constants.py` lines 40-43
|
||||
|
||||
**Code:**
|
||||
```python
|
||||
VALID_SIZES_BY_MODEL = {
|
||||
'dall-e-3': ['1024x1024', '1024x1792', '1792x1024'],
|
||||
'dall-e-2': ['256x256', '512x512', '1024x1024'],
|
||||
}
|
||||
```
|
||||
|
||||
**Impact:** MEDIUM - Used for OpenAI validation only. Runware models bypass this validation.
|
||||
|
||||
**Status:** PARTIAL - Only affects OpenAI validation. Runware has its own validation via provider-specific code.
|
||||
|
||||
**Recommendation:**
|
||||
- [ ] Move validation to AIModelConfig.valid_sizes field
|
||||
- [ ] Validate against model's valid_sizes from database
|
||||
|
||||
---
|
||||
|
||||
### 🔴 GAP-5: Missing Runware Model Size Validation
|
||||
|
||||
**Location:** `backend/igny8_core/ai/ai_core.py` lines 943-1050
|
||||
|
||||
**Code:** The `_generate_image_runware()` method does NOT validate sizes against `valid_sizes` from database.
|
||||
|
||||
**Impact:** LOW - Runware API will reject invalid sizes anyway, but error message won't be clear.
|
||||
|
||||
**Recommendation:**
|
||||
- [ ] Add validation: check `size` against `AIModelConfig.valid_sizes` before API call
|
||||
- [ ] Return clear error: "Size X is not valid for model Y"
|
||||
|
||||
---
|
||||
|
||||
### 🔴 GAP-6: Seedream Minimum Pixel Validation Hardcoded
|
||||
|
||||
**Location:** `backend/igny8_core/ai/ai_core.py` lines 1018-1027
|
||||
|
||||
**Code:**
|
||||
```python
|
||||
elif runware_model.startswith('bytedance:'):
|
||||
# Enforce minimum size for Seedream (min 3,686,400 pixels ~ 1920x1920)
|
||||
current_pixels = width * height
|
||||
if current_pixels < 3686400:
|
||||
# Use default Seedream square size
|
||||
inference_task['width'] = 2048
|
||||
inference_task['height'] = 2048
|
||||
```
|
||||
|
||||
**Impact:** LOW - This hardcoded check works, but should come from database.
|
||||
|
||||
**Recommendation:**
|
||||
- [ ] Add `min_pixels` field to AIModelConfig
|
||||
- [ ] Check model.min_pixels instead of hardcoded 3686400
|
||||
|
||||
---
|
||||
|
||||
### 🔴 GAP-7: Provider-Specific Steps/CFGScale Hardcoded
|
||||
|
||||
**Location:** `backend/igny8_core/ai/ai_core.py` lines 995-1040
|
||||
|
||||
**Code:**
|
||||
```python
|
||||
if runware_model.startswith('bria:'):
|
||||
inference_task['steps'] = 20
|
||||
# ...
|
||||
elif runware_model.startswith('runware:'):
|
||||
inference_task['steps'] = 20
|
||||
inference_task['CFGScale'] = 7
|
||||
```
|
||||
|
||||
**Impact:** LOW - Works correctly but adding new models requires code changes.
|
||||
|
||||
**Recommendation:**
|
||||
- [ ] Add `generation_params` JSON field to AIModelConfig
|
||||
- [ ] Store steps, CFGScale, etc. in database per model
|
||||
|
||||
---
|
||||
|
||||
### 🔴 GAP-8: Image Count Not Per-Content Configurable
|
||||
|
||||
**Location:** System-wide setting only
|
||||
|
||||
**Current Behavior:**
|
||||
- `max_images` is a global setting (site-wide)
|
||||
- All articles get the same number of in_article images
|
||||
|
||||
**Impact:** MEDIUM - Users cannot set different image counts per article/keyword.
|
||||
|
||||
**Recommendation:**
|
||||
- [ ] Add `images_count` field to Content model (nullable, inherits from site default)
|
||||
- [ ] Or: Add to Keywords model for keyword-level override
|
||||
|
||||
---
|
||||
|
||||
### 🟡 GAP-9: Legacy generate_images.py Function (Partially Dead Code)
|
||||
|
||||
**Location:** `backend/igny8_core/ai/functions/generate_images.py`
|
||||
|
||||
**Issue:** `generate_images_core()` function exists but appears to be legacy. Main flow uses `process_image_generation_queue()` in tasks.py.
|
||||
|
||||
**Impact:** LOW - Code duplication, potential maintenance burden.
|
||||
|
||||
**Recommendation:**
|
||||
- [ ] Audit if `generate_images.py` is actually used anywhere
|
||||
- [ ] If not: Add deprecation warning or remove
|
||||
- [ ] If used: Ensure it uses same dynamic config loading
|
||||
|
||||
---
|
||||
|
||||
### 🟡 GAP-10: No Validation of quality_tier Values
|
||||
|
||||
**Location:** Multiple locations
|
||||
|
||||
**Issue:** When user selects a quality_tier, there's no validation that:
|
||||
1. The tier exists in AIModelConfig
|
||||
2. The tier has an active model
|
||||
3. The user's plan allows that tier
|
||||
|
||||
**Impact:** MEDIUM - Could lead to runtime errors if tier doesn't exist.
|
||||
|
||||
**Recommendation:**
|
||||
- [ ] Add validation in settings save endpoint
|
||||
- [ ] Return error if selected tier has no active model
|
||||
|
||||
---
|
||||
|
||||
## 4. Image Count Flow (Working Correctly)
|
||||
|
||||
### How image count works:
|
||||
|
||||
1. **User configures:**
|
||||
- `max_images` (1-8) in Site Settings → saved to AccountSettings
|
||||
|
||||
2. **Prompt generation:**
|
||||
```python
|
||||
# generate_image_prompts.py
|
||||
max_images = AISettings.get_effective_max_images(account) # e.g., 4
|
||||
# Creates: 1 featured + 4 in_article = 5 image prompts
|
||||
```
|
||||
|
||||
3. **Image types:**
|
||||
- `featured` - Always 1, position=0, landscape size
|
||||
- `in_article` - Up to max_images, positions 0,1,2,3..., alternating square/landscape
|
||||
|
||||
4. **Size determination:**
|
||||
```python
|
||||
# tasks.py
|
||||
if image.image_type == 'featured':
|
||||
image_size = featured_image_size # landscape
|
||||
elif image.image_type == 'in_article':
|
||||
position = image.position or 0
|
||||
if position % 2 == 0: # 0, 2
|
||||
image_size = square_size
|
||||
else: # 1, 3
|
||||
image_size = landscape_size
|
||||
```
|
||||
|
||||
**STATUS:** ✅ Working correctly. No gaps identified in image count logic.
|
||||
|
||||
---
|
||||
|
||||
## 5. Automation Flow (Working Correctly)
|
||||
|
||||
### Stage 6: Image Generation
|
||||
|
||||
**Location:** `automation_service.py` lines 1236-1400
|
||||
|
||||
**Flow:**
|
||||
1. Query `Images.objects.filter(site=site, status='pending')`
|
||||
2. For each image: `process_image_generation_queue.delay(image_ids=[image.id], ...)`
|
||||
3. Monitor task completion
|
||||
4. Update run progress
|
||||
|
||||
**STATUS:** ✅ Uses same task as manual generation. Consistent behavior.
|
||||
|
||||
---
|
||||
|
||||
## 6. Model Provider Support Matrix
|
||||
|
||||
| Provider | Models | Status | Gaps |
|
||||
|----------|--------|--------|------|
|
||||
| OpenAI | dall-e-3, dall-e-2 | ✅ Working | Valid sizes hardcoded |
|
||||
| Runware | runware:97@1 | ✅ Working | No size validation |
|
||||
| Runware | bria:10@1 | ✅ Working | Steps hardcoded |
|
||||
| Runware | google:4@2 | ✅ Working | Resolution param hardcoded |
|
||||
| Runware | bytedance:seedream@4.5 | ✅ Working | Min pixels hardcoded |
|
||||
|
||||
---
|
||||
|
||||
## 7. Priority Action Items
|
||||
|
||||
### High Priority
|
||||
1. **GAP-4/5:** Implement database-driven size validation for all providers
|
||||
2. **GAP-10:** Add quality_tier validation on save
|
||||
|
||||
### Medium Priority
|
||||
3. **GAP-6/7:** Move provider-specific params to AIModelConfig.generation_params
|
||||
4. **GAP-8:** Consider per-content image count override
|
||||
|
||||
### Low Priority
|
||||
5. **GAP-1:** Clean up unused constants
|
||||
6. **GAP-9:** Audit and deprecate legacy code
|
||||
7. **GAP-3:** Fetch valid sizes from API in frontend
|
||||
|
||||
---
|
||||
|
||||
## 8. Recommendations Summary
|
||||
|
||||
### Short-term (Before Launch)
|
||||
- Ensure all hardcoded fallbacks are clearly logged
|
||||
- Test each model tier end-to-end
|
||||
|
||||
### Medium-term (Post-Launch)
|
||||
- Migrate all hardcoded params to AIModelConfig fields
|
||||
- Add model validation on quality_tier save
|
||||
|
||||
### Long-term (Future Enhancement)
|
||||
- Per-content image count override
|
||||
- Per-keyword image style override
|
||||
- Image regeneration without deleting existing
|
||||
|
||||
---
|
||||
|
||||
## Appendix: Key Files Reference
|
||||
|
||||
| File | Purpose |
|
||||
|------|---------|
|
||||
| `ai/tasks.py` | Main image generation Celery task |
|
||||
| `ai/ai_core.py` | Provider-specific generation methods |
|
||||
| `ai/functions/generate_image_prompts.py` | Extract prompts from content |
|
||||
| `modules/system/ai_settings.py` | System defaults + account overrides |
|
||||
| `modules/system/settings_views.py` | Frontend settings API |
|
||||
| `business/billing/models.py` | AIModelConfig model |
|
||||
| `business/automation/services/automation_service.py` | Automation Stage 6 |
|
||||
Reference in New Issue
Block a user