""" ChatWidget - Interfaccia chat per conversazioni con Marco Aurelio """ from PyQt6.QtWidgets import ( QWidget, QVBoxLayout, QHBoxLayout, QTextEdit, QLineEdit, QPushButton, QLabel, QMessageBox ) from PyQt6.QtCore import Qt from PyQt6.QtGui import QTextCursor from desktop.ui.inference_worker import InferenceWorker from desktop.ui.greeting_worker import GreetingWorker import os class ChatWidget(QWidget): """Widget per la chat con Marco Aurelio""" def __init__(self, kernel): super().__init__() self.kernel = kernel self.worker = None # Worker thread corrente per query utente self.greeting_worker = None # Worker thread per generazione saluto # Session ID con prefix profilo per isolamento memoria self.session_id = f"{self.kernel.active_profile}_desktop_session" # Metadata assistente dinamico basato su profilo self.assistant_metadata = { 'aurelio': {'name': 'Marco Aurelio', 'icon': '🏛️', 'color': '#4CAF50'}, 'warren': {'name': 'Warren Mentor', 'icon': '💰', 'color': '#006400'} } profile_meta = self.assistant_metadata.get( self.kernel.active_profile, {'name': self.kernel.active_profile.capitalize(), 'icon': '🤖', 'color': '#666'} ) self.assistant_name = profile_meta['name'] self.assistant_icon = profile_meta['icon'] self.assistant_color = profile_meta['color'] self.init_ui() # Abilita drag & drop self.setAcceptDrops(True) # Genera saluto dinamico DOPO init_ui() self.generate_dynamic_greeting() def init_ui(self): """Inizializza l'interfaccia utente""" layout = QVBoxLayout() # Header dinamico basato su profilo profile_names = { 'aurelio': 'Marco Aurelio', 'warren': 'Warren Mentor' } profile_display = profile_names.get(self.kernel.active_profile, self.kernel.active_profile.capitalize()) header = QLabel(f"💬 Conversazione con {profile_display}") header.setStyleSheet("font-size: 16px; font-weight: bold; padding: 10px;") layout.addWidget(header) # Area di testo per la conversazione (read-only) self.chat_display = QTextEdit() self.chat_display.setReadOnly(True) profile_placeholders = { 'aurelio': "Le tue conversazioni con l'Imperatore Filosofo appariranno qui...\n\nSuggerimento: Puoi trascinare file PDF qui per indicizzarli!", 'warren': "Le tue conversazioni con Warren Mentor appariranno qui...\n\nSuggerimento: Puoi trascinare file PDF qui per indicizzarli!" } placeholder = profile_placeholders.get(self.kernel.active_profile, "Le tue conversazioni appariranno qui...") self.chat_display.setPlaceholderText(placeholder) layout.addWidget(self.chat_display) # Area input utente input_layout = QHBoxLayout() self.input_field = QLineEdit() profile_input_placeholders = { 'aurelio': "Scrivi la tua domanda a Marco Aurelio...", 'warren': "Scrivi la tua domanda a Warren..." } input_placeholder = profile_input_placeholders.get(self.kernel.active_profile, "Scrivi la tua domanda...") self.input_field.setPlaceholderText(input_placeholder) self.input_field.returnPressed.connect(self.send_message) input_layout.addWidget(self.input_field) self.send_button = QPushButton("Invia") self.send_button.clicked.connect(self.send_message) input_layout.addWidget(self.send_button) layout.addLayout(input_layout) # Bottoni azioni actions_layout = QHBoxLayout() clear_btn = QPushButton("🗑️ Pulisci Chat") clear_btn.clicked.connect(self.clear_chat) actions_layout.addWidget(clear_btn) actions_layout.addStretch() layout.addLayout(actions_layout) self.setLayout(layout) # Il messaggio di benvenuto sarà generato dinamicamente da generate_dynamic_greeting() def send_message(self): """Invia il messaggio a Marco Aurelio""" user_input = self.input_field.text().strip() if not user_input: return # Mostra messaggio utente self.add_message("Tu", user_input) self.input_field.clear() # Disabilita input durante l'elaborazione self.set_input_enabled(False) self.add_message(self.assistant_name, "...[riflettendo]...", is_temp=True) # Avvia worker thread self.worker = InferenceWorker(self.kernel, user_input, self.session_id) self.worker.finished.connect(self.on_response_received) self.worker.error.connect(self.on_error) self.worker.start() def on_response_received(self, answer): """Gestisce la risposta ricevuta""" # Rimuovi messaggio temporaneo self.remove_temp_message() # Aggiungi risposta self.add_message(self.assistant_name, answer) # Riabilita input self.set_input_enabled(True) self.input_field.setFocus() def on_error(self, error_msg): """Gestisce errori durante l'inferenza""" self.remove_temp_message() self.add_message("Sistema", f"⚠️ {error_msg}") self.set_input_enabled(True) def add_message(self, sender, message, is_temp=False): """Aggiunge un messaggio alla chat""" cursor = self.chat_display.textCursor() cursor.movePosition(QTextCursor.MoveOperation.End) # Formatta il messaggio if sender == "Tu": formatted = f"
🧑 {sender}: {message}
" elif sender == self.assistant_name: # Ritorno a capo prima della risposta per migliore leggibilità (stile CLI) # Converti \n in
per rispettare gli a capo nel testo message_html = message.replace('\n', '
') formatted = f"
{self.assistant_icon} {sender}:
{message_html}
" else: formatted = f"
{sender}: {message}
" cursor.insertHtml(formatted) self.chat_display.setTextCursor(cursor) self.chat_display.ensureCursorVisible() def remove_temp_message(self): """Rimuove l'ultimo messaggio temporaneo COMPLETO (etichetta + testo)""" # Recupera il testo HTML html = self.chat_display.toHtml() # Lista di messaggi temporanei da cercare temp_messages = ["...[riflettendo]...", "...[preparando il benvenuto]..."] for temp_msg in temp_messages: if temp_msg in html: # Trova il placeholder idx = html.rfind(temp_msg) if idx != -1: # PyQt6 genera HTML:
LABEL
TEXT
# Dobbiamo trovare il SECONDO
all'indietro (quello prima dell'etichetta) # Primo
all'indietro (tra etichetta e testo) first_br = html.rfind("
", 0, idx) # Secondo
all'indietro (prima dell'etichetta) = inizio messaggio if first_br != -1: label_start = html.rfind("
", 0, first_br) else: label_start = -1 # Trova il
finale dopo il messaggio end_tag = html.find("
", idx) if end_tag != -1: end = end_tag + 6 # +6 per includere
if label_start != -1 and end != -1: # Rimuove TUTTO:
LABEL
TEXT
html = html[:label_start] + html[end:] self.chat_display.setHtml(html) break # Rimuovi solo il primo trovato def clear_chat(self): """Pulisce la cronologia chat""" reply = QMessageBox.question( self, "Conferma", "Vuoi davvero pulire la cronologia della chat?", QMessageBox.StandardButton.Yes | QMessageBox.StandardButton.No ) if reply == QMessageBox.StandardButton.Yes: self.chat_display.clear() self.add_message(self.assistant_name, "La conversazione è stata reimpostata. Ricominciamo.") def set_input_enabled(self, enabled): """Abilita/disabilita l'input durante elaborazione""" self.input_field.setEnabled(enabled) self.send_button.setEnabled(enabled) def generate_dynamic_greeting(self): """Genera e mostra saluto dinamico usando Claude con consapevolezza delle conversazioni precedenti""" # Mostra messaggio temporaneo self.add_message(self.assistant_name, "...[preparando il benvenuto]...") # Avvia worker per generazione saluto self.greeting_worker = GreetingWorker(self.kernel, self.session_id) self.greeting_worker.finished.connect(self.on_greeting_received) self.greeting_worker.error.connect(self.on_greeting_error) self.greeting_worker.start() def on_greeting_received(self, greeting): """Gestisce saluto generato da Claude""" # Rimuovi messaggio temporaneo self.remove_temp_message() # Aggiungi saluto reale generato da Claude self.add_message(self.assistant_name, greeting) def on_greeting_error(self, error_msg): """Fallback a saluto statico in caso di errore durante la generazione""" self.remove_temp_message() # Saluto statico di emergenza fallback_greeting = ( "Ave, anima curiosa. Sono Marco Aurelio Antonino, " "Imperatore di Roma e filosofo stoico.\n\n" "Poni le tue domande sulla virtù, la ragione, e sulla vita saggia. " "Sarò lieto di condividere la mia saggezza con te." ) self.add_message(self.assistant_name, fallback_greeting) # Log errore print(f"[CHAT] Errore saluto dinamico: {error_msg}. Uso fallback statico.")