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

348 lines
11 KiB
Python
Raw Blame History

This file contains invisible Unicode characters
This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
"""
LogsPanel - Widget Qt pour affichage des logs avec scroll et auto-scroll conditionnel
Affiche les messages de log avec timestamp, emoji et formatage.
"""
from PyQt5.QtWidgets import (
QWidget, QVBoxLayout, QScrollArea, QLabel, QFrame
)
from PyQt5.QtCore import Qt, QTimer, pyqtSignal
from PyQt5.QtGui import QFont
from datetime import datetime
from typing import List, Optional
from dataclasses import dataclass
@dataclass
class LogMessage:
"""Message de log pour affichage."""
timestamp: datetime
emoji: str
message: str
level: str # 'info', 'success', 'warning', 'error'
technical_details: Optional[str] = None # Pour logs techniques
class LogsPanel(QWidget):
"""
Panneau d'affichage des logs avec scroll automatique conditionnel.
Caractéristiques:
- Affiche les 5 dernières actions visibles par défaut
- Scrollable jusqu'à 30 messages maximum
- Auto-scroll uniquement si déjà en bas
- Format: HH:MM emoji Message
- Supprime automatiquement les messages les plus anciens au-delà de 30
Attributes:
max_logs: Nombre maximum de logs à conserver (30)
logs: Liste des messages de log
log_labels: Liste des widgets QLabel pour chaque log
scroll_area: Zone de scroll pour les logs
content_widget: Widget contenant tous les logs
"""
# Signal émis quand un log est ajouté
log_added = pyqtSignal(str)
def __init__(self, parent=None):
"""
Initialise le panneau de logs.
Args:
parent: Widget parent (optionnel)
"""
super().__init__(parent)
self.max_logs = 30
self.logs: List[LogMessage] = []
self.log_labels: List[QLabel] = []
self._init_ui()
def _init_ui(self):
"""Initialise l'interface utilisateur du panneau."""
# Layout principal
main_layout = QVBoxLayout()
main_layout.setContentsMargins(0, 0, 0, 0)
main_layout.setSpacing(0)
self.setLayout(main_layout)
# Titre du panneau
title_label = QLabel("📝 Journal d'activité")
title_label.setFont(QFont("Arial", 11, QFont.Bold))
title_label.setStyleSheet("""
QLabel {
padding: 8px;
background-color: #f5f5f5;
border-bottom: 2px solid #e0e0e0;
}
""")
main_layout.addWidget(title_label)
# Zone de scroll pour les logs
self.scroll_area = QScrollArea()
self.scroll_area.setWidgetResizable(True)
self.scroll_area.setHorizontalScrollBarPolicy(Qt.ScrollBarAlwaysOff)
self.scroll_area.setVerticalScrollBarPolicy(Qt.ScrollBarAsNeeded)
self.scroll_area.setStyleSheet("""
QScrollArea {
border: none;
background-color: white;
}
""")
# Widget contenant les logs
self.content_widget = QWidget()
self.content_layout = QVBoxLayout()
self.content_layout.setContentsMargins(5, 5, 5, 5)
self.content_layout.setSpacing(2)
self.content_layout.addStretch() # Push logs to top
self.content_widget.setLayout(self.content_layout)
self.scroll_area.setWidget(self.content_widget)
main_layout.addWidget(self.scroll_area)
# Message par défaut si aucun log
self._show_empty_message()
def _show_empty_message(self):
"""Affiche un message quand il n'y a pas de logs."""
if len(self.logs) == 0:
empty_label = QLabel("💤 En attente d'activité...")
empty_label.setFont(QFont("Arial", 10))
empty_label.setStyleSheet("""
QLabel {
color: #999;
padding: 20px;
text-align: center;
}
""")
empty_label.setAlignment(Qt.AlignCenter)
self.content_layout.insertWidget(0, empty_label)
def _remove_empty_message(self):
"""Supprime le message vide si présent."""
if self.content_layout.count() > 1:
item = self.content_layout.itemAt(0)
if item and item.widget():
widget = item.widget()
if isinstance(widget, QLabel) and "En attente" in widget.text():
self.content_layout.removeWidget(widget)
widget.deleteLater()
def _is_scrolled_to_bottom(self) -> bool:
"""
Vérifie si le scroll est en bas.
Returns:
True si le scroll est en bas (ou proche), False sinon
"""
scrollbar = self.scroll_area.verticalScrollBar()
# Considérer "en bas" si on est à moins de 10 pixels du bas
return scrollbar.value() >= scrollbar.maximum() - 10
def _scroll_to_bottom(self):
"""Scroll vers le bas de la zone de logs."""
scrollbar = self.scroll_area.verticalScrollBar()
scrollbar.setValue(scrollbar.maximum())
def add_log(self, message: str, emoji: str = "", level: str = "info"):
"""
Ajoute un log avec timestamp.
Args:
message: Message à afficher
emoji: Emoji à afficher (défaut: )
level: Niveau du log ('info', 'success', 'warning', 'error')
Examples:
>>> panel = LogsPanel()
>>> panel.add_log("J'observe vos actions", "👀", "info")
>>> panel.add_log("Pattern détecté", "🎯", "success")
"""
# Vérifier si on doit auto-scroll
should_auto_scroll = self._is_scrolled_to_bottom()
# Créer le message de log
log_msg = LogMessage(
timestamp=datetime.now(),
emoji=emoji,
message=message,
level=level
)
# Ajouter à la liste
self.logs.append(log_msg)
# Supprimer le message vide si c'est le premier log
if len(self.logs) == 1:
self._remove_empty_message()
# Supprimer les logs les plus anciens si on dépasse la limite
while len(self.logs) > self.max_logs:
removed_log = self.logs.pop(0)
# Supprimer le widget correspondant
if len(self.log_labels) > 0:
old_label = self.log_labels.pop(0)
self.content_layout.removeWidget(old_label)
old_label.deleteLater()
# Créer le widget pour ce log
log_label = self._create_log_label(log_msg)
self.log_labels.append(log_label)
# Insérer avant le stretch (qui est toujours en dernier)
insert_position = self.content_layout.count() - 1
self.content_layout.insertWidget(insert_position, log_label)
# Auto-scroll si on était déjà en bas
if should_auto_scroll:
# Utiliser un timer pour s'assurer que le layout est mis à jour
QTimer.singleShot(10, self._scroll_to_bottom)
# Émettre le signal
self.log_added.emit(message)
def _create_log_label(self, log_msg: LogMessage) -> QLabel:
"""
Crée un widget QLabel pour un message de log.
Args:
log_msg: Message de log à afficher
Returns:
QLabel formaté pour le log
"""
# Formater le timestamp (HH:MM)
time_str = log_msg.timestamp.strftime("%H:%M")
# Créer le texte complet
text = f"{time_str} {log_msg.emoji} {log_msg.message}"
# Créer le label
label = QLabel(text)
label.setFont(QFont("Arial", 9))
label.setWordWrap(True)
label.setTextInteractionFlags(Qt.TextSelectableByMouse)
# Couleurs selon le niveau
level_colors = {
"info": "#333",
"success": "#4CAF50",
"warning": "#FF9800",
"error": "#f44336"
}
level_bg_colors = {
"info": "#f9f9f9",
"success": "#E8F5E9",
"warning": "#FFF3E0",
"error": "#FFEBEE"
}
color = level_colors.get(log_msg.level, level_colors["info"])
bg_color = level_bg_colors.get(log_msg.level, level_bg_colors["info"])
label.setStyleSheet(f"""
QLabel {{
color: {color};
background-color: {bg_color};
padding: 6px 8px;
border-radius: 4px;
border-left: 3px solid {color};
}}
""")
return label
def clear(self):
"""Efface tous les logs."""
# Supprimer tous les widgets
for label in self.log_labels:
self.content_layout.removeWidget(label)
label.deleteLater()
# Réinitialiser les listes
self.logs.clear()
self.log_labels.clear()
# Afficher le message vide
self._show_empty_message()
def get_logs(self) -> List[LogMessage]:
"""
Retourne l'historique des logs.
Returns:
Liste des messages de log
"""
return self.logs.copy()
def get_log_count(self) -> int:
"""
Retourne le nombre de logs actuellement affichés.
Returns:
Nombre de logs
"""
return len(self.logs)
def get_last_log(self) -> Optional[LogMessage]:
"""
Retourne le dernier log ajouté.
Returns:
Dernier message de log ou None si aucun log
"""
if len(self.logs) > 0:
return self.logs[-1]
return None
if __name__ == "__main__":
"""Test du LogsPanel"""
import sys
from PyQt5.QtWidgets import QApplication, QMainWindow
app = QApplication(sys.argv)
# Créer une fenêtre de test
window = QMainWindow()
window.setWindowTitle("Test LogsPanel")
window.setGeometry(100, 100, 400, 500)
# Créer le panneau de logs
logs_panel = LogsPanel()
window.setCentralWidget(logs_panel)
# Ajouter quelques logs de test
logs_panel.add_log("J'observe vos actions dans Calculator", "👀", "info")
logs_panel.add_log("Tiens ! Vous avez fait 3 fois la même chose", "🎯", "success")
logs_panel.add_log("J'apprends: Calculer 9/9 (5 observations)", "📚", "info")
logs_panel.add_log("Mode Suggestions activé", "", "success")
logs_panel.add_log("Prêt à suggérer: Calculer 9/9", "💡", "info")
# Ajouter un log d'erreur
logs_panel.add_log("Impossible de se connecter - Calculator", "⚠️", "error")
# Ajouter un log d'avertissement
logs_panel.add_log("Application non autorisée", "⚠️", "warning")
# Test: Ajouter beaucoup de logs pour tester la limite de 30
print(f"Nombre de logs avant test: {logs_panel.get_log_count()}")
for i in range(25):
logs_panel.add_log(f"Test log #{i+8}", "📝", "info")
print(f"Nombre de logs après ajout de 25: {logs_panel.get_log_count()}")
print(f"Limite max: {logs_panel.max_logs}")
# Vérifier que le dernier log est bien le dernier ajouté
last_log = logs_panel.get_last_log()
if last_log:
print(f"Dernier log: {last_log.message}")
window.show()
sys.exit(app.exec_())