fixes for ai and iamge related models bacekedn
This commit is contained in:
@@ -1,78 +0,0 @@
|
||||
# Generated migration for adding image size fields to AIModelConfig
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
def populate_image_sizes(apps, schema_editor):
|
||||
"""Populate image sizes based on model name"""
|
||||
AIModelConfig = apps.get_model('billing', 'AIModelConfig')
|
||||
|
||||
# Model-specific sizes
|
||||
model_sizes = {
|
||||
'runware:97@1': {
|
||||
'landscape_size': '1280x768',
|
||||
'square_size': '1024x1024',
|
||||
'valid_sizes': ['1024x1024', '1280x768', '768x1280'],
|
||||
},
|
||||
'bria:10@1': {
|
||||
'landscape_size': '1344x768',
|
||||
'square_size': '1024x1024',
|
||||
'valid_sizes': ['1024x1024', '1344x768', '768x1344'],
|
||||
},
|
||||
'google:4@2': {
|
||||
'landscape_size': '1376x768',
|
||||
'square_size': '1024x1024',
|
||||
'valid_sizes': ['1024x1024', '1376x768', '768x1376'],
|
||||
},
|
||||
'dall-e-3': {
|
||||
'landscape_size': '1792x1024',
|
||||
'square_size': '1024x1024',
|
||||
'valid_sizes': ['1024x1024', '1792x1024', '1024x1792'],
|
||||
},
|
||||
'dall-e-2': {
|
||||
'landscape_size': '1024x1024',
|
||||
'square_size': '1024x1024',
|
||||
'valid_sizes': ['256x256', '512x512', '1024x1024'],
|
||||
},
|
||||
}
|
||||
|
||||
for model_name, sizes in model_sizes.items():
|
||||
AIModelConfig.objects.filter(
|
||||
model_name=model_name,
|
||||
model_type='image'
|
||||
).update(**sizes)
|
||||
|
||||
|
||||
def reverse_migration(apps, schema_editor):
|
||||
"""Clear image size fields"""
|
||||
AIModelConfig = apps.get_model('billing', 'AIModelConfig')
|
||||
AIModelConfig.objects.filter(model_type='image').update(
|
||||
landscape_size=None,
|
||||
square_size='1024x1024',
|
||||
valid_sizes=[],
|
||||
)
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('billing', '0026_populate_aimodel_credits'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AddField(
|
||||
model_name='aimodelconfig',
|
||||
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='aimodelconfig',
|
||||
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='aimodelconfig',
|
||||
name='valid_sizes',
|
||||
field=models.JSONField(blank=True, default=list, help_text="List of valid sizes for this model (e.g., ['1024x1024', '1792x1024'])"),
|
||||
),
|
||||
migrations.RunPython(populate_image_sizes, reverse_migration),
|
||||
]
|
||||
@@ -0,0 +1,15 @@
|
||||
# Generated manually - Add image size fields to AIModelConfig
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
"""Add landscape_size, square_size, and valid_sizes fields to AIModelConfig."""
|
||||
|
||||
dependencies = [
|
||||
('billing', '0029_add_webhook_event_and_manual_reference_constraint'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
# Fields already added via direct SQL, just mark as noop
|
||||
# This ensures the model matches the database schema
|
||||
]
|
||||
@@ -47,6 +47,12 @@ class SystemAISettings(models.Model):
|
||||
('hd', 'HD'),
|
||||
]
|
||||
|
||||
QUALITY_TIER_CHOICES = [
|
||||
('basic', 'Basic'),
|
||||
('quality', 'Quality'),
|
||||
('premium', 'Premium'),
|
||||
]
|
||||
|
||||
IMAGE_SIZE_CHOICES = [
|
||||
('1024x1024', '1024x1024 (Square)'),
|
||||
('1792x1024', '1792x1024 (Landscape)'),
|
||||
@@ -70,6 +76,12 @@ class SystemAISettings(models.Model):
|
||||
choices=IMAGE_STYLE_CHOICES,
|
||||
help_text="Default image style"
|
||||
)
|
||||
default_quality_tier = models.CharField(
|
||||
max_length=20,
|
||||
default='basic',
|
||||
choices=QUALITY_TIER_CHOICES,
|
||||
help_text="Default quality tier for image generation"
|
||||
)
|
||||
image_quality = models.CharField(
|
||||
max_length=20,
|
||||
default='standard',
|
||||
@@ -78,7 +90,11 @@ class SystemAISettings(models.Model):
|
||||
)
|
||||
max_images_per_article = models.IntegerField(
|
||||
default=4,
|
||||
help_text="Max in-article images (1-8)"
|
||||
help_text="Default number of in-article images"
|
||||
)
|
||||
max_allowed_images = models.IntegerField(
|
||||
default=8,
|
||||
help_text="Maximum allowed in-article images (dropdown limit)"
|
||||
)
|
||||
image_size = models.CharField(
|
||||
max_length=20,
|
||||
|
||||
@@ -0,0 +1,18 @@
|
||||
# Generated by Django 5.2.10 on 2026-01-10 04:45
|
||||
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('system', '0021_add_smtp_email_settings'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AddField(
|
||||
model_name='systemaisettings',
|
||||
name='default_quality_tier',
|
||||
field=models.CharField(choices=[('basic', 'Basic'), ('quality', 'Quality'), ('premium', 'Premium')], default='basic', help_text='Default quality tier for image generation', max_length=20),
|
||||
),
|
||||
]
|
||||
@@ -0,0 +1,23 @@
|
||||
# Generated by Django 5.2.10 on 2026-01-10 04:49
|
||||
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('system', '0022_systemaisettings_default_quality_tier_and_more'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AddField(
|
||||
model_name='systemaisettings',
|
||||
name='max_allowed_images',
|
||||
field=models.IntegerField(default=8, help_text='Maximum allowed in-article images (dropdown limit)'),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='systemaisettings',
|
||||
name='max_images_per_article',
|
||||
field=models.IntegerField(default=4, help_text='Default number of in-article images'),
|
||||
),
|
||||
]
|
||||
@@ -528,12 +528,17 @@ class ContentGenerationSettingsViewSet(viewsets.ViewSet):
|
||||
This endpoint returns:
|
||||
- content_generation: temperature, max_tokens
|
||||
- image_generation: quality_tiers, selected_tier, styles, selected_style, max_images
|
||||
|
||||
Settings are stored in a single AccountSettings record with key='ai_settings'
|
||||
"""
|
||||
permission_classes = [IsAuthenticatedAndActive, HasTenantAccess]
|
||||
authentication_classes = [JWTAuthentication]
|
||||
throttle_scope = 'system'
|
||||
throttle_classes = [DebugScopedRateThrottle]
|
||||
|
||||
# Single key for all AI settings per account
|
||||
AI_SETTINGS_KEY = 'ai_settings'
|
||||
|
||||
def _get_account(self, request):
|
||||
"""Get account from request"""
|
||||
account = getattr(request, 'account', None)
|
||||
@@ -543,6 +548,20 @@ class ContentGenerationSettingsViewSet(viewsets.ViewSet):
|
||||
account = getattr(user, 'account', None)
|
||||
return account
|
||||
|
||||
def _get_account_ai_settings(self, account):
|
||||
"""Get consolidated AI settings for account, returns dict with all settings"""
|
||||
if not account:
|
||||
return {}
|
||||
|
||||
setting = AccountSettings.objects.filter(
|
||||
account=account,
|
||||
key=self.AI_SETTINGS_KEY
|
||||
).first()
|
||||
|
||||
if setting and setting.value:
|
||||
return setting.value
|
||||
return {}
|
||||
|
||||
def list(self, request):
|
||||
"""
|
||||
GET /api/v1/accounts/settings/ai/
|
||||
@@ -566,6 +585,9 @@ class ContentGenerationSettingsViewSet(viewsets.ViewSet):
|
||||
try:
|
||||
from igny8_core.business.billing.models import AIModelConfig
|
||||
|
||||
# Get consolidated account settings
|
||||
account_settings = self._get_account_ai_settings(account)
|
||||
|
||||
# Get quality tiers from AIModelConfig (image models)
|
||||
quality_tiers = []
|
||||
for model in AIModelConfig.objects.filter(model_type='image', is_active=True).order_by('credits_per_image'):
|
||||
@@ -594,21 +616,15 @@ class ContentGenerationSettingsViewSet(viewsets.ViewSet):
|
||||
for opt in SystemAISettings.IMAGE_STYLE_CHOICES
|
||||
]
|
||||
|
||||
# Get effective settings (SystemAISettings with AccountSettings overrides)
|
||||
temperature = SystemAISettings.get_effective_temperature(account)
|
||||
max_tokens = SystemAISettings.get_effective_max_tokens(account)
|
||||
image_style = SystemAISettings.get_effective_image_style(account)
|
||||
max_images = SystemAISettings.get_effective_max_images(account)
|
||||
# Get system defaults
|
||||
system_defaults = SystemAISettings.get_instance()
|
||||
|
||||
# Get selected quality tier from AccountSettings
|
||||
selected_tier = 'quality' # Default
|
||||
if account:
|
||||
tier_setting = AccountSettings.objects.filter(
|
||||
account=account,
|
||||
key='ai.image_quality_tier'
|
||||
).first()
|
||||
if tier_setting and tier_setting.value: # Model uses 'value' field
|
||||
selected_tier = tier_setting.value.get('value', 'quality')
|
||||
# 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()
|
||||
@@ -641,7 +657,7 @@ class ContentGenerationSettingsViewSet(viewsets.ViewSet):
|
||||
'styles': styles,
|
||||
'selected_style': image_style,
|
||||
'max_images': max_images,
|
||||
'max_allowed': 8,
|
||||
'max_allowed': system_defaults.max_allowed_images,
|
||||
# Image sizes based on selected model
|
||||
'featured_image_size': featured_image_size,
|
||||
'landscape_image_size': landscape_image_size,
|
||||
@@ -665,20 +681,14 @@ class ContentGenerationSettingsViewSet(viewsets.ViewSet):
|
||||
"""
|
||||
PUT/POST /api/v1/accounts/settings/ai/
|
||||
|
||||
Save account-specific overrides to AccountSettings.
|
||||
Save account-specific overrides to a single AccountSettings record.
|
||||
All AI settings are stored in one record with key='ai_settings'.
|
||||
|
||||
Accepts nested structure:
|
||||
{
|
||||
"content_generation": { "temperature": 0.8, "max_tokens": 4096 },
|
||||
"image_generation": { "quality_tier": "premium", "image_style": "illustration", "max_images_per_article": 6 }
|
||||
}
|
||||
Or flat structure:
|
||||
{
|
||||
"temperature": 0.8,
|
||||
"max_tokens": 4096,
|
||||
"image_quality_tier": "premium",
|
||||
"image_style": "illustration",
|
||||
"max_images": 6
|
||||
}
|
||||
"""
|
||||
account = self._get_account(request)
|
||||
|
||||
@@ -691,44 +701,38 @@ class ContentGenerationSettingsViewSet(viewsets.ViewSet):
|
||||
|
||||
try:
|
||||
data = request.data
|
||||
saved_keys = []
|
||||
|
||||
# Get existing settings or start fresh
|
||||
existing_settings = self._get_account_ai_settings(account)
|
||||
|
||||
# Handle nested structure from frontend
|
||||
content_gen = data.get('content_generation', {})
|
||||
image_gen = data.get('image_generation', {})
|
||||
|
||||
# Flatten nested structure or use flat keys
|
||||
flat_data = {
|
||||
'temperature': content_gen.get('temperature') if content_gen else data.get('temperature'),
|
||||
'max_tokens': content_gen.get('max_tokens') if content_gen else data.get('max_tokens'),
|
||||
'image_quality_tier': image_gen.get('quality_tier') if image_gen else data.get('image_quality_tier'),
|
||||
'image_style': image_gen.get('image_style') if image_gen else data.get('image_style'),
|
||||
'max_images': image_gen.get('max_images_per_article') if image_gen else data.get('max_images'),
|
||||
}
|
||||
# Update with new values (only if provided)
|
||||
if content_gen.get('temperature') is not None:
|
||||
existing_settings['temperature'] = content_gen['temperature']
|
||||
if content_gen.get('max_tokens') is not None:
|
||||
existing_settings['max_tokens'] = content_gen['max_tokens']
|
||||
|
||||
# Map request fields to AccountSettings keys
|
||||
key_mappings = {
|
||||
'temperature': 'ai.temperature',
|
||||
'max_tokens': 'ai.max_tokens',
|
||||
'image_quality_tier': 'ai.image_quality_tier',
|
||||
'image_style': 'ai.image_style',
|
||||
'max_images': 'ai.max_images',
|
||||
}
|
||||
if image_gen.get('quality_tier') is not None:
|
||||
existing_settings['quality_tier'] = image_gen['quality_tier']
|
||||
if image_gen.get('image_style') is not None:
|
||||
existing_settings['image_style'] = image_gen['image_style']
|
||||
if image_gen.get('max_images_per_article') is not None:
|
||||
existing_settings['max_images'] = image_gen['max_images_per_article']
|
||||
|
||||
for field, account_key in key_mappings.items():
|
||||
value = flat_data.get(field)
|
||||
if value is not None:
|
||||
AccountSettings.objects.update_or_create(
|
||||
account=account,
|
||||
key=account_key,
|
||||
defaults={'value': {'value': value}} # Model uses 'value' field
|
||||
)
|
||||
saved_keys.append(account_key)
|
||||
# Save as single consolidated record
|
||||
setting, created = AccountSettings.objects.update_or_create(
|
||||
account=account,
|
||||
key=self.AI_SETTINGS_KEY,
|
||||
defaults={'value': existing_settings}
|
||||
)
|
||||
|
||||
logger.info(f"[ContentGenerationSettings] Saved {saved_keys} for account {account.id}")
|
||||
logger.info(f"[ContentGenerationSettings] Saved ai_settings for account {account.id}: {existing_settings}")
|
||||
|
||||
return success_response(
|
||||
data={'saved_keys': saved_keys},
|
||||
data={'settings': existing_settings},
|
||||
message='AI settings saved successfully',
|
||||
request=request
|
||||
)
|
||||
|
||||
Reference in New Issue
Block a user