450 lines
16 KiB
Python
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_())
|