mirror of
https://github.com/Ladebeze66/ragflow_preprocess.git
synced 2026-02-04 05:30:26 +01:00
513 lines
22 KiB
Python
513 lines
22 KiB
Python
#!/usr/bin/env python3
|
|
# -*- coding: utf-8 -*-
|
|
|
|
"""
|
|
Panneau de configuration des agents LLM
|
|
"""
|
|
|
|
import os
|
|
import json
|
|
from PyQt6.QtWidgets import (QWidget, QVBoxLayout, QHBoxLayout, QLabel,
|
|
QPushButton, QComboBox, QTextEdit, QGroupBox,
|
|
QListWidget, QSplitter, QTabWidget, QSpinBox,
|
|
QDoubleSpinBox, QFormLayout, QMessageBox, QCheckBox,
|
|
QApplication)
|
|
from PyQt6.QtCore import Qt, QSize
|
|
|
|
class LLMConfigPanel(QWidget):
|
|
"""Panneau de configuration des agents LLM et de gestion des sélections"""
|
|
|
|
def __init__(self, parent):
|
|
super().__init__(parent)
|
|
self.parent = parent
|
|
self.current_selection = None
|
|
self.analysis_results = {}
|
|
|
|
self.init_ui()
|
|
self.load_llm_profiles()
|
|
|
|
def init_ui(self):
|
|
"""Initialise l'interface utilisateur du panneau"""
|
|
main_layout = QVBoxLayout(self)
|
|
|
|
# Tabs pour organiser les fonctionnalités
|
|
tabs = QTabWidget()
|
|
main_layout.addWidget(tabs)
|
|
|
|
# 1. Onglet Sélections
|
|
selections_tab = QWidget()
|
|
tab_layout = QVBoxLayout(selections_tab)
|
|
|
|
# Liste des sélections
|
|
selection_group = QGroupBox("Régions sélectionnées")
|
|
selection_layout = QVBoxLayout(selection_group)
|
|
|
|
self.selection_list = QListWidget()
|
|
self.selection_list.setMinimumHeight(100)
|
|
self.selection_list.currentRowChanged.connect(self.selection_changed)
|
|
selection_layout.addWidget(self.selection_list)
|
|
|
|
# Boutons pour la gestion des sélections
|
|
selection_btns = QHBoxLayout()
|
|
|
|
self.remove_btn = QPushButton("Supprimer")
|
|
self.remove_btn.clicked.connect(self.remove_selection)
|
|
selection_btns.addWidget(self.remove_btn)
|
|
|
|
selection_layout.addLayout(selection_btns)
|
|
tab_layout.addWidget(selection_group)
|
|
|
|
# Type de la sélection
|
|
type_group = QGroupBox("Type de contenu")
|
|
type_layout = QVBoxLayout(type_group)
|
|
|
|
self.type_combo = QComboBox()
|
|
self.type_combo.addItems(["schéma", "tableau", "formule", "texte", "autre"])
|
|
self.type_combo.currentTextChanged.connect(self.update_selection_type)
|
|
type_layout.addWidget(self.type_combo)
|
|
|
|
tab_layout.addWidget(type_group)
|
|
|
|
# Contexte textuel
|
|
context_group = QGroupBox("Contexte textuel")
|
|
context_layout = QVBoxLayout(context_group)
|
|
|
|
self.context_edit = QTextEdit()
|
|
self.context_edit.setPlaceholderText("Ajoutez ici le contexte pour cette sélection...")
|
|
self.context_edit.textChanged.connect(self.update_selection_context)
|
|
context_layout.addWidget(self.context_edit)
|
|
|
|
tab_layout.addWidget(context_group)
|
|
|
|
# 2. Onglet Agents LLM
|
|
agent_tab = QWidget()
|
|
agent_layout = QVBoxLayout(agent_tab)
|
|
|
|
# Mode d'analyse
|
|
mode_group = QGroupBox("Mode d'analyse")
|
|
mode_layout = QHBoxLayout(mode_group)
|
|
|
|
mode_label = QLabel("Niveau :")
|
|
mode_layout.addWidget(mode_label)
|
|
|
|
self.mode_combo = QComboBox()
|
|
self.mode_combo.addItems(["🔹 Léger", "⚪ Moyen", "🔸 Avancé"])
|
|
self.mode_combo.currentTextChanged.connect(self.update_mode)
|
|
mode_layout.addWidget(self.mode_combo)
|
|
|
|
agent_layout.addWidget(mode_group)
|
|
|
|
# Sélection des agents
|
|
agent_config_group = QGroupBox("Configuration des agents")
|
|
agent_config_layout = QVBoxLayout(agent_config_group)
|
|
|
|
# Sélection du type d'agent
|
|
agent_type_layout = QFormLayout()
|
|
|
|
# Agent Vision
|
|
agent_type_layout.addRow(QLabel("<b>Agent Vision</b>"))
|
|
|
|
self.vision_model_combo = QComboBox()
|
|
self.vision_model_combo.addItems(["llava:34b", "llava", "llama3.2-vision"])
|
|
agent_type_layout.addRow("Modèle:", self.vision_model_combo)
|
|
|
|
self.vision_lang_combo = QComboBox()
|
|
self.vision_lang_combo.addItems(["fr", "en"])
|
|
agent_type_layout.addRow("Langue:", self.vision_lang_combo)
|
|
|
|
# Agent de résumé/reformulation
|
|
agent_type_layout.addRow(QLabel("<b>Agent Résumé</b>"))
|
|
|
|
self.summary_model_combo = QComboBox()
|
|
self.summary_model_combo.addItems(["mistral", "deepseek-r1"])
|
|
agent_type_layout.addRow("Modèle:", self.summary_model_combo)
|
|
|
|
# Case à cocher pour activer/désactiver l'agent de résumé
|
|
self.summary_enabled_checkbox = QCheckBox("Activer l'agent de résumé")
|
|
self.summary_enabled_checkbox.setChecked(False) # Désactivé par défaut
|
|
agent_type_layout.addRow("", self.summary_enabled_checkbox)
|
|
|
|
# Agent de traduction
|
|
agent_type_layout.addRow(QLabel("<b>Agent Traduction</b>"))
|
|
|
|
self.translation_model_combo = QComboBox()
|
|
self.translation_model_combo.addItems(["mistral", "qwen2.5", "deepseek"])
|
|
agent_type_layout.addRow("Modèle:", self.translation_model_combo)
|
|
|
|
agent_config_layout.addLayout(agent_type_layout)
|
|
|
|
# Paramètres communs
|
|
params_layout = QFormLayout()
|
|
params_layout.addRow(QLabel("<b>Paramètres de génération</b>"))
|
|
|
|
self.temp_spin = QDoubleSpinBox()
|
|
self.temp_spin.setRange(0.1, 1.0)
|
|
self.temp_spin.setSingleStep(0.1)
|
|
self.temp_spin.setValue(0.2)
|
|
params_layout.addRow("Température:", self.temp_spin)
|
|
|
|
self.top_p_spin = QDoubleSpinBox()
|
|
self.top_p_spin.setRange(0.1, 1.0)
|
|
self.top_p_spin.setSingleStep(0.05)
|
|
self.top_p_spin.setValue(0.95)
|
|
params_layout.addRow("Top-p:", self.top_p_spin)
|
|
|
|
self.token_spin = QSpinBox()
|
|
self.token_spin.setRange(100, 4000)
|
|
self.token_spin.setSingleStep(100)
|
|
self.token_spin.setValue(1024)
|
|
params_layout.addRow("Max tokens:", self.token_spin)
|
|
|
|
agent_config_layout.addLayout(params_layout)
|
|
|
|
agent_layout.addWidget(agent_config_group)
|
|
|
|
# Boutons d'action
|
|
action_layout = QHBoxLayout()
|
|
|
|
self.run_btn = QPushButton("▶ Appliquer l'agent")
|
|
self.run_btn.setMinimumHeight(40)
|
|
self.run_btn.clicked.connect(self.run_agent)
|
|
action_layout.addWidget(self.run_btn)
|
|
|
|
agent_layout.addLayout(action_layout)
|
|
|
|
# Aperçu des résultats
|
|
result_group = QGroupBox("Résultat")
|
|
result_layout = QVBoxLayout(result_group)
|
|
|
|
self.result_text = QTextEdit()
|
|
self.result_text.setReadOnly(True)
|
|
self.result_text.setMinimumHeight(100)
|
|
result_layout.addWidget(self.result_text)
|
|
|
|
agent_layout.addWidget(result_group)
|
|
|
|
# 3. Onglet Export
|
|
export_tab = QWidget()
|
|
export_layout = QVBoxLayout(export_tab)
|
|
|
|
export_group = QGroupBox("Options d'export")
|
|
export_group_layout = QVBoxLayout(export_group)
|
|
|
|
export_format_layout = QHBoxLayout()
|
|
export_format_layout.addWidget(QLabel("Format:"))
|
|
self.export_format_combo = QComboBox()
|
|
self.export_format_combo.addItems(["Markdown (.md)"])
|
|
export_format_layout.addWidget(self.export_format_combo)
|
|
|
|
export_group_layout.addLayout(export_format_layout)
|
|
|
|
self.include_images_check = QCheckBox("Inclure les images")
|
|
self.include_images_check.setChecked(True)
|
|
export_group_layout.addWidget(self.include_images_check)
|
|
|
|
# Option pour enregistrer les images capturées
|
|
self.save_captured_images_check = QCheckBox("Enregistrer les images capturées")
|
|
self.save_captured_images_check.setChecked(True)
|
|
export_group_layout.addWidget(self.save_captured_images_check)
|
|
|
|
export_layout.addWidget(export_group)
|
|
|
|
export_btn = QPushButton("Exporter")
|
|
export_btn.setMinimumHeight(40)
|
|
export_btn.clicked.connect(self.export_results)
|
|
export_layout.addWidget(export_btn)
|
|
|
|
export_layout.addStretch()
|
|
|
|
# Ajout des onglets
|
|
tabs.addTab(selections_tab, "Sélections")
|
|
tabs.addTab(agent_tab, "Agents LLM")
|
|
tabs.addTab(export_tab, "Export")
|
|
|
|
def load_llm_profiles(self):
|
|
"""Charge les profils LLM depuis le fichier de configuration"""
|
|
try:
|
|
config_dir = os.path.join(os.path.dirname(os.path.dirname(__file__)), "config")
|
|
profile_path = os.path.join(config_dir, "llm_profiles.json")
|
|
|
|
if os.path.exists(profile_path):
|
|
with open(profile_path, "r", encoding="utf-8") as f:
|
|
self.profiles = json.load(f)
|
|
else:
|
|
# Profil par défaut si le fichier n'existe pas
|
|
self.profiles = {
|
|
"léger": {
|
|
"vision": {"model": "llava:34b", "language": "en", "temperature": 0.2},
|
|
"translation": {"model": "mistral", "language": "fr", "temperature": 0.1},
|
|
"summary": {"model": "mistral", "language": "fr", "temperature": 0.2}
|
|
},
|
|
"moyen": {
|
|
"vision": {"model": "llava", "language": "en", "temperature": 0.2},
|
|
"translation": {"model": "qwen2.5", "language": "fr", "temperature": 0.1},
|
|
"summary": {"model": "deepseek-r1", "language": "fr", "temperature": 0.2}
|
|
},
|
|
"avancé": {
|
|
"vision": {"model": "llama3.2-vision", "language": "en", "temperature": 0.2},
|
|
"translation": {"model": "deepseek", "language": "fr", "temperature": 0.1},
|
|
"summary": {"model": "deepseek-r1", "language": "fr", "temperature": 0.2}
|
|
}
|
|
}
|
|
|
|
# Créer le répertoire de configuration s'il n'existe pas
|
|
os.makedirs(config_dir, exist_ok=True)
|
|
|
|
# Sauvegarder le profil par défaut
|
|
with open(profile_path, "w", encoding="utf-8") as f:
|
|
json.dump(self.profiles, f, indent=2, ensure_ascii=False)
|
|
|
|
except Exception as e:
|
|
print(f"Erreur lors du chargement des profils: {str(e)}")
|
|
|
|
def update_selection_list(self):
|
|
"""Met à jour la liste des sélections"""
|
|
self.selection_list.clear()
|
|
|
|
for i, selection in enumerate(self.parent.selected_regions):
|
|
page = selection["page"] + 1
|
|
typ = selection["type"]
|
|
self.selection_list.addItem(f"Page {page} - {typ}")
|
|
|
|
def selection_changed(self, index):
|
|
"""Appelé lorsque la sélection change dans la liste"""
|
|
if index >= 0 and index < len(self.parent.selected_regions):
|
|
selection = self.parent.selected_regions[index]
|
|
self.current_selection = selection
|
|
|
|
# Mettre à jour l'interface
|
|
self.type_combo.setCurrentText(selection["type"])
|
|
self.context_edit.setText(selection["context"])
|
|
|
|
# Afficher les résultats si disponibles
|
|
if id(selection) in self.analysis_results:
|
|
self.result_text.setText(self.analysis_results[id(selection)])
|
|
else:
|
|
self.result_text.clear()
|
|
|
|
def update_selection_type(self, new_type):
|
|
"""Met à jour le type de la sélection actuelle"""
|
|
if self.current_selection:
|
|
self.current_selection["type"] = new_type
|
|
self.update_selection_list()
|
|
|
|
def update_selection_context(self):
|
|
"""Met à jour le contexte de la sélection actuelle"""
|
|
if self.current_selection:
|
|
self.current_selection["context"] = self.context_edit.toPlainText()
|
|
|
|
def remove_selection(self):
|
|
"""Supprime la sélection actuelle"""
|
|
if self.current_selection:
|
|
idx = self.parent.selected_regions.index(self.current_selection)
|
|
self.parent.selected_regions.remove(self.current_selection)
|
|
|
|
# Supprimer les résultats associés
|
|
if id(self.current_selection) in self.analysis_results:
|
|
del self.analysis_results[id(self.current_selection)]
|
|
|
|
self.current_selection = None
|
|
self.update_selection_list()
|
|
|
|
# Sélectionner le prochain élément si disponible
|
|
if self.selection_list.count() > 0:
|
|
new_idx = min(idx, self.selection_list.count() - 1)
|
|
self.selection_list.setCurrentRow(new_idx)
|
|
else:
|
|
self.context_edit.clear()
|
|
self.result_text.clear()
|
|
|
|
def update_mode(self, mode_text):
|
|
"""Met à jour le mode d'analyse en fonction du niveau sélectionné"""
|
|
if "léger" in mode_text.lower():
|
|
profile = self.profiles.get("léger", {})
|
|
elif "moyen" in mode_text.lower():
|
|
profile = self.profiles.get("moyen", {})
|
|
elif "avancé" in mode_text.lower():
|
|
profile = self.profiles.get("avancé", {})
|
|
else:
|
|
return
|
|
|
|
# Mise à jour des combobox avec les paramètres du profil
|
|
if "vision" in profile:
|
|
vision = profile["vision"]
|
|
if "model" in vision and vision["model"] in [self.vision_model_combo.itemText(i) for i in range(self.vision_model_combo.count())]:
|
|
self.vision_model_combo.setCurrentText(vision["model"])
|
|
if "language" in vision:
|
|
self.vision_lang_combo.setCurrentText(vision["language"])
|
|
if "temperature" in vision:
|
|
self.temp_spin.setValue(vision["temperature"])
|
|
|
|
if "summary" in profile:
|
|
summary = profile["summary"]
|
|
if "model" in summary and summary["model"] in [self.summary_model_combo.itemText(i) for i in range(self.summary_model_combo.count())]:
|
|
self.summary_model_combo.setCurrentText(summary["model"])
|
|
|
|
if "translation" in profile:
|
|
translation = profile["translation"]
|
|
if "model" in translation and translation["model"] in [self.translation_model_combo.itemText(i) for i in range(self.translation_model_combo.count())]:
|
|
self.translation_model_combo.setCurrentText(translation["model"])
|
|
|
|
def run_agent(self):
|
|
"""Exécute l'agent LLM sur la sélection actuelle"""
|
|
if not self.current_selection:
|
|
self.result_text.setText("Veuillez sélectionner une région avant d'exécuter l'agent.")
|
|
return
|
|
|
|
try:
|
|
# Récupération des paramètres
|
|
selection_type = self.current_selection.get("type", "autre")
|
|
context = self.current_selection.get("context", "")
|
|
|
|
# Paramètres de génération
|
|
gen_params = {
|
|
"temperature": self.temp_spin.value(),
|
|
"top_p": self.top_p_spin.value(),
|
|
"max_tokens": self.token_spin.value(),
|
|
"save_images": self.save_captured_images_check.isChecked() # Ajout du paramètre d'enregistrement
|
|
}
|
|
|
|
self.result_text.setText("Analyse en cours...\nCette opération peut prendre plusieurs minutes.")
|
|
self.parent.status_bar.showMessage("Traitement de l'image en cours...")
|
|
|
|
# Forcer la mise à jour de l'interface
|
|
QApplication.processEvents()
|
|
|
|
# Afficher les informations sur la sélection actuelle (débogue)
|
|
rect = self.current_selection.get("rect")
|
|
if rect:
|
|
rect_info = f"Sélection: x={rect.x()}, y={rect.y()}, w={rect.width()}, h={rect.height()}, page={self.current_selection.get('page', 0)+1}"
|
|
print(f"INFO SÉLECTION: {rect_info}")
|
|
|
|
# Récupérer l'image de la sélection
|
|
selection_image = self.parent.get_selection_image(self.current_selection)
|
|
|
|
if selection_image:
|
|
# Vérifier la taille des données d'image pour le débogue
|
|
print(f"Taille des données image: {len(selection_image)} octets")
|
|
|
|
# Récupération des paramètres depuis l'interface
|
|
vision_model = self.vision_model_combo.currentText()
|
|
vision_lang = self.vision_lang_combo.currentText()
|
|
summary_model = self.summary_model_combo.currentText()
|
|
translation_model = self.translation_model_combo.currentText()
|
|
|
|
# Configuration du pipeline de traitement
|
|
from utils.workflow_manager import WorkflowManager
|
|
from config.agent_config import ACTIVE_AGENTS
|
|
|
|
# Mise à jour dynamique de l'activation des agents
|
|
active_agents = ACTIVE_AGENTS.copy()
|
|
active_agents["summary"] = self.summary_enabled_checkbox.isChecked()
|
|
|
|
# Afficher les agents actifs pour le débogage
|
|
print(f"Agents actifs: {active_agents}")
|
|
print(f"Modèles utilisés: vision={vision_model}, translation={translation_model}, summary={summary_model}")
|
|
|
|
# Créer le gestionnaire de workflow
|
|
workflow = WorkflowManager(
|
|
vision_model=vision_model,
|
|
summary_model=summary_model,
|
|
translation_model=translation_model,
|
|
active_agents=active_agents,
|
|
config=gen_params
|
|
)
|
|
|
|
# Exécution du workflow
|
|
results = workflow.process_image(
|
|
image_data=selection_image,
|
|
content_type=selection_type,
|
|
context=context,
|
|
target_lang=vision_lang
|
|
)
|
|
|
|
# Affichage des résultats
|
|
if results:
|
|
result_text = ""
|
|
|
|
# Vision (original)
|
|
if "vision" in results and results["vision"]:
|
|
result_text += "🔍 ANALYSE VISUELLE (en):\n"
|
|
result_text += results["vision"]
|
|
result_text += "\n\n"
|
|
|
|
# Traduction
|
|
if "translation" in results and results["translation"]:
|
|
result_text += "🌐 TRADUCTION (fr):\n"
|
|
result_text += results["translation"]
|
|
result_text += "\n\n"
|
|
|
|
# Résumé (si activé)
|
|
if "summary" in results and results["summary"]:
|
|
result_text += "📝 RÉSUMÉ (fr):\n"
|
|
result_text += results["summary"]
|
|
|
|
# Message d'erreur
|
|
if "error" in results:
|
|
result_text += "\n\n❌ ERREUR:\n"
|
|
result_text += results["error"]
|
|
|
|
# Enregistrer l'analyse dans l'historique
|
|
self.analysis_results[self.selection_list.currentRow()] = results
|
|
|
|
self.result_text.setText(result_text)
|
|
self.parent.status_bar.showMessage("Analyse terminée.")
|
|
else:
|
|
self.result_text.setText("Erreur: Impossible d'obtenir une analyse de l'image.")
|
|
self.parent.status_bar.showMessage("Analyse échouée.")
|
|
else:
|
|
self.result_text.setText("Erreur: Impossible d'extraire l'image de la sélection.")
|
|
self.parent.status_bar.showMessage("Erreur: Extraction d'image impossible.")
|
|
|
|
except Exception as e:
|
|
import traceback
|
|
error_details = traceback.format_exc()
|
|
print(f"Erreur lors de l'analyse: {error_details}")
|
|
self.result_text.setText(f"Erreur lors de l'analyse: {str(e)}\n\nDétails de l'erreur:\n{error_details}")
|
|
self.parent.status_bar.showMessage("Erreur: Analyse échouée.")
|
|
|
|
def export_results(self):
|
|
"""Exporte les résultats au format Markdown"""
|
|
if not self.parent.selected_regions:
|
|
QMessageBox.warning(self, "Aucune sélection",
|
|
"Il n'y a aucune sélection à exporter.")
|
|
return
|
|
|
|
try:
|
|
# Créer le répertoire de sortie s'il n'existe pas
|
|
output_dir = os.path.join(os.path.dirname(os.path.dirname(__file__)), "data", "outputs")
|
|
os.makedirs(output_dir, exist_ok=True)
|
|
|
|
# Générer le contenu Markdown
|
|
md_content = "# Analyse de document\n\n"
|
|
|
|
# Trier les sélections par numéro de page
|
|
sorted_selections = sorted(self.parent.selected_regions, key=lambda x: x["page"])
|
|
|
|
for selection in sorted_selections:
|
|
page = selection["page"] + 1
|
|
typ = selection["type"]
|
|
context = selection["context"]
|
|
|
|
md_content += f"## {typ.capitalize()} - Page {page}\n\n"
|
|
|
|
if context:
|
|
md_content += f"**Contexte** :\n{context}\n\n"
|
|
|
|
# Ajouter les résultats d'analyse si disponibles
|
|
if id(selection) in self.analysis_results:
|
|
md_content += f"**Analyse IA** :\n{self.analysis_results[id(selection)]}\n\n"
|
|
|
|
md_content += "---\n\n"
|
|
|
|
# Écrire le fichier Markdown
|
|
file_path = os.path.join(output_dir, "analyse_document.md")
|
|
with open(file_path, "w", encoding="utf-8") as f:
|
|
f.write(md_content)
|
|
|
|
QMessageBox.information(self, "Export réussi",
|
|
f"Le fichier a été exporté avec succès :\n{file_path}")
|
|
|
|
except Exception as e:
|
|
QMessageBox.critical(self, "Erreur d'export",
|
|
f"Une erreur est survenue lors de l'export :\n{str(e)}") |