Initial commit

This commit is contained in:
Dom
2026-03-05 00:20:25 +01:00
commit dcd4de9945
1954 changed files with 669380 additions and 0 deletions

347
geniusia2/gui/logs_panel.py Normal file
View File

@@ -0,0 +1,347 @@
"""
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_())