diff --git a/backend/igny8_core/admin/site.py b/backend/igny8_core/admin/site.py
index 9055111f..70a30ae8 100644
--- a/backend/igny8_core/admin/site.py
+++ b/backend/igny8_core/admin/site.py
@@ -161,6 +161,7 @@ class Igny8AdminSite(UnfoldAdminSite):
'models': [
('system', 'GlobalIntegrationSettings'),
('system', 'GlobalModuleSettings'),
+ ('billing', 'AIModelConfig'),
('system', 'GlobalAIPrompt'),
('system', 'GlobalAuthorProfile'),
('system', 'GlobalStrategy'),
diff --git a/backend/igny8_core/ai/settings.py b/backend/igny8_core/ai/settings.py
index 55574237..b233979a 100644
--- a/backend/igny8_core/ai/settings.py
+++ b/backend/igny8_core/ai/settings.py
@@ -59,9 +59,21 @@ def get_model_config(function_name: str, account) -> Dict[str, Any]:
# Start with global defaults
model = global_settings.openai_model
temperature = global_settings.openai_temperature
- max_tokens = global_settings.openai_max_tokens
api_key = global_settings.openai_api_key # ALWAYS from global
+ # Get max_tokens from AIModelConfig for the selected model
+ max_tokens = global_settings.openai_max_tokens # Fallback
+ try:
+ from igny8_core.business.billing.models import AIModelConfig
+ model_config = AIModelConfig.objects.filter(
+ model_name=model,
+ is_active=True
+ ).first()
+ if model_config and model_config.max_output_tokens:
+ max_tokens = model_config.max_output_tokens
+ except Exception as e:
+ logger.warning(f"Could not load max_tokens from AIModelConfig for {model}: {e}")
+
# Check if account has overrides (only for Starter/Growth/Scale plans)
# Free plan users cannot create IntegrationSettings records
try:
@@ -76,12 +88,23 @@ def get_model_config(function_name: str, account) -> Dict[str, Any]:
# Override model if specified (NULL = use global)
if config.get('model'):
model = config['model']
+ # Also update max_tokens for the overridden model
+ try:
+ from igny8_core.business.billing.models import AIModelConfig
+ override_config = AIModelConfig.objects.filter(
+ model_name=model,
+ is_active=True
+ ).first()
+ if override_config and override_config.max_output_tokens:
+ max_tokens = override_config.max_output_tokens
+ except Exception:
+ pass
# Override temperature if specified
if config.get('temperature') is not None:
temperature = config['temperature']
- # Override max_tokens if specified
+ # Override max_tokens if explicitly specified (rare case)
if config.get('max_tokens'):
max_tokens = config['max_tokens']
diff --git a/backend/igny8_core/api/permissions.py b/backend/igny8_core/api/permissions.py
index 7acc3707..6c3f91f9 100644
--- a/backend/igny8_core/api/permissions.py
+++ b/backend/igny8_core/api/permissions.py
@@ -124,12 +124,22 @@ class IsEditorOrAbove(permissions.BasePermission):
class IsAdminOrOwner(permissions.BasePermission):
"""
Permission class that requires admin or owner role only
+ OR user belongs to aws-admin account
For settings, keys, billing operations
"""
def has_permission(self, request, view):
if not request.user or not request.user.is_authenticated:
return False
+ # Check if user belongs to aws-admin account (case-insensitive)
+ if hasattr(request.user, 'account') and request.user.account:
+ account_name = getattr(request.user.account, 'name', None)
+ account_slug = getattr(request.user.account, 'slug', None)
+ if account_name and account_name.lower() == 'aws admin':
+ return True
+ if account_slug == 'aws-admin':
+ return True
+
# Check user role
if hasattr(request.user, 'role'):
role = request.user.role
diff --git a/backend/igny8_core/modules/billing/migrations/0023_update_runware_models.py b/backend/igny8_core/modules/billing/migrations/0023_update_runware_models.py
new file mode 100644
index 00000000..34ffc666
--- /dev/null
+++ b/backend/igny8_core/modules/billing/migrations/0023_update_runware_models.py
@@ -0,0 +1,87 @@
+"""
+Migration: Update Runware model configurations in AIModelConfig
+
+This migration:
+1. Updates runware:97@1 to have display_name "Hi Dream Full - Standard"
+2. Adds Bria 3.2 model as civitai:618692@691639
+"""
+from decimal import Decimal
+from django.db import migrations
+
+
+def update_runware_models(apps, schema_editor):
+ """Update Runware models in AIModelConfig"""
+ AIModelConfig = apps.get_model('billing', 'AIModelConfig')
+
+ # Update existing runware:97@1 model
+ AIModelConfig.objects.update_or_create(
+ model_name='runware:97@1',
+ defaults={
+ 'display_name': 'Hi Dream Full - Standard',
+ 'model_type': 'image',
+ 'provider': 'runware',
+ 'cost_per_image': Decimal('0.008'),
+ 'valid_sizes': ['512x512', '768x768', '1024x1024', '1024x1792', '1792x1024'],
+ 'supports_json_mode': False,
+ 'supports_vision': False,
+ 'supports_function_calling': False,
+ 'is_active': True,
+ 'is_default': True, # Make this the default Runware model
+ 'sort_order': 10,
+ 'description': 'Hi Dream Full - Standard quality image generation via Runware',
+ }
+ )
+
+ # Add Bria 3.2 Premium model
+ AIModelConfig.objects.update_or_create(
+ model_name='civitai:618692@691639',
+ defaults={
+ 'display_name': 'Bria 3.2 - Premium',
+ 'model_type': 'image',
+ 'provider': 'runware',
+ 'cost_per_image': Decimal('0.012'),
+ 'valid_sizes': ['512x512', '768x768', '1024x1024', '1024x1792', '1792x1024'],
+ 'supports_json_mode': False,
+ 'supports_vision': False,
+ 'supports_function_calling': False,
+ 'is_active': True,
+ 'is_default': False,
+ 'sort_order': 11,
+ 'description': 'Bria 3.2 - Premium quality image generation via Runware/Civitai',
+ }
+ )
+
+ # Optionally remove the old runware:100@1 and runware:101@1 models if they exist
+ AIModelConfig.objects.filter(
+ model_name__in=['runware:100@1', 'runware:101@1']
+ ).update(is_active=False)
+
+
+def reverse_migration(apps, schema_editor):
+ """Reverse the migration"""
+ AIModelConfig = apps.get_model('billing', 'AIModelConfig')
+
+ # Restore old display name
+ AIModelConfig.objects.filter(model_name='runware:97@1').update(
+ display_name='Runware Standard',
+ is_default=False,
+ )
+
+ # Remove Bria 3.2 model
+ AIModelConfig.objects.filter(model_name='civitai:618692@691639').delete()
+
+ # Re-activate old models
+ AIModelConfig.objects.filter(
+ model_name__in=['runware:100@1', 'runware:101@1']
+ ).update(is_active=True)
+
+
+class Migration(migrations.Migration):
+
+ dependencies = [
+ ('billing', '0022_fix_historical_calculation_mode_null'),
+ ]
+
+ operations = [
+ migrations.RunPython(update_runware_models, reverse_migration),
+ ]
diff --git a/backend/igny8_core/modules/system/admin.py b/backend/igny8_core/modules/system/admin.py
index 2cdc8bc2..325a4b91 100644
--- a/backend/igny8_core/modules/system/admin.py
+++ b/backend/igny8_core/modules/system/admin.py
@@ -2,6 +2,7 @@
System Module Admin
"""
from django.contrib import admin
+from django import forms
from unfold.admin import ModelAdmin
from igny8_core.admin.base import AccountAdminMixin, Igny8ModelAdmin
from .models import AIPrompt, IntegrationSettings, AuthorProfile, Strategy
@@ -333,16 +334,61 @@ class StrategyAdmin(ImportExportMixin, AccountAdminMixin, Igny8ModelAdmin):
# GLOBAL SETTINGS ADMIN - Platform-wide defaults
# =============================================================================
+class GlobalIntegrationSettingsForm(forms.ModelForm):
+ """Custom form for GlobalIntegrationSettings with dynamic choices from AIModelConfig"""
+
+ class Meta:
+ model = GlobalIntegrationSettings
+ fields = '__all__'
+
+ def __init__(self, *args, **kwargs):
+ super().__init__(*args, **kwargs)
+
+ # Load choices dynamically from AIModelConfig
+ from igny8_core.modules.system.global_settings_models import (
+ get_text_model_choices,
+ get_image_model_choices,
+ get_provider_choices,
+ )
+
+ # OpenAI text model choices
+ openai_choices = get_text_model_choices()
+ openai_text_choices = [(m, d) for m, d in openai_choices if 'gpt' in m.lower() or 'openai' in m.lower()]
+ if openai_text_choices:
+ self.fields['openai_model'].choices = openai_text_choices
+
+ # DALL-E image model choices
+ dalle_choices = get_image_model_choices(provider='openai')
+ if dalle_choices:
+ self.fields['dalle_model'].choices = dalle_choices
+
+ # Runware image model choices
+ runware_choices = get_image_model_choices(provider='runware')
+ if runware_choices:
+ self.fields['runware_model'].choices = runware_choices
+
+ # Image service provider choices (only OpenAI and Runware for now)
+ image_providers = get_provider_choices(model_type='image')
+ # Filter to only OpenAI and Runware
+ allowed_image_providers = [
+ (p, d) for p, d in image_providers
+ if p in ('openai', 'runware')
+ ]
+ if allowed_image_providers:
+ self.fields['default_image_service'].choices = allowed_image_providers
+
+
@admin.register(GlobalIntegrationSettings)
class GlobalIntegrationSettingsAdmin(Igny8ModelAdmin):
"""Admin for global integration settings (singleton)"""
+ form = GlobalIntegrationSettingsForm
list_display = ["id", "is_active", "last_updated", "updated_by"]
- readonly_fields = ["last_updated"]
+ readonly_fields = ["last_updated", "openai_max_tokens", "anthropic_max_tokens"]
fieldsets = (
("OpenAI Settings", {
"fields": ("openai_api_key", "openai_model", "openai_temperature", "openai_max_tokens"),
- "description": "Global OpenAI configuration used by all accounts (unless overridden)"
+ "description": "Global OpenAI configuration used by all accounts (unless overridden). Max tokens is loaded from AI Model Configuration."
}),
("Image Generation - Default Service", {
"fields": ("default_image_service",),
@@ -365,6 +411,49 @@ class GlobalIntegrationSettingsAdmin(Igny8ModelAdmin):
}),
)
+ def get_readonly_fields(self, request, obj=None):
+ """Make max_tokens fields readonly - they are populated from AI Model Configuration"""
+ readonly = list(super().get_readonly_fields(request, obj))
+ if 'openai_max_tokens' not in readonly:
+ readonly.append('openai_max_tokens')
+ if 'anthropic_max_tokens' not in readonly:
+ readonly.append('anthropic_max_tokens')
+ return readonly
+
+ def openai_max_tokens(self, obj):
+ """Display max tokens from the selected OpenAI model's configuration"""
+ from igny8_core.modules.system.global_settings_models import get_model_max_tokens
+ max_tokens = get_model_max_tokens(obj.openai_model) if obj else None
+ if max_tokens:
+ return f"{max_tokens:,} (from AI Model Configuration)"
+ return obj.openai_max_tokens if obj else "8192 (default)"
+ openai_max_tokens.short_description = "Max Output Tokens"
+
+ def anthropic_max_tokens(self, obj):
+ """Display max tokens from the selected Anthropic model's configuration"""
+ from igny8_core.modules.system.global_settings_models import get_model_max_tokens
+ max_tokens = get_model_max_tokens(obj.anthropic_model) if obj else None
+ if max_tokens:
+ return f"{max_tokens:,} (from AI Model Configuration)"
+ return obj.anthropic_max_tokens if obj else "8192 (default)"
+ anthropic_max_tokens.short_description = "Max Output Tokens"
+
+ def save_model(self, request, obj, form, change):
+ """Update max_tokens from model config on save"""
+ from igny8_core.modules.system.global_settings_models import get_model_max_tokens
+
+ # Update OpenAI max tokens from model config
+ openai_max = get_model_max_tokens(obj.openai_model)
+ if openai_max:
+ obj.openai_max_tokens = openai_max
+
+ # Update Anthropic max tokens from model config
+ anthropic_max = get_model_max_tokens(obj.anthropic_model)
+ if anthropic_max:
+ obj.anthropic_max_tokens = anthropic_max
+
+ super().save_model(request, obj, form, change)
+
def has_add_permission(self, request):
"""Only allow one instance (singleton pattern)"""
return not GlobalIntegrationSettings.objects.exists()
diff --git a/backend/igny8_core/modules/system/global_settings_models.py b/backend/igny8_core/modules/system/global_settings_models.py
index 35f11679..e859962d 100644
--- a/backend/igny8_core/modules/system/global_settings_models.py
+++ b/backend/igny8_core/modules/system/global_settings_models.py
@@ -5,6 +5,125 @@ Accounts can override model selection and parameters (but NOT API keys).
"""
from django.db import models
from django.conf import settings
+import logging
+
+logger = logging.getLogger(__name__)
+
+
+def get_text_model_choices():
+ """
+ Get text model choices from AIModelConfig database.
+ Returns list of tuples (model_name, display_name) for active text models.
+ Falls back to hardcoded defaults if database unavailable.
+ """
+ try:
+ from igny8_core.business.billing.models import AIModelConfig
+ models = AIModelConfig.objects.filter(
+ model_type='text',
+ is_active=True
+ ).order_by('sort_order', 'model_name')
+
+ if models.exists():
+ return [(m.model_name, m.display_name) for m in models]
+ except Exception as e:
+ logger.warning(f"Could not load text models from database: {e}")
+
+ # Fallback to hardcoded defaults
+ return [
+ ('gpt-4o-mini', 'GPT-4o mini - $0.15 / $0.60 per 1M tokens'),
+ ('gpt-4o', 'GPT-4o - $2.50 / $10.00 per 1M tokens'),
+ ]
+
+
+def get_image_model_choices(provider=None):
+ """
+ Get image model choices from AIModelConfig database.
+ Optionally filter by provider (openai, runware, etc.)
+ """
+ try:
+ from igny8_core.business.billing.models import AIModelConfig
+ qs = AIModelConfig.objects.filter(
+ model_type='image',
+ is_active=True
+ )
+ if provider:
+ qs = qs.filter(provider=provider)
+ qs = qs.order_by('sort_order', 'model_name')
+
+ if qs.exists():
+ return [(m.model_name, m.display_name) for m in qs]
+ except Exception as e:
+ logger.warning(f"Could not load image models from database: {e}")
+
+ # Fallback based on provider
+ if provider == 'openai':
+ return [
+ ('dall-e-3', 'DALL·E 3 - $0.040 per image'),
+ ('dall-e-2', 'DALL·E 2 - $0.020 per image'),
+ ]
+ elif provider == 'runware':
+ return [
+ ('runware:97@1', 'Hi Dream Full - Standard'),
+ ('civitai:618692@691639', 'Bria 3.2 - Premium'),
+ ]
+ return []
+
+
+def get_provider_choices(model_type='text'):
+ """
+ Get provider choices from AIModelConfig database.
+ Returns unique providers for the given model type.
+ """
+ try:
+ from igny8_core.business.billing.models import AIModelConfig
+ providers = list(AIModelConfig.objects.filter(
+ model_type=model_type,
+ is_active=True
+ ).values_list('provider', flat=True).distinct())
+
+ provider_display = {
+ 'openai': 'OpenAI DALL-E' if model_type == 'image' else 'OpenAI',
+ 'anthropic': 'Anthropic (Claude)',
+ 'runware': 'Runware',
+ 'google': 'Google',
+ }
+
+ if providers:
+ # Use dict to ensure unique entries
+ unique_providers = {}
+ for p in providers:
+ if p not in unique_providers:
+ unique_providers[p] = provider_display.get(p, p.title())
+ return [(p, d) for p, d in unique_providers.items()]
+ except Exception as e:
+ logger.warning(f"Could not load providers from database: {e}")
+
+ # Fallback
+ if model_type == 'text':
+ return [('openai', 'OpenAI (GPT)'), ('anthropic', 'Anthropic (Claude)')]
+ elif model_type == 'image':
+ return [('openai', 'OpenAI DALL-E'), ('runware', 'Runware')]
+ return []
+
+
+def get_model_max_tokens(model_name):
+ """
+ Get max_output_tokens for a specific model from AIModelConfig.
+ Returns None if model not found or doesn't have max_output_tokens.
+ """
+ try:
+ from igny8_core.business.billing.models import AIModelConfig
+ model_config = AIModelConfig.objects.filter(
+ model_name=model_name,
+ is_active=True
+ ).first()
+
+ if model_config and model_config.max_output_tokens:
+ return model_config.max_output_tokens
+ except Exception as e:
+ logger.warning(f"Could not get max tokens for model {model_name}: {e}")
+
+ return None
class GlobalIntegrationSettings(models.Model):
@@ -52,9 +171,8 @@ class GlobalIntegrationSettings(models.Model):
]
RUNWARE_MODEL_CHOICES = [
- ('runware:97@1', 'Runware 97@1 - Versatile Model'),
- ('runware:100@1', 'Runware 100@1 - High Quality'),
- ('runware:101@1', 'Runware 101@1 - Fast Generation'),
+ ('runware:97@1', 'Hi Dream Full - Standard'),
+ ('civitai:618692@691639', 'Bria 3.2 - Premium'),
]
BRIA_MODEL_CHOICES = [
@@ -79,7 +197,6 @@ class GlobalIntegrationSettings(models.Model):
IMAGE_SERVICE_CHOICES = [
('openai', 'OpenAI DALL-E'),
('runware', 'Runware'),
- ('bria', 'Bria AI'),
]
ANTHROPIC_MODEL_CHOICES = [
diff --git a/backend/igny8_core/modules/system/integration_views.py b/backend/igny8_core/modules/system/integration_views.py
index 002a828a..337d09ac 100644
--- a/backend/igny8_core/modules/system/integration_views.py
+++ b/backend/igny8_core/modules/system/integration_views.py
@@ -795,12 +795,25 @@ class IntegrationSettingsViewSet(viewsets.ViewSet):
# Build response with global defaults
if integration_type == 'openai':
+ # Get max_tokens from AIModelConfig for the selected model
+ max_tokens = global_settings.openai_max_tokens # Fallback
+ try:
+ from igny8_core.business.billing.models import AIModelConfig
+ model_config = AIModelConfig.objects.filter(
+ model_name=global_settings.openai_model,
+ is_active=True
+ ).first()
+ if model_config and model_config.max_output_tokens:
+ max_tokens = model_config.max_output_tokens
+ except Exception:
+ pass
+
response_data = {
'id': 'openai',
'enabled': True, # Always enabled (platform-wide)
'model': global_settings.openai_model,
'temperature': global_settings.openai_temperature,
- 'max_tokens': global_settings.openai_max_tokens,
+ 'max_tokens': max_tokens,
'using_global': True, # Flag to show it's using global
}
diff --git a/docs/50-DEPLOYMENT/final-clean-best-deployment-plan/IGNY8-APP-STRUCTURE.md b/docs/50-DEPLOYMENT/final-clean-best-deployment-plan/IGNY8-APP-STRUCTURE.md
new file mode 100644
index 00000000..ce1a6449
--- /dev/null
+++ b/docs/50-DEPLOYMENT/final-clean-best-deployment-plan/IGNY8-APP-STRUCTURE.md
@@ -0,0 +1,360 @@
+# IGNY8 Application Repository
+
+**Repository Name:** `igny8-app`
+**Purpose:** Custom application code for the IGNY8 Content AI Platform
+
+---
+
+## Overview
+
+This repository contains ONLY the custom business logic for IGNY8. It does not contain:
+- Dockerfiles (in stack repo)
+- package.json / requirements.txt (in stack repo)
+- Vite/TypeScript configs (in stack repo)
+- Docker compose files (in stack repo)
+
+---
+
+## Complete Folder Structure
+
+```
+igny8-app/
+│
+├── README.md # App overview
+├── CHANGELOG.md # Version history
+├── .gitignore # Git ignore rules
+├── .env.example # Environment template
+│
+├── backend/
+│ └── igny8_core/ # Django application
+│ ├── __init__.py
+│ ├── settings.py # Django settings
+│ ├── urls.py # Root URL routing
+│ ├── celery.py # Celery configuration
+│ ├── wsgi.py # WSGI entry point
+│ ├── asgi.py # ASGI entry point
+│ ├── tasks.py # Root Celery tasks
+│ │
+│ ├── auth/ # Authentication module
+│ │ ├── __init__.py
+│ │ ├── models.py # User, Account, Site, Sector, Plan
+│ │ ├── views.py # Login, register, password reset
+│ │ ├── serializers.py # DRF serializers
+│ │ ├── middleware.py # AccountContextMiddleware
+│ │ ├── urls.py # Auth URL routes
+│ │ └── migrations/ # Database migrations
+│ │
+│ ├── api/ # API infrastructure
+│ │ ├── __init__.py
+│ │ ├── base.py # Base ViewSets (AccountModelViewSet)
+│ │ ├── authentication.py # JWT, API key auth
+│ │ ├── pagination.py # Unified pagination
+│ │ └── tests/ # API tests
+│ │
+│ ├── ai/ # AI engine
+│ │ ├── __init__.py
+│ │ ├── engine.py # AIEngine orchestrator
+│ │ ├── registry.py # Function registry
+│ │ ├── model_registry.py # AI model configuration
+│ │ ├── progress.py # Progress tracking
+│ │ └── functions/ # AI function implementations
+│ │ ├── __init__.py
+│ │ ├── auto_cluster.py
+│ │ ├── generate_ideas.py
+│ │ ├── generate_content.py
+│ │ └── ...
+│ │
+│ ├── modules/ # Feature modules (API layer)
+│ │ ├── __init__.py
+│ │ ├── planner/ # Keywords, Clusters, Ideas
+│ │ │ ├── models.py
+│ │ │ ├── views.py
+│ │ │ ├── serializers.py
+│ │ │ ├── urls.py
+│ │ │ └── migrations/
+│ │ ├── writer/ # Tasks, Content, Images
+│ │ ├── billing/ # Credits, usage, transactions
+│ │ ├── integration/ # WordPress integration
+│ │ ├── system/ # Settings, prompts, AI config
+│ │ ├── linker/ # Internal linking
+│ │ ├── optimizer/ # Content optimization
+│ │ └── publisher/ # Publishing pipeline
+│ │
+│ ├── business/ # Business logic (services)
+│ │ ├── __init__.py
+│ │ ├── automation/ # 7-stage automation pipeline
+│ │ ├── billing/ # Credit service
+│ │ ├── content/ # Content generation
+│ │ ├── integration/ # Sync services
+│ │ ├── linking/ # Link processing
+│ │ ├── notifications/ # Notification system
+│ │ ├── optimization/ # Content optimization
+│ │ ├── planning/ # Clustering, ideas
+│ │ └── publishing/ # Publishing orchestration
+│ │
+│ ├── middleware/ # Custom middleware
+│ │ ├── __init__.py
+│ │ ├── request_id.py # X-Request-ID header
+│ │ └── resource_tracker.py # Resource tracking
+│ │
+│ ├── tasks/ # Celery background tasks
+│ │ ├── __init__.py
+│ │ └── *.py
+│ │
+│ ├── admin/ # Django admin customization
+│ ├── common/ # Shared utilities
+│ ├── management/ # Django management commands
+│ │ └── commands/
+│ ├── utils/ # Helper functions
+│ ├── urls/ # URL routing modules
+│ ├── static/ # App static files
+│ └── templates/ # Django templates
+│
+├── frontend/
+│ └── src/ # React application
+│ ├── main.tsx # Entry point
+│ ├── App.tsx # Root component, routing
+│ ├── index.css # Global styles, Tailwind
+│ ├── vite-env.d.ts # Vite type definitions
+│ ├── svg.d.ts # SVG type definitions
+│ │
+│ ├── api/ # API client modules
+│ │ ├── linker.api.ts
+│ │ ├── optimizer.api.ts
+│ │ └── ...
+│ │
+│ ├── services/ # Core services
+│ │ ├── api.ts # Main API service
+│ │ └── notifications.api.ts # Notification API
+│ │
+│ ├── store/ # Zustand state stores
+│ │ ├── authStore.ts # Authentication state
+│ │ ├── siteStore.ts # Active site
+│ │ ├── sectorStore.ts # Active sector
+│ │ ├── billingStore.ts # Billing state
+│ │ ├── moduleStore.ts # Module enable/disable
+│ │ ├── notificationStore.ts # Notifications
+│ │ └── ...
+│ │
+│ ├── pages/ # Route pages
+│ │ ├── Dashboard/
+│ │ ├── Planner/
+│ │ ├── Writer/
+│ │ ├── Automation/
+│ │ ├── Linker/
+│ │ ├── Optimizer/
+│ │ ├── Settings/
+│ │ ├── Billing/
+│ │ └── Auth/
+│ │
+│ ├── components/ # Reusable components
+│ │ ├── common/ # Shared UI components
+│ │ ├── dashboard/ # Dashboard widgets
+│ │ ├── header/ # Header components
+│ │ └── shared/ # Cross-feature components
+│ │
+│ ├── layout/ # Layout components
+│ │ ├── AppLayout.tsx
+│ │ ├── AppHeader.tsx
+│ │ └── AppSidebar.tsx
+│ │
+│ ├── hooks/ # Custom React hooks
+│ ├── context/ # React contexts
+│ ├── config/ # App configuration
+│ ├── icons/ # Icon components
+│ ├── styles/ # CSS modules/styles
+│ ├── utils/ # Utility functions
+│ ├── types/ # TypeScript types
+│ ├── templates/ # Content templates
+│ ├── modules/ # Feature modules
+│ ├── marketing/ # Marketing site
+│ │ ├── index.tsx
+│ │ ├── MarketingApp.tsx
+│ │ ├── config/
+│ │ ├── components/
+│ │ ├── layout/
+│ │ ├── pages/
+│ │ ├── images/
+│ │ └── styles/
+│ │
+│ └── __tests__/ # Test files
+│
+├── public/ # Public static assets
+│ ├── favicon.ico
+│ ├── logo.svg
+│ └── images/
+│
+└── docs/ # Documentation
+ ├── 00-SYSTEM/
+ │ └── ARCHITECTURE.md
+ ├── 10-MODULES/
+ ├── 20-API/
+ ├── 30-FRONTEND/
+ ├── 40-WORKFLOWS/
+ ├── 50-DEPLOYMENT/
+ │ ├── TWO-REPO-ARCHITECTURE.md
+ │ ├── INFRASTRUCTURE-STACK.md
+ │ ├── IGNY8-APP-STRUCTURE.md # This file
+ │ ├── DOCKER-DEPLOYMENT.md
+ │ └── ENVIRONMENT-SETUP.md
+ ├── 90-REFERENCE/
+ └── plans/
+```
+
+---
+
+## Deployment to New Server
+
+### Prerequisites
+- Stack already installed (see INFRASTRUCTURE-STACK.md)
+- Server has `/data/stack/igny8-stack/` ready
+- Docker network `igny8_net` exists
+
+### Step-by-Step Deployment
+
+```
+1. Copy app code to server
+
+ Option A: Git clone
+ cd /data/app
+ git clone https://github.com/yourorg/igny8-app.git igny8
+
+ Option B: Rsync from existing server
+ rsync -avz --exclude='.git' \
+ --exclude='node_modules' \
+ --exclude='__pycache__' \
+ --exclude='*.pyc' \
+ --exclude='staticfiles' \
+ --exclude='dist' \
+ old-server:/data/app/igny8/ /data/app/igny8/
+
+2. Create symlinks to stack
+ cd /data/stack/igny8-stack
+ ./scripts/link-app.sh igny8
+
+3. Configure environment
+ cd /data/app/igny8
+ cp .env.example .env
+ nano .env # Set your secrets
+
+4. Install frontend dependencies
+ docker exec igny8_frontend npm install
+
+5. Run database migrations
+ docker exec igny8_backend python manage.py migrate
+
+6. Create superuser (first time only)
+ docker exec -it igny8_backend python manage.py createsuperuser
+
+7. Collect static files
+ docker exec igny8_backend python manage.py collectstatic --noinput
+
+8. Start all services
+ docker compose -f docker-compose.app.yml up -d
+
+9. Verify
+ curl https://api.igny8.com/api/v1/system/status/
+```
+
+---
+
+## What Gets Copied vs Symlinked
+
+### Copied (Your Code)
+
+| Path | Content |
+|------|---------|
+| `backend/igny8_core/` | All Django app code |
+| `frontend/src/` | All React code |
+| `public/` | Static assets |
+| `docs/` | Documentation |
+| `.env` | Environment config |
+
+### Symlinked (From Stack)
+
+| App Path | Stack Path |
+|----------|------------|
+| `backend/Dockerfile` | `stack/backend/Dockerfile` |
+| `backend/requirements.txt` | `stack/backend/requirements.txt` |
+| `backend/manage.py` | `stack/backend/manage.py` |
+| `frontend/Dockerfile.dev` | `stack/frontend/Dockerfile.dev` |
+| `frontend/package.json` | `stack/frontend/package.json` |
+| `frontend/vite.config.ts` | `stack/frontend/vite.config.ts` |
+| `frontend/tsconfig*.json` | `stack/frontend/tsconfig*.json` |
+| `docker-compose.app.yml` | `stack/docker/docker-compose.app.yml` |
+
+---
+
+## Updating App on Existing Server
+
+### Code Update (Most Common)
+
+```
+cd /data/app/igny8
+git pull # Get latest code
+
+# If migrations changed:
+docker exec igny8_backend python manage.py migrate
+
+# If frontend deps changed:
+docker exec igny8_frontend npm install
+
+# Restart to apply:
+docker compose restart
+```
+
+### Full Rebuild (After Major Changes)
+
+```
+cd /data/app/igny8
+docker compose down
+docker compose build --no-cache
+docker compose up -d
+```
+
+---
+
+## Backup Before Migration
+
+### What to Backup
+
+| Item | Command/Method |
+|------|----------------|
+| Database | `pg_dump igny8_db > backup.sql` |
+| App code | `git push` or `rsync` |
+| Uploads | Copy `/data/app/igny8/backend/media/` |
+| Environment | Copy `.env` file |
+
+### Full Backup Script
+
+```
+# On old server
+DATE=$(date +%Y%m%d)
+mkdir -p /data/backups/$DATE
+
+# Database
+docker exec igny8_postgres pg_dump -U igny8 igny8_db > /data/backups/$DATE/db.sql
+
+# App code (excluding generated files)
+tar -czf /data/backups/$DATE/app.tar.gz \
+ --exclude='node_modules' \
+ --exclude='__pycache__' \
+ --exclude='staticfiles' \
+ --exclude='dist' \
+ --exclude='.git' \
+ /data/app/igny8/
+
+# Environment
+cp /data/app/igny8/.env /data/backups/$DATE/
+
+echo "Backup complete: /data/backups/$DATE/"
+```
+
+---
+
+## Related Documentation
+
+- [TWO-REPO-ARCHITECTURE.md](TWO-REPO-ARCHITECTURE.md) - Why this structure
+- [INFRASTRUCTURE-STACK.md](INFRASTRUCTURE-STACK.md) - Stack repo details
+- [DOCKER-DEPLOYMENT.md](DOCKER-DEPLOYMENT.md) - Container details
+- [ARCHITECTURE.md](../00-SYSTEM/ARCHITECTURE.md) - System architecture
diff --git a/docs/50-DEPLOYMENT/final-clean-best-deployment-plan/INFRASTRUCTURE-STACK.md b/docs/50-DEPLOYMENT/final-clean-best-deployment-plan/INFRASTRUCTURE-STACK.md
new file mode 100644
index 00000000..13cd9d96
--- /dev/null
+++ b/docs/50-DEPLOYMENT/final-clean-best-deployment-plan/INFRASTRUCTURE-STACK.md
@@ -0,0 +1,219 @@
+# Infrastructure Stack Repository
+
+**Repository Name:** `igny8-stack`
+**Purpose:** Reusable tech stack for Django + React applications
+
+---
+
+## Complete Folder Structure
+
+```
+igny8-stack/
+│
+├── README.md # Stack overview and quick start
+├── install.sh # Main installation script
+├── uninstall.sh # Cleanup script
+│
+├── config/
+│ ├── .env.example # Environment variables template
+│ │
+│ └── caddy/
+│ └── Caddyfile # Reverse proxy configuration
+│
+├── docker/
+│ ├── docker-compose.infra.yml # Shared services (Postgres, Redis, Caddy)
+│ └── docker-compose.app.yml # App services template
+│
+├── backend/
+│ ├── Dockerfile # Python 3.11 slim + dependencies
+│ ├── requirements.txt # Django, DRF, Celery, Gunicorn, etc.
+│ ├── manage.py # Django CLI entry point
+│ ├── container_startup.sh # Container entrypoint script
+│ └── create_groups.py # Initial permission groups setup
+│
+├── frontend/
+│ ├── Dockerfile.dev # Vite dev server with HMR
+│ ├── Dockerfile.prod # Production build with Caddy
+│ ├── Dockerfile.marketing.dev # Marketing site dev server
+│ ├── package.json # React, Vite, Tailwind, Zustand, etc.
+│ ├── package-lock.json # Locked dependency versions
+│ ├── vite.config.ts # Vite configuration
+│ ├── tsconfig.json # TypeScript base config
+│ ├── tsconfig.app.json # App TypeScript config
+│ ├── tsconfig.node.json # Node TypeScript config
+│ ├── postcss.config.js # PostCSS for Tailwind
+│ ├── eslint.config.js # ESLint rules
+│ ├── index.html # Main app HTML template
+│ └── marketing.html # Marketing site HTML template
+│
+└── scripts/
+ ├── build-images.sh # Build all Docker images
+ ├── migrate.sh # Run Django migrations
+ ├── backup-db.sh # Database backup utility
+ ├── restore-db.sh # Database restore utility
+ └── health-check.sh # System health check
+```
+
+---
+
+## File Purposes
+
+### Root Files
+
+| File | Purpose |
+|------|---------|
+| `install.sh` | Creates directories, symlinks, builds images, sets up network |
+| `uninstall.sh` | Removes symlinks and cleans up (keeps data) |
+
+### Config Folder
+
+| File | Purpose |
+|------|---------|
+| `.env.example` | Template for environment variables (DB passwords, secrets, domains) |
+| `caddy/Caddyfile` | Routes domains to containers, handles SSL, WebSocket proxying |
+
+### Docker Folder
+
+| File | Purpose |
+|------|---------|
+| `docker-compose.infra.yml` | Postgres, Redis, Caddy, PgAdmin, FileBrowser, Portainer |
+| `docker-compose.app.yml` | Backend, Frontend, Celery Worker, Celery Beat, Flower |
+
+### Backend Folder
+
+| File | Purpose |
+|------|---------|
+| `Dockerfile` | Python 3.11 slim, installs requirements, runs Gunicorn |
+| `requirements.txt` | All Python dependencies (Django 5.x, DRF, Celery, etc.) |
+| `manage.py` | Django management command entry point |
+| `container_startup.sh` | Logs startup, runs migrations if needed |
+| `create_groups.py` | Creates initial Django permission groups |
+
+### Frontend Folder
+
+| File | Purpose |
+|------|---------|
+| `Dockerfile.dev` | Node 18, Vite dev server with hot reload |
+| `Dockerfile.prod` | Multi-stage build, Caddy serves static files |
+| `Dockerfile.marketing.dev` | Marketing site dev server (port 5174) |
+| `package.json` | All npm dependencies |
+| `vite.config.ts` | Build config, HMR settings, code splitting |
+| `tsconfig*.json` | TypeScript compiler settings |
+| `postcss.config.js` | Tailwind CSS processing |
+| `eslint.config.js` | Code linting rules |
+| `index.html` | Main app entry HTML |
+| `marketing.html` | Marketing site entry HTML |
+
+### Scripts Folder
+
+| Script | Purpose |
+|--------|---------|
+| `build-images.sh` | Builds all Docker images with proper tags |
+| `migrate.sh` | Runs Django migrations inside container |
+| `backup-db.sh` | Dumps PostgreSQL database to backup file |
+| `restore-db.sh` | Restores database from backup file |
+| `health-check.sh` | Checks all services are running |
+
+---
+
+## New Server Installation
+
+### Prerequisites
+- Ubuntu 22.04+ or Debian 12+
+- Docker and Docker Compose installed
+- Git installed
+- Domain DNS pointing to server IP
+
+### Step-by-Step Installation
+
+```
+1. Create directories
+ mkdir -p /data/stack /data/app /data/logs
+
+2. Clone stack repository
+ cd /data/stack
+ git clone https://github.com/yourorg/igny8-stack.git
+
+3. Run installation script
+ cd igny8-stack
+ chmod +x install.sh
+ ./install.sh
+
+4. Verify installation
+ docker images # Should show app-backend, app-frontend images
+ docker network ls # Should show igny8_net
+ ls -la /data/app/ # Should show prepared structure
+```
+
+### What install.sh Does
+
+1. Creates Docker network (`igny8_net`)
+2. Creates directory structure under `/data/app/`
+3. Builds Docker images:
+ - `app-backend:latest`
+ - `app-frontend-dev:latest`
+ - `app-marketing-dev:latest`
+4. Starts infrastructure services (Postgres, Redis, Caddy)
+5. Creates symlinks for app folder structure
+6. Prints next steps
+
+---
+
+## Updating Stack on Existing Server
+
+```
+1. Pull latest changes
+ cd /data/stack/igny8-stack
+ git pull
+
+2. Rebuild images if Dockerfile changed
+ ./scripts/build-images.sh
+
+3. Restart containers to use new images
+ cd /data/app/igny8
+ docker compose -f docker-compose.app.yml down
+ docker compose -f docker-compose.app.yml up -d
+
+4. Verify
+ docker ps
+ ./scripts/health-check.sh
+```
+
+---
+
+## Customizing for Different Apps
+
+The stack is designed to run any Django + React application. To use it for a different app (not igny8):
+
+1. Clone stack to new server
+2. Run `install.sh`
+3. Clone your app repo to `/data/app/yourapp/`
+4. Update `.env` with your app's settings
+5. Update Caddyfile with your domains
+6. Start containers
+
+The stack doesn't contain any igny8-specific logic - it's a generic Django+React runtime environment.
+
+---
+
+## Environment Variables
+
+Key variables to set in `.env`:
+
+| Variable | Purpose | Example |
+|----------|---------|---------|
+| `DB_HOST` | PostgreSQL host | `postgres` |
+| `DB_NAME` | Database name | `igny8_db` |
+| `DB_USER` | Database user | `igny8` |
+| `DB_PASSWORD` | Database password | `secure_password` |
+| `REDIS_HOST` | Redis host | `redis` |
+| `SECRET_KEY` | Django secret key | `random_50_char_string` |
+| `DOMAIN` | Primary domain | `igny8.com` |
+| `DEBUG` | Debug mode | `False` |
+
+---
+
+## Related Documentation
+
+- [TWO-REPO-ARCHITECTURE.md](TWO-REPO-ARCHITECTURE.md) - Why two repos
+- [IGNY8-APP-STRUCTURE.md](IGNY8-APP-STRUCTURE.md) - App-specific code structure
diff --git a/docs/50-DEPLOYMENT/final-clean-best-deployment-plan/TWO-REPO-ARCHITECTURE.md b/docs/50-DEPLOYMENT/final-clean-best-deployment-plan/TWO-REPO-ARCHITECTURE.md
new file mode 100644
index 00000000..4b839881
--- /dev/null
+++ b/docs/50-DEPLOYMENT/final-clean-best-deployment-plan/TWO-REPO-ARCHITECTURE.md
@@ -0,0 +1,150 @@
+# Two-Repository Architecture
+
+**Purpose:** Separate tech stack infrastructure from custom application code for easy server migration and deployment.
+
+---
+
+## Overview
+
+Instead of one monolithic repository, split into two:
+
+| Repository | Contains | Changes | Deployment |
+|------------|----------|---------|------------|
+| `igny8-stack` | Docker, configs, dependencies | Rarely | Clone once per server |
+| `igny8-app` | Your custom business logic | Frequently | Copy to deploy |
+
+---
+
+## Why This Architecture?
+
+### Current Problem
+- Everything mixed together (Dockerfiles, configs, your code)
+- To deploy to new server, must copy everything
+- Hard to distinguish "what's mine" vs "what's framework"
+
+### Solution Benefits
+- **Clean separation**: Stack vs App code
+- **Easy migration**: Just copy app folder after stack is installed
+- **Version independence**: Update stack without touching app
+- **Reusable stack**: Same stack can run different apps on different servers
+
+---
+
+## How It Works
+
+### Server Structure After Setup
+
+```
+/data/
+├── stack/
+│ └── igny8-stack/ # Cloned from stack repo (read-only)
+│
+├── app/
+│ └── igny8/ # Your app code + symlinks to stack
+│ ├── backend/
+│ │ ├── Dockerfile → symlink to stack
+│ │ ├── requirements.txt → symlink to stack
+│ │ ├── manage.py → symlink to stack
+│ │ └── igny8_core/ # YOUR CODE
+│ │
+│ └── frontend/
+│ ├── package.json → symlink to stack
+│ ├── vite.config.ts → symlink to stack
+│ └── src/ # YOUR CODE
+│
+└── logs/
+```
+
+### Symlinks Explained
+
+The `install.sh` script creates symbolic links from your app folder to the stack folder. This means:
+- Stack files (Dockerfile, package.json, etc.) live in one place
+- Your app folder references them via symlinks
+- Docker sees a complete project structure
+- You only manage your custom code
+
+---
+
+## Migration Workflow
+
+### On New Server
+
+```
+Step 1: Install Stack (one-time)
+────────────────────────────────
+cd /data/stack
+git clone https://your-repo/igny8-stack.git
+cd igny8-stack
+./install.sh
+
+Step 2: Deploy Your App
+────────────────────────────────
+cd /data/app
+git clone https://your-repo/igny8-app.git igny8
+# OR: rsync/scp from old server
+
+Step 3: Configure
+────────────────────────────────
+cp .env.example .env
+nano .env # Set secrets, domains
+
+Step 4: Start
+────────────────────────────────
+docker compose -f docker-compose.app.yml up -d
+```
+
+### Updating Existing Server
+
+**Update app code only:**
+```
+cd /data/app/igny8
+git pull
+docker compose restart
+```
+
+**Update stack (rare):**
+```
+cd /data/stack/igny8-stack
+git pull
+./scripts/build-images.sh
+docker compose -f /data/app/igny8/docker-compose.app.yml up -d
+```
+
+---
+
+## What Goes Where?
+
+### Stack Repo (igny8-stack)
+
+Things that are **same for any Django+React app**:
+- Dockerfiles
+- Base requirements.txt / package.json
+- Vite config, TypeScript config
+- Docker compose templates
+- Caddy config templates
+- Utility scripts
+
+### App Repo (igny8-app)
+
+Things that are **specific to your application**:
+- Django app code (models, views, serializers, business logic)
+- React components, pages, stores
+- App-specific static assets
+- Documentation
+- Environment config templates
+
+---
+
+## Key Principle
+
+> If you deleted your app code and replaced it with a different Django+React app,
+> would this file still make sense?
+> - YES → Stack repo
+> - NO → App repo
+
+---
+
+## Related Documentation
+
+- [INFRASTRUCTURE-STACK.md](INFRASTRUCTURE-STACK.md) - Complete stack repo structure
+- [IGNY8-APP-STRUCTURE.md](IGNY8-APP-STRUCTURE.md) - App repo structure
diff --git a/docs/plans/FINAL-PRELAUNCH.md b/docs/plans/FINAL-PRELAUNCH.md
index 86ce43ae..6221a999 100644
--- a/docs/plans/FINAL-PRELAUNCH.md
+++ b/docs/plans/FINAL-PRELAUNCH.md
@@ -87,6 +87,12 @@ Combined Free Limits:
- Brevo: 9,000/mo marketing
- Total: 12,000 emails/month free
+1. Configure both email services
+2. integrate app with both providers for both types of iamges,
+3. create a mail history log with small mail details, archived eery 30 days (in some compact format) want to keep it to minalml records which can eb acesed later, so nto to cluter the databse,
+4. verification, that all signup adn alerts and notifications mails are beign sent successfully, so that has to be deifend also when emailas are supposed to be triggered and cofnigured, if nto present already in bacekdn /frotnedn
+
+
---
## 9. Pipeline Verification
@@ -132,3 +138,17 @@ Combined Free Limits:
----
+Other items
+
+1. SEtup wizard tobe updated to use page style like other app pages, (currently its converted form modal not a good design at all) nad also removal of unneccsary header and icon from the wizard main apage,
+and it needs mroe clearner adn better intro cards of waht is beign provided through wizrd
+
+2. adding 3 more widgets in home(dsahabrod)
+i) sites and small sites data/status info with buttons to directly land to settings or action
+ii) rmeaining credits uasge , ai runs (jsut liek site dahbarod)
+iii) account realted info like x credits consumed, x remainig, reset day, last payment date, next payment due, plan package tyep these kind of info , trarack waht is the best accoutn realted info to add to this widget
+
+3. many buttons in sytem are configured with icon on top, tits a kind of bug, and some buttons use type attribute while some not, sinc ewe agreed and plplnnaed for gloabl compoeent usage, iw ant to standrdize al lbutton in system, if it si for toogle, or button group or single button with icon button oor without icon, all should suse singe compoenetn for each type item or variant, idelly the one which doesnt need the type=button at the ened of boutton defiention showign in html
+
+4. integrate Stripe & Paypal payment methods
+5. exact same padding defeined in gloabl app layout, so below header and after sidebar, adn on all pages of app there is exact asmae mardign and padding 12px on top bottom, left right, but only defiend in lgoabl layout, and remoing of all inner margind and padding from internal page main wrappers
diff --git a/frontend/src/components/auth/AdminRoute.tsx b/frontend/src/components/auth/AdminRoute.tsx
index 5a293304..489f9c87 100644
--- a/frontend/src/components/auth/AdminRoute.tsx
+++ b/frontend/src/components/auth/AdminRoute.tsx
@@ -8,7 +8,8 @@ interface AdminRouteProps {
/**
* AdminRoute component - guards routes requiring admin or staff privileges
- * Redirects to dashboard if user is not admin/staff
+ * OR users belonging to the aws-admin account
+ * Redirects to dashboard if user doesn't meet requirements
*/
export default function AdminRoute({ children }: AdminRouteProps) {
const { user, isAuthenticated } = useAuthStore();
@@ -19,12 +20,14 @@ export default function AdminRoute({ children }: AdminRouteProps) {
return null;
}
- // Check if user is admin or staff
+ // Check if user is admin/staff OR belongs to aws-admin account
const isAdmin = user?.role === 'admin' || user?.is_staff === true;
+ const isAwsAdminAccount = user?.account?.name === 'aws-admin' || user?.account?.slug === 'aws-admin';
+ const hasAccess = isAdmin || isAwsAdminAccount;
- if (!isAdmin) {
- // Redirect non-admin users to dashboard
- console.log('AdminRoute: User is not admin/staff, redirecting to dashboard');
+ if (!hasAccess) {
+ // Redirect unauthorized users to dashboard
+ console.log('AdminRoute: User does not have admin access, redirecting to dashboard');
return ;
}
diff --git a/frontend/src/components/common/ImageServiceCard.tsx b/frontend/src/components/common/ImageServiceCard.tsx
index 5559b18d..db969525 100644
--- a/frontend/src/components/common/ImageServiceCard.tsx
+++ b/frontend/src/components/common/ImageServiceCard.tsx
@@ -91,9 +91,8 @@ export default function ImageServiceCard({
const model = imageSettings.runwareModel || 'runware:97@1';
// Map model ID to display name
const modelDisplayNames: Record = {
- 'runware:97@1': 'HiDream-I1 Full',
- 'runware:gen3a_turbo': 'Gen3a Turbo',
- 'runware:gen3a': 'Gen3a',
+ 'runware:97@1': 'Hi Dream Full - Standard',
+ 'civitai:618692@691639': 'Bria 3.2 - Premium',
};
const displayName = modelDisplayNames[model] || model;
return `Runware ${displayName}`;
diff --git a/frontend/src/layout/AppSidebar.tsx b/frontend/src/layout/AppSidebar.tsx
index 53a5a4de..758f6b68 100644
--- a/frontend/src/layout/AppSidebar.tsx
+++ b/frontend/src/layout/AppSidebar.tsx
@@ -332,8 +332,13 @@ const AppSidebar: React.FC = () => {
{items
.filter((nav) => {
// Filter out admin-only items for non-admin users
- if (nav.adminOnly && user?.role !== 'admin' && !user?.is_staff) {
- return false;
+ // Allow access for: admin role, staff users, or aws-admin account members
+ if (nav.adminOnly) {
+ const isAdmin = user?.role === 'admin' || user?.is_staff === true;
+ const isAwsAdminAccount = user?.account?.name === 'aws-admin' || user?.account?.slug === 'aws-admin';
+ if (!isAdmin && !isAwsAdminAccount) {
+ return false;
+ }
}
return true;
})
diff --git a/frontend/src/pages/Settings/Integration.tsx b/frontend/src/pages/Settings/Integration.tsx
index 43cb1a3c..dc904769 100644
--- a/frontend/src/pages/Settings/Integration.tsx
+++ b/frontend/src/pages/Settings/Integration.tsx
@@ -552,9 +552,8 @@ export default function Integration() {
});
},
options: [
- { value: 'runware:97@1', label: 'HiDream-I1 Full - $0.009 per image' },
- { value: 'runware:gen3a_turbo', label: 'Gen3a Turbo - $0.009 per image' },
- { value: 'runware:gen3a', label: 'Gen3a - $0.009 per image' },
+ { value: 'runware:97@1', label: 'Hi Dream Full - Standard' },
+ { value: 'civitai:618692@691639', label: 'Bria 3.2 - Premium' },
],
});
}
@@ -922,9 +921,8 @@ export default function Integration() {
? (() => {
// Map model ID to display name
const modelDisplayNames: Record = {
- 'runware:97@1': 'HiDream-I1 Full',
- 'runware:gen3a_turbo': 'Gen3a Turbo',
- 'runware:gen3a': 'Gen3a',
+ 'runware:97@1': 'Hi Dream Full - Standard',
+ 'civitai:618692@691639': 'Bria 3.2 - Premium',
};
return modelDisplayNames[integrations.image_generation.runwareModel] || integrations.image_generation.runwareModel;
})()