# Settings & AI Configuration Consolidation Plan **Created:** January 17, 2026 **Updated:** January 17, 2026 **Status:** FINALIZED - READY FOR IMPLEMENTATION **Priority:** π‘ HIGH - Improve UX & Reduce Confusion **Depends On:** None (standalone improvement) > **π SINGLE PLAN FILE** - This document covers both backend AND frontend implementation. --- ## Executive Summary The IGNY8 codebase has **40+ scattered settings** across **17 different models** with significant redundancy. This plan consolidates them into a clean, hierarchical structure with a unified frontend experience. ### Key Decisions (Finalized) 1. **AI Models:** Simple **Testing** vs **Live** toggle (no quality tiers) 2. **Credit Budget:** Enabled by default, uses all available credits 3. **Per-Run Limits:** Apply to INPUT items (keywords, clusters, etc.) 4. **SiteIntegration Model:** To be REMOVED (redundant with Site.wp_* fields) 5. **Terminology:** Use "Site" not "WordPress" throughout --- ## Part 1: Current State Analysis ### 1.1 Settings Model Inventory | Scope | Model | Location | Purpose | Status | |-------|-------|----------|---------|--------| | **GLOBAL** | `IntegrationProvider` | modules/system/models.py | API keys - ONE record per provider (openai, runware, stripe, paypal, resend) | β CANONICAL (SINGLE) | | | `SystemAISettings` | modules/system/ai_settings.py | AI parameter defaults (temp, tokens, image style) | β Active | | | `GlobalIntegrationSettings` | modules/system/models.py | Legacy API keys + AI defaults | β οΈ **DEPRECATED** | | | `AIModelConfig` | business/billing/models.py | Model definitions + credit pricing | β Active | | | `CreditCostConfig` | business/billing/models.py | Fixed credits per operation | β Active | | | `BillingConfiguration` | business/billing/models.py | Token-credit ratios | β Active | | | `GlobalModuleSettings` | modules/system/models.py | Platform module toggles | β Active | | | `CreditPackage` | business/billing/models.py | Credit purchase bundles | β Active | | | `PaymentMethodConfig` | business/billing/models.py | Payment methods per country | β Active | | **ACCOUNT** | `AccountSettings` | modules/system/settings_models.py | Key-value store - MULTIPLE records per account (ai.temperature, ai.max_tokens, etc.) | β CANONICAL | | | `ModuleEnableSettings` | modules/system/settings_models.py | Account module toggles | β Active | | | `AISettings` | modules/system/settings_models.py | Integration configs per account (overlaps AccountSettings) | β οΈ **TO REMOVE** - use AccountSettings instead | | | `IntegrationSettings` | business/integration/models.py | Account integration overrides | β Active | | | `AccountPaymentMethod` | business/billing/models.py | Saved payment methods | β Active | | **SITE** | `AutomationConfig` | business/automation/models.py | Automation stages, limits | β Active | | | `PublishingSettings` | business/integration/models.py | Publishing schedule | β Active | | | `SiteAIBudgetAllocation` | business/billing/models.py | Credit % per AI function | β Active | | | `SiteIntegration` | business/integration/models.py | Site platform credentials | β οΈ **TO BE REMOVED** - Redundant with Site.wp_* fields | | **USER** | `UserSettings` | modules/system/settings_models.py | User preferences | β Active | ### 1.2 Identified Problems #### Problem 1: API Keys in Multiple Places ``` β GlobalIntegrationSettings.openai_api_key β GlobalIntegrationSettings.anthropic_api_key β GlobalIntegrationSettings.runware_api_key β IntegrationProvider.encrypted_credentials (CANONICAL) ``` **Fix:** Remove API key fields from GlobalIntegrationSettings #### Problem 2: AI Parameters Fragmented ``` SystemAISettings.temperature β Global default GlobalIntegrationSettings.openai_temperature β Duplicate AccountSettings[ai.temperature] β Account override IntegrationSettings.config.temperature β Another override ``` **Fix:** Keep only SystemAISettings + AccountSettings override #### Problem 3: Model Selection Confusion ``` AIModelConfig.is_default = True β Should be canonical GlobalIntegrationSettings.default_text_model β Duplicate GlobalIntegrationSettings.default_image_model β Duplicate ``` **Fix:** Use AIModelConfig.is_default only #### Problem 4: Frontend "Testing vs Live" Model Not Implemented ``` Current: Model names exposed directly (GPT-4.1, Claude 3.5, etc.) Needed: "Testing Model" and "Live Model" abstraction ``` **Fix:** Add `model_tier` field to AIModelConfig #### Problem 5: Redundant AISettings Model ``` AISettings.integration_type + config β Overlaps with: - AccountSettings (ai.* keys) - IntegrationSettings.config ``` **Fix:** Remove AISettings model, consolidate into AccountSettings --- ## Part 2: Target Architecture ### 2.0 Frontend Location: CONFIRMED **Location:** Site Settings > AI & Automation Tab This is the CORRECT location for the unified settings page: - Near existing site configuration - Familiar location for users - Keeps all site-specific settings together - Rename existing "AI Settings" tab to "AI & Automation" **NOT** in Setup menu - Setup is for initial configuration, not ongoing settings. --- ### 2.1 Django Admin Sidebar - Before & After #### BEFORE (Current - From Screenshot) ``` Accounts & Users βΌ βββ Accounts βββ Users βββ Sites βββ Sectors βββ Site Access Plans & Billing βΌ βββ Plans βββ Subscriptions βββ Invoices βββ Payments βββ Credit Packages βββ Payment Methods (Global) βββ Account Payment Methods Credits βΌ βββ Credit Transactions βββ Credit Usage Logs βββ Credit Cost Config β Rarely used βββ Billing Configuration β Rarely used (AI Models scattered elsewhere) ``` #### AFTER (Proposed - Reorganized) ``` Dashboard & Reports βΌ βββ Dashboard βββ Revenue Report βββ Usage Report βββ Content Report βββ AI Cost Analysis β Margin/pricing reports here βββ Token Usage Accounts & Users βΌ βββ Accounts βββ Users βββ Sites βββ Sectors βββ Site Access Plans & Billing βΌ βββ Plans βββ Subscriptions βββ Invoices βββ Payments βββ Credit Packages βββ Payment Methods (Global) βββ Account Payment Methods Credits & AI Usage βΌ β CONSOLIDATED LOGS βββ Credit Transactions βββ Credit Usage Log βββ AI Task Logs β Moved from AI Configuration βββ Plan Limits AI Configuration βΌ β SIMPLIFIED βββ AI Models (Testing/Live) β Main config with COST fields βββ System AI Settings β Default parameters βββ Integration Providers β API keys (OpenAI, Runware, Stripe, etc.) Content Pipeline βΌ β RENAMED (was Planning + Writing) βββ Keywords βββ Clusters βββ Content Ideas βββ Tasks βββ Content βββ Images βββ Image Prompts Publishing βΌ βββ Publishing Records βββ Deployments βββ Sync Events (Removed: Integrations - SiteIntegration removed) Automation βΌ β NEW SECTION βββ Automation Configs βββ Automation Runs Global Settings βΌ β SIMPLIFIED βββ Global AI Prompts βββ Module Settings βββ Author Profiles βββ Strategies Account & User Settings βΌ β CONSOLIDATED βββ Account Settings β All key-value settings βββ User Settings βββ Module Enable Settings Resources βΌ βββ Industries βββ Industry Sectors βββ Seed Keywords Plugin Management βΌ βββ Plugins βββ Plugin Versions βββ Installations βββ Downloads Email Settings βΌ βββ Email Configuration βββ Email Templates βββ Email Logs Logs & Monitoring βΌ βββ System Health βββ API Monitor βββ Debug Console βββ Celery Tasks βββ Admin Log Django Admin βΌ βββ Groups βββ Permissions βββ Content Types βββ Sessions Trash βΌ βββ ... (all trash items) ``` #### Key Reorganization Changes | Before | After | Reason | |--------|-------|--------| | AI Task Logs in "AI Configuration" | Moved to "Credits & AI Usage" | All logs together | | Credit Cost Config, Billing Config | REMOVED | Costs in AIModelConfig | | Planning + Writing (separate) | "Content Pipeline" (combined) | Logical flow | | SiteIntegration in Publishing | REMOVED | Redundant with Site.wp_* | | Global Settings (cluttered) | Simplified + separate Automation | Cleaner | | System Configuration (scattered) | "Account & User Settings" | Consolidated | --- ### 2.2 Model Hierarchy (After Consolidation) ``` GLOBAL (Admin-Only Configuration) βββ IntegrationProvider # API keys for all providers (OpenAI, Anthropic, Runware) βββ AIModelConfig # Model definitions + pricing + is_testing flag βββ SystemAISettings # Default AI parameters (temp, tokens, image style) βββ GlobalModuleSettings # Platform module toggles ACCOUNT (User Account Settings) βββ AccountSettings # Key-value store for all account config βββ ModuleEnableSettings # Which modules user can access βββ AccountPaymentMethod # Saved payment methods SITE (Per-Site Configuration) βββ SiteAIAutomationSettings # NEW: Unified automation+AI+publishing βββ Site.wp_url, Site.wp_api_key # Integration credentials (moved from SiteIntegration) USER βββ UserSettings # User preferences ``` **REMOVED Models:** - `SiteIntegration` β credentials now in `Site` model - `CreditCostConfig` β not actively used - `BillingConfiguration` β hardcode values - `GlobalIntegrationSettings` β replaced by `IntegrationProvider` - `AISettings` (settings_models) β use `AccountSettings` --- ### 2.3 AI Model Designation: Testing vs Live (Simplified) **REMOVED:** Standard, Premium, Quality tiers **NEW:** Simple boolean `is_testing` field | Field Value | Frontend Label | Description | |-------------|---------------|-------------| | `is_testing = True` | **Testing** | Cheap model for testing (100x cheaper, low quality) | | `is_testing = False` | **Live** | Production model | **Constraint:** Only ONE model can be `is_testing=True` per `model_type` (text/image). **Frontend Column Header:** "Testing/Live AI Model" **Dropdown Options:** ``` [Testing βΌ] β Shows only the testing model [Live βΌ] β Shows only the live model ``` --- ### 2.3.1 Costing & Pricing - Single Source of Truth **IMPORTANT:** After removing CreditCostConfig, ALL costing/pricing lives in `AIModelConfig`. #### Cost vs Price Definitions | Term | Definition | Where Stored | |------|------------|--------------| | **Cost** | Our expense per token/image (what we pay to OpenAI, Runware, etc.) | `AIModelConfig.cost_per_1k_input`, `cost_per_1k_output` | | **Price** | What we charge users in credits (revenue calculation) | `AIModelConfig.tokens_per_credit`, `credits_per_image` | | **Margin** | Price - Cost = Revenue margin | Calculated in reports | #### AIModelConfig Fields for Costing ```python class AIModelConfig(models.Model): # βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ # COST (Our expense - what we pay providers) # βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ # Text models: Cost per 1K tokens (USD) cost_per_1k_input = models.DecimalField(...) # e.g., $0.01 for GPT-4.1-mini input cost_per_1k_output = models.DecimalField(...) # e.g., $0.03 for GPT-4.1-mini output # Image models: Cost per image (USD) - stored in metadata or calculated # e.g., DALL-E 3 = $0.04/image, Runware basic = $0.004/image # βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ # PRICE (What we charge users in credits) # βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ # Text models: How many tokens = 1 credit tokens_per_credit = models.IntegerField(...) # e.g., 10000 tokens = 1 credit # Image models: How many credits per image credits_per_image = models.IntegerField(...) # e.g., 1 credit, 5 credits, 15 credits ``` #### Usage in Reports ```python # AI Cost Analysis Report (Backend) def calculate_margin(usage_log): model = usage_log.model_config # Calculate our cost if model.model_type == 'text': cost = (usage_log.input_tokens / 1000 * model.cost_per_1k_input + usage_log.output_tokens / 1000 * model.cost_per_1k_output) else: # image cost = usage_log.images_generated * model.cost_per_image # from metadata # Calculate what user paid (credits Γ credit value) credits_used = usage_log.credits_used credit_value = 0.01 # $0.01 per credit (configurable) revenue = credits_used * credit_value # Margin margin = revenue - cost margin_pct = (margin / revenue * 100) if revenue > 0 else 0 return { 'cost': cost, 'revenue': revenue, 'margin': margin, 'margin_pct': margin_pct, } ``` #### What Gets Removed | Model | Reason | |-------|--------| | `CreditCostConfig` | Redundant - costs now in AIModelConfig per model | | `BillingConfiguration` | Rarely used - can hardcode credit value ($0.01) | --- ### 2.3.2 Settings Hierarchy - Where Defaults & Overrides Live ``` βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ β SETTINGS HIERARCHY β βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ€ β β β GLOBAL DEFAULTS (Admin-only, one record each) β β βββ IntegrationProvider (one per provider: openai, runware, stripe, etc.) β β β βββ API keys, endpoints, sandbox mode β β β β β βββ AIModelConfig (one per model: gpt-4.1, dall-e-3, etc.) β β β βββ Model pricing, costs, capabilities, is_testing flag β β β β β βββ SystemAISettings (SINGLETON - one record total) β β βββ Default temperature, max_tokens, image_style, image_quality β β β β ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ β β β β ACCOUNT OVERRIDES (Multiple key-value records per account) β β βββ AccountSettings (unique_together: account + key) β β βββ ai.temperature = 0.8 # Override SystemAISettings default β β βββ ai.max_tokens = 4000 # Override SystemAISettings default β β βββ ai.image_style = "vivid" # Override SystemAISettings default β β βββ ... other account-specific settings β β β β ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ β β β β SITE CONFIGURATION (One record per site) β β βββ SiteAIAutomationSettings (NEW unified model) β β βββ Automation: enabled, frequency, time β β βββ Stages: JSON config per stage (batch_size, per_run_limit, etc.) β β βββ Publishing: days, time_slots β β β β ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ β β β β Resolution Order: Site β Account β Global Default β β β βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ ``` --- ### 2.3.3 AISettings Model - REMOVAL PLAN **Current State:** `AISettings` in `settings_models.py` stores per-account integration configs. **Problem:** Overlaps with `AccountSettings` which already has `ai.*` keys. **Migration:** 1. Any data in `AISettings.config` β Move to `AccountSettings` keys 2. `AISettings.model_preferences` β `AccountSettings['ai.model_preferences']` 3. `AISettings.cost_limits` β Not needed (credit budget in SiteAIAutomationSettings) 4. Remove `AISettings` model and admin **After Removal:** - Account AI settings use `AccountSettings` with keys like: - `ai.temperature` - `ai.max_tokens` - `ai.image_style` - `ai.image_quality` - `ai.model_preferences` --- ### 2.4 SiteIntegration Model - REMOVAL PLAN **Status:** CONFIRMED REDUNDANT - Safe to remove **Analysis:** | What SiteIntegration Stores | Where It Actually Lives | |----------------------------|------------------------| | `credentials_json.api_key` | `Site.wp_api_key` (single source of truth) | | `config_json.url` | `Site.wp_url` (single source of truth) | | `platform` | Always 'wordpress' (hardcoded) | | `sync_status`, `last_sync_at` | Can move to `Site` model | **Evidence from codebase:** ```python # All services use Site model directly: api_key = site.wp_api_key # NOT SiteIntegration.credentials_json url = site.wp_url # NOT SiteIntegration.config_json ``` **Removal Steps:** 1. Add `sync_status`, `last_sync_at` fields to `Site` model 2. Update `SyncEvent.integration` FK β `SyncEvent.site` FK 3. Update `sync_health_service.py` to use Site directly 4. Remove `SiteIntegration` admin registration 5. Remove `SiteIntegration` model 6. Create migration to drop table --- ### 2.5 New Unified Model: SiteAIAutomationSettings **Replaces:** AutomationConfig + PublishingSettings + SiteAIBudgetAllocation fields ```python class SiteAIAutomationSettings(AccountBaseModel): """ Unified AI & Automation settings for a site. Consolidates AutomationConfig, PublishingSettings, and budget allocation. """ site = models.OneToOneField('Site', on_delete=models.CASCADE) # βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ # AUTOMATION SCHEDULING # βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ automation_enabled = models.BooleanField(default=False) automation_frequency = models.CharField(max_length=20, default='daily', choices=[('hourly', 'Hourly'), ('daily', 'Daily'), ('weekly', 'Weekly')]) automation_time = models.TimeField(default='02:00') # βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ # STAGE CONFIGURATION (JSON for flexibility) # βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ # Structure: { # "1": {"enabled": true, "batch_size": 50, "per_run_limit": 0, "use_testing": false, "budget_pct": 15}, # "2": {"enabled": true, "batch_size": 1, "per_run_limit": 10, "use_testing": false, "budget_pct": 10}, # ... # } # NOTE: per_run_limit applies to INPUT items: # Stage 1: max keywords to cluster # Stage 2: max clusters to generate ideas from # Stage 4: max tasks to generate content for # etc. stage_config = models.JSONField(default=dict) # βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ # DELAYS # βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ within_stage_delay = models.IntegerField(default=3, help_text="Seconds between items") between_stage_delay = models.IntegerField(default=5, help_text="Seconds between stages") # βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ # PUBLISHING SCHEDULE (Simplified - Time Slots only) # βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ auto_approval_enabled = models.BooleanField(default=True) auto_publish_enabled = models.BooleanField(default=True) # Schedule: Days + Time Slots (NO stagger, NO limits) publish_days = models.JSONField(default=list) # ['mon', 'tue', 'wed', 'thu', 'fri'] publish_time_slots = models.JSONField(default=list) # ['09:00', '14:00', '18:00'] # REMOVED: scheduling_mode, daily/weekly/monthly limits, stagger_*, queue_limit class Meta: db_table = 'igny8_site_ai_automation_settings' ``` --- ### 2.6 Publishing Schedule - SIMPLIFIED (Corrected) **What PublishingSettings Controls:** SCHEDULING (when content gets published), NOT publishing itself. **REMOVED Features (Unnecessary Complexity):** - β Staggered mode - Not needed - β Daily/weekly/monthly limits - Derived from time slots Γ days - β Queue limit - No artificial limit needed **SIMPLIFIED Model:** ```python class PublishingSettings(AccountBaseModel): """Site-level publishing SCHEDULE configuration.""" site = models.OneToOneField('Site', on_delete=models.CASCADE) # Auto-approval/publish toggles auto_approval_enabled = models.BooleanField(default=True) auto_publish_enabled = models.BooleanField(default=True) # Schedule Configuration (Time Slots mode ONLY) publish_days = models.JSONField(default=list) # ['mon', 'tue', 'wed', 'thu', 'fri'] publish_time_slots = models.JSONField(default=list) # ['09:00', '14:00', '18:00'] # REMOVED: # - scheduling_mode (only time_slots needed) # - daily_publish_limit (derived: len(time_slots)) # - weekly_publish_limit (derived: len(time_slots) Γ len(publish_days)) # - monthly_publish_limit (not needed) # - stagger_* fields (not needed) # - queue_limit (not needed) ``` **How It Works:** ``` βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ β PUBLISHING SCHEDULE = Days Γ Time Slots β βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ€ β β β Selected Days: [β] Mon [β] Tue [β] Wed [β] Thu [β] Fri [ ] Sat [ ] Sun β β βββ 5 days selected β β β β Time Slots: [09:00] [14:00] [18:00] β β βββ 3 slots per day β β β β ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ β β β β Calculated Capacity: β β β’ Daily: 3 articles (one per time slot) β β β’ Weekly: 15 articles (3 slots Γ 5 days) β β β’ Monthly: ~65 articles (15 Γ 4.3 weeks) β β β β ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ β β β β Content Flow: β β [Approved] β [Scheduled for next available slot] β [Published] β β β β Example (Wednesday 10:30 AM): β β Content approved β Scheduled for Wed 14:00 (next available slot) β β β βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ ``` **No Queue Limit Needed:** - Content is scheduled to the next available time slot - If all slots are full for today, schedule for tomorrow - Natural flow, no artificial limits --- ## Part 3: Backend Changes ### 3.1 Add is_testing Flag to AIModelConfig ```python # In business/billing/models.py - AIModelConfig class AIModelConfig(models.Model): # ... existing fields ... # REMOVE these fields: # - quality_tier (was: basic/quality/premium for images) # - model_tier (proposed but not needed) # ADD: Simple testing flag is_testing = models.BooleanField( default=False, db_index=True, help_text="Testing model (cheap, for testing only). One per model_type." ) class Meta: constraints = [ # Only one testing model per type (text/image) models.UniqueConstraint( fields=['model_type'], condition=models.Q(is_testing=True, is_active=True), name='unique_testing_model_per_type' ) ] @classmethod def get_testing_model(cls, model_type: str): """Get the testing model for text or image""" return cls.objects.filter( model_type=model_type, is_testing=True, is_active=True ).first() @classmethod def get_live_model(cls, model_type: str): """Get the live (default production) model for text or image""" return cls.objects.filter( model_type=model_type, is_testing=False, is_default=True, is_active=True ).first() # NEW: Only one model per (model_type, model_tier) can be active @classmethod def get_model_for_tier(cls, model_type: str, tier: str): """Get the active model for a given type and tier""" return cls.objects.filter( model_type=model_type, model_tier=tier, is_active=True ).first() ``` ### 3.2 Unified Settings API Endpoint ```python # New file: backend/igny8_core/api/unified_settings.py class UnifiedSiteSettingsViewSet(viewsets.ViewSet): """ Unified API for all site AI & automation settings. GET /api/v1/sites/{site_id}/unified-settings/ PUT /api/v1/sites/{site_id}/unified-settings/ """ def retrieve(self, request, site_id): """Get all settings for a site in one response""" site = get_object_or_404(Site, id=site_id, account=request.user.account) # Get or create the unified settings settings, _ = SiteAIAutomationSettings.objects.get_or_create( site=site, defaults=self._get_defaults() ) # Get available models (Testing vs Live) text_models = { 'testing': AIModelConfig.get_testing_model('text'), 'live': AIModelConfig.get_live_model('text'), } image_models = { 'testing': AIModelConfig.get_testing_model('image'), 'live': AIModelConfig.get_live_model('image'), } return Response({ 'automation': { 'enabled': settings.automation_enabled, 'frequency': settings.automation_frequency, 'time': settings.automation_time.strftime('%H:%M'), }, 'stages': self._build_stage_matrix(settings), 'delays': { 'within_stage': settings.within_stage_delay, 'between_stage': settings.between_stage_delay, }, 'publishing': { 'auto_approval_enabled': settings.auto_approval_enabled, 'auto_publish_enabled': settings.auto_publish_enabled, 'publish_days': settings.publish_days, 'time_slots': settings.publish_time_slots, # Calculated capacity (read-only, derived from days Γ slots) 'daily_capacity': len(settings.publish_time_slots), 'weekly_capacity': len(settings.publish_time_slots) * len(settings.publish_days), }, 'available_models': { 'text': { 'testing': text_models['testing'].display_name if text_models['testing'] else None, 'live': text_models['live'].display_name if text_models['live'] else None, }, 'image': { 'testing': image_models['testing'].display_name if image_models['testing'] else None, 'live': image_models['live'].display_name if image_models['live'] else None, }, }, }) def _build_stage_matrix(self, settings): """Build stage configuration matrix""" stage_config = settings.stage_config or {} stages = [ {'number': 1, 'name': 'Keywords β Clusters', 'has_ai': True}, {'number': 2, 'name': 'Clusters β Ideas', 'has_ai': True}, {'number': 3, 'name': 'Ideas β Tasks', 'has_ai': False}, {'number': 4, 'name': 'Tasks β Content', 'has_ai': True}, {'number': 5, 'name': 'Content β Prompts', 'has_ai': True}, {'number': 6, 'name': 'Prompts β Images', 'has_ai': True}, {'number': 7, 'name': 'Review β Approved', 'has_ai': False}, ] result = [] for stage in stages: num = str(stage['number']) config = stage_config.get(num, {}) result.append({ 'number': stage['number'], 'name': stage['name'], 'has_ai': stage['has_ai'], 'enabled': config.get('enabled', True), 'batch_size': config.get('batch_size', self._default_batch_size(stage['number'])), 'per_run_limit': config.get('per_run_limit', 0), 'model_tier': config.get('model_tier', 'standard') if stage['has_ai'] else None, 'credit_percentage': config.get('credit_percentage', 20) if stage['has_ai'] else None, }) return result ``` ### 3.3 Remove Deprecated Models/Fields **Phase 1 (This Release):** - Add deprecation warnings to `GlobalIntegrationSettings` API key fields - Add `model_tier` to `AIModelConfig` - Create `SiteAIAutomationSettings` model **Phase 2 (Next Release):** - Remove API key fields from `GlobalIntegrationSettings` - Remove `AISettings` model (settings_models.py) - Migrate data from old models to new unified model **Phase 3 (v2.0):** - Remove `GlobalIntegrationSettings` model entirely - Remove separate `AutomationConfig`, `PublishingSettings` models - Full migration to unified settings --- ## Part 4: Frontend Changes ### 4.1 Unified Settings Page - Full UI Mockup **Location:** `frontend/src/pages/Sites/AIAutomationSettings.tsx` **Access:** Site Settings > AI & Automation tab ``` βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ β AI & Automation Settings [Save Changes] β βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ€ β β β ββββ Quick Settings βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ β β β β β β β Automation [β] Enable Scheduled Runs Frequency: [Daily βΌ] Time: [02:00] β β β β Publishing [β] Auto-Approve Content [β] Auto-Publish to Site β β β β β β β ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ β β β β ββββ Stage Configuration Matrix βββββββββββββββββββββββββββββββββββββββββββββββββββββββββ β β β β β β β ββββββββββββββββββββββββββ¬βββββββββ¬ββββββββββββ¬ββββββββββββ¬βββββββββββββββ¬ββββββββββ β β β β β Stage β Enable β Batch Sizeβ Per-Run β Testing/Live β Budget β β β β β β β β β Limit* β AI Model β % β β β β β ββββββββββββββββββββββββββΌβββββββββΌββββββββββββΌββββββββββββΌβββββββββββββββΌββββββββββ€ β β β β β 1. KeywordsβClusters β [β] β [50] β [0] β Live βΌ β [15] β β β β β ββββββββββββββββββββββββββΌβββββββββΌββββββββββββΌββββββββββββΌβββββββββββββββΌββββββββββ€ β β β β β 2. ClustersβIdeas β [β] β [1] β [10] β Live βΌ β [10] β β β β β ββββββββββββββββββββββββββΌβββββββββΌββββββββββββΌββββββββββββΌβββββββββββββββΌββββββββββ€ β β β β β 3. IdeasβTasks β [β] β [20] β [0] β - β - β β β β β β (local operation) β β β β β β β β β β ββββββββββββββββββββββββββΌβββββββββΌββββββββββββΌββββββββββββΌβββββββββββββββΌββββββββββ€ β β β β β 4. TasksβContent β [β] β [1] β [5] β Live βΌ β [40] β β β β β ββββββββββββββββββββββββββΌβββββββββΌββββββββββββΌββββββββββββΌβββββββββββββββΌββββββββββ€ β β β β β 5. ContentβPrompts β [β] β [1] β [5] β Live βΌ β [5] β β β β β ββββββββββββββββββββββββββΌβββββββββΌββββββββββββΌββββββββββββΌβββββββββββββββΌββββββββββ€ β β β β β 6. PromptsβImages β [β] β [1] β [20] β Testing βΌ β [30] β β β β β ββββββββββββββββββββββββββΌβββββββββΌββββββββββββΌββββββββββββΌβββββββββββββββΌββββββββββ€ β β β β β 7. ReviewβApproved β [β] β - β [10] β - β - β β β β β β (local operation) β β β β β β β β β β ββββββββββββββββββββββββββ΄βββββββββ΄ββββββββββββ΄ββββββββββββ΄βββββββββββββββ΄ββββββββββ β β β β β β β β * Per-Run Limit: Max INPUT items to process per run (0 = all pending) β β β β Stage 1: max keywords to cluster β β β β Stage 2: max clusters to generate ideas from β β β β Stage 4: max tasks to generate content for β β β β β β β β Budget %: Credit allocation per AI stage (must total β€100%) Total: 100% β β β β β β β β β Delays: Between stages [5] seconds Within stage [3] seconds β β β β β β β ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ β β β β ββββ Publishing Schedule (Simplified) βββββββββββββββββββββββββββββββββββββββββββββββββββ β β β β β β β [β] Auto-Approve Content [β] Auto-Publish to Site β β β β β β β β Publish Days: [β] Mon [β] Tue [β] Wed [β] Thu [β] Fri [ ] Sat [ ] Sun β β β β β β β β Time Slots: [09:00] [14:00] [18:00] [+ Add Slot] β β β β β β β β ββ Calculated Capacity βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ β β β β β π Daily: 3 articles Weekly: 15 articles Monthly: ~65 articles β β β β β ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ β β β β β β β β βΉοΈ Content is scheduled to the next available time slot automatically β β β β β β β ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ β β β β [Reset to Defaults] [Save Changes] β βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ ``` ### 4.1.1 Stage Configuration Matrix - Column Reference | Column | Description | Input Type | Applicable Stages | |--------|-------------|------------|-------------------| | **Enable** | Turn stage on/off | Toggle | All (1-7) | | **Batch Size** | Items per batch | Number input | 1, 2, 3, 4, 5, 6 | | **Per-Run Limit** | Max INPUT items per run (0=all) | Number input | All (1-7) | | **Testing/Live AI Model** | Which model to use | Dropdown | 1, 2, 4, 5, 6 (AI stages) | | **Budget %** | Credit allocation | Number (0-100) | 1, 2, 4, 5, 6 (AI stages) | Stages 3 and 7 show "-" for AI-related columns (they're local operations, no AI calls). ### 4.2 Testing/Live AI Model Dropdown Component ```tsx // components/AIModelSelect.tsx interface AIModelSelectProps { modelType: 'text' | 'image'; useTesting: boolean; onChange: (useTesting: boolean) => void; disabled?: boolean; } export function AIModelSelect({ modelType, useTesting, onChange, disabled }: AIModelSelectProps) { return ( onChange(v === 'testing')} disabled={disabled} > β‘ Testing β Live ); } // Tooltip for Testing mode β‘ Testing 100x cheaper. For testing only. Not suitable for production content. ``` ### 4.3 Updated Sidebar Navigation ```tsx // In AppSidebar.tsx - Updated menu structure const menuSections: MenuSection[] = useMemo(() => { return [ // Dashboard standalone { label: "", items: [{ icon: , name: "Dashboard", path: "/" }], }, // SETUP - Site configuration { label: "SETUP", items: [ { icon: , name: "Setup Wizard", path: "/setup/wizard" }, { icon: , name: "Sites", path: "/sites" }, { icon: , name: "Keyword Library", path: "/setup/add-keywords" }, // Thinker (admin only) isModuleEnabled('thinker') && { icon: , name: "Thinker", subItems: [ { name: "Prompts", path: "/thinker/prompts" }, { name: "Author Profiles", path: "/thinker/author-profiles" }, ], adminOnly: true, }, ].filter(Boolean), }, // WORKFLOW - Content pipeline { label: "WORKFLOW", items: [ isModuleEnabled('planner') && { icon: , name: "Planner", subItems: [ { name: "Keywords", path: "/planner/keywords" }, { name: "Clusters", path: "/planner/clusters" }, { name: "Ideas", path: "/planner/ideas" }, ], }, isModuleEnabled('writer') && { icon: , name: "Writer", subItems: [ { name: "Content Queue", path: "/writer/tasks" }, { name: "Content Drafts", path: "/writer/content" }, { name: "Content Images", path: "/writer/images" }, ], }, { icon: , name: "Publisher", subItems: [ { name: "Content Review", path: "/writer/review" }, { name: "Publish / Schedule", path: "/writer/approved" }, { name: "Content Calendar", path: "/publisher/content-calendar" }, ], }, isModuleEnabled('automation') && { icon: , name: "Automation", subItems: [ { name: "Overview", path: "/automation/overview" }, { name: "Run Now", path: "/automation/run" }, ], }, ].filter(Boolean), }, // ACCOUNT - User account management { label: "ACCOUNT", items: [ { icon: , name: "Account Settings", path: "/account/settings" }, { icon: , name: "Plans & Billing", path: "/account/plans" }, { icon: , name: "Usage", path: "/account/usage" }, { icon: , name: "AI Configuration", path: "/settings/ai-models", adminOnly: true, }, ], }, // HELP { label: "HELP", items: [ { icon: , name: "Notifications", path: "/account/notifications" }, { icon: , name: "Help & Docs", path: "/help" }, ], }, ]; }, [isModuleEnabled]); ``` ### 4.4 Sidebar Dropdown Stay-Open Fix ```tsx // In AppSidebar.tsx - Fix dropdown to stay open when inner page is active useEffect(() => { const currentPath = location.pathname; let foundMatch = false; allSections.forEach((section, sectionIndex) => { section.items.forEach((nav, itemIndex) => { if (nav.subItems && !foundMatch) { // Check if any subitem matches current path const shouldOpen = nav.subItems.some((subItem) => { // Exact match if (currentPath === subItem.path) return true; // Prefix match for nested routes (e.g., /sites/123/settings matches /sites) if (currentPath.startsWith(subItem.path + '/')) return true; return false; }); if (shouldOpen) { setOpenSubmenu({ sectionIndex, itemIndex }); foundMatch = true; } } }); }); // IMPORTANT: Don't close if no match - user may have manually opened }, [location.pathname, allSections]); ``` --- ## Part 5: Django Admin Error Prevention ### 5.1 Safe Admin Actions Add try/except wrappers to all admin actions to prevent 500 errors: ```python # In admin/base.py - Enhanced base admin class class Igny8ModelAdmin(ModelAdmin): """Base admin class with error handling""" def save_model(self, request, obj, form, change): """Safe save with error handling""" try: super().save_model(request, obj, form, change) except IntegrityError as e: messages.error(request, f"Database error: {str(e)}") except ValidationError as e: messages.error(request, f"Validation error: {str(e)}") except Exception as e: messages.error(request, f"Unexpected error: {str(e)}") logger.exception(f"Error saving {obj.__class__.__name__}") def delete_model(self, request, obj): """Safe delete with error handling""" try: super().delete_model(request, obj) except ProtectedError as e: messages.error(request, f"Cannot delete: {str(e)}") except Exception as e: messages.error(request, f"Delete error: {str(e)}") logger.exception(f"Error deleting {obj.__class__.__name__}") def delete_queryset(self, request, queryset): """Safe bulk delete""" try: super().delete_queryset(request, queryset) except ProtectedError as e: messages.error(request, f"Cannot delete some items: {str(e)}") except Exception as e: messages.error(request, f"Bulk delete error: {str(e)}") ``` ### 5.2 Admin Error Logging ```python # Add to all admin classes that process data class CreditTransactionAdmin(Igny8ModelAdmin): @admin.action(description='Process selected transactions') def process_transactions(self, request, queryset): success_count = 0 error_count = 0 for obj in queryset: try: obj.process() success_count += 1 except Exception as e: error_count += 1 logger.error(f"Failed to process transaction {obj.id}: {e}") if success_count: messages.success(request, f'{success_count} transaction(s) processed.') if error_count: messages.warning(request, f'{error_count} transaction(s) failed. Check logs.') ``` --- ## Part 6: Migration Plan ### Phase 1: Preparation (Week 1) 1. Add `model_tier` field to `AIModelConfig` - Migration 2. Create `SiteAIAutomationSettings` model - Migration 3. Add deprecation warnings to `GlobalIntegrationSettings` 4. Create unified API endpoint (read-only initially) ### Phase 2: Data Migration (Week 2) 1. Migrate existing `AutomationConfig` β `SiteAIAutomationSettings` 2. Migrate existing `PublishingSettings` β `SiteAIAutomationSettings` 3. Migrate existing `SiteAIBudgetAllocation` β `SiteAIAutomationSettings.budget_allocation` 4. Update automation service to use new model ### Phase 3: Frontend Update (Week 3) 1. Create unified settings component 2. Update sidebar navigation 3. Update Site Settings to use new unified tab 4. Implement model tier dropdowns ### Phase 4: Cleanup (Week 4) 1. Remove old separate settings pages 2. Mark old API endpoints as deprecated 3. Remove `AISettings` model 4. Update documentation ### Phase 5: Final Migration (v2.0) 1. Remove `GlobalIntegrationSettings` model 2. Remove separate `AutomationConfig`, `PublishingSettings` models 3. Remove deprecated API endpoints --- ## Part 7: Testing Checklist ### Backend Tests - [ ] `SiteAIAutomationSettings` CRUD works correctly - [ ] Unified API returns complete settings - [ ] Unified API saves all settings atomically - [ ] `is_testing` flag works on AIModelConfig - [ ] Migration scripts run without errors - [ ] Old models still work during transition - [ ] SiteIntegration removal doesn't break SyncEvent ### Frontend Tests - [ ] Site Settings > AI & Automation tab loads correctly - [ ] Stage matrix displays all 7 stages - [ ] Testing/Live AI Model dropdown works - [ ] Budget % validation (must total β€100%) - [ ] Save button saves all settings atomically - [ ] Sidebar stays open on inner pages - [ ] No 500 errors in admin actions ### Integration Tests - [ ] Automation runs use new unified settings - [ ] Publishing uses new settings - [ ] Credit budget (stage %) works correctly - [ ] Testing/Live model selection works in AI calls --- ## Part 8: Files to Create/Modify ### Backend Files | File | Action | Purpose | |------|--------|---------| | `business/billing/models.py` | MODIFY | Add `is_testing` field to AIModelConfig | | `business/automation/models.py` | CREATE | Add `SiteAIAutomationSettings` model | | `api/unified_settings.py` | CREATE | New unified API endpoint | | `urls/api_v1.py` | MODIFY | Register new endpoint | | `business/integration/models.py` | MODIFY | Remove SiteIntegration (Phase 2) | | `admin/billing.py` | MODIFY | Update Django Admin grouping | | `migrations/XXXX_*.py` | CREATE | Database migrations | ### Frontend Files | File | Action | Purpose | |------|--------|---------| | `pages/Sites/AIAutomationSettings.tsx` | CREATE | Main unified settings page | | `components/Settings/StageMatrix.tsx` | CREATE | Reusable stage matrix component | | `components/Settings/AIModelSelect.tsx` | CREATE | Testing/Live dropdown component | | `services/unifiedSettingsService.ts` | CREATE | API service for unified endpoint | | `pages/Sites/SiteSettings.tsx` | MODIFY | Add AI & Automation tab | | `layout/AppSidebar.tsx` | MODIFY | Keep dropdowns open on inner pages | ### Files to Remove (Phase 2) | File | Reason | |------|--------| | Separate automation settings pages | Replaced by unified page | | Old settings API endpoints | Deprecated, use unified | --- ## Part 9: Quick Setup Presets (Future Enhancement) ``` ββββ Quick Setup Presets ββββββββββββββββββββββββββββββββββββββββββ β β β [ ] Conservative - Low throughput, high quality β β 5 tasks/run, 3 images/run, 30min stagger β β β β [β] Balanced - Moderate throughput (Recommended) β β 10 tasks/run, 20 images/run, 15min stagger β β β β [ ] Aggressive - Maximum throughput β β Unlimited, 5min stagger β β β β [ ] Custom - Configure manually β β β ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ ``` --- ## Summary **Before:** 17 models, 40+ settings, 4+ pages to configure one workflow **After:** ~10 models, unified page, matrix view, clear hierarchy | Metric | Before | After | |--------|--------|-------| | Settings Models | 17 | ~10 | | Frontend Pages for Config | 4+ | 1 | | API Calls to Load | 5+ | 1 | | API Calls to Save | 5+ | 1 | | AI Model Options | GPT-4.1, Claude, etc. (confusing) | Testing / Live (simple) | | Credit Budget Config | Manual max + allocation | Auto (all credits) + stage % | | User Confusion | High | Low | ### Key Simplifications 1. **AI Models:** Just "Testing" vs "Live" - no quality tiers 2. **Credit Budget:** Always enabled, uses all available credits 3. **Publishing:** Only 3 modes + queue limit (removed daily/weekly targets) 4. **Per-Run Limit:** Clear - applies to INPUT items per stage 5. **SiteIntegration:** REMOVED - credentials in Site model
100x cheaper. For testing only.
Not suitable for production content.