"""
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.")