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'),
|
('hd', 'HD'),
|
||||||
]
|
]
|
||||||
|
|
||||||
|
QUALITY_TIER_CHOICES = [
|
||||||
|
('basic', 'Basic'),
|
||||||
|
('quality', 'Quality'),
|
||||||
|
('premium', 'Premium'),
|
||||||
|
]
|
||||||
|
|
||||||
IMAGE_SIZE_CHOICES = [
|
IMAGE_SIZE_CHOICES = [
|
||||||
('1024x1024', '1024x1024 (Square)'),
|
('1024x1024', '1024x1024 (Square)'),
|
||||||
('1792x1024', '1792x1024 (Landscape)'),
|
('1792x1024', '1792x1024 (Landscape)'),
|
||||||
@@ -70,6 +76,12 @@ class SystemAISettings(models.Model):
|
|||||||
choices=IMAGE_STYLE_CHOICES,
|
choices=IMAGE_STYLE_CHOICES,
|
||||||
help_text="Default image style"
|
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(
|
image_quality = models.CharField(
|
||||||
max_length=20,
|
max_length=20,
|
||||||
default='standard',
|
default='standard',
|
||||||
@@ -78,7 +90,11 @@ class SystemAISettings(models.Model):
|
|||||||
)
|
)
|
||||||
max_images_per_article = models.IntegerField(
|
max_images_per_article = models.IntegerField(
|
||||||
default=4,
|
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(
|
image_size = models.CharField(
|
||||||
max_length=20,
|
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:
|
This endpoint returns:
|
||||||
- content_generation: temperature, max_tokens
|
- content_generation: temperature, max_tokens
|
||||||
- image_generation: quality_tiers, selected_tier, styles, selected_style, max_images
|
- 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]
|
permission_classes = [IsAuthenticatedAndActive, HasTenantAccess]
|
||||||
authentication_classes = [JWTAuthentication]
|
authentication_classes = [JWTAuthentication]
|
||||||
throttle_scope = 'system'
|
throttle_scope = 'system'
|
||||||
throttle_classes = [DebugScopedRateThrottle]
|
throttle_classes = [DebugScopedRateThrottle]
|
||||||
|
|
||||||
|
# Single key for all AI settings per account
|
||||||
|
AI_SETTINGS_KEY = 'ai_settings'
|
||||||
|
|
||||||
def _get_account(self, request):
|
def _get_account(self, request):
|
||||||
"""Get account from request"""
|
"""Get account from request"""
|
||||||
account = getattr(request, 'account', None)
|
account = getattr(request, 'account', None)
|
||||||
@@ -543,6 +548,20 @@ class ContentGenerationSettingsViewSet(viewsets.ViewSet):
|
|||||||
account = getattr(user, 'account', None)
|
account = getattr(user, 'account', None)
|
||||||
return account
|
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):
|
def list(self, request):
|
||||||
"""
|
"""
|
||||||
GET /api/v1/accounts/settings/ai/
|
GET /api/v1/accounts/settings/ai/
|
||||||
@@ -566,6 +585,9 @@ class ContentGenerationSettingsViewSet(viewsets.ViewSet):
|
|||||||
try:
|
try:
|
||||||
from igny8_core.business.billing.models import AIModelConfig
|
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)
|
# Get quality tiers from AIModelConfig (image models)
|
||||||
quality_tiers = []
|
quality_tiers = []
|
||||||
for model in AIModelConfig.objects.filter(model_type='image', is_active=True).order_by('credits_per_image'):
|
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
|
for opt in SystemAISettings.IMAGE_STYLE_CHOICES
|
||||||
]
|
]
|
||||||
|
|
||||||
# Get effective settings (SystemAISettings with AccountSettings overrides)
|
# Get system defaults
|
||||||
temperature = SystemAISettings.get_effective_temperature(account)
|
system_defaults = SystemAISettings.get_instance()
|
||||||
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 selected quality tier from AccountSettings
|
# Apply account overrides or use system defaults
|
||||||
selected_tier = 'quality' # Default
|
temperature = account_settings.get('temperature', system_defaults.temperature)
|
||||||
if account:
|
max_tokens = account_settings.get('max_tokens', system_defaults.max_tokens)
|
||||||
tier_setting = AccountSettings.objects.filter(
|
image_style = account_settings.get('image_style', system_defaults.image_style)
|
||||||
account=account,
|
max_images = account_settings.get('max_images', system_defaults.max_images_per_article)
|
||||||
key='ai.image_quality_tier'
|
selected_tier = account_settings.get('quality_tier', system_defaults.default_quality_tier)
|
||||||
).first()
|
|
||||||
if tier_setting and tier_setting.value: # Model uses 'value' field
|
|
||||||
selected_tier = tier_setting.value.get('value', 'quality')
|
|
||||||
|
|
||||||
# Get default image model (or model for selected tier)
|
# Get default image model (or model for selected tier)
|
||||||
default_image_model = AIModelConfig.get_default_image_model()
|
default_image_model = AIModelConfig.get_default_image_model()
|
||||||
@@ -641,7 +657,7 @@ class ContentGenerationSettingsViewSet(viewsets.ViewSet):
|
|||||||
'styles': styles,
|
'styles': styles,
|
||||||
'selected_style': image_style,
|
'selected_style': image_style,
|
||||||
'max_images': max_images,
|
'max_images': max_images,
|
||||||
'max_allowed': 8,
|
'max_allowed': system_defaults.max_allowed_images,
|
||||||
# Image sizes based on selected model
|
# Image sizes based on selected model
|
||||||
'featured_image_size': featured_image_size,
|
'featured_image_size': featured_image_size,
|
||||||
'landscape_image_size': landscape_image_size,
|
'landscape_image_size': landscape_image_size,
|
||||||
@@ -665,20 +681,14 @@ class ContentGenerationSettingsViewSet(viewsets.ViewSet):
|
|||||||
"""
|
"""
|
||||||
PUT/POST /api/v1/accounts/settings/ai/
|
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:
|
Accepts nested structure:
|
||||||
{
|
{
|
||||||
"content_generation": { "temperature": 0.8, "max_tokens": 4096 },
|
"content_generation": { "temperature": 0.8, "max_tokens": 4096 },
|
||||||
"image_generation": { "quality_tier": "premium", "image_style": "illustration", "max_images_per_article": 6 }
|
"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)
|
account = self._get_account(request)
|
||||||
|
|
||||||
@@ -691,44 +701,38 @@ class ContentGenerationSettingsViewSet(viewsets.ViewSet):
|
|||||||
|
|
||||||
try:
|
try:
|
||||||
data = request.data
|
data = request.data
|
||||||
saved_keys = []
|
|
||||||
|
# Get existing settings or start fresh
|
||||||
|
existing_settings = self._get_account_ai_settings(account)
|
||||||
|
|
||||||
# Handle nested structure from frontend
|
# Handle nested structure from frontend
|
||||||
content_gen = data.get('content_generation', {})
|
content_gen = data.get('content_generation', {})
|
||||||
image_gen = data.get('image_generation', {})
|
image_gen = data.get('image_generation', {})
|
||||||
|
|
||||||
# Flatten nested structure or use flat keys
|
# Update with new values (only if provided)
|
||||||
flat_data = {
|
if content_gen.get('temperature') is not None:
|
||||||
'temperature': content_gen.get('temperature') if content_gen else data.get('temperature'),
|
existing_settings['temperature'] = content_gen['temperature']
|
||||||
'max_tokens': content_gen.get('max_tokens') if content_gen else data.get('max_tokens'),
|
if content_gen.get('max_tokens') is not None:
|
||||||
'image_quality_tier': image_gen.get('quality_tier') if image_gen else data.get('image_quality_tier'),
|
existing_settings['max_tokens'] = content_gen['max_tokens']
|
||||||
'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'),
|
|
||||||
}
|
|
||||||
|
|
||||||
# Map request fields to AccountSettings keys
|
if image_gen.get('quality_tier') is not None:
|
||||||
key_mappings = {
|
existing_settings['quality_tier'] = image_gen['quality_tier']
|
||||||
'temperature': 'ai.temperature',
|
if image_gen.get('image_style') is not None:
|
||||||
'max_tokens': 'ai.max_tokens',
|
existing_settings['image_style'] = image_gen['image_style']
|
||||||
'image_quality_tier': 'ai.image_quality_tier',
|
if image_gen.get('max_images_per_article') is not None:
|
||||||
'image_style': 'ai.image_style',
|
existing_settings['max_images'] = image_gen['max_images_per_article']
|
||||||
'max_images': 'ai.max_images',
|
|
||||||
}
|
|
||||||
|
|
||||||
for field, account_key in key_mappings.items():
|
# Save as single consolidated record
|
||||||
value = flat_data.get(field)
|
setting, created = AccountSettings.objects.update_or_create(
|
||||||
if value is not None:
|
|
||||||
AccountSettings.objects.update_or_create(
|
|
||||||
account=account,
|
account=account,
|
||||||
key=account_key,
|
key=self.AI_SETTINGS_KEY,
|
||||||
defaults={'value': {'value': value}} # Model uses 'value' field
|
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(
|
return success_response(
|
||||||
data={'saved_keys': saved_keys},
|
data={'settings': existing_settings},
|
||||||
message='AI settings saved successfully',
|
message='AI settings saved successfully',
|
||||||
request=request
|
request=request
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -776,25 +776,16 @@ UNFOLD = {
|
|||||||
{"title": "Downloads", "icon": "download", "link": lambda request: "/admin/plugins/plugindownload/"},
|
{"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
|
# AI Configuration
|
||||||
{
|
{
|
||||||
"title": "AI Configuration",
|
"title": "AI Configuration",
|
||||||
"icon": "psychology",
|
"icon": "psychology",
|
||||||
"collapsible": True,
|
"collapsible": True,
|
||||||
"items": [
|
"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": "AI Models", "icon": "model_training", "link": lambda request: "/admin/billing/aimodelconfig/"},
|
||||||
{"title": "Credit Costs", "icon": "calculate", "link": lambda request: "/admin/billing/creditcostconfig/"},
|
{"title": "Credit Costs by Function", "icon": "calculate", "link": lambda request: "/admin/billing/creditcostconfig/"},
|
||||||
{"title": "Billing Config", "icon": "tune", "link": lambda request: "/admin/billing/billingconfiguration/"},
|
{"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/"},
|
{"title": "AI Task Logs", "icon": "history", "link": lambda request: "/admin/ai/aitasklog/"},
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
@@ -817,11 +808,25 @@ UNFOLD = {
|
|||||||
"collapsible": True,
|
"collapsible": True,
|
||||||
"items": [
|
"items": [
|
||||||
{"title": "Integration Providers", "icon": "key", "link": lambda request: "/admin/system/integrationprovider/"},
|
{"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": "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": "Author Profiles", "icon": "person_outline", "link": lambda request: "/admin/system/globalauthorprofile/"},
|
||||||
{"title": "Strategies", "icon": "strategy", "link": lambda request: "/admin/system/globalstrategy/"},
|
{"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
|
# Resources
|
||||||
|
|||||||
@@ -87,23 +87,8 @@ export default function SiteList() {
|
|||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
const loadUserPreferences = async () => {
|
const loadUserPreferences = async () => {
|
||||||
try {
|
// User preferences are now loaded from site/account data, not from a separate endpoint
|
||||||
const { fetchAccountSetting } = await import('../../services/api');
|
// This function is kept for backward compatibility but does nothing
|
||||||
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
|
|
||||||
}
|
|
||||||
};
|
};
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
|
|||||||
@@ -433,16 +433,8 @@ export default function SiteSettings() {
|
|||||||
};
|
};
|
||||||
|
|
||||||
const loadUserPreferences = async () => {
|
const loadUserPreferences = async () => {
|
||||||
try {
|
// User preferences are now loaded from site/account data, not from a separate endpoint
|
||||||
const { fetchAccountSetting } = await import('../../services/api');
|
// This function is kept for backward compatibility but does nothing
|
||||||
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
|
|
||||||
}
|
|
||||||
};
|
};
|
||||||
|
|
||||||
const loadSiteSectors = async () => {
|
const loadSiteSectors = async () => {
|
||||||
@@ -947,7 +939,7 @@ export default function SiteSettings() {
|
|||||||
<SelectDropdown
|
<SelectDropdown
|
||||||
options={qualityTiers.length > 0
|
options={qualityTiers.length > 0
|
||||||
? qualityTiers.map(tier => ({
|
? qualityTiers.map(tier => ({
|
||||||
value: tier.value,
|
value: tier.tier || tier.value,
|
||||||
label: `${tier.label} (${tier.credits} credits)`
|
label: `${tier.label} (${tier.credits} credits)`
|
||||||
}))
|
}))
|
||||||
: [
|
: [
|
||||||
@@ -956,7 +948,7 @@ export default function SiteSettings() {
|
|||||||
{ value: 'premium', label: 'Premium (15 credits)' },
|
{ value: 'premium', label: 'Premium (15 credits)' },
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
value={selectedTier}
|
value={selectedTier || 'quality'}
|
||||||
onChange={(value) => setSelectedTier(value)}
|
onChange={(value) => setSelectedTier(value)}
|
||||||
className="w-full"
|
className="w-full"
|
||||||
/>
|
/>
|
||||||
@@ -987,11 +979,11 @@ export default function SiteSettings() {
|
|||||||
<div>
|
<div>
|
||||||
<Label className="mb-2">Images per Article</Label>
|
<Label className="mb-2">Images per Article</Label>
|
||||||
<SelectDropdown
|
<SelectDropdown
|
||||||
options={Array.from({ length: 4 }, (_, i) => ({
|
options={Array.from({ length: maxAllowed || 8 }, (_, i) => ({
|
||||||
value: String(i + 1),
|
value: String(i + 1),
|
||||||
label: `${i + 1} image${i > 0 ? 's' : ''}`,
|
label: `${i + 1} image${i > 0 ? 's' : ''}`,
|
||||||
}))}
|
}))}
|
||||||
value={String(maxImages)}
|
value={String(maxImages || 4)}
|
||||||
onChange={(value) => setMaxImages(parseInt(value))}
|
onChange={(value) => setMaxImages(parseInt(value))}
|
||||||
className="w-full"
|
className="w-full"
|
||||||
/>
|
/>
|
||||||
|
|||||||
Reference in New Issue
Block a user