fixes for ai and iamge related models bacekedn

This commit is contained in:
IGNY8 VPS (Salman)
2026-01-10 05:11:24 +00:00
parent 0c693dc1cc
commit 975eab46cf
9 changed files with 156 additions and 176 deletions

View File

@@ -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),
]

View File

@@ -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
]

View File

@@ -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,

View File

@@ -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),
),
]

View File

@@ -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'),
),
]

View File

@@ -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(
# Save as single consolidated record
setting, created = AccountSettings.objects.update_or_create(
account=account,
key=account_key,
defaults={'value': {'value': value}} # Model uses 'value' field
key=self.AI_SETTINGS_KEY,
defaults={'value': existing_settings}
)
saved_keys.append(account_key)
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
)

View File

@@ -776,25 +776,16 @@ UNFOLD = {
{"title": "Downloads", "icon": "download", "link": lambda request: "/admin/plugins/plugindownload/"},
],
},
# Automation
{
"title": "Automation",
"icon": "smart_toy",
"collapsible": True,
"items": [
{"title": "Configs", "icon": "settings_suggest", "link": lambda request: "/admin/automation/automationconfig/"},
{"title": "Runs", "icon": "play_circle", "link": lambda request: "/admin/automation/automationrun/"},
],
},
# AI Configuration
{
"title": "AI Configuration",
"icon": "psychology",
"collapsible": True,
"items": [
{"title": "System AI Settings", "icon": "tune", "link": lambda request: "/admin/system/systemaisettings/"},
{"title": "AI Models", "icon": "model_training", "link": lambda request: "/admin/billing/aimodelconfig/"},
{"title": "Credit Costs", "icon": "calculate", "link": lambda request: "/admin/billing/creditcostconfig/"},
{"title": "Billing Config", "icon": "tune", "link": lambda request: "/admin/billing/billingconfiguration/"},
{"title": "Credit Costs by Function", "icon": "calculate", "link": lambda request: "/admin/billing/creditcostconfig/"},
{"title": "Account-Specific AI Settings", "icon": "account_circle", "link": lambda request: "/admin/system/aisettings/"},
{"title": "AI Task Logs", "icon": "history", "link": lambda request: "/admin/ai/aitasklog/"},
],
},
@@ -817,11 +808,25 @@ UNFOLD = {
"collapsible": True,
"items": [
{"title": "Integration Providers", "icon": "key", "link": lambda request: "/admin/system/integrationprovider/"},
{"title": "System AI Settings", "icon": "psychology", "link": lambda request: "/admin/system/systemaisettings/"},
{"title": "Global AI Prompts", "icon": "chat", "link": lambda request: "/admin/system/globalaiprompt/"},
{"title": "Automation Configs", "icon": "settings_suggest", "link": lambda request: "/admin/automation/automationconfig/"},
{"title": "Automation Runs", "icon": "play_circle", "link": lambda request: "/admin/automation/automationrun/"},
{"title": "Module Settings", "icon": "view_module", "link": lambda request: "/admin/system/globalmodulesettings/"},
{"title": "AI Prompts", "icon": "smart_toy", "link": lambda request: "/admin/system/globalaiprompt/"},
{"title": "Author Profiles", "icon": "person_outline", "link": lambda request: "/admin/system/globalauthorprofile/"},
{"title": "Strategies", "icon": "strategy", "link": lambda request: "/admin/system/globalstrategy/"},
{"title": "Billing Configuration", "icon": "payments", "link": lambda request: "/admin/billing/billingconfiguration/"},
],
},
# System Configuration
{
"title": "System Configuration",
"icon": "tune",
"collapsible": True,
"items": [
{"title": "System Settings", "icon": "settings", "link": lambda request: "/admin/system/systemsettings/"},
{"title": "Account Settings", "icon": "account_circle", "link": lambda request: "/admin/system/accountsettings/"},
{"title": "User Settings", "icon": "person_search", "link": lambda request: "/admin/system/usersettings/"},
{"title": "Module Settings", "icon": "view_module", "link": lambda request: "/admin/system/modulesettings/"},
],
},
# Resources

View File

@@ -87,23 +87,8 @@ export default function SiteList() {
}, []);
const loadUserPreferences = async () => {
try {
const { fetchAccountSetting } = await import('../../services/api');
const setting = await fetchAccountSetting('user_preferences');
const preferences = setting.config as { selectedIndustry?: string; selectedSectors?: string[] } | undefined;
if (preferences) {
setUserPreferences(preferences);
}
} catch (error: any) {
// 404 means preferences don't exist yet - that's fine
// 500 and other errors should be handled silently - user can still use the page
if (error?.status === 404) {
// Preferences don't exist yet - this is expected for new users
return;
}
// Silently handle other errors (500, network errors, etc.) - don't spam console
// User can still use the page without preferences
}
// User preferences are now loaded from site/account data, not from a separate endpoint
// This function is kept for backward compatibility but does nothing
};
useEffect(() => {

View File

@@ -433,16 +433,8 @@ export default function SiteSettings() {
};
const loadUserPreferences = async () => {
try {
const { fetchAccountSetting } = await import('../../services/api');
const setting = await fetchAccountSetting('user_preferences');
const preferences = setting.config as { selectedIndustry?: string; selectedSectors?: string[] } | undefined;
if (preferences) {
setUserPreferences(preferences);
}
} catch (error: any) {
// Silently handle errors
}
// User preferences are now loaded from site/account data, not from a separate endpoint
// This function is kept for backward compatibility but does nothing
};
const loadSiteSectors = async () => {
@@ -947,7 +939,7 @@ export default function SiteSettings() {
<SelectDropdown
options={qualityTiers.length > 0
? qualityTiers.map(tier => ({
value: tier.value,
value: tier.tier || tier.value,
label: `${tier.label} (${tier.credits} credits)`
}))
: [
@@ -956,7 +948,7 @@ export default function SiteSettings() {
{ value: 'premium', label: 'Premium (15 credits)' },
]
}
value={selectedTier}
value={selectedTier || 'quality'}
onChange={(value) => setSelectedTier(value)}
className="w-full"
/>
@@ -987,11 +979,11 @@ export default function SiteSettings() {
<div>
<Label className="mb-2">Images per Article</Label>
<SelectDropdown
options={Array.from({ length: 4 }, (_, i) => ({
options={Array.from({ length: maxAllowed || 8 }, (_, i) => ({
value: String(i + 1),
label: `${i + 1} image${i > 0 ? 's' : ''}`,
}))}
value={String(maxImages)}
value={String(maxImages || 4)}
onChange={(value) => setMaxImages(parseInt(value))}
className="w-full"
/>