""" ImprovedGUI - Interface utilisateur minimaliste et améliorée Fenêtre principale avec LogsPanel, statistiques et system tray """ from PyQt5.QtWidgets import ( QMainWindow, QWidget, QVBoxLayout, QHBoxLayout, QPushButton, QLabel, QFrame, QSystemTrayIcon, QMenu, QAction ) from PyQt5.QtCore import Qt, pyqtSignal from PyQt5.QtGui import QIcon, QFont from typing import Optional import logging from .logs_panel import LogsPanel from .models import GUIState from .signals import GUISignals class ImprovedGUI(QMainWindow): """ Interface GUI minimaliste et améliorée pour GeniusIA v2. Caractéristiques: - Fenêtre 300x400px - Panneau de statut avec mode et icône - LogsPanel intégré - Boutons Pause/Arrêter - System tray avec menu contextuel - Statistiques en temps réel Signals: start_requested: Demande de démarrage stop_requested: Demande d'arrêt pause_requested: Demande de pause """ # Signaux pour communication avec l'orchestrateur start_requested = pyqtSignal() stop_requested = pyqtSignal() pause_requested = pyqtSignal() def __init__(self, orchestrator=None): """ Initialise l'interface GUI améliorée. Args: orchestrator: Instance de l'orchestrateur (optionnel) """ super().__init__() self.orchestrator = orchestrator self.logger = logging.getLogger(__name__) # État de la GUI self.state = GUIState() # Signaux pour communication self.signals = GUISignals() # Icônes de mode self.mode_icons = { "shadow": "👀", "assist": "💡", "copilot": "🤝", "auto": "🤖" } # Initialiser l'interface self._init_ui() self._setup_system_tray() self._connect_signals() self.logger.info("ImprovedGUI initialisée") def _init_ui(self): """Initialise l'interface utilisateur.""" # Configuration de la fenêtre self.setWindowTitle("GeniusIA v2") self.setGeometry(100, 100, 300, 500) self.setMinimumSize(300, 400) self.setMaximumSize(400, 700) # Widget central central_widget = QWidget() self.setCentralWidget(central_widget) # Layout principal main_layout = QVBoxLayout() main_layout.setContentsMargins(0, 0, 0, 0) main_layout.setSpacing(0) central_widget.setLayout(main_layout) # Panneau de statut self._create_status_panel(main_layout) # Séparateur separator = QFrame() separator.setFrameShape(QFrame.HLine) separator.setStyleSheet("background-color: #e0e0e0;") main_layout.addWidget(separator) # Statistiques self._create_stats_panel(main_layout) # Séparateur separator2 = QFrame() separator2.setFrameShape(QFrame.HLine) separator2.setStyleSheet("background-color: #e0e0e0;") main_layout.addWidget(separator2) # Panneau de logs self.logs_panel = LogsPanel() main_layout.addWidget(self.logs_panel, 1) # Stretch factor 1 # Boutons de contrôle self._create_control_buttons(main_layout) def _create_status_panel(self, parent_layout): """Crée le panneau de statut avec mode et icône.""" status_widget = QWidget() status_widget.setStyleSheet(""" QWidget { background-color: #f5f5f5; padding: 10px; } """) status_layout = QVBoxLayout() status_layout.setContentsMargins(10, 10, 10, 10) status_widget.setLayout(status_layout) # Mode actuel mode_layout = QHBoxLayout() mode_label_text = QLabel("Mode:") mode_label_text.setFont(QFont("Arial", 10)) mode_layout.addWidget(mode_label_text) self.mode_label = QLabel(f"{self.mode_icons['shadow']} Observation") self.mode_label.setFont(QFont("Arial", 12, QFont.Bold)) self.mode_label.setStyleSheet("color: #2196F3;") mode_layout.addWidget(self.mode_label) mode_layout.addStretch() status_layout.addLayout(mode_layout) # Statut self.status_label = QLabel("💤 En attente...") self.status_label.setFont(QFont("Arial", 9)) self.status_label.setStyleSheet("color: #666;") status_layout.addWidget(self.status_label) parent_layout.addWidget(status_widget) def _create_stats_panel(self, parent_layout): """Crée le panneau de statistiques.""" stats_widget = QWidget() stats_widget.setStyleSheet(""" QWidget { background-color: white; padding: 8px; } """) stats_layout = QVBoxLayout() stats_layout.setContentsMargins(10, 8, 10, 8) stats_layout.setSpacing(4) stats_widget.setLayout(stats_layout) # Titre stats_title = QLabel("📊 Activité") stats_title.setFont(QFont("Arial", 10, QFont.Bold)) stats_layout.addWidget(stats_title) # Actions observées self.actions_label = QLabel("• 0 actions observées") self.actions_label.setFont(QFont("Arial", 9)) stats_layout.addWidget(self.actions_label) # Patterns détectés self.patterns_label = QLabel("• 0 patterns détectés") self.patterns_label.setFont(QFont("Arial", 9)) stats_layout.addWidget(self.patterns_label) # Workflows appris self.workflows_label = QLabel("• 0 workflows appris") self.workflows_label.setFont(QFont("Arial", 9)) stats_layout.addWidget(self.workflows_label) # Fine-tuning (caché par défaut) self.finetuning_label = QLabel("") self.finetuning_label.setFont(QFont("Arial", 9)) self.finetuning_label.setStyleSheet("color: #9C27B0;") self.finetuning_label.hide() stats_layout.addWidget(self.finetuning_label) parent_layout.addWidget(stats_widget) def _create_control_buttons(self, parent_layout): """Crée les boutons de contrôle.""" button_widget = QWidget() button_widget.setStyleSheet("background-color: #f5f5f5; padding: 10px;") button_layout = QHBoxLayout() button_layout.setContentsMargins(10, 10, 10, 10) button_widget.setLayout(button_layout) # Bouton Pause self.pause_button = QPushButton("⏸ Pause") self.pause_button.setFont(QFont("Arial", 10)) self.pause_button.setStyleSheet(""" QPushButton { background-color: #FF9800; color: white; border: none; padding: 8px 16px; border-radius: 4px; } QPushButton:hover { background-color: #e68900; } QPushButton:disabled { background-color: #cccccc; } """) self.pause_button.clicked.connect(self._on_pause_clicked) self.pause_button.setEnabled(False) button_layout.addWidget(self.pause_button) # Bouton Arrêter self.stop_button = QPushButton("⏹ Arrêter") self.stop_button.setFont(QFont("Arial", 10)) self.stop_button.setStyleSheet(""" QPushButton { background-color: #f44336; color: white; border: none; padding: 8px 16px; border-radius: 4px; } QPushButton:hover { background-color: #da190b; } QPushButton:disabled { background-color: #cccccc; } """) self.stop_button.clicked.connect(self._on_stop_clicked) self.stop_button.setEnabled(False) button_layout.addWidget(self.stop_button) parent_layout.addWidget(button_widget) def _setup_system_tray(self): """Configure le system tray.""" if not QSystemTrayIcon.isSystemTrayAvailable(): self.logger.warning("System tray non disponible") self.tray_icon = None return # Créer l'icône (utiliser une icône système par défaut) self.tray_icon = QSystemTrayIcon(self) self.tray_icon.setIcon(self.style().standardIcon(self.style().SP_ComputerIcon)) self.tray_icon.setToolTip("GeniusIA v2") # Créer le menu contextuel tray_menu = QMenu() # Action Afficher/Masquer show_action = QAction("Afficher", self) show_action.triggered.connect(self.show) tray_menu.addAction(show_action) hide_action = QAction("Masquer", self) hide_action.triggered.connect(self.hide) tray_menu.addAction(hide_action) tray_menu.addSeparator() # Action Quitter quit_action = QAction("Quitter", self) quit_action.triggered.connect(self._on_quit) tray_menu.addAction(quit_action) self.tray_icon.setContextMenu(tray_menu) self.tray_icon.activated.connect(self._on_tray_activated) self.tray_icon.show() self.logger.info("System tray configuré") def _connect_signals(self): """Connecte les signaux aux slots.""" self.signals.log_message.connect(self._on_log_message) self.signals.update_stats.connect(self._on_update_stats) self.signals.mode_changed.connect(self._on_mode_changed) self.signals.status_changed.connect(self._on_status_changed) def _on_log_message(self, emoji: str, message: str, level: str): """Gestionnaire de réception de message de log.""" self.logs_panel.add_log(message, emoji, level) def _on_update_stats(self, stats: dict): """Gestionnaire de mise à jour des statistiques.""" self.state.actions_count = stats.get('actions_count', 0) self.state.patterns_count = stats.get('patterns_count', 0) self.state.workflows_count = stats.get('workflows_count', 0) self.state.finetuning_status = stats.get('finetuning_status') self.state.finetuning_progress = stats.get('finetuning_progress') # Mettre à jour l'affichage self.actions_label.setText(f"• {self.state.actions_count} actions observées") self.patterns_label.setText(f"• {self.state.patterns_count} patterns détectés") self.workflows_label.setText(f"• {self.state.workflows_count} workflows appris") # Afficher le fine-tuning si actif if self.state.finetuning_status: if self.state.finetuning_status == "collecting": self.finetuning_label.setText(f"🧠 Collecte d'exemples: {self.state.finetuning_progress}/10") self.finetuning_label.show() elif self.state.finetuning_status == "training": self.finetuning_label.setText("🧠 Amélioration en cours...") self.finetuning_label.show() elif self.state.finetuning_status == "completed": self.finetuning_label.setText("✅ Modèle amélioré") self.finetuning_label.show() else: self.finetuning_label.hide() def _on_mode_changed(self, mode: str): """Gestionnaire de changement de mode.""" self.state.mode = mode mode_names = { "shadow": "Observation", "assist": "Suggestions", "copilot": "Copilote", "auto": "Autonome" } mode_colors = { "shadow": "#2196F3", "assist": "#FF9800", "copilot": "#9C27B0", "auto": "#4CAF50" } icon = self.mode_icons.get(mode, "🤖") name = mode_names.get(mode, mode.capitalize()) color = mode_colors.get(mode, "#666") self.mode_label.setText(f"{icon} {name}") self.mode_label.setStyleSheet(f"color: {color};") def _on_status_changed(self, is_running: bool): """Gestionnaire de changement de statut.""" self.state.is_running = is_running if is_running: self.status_label.setText("✅ En cours d'exécution") self.status_label.setStyleSheet("color: #4CAF50;") self.pause_button.setEnabled(True) self.stop_button.setEnabled(True) else: self.status_label.setText("💤 En attente...") self.status_label.setStyleSheet("color: #666;") self.pause_button.setEnabled(False) self.stop_button.setEnabled(False) def _on_pause_clicked(self): """Gestionnaire du bouton Pause.""" if self.state.is_running: self.pause_button.setText("▶ Reprendre") self.status_label.setText("⏸ En pause") self.status_label.setStyleSheet("color: #FF9800;") else: self.pause_button.setText("⏸ Pause") self.status_label.setText("✅ En cours d'exécution") self.status_label.setStyleSheet("color: #4CAF50;") self.pause_requested.emit() self.logger.info("Pause demandée") def _on_stop_clicked(self): """Gestionnaire du bouton Arrêter.""" self.stop_requested.emit() self.logger.info("Arrêt demandé") def _on_tray_activated(self, reason): """Gestionnaire d'activation de l'icône system tray.""" if reason == QSystemTrayIcon.Trigger: # Clic simple: afficher/masquer if self.isVisible(): self.hide() else: self.show() self.activateWindow() def _on_quit(self): """Gestionnaire de fermeture de l'application.""" if self.state.is_running: self._on_stop_clicked() self.close() def closeEvent(self, event): """Gestionnaire de fermeture de fenêtre.""" # Minimiser vers le tray au lieu de fermer if self.tray_icon and self.tray_icon.isVisible(): self.hide() event.ignore() else: event.accept() if __name__ == "__main__": """Test de l'ImprovedGUI""" import sys from PyQt5.QtWidgets import QApplication from PyQt5.QtCore import QTimer app = QApplication(sys.argv) # Créer la GUI gui = ImprovedGUI() gui.show() # Simuler des événements après 1 seconde def simulate_events(): print("Simulation d'événements...") # Changer le statut gui.signals.emit_status_change(True) # Ajouter des logs gui.signals.emit_log("👀", "J'observe vos actions dans Calculator", "info") gui.signals.emit_log("🎯", "Tiens ! Vous avez fait 3 fois la même chose", "success") gui.signals.emit_log("📚", "J'apprends: Calculer 9/9 (5 observations)", "info") # Mettre à jour les stats gui.signals.emit_stats_update({ 'actions_count': 12, 'patterns_count': 2, 'workflows_count': 1, 'finetuning_status': 'collecting', 'finetuning_progress': 8 }) # Changer le mode QTimer.singleShot(2000, lambda: gui.signals.emit_mode_change("assist")) QTimer.singleShot(2000, lambda: gui.signals.emit_log("✅", "Mode Suggestions activé", "success")) QTimer.singleShot(1000, simulate_events) sys.exit(app.exec_())