mirror of
https://github.com/Ladebeze66/llm_lab_perso.git
synced 2025-12-15 19:36:51 +01:00
980 lines
40 KiB
Python
980 lines
40 KiB
Python
"""
|
|
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('<<ListboxSelect>>', 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("<<ComboboxSelected>>", 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(
|
|
"<Configure>",
|
|
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('<<ListboxSelect>>', 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("<Shift-Return>", 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() |