diff --git a/backend/igny8_core/business/automation/migrations/0007_add_stage_enabled_toggles.py b/backend/igny8_core/business/automation/migrations/0007_add_stage_enabled_toggles.py new file mode 100644 index 00000000..1fb5d0de --- /dev/null +++ b/backend/igny8_core/business/automation/migrations/0007_add_stage_enabled_toggles.py @@ -0,0 +1,48 @@ +# Generated migration for automation stage enabled toggles + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('automation', '0006_automationrun_initial_snapshot'), + ] + + operations = [ + migrations.AddField( + model_name='automationconfig', + name='stage_1_enabled', + field=models.BooleanField(default=True, help_text='Process Stage 1: Keywords → Clusters'), + ), + migrations.AddField( + model_name='automationconfig', + name='stage_2_enabled', + field=models.BooleanField(default=True, help_text='Process Stage 2: Clusters → Ideas'), + ), + migrations.AddField( + model_name='automationconfig', + name='stage_3_enabled', + field=models.BooleanField(default=True, help_text='Process Stage 3: Ideas → Tasks'), + ), + migrations.AddField( + model_name='automationconfig', + name='stage_4_enabled', + field=models.BooleanField(default=True, help_text='Process Stage 4: Tasks → Content'), + ), + migrations.AddField( + model_name='automationconfig', + name='stage_5_enabled', + field=models.BooleanField(default=True, help_text='Process Stage 5: Content → Image Prompts'), + ), + migrations.AddField( + model_name='automationconfig', + name='stage_6_enabled', + field=models.BooleanField(default=True, help_text='Process Stage 6: Image Prompts → Images'), + ), + migrations.AddField( + model_name='automationconfig', + name='stage_7_enabled', + field=models.BooleanField(default=True, help_text='Process Stage 7: Review → Published'), + ), + ] diff --git a/backend/igny8_core/business/automation/models.py b/backend/igny8_core/business/automation/models.py index 9246532e..6aefb841 100644 --- a/backend/igny8_core/business/automation/models.py +++ b/backend/igny8_core/business/automation/models.py @@ -23,6 +23,15 @@ class AutomationConfig(models.Model): frequency = models.CharField(max_length=20, choices=FREQUENCY_CHOICES, default='daily') scheduled_time = models.TimeField(default='02:00', help_text="Time to run (e.g., 02:00)") + # Stage processing toggles + stage_1_enabled = models.BooleanField(default=True, help_text="Process Stage 1: Keywords → Clusters") + stage_2_enabled = models.BooleanField(default=True, help_text="Process Stage 2: Clusters → Ideas") + stage_3_enabled = models.BooleanField(default=True, help_text="Process Stage 3: Ideas → Tasks") + stage_4_enabled = models.BooleanField(default=True, help_text="Process Stage 4: Tasks → Content") + stage_5_enabled = models.BooleanField(default=True, help_text="Process Stage 5: Content → Image Prompts") + stage_6_enabled = models.BooleanField(default=True, help_text="Process Stage 6: Image Prompts → Images") + stage_7_enabled = models.BooleanField(default=True, help_text="Process Stage 7: Review → Published") + # Batch sizes per stage stage_1_batch_size = models.IntegerField(default=50, help_text="Keywords per batch") stage_2_batch_size = models.IntegerField(default=1, help_text="Clusters at a time") diff --git a/backend/igny8_core/business/automation/tasks.py b/backend/igny8_core/business/automation/tasks.py index 02d14777..1c4f0c48 100644 --- a/backend/igny8_core/business/automation/tasks.py +++ b/backend/igny8_core/business/automation/tasks.py @@ -81,42 +81,64 @@ def run_automation_task(self, run_id: str): try: service = AutomationService.from_run_id(run_id) + config = service.config - # Run all stages sequentially, checking for pause/cancel between stages - service.run_stage_1() - if service.run.status in ['paused', 'cancelled']: - logger.info(f"[AutomationTask] Automation {service.run.status} after stage 1") - return + # Run all stages sequentially, checking for enabled status, pause/cancel between stages + if config.stage_1_enabled: + service.run_stage_1() + if service.run.status in ['paused', 'cancelled']: + logger.info(f"[AutomationTask] Automation {service.run.status} after stage 1") + return + else: + logger.info(f"[AutomationTask] Stage 1 is disabled, skipping") - service.run_stage_2() - if service.run.status in ['paused', 'cancelled']: - logger.info(f"[AutomationTask] Automation {service.run.status} after stage 2") - return + if config.stage_2_enabled: + service.run_stage_2() + if service.run.status in ['paused', 'cancelled']: + logger.info(f"[AutomationTask] Automation {service.run.status} after stage 2") + return + else: + logger.info(f"[AutomationTask] Stage 2 is disabled, skipping") - service.run_stage_3() - if service.run.status in ['paused', 'cancelled']: - logger.info(f"[AutomationTask] Automation {service.run.status} after stage 3") - return + if config.stage_3_enabled: + service.run_stage_3() + if service.run.status in ['paused', 'cancelled']: + logger.info(f"[AutomationTask] Automation {service.run.status} after stage 3") + return + else: + logger.info(f"[AutomationTask] Stage 3 is disabled, skipping") - service.run_stage_4() - if service.run.status in ['paused', 'cancelled']: - logger.info(f"[AutomationTask] Automation {service.run.status} after stage 4") - return + if config.stage_4_enabled: + service.run_stage_4() + if service.run.status in ['paused', 'cancelled']: + logger.info(f"[AutomationTask] Automation {service.run.status} after stage 4") + return + else: + logger.info(f"[AutomationTask] Stage 4 is disabled, skipping") - service.run_stage_5() - if service.run.status in ['paused', 'cancelled']: - logger.info(f"[AutomationTask] Automation {service.run.status} after stage 5") - return + if config.stage_5_enabled: + service.run_stage_5() + if service.run.status in ['paused', 'cancelled']: + logger.info(f"[AutomationTask] Automation {service.run.status} after stage 5") + return + else: + logger.info(f"[AutomationTask] Stage 5 is disabled, skipping") - service.run_stage_6() - if service.run.status in ['paused', 'cancelled']: - logger.info(f"[AutomationTask] Automation {service.run.status} after stage 6") - return + if config.stage_6_enabled: + service.run_stage_6() + if service.run.status in ['paused', 'cancelled']: + logger.info(f"[AutomationTask] Automation {service.run.status} after stage 6") + return + else: + logger.info(f"[AutomationTask] Stage 6 is disabled, skipping") - service.run_stage_7() - if service.run.status in ['paused', 'cancelled']: - logger.info(f"[AutomationTask] Automation {service.run.status} after stage 7") - return + if config.stage_7_enabled: + service.run_stage_7() + if service.run.status in ['paused', 'cancelled']: + logger.info(f"[AutomationTask] Automation {service.run.status} after stage 7") + return + else: + logger.info(f"[AutomationTask] Stage 7 is disabled, skipping") logger.info(f"[AutomationTask] Completed automation run: {run_id}") @@ -147,6 +169,7 @@ def resume_automation_task(self, run_id: str): try: service = AutomationService.from_run_id(run_id) run = service.run + config = service.config # Continue from current stage stage_methods = [ @@ -159,9 +182,22 @@ def resume_automation_task(self, run_id: str): service.run_stage_7, ] - # Run from current_stage to end + stage_enabled = [ + config.stage_1_enabled, + config.stage_2_enabled, + config.stage_3_enabled, + config.stage_4_enabled, + config.stage_5_enabled, + config.stage_6_enabled, + config.stage_7_enabled, + ] + + # Run from current_stage to end, only if stage is enabled for stage in range(run.current_stage - 1, 7): - stage_methods[stage]() + if stage_enabled[stage]: + stage_methods[stage]() + else: + logger.info(f"[AutomationTask] Stage {stage + 1} is disabled, skipping") logger.info(f"[AutomationTask] Resumed automation run: {run_id}") diff --git a/backend/igny8_core/business/automation/views.py b/backend/igny8_core/business/automation/views.py index 57cf3003..7571a096 100644 --- a/backend/igny8_core/business/automation/views.py +++ b/backend/igny8_core/business/automation/views.py @@ -58,6 +58,13 @@ class AutomationViewSet(viewsets.ViewSet): 'is_enabled': config.is_enabled, 'frequency': config.frequency, 'scheduled_time': str(config.scheduled_time), + 'stage_1_enabled': config.stage_1_enabled, + 'stage_2_enabled': config.stage_2_enabled, + 'stage_3_enabled': config.stage_3_enabled, + 'stage_4_enabled': config.stage_4_enabled, + 'stage_5_enabled': config.stage_5_enabled, + 'stage_6_enabled': config.stage_6_enabled, + 'stage_7_enabled': config.stage_7_enabled, 'stage_1_batch_size': config.stage_1_batch_size, 'stage_2_batch_size': config.stage_2_batch_size, 'stage_3_batch_size': config.stage_3_batch_size, @@ -102,6 +109,22 @@ class AutomationViewSet(viewsets.ViewSet): config.frequency = request.data['frequency'] if 'scheduled_time' in request.data: config.scheduled_time = request.data['scheduled_time'] + # Stage enabled toggles + if 'stage_1_enabled' in request.data: + config.stage_1_enabled = request.data['stage_1_enabled'] + if 'stage_2_enabled' in request.data: + config.stage_2_enabled = request.data['stage_2_enabled'] + if 'stage_3_enabled' in request.data: + config.stage_3_enabled = request.data['stage_3_enabled'] + if 'stage_4_enabled' in request.data: + config.stage_4_enabled = request.data['stage_4_enabled'] + if 'stage_5_enabled' in request.data: + config.stage_5_enabled = request.data['stage_5_enabled'] + if 'stage_6_enabled' in request.data: + config.stage_6_enabled = request.data['stage_6_enabled'] + if 'stage_7_enabled' in request.data: + config.stage_7_enabled = request.data['stage_7_enabled'] + # Batch sizes if 'stage_1_batch_size' in request.data: config.stage_1_batch_size = request.data['stage_1_batch_size'] if 'stage_2_batch_size' in request.data: @@ -133,6 +156,13 @@ class AutomationViewSet(viewsets.ViewSet): 'is_enabled': config.is_enabled, 'frequency': config.frequency, 'scheduled_time': str(config.scheduled_time), + 'stage_1_enabled': config.stage_1_enabled, + 'stage_2_enabled': config.stage_2_enabled, + 'stage_3_enabled': config.stage_3_enabled, + 'stage_4_enabled': config.stage_4_enabled, + 'stage_5_enabled': config.stage_5_enabled, + 'stage_6_enabled': config.stage_6_enabled, + 'stage_7_enabled': config.stage_7_enabled, 'stage_1_batch_size': config.stage_1_batch_size, 'stage_2_batch_size': config.stage_2_batch_size, 'stage_3_batch_size': config.stage_3_batch_size, diff --git a/frontend/src/components/Automation/ConfigModal.tsx b/frontend/src/components/Automation/ConfigModal.tsx index 458c27b2..9fa59499 100644 --- a/frontend/src/components/Automation/ConfigModal.tsx +++ b/frontend/src/components/Automation/ConfigModal.tsx @@ -21,6 +21,13 @@ const ConfigModal: React.FC = ({ config, onSave, onCancel }) = is_enabled: config.is_enabled, frequency: config.frequency, scheduled_time: config.scheduled_time, + stage_1_enabled: config.stage_1_enabled ?? true, + stage_2_enabled: config.stage_2_enabled ?? true, + stage_3_enabled: config.stage_3_enabled ?? true, + stage_4_enabled: config.stage_4_enabled ?? true, + stage_5_enabled: config.stage_5_enabled ?? true, + stage_6_enabled: config.stage_6_enabled ?? true, + stage_7_enabled: config.stage_7_enabled ?? true, stage_1_batch_size: config.stage_1_batch_size, stage_2_batch_size: config.stage_2_batch_size, stage_3_batch_size: config.stage_3_batch_size, @@ -97,8 +104,68 @@ const ConfigModal: React.FC = ({ config, onSave, onCancel }) = /> + {/* Stage Processing Toggles */} +
+

Stage Processing

+

+ Enable or disable individual stages. Disabled stages will be skipped during automation. +

+ +
+ + setFormData({ ...formData, stage_1_enabled: checked }) + } + /> + + setFormData({ ...formData, stage_2_enabled: checked }) + } + /> + + setFormData({ ...formData, stage_3_enabled: checked }) + } + /> + + setFormData({ ...formData, stage_4_enabled: checked }) + } + /> + + setFormData({ ...formData, stage_5_enabled: checked }) + } + /> + + setFormData({ ...formData, stage_6_enabled: checked }) + } + /> + + setFormData({ ...formData, stage_7_enabled: checked }) + } + /> +
+
+ {/* Batch Sizes */} -
+

Batch Sizes

Configure how many items to process in each stage diff --git a/frontend/src/pages/Automation/AutomationPage.tsx b/frontend/src/pages/Automation/AutomationPage.tsx index 0edc90c8..85860154 100644 --- a/frontend/src/pages/Automation/AutomationPage.tsx +++ b/frontend/src/pages/Automation/AutomationPage.tsx @@ -923,6 +923,10 @@ const AutomationPage: React.FC = () => { const stageBorderColors = ['border-l-brand-500', 'border-l-purple-500', 'border-l-warning-500', 'border-l-gray-600']; const stageBorderColor = stageBorderColors[index] || 'border-l-brand-500'; + // Check if this stage is enabled in config + const stageEnabledKey = `stage_${stage.number}_enabled` as keyof AutomationConfig; + const isStageEnabled = config?.[stageEnabledKey] ?? true; + return (

{
Stage {stage.number} - {isActive && ● Active} + {isActive && isStageEnabled && ● Active} + {isActive && !isStageEnabled && Skipped} {isComplete && } - {!isActive && !isComplete && stage.pending > 0 && Ready} + {!isActive && !isComplete && stage.pending > 0 && !isStageEnabled && Skipped} + {!isActive && !isComplete && stage.pending > 0 && isStageEnabled && Ready}
{/* Stage Function Name - Right side, larger font */}
{stageConfig.name}
@@ -1053,6 +1059,10 @@ const AutomationPage: React.FC = () => { const stageBorderColors56 = ['border-l-brand-500', 'border-l-purple-500']; const stageBorderColor = stageBorderColors56[index] || 'border-l-brand-500'; + // Check if this stage is enabled in config + const stageEnabledKey = `stage_${stage.number}_enabled` as keyof AutomationConfig; + const isStageEnabled = config?.[stageEnabledKey] ?? true; + return (
{
Stage {stage.number} - {isActive && ● Active} + {isActive && isStageEnabled && ● Active} + {isActive && !isStageEnabled && Skipped} {isComplete && } - {!isActive && !isComplete && stage.pending > 0 && Ready} + {!isActive && !isComplete && stage.pending > 0 && !isStageEnabled && Skipped} + {!isActive && !isComplete && stage.pending > 0 && isStageEnabled && Ready}
{/* Stage Function Name - Right side, larger font */}
{stageConfig.name}
@@ -1162,6 +1174,9 @@ const AutomationPage: React.FC = () => { const progressPercent = totalReview > 0 ? Math.min(Math.round((approvedCount / totalReview) * 100), 100) : 0; + // Check if stage 7 is enabled in config + const isStage7Enabled = config?.stage_7_enabled ?? true; + return (
{
Stage 7 - {isActive && ● Active} + {isActive && isStage7Enabled && ● Active} + {isActive && !isStage7Enabled && Skipped} + {isActive && !isStage7Enabled && Skipped} {isComplete && } - {!isActive && !isComplete && pendingReview > 0 && Ready} + {!isActive && !isComplete && pendingReview > 0 && !isStage7Enabled && Skipped} + {!isActive && !isComplete && pendingReview > 0 && isStage7Enabled && Ready} {/* Stage Function Name - Right side, larger font */}
{stageConfig.name}
diff --git a/frontend/src/services/automationService.ts b/frontend/src/services/automationService.ts index 508bc655..eec5e261 100644 --- a/frontend/src/services/automationService.ts +++ b/frontend/src/services/automationService.ts @@ -7,6 +7,13 @@ export interface AutomationConfig { is_enabled: boolean; frequency: 'daily' | 'weekly' | 'monthly'; scheduled_time: string; + stage_1_enabled: boolean; + stage_2_enabled: boolean; + stage_3_enabled: boolean; + stage_4_enabled: boolean; + stage_5_enabled: boolean; + stage_6_enabled: boolean; + stage_7_enabled: boolean; stage_1_batch_size: number; stage_2_batch_size: number; stage_3_batch_size: number;