Files
Geniusia_v2/geniusia2/gui/improved_gui.py
2026-03-05 00:20:25 +01:00

450 lines
16 KiB
Python

"""
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_())