""" Point d'entrée principal pour RPA Vision V2. Initialise tous les composants et lance l'application. """ import sys import signal import asyncio from pathlib import Path from typing import Optional from PyQt5.QtWidgets import QApplication from PyQt5.QtCore import QThread, pyqtSignal from core.config import CONFIG from core.logger import Logger from core.embeddings_manager import EmbeddingsManager from core.llm_manager import LLMManager from core.learning_manager import LearningManager from core.orchestrator import Orchestrator from core.whitelist_manager import WhitelistManager from core.ui_change_detector import UIChangeDetector from core.metrics_collector import MetricsCollector from core.utils.vision_utils import VisionUtils from core.utils.input_utils import InputUtils from core.replay_async import ReplayEngine from gui.minimal_gui import MinimalGUI from gui import setup_gui_for_orchestrator class OrchestratorThread(QThread): """Thread pour exécuter l'orchestrateur de manière asynchrone.""" error_signal = pyqtSignal(str) status_signal = pyqtSignal(str) def __init__(self, orchestrator: Orchestrator): super().__init__() self.orchestrator = orchestrator self.running = False def run(self): """Exécute la boucle cognitive.""" self.running = True self.status_signal.emit("started") try: # Exécuter l'orchestrateur (méthode synchrone) self.orchestrator.run() except Exception as e: self.error_signal.emit(str(e)) finally: self.running = False self.status_signal.emit("stopped") def stop(self): """Arrête l'orchestrateur.""" self.running = False self.orchestrator.stop() class RPAVisionApp: """Application principale RPA Vision V2.""" def __init__(self): """Initialise l'application.""" self.config = None self.logger = None self.embeddings_manager = None self.llm_manager = None self.vision_utils = None self.input_utils = None self.replay_engine = None self.whitelist_manager = None self.ui_change_detector = None self.metrics_collector = None self.learning_manager = None self.orchestrator = None self.gui = None self.gui_bridge = None self.orchestrator_thread = None self.qt_app = None def initialize(self): """Initialise tous les composants.""" print("🚀 Initialisation de RPA Vision V2...") # 1. Charger la configuration print("📋 Chargement de la configuration...") self.config = CONFIG config_dict = CONFIG # 2. Initialiser le logger print("📝 Initialisation du logger...") self.logger = Logger() self.logger.log_action({ "action": "application_startup", "version": "2.0" }) # 3. Initialiser le gestionnaire d'embeddings print("🧠 Chargement du modèle OpenCLIP...") self.embeddings_manager = EmbeddingsManager( model_name=CONFIG["models"]["clip"], logger=self.logger ) # 4. Initialiser le gestionnaire LLM print("💬 Connexion à Ollama...") self.llm_manager = LLMManager( model_name=CONFIG["models"]["llm"], logger=self.logger ) # 5. Initialiser les utilitaires de vision print("👁️ Chargement des modèles de vision...") self.vision_utils = VisionUtils(config=CONFIG) # 6. Initialiser les utilitaires d'entrée print("⌨️ Initialisation des utilitaires d'entrée...") self.input_utils = InputUtils(logger=self.logger, config=CONFIG) # 7. Initialiser le moteur de rejeu print("🔄 Initialisation du moteur de rejeu...") self.replay_engine = ReplayEngine( input_utils=self.input_utils, logger=self.logger, config=CONFIG ) # 8. Initialiser le gestionnaire de liste blanche print("🛡️ Chargement de la liste blanche...") self.whitelist_manager = WhitelistManager(logger=self.logger) # 9. Initialiser le détecteur de changements UI print("🔍 Initialisation du détecteur de changements UI...") self.ui_change_detector = UIChangeDetector( embeddings_manager=self.embeddings_manager, logger=self.logger, config=CONFIG ) # 10. Initialiser le collecteur de métriques print("📊 Initialisation du collecteur de métriques...") self.metrics_collector = MetricsCollector(logger=self.logger, config=CONFIG) # 11. Initialiser le gestionnaire d'apprentissage print("🎓 Initialisation du gestionnaire d'apprentissage...") self.learning_manager = LearningManager( embeddings_manager=self.embeddings_manager, logger=self.logger, config=CONFIG ) # 12. Initialiser l'orchestrateur print("🎯 Initialisation de l'orchestrateur...") self.orchestrator = Orchestrator( learning_manager=self.learning_manager, vision_utils=self.vision_utils, llm_manager=self.llm_manager, logger=self.logger, config=CONFIG, whitelist_manager=self.whitelist_manager, input_utils=self.input_utils ) print("✅ Tous les composants initialisés avec succès!") def setup_gui(self): """Configure l'interface graphique.""" print("🖥️ Initialisation de l'interface graphique améliorée...") # Créer l'application Qt self.qt_app = QApplication(sys.argv) # Utiliser la nouvelle GUI améliorée avec intégration automatique self.gui_bridge = setup_gui_for_orchestrator(self.orchestrator) self.gui = self.gui_bridge.gui # Connecter les signaux (déjà fait par le bridge, mais on garde pour compatibilité) self.gui.start_requested.connect(self.start_orchestrator) self.gui.stop_requested.connect(self.stop_orchestrator) self.gui.pause_requested.connect(self.pause_orchestrator) # Envoyer un message de bienvenue self.orchestrator.log_to_gui("✅", "Système initialisé et prêt", "success") self.orchestrator.update_gui_stats( actions_count=0, patterns_count=0, workflows_count=0 ) # Activer les boutons Pause et Arrêter (car démarrage automatique) self.gui.pause_button.setEnabled(True) self.gui.stop_button.setEnabled(True) print("✅ Interface graphique améliorée prête!") def start_orchestrator(self): """Démarre l'orchestrateur dans un thread séparé.""" if self.orchestrator_thread is None or not self.orchestrator_thread.running: self.orchestrator_thread = OrchestratorThread(self.orchestrator) # Connecter les signaux self.orchestrator_thread.error_signal.connect(self.on_orchestrator_error) self.orchestrator_thread.status_signal.connect(self.on_orchestrator_status) # Démarrer le thread self.orchestrator_thread.start() self.logger.log_action({ "action": "orchestrator_started" }) def stop_orchestrator(self): """Arrête l'orchestrateur.""" # Arrêter la capture d'événements if hasattr(self, 'event_capture') and self.event_capture: self.event_capture.stop() # Arrêter le thread de l'orchestrateur if self.orchestrator_thread and self.orchestrator_thread.running: self.orchestrator_thread.stop() self.orchestrator_thread.wait() # Attendre la fin du thread self.logger.log_action({ "action": "orchestrator_stopped" }) def pause_orchestrator(self): """Met en pause l'orchestrateur.""" if self.orchestrator: self.orchestrator.pause() self.logger.log_action({ "action": "orchestrator_paused" }) def on_orchestrator_error(self, error: str): """Gère les erreurs de l'orchestrateur.""" self.logger.log_action({ "action": "orchestrator_error", "error": error }) # Logger l'erreur dans la GUI if self.orchestrator and hasattr(self.orchestrator, 'log_to_gui'): self.orchestrator.log_to_gui("❌", f"Erreur: {error}", "error") def on_orchestrator_status(self, status: str): """Gère les changements de statut de l'orchestrateur.""" self.logger.log_action({ "action": "orchestrator_status_changed", "status": status }) def run(self, mode='shadow', headless=False): """ Lance l'application. Args: mode: Mode opérationnel (shadow, assist, auto) headless: Si True, lance sans GUI """ try: # Initialiser les composants self.initialize() # Définir le mode if self.learning_manager: # Mode progressif: démarre en shadow, propose assist après patterns if mode == 'progressive': self.orchestrator.enable_progressive_mode() actual_mode = 'shadow' # Démarre en shadow else: actual_mode = mode self.learning_manager.mode = actual_mode self.logger.log_action({ "action": "mode_set", "mode": mode, "actual_mode": actual_mode if mode == 'progressive' else mode, "headless": headless }) if headless: # Mode headless: juste l'orchestrateur print(f"🔇 Mode headless activé - Mode: {mode.upper()}") self.orchestrator.run() else: # Mode normal avec GUI self.setup_gui() self.gui.show() # Configurer le gestionnaire de signaux pour arrêt gracieux signal.signal(signal.SIGINT, self.signal_handler) signal.signal(signal.SIGTERM, self.signal_handler) print("\n" + "="*60) print("🎉 RPA Vision V2 est prêt!") print("="*60) print(f"\n🎯 Mode: {mode.upper()}") print("\n📌 Raccourcis clavier:") print(" - Ctrl+Pause : Arrêt d'urgence") print(" - Entrée : Valider une suggestion") print(" - Échap : Rejeter une suggestion") print(" - Alt+C : Corriger une action") print("\n� CDémarrage automatique de l'observation...") print("="*60 + "\n") # Envoyer un log de démarrage self.orchestrator.log_to_gui("🚀", "Démarrage de l'observation...", "info") # Démarrer automatiquement l'orchestrator self.start_orchestrator() # Confirmer le démarrage self.orchestrator.log_to_gui("👀", "Observation active - En attente d'actions...", "success") self.gui.signals.emit_status_change(True) # Lancer la boucle d'événements Qt sys.exit(self.qt_app.exec_()) except KeyboardInterrupt: print("\n🛑 Arrêt demandé...") self.shutdown() except Exception as e: print(f"\n❌ Erreur fatale: {e}") import traceback traceback.print_exc() self.logger.log_action({ "action": "application_fatal_error", "error": str(e) }) sys.exit(1) def signal_handler(self, signum, frame): """Gère les signaux d'arrêt (Ctrl+C, etc.).""" print("\n\n🛑 Arrêt demandé...") self.shutdown() def shutdown(self): """Arrête proprement l'application avec timeout de 5 secondes.""" import time from threading import Thread print("🔄 Arrêt en cours...") start_time = time.time() # Arrêter la capture d'événements (IMPORTANT pour libérer les listeners pynput) if self.orchestrator and hasattr(self.orchestrator, 'event_capture'): print(" - Arrêt de la capture d'événements...") try: self.orchestrator.event_capture.stop() except Exception as e: print(f" ⚠️ Erreur lors de l'arrêt de la capture: {e}") # Arrêter l'orchestrateur avec timeout if self.orchestrator_thread: print(" - Arrêt de l'orchestrateur...") try: self.stop_orchestrator() # Attendre max 3 secondes que le thread se termine if self.orchestrator_thread.isRunning(): self.orchestrator_thread.wait(3000) # 3 secondes en ms if self.orchestrator_thread.isRunning(): print(" ⚠️ Timeout: forçage de l'arrêt") self.orchestrator_thread.terminate() except Exception as e: print(f" ⚠️ Erreur lors de l'arrêt de l'orchestrateur: {e}") # Sauvegarder l'état d'apprentissage if self.learning_manager: print(" - Sauvegarde de l'état d'apprentissage...") try: # Le learning_manager sauvegarde automatiquement via le logger pass except Exception as e: print(f" ⚠️ Erreur lors de la sauvegarde: {e}") # Sauvegarder la liste blanche if self.whitelist_manager: print(" - Sauvegarde de la liste blanche...") try: self.whitelist_manager.save_whitelist() except Exception as e: print(f" ⚠️ Erreur lors de la sauvegarde: {e}") # Finaliser les logs if self.logger: print(" - Finalisation des logs...") try: self.logger.log_action({ "action": "application_shutdown", "graceful": True, "duration_seconds": time.time() - start_time }) except Exception as e: print(f" ⚠️ Erreur lors de la finalisation des logs: {e}") elapsed = time.time() - start_time print(f"✅ Arrêt terminé en {elapsed:.1f}s. Au revoir!") # Quitter l'application Qt if self.qt_app: self.qt_app.quit() def main(): """Point d'entrée principal.""" import argparse # Parser les arguments parser = argparse.ArgumentParser(description='RPA Vision V2 - Automatisation intelligente') parser.add_argument( '--mode', type=str, choices=['shadow', 'assist', 'auto', 'progressive'], default='progressive', help='Mode opérationnel: progressive (adaptatif), shadow (observation), assist (suggestions), auto (automatique)' ) parser.add_argument( '--headless', action='store_true', help='Lancer sans interface graphique (pour tests)' ) args = parser.parse_args() # Créer et lancer l'application app = RPAVisionApp() app.run(mode=args.mode, headless=args.headless) if __name__ == "__main__": main()