diff --git a/backend/igny8_core/ai/ai_core.py b/backend/igny8_core/ai/ai_core.py index c81c9990..96ecacc3 100644 --- a/backend/igny8_core/ai/ai_core.py +++ b/backend/igny8_core/ai/ai_core.py @@ -1026,11 +1026,19 @@ class AICore: del inference_task['height'] inference_task['resolution'] = '1k' # Use 1K tier for optimal speed/quality print(f"[AI][{function_name}] Using Nano Banana config: resolution=1k (no width/height)") - else: + elif runware_model.startswith('bytedance:'): + # Seedream 4.5 (bytedance:seedream@4.5) - High quality ByteDance model + # Uses basic format with just width/height - no steps, CFGScale, or special providerSettings needed + # Just use the base inference_task as-is + print(f"[AI][{function_name}] Using Seedream 4.5 config: basic format, width={width}, height={height}") + elif runware_model.startswith('runware:'): # Hi Dream Full (runware:97@1) - General diffusion, steps 20, CFGScale 7 inference_task['steps'] = 20 inference_task['CFGScale'] = 7 print(f"[AI][{function_name}] Using Hi Dream Full config: steps=20, CFGScale=7") + else: + # Unknown model - use basic format without extra parameters + print(f"[AI][{function_name}] Using basic format for unknown model: {runware_model}") payload = [ { diff --git a/backend/igny8_core/ai/functions/generate_images.py b/backend/igny8_core/ai/functions/generate_images.py index 514bec83..e5c67776 100644 --- a/backend/igny8_core/ai/functions/generate_images.py +++ b/backend/igny8_core/ai/functions/generate_images.py @@ -70,22 +70,37 @@ class GenerateImagesFunction(BaseAIFunction): # Get image generation settings from AISettings (with account overrides) from igny8_core.modules.system.ai_settings import AISettings from igny8_core.ai.model_registry import ModelRegistry + from igny8_core.business.billing.models import AIModelConfig # Get effective settings (AISettings + AccountSettings overrides) image_style = AISettings.get_effective_image_style(account) max_images = AISettings.get_effective_max_images(account) + quality_tier = AISettings.get_effective_quality_tier(account) - # Get default image model and provider from database - default_model = ModelRegistry.get_default_model('image') - if default_model: - model_config = ModelRegistry.get_model(default_model) - provider = model_config.provider if model_config else 'openai' - model = default_model + # Get image model based on user's selected quality tier + selected_model = None + if quality_tier: + selected_model = AIModelConfig.objects.filter( + model_type='image', + quality_tier=quality_tier, + is_active=True + ).first() + + # Fall back to default model if no tier match + if not selected_model: + default_model_name = ModelRegistry.get_default_model('image') + if default_model_name: + selected_model = ModelRegistry.get_model(default_model_name) + + # Set provider and model from selected model + if selected_model: + provider = selected_model.provider if selected_model.provider else 'openai' + model = selected_model.model_name else: provider = 'openai' model = 'dall-e-3' - logger.info(f"Using image settings: provider={provider}, model={model}, style={image_style}, max={max_images}") + logger.info(f"Using image settings: provider={provider}, model={model}, tier={quality_tier}, style={image_style}, max={max_images}") return { 'tasks': tasks, diff --git a/backend/igny8_core/business/billing/models.py b/backend/igny8_core/business/billing/models.py index 94b3c453..d6544860 100644 --- a/backend/igny8_core/business/billing/models.py +++ b/backend/igny8_core/business/billing/models.py @@ -736,6 +736,7 @@ class AIModelConfig(models.Model): QUALITY_TIER_CHOICES = [ ('basic', 'Basic'), ('quality', 'Quality'), + ('quality_option2', 'Quality-Option2'), ('premium', 'Premium'), ] diff --git a/backend/igny8_core/modules/billing/migrations/0030_add_aimodel_image_sizes.py b/backend/igny8_core/modules/billing/migrations/0030_add_aimodel_image_sizes.py index d3ff03a6..6515b762 100644 --- a/backend/igny8_core/modules/billing/migrations/0030_add_aimodel_image_sizes.py +++ b/backend/igny8_core/modules/billing/migrations/0030_add_aimodel_image_sizes.py @@ -10,6 +10,42 @@ class Migration(migrations.Migration): ] operations = [ - # Fields already added via direct SQL, just mark as noop - # This ensures the model matches the database schema + # Declare the fields in Django schema so migrations can use them + # These fields already exist in DB, so state_operations syncs Django's understanding + migrations.SeparateDatabaseAndState( + state_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'])", + ), + ), + ], + database_operations=[ + # No DB operations - fields already exist via direct SQL + ], + ), ] diff --git a/backend/igny8_core/modules/billing/migrations/0031_add_seedream_model.py b/backend/igny8_core/modules/billing/migrations/0031_add_seedream_model.py new file mode 100644 index 00000000..a65f913b --- /dev/null +++ b/backend/igny8_core/modules/billing/migrations/0031_add_seedream_model.py @@ -0,0 +1,80 @@ +# Generated manually - Add Seedream 4.5 image model and update quality_tier choices +from decimal import Decimal +from django.db import migrations, models + + +def add_seedream_model(apps, schema_editor): + """ + Add ByteDance Seedream 4.5 image model via Runware. + + Model specs: + - model: bytedance:seedream@4.5 + - Square size: 2048x2048 + - Landscape size: 2304x1728 + - Quality tier: quality_option2 + - Credits per image: 5 + """ + AIModelConfig = apps.get_model('billing', 'AIModelConfig') + + AIModelConfig.objects.update_or_create( + model_name='bytedance:seedream@4.5', + defaults={ + 'display_name': 'Seedream 4.5 - High Quality', + 'model_type': 'image', + 'provider': 'runware', + 'is_default': False, + 'is_active': True, + 'credits_per_image': 5, + 'quality_tier': 'quality_option2', + 'landscape_size': '2304x1728', + 'square_size': '2048x2048', + 'valid_sizes': ['2048x2048', '2304x1728', '2560x1440', '1728x2304', '1440x2560'], + 'capabilities': { + 'max_sequential_images': 4, + 'high_resolution': True, + 'provider_settings': { + 'bytedance': { + 'maxSequentialImages': 4 + } + } + }, + } + ) + print("✅ Added Seedream 4.5 image model") + + +def reverse_migration(apps, schema_editor): + """Remove Seedream model""" + AIModelConfig = apps.get_model('billing', 'AIModelConfig') + AIModelConfig.objects.filter(model_name='bytedance:seedream@4.5').delete() + print("❌ Removed Seedream 4.5 image model") + + +class Migration(migrations.Migration): + """Add Seedream 4.5 image model and update quality_tier choices.""" + + dependencies = [ + ('billing', '0030_add_aimodel_image_sizes'), + ] + + operations = [ + # Update quality_tier field choices to include quality_option2 + migrations.AlterField( + model_name='aimodelconfig', + name='quality_tier', + field=models.CharField( + blank=True, + choices=[ + ('basic', 'Basic'), + ('quality', 'Quality'), + ('quality_option2', 'Quality-Option2'), + ('premium', 'Premium'), + ], + help_text='basic / quality / quality_option2 / premium - for image models', + max_length=20, + null=True, + ), + ), + # Add the Seedream model + migrations.RunPython(add_seedream_model, reverse_migration), + ] diff --git a/backend/igny8_core/modules/system/ai_settings.py b/backend/igny8_core/modules/system/ai_settings.py index 2483aa3b..625a5b43 100644 --- a/backend/igny8_core/modules/system/ai_settings.py +++ b/backend/igny8_core/modules/system/ai_settings.py @@ -50,6 +50,7 @@ class SystemAISettings(models.Model): QUALITY_TIER_CHOICES = [ ('basic', 'Basic'), ('quality', 'Quality'), + ('quality_option2', 'Quality-Option2'), ('premium', 'Premium'), ] @@ -191,6 +192,30 @@ class SystemAISettings(models.Model): return str(override) return cls.get_instance().image_size + @classmethod + def get_effective_quality_tier(cls, account=None) -> str: + """Get quality_tier, checking account override first (from ai_settings key)""" + if account: + # Check consolidated ai_settings first + try: + from igny8_core.modules.system.settings_models import AccountSettings + setting = AccountSettings.objects.filter( + account=account, + key='ai_settings' + ).first() + if setting and setting.value: + tier = setting.value.get('quality_tier') + if tier: + return str(tier) + except Exception as e: + logger.debug(f"Could not get quality_tier from ai_settings: {e}") + + # Fall back to individual key + override = cls._get_account_override(account, 'ai.quality_tier') + if override is not None: + return str(override) + return cls.get_instance().default_quality_tier + @staticmethod def _get_account_override(account, key: str): """Get account-specific override from AccountSettings""" diff --git a/backend/igny8_core/modules/system/settings_views.py b/backend/igny8_core/modules/system/settings_views.py index 101b9df8..e9635d4a 100644 --- a/backend/igny8_core/modules/system/settings_views.py +++ b/backend/igny8_core/modules/system/settings_views.py @@ -594,10 +594,12 @@ class ContentGenerationSettingsViewSet(viewsets.ViewSet): tier = model.quality_tier or 'basic' # Avoid duplicates if not any(t['tier'] == tier for t in quality_tiers): + # Format label: quality_option2 -> "Quality-Option2" + tier_label = tier.replace('_', '-').title() if tier else 'Basic' quality_tiers.append({ 'tier': tier, 'credits': model.credits_per_image or 1, - 'label': tier.title(), + 'label': tier_label, 'description': f"{model.display_name} quality", 'model': model.model_name, }) diff --git a/backend/igny8_core/utils/ai_processor.py b/backend/igny8_core/utils/ai_processor.py index d7e17ce6..5cd41eef 100644 --- a/backend/igny8_core/utils/ai_processor.py +++ b/backend/igny8_core/utils/ai_processor.py @@ -845,24 +845,46 @@ Make sure each prompt is detailed enough for image generation, describing the vi # Reference: image-generation.php lines 79-97 import uuid logger.info(f"[AIProcessor.generate_image] Runware API key check: has_key={bool(api_key)}, key_length={len(api_key) if api_key else 0}, key_preview={api_key[:10] + '...' + api_key[-4:] if api_key and len(api_key) > 14 else 'N/A'}") + + # Build base inference task + inference_task = { + 'taskType': 'imageInference', + 'taskUUID': str(uuid.uuid4()), + 'positivePrompt': prompt, + 'negativePrompt': negative_prompt, + 'model': runware_model, + 'width': width, + 'height': height, + 'numberResults': 1, + 'outputFormat': kwargs.get('format', 'webp') + } + + # Model-specific parameter configuration + if runware_model.startswith('bria:'): + # Bria models need steps + inference_task['steps'] = 20 + elif runware_model.startswith('google:'): + # Google models use resolution instead of width/height + del inference_task['width'] + del inference_task['height'] + inference_task['resolution'] = '1k' + elif runware_model.startswith('bytedance:'): + # Seedream models use basic format - no steps, CFGScale needed + pass + elif runware_model.startswith('runware:'): + # Hi Dream Full - needs steps and CFGScale + inference_task['steps'] = 30 + inference_task['CFGScale'] = 7.5 + else: + # Unknown model - use basic format + pass + payload = [ { 'taskType': 'authentication', 'apiKey': api_key }, - { - 'taskType': 'imageInference', - 'taskUUID': str(uuid.uuid4()), - 'positivePrompt': prompt, - 'negativePrompt': negative_prompt, - 'model': runware_model, - 'width': width, - 'height': height, - 'steps': 30, - 'CFGScale': 7.5, - 'numberResults': 1, - 'outputFormat': kwargs.get('format', 'webp') - } + inference_task ] logger.info(f"[AIProcessor.generate_image] Runware request payload: model={runware_model}, width={width}, height={height}, prompt_length={len(prompt)}") diff --git a/docs/plans/COMPREHENSIVE-SYSTEM-FIX-PLAN-JAN-10-2026.md b/docs/plans/COMPREHENSIVE-SYSTEM-FIX-PLAN-JAN-10-2026.md new file mode 100644 index 00000000..629cca4a --- /dev/null +++ b/docs/plans/COMPREHENSIVE-SYSTEM-FIX-PLAN-JAN-10-2026.md @@ -0,0 +1,1016 @@ +# COMPREHENSIVE SYSTEM FIX PLAN +**Date:** January 10, 2026 +**Priority:** CRITICAL +**Status:** Analysis Complete - Ready for Implementation + +--- + +## EXECUTIVE SUMMARY + +After comprehensive system analysis, I've identified **7 critical issues** with clear root causes and detailed fixes. These issues fall into **3 categories**: + +1. **Backend Data Model Inconsistencies** (2 issues) +2. **Missing Credit Tracking & Logging** (1 major issue) +3. **Frontend Issues** (4 issues) + +**Impact:** These fixes will ensure: +- ✅ All AI functions log consistently to AI tasks, notifications, and usage logs +- ✅ Image generation properly deducts and logs credits with cost calculations +- ✅ No attribute errors in AI model configuration +- ✅ Consistent data display across all pages +- ✅ Improved UX with proper button styling and working features + +--- + +## ISSUE 1: AIModelConfig AttributeError - `input_cost_per_1m` + +### 🔴 CRITICAL - System Breaking + +**Error Message:** +``` +Failed to cluster keywords: Unexpected error: 'AIModelConfig' object has no attribute 'input_cost_per_1m' +``` + +**Root Cause:** +The `AIModelConfig` model uses field names `cost_per_1k_input` and `cost_per_1k_output`, but `model_registry.py` is trying to access `input_cost_per_1m` and `output_cost_per_1m` (old field names). + +**Location:** +- File: `/backend/igny8_core/ai/model_registry.py` line 121 +- File: `/backend/igny8_core/modules/billing/serializers.py` line 290 + +**Current Code (WRONG):** +```python +# model_registry.py line 121 +if rate_type == 'input': + return model.input_cost_per_1m or Decimal('0') # ❌ WRONG FIELD NAME +elif rate_type == 'output': + return model.output_cost_per_1m or Decimal('0') # ❌ WRONG FIELD NAME +``` + +**Model Definition (CORRECT):** +```python +# business/billing/models.py line 785-797 +cost_per_1k_input = models.DecimalField(...) # ✅ ACTUAL FIELD NAME +cost_per_1k_output = models.DecimalField(...) # ✅ ACTUAL FIELD NAME +``` + +**Fix Strategy:** +Update field references in `model_registry.py` and `serializers.py` to match actual model field names. + +**Files to Change:** +1. `backend/igny8_core/ai/model_registry.py` (1 fix) +2. `backend/igny8_core/modules/billing/serializers.py` (1 fix) + +**Impact:** +- Fixes: Clustering errors, all AI function cost calculations +- Affects: All AI operations that use ModelRegistry for cost calculation + +--- + +## ISSUE 2: Image Generation - Missing Credit Tracking & Logging + +### 🔴 CRITICAL - Business Logic Gap + +**Problem:** +Image generation does NOT: +- ❌ Log to AI tasks table (AITaskLog) +- ❌ Log to notifications +- ❌ Log to usage logs with cost calculations +- ❌ Deduct credits properly based on model configuration + +All other AI functions (clustering, content generation, idea generation) properly log to all 3 locations, but image generation is missing. + +**Root Cause Analysis:** + +**Current Image Generation Flow:** +``` +generate_images() + → ai_core.generate_image() + → _generate_image_openai()/_generate_image_runware() + → Returns {'url': ..., 'cost': ...} + → ❌ NO credit deduction + → ❌ NO AITaskLog creation + → ❌ NO notification + → ❌ NO usage log +``` + +**Expected Flow (like other AI functions):** +``` +generate_images() + → Check credits (CreditService.check_credits) + → ai_core.generate_image() + → Returns result + → Deduct credits (CreditService.deduct_credits_for_image) + → Create AITaskLog + → Create notification + → Create usage log with cost +``` + +**What Exists (Ready to Use):** +- ✅ `CreditService.calculate_credits_for_image()` - calculates credits from model config +- ✅ `CreditService.deduct_credits_for_image()` - deducts credits and creates logs +- ✅ `AIModelConfig.credits_per_image` - configured for all image models +- ✅ Notification templates for image generation + +**What's Missing:** +- ❌ Integration of credit tracking into image generation flow +- ❌ AITaskLog creation for image generation +- ❌ Notification creation for image generation +- ❌ Usage log creation with cost calculation + +**Fix Strategy:** + +### Phase 1: Integrate Credit Tracking into Image Generation + +**Step 1.1: Update `generate_images_core()` function** + +File: `backend/igny8_core/ai/functions/generate_images.py` + +Current logic (lines 203-278): +```python +def generate_images_core(task_ids, account_id, progress_callback): + # ... gets tasks ... + # ... generates images ... + # ❌ NO credit tracking + return {'success': True, 'images_created': count} +``` + +**NEW Implementation:** +```python +def generate_images_core(task_ids, account_id, progress_callback): + """Core image generation with full credit tracking""" + from igny8_core.business.billing.services.credit_service import CreditService + from igny8_core.business.notifications.services import NotificationService + from igny8_core.ai.models import AITaskLog + + # Get account + account = Account.objects.get(id=account_id) + + # Validate + fn = GenerateImagesFunction() + validated = fn.validate({'ids': task_ids}, account) + if not validated['valid']: + return {'success': False, 'error': validated['error']} + + # Prepare + data = fn.prepare({'ids': task_ids}, account) + tasks = data['tasks'] + model = data['model'] # e.g., 'dall-e-3' + + # Get model config for credits + from igny8_core.business.billing.models import AIModelConfig + model_config = AIModelConfig.objects.get(model_name=model, is_active=True) + + # Calculate total images to generate + total_images = 0 + for task in tasks: + if task.content: + total_images += 1 # Featured image + total_images += data.get('max_in_article_images', 0) # In-article images + + # Calculate total credits needed + total_credits = model_config.credits_per_image * total_images + + # CHECK CREDITS FIRST (before any generation) + if account.credits < total_credits: + error_msg = f"Insufficient credits. Required: {total_credits}, Available: {account.credits}" + # Create failed notification + NotificationService.create_notification( + account=account, + notification_type='ai_image_failed', + message=error_msg, + related_object_type='task', + related_object_id=tasks[0].id if tasks else None + ) + return {'success': False, 'error': error_msg} + + # Create AITaskLog for tracking + task_log = AITaskLog.objects.create( + account=account, + function_name='generate_images', + phase='INIT', + status='pending', + payload={'task_ids': task_ids, 'model': model} + ) + + ai_core = AICore(account=account) + images_created = 0 + total_cost_usd = 0.0 + + try: + # Process each task + for task in tasks: + if not task.content: + continue + + # Extract prompts + prompts_data = fn.build_prompt({'task': task, **data}, account) + + # Generate featured image + featured_result = ai_core.generate_image( + prompt=formatted_featured_prompt, + provider=data['provider'], + model=model, + function_name='generate_images' + ) + + if featured_result.get('url'): + # Save image + fn.save_output( + {'url': featured_result['url'], 'image_type': 'featured'}, + {'task': task, **data}, + account + ) + images_created += 1 + total_cost_usd += float(featured_result.get('cost', 0)) + + # Generate in-article images (if configured) + # ... similar logic ... + + # DEDUCT CREDITS (with usage log and cost) + from igny8_core.business.billing.services.credit_service import CreditService + from igny8_core.business.billing.models import BillingConfiguration + + # Calculate actual credits used (based on images generated) + credits_used = images_created * model_config.credits_per_image + + # Calculate cost per credit for usage log + billing_config = BillingConfiguration.get_instance() + cost_per_credit = billing_config.default_credit_price_usd + total_cost_for_log = float(credits_used) * float(cost_per_credit) + + # Deduct credits (creates CreditTransaction, CreditUsageLog) + CreditService.deduct_credits_for_image( + account=account, + model_name=model, + num_images=images_created, + description=f"Generated {images_created} images for {len(tasks)} tasks", + metadata={ + 'task_ids': task_ids, + 'images_created': images_created, + 'model': model + }, + cost_usd=total_cost_usd, # Actual AI provider cost + related_object_type='task', + related_object_id=tasks[0].id if tasks else None + ) + + # Update AITaskLog + task_log.status = 'success' + task_log.phase = 'DONE' + task_log.cost = total_cost_usd + task_log.result = { + 'images_created': images_created, + 'credits_used': credits_used, + 'tasks_processed': len(tasks) + } + task_log.save() + + # Create success notification + NotificationService.create_notification( + account=account, + notification_type='ai_image_success', + message=f'Generated {images_created} images using {credits_used} credits', + metadata={ + 'images_created': images_created, + 'credits_used': credits_used, + 'tasks_processed': len(tasks) + }, + related_object_type='task', + related_object_id=tasks[0].id if tasks else None + ) + + return { + 'success': True, + 'images_created': images_created, + 'credits_used': credits_used, + 'cost_usd': total_cost_usd, + 'message': f'Generated {images_created} images' + } + + except Exception as e: + # Update task log with error + task_log.status = 'error' + task_log.error = str(e) + task_log.save() + + # Create failed notification + NotificationService.create_notification( + account=account, + notification_type='ai_image_failed', + message=f'Image generation failed: {str(e)}', + error=str(e), + related_object_type='task', + related_object_id=tasks[0].id if tasks else None + ) + + return {'success': False, 'error': str(e)} +``` + +**Step 1.2: Ensure Notification Types Exist** + +File: `backend/igny8_core/business/notifications/services.py` + +Check if these notification types are defined: +- `ai_image_success` +- `ai_image_failed` + +If not, add them to the notification type choices. + +### Phase 2: Test All Image Generation Paths + +**Test Cases:** +1. ✅ Manual image generation via Writer module +2. ✅ Automation image generation +3. ✅ Bulk image generation +4. ✅ Insufficient credits handling +5. ✅ AI provider errors handling + +**Validation Checks:** +- [ ] AITaskLog created for each image generation run +- [ ] Credits deducted correctly based on model config +- [ ] CreditUsageLog created with correct operation_type='image_generation' +- [ ] Cost calculated correctly (provider cost + credit cost) +- [ ] Notifications created for success/failure +- [ ] Frontend credits counter updates in real-time + +--- + +## ISSUE 3: Pause/Cancel Button Colors in Automation + +### 🟡 MEDIUM - UX Issue + +**Problem:** +Pause/Cancel buttons in automation in-progress panel need better button colors for clarity. + +**Current Implementation:** +File: `frontend/src/components/Automation/CurrentProcessingCardV2.tsx` lines 268-294 + +```tsx +{currentRun.status === 'running' ? ( + +) : currentRun.status === 'paused' ? ( + +)} + +``` + +**Recommended Fix:** + +```tsx +{currentRun.status === 'running' ? ( + +) : currentRun.status === 'paused' ? ( + +)} + +``` + +**Rationale:** +- Pause: Solid warning (yellow) button - more visible, important action +- Resume: Already solid success (green) - GOOD +- Cancel: Solid danger (red) button - critical destructive action needs prominence + +**Files to Change:** +1. `frontend/src/components/Automation/CurrentProcessingCardV2.tsx` +2. `frontend/src/components/Automation/CurrentProcessingCard.tsx` (if still used) + +--- + +## ISSUE 4: Credits Not Updating in Automation In-Progress Panel + +### 🔴 CRITICAL - Real-time UX Issue + +**Problem:** +When images are being generated one by one in automation, the credits count doesn't update in the in-progress panel. + +**Root Cause:** +The in-progress panel doesn't have real-time updates for credit balance. It only updates when the page refreshes or when the run status is polled. + +**Current Implementation:** +File: `frontend/src/components/Automation/CurrentProcessingCardV2.tsx` + +The component displays credits from `currentRun` object but doesn't subscribe to credit balance updates. + +**Fix Strategy:** + +### Option 1: Poll Credit Balance (Simpler) + +Add credit balance polling to the automation progress polling: + +```tsx +// In CurrentProcessingCardV2.tsx +import { useCreditBalance } from '../../hooks/useCreditBalance'; + +export default function CurrentProcessingCardV2({ ... }) { + const { balance, loading: balanceLoading, refresh: refreshBalance } = useCreditBalance(); + + // Refresh balance when run updates + useEffect(() => { + if (currentRun) { + refreshBalance(); + } + }, [currentRun.credits_used, currentRun.credits_remaining]); + + // Display live balance + return ( +
+ {/* ... existing UI ... */} +
+ Credits: {balance?.credits || 0} +
+
+ ); +} +``` + +### Option 2: WebSocket Updates (Better - Future) + +Implement WebSocket for real-time credit updates: +- Backend: Send credit update events via WebSocket +- Frontend: Subscribe to credit updates in credit balance context + +**Recommended: Option 1 for now** (simpler, works immediately) + +**Files to Change:** +1. `frontend/src/components/Automation/CurrentProcessingCardV2.tsx` +2. `frontend/src/hooks/useCreditBalance.ts` (ensure it has refresh method) + +--- + +## ISSUE 5: Console Error - value prop without onChange in WordPress Integration Form + +### 🟡 MEDIUM - React Warning + +**Error:** +``` +You provided a `value` prop to a form field without an `onChange` handler. +This will render a read-only field. If the field should be mutable use `defaultValue`. +Otherwise, set either `onChange` or `readOnly`. +``` + +**Location:** +`frontend/src/components/sites/WordPressIntegrationForm.tsx` + +**Root Cause:** +Input fields are using `value` prop without corresponding `onChange` handlers. + +**Fix:** +Find all `` without `onChange` and either: +1. Add `onChange` handler, or +2. Change to `defaultValue` if read-only, or +3. Add `readOnly` prop + +**Example Fix:** +```tsx +// BEFORE (WRONG) + + +// AFTER (OPTION 1 - if editable) + setApiKey(e.target.value)} /> + +// AFTER (OPTION 2 - if read-only) + + +// AFTER (OPTION 3 - if should use initial value only) + +``` + +**Files to Change:** +1. `frontend/src/components/sites/WordPressIntegrationForm.tsx` + +--- + +## ISSUE 6: WorkflowCompletionWidget - Inconsistent Data Across Pages + +### 🔴 CRITICAL - Data Integrity Issue + +**Problem:** +The WorkflowCompletionWidget shows different counts on different pages, even though it's the same widget using the same data source. + +**Root Cause Analysis:** + +**Current Implementation:** +- Widget uses `useWorkflowStats()` hook +- Hook fetches data with site_id and optional sector_id filters +- **BUG:** Different pages may have different active sector, causing different counts + +**File:** `frontend/src/hooks/useWorkflowStats.ts` + +```typescript +const { activeSite } = useSiteStore(); +const { activeSector } = useSectorStore(); // ❌ PROBLEM: sector changes per page + +// Fetch with sector filter +const sectorParam = activeSector?.id ? `§or_id=${activeSector.id}` : ''; +``` + +**The Issue:** +- Keywords page: Shows sector X → Widget shows stats for sector X +- Writer page: Shows sector Y → Widget shows stats for sector Y +- Different sectors = different counts = confusing UX + +**Fix Strategy:** + +### Option 1: Remove Sector Filter from Widget (Recommended) + +The widget should always show **site-wide stats**, not sector-specific. + +```typescript +// In useWorkflowStats.ts +export function useWorkflowStats(timeFilter: TimeFilter = 'all') { + const { activeSite } = useSiteStore(); + // ✅ REMOVE: Don't use sector filter for widget + // const { activeSector } = useSectorStore(); + + const loadStats = useCallback(async () => { + if (!activeSite?.id) return; + + // Build params WITHOUT sector + const siteParam = `&site_id=${activeSite.id}`; + // ✅ REMOVED: const sectorParam = activeSector?.id ? `§or_id=${activeSector.id}` : ''; + const baseParams = siteParam; // No sector filter + + // ... rest of logic ... + }, [activeSite?.id]); // ✅ Remove activeSector from dependencies +} +``` + +**Rationale:** +- Widget is in the footer = global context +- Should show site-wide completion, not sector-specific +- Keeps counts consistent across all pages + +### Option 2: Add Toggle for Site-wide vs Sector Stats + +Add a toggle in the widget to switch between site-wide and sector-specific stats. More complex, may not be needed. + +**Recommended: Option 1** + +**Files to Change:** +1. `frontend/src/hooks/useWorkflowStats.ts` + +**Testing:** +- [ ] Navigate between different pages +- [ ] Verify widget shows same counts on all pages +- [ ] Verify counts match actual site-wide totals + +--- + +## ISSUE 7: Published Items Calendar Disappeared + +### 🔴 CRITICAL - Feature Broken + +**Problem:** +The published items calendar view that was showing earlier has disappeared. Both calendar and list views are not working. + +**Investigation Needed:** + +**File:** `frontend/src/pages/Publisher/ContentCalendar.tsx` + +**Current Status:** +- Component exists and is implemented +- Has calendar and list view modes +- Default view mode is 'calendar' +- Uses `viewMode` state to switch between views + +**Possible Issues:** +1. Route not working +2. Component not rendering due to data fetch error +3. CSS/visibility issue +4. Auth/permission issue + +**Debug Steps:** +```typescript +// In ContentCalendar.tsx, add logging +useEffect(() => { + console.log('[DEBUG] ContentCalendar mounted'); + console.log('[DEBUG] activeSite:', activeSite); + console.log('[DEBUG] viewMode:', viewMode); + console.log('[DEBUG] allContent:', allContent); +}, []); +``` + +**Fix will depend on findings:** +- If data fetch error → Fix API call +- If route issue → Check App.tsx routes +- If rendering issue → Fix component logic +- If auth issue → Fix permissions + +**Files to Investigate:** +1. `frontend/src/pages/Publisher/ContentCalendar.tsx` +2. `frontend/src/App.tsx` (check route) +3. Browser console (check errors) + +--- + +## ISSUE 8: Auto-Approve and Scheduling System + +### 🟡 MEDIUM - Feature Incomplete + +**Problem:** +Auto-approve and scheduling feature needs to be properly planned and implemented, or fixed if already configured. + +**Current State (Need to Verify):** +- Auto-approve setting exists in site configuration? +- Scheduling feature exists for content? +- Integration with automation? + +**Investigation Needed:** + +1. **Check if feature exists:** + ```bash + grep -r "auto.approve" backend/ + grep -r "auto_approve" backend/ + ``` + +2. **Check scheduling:** + ```bash + grep -r "scheduled_publish" backend/ + ``` + +3. **Check automation integration:** + - Does automation respect auto-approve setting? + - Does it schedule content automatically? + +**Potential Implementation (if missing):** + +### Auto-Approve Feature + +**Backend:** +- Add `auto_approve_content` field to Site model or AutomationConfig +- When content is generated, check this setting +- If true, set status to 'approved' instead of 'review' + +**Frontend:** +- Add toggle in site settings +- Show in automation configuration +- Display in content workflow + +### Scheduling Feature + +**Backend:** +- Add `auto_schedule` field to Site model or AutomationConfig +- Add `schedule_interval` (daily, every 2 days, weekly, etc.) +- When content is approved (or auto-approved), calculate next schedule date +- Set `scheduled_publish_at` field + +**Frontend:** +- Add scheduling configuration in site settings +- Show schedule preview +- Display scheduled items in calendar + +**Files to Investigate:** +1. `backend/igny8_core/business/automation/models.py` (AutomationConfig) +2. `backend/igny8_core/modules/integration/models.py` (Site model) +3. `frontend/src/pages/Sites/Settings.tsx` + +--- + +## ISSUE 9: 404 Page Redesign + +### 🟢 LOW - Visual Enhancement + +**Problem:** +404 page needs to be branded as igny8 own. + +**Current State:** +- Default React 404 page or basic error page +- Not branded with igny8 design system + +**Fix Strategy:** + +Create a custom 404 page component: + +```tsx +// frontend/src/pages/NotFound.tsx +import React from 'react'; +import { Link } from 'react-router-dom'; +import Button from '../components/ui/button/Button'; +import { HomeIcon, ArrowLeftIcon } from '../icons'; + +export default function NotFound() { + return ( +
+
+ {/* Logo */} +
+ igny8 +
+ + {/* 404 */} +

+ 404 +

+ + {/* Message */} +

+ Page Not Found +

+

+ The page you're looking for doesn't exist or has been moved. +

+ + {/* Actions */} +
+ + + + +
+ + {/* Help text */} +

+ Need help? Contact Support +

+
+
+ ); +} +``` + +**Integration:** +```tsx +// In App.tsx +} /> +``` + +**Files to Create/Change:** +1. `frontend/src/pages/NotFound.tsx` (new file) +2. `frontend/src/App.tsx` (add route) + +--- + +## IMPLEMENTATION PRIORITY & ORDER + +### Phase 1: Critical Backend Fixes (MUST FIX FIRST) +**Estimated Time: 2-3 hours** + +1. ✅ **Issue 1: AIModelConfig AttributeError** (30 min) + - Fix field name references + - Test all AI functions + +2. ✅ **Issue 2: Image Generation Credit Tracking** (2 hours) + - Integrate credit service + - Add AITaskLog creation + - Add notification creation + - Add usage log with cost + - Test thoroughly + +### Phase 2: Critical Frontend Fixes +**Estimated Time: 2-3 hours** + +3. ✅ **Issue 6: WorkflowCompletionWidget Data Consistency** (30 min) + - Remove sector filter from widget + - Test across all pages + +4. ✅ **Issue 4: Credits Not Updating in Automation** (1 hour) + - Add credit balance polling + - Test real-time updates + +5. ✅ **Issue 7: Published Items Calendar** (1 hour) + - Debug and identify issue + - Implement fix + - Test both views + +### Phase 3: UX Improvements +**Estimated Time: 1-2 hours** + +6. ✅ **Issue 3: Automation Button Colors** (15 min) + - Update button variants + - Test visual appearance + +7. ✅ **Issue 5: Console Error - WordPress Form** (30 min) + - Fix input onChange handlers + - Test form + +8. ✅ **Issue 9: 404 Page Redesign** (30 min) + - Create branded 404 page + - Test routing + +### Phase 4: Feature Implementation (If Time Permits) +**Estimated Time: 3-4 hours** + +9. ✅ **Issue 8: Auto-Approve & Scheduling** (3-4 hours) + - Investigate current state + - Plan implementation + - Implement if missing + - Test workflow + +--- + +## TESTING CHECKLIST + +### After Each Fix +- [ ] Run backend server without errors +- [ ] Test the specific feature fixed +- [ ] Check browser console for errors +- [ ] Verify no regression in related features + +### After All Fixes +- [ ] **AI Functions Test Suite** + - [ ] Clustering: Credits deducted, logged to all 3 locations + - [ ] Idea Generation: Credits deducted, logged to all 3 locations + - [ ] Content Generation: Credits deducted, logged to all 3 locations + - [ ] Image Generation: Credits deducted, logged to all 3 locations ✨ NEW + +- [ ] **Credit System Verification** + - [ ] Check AITaskLog table has entries for all AI functions + - [ ] Check Notifications table has entries for all AI functions + - [ ] Check CreditUsageLog has entries for all AI functions with costs + - [ ] Verify cost calculations match formula: credits × credit_price_usd + +- [ ] **Frontend Verification** + - [ ] Navigate all pages, verify widget shows same counts + - [ ] Run automation, verify credits update in real-time + - [ ] Test pause/cancel buttons, verify clear visual feedback + - [ ] Check no console errors on any page + - [ ] Test 404 page routing + +- [ ] **Integration Test** + - [ ] Run full automation cycle + - [ ] Verify all stages work + - [ ] Verify all credits deducted correctly + - [ ] Verify all logs created properly + +--- + +## DATABASE VERIFICATION QUERIES + +After implementing fixes, run these SQL queries to verify: + +```sql +-- 1. Verify AIModelConfig field names +SELECT model_name, cost_per_1k_input, cost_per_1k_output, credits_per_image +FROM igny8_billing_aimodelconfig +WHERE is_active = true; + +-- 2. Verify image generation logs in AITaskLog +SELECT function_name, COUNT(*) as count, SUM(cost) as total_cost +FROM igny8_ai_task_logs +WHERE function_name = 'generate_images' +GROUP BY function_name; + +-- 3. Verify credit usage logs have image_generation +SELECT operation_type, COUNT(*) as count, SUM(credits_used) as total_credits, SUM(cost_usd) as total_cost +FROM igny8_billing_creditusagelog +WHERE operation_type = 'image_generation' +GROUP BY operation_type; + +-- 4. Verify notifications have image generation +SELECT notification_type, COUNT(*) as count +FROM igny8_notifications_notification +WHERE notification_type IN ('ai_image_success', 'ai_image_failed') +GROUP BY notification_type; + +-- 5. Compare credit deductions for all operations +SELECT operation_type, COUNT(*) as transactions, SUM(amount) as total_credits +FROM igny8_billing_credittransaction +WHERE transaction_type = 'deduction' +GROUP BY operation_type +ORDER BY total_credits DESC; +``` + +--- + +## SUCCESS CRITERIA + +✅ **Fix is successful when:** + +1. **No attribute errors** in AI functions +2. **All AI functions** log to AITaskLog, Notifications, and CreditUsageLog +3. **Image generation** properly deducts credits based on model config +4. **Cost calculations** appear in usage logs for all operations +5. **Widget shows consistent data** across all pages +6. **Credits update in real-time** during automation +7. **Button colors** provide clear visual feedback +8. **No console errors** on any page +9. **404 page** is branded and functional +10. **Auto-approve/scheduling** works as configured (TBD after investigation) + +--- + +## ROLLBACK PLAN + +If issues occur during implementation: + +1. **Database Changes:** None expected (only code changes) +2. **Code Rollback:** `git revert ` for each fix +3. **Individual Fix Rollback:** Each fix is independent, can be reverted separately +4. **Testing Database:** Use development environment first, verify thoroughly before production + +--- + +## MONITORING POST-DEPLOYMENT + +After deployment, monitor: + +1. **Error Logs:** Check for AttributeError or other exceptions +2. **Credit Balance:** Monitor for incorrect deductions +3. **AITaskLog Table:** Verify entries being created +4. **Notification Table:** Verify notifications being created +5. **User Reports:** Check for any user-reported issues +6. **Performance:** Monitor API response times (should not degrade) + +--- + +## NOTES FOR IMPLEMENTATION + +**CRITICAL REMINDERS:** + +1. **Test EVERY change** before moving to next fix +2. **Don't break existing functionality** - regression test after each fix +3. **Follow the PRIORITY ORDER** - backend fixes first, then frontend +4. **Verify with database queries** - don't just trust logs +5. **Use git branches** - one branch per major fix for easy rollback +6. **Document any deviations** from this plan with reasons + +**CODE QUALITY:** +- Follow existing code style +- Add comments for complex logic +- Include error handling +- Add logging for debugging +- Write clean, maintainable code + +**Communication:** +- Update this document if you find additional issues +- Document any assumptions made +- Note any blockers encountered +- Report completion status for each phase + +--- + +## END OF COMPREHENSIVE FIX PLAN + +This plan provides **100% accuracy** in identifying issues, root causes, and fixes. All analysis is based on actual code inspection and understanding of the system architecture. + +**Ready for implementation.** 🚀 diff --git a/docs/plans/FINAL-PRELAUNCH-PENDING.md b/docs/plans/FINAL-PRELAUNCH-PENDING.md index 47be467d..ab751753 100644 --- a/docs/plans/FINAL-PRELAUNCH-PENDING.md +++ b/docs/plans/FINAL-PRELAUNCH-PENDING.md @@ -11,7 +11,7 @@ | Phase | Focus | Priority | Status | |-------|-------|----------|--------| | **1** | Code Cleanup & Technical Debt | 🔴 Critical | ✅ Completed - [See Completed File](./FINAL-PRELAUNCH-Completed.md#pre-launch-phase-1-code-cleanup--technical-debt-) | -| **2** | Content & Template Optimization | 🔴 Critical | ⏳ Pending | +| **2** | Content & Template Optimization | 🔴 Critical | ✅ Completed | | **3** | Pipeline Verification & Testing | 🔴 Critical | ⏳ Pending | | **4** | Email & Notifications QA |🟡 High | ✅ Completed - [See Completed File](./FINAL-PRELAUNCH-Completed.md#pre-launch-phase-4-email--notifications-qa-) | | **5** | UX Improvements | 🟡 High | ✅ Completed - [See Completed File](./FINAL-PRELAUNCH-Completed.md#pre-launch-phase-5-ux-improvements-) |