""" Interface graphique pour le chat avec les modèles LLM """ import tkinter as tk from tkinter import ttk, scrolledtext, messagebox, filedialog import json import os from datetime import datetime from utils.agent_manager import AgentManager from core.factory import LLMFactory class ChatUI: """Interface graphique pour interagir avec les agents LLM""" def __init__(self, root): """Initialise l'interface graphique""" self.root = root self.root.title("LLM Lab - Chat") self.root.geometry("1000x700") self.root.minsize(800, 600) # Variables pour le chat self.current_agent = None self.conversation_history = [] self.custom_params = {} self.history_files = [] # Style self.style = ttk.Style() self.style.theme_use('alt') # 'clam', 'alt', 'default', 'classic' # Création de l'interface self._create_ui() # Chargement de la liste des agents self._load_agents() # Chargement de l'historique des conversations self._load_conversation_history() def _create_ui(self): """Crée l'interface utilisateur""" # Interface principale en deux parties self.main_paned = ttk.PanedWindow(self.root, orient=tk.HORIZONTAL) self.main_paned.pack(fill=tk.BOTH, expand=True, padx=5, pady=5) # Panneau de gauche : configuration et historique self.left_frame = ttk.Frame(self.main_paned, width=300) self.main_paned.add(self.left_frame, weight=1) # Panneau de droite : chat self.right_frame = ttk.Frame(self.main_paned) self.main_paned.add(self.right_frame, weight=3) # Configuration du panneau de gauche self._create_config_panel() # Configuration du panneau de droite self._create_chat_panel() def _create_config_panel(self): """Crée le panneau de configuration (gauche)""" # Utilisation d'un notebook pour organiser les paramètres self.config_notebook = ttk.Notebook(self.left_frame) self.config_notebook.pack(fill=tk.BOTH, expand=True, padx=5, pady=5) # Onglet 1: Sélection d'agent self.agent_frame = ttk.Frame(self.config_notebook) self.config_notebook.add(self.agent_frame, text="Agent") # Onglet 2: Paramètres self.params_frame = ttk.Frame(self.config_notebook) self.config_notebook.add(self.params_frame, text="Paramètres") # Onglet 3: Historique self.history_frame = ttk.Frame(self.config_notebook) self.config_notebook.add(self.history_frame, text="Historique") # Remplissage de l'onglet Agent self._create_agent_tab() # Remplissage de l'onglet Paramètres self._create_params_tab() # Remplissage de l'onglet Historique self._create_history_tab() def _create_agent_tab(self): """Crée l'onglet de sélection d'agent""" # Label et liste des agents ttk.Label(self.agent_frame, text="Sélectionnez un agent:", font=("Arial", 11, "bold")).pack(pady=(10, 5), padx=5, anchor=tk.W) # Frame pour la liste des agents agent_list_frame = ttk.Frame(self.agent_frame) agent_list_frame.pack(fill=tk.BOTH, expand=True, padx=5, pady=5) # Scrollbar scrollbar = ttk.Scrollbar(agent_list_frame) scrollbar.pack(side=tk.RIGHT, fill=tk.Y) # Liste des agents self.agent_listbox = tk.Listbox(agent_list_frame, yscrollcommand=scrollbar.set, selectmode=tk.SINGLE, activestyle='dotbox', font=("Arial", 10)) self.agent_listbox.pack(fill=tk.BOTH, expand=True) scrollbar.config(command=self.agent_listbox.yview) # Gestionnaire d'événements pour la sélection d'agent self.agent_listbox.bind('<>', self._on_agent_select) # Informations sur l'agent agent_info_frame = ttk.LabelFrame(self.agent_frame, text="Informations sur l'agent") agent_info_frame.pack(fill=tk.X, padx=5, pady=5) # Description de l'agent ttk.Label(agent_info_frame, text="Description:").pack(anchor=tk.W, padx=5, pady=(5, 0)) self.agent_description = tk.Text(agent_info_frame, wrap=tk.WORD, height=5, width=30, font=("Arial", 9)) self.agent_description.pack(fill=tk.X, padx=5, pady=5) self.agent_description.config(state=tk.DISABLED) # Modèle utilisé model_frame = ttk.Frame(agent_info_frame) model_frame.pack(fill=tk.X, padx=5, pady=(0, 5)) ttk.Label(model_frame, text="Modèle:").pack(side=tk.LEFT, padx=(0, 5)) self.model_label = ttk.Label(model_frame, text="-") self.model_label.pack(side=tk.LEFT) # Bouton pour changer de modèle self.model_combo = ttk.Combobox(self.agent_frame, state="readonly") self.model_combo.pack(fill=tk.X, padx=5, pady=5) # Événement de changement self.model_combo.bind("<>", self._on_model_change) # Bouton de test test_btn = ttk.Button(self.agent_frame, text="Tester l'agent", command=self._test_agent) test_btn.pack(fill=tk.X, padx=5, pady=(0, 10)) def _create_params_tab(self): """Crée l'onglet de configuration des paramètres""" # Canvas et scrollbar pour permettre le défilement canvas = tk.Canvas(self.params_frame) scrollbar = ttk.Scrollbar(self.params_frame, orient="vertical", command=canvas.yview) scroll_frame = ttk.Frame(canvas) scroll_frame.bind( "", lambda e: canvas.configure(scrollregion=canvas.bbox("all")) ) canvas.create_window((0, 0), window=scroll_frame, anchor="nw") canvas.configure(yscrollcommand=scrollbar.set) canvas.pack(side="left", fill="both", expand=True) scrollbar.pack(side="right", fill="y") # Titre ttk.Label(scroll_frame, text="Paramètres du modèle:", font=("Arial", 11, "bold")).pack(pady=(10, 5), padx=5, anchor=tk.W) # Température temp_frame = ttk.Frame(scroll_frame) temp_frame.pack(fill=tk.X, padx=5, pady=5) ttk.Label(temp_frame, text="Température:").pack(side=tk.LEFT, padx=(0, 5)) self.temp_var = tk.DoubleVar(value=0.7) temp_scale = ttk.Scale(temp_frame, from_=0.0, to=2.0, orient=tk.HORIZONTAL, variable=self.temp_var, length=150) temp_scale.pack(side=tk.LEFT, fill=tk.X, expand=True, padx=(0, 5)) temp_label = ttk.Label(temp_frame, textvariable=self.temp_var, width=5) temp_label.pack(side=tk.LEFT) self.temp_var.trace_add("write", self._on_param_change) # Top-p top_p_frame = ttk.Frame(scroll_frame) top_p_frame.pack(fill=tk.X, padx=5, pady=5) ttk.Label(top_p_frame, text="Top-p:").pack(side=tk.LEFT, padx=(0, 5)) self.top_p_var = tk.DoubleVar(value=0.9) top_p_scale = ttk.Scale(top_p_frame, from_=0.0, to=1.0, orient=tk.HORIZONTAL, variable=self.top_p_var, length=150) top_p_scale.pack(side=tk.LEFT, fill=tk.X, expand=True, padx=(0, 5)) top_p_label = ttk.Label(top_p_frame, textvariable=self.top_p_var, width=5) top_p_label.pack(side=tk.LEFT) self.top_p_var.trace_add("write", self._on_param_change) # Top-k top_k_frame = ttk.Frame(scroll_frame) top_k_frame.pack(fill=tk.X, padx=5, pady=5) ttk.Label(top_k_frame, text="Top-k:").pack(side=tk.LEFT, padx=(0, 5)) self.top_k_var = tk.IntVar(value=40) top_k_scale = ttk.Scale(top_k_frame, from_=0, to=100, orient=tk.HORIZONTAL, variable=self.top_k_var, length=150) top_k_scale.pack(side=tk.LEFT, fill=tk.X, expand=True, padx=(0, 5)) top_k_label = ttk.Label(top_k_frame, textvariable=self.top_k_var, width=5) top_k_label.pack(side=tk.LEFT) self.top_k_var.trace_add("write", self._on_param_change) # Repeat penalty repeat_frame = ttk.Frame(scroll_frame) repeat_frame.pack(fill=tk.X, padx=5, pady=5) ttk.Label(repeat_frame, text="Repeat Penalty:").pack(side=tk.LEFT, padx=(0, 5)) self.repeat_var = tk.DoubleVar(value=1.1) repeat_scale = ttk.Scale(repeat_frame, from_=1.0, to=2.0, orient=tk.HORIZONTAL, variable=self.repeat_var, length=150) repeat_scale.pack(side=tk.LEFT, fill=tk.X, expand=True, padx=(0, 5)) repeat_label = ttk.Label(repeat_frame, textvariable=self.repeat_var, width=5) repeat_label.pack(side=tk.LEFT) self.repeat_var.trace_add("write", self._on_param_change) # Tokens max tokens_frame = ttk.Frame(scroll_frame) tokens_frame.pack(fill=tk.X, padx=5, pady=5) ttk.Label(tokens_frame, text="Tokens max:").pack(side=tk.LEFT, padx=(0, 5)) self.tokens_var = tk.IntVar(value=512) tokens_scale = ttk.Scale(tokens_frame, from_=32, to=4096, orient=tk.HORIZONTAL, variable=self.tokens_var, length=150) tokens_scale.pack(side=tk.LEFT, fill=tk.X, expand=True, padx=(0, 5)) tokens_label = ttk.Label(tokens_frame, textvariable=self.tokens_var, width=5) tokens_label.pack(side=tk.LEFT) self.tokens_var.trace_add("write", self._on_param_change) # Boutons de gestion buttons_frame = ttk.Frame(scroll_frame) buttons_frame.pack(fill=tk.X, padx=5, pady=10) self.reset_btn = ttk.Button(buttons_frame, text="Réinitialiser", command=self._reset_params) self.reset_btn.pack(side=tk.LEFT, padx=(0, 5)) self.save_btn = ttk.Button(buttons_frame, text="Enregistrer", command=self._save_params) self.save_btn.pack(side=tk.LEFT) # Séparateur ttk.Separator(scroll_frame, orient=tk.HORIZONTAL).pack(fill=tk.X, padx=5, pady=10) # Système de presets ttk.Label(scroll_frame, text="Presets:", font=("Arial", 11, "bold")).pack(pady=(5, 5), padx=5, anchor=tk.W) presets_frame = ttk.Frame(scroll_frame) presets_frame.pack(fill=tk.X, padx=5, pady=5) self.preset_combo = ttk.Combobox(presets_frame, values=[ "Créatif", "Précis", "Équilibré", "Code", "Conversation" ]) self.preset_combo.pack(side=tk.LEFT, fill=tk.X, expand=True, padx=(0, 5)) self.preset_combo.current(2) # Par défaut: Équilibré self.apply_preset_btn = ttk.Button(presets_frame, text="Appliquer", command=self._apply_preset) self.apply_preset_btn.pack(side=tk.LEFT) def _create_history_tab(self): """Crée l'onglet d'historique des conversations""" # Label ttk.Label(self.history_frame, text="Historique des conversations:", font=("Arial", 11, "bold")).pack(pady=(10, 5), padx=5, anchor=tk.W) # Liste des conversations history_list_frame = ttk.Frame(self.history_frame) history_list_frame.pack(fill=tk.BOTH, expand=True, padx=5, pady=5) scrollbar = ttk.Scrollbar(history_list_frame) scrollbar.pack(side=tk.RIGHT, fill=tk.Y) self.history_listbox = tk.Listbox(history_list_frame, yscrollcommand=scrollbar.set, font=("Arial", 10)) self.history_listbox.pack(fill=tk.BOTH, expand=True) scrollbar.config(command=self.history_listbox.yview) # Événement de sélection self.history_listbox.bind('<>', self._on_history_select) # Boutons button_frame = ttk.Frame(self.history_frame) button_frame.pack(fill=tk.X, padx=5, pady=(0, 10)) self.load_history_btn = ttk.Button(button_frame, text="Charger", command=self._load_selected_history) self.load_history_btn.pack(side=tk.LEFT, padx=(0, 5)) self.delete_history_btn = ttk.Button(button_frame, text="Supprimer", command=self._delete_history) self.delete_history_btn.pack(side=tk.LEFT, padx=(0, 5)) self.export_history_btn = ttk.Button(button_frame, text="Exporter", command=self._export_history) self.export_history_btn.pack(side=tk.LEFT) def _create_chat_panel(self): """Crée le panneau de chat (droite)""" # Titre chat_title_frame = ttk.Frame(self.right_frame) chat_title_frame.pack(fill=tk.X, padx=5, pady=5) self.chat_title = ttk.Label(chat_title_frame, text="Chat - Aucun agent sélectionné", font=("Arial", 12, "bold")) self.chat_title.pack(side=tk.LEFT) # Zone de chat chat_frame = ttk.Frame(self.right_frame) chat_frame.pack(fill=tk.BOTH, expand=True, padx=5, pady=5) # Zone d'affichage des messages self.chat_display = scrolledtext.ScrolledText(chat_frame, wrap=tk.WORD, font=("Arial", 10)) self.chat_display.pack(fill=tk.BOTH, expand=True) self.chat_display.config(state=tk.DISABLED) # Zone de saisie input_frame = ttk.Frame(self.right_frame) input_frame.pack(fill=tk.X, padx=5, pady=(0, 5)) self.user_input = scrolledtext.ScrolledText(input_frame, wrap=tk.WORD, height=3, font=("Arial", 10)) self.user_input.pack(fill=tk.X, pady=(0, 5)) self.user_input.bind("", self._on_shift_enter) # Boutons d'action button_frame = ttk.Frame(self.right_frame) button_frame.pack(fill=tk.X, padx=5, pady=(0, 5)) self.send_btn = ttk.Button(button_frame, text="Envoyer", width=10, command=self._send_message) self.send_btn.pack(side=tk.RIGHT, padx=(5, 0)) self.clear_btn = ttk.Button(button_frame, text="Effacer", width=10, command=self._clear_chat) self.clear_btn.pack(side=tk.RIGHT, padx=(5, 0)) self.new_chat_btn = ttk.Button(button_frame, text="Nouvelle conversation", width=20, command=self._new_chat) self.new_chat_btn.pack(side=tk.RIGHT) # Barre d'état self.status_bar = ttk.Label(self.right_frame, text="Prêt", relief=tk.SUNKEN, anchor=tk.W) self.status_bar.pack(side=tk.BOTTOM, fill=tk.X) def _load_agents(self): """Charge la liste des agents disponibles""" # Effacer la liste actuelle self.agent_listbox.delete(0, tk.END) # Récupérer les agents agents_info = AgentManager.list_agents() # Ajouter les agents à la liste for agent_name in agents_info: self.agent_listbox.insert(tk.END, agent_name) # Charger la liste des modèles disponibles self._load_models() def _load_models(self): """Charge la liste des modèles disponibles""" try: # Récupérer les modèles disponibles models = LLMFactory.get_available_models() self.model_combo['values'] = models except Exception as e: print(f"Erreur lors du chargement des modèles: {str(e)}") self.model_combo['values'] = ["mistral:latest", "codellama:13b-python", "llama2:13b"] def _on_agent_select(self, event): """Gestionnaire d'événement pour la sélection d'un agent""" # Récupérer l'index sélectionné selection = self.agent_listbox.curselection() if not selection: return # Récupérer le nom de l'agent agent_name = self.agent_listbox.get(selection[0]) # Récupérer les informations de l'agent agents_info = AgentManager.list_agents() if agent_name not in agents_info: return agent_info = agents_info[agent_name] # Mettre à jour les informations affichées self.agent_description.config(state=tk.NORMAL) self.agent_description.delete(1.0, tk.END) self.agent_description.insert(tk.END, agent_info['description']) self.agent_description.config(state=tk.DISABLED) # Mettre à jour le modèle model_name = agent_info['model'] self.model_label.config(text=model_name) # Sélectionner le modèle dans la combobox if model_name in self.model_combo['values']: self.model_combo.set(model_name) # Mettre à jour le titre du chat self.chat_title.config(text=f"Chat avec {agent_name} ({model_name})") # Mettre à jour l'agent courant self.current_agent = agent_name # Charger les paramètres de l'agent self._load_agent_params(agent_name) # Mettre à jour le statut self.status_bar.config(text=f"Agent {agent_name} sélectionné avec le modèle {model_name}") def _on_model_change(self, event): """Gestionnaire d'événement pour le changement de modèle""" if not self.current_agent: messagebox.showwarning("Aucun agent sélectionné", "Veuillez d'abord sélectionner un agent.") return # Récupérer le modèle sélectionné model_name = self.model_combo.get() # Mettre à jour l'affichage self.model_label.config(text=model_name) # Mettre à jour le titre du chat self.chat_title.config(text=f"Chat avec {self.current_agent} ({model_name})") # Mettre à jour les paramètres du modèle self._load_model_params(model_name) # Mettre à jour le statut self.status_bar.config(text=f"Modèle changé pour {model_name}") def _test_agent(self): """Fonction de test rapide de l'agent sélectionné""" if not self.current_agent: messagebox.showwarning("Aucun agent sélectionné", "Veuillez d'abord sélectionner un agent.") return # Message de test test_message = "Présente-toi brièvement et explique tes capacités." # Ajouter le message au chat self._add_message_to_chat("Utilisateur", test_message) # Générer une réponse try: # Mettre à jour le statut self.status_bar.config(text=f"Génération en cours...") self.root.update() # Créer l'agent avec les paramètres personnalisés agent = self._create_agent_with_custom_params() # Générer la réponse response = agent.generate(test_message) # Ajouter la réponse au chat self._add_message_to_chat(self.current_agent, response) # Mettre à jour le statut self.status_bar.config(text=f"Test terminé") except Exception as e: messagebox.showerror("Erreur", f"Erreur lors du test de l'agent: {str(e)}") self.status_bar.config(text=f"Erreur lors du test") def _on_param_change(self, *args): """Gestionnaire d'événement pour le changement des paramètres""" if not self.current_agent: return # Mettre à jour les paramètres personnalisés self.custom_params = { "temperature": self.temp_var.get(), "top_p": self.top_p_var.get(), "top_k": self.top_k_var.get(), "repeat_penalty": self.repeat_var.get(), "num_predict": self.tokens_var.get() } def _reset_params(self): """Réinitialise les paramètres aux valeurs par défaut de l'agent""" if not self.current_agent: messagebox.showwarning("Aucun agent sélectionné", "Veuillez d'abord sélectionner un agent.") return # Charger les paramètres de l'agent self._load_agent_params(self.current_agent) # Mettre à jour le statut self.status_bar.config(text=f"Paramètres réinitialisés") def _save_params(self): """Enregistre les paramètres personnalisés""" if not self.current_agent: messagebox.showwarning("Aucun agent sélectionné", "Veuillez d'abord sélectionner un agent.") return # Créer le dossier de sauvegarde si nécessaire save_dir = "saved_params" os.makedirs(save_dir, exist_ok=True) # Nom du fichier de sauvegarde file_name = f"{save_dir}/{self.current_agent}_{self.model_combo.get()}.json" # Enregistrer les paramètres with open(file_name, "w") as f: json.dump(self.custom_params, f, indent=2) # Mettre à jour le statut self.status_bar.config(text=f"Paramètres enregistrés dans {file_name}") def _apply_preset(self): """Applique un preset de paramètres""" preset = self.preset_combo.get() # Définir les presets presets = { "Créatif": { "temperature": 1.2, "top_p": 0.95, "top_k": 60, "repeat_penalty": 1.0, "num_predict": 1024 }, "Précis": { "temperature": 0.3, "top_p": 0.85, "top_k": 30, "repeat_penalty": 1.2, "num_predict": 512 }, "Équilibré": { "temperature": 0.7, "top_p": 0.9, "top_k": 40, "repeat_penalty": 1.1, "num_predict": 768 }, "Code": { "temperature": 0.2, "top_p": 0.95, "top_k": 30, "repeat_penalty": 1.2, "num_predict": 2048 }, "Conversation": { "temperature": 0.8, "top_p": 0.92, "top_k": 50, "repeat_penalty": 1.05, "num_predict": 512 } } # Appliquer le preset if preset in presets: params = presets[preset] # Mettre à jour les variables self.temp_var.set(params["temperature"]) self.top_p_var.set(params["top_p"]) self.top_k_var.set(params["top_k"]) self.repeat_var.set(params["repeat_penalty"]) self.tokens_var.set(params["num_predict"]) # Mettre à jour les paramètres personnalisés self.custom_params = params.copy() # Mettre à jour le statut self.status_bar.config(text=f"Preset '{preset}' appliqué") def _load_agent_params(self, agent_name): """Charge les paramètres par défaut de l'agent""" try: # Récupérer la configuration de l'agent from agents.roles import AGENTS if agent_name in AGENTS: agent_config = AGENTS[agent_name] params = agent_config.get("params", {}) # Mettre à jour les variables avec les valeurs par défaut self.temp_var.set(params.get("temperature", 0.7)) self.top_p_var.set(params.get("top_p", 0.9)) self.top_k_var.set(params.get("top_k", 40)) self.repeat_var.set(params.get("repeat_penalty", 1.1)) self.tokens_var.set(params.get("num_predict", 512)) # Mettre à jour les paramètres personnalisés self.custom_params = params.copy() except Exception as e: print(f"Erreur lors du chargement des paramètres de l'agent: {str(e)}") def _load_model_params(self, model_name): """Charge les paramètres par défaut du modèle""" # Vérifier si un fichier de paramètres personnalisés existe save_dir = "saved_params" file_name = f"{save_dir}/{self.current_agent}_{model_name}.json" if os.path.exists(file_name): try: # Charger les paramètres personnalisés with open(file_name, "r") as f: params = json.load(f) # Mettre à jour les variables self.temp_var.set(params.get("temperature", 0.7)) self.top_p_var.set(params.get("top_p", 0.9)) self.top_k_var.set(params.get("top_k", 40)) self.repeat_var.set(params.get("repeat_penalty", 1.1)) self.tokens_var.set(params.get("num_predict", 512)) # Mettre à jour les paramètres personnalisés self.custom_params = params.copy() # Mettre à jour le statut self.status_bar.config(text=f"Paramètres personnalisés chargés depuis {file_name}") except Exception as e: print(f"Erreur lors du chargement des paramètres personnalisés: {str(e)}") else: # Si aucun fichier personnalisé, charger les paramètres par défaut self._load_agent_params(self.current_agent) def _create_agent_with_custom_params(self): """Crée un agent avec les paramètres personnalisés""" if not self.current_agent: raise ValueError("Aucun agent sélectionné") # Créer l'agent agent = AgentManager.create(self.current_agent) # Appliquer les paramètres personnalisés for key, value in self.custom_params.items(): agent.params[key] = value # Appliquer le modèle si différent model_name = self.model_combo.get() if model_name != agent.model: # Créer un nouveau modèle from core.factory import LLMFactory new_model = LLMFactory.create(model_name) # Copier les paramètres new_model.params.update(agent.params) new_model.system_prompt = agent.system_prompt new_model.agent = agent.agent agent = new_model return agent def _load_conversation_history(self): """Charge l'historique des conversations""" # Créer le dossier d'historique si nécessaire history_dir = "chat_history" os.makedirs(history_dir, exist_ok=True) # Lister les fichiers d'historique self.history_files = [] for file in os.listdir(history_dir): if file.endswith(".json"): self.history_files.append(os.path.join(history_dir, file)) # Trier par date (plus récent en premier) self.history_files.sort(key=os.path.getmtime, reverse=True) # Mettre à jour la liste d'historique self.history_listbox.delete(0, tk.END) for file in self.history_files: try: with open(file, "r", encoding="utf-8") as f: history = json.load(f) # Extraire les informations agent = history.get("agent", "Inconnu") date = history.get("date", "Inconnue") messages = history.get("messages", []) # Ajouter à la liste if messages: first_message = messages[0].get("content", "") preview = first_message[:30] + "..." if len(first_message) > 30 else first_message self.history_listbox.insert(tk.END, f"{agent} - {date} - {preview}") except Exception as e: print(f"Erreur lors du chargement de l'historique {file}: {str(e)}") def _on_history_select(self, event): """Gestionnaire d'événement pour la sélection d'un historique""" # Récupérer l'index sélectionné selection = self.history_listbox.curselection() if not selection: return # Activer le bouton de chargement self.load_history_btn.config(state=tk.NORMAL) def _load_selected_history(self): """Charge l'historique sélectionné""" # Récupérer l'index sélectionné selection = self.history_listbox.curselection() if not selection or selection[0] >= len(self.history_files): return # Récupérer le fichier d'historique file = self.history_files[selection[0]] try: # Charger l'historique with open(file, "r", encoding="utf-8") as f: history = json.load(f) # Extraire les informations agent = history.get("agent", None) messages = history.get("messages", []) # Sélectionner l'agent si disponible if agent: # Trouver l'index de l'agent dans la listbox for i in range(self.agent_listbox.size()): if self.agent_listbox.get(i) == agent: self.agent_listbox.selection_clear(0, tk.END) self.agent_listbox.selection_set(i) self.agent_listbox.see(i) self._on_agent_select(None) break # Effacer le chat actuel self._clear_chat() # Ajouter les messages au chat for message in messages: sender = message.get("sender", "Inconnu") content = message.get("content", "") self._add_message_to_chat(sender, content, add_to_history=False) # Mettre à jour l'historique self.conversation_history = messages # Mettre à jour le statut self.status_bar.config(text=f"Historique chargé depuis {file}") except Exception as e: messagebox.showerror("Erreur", f"Erreur lors du chargement de l'historique: {str(e)}") def _delete_history(self): """Supprime l'historique sélectionné""" # Récupérer l'index sélectionné selection = self.history_listbox.curselection() if not selection or selection[0] >= len(self.history_files): return # Récupérer le fichier d'historique file = self.history_files[selection[0]] # Demander confirmation if messagebox.askyesno("Confirmation", f"Voulez-vous vraiment supprimer cet historique ?"): try: # Supprimer le fichier os.remove(file) # Mettre à jour la liste self.history_files.pop(selection[0]) self.history_listbox.delete(selection[0]) # Mettre à jour le statut self.status_bar.config(text=f"Historique supprimé") except Exception as e: messagebox.showerror("Erreur", f"Erreur lors de la suppression: {str(e)}") def _export_history(self): """Exporte l'historique sélectionné""" # Récupérer l'index sélectionné selection = self.history_listbox.curselection() if not selection or selection[0] >= len(self.history_files): return # Récupérer le fichier d'historique file = self.history_files[selection[0]] try: # Charger l'historique with open(file, "r", encoding="utf-8") as f: history = json.load(f) # Ouvrir une boîte de dialogue pour choisir l'emplacement d'exportation export_file = filedialog.asksaveasfilename( defaultextension=".md", filetypes=[("Markdown", "*.md"), ("Texte", "*.txt"), ("JSON", "*.json")], title="Exporter l'historique" ) if not export_file: return # Format d'exportation selon l'extension ext = os.path.splitext(export_file)[1].lower() if ext == ".json": # Exporter au format JSON with open(export_file, "w", encoding="utf-8") as f: json.dump(history, f, indent=2, ensure_ascii=False) elif ext == ".md" or ext == ".txt": # Exporter au format Markdown/Texte with open(export_file, "w", encoding="utf-8") as f: # En-tête agent = history.get("agent", "Inconnu") date = history.get("date", "Inconnue") f.write(f"# Conversation avec {agent} - {date}\n\n") # Messages for message in history.get("messages", []): sender = message.get("sender", "Inconnu") content = message.get("content", "") if ext == ".md": f.write(f"## {sender}\n\n{content}\n\n") else: f.write(f"{sender}:\n{content}\n\n") # Mettre à jour le statut self.status_bar.config(text=f"Historique exporté vers {export_file}") except Exception as e: messagebox.showerror("Erreur", f"Erreur lors de l'exportation: {str(e)}") def _on_shift_enter(self, event): """Gestionnaire d'événement pour Shift+Enter""" # Ajouter un saut de ligne return def _send_message(self): """Envoie le message saisi par l'utilisateur""" if not self.current_agent: messagebox.showwarning("Aucun agent sélectionné", "Veuillez d'abord sélectionner un agent.") return # Récupérer le message message = self.user_input.get(1.0, tk.END).strip() if not message: return # Effacer le champ de saisie self.user_input.delete(1.0, tk.END) # Ajouter le message au chat self._add_message_to_chat("Utilisateur", message) # Générer une réponse try: # Mettre à jour le statut self.status_bar.config(text=f"Génération en cours...") self.root.update() # Créer l'agent avec les paramètres personnalisés agent = self._create_agent_with_custom_params() # Générer la réponse response = agent.generate(message) # Ajouter la réponse au chat self._add_message_to_chat(self.current_agent, response) # Mettre à jour le statut self.status_bar.config(text=f"Réponse générée") except Exception as e: messagebox.showerror("Erreur", f"Erreur lors de la génération: {str(e)}") self.status_bar.config(text=f"Erreur lors de la génération") def _add_message_to_chat(self, sender, content, add_to_history=True): """Ajoute un message au chat""" # Activer la zone de chat pour modification self.chat_display.config(state=tk.NORMAL) # Ajouter le message self.chat_display.insert(tk.END, f"{sender}:\n", "sender") self.chat_display.insert(tk.END, f"{content}\n\n", "message") # Configurer les tags self.chat_display.tag_configure("sender", font=("Arial", 10, "bold")) self.chat_display.tag_configure("message", font=("Arial", 10)) # Défiler vers le bas self.chat_display.see(tk.END) # Désactiver la zone de chat self.chat_display.config(state=tk.DISABLED) # Ajouter à l'historique if add_to_history: self.conversation_history.append({ "sender": sender, "content": content, "timestamp": datetime.now().isoformat() }) # Sauvegarder l'historique self._save_conversation_history() def _clear_chat(self): """Efface le contenu du chat""" # Effacer la zone de chat self.chat_display.config(state=tk.NORMAL) self.chat_display.delete(1.0, tk.END) self.chat_display.config(state=tk.DISABLED) # Ne pas effacer l'historique def _new_chat(self): """Démarre une nouvelle conversation""" # Demander confirmation si la conversation actuelle n'est pas vide if self.conversation_history and messagebox.askyesno( "Nouvelle conversation", "Voulez-vous démarrer une nouvelle conversation ?" ): # Effacer la zone de chat self._clear_chat() # Réinitialiser l'historique self.conversation_history = [] # Mettre à jour le statut self.status_bar.config(text=f"Nouvelle conversation démarrée") def _save_conversation_history(self): """Sauvegarde l'historique de la conversation actuelle""" if not self.conversation_history or not self.current_agent: return # Créer le dossier d'historique si nécessaire history_dir = "chat_history" os.makedirs(history_dir, exist_ok=True) # Créer un ID unique pour la conversation now = datetime.now() date_str = now.strftime("%Y-%m-%d_%H-%M-%S") file_name = f"{history_dir}/{self.current_agent}_{date_str}.json" # Préparer les données history_data = { "agent": self.current_agent, "model": self.model_combo.get(), "date": now.strftime("%Y-%m-%d %H:%M:%S"), "params": self.custom_params, "messages": self.conversation_history } # Sauvegarder l'historique try: with open(file_name, "w", encoding="utf-8") as f: json.dump(history_data, f, indent=2, ensure_ascii=False) # Mettre à jour la liste des fichiers d'historique if file_name not in self.history_files: self.history_files.insert(0, file_name) self.history_listbox.insert(0, f"{self.current_agent} - {now.strftime('%Y-%m-%d %H:%M:%S')}") except Exception as e: print(f"Erreur lors de la sauvegarde de l'historique: {str(e)}") def main(): """Point d'entrée principal""" root = tk.Tk() app = ChatUI(root) root.mainloop() if __name__ == "__main__": main()