diff --git a/agent_chat/app.py b/agent_chat/app.py index db4e41968..d75ac4f65 100644 --- a/agent_chat/app.py +++ b/agent_chat/app.py @@ -48,6 +48,17 @@ try: except ImportError: GPU_AVAILABLE = False +# Execution components (optional - pour exécution réelle) +try: + from core.execution import ActionExecutor, TargetResolver, ErrorHandler + from core.execution.execution_loop import ExecutionLoop, ExecutionMode as ExecMode, ExecutionState + from core.pipeline.workflow_pipeline import WorkflowPipeline + from core.capture import ScreenCapturer + EXECUTION_AVAILABLE = True +except ImportError as e: + logger.warning(f"Composants d'exécution non disponibles: {e}") + EXECUTION_AVAILABLE = False + logging.basicConfig(level=logging.INFO) logger = logging.getLogger(__name__) @@ -63,6 +74,12 @@ confirmation_loop: Optional[ConfirmationLoop] = None response_generator: Optional[ResponseGenerator] = None conversation_manager: Optional[ConversationManager] = None +# Execution components +workflow_pipeline = None +action_executor = None +execution_loop = None +screen_capturer = None + execution_status = { "running": False, "workflow": None, @@ -110,6 +127,47 @@ def init_system(): response_generator = ResponseGenerator() conversation_manager = ConversationManager() + # 4. Composants d'exécution réelle + global workflow_pipeline, action_executor, execution_loop, screen_capturer + if EXECUTION_AVAILABLE: + try: + # Pipeline de workflow (matching + actions) + workflow_pipeline = WorkflowPipeline() + logger.info("✓ WorkflowPipeline initialisé") + + # Capture d'écran + screen_capturer = ScreenCapturer() + logger.info("✓ ScreenCapturer initialisé") + + # Résolveur de cibles et gestionnaire d'erreurs + target_resolver = TargetResolver() + error_handler = ErrorHandler() + + # Exécuteur d'actions + action_executor = ActionExecutor( + target_resolver=target_resolver, + error_handler=error_handler, + verify_postconditions=True + ) + logger.info("✓ ActionExecutor initialisé") + + # Boucle d'exécution (pour mode automatique) + execution_loop = ExecutionLoop( + pipeline=workflow_pipeline, + action_executor=action_executor, + screen_capturer=screen_capturer + ) + logger.info("✓ ExecutionLoop initialisé") + + except Exception as e: + logger.warning(f"⚠ Composants d'exécution partiels: {e}") + # Mode dégradé: simulation uniquement + workflow_pipeline = None + action_executor = None + execution_loop = None + else: + logger.info("ℹ Mode simulation (composants d'exécution non disponibles)") + # ============================================================================= # Routes Web @@ -580,34 +638,34 @@ def handle_cancel(): # ============================================================================= def execute_workflow(match, params): - """Exécuter un workflow avec le vrai système.""" + """Exécuter un workflow avec le vrai système d'exécution.""" global execution_status - + import time - + try: # Charger le workflow with open(match.workflow_path, 'r') as f: workflow_data = json.load(f) - + # Créer le VariableManager et injecter les paramètres var_manager = VariableManager() var_manager.set_variables(params) - + # Substituer les variables workflow_data = var_manager.substitute_dict(workflow_data) - + # Obtenir les étapes (edges) edges = workflow_data.get("edges", []) - total_steps = len(edges) if edges else 5 - + total_steps = len(edges) if edges else 1 + # Étape 1: Initialisation update_progress(10, "Initialisation", 1, total_steps + 2) - time.sleep(0.5) - + time.sleep(0.3) + # Étape 2: Préparation GPU (si disponible) if gpu_manager and GPU_AVAILABLE: - update_progress(20, "Préparation GPU...", 2, total_steps + 2) + update_progress(15, "Préparation GPU...", 2, total_steps + 2) try: loop = asyncio.new_event_loop() asyncio.set_event_loop(loop) @@ -615,36 +673,187 @@ def execute_workflow(match, params): loop.close() except Exception as e: logger.warning(f"GPU mode change failed: {e}") - - # Exécuter chaque étape du workflow - for i, edge in enumerate(edges): - if not execution_status["running"]: - break - - action = edge.get("action", {}) - action_type = action.get("type", "unknown") - - progress = int(20 + (i + 1) / total_steps * 70) - step_name = f"Étape {i+1}: {action_type}" - - update_progress(progress, step_name, i + 3, total_steps + 2) - - # Simuler l'exécution de l'action - # TODO: Connecter au vrai ActionExecutor - time.sleep(0.8) - + + # Vérifier si l'exécution réelle est disponible + use_real_execution = ( + EXECUTION_AVAILABLE and + action_executor is not None and + screen_capturer is not None + ) + + if use_real_execution: + logger.info(f"🚀 Exécution RÉELLE du workflow: {match.workflow_name}") + _execute_workflow_real(workflow_data, edges, total_steps, params) + else: + logger.info(f"🎭 Exécution SIMULÉE du workflow: {match.workflow_name}") + _execute_workflow_simulated(edges, total_steps) + # Finalisation - update_progress(95, "Finalisation...", total_steps + 1, total_steps + 2) - time.sleep(0.3) - - # Terminé avec succès - finish_execution(match.workflow_name, True, "Workflow terminé avec succès") - + if execution_status["running"]: + update_progress(95, "Finalisation...", total_steps + 1, total_steps + 2) + time.sleep(0.2) + finish_execution(match.workflow_name, True, "Workflow terminé avec succès") + except Exception as e: logger.error(f"Execution error: {e}") + import traceback + traceback.print_exc() finish_execution(match.workflow_name, False, f"Erreur: {str(e)}") +def _execute_workflow_real(workflow_data, edges, total_steps, params): + """Exécution réelle avec ActionExecutor.""" + import time + from dataclasses import dataclass + + # Capturer l'écran initial + update_progress(20, "Capture écran initial...", 2, total_steps + 2) + + try: + screenshot_path = screen_capturer.capture() + logger.info(f"📸 Screenshot capturé: {screenshot_path}") + except Exception as e: + logger.warning(f"Capture écran échouée: {e}, utilisation mode dégradé") + _execute_workflow_simulated(edges, total_steps) + return + + # Créer le ScreenState pour l'exécution + try: + from core.models import ScreenState + screen_state = ScreenState.from_screenshot(screenshot_path) + except Exception as e: + logger.warning(f"Création ScreenState échouée: {e}") + # Créer un ScreenState minimal + screen_state = type('ScreenState', (), { + 'screenshot_path': screenshot_path, + 'detected_elements': [], + 'timestamp': datetime.now() + })() + + # Exécuter chaque edge avec ActionExecutor + success_count = 0 + fail_count = 0 + + for i, edge in enumerate(edges): + if not execution_status["running"]: + logger.info("⏹️ Exécution annulée par l'utilisateur") + break + + action = edge.get("action", {}) + action_type = action.get("type", "unknown") + + progress = int(20 + (i + 1) / total_steps * 70) + step_name = f"Étape {i+1}/{total_steps}: {action_type}" + + update_progress(progress, step_name, i + 3, total_steps + 2) + logger.info(f"▶️ Exécution: {step_name}") + + try: + # Créer un objet Edge compatible avec ActionExecutor + workflow_edge = _create_workflow_edge(edge, params) + + # Exécuter l'action réelle + result = action_executor.execute_edge(workflow_edge, screen_state) + + if result.status.value == "success": + success_count += 1 + logger.info(f"✅ {step_name} - Succès") + + # Recapturer l'écran après chaque action réussie + try: + time.sleep(0.3) # Petit délai pour laisser l'UI se mettre à jour + screenshot_path = screen_capturer.capture() + screen_state = ScreenState.from_screenshot(screenshot_path) + except: + pass # Continuer même si la recapture échoue + else: + fail_count += 1 + logger.warning(f"⚠️ {step_name} - {result.status.value}: {result.message}") + + # Continuer malgré l'échec (mode best-effort) + if fail_count >= 3: + logger.error("❌ Trop d'échecs, arrêt de l'exécution") + break + + except Exception as e: + fail_count += 1 + logger.error(f"❌ Erreur lors de {step_name}: {e}") + + if fail_count >= 3: + logger.error("❌ Trop d'erreurs, arrêt de l'exécution") + break + + logger.info(f"📊 Résultat: {success_count} succès, {fail_count} échecs sur {total_steps} étapes") + + +def _execute_workflow_simulated(edges, total_steps): + """Exécution simulée (fallback).""" + import time + + for i, edge in enumerate(edges): + if not execution_status["running"]: + break + + action = edge.get("action", {}) + action_type = action.get("type", "unknown") + + progress = int(20 + (i + 1) / total_steps * 70) + step_name = f"Étape {i+1}: {action_type} (simulé)" + + update_progress(progress, step_name, i + 3, total_steps + 2) + + # Simulation avec délai + time.sleep(0.5) + logger.info(f"🎭 Simulé: {step_name}") + + +def _create_workflow_edge(edge_dict, params): + """Créer un objet WorkflowEdge depuis un dictionnaire.""" + from dataclasses import dataclass, field + from typing import Optional, Dict, Any, List + + @dataclass + class Action: + type: str + target: Optional[Dict] = None + value: Optional[str] = None + parameters: Dict[str, Any] = field(default_factory=dict) + + @dataclass + class WorkflowEdge: + id: str + source: str + target: str + action: Action + pre_conditions: List[Dict] = field(default_factory=list) + post_conditions: List[Dict] = field(default_factory=list) + + action_dict = edge_dict.get("action", {}) + + # Substituer les paramètres dans l'action + action_value = action_dict.get("value", "") + if action_value and isinstance(action_value, str): + for key, val in params.items(): + action_value = action_value.replace(f"${{{key}}}", str(val)) + action_value = action_value.replace(f"${key}", str(val)) + + action = Action( + type=action_dict.get("type", "unknown"), + target=action_dict.get("target"), + value=action_value, + parameters=action_dict.get("parameters", {}) + ) + + return WorkflowEdge( + id=edge_dict.get("id", f"edge_{id(edge_dict)}"), + source=edge_dict.get("source", ""), + target=edge_dict.get("target", ""), + action=action, + pre_conditions=edge_dict.get("pre_conditions", []), + post_conditions=edge_dict.get("post_conditions", []) + ) + + def update_progress(progress: int, message: str, current: int, total: int): """Mettre à jour la progression.""" global execution_status