Enhance chatbot UI by increasing height, adding copy button, and refining image gallery display
This commit is contained in:
parent
819d3a0956
commit
9fd056baaf
@ -9,6 +9,9 @@ from translations.lang_mappings import LANGUAGE_MAPPING
|
|||||||
from utils.image_utils import base64_to_image
|
from utils.image_utils import base64_to_image
|
||||||
from langchain.callbacks.base import BaseCallbackHandler
|
from langchain.callbacks.base import BaseCallbackHandler
|
||||||
import re
|
import re
|
||||||
|
from typing import List, Union, Dict, Any
|
||||||
|
# Pour Gradio 4.x
|
||||||
|
# from gradio.types.message import ImageMessage, HtmlMessage, TextMessage
|
||||||
|
|
||||||
def clean_llm_response(text):
|
def clean_llm_response(text):
|
||||||
"""Nettoie la réponse du LLM en enlevant les balises de pensée et autres éléments non désirés."""
|
"""Nettoie la réponse du LLM en enlevant les balises de pensée et autres éléments non désirés."""
|
||||||
@ -53,7 +56,9 @@ def display_images(images_list=None):
|
|||||||
for img_data in images_to_use:
|
for img_data in images_to_use:
|
||||||
image = img_data["image"]
|
image = img_data["image"]
|
||||||
if image:
|
if image:
|
||||||
caption = f"{img_data['caption']} (Source: {img_data['source']}, Page: {img_data['page']})"
|
# Supprimer les infos de type "(Texte 5)" dans la caption
|
||||||
|
caption = re.sub(pattern_texte, '', img_data["caption"])
|
||||||
|
caption = f"{caption} (Source: {img_data['source']}, Page: {img_data['page']})"
|
||||||
gallery.append((image, caption))
|
gallery.append((image, caption))
|
||||||
|
|
||||||
return gallery if gallery else None
|
return gallery if gallery else None
|
||||||
@ -160,7 +165,7 @@ def convert_to_messages_format(history):
|
|||||||
messages = []
|
messages = []
|
||||||
|
|
||||||
# Vérifier si nous avons déjà le format messages
|
# Vérifier si nous avons déjà le format messages
|
||||||
if history and isinstance(history[0], dict) and "role" in history[0]:
|
if history and isinstance(history[0], dict) and "role" in history[0]:
|
||||||
return history
|
return history
|
||||||
|
|
||||||
# Format tuples [(user_msg, assistant_msg), ...]
|
# Format tuples [(user_msg, assistant_msg), ...]
|
||||||
@ -171,71 +176,75 @@ def convert_to_messages_format(history):
|
|||||||
messages.append({"role": "user", "content": user_msg})
|
messages.append({"role": "user", "content": user_msg})
|
||||||
if assistant_msg: # Éviter les messages vides
|
if assistant_msg: # Éviter les messages vides
|
||||||
messages.append({"role": "assistant", "content": assistant_msg})
|
messages.append({"role": "assistant", "content": assistant_msg})
|
||||||
except ValueError:
|
except Exception as e:
|
||||||
# Journaliser l'erreur pour le débogage
|
# Journaliser l'erreur pour le débogage
|
||||||
print(f"Format d'historique non reconnu: {history}")
|
print(f"Format d'historique non reconnu: {history}")
|
||||||
|
print(f"Erreur: {str(e)}")
|
||||||
# Retourner un historique vide en cas d'erreur
|
# Retourner un historique vide en cas d'erreur
|
||||||
return []
|
return []
|
||||||
|
|
||||||
return messages
|
return messages
|
||||||
|
|
||||||
|
# Définir le pattern de l'expression régulière en dehors de la f-string
|
||||||
|
pattern_texte = r'\(Texte \d+\)'
|
||||||
|
|
||||||
def process_query(message, history, streaming, show_sources, max_images, language):
|
def process_query(message, history, streaming, show_sources, max_images, language):
|
||||||
global current_images, current_tables
|
global current_images, current_tables
|
||||||
|
|
||||||
# Debug plus clair
|
print(f"Language selected for response: {language} -> {LANGUAGE_MAPPING.get(language, 'français')}")
|
||||||
print(f"Langue sélectionnée pour la réponse: {language} -> {LANGUAGE_MAPPING.get(language, 'français')}")
|
|
||||||
|
|
||||||
if not message.strip():
|
if not message.strip():
|
||||||
return history, "", None, None
|
return history, "", None, None
|
||||||
|
|
||||||
current_images = []
|
current_images = []
|
||||||
current_tables = []
|
current_tables = []
|
||||||
print(f"Traitement du message: {message}")
|
|
||||||
print(f"Streaming: {streaming}")
|
|
||||||
|
|
||||||
try:
|
try:
|
||||||
|
# Convert history to messages format
|
||||||
|
messages_history = convert_to_messages_format(history)
|
||||||
|
|
||||||
if streaming:
|
if streaming:
|
||||||
# Convertir history en format messages pour l'affichage
|
# Add user message to history
|
||||||
messages_history = convert_to_messages_format(history)
|
|
||||||
messages_history.append({"role": "user", "content": message})
|
messages_history.append({"role": "user", "content": message})
|
||||||
|
# Add empty message for assistant response
|
||||||
messages_history.append({"role": "assistant", "content": ""})
|
messages_history.append({"role": "assistant", "content": ""})
|
||||||
|
|
||||||
# 1. Récupérer les documents pertinents
|
# Get relevant documents
|
||||||
docs = rag_bot._retrieve_relevant_documents(message)
|
docs = rag_bot._retrieve_relevant_documents(message)
|
||||||
|
|
||||||
# 2. Préparer le contexte et l'historique
|
# Process context and history
|
||||||
context = rag_bot._format_documents(docs)
|
context = rag_bot._format_documents(docs)
|
||||||
history_text = rag_bot._format_chat_history()
|
history_text = rag_bot._format_chat_history()
|
||||||
|
|
||||||
# 3. Préparer le prompt
|
# Create prompt
|
||||||
prompt_template = ChatPromptTemplate.from_template("""
|
prompt_template = ChatPromptTemplate.from_template("""
|
||||||
Tu es un assistant documentaire spécialisé qui utilise le contexte fourni.
|
You are a specialized document assistant that uses the provided context.
|
||||||
|
|
||||||
===== INSTRUCTION CRUCIALE SUR LA LANGUE =====
|
===== CRITICAL LANGUAGE INSTRUCTION =====
|
||||||
RÉPONDS UNIQUEMENT EN {language}. C'est une exigence ABSOLUE.
|
RESPOND ONLY IN {language}. This is an ABSOLUTE requirement.
|
||||||
NE RÉPONDS JAMAIS dans une autre langue que {language}, quelle que soit la langue de la question.
|
NEVER RESPOND in any language other than {language}, regardless of question language.
|
||||||
==============================================
|
==============================================
|
||||||
|
|
||||||
Instructions spécifiques:
|
Specific instructions:
|
||||||
1. Pour chaque image mentionnée: inclure la légende, source, page et description
|
1. For each image mentioned: include caption, source, page and description
|
||||||
2. Pour chaque tableau: inclure titre, source, page et signification
|
2. For each table: include title, source, page and significance
|
||||||
3. Pour les équations: utiliser la syntaxe LaTeX exacte
|
3. For equations: use exact LaTeX syntax
|
||||||
4. Ne pas inventer d'informations hors du contexte fourni
|
4. Don't invent information outside the provided context
|
||||||
5. Citer précisément les sources
|
5. Cite sources precisely
|
||||||
|
|
||||||
Historique de conversation:
|
Conversation history:
|
||||||
{chat_history}
|
{chat_history}
|
||||||
|
|
||||||
Contexte:
|
Context:
|
||||||
{context}
|
{context}
|
||||||
|
|
||||||
Question: {question}
|
Question: {question}
|
||||||
|
|
||||||
Réponds de façon structurée en intégrant les images, tableaux et équations disponibles.
|
Respond in a structured way incorporating available images, tables and equations.
|
||||||
TA RÉPONSE DOIT ÊTRE UNIQUEMENT ET ENTIÈREMENT EN {language}. CETTE RÈGLE EST ABSOLUE.
|
YOUR RESPONSE MUST BE SOLELY AND ENTIRELY IN {language}. THIS RULE IS ABSOLUTE.
|
||||||
""")
|
""")
|
||||||
|
|
||||||
# Assurer que la langue est bien passée dans le format du prompt
|
# Set language for the response
|
||||||
selected_language = LANGUAGE_MAPPING.get(language, "français")
|
selected_language = LANGUAGE_MAPPING.get(language, "français")
|
||||||
messages = prompt_template.format_messages(
|
messages = prompt_template.format_messages(
|
||||||
chat_history=history_text,
|
chat_history=history_text,
|
||||||
@ -244,10 +253,10 @@ def process_query(message, history, streaming, show_sources, max_images, languag
|
|||||||
language=selected_language
|
language=selected_language
|
||||||
)
|
)
|
||||||
|
|
||||||
# 5. Créer un handler de streaming personnalisé
|
# Create streaming handler
|
||||||
handler = GradioStreamingHandler()
|
handler = GradioStreamingHandler()
|
||||||
|
|
||||||
# 6. Créer un modèle LLM avec notre handler
|
# Create LLM model with our handler
|
||||||
streaming_llm = ChatOllama(
|
streaming_llm = ChatOllama(
|
||||||
model=rag_bot.llm.model,
|
model=rag_bot.llm.model,
|
||||||
base_url=rag_bot.llm.base_url,
|
base_url=rag_bot.llm.base_url,
|
||||||
@ -255,87 +264,81 @@ def process_query(message, history, streaming, show_sources, max_images, languag
|
|||||||
callbacks=[handler]
|
callbacks=[handler]
|
||||||
)
|
)
|
||||||
|
|
||||||
# 7. Lancer la génération dans un thread pour ne pas bloquer l'UI
|
# Generate response in a separate thread
|
||||||
def generate_response():
|
def generate_response():
|
||||||
streaming_llm.invoke(messages)
|
streaming_llm.invoke(messages)
|
||||||
|
|
||||||
thread = threading.Thread(target=generate_response)
|
thread = threading.Thread(target=generate_response)
|
||||||
thread.start()
|
thread.start()
|
||||||
|
|
||||||
# 8. Récupérer les tokens et mettre à jour l'interface
|
# Process tokens and update interface
|
||||||
partial_response = ""
|
partial_response = ""
|
||||||
|
|
||||||
# Attendre les tokens avec un timeout
|
# Wait for tokens with timeout
|
||||||
while thread.is_alive() or not handler.tokens_queue.empty():
|
while thread.is_alive() or not handler.tokens_queue.empty():
|
||||||
try:
|
try:
|
||||||
token = handler.tokens_queue.get(timeout=0.05)
|
token = handler.tokens_queue.get(timeout=0.05)
|
||||||
partial_response += token
|
partial_response += token
|
||||||
|
|
||||||
# Nettoyer la réponse uniquement pour l'affichage (pas pour l'historique interne)
|
# Clean response for display
|
||||||
clean_response = clean_llm_response(partial_response)
|
clean_response = clean_llm_response(partial_response)
|
||||||
# Mettre à jour le dernier message (assistant)
|
# Update assistant message - JUST TEXT, not multimodal
|
||||||
messages_history[-1]["content"] = clean_response
|
messages_history[-1]["content"] = clean_response
|
||||||
yield messages_history, "", None, None
|
yield messages_history, "", None, None
|
||||||
except queue.Empty:
|
except queue.Empty:
|
||||||
continue
|
continue
|
||||||
|
|
||||||
# Après la boucle, nettoyer la réponse complète pour l'historique interne
|
# After loop, clean the complete response for internal history
|
||||||
partial_response = clean_llm_response(partial_response)
|
partial_response = clean_llm_response(partial_response)
|
||||||
rag_bot.chat_history.append({"role": "user", "content": message})
|
rag_bot.chat_history.append({"role": "user", "content": message})
|
||||||
rag_bot.chat_history.append({"role": "assistant", "content": partial_response})
|
rag_bot.chat_history.append({"role": "assistant", "content": partial_response})
|
||||||
|
|
||||||
# 10. Récupérer les sources, images, tableaux
|
# Get sources, images, tables
|
||||||
texts, images, tables = rag_bot._process_documents(docs)
|
texts, images, tables = rag_bot._process_documents(docs)
|
||||||
|
|
||||||
# Préparer les informations sur les sources
|
# Process sources
|
||||||
source_info = ""
|
source_info = ""
|
||||||
if texts:
|
if texts:
|
||||||
source_info += f"📚 {len(texts)} textes • "
|
clean_texts = [re.sub(pattern_texte, '', t.get("source", "")) for t in texts]
|
||||||
if images:
|
# Remove duplicates and empty items
|
||||||
source_info += f"🖼️ {len(images)} images • "
|
clean_texts = [t for t in clean_texts if t.strip()]
|
||||||
if tables:
|
clean_texts = list(set(clean_texts))
|
||||||
source_info += f"📊 {len(tables)} tableaux"
|
if clean_texts:
|
||||||
|
source_info += f"📚 Sources: {', '.join(clean_texts)} • "
|
||||||
|
|
||||||
if source_info:
|
# Process images and tables for SEPARATE display only
|
||||||
source_info = "Sources trouvées: " + source_info
|
if show_sources and images and max_images > 0:
|
||||||
|
for img in images[:max_images]:
|
||||||
# 11. Traiter les images
|
|
||||||
if show_sources and images:
|
|
||||||
images = images[:max_images]
|
|
||||||
for img in images:
|
|
||||||
img_data = img.get("image_data")
|
img_data = img.get("image_data")
|
||||||
if img_data:
|
if img_data:
|
||||||
image = base64_to_image(img_data)
|
image = base64_to_image(img_data)
|
||||||
if image:
|
if image:
|
||||||
|
caption = re.sub(pattern_texte, '', img.get("caption", ""))
|
||||||
|
# Only add to gallery, not to chat messages
|
||||||
current_images.append({
|
current_images.append({
|
||||||
"image": image,
|
"image": image,
|
||||||
"caption": img.get("caption", ""),
|
"caption": caption,
|
||||||
"source": img.get("source", ""),
|
"source": img.get("source", ""),
|
||||||
"page": img.get("page", ""),
|
"page": img.get("page", "")
|
||||||
"description": img.get("description", "")
|
|
||||||
})
|
})
|
||||||
|
|
||||||
# 12. Traiter les tableaux
|
# Final yield with separate image gallery
|
||||||
if show_sources and tables:
|
yield messages_history, source_info, display_images(), display_tables()
|
||||||
for table in tables:
|
|
||||||
current_tables.append({
|
|
||||||
"data": rag_bot.format_table(table.get("table_data", "")),
|
|
||||||
"caption": table.get("caption", ""),
|
|
||||||
"source": table.get("source", ""),
|
|
||||||
"page": table.get("page", ""),
|
|
||||||
"description": table.get("description", "")
|
|
||||||
})
|
|
||||||
|
|
||||||
# 13. Retourner les résultats finaux
|
|
||||||
images_display = display_images()
|
|
||||||
tables_display = display_tables()
|
|
||||||
yield messages_history, source_info, images_display, tables_display
|
|
||||||
|
|
||||||
else:
|
else:
|
||||||
# Version sans streaming
|
# Version non-streaming
|
||||||
print("Mode non-streaming activé")
|
print("Mode non-streaming activé")
|
||||||
source_info = ""
|
source_info = ""
|
||||||
|
|
||||||
|
history_tuples = history if isinstance(history, list) else []
|
||||||
|
|
||||||
|
# Ajouter le message utilisateur à l'historique au format message
|
||||||
|
messages_history.append({"role": "user", "content": message})
|
||||||
|
|
||||||
|
# Initialize multimodal_content first
|
||||||
|
multimodal_content = [result["response"]] # Start with text response
|
||||||
|
|
||||||
|
# Après avoir obtenu le résultat
|
||||||
result = rag_bot.chat(
|
result = rag_bot.chat(
|
||||||
message,
|
message,
|
||||||
stream=False,
|
stream=False,
|
||||||
@ -344,12 +347,10 @@ def process_query(message, history, streaming, show_sources, max_images, languag
|
|||||||
# Nettoyer la réponse des balises <think>
|
# Nettoyer la réponse des balises <think>
|
||||||
result["response"] = clean_llm_response(result["response"])
|
result["response"] = clean_llm_response(result["response"])
|
||||||
|
|
||||||
# Convertir l'historique au format messages
|
# Ajouter la réponse de l'assistant au format message
|
||||||
messages_history = convert_to_messages_format(history)
|
|
||||||
messages_history.append({"role": "user", "content": message})
|
|
||||||
messages_history.append({"role": "assistant", "content": result["response"]})
|
messages_history.append({"role": "assistant", "content": result["response"]})
|
||||||
|
|
||||||
# Mise à jour de l'historique interne
|
# Mise à jour de l'historique interne du chatbot
|
||||||
rag_bot.chat_history.append({"role": "user", "content": message})
|
rag_bot.chat_history.append({"role": "user", "content": message})
|
||||||
rag_bot.chat_history.append({"role": "assistant", "content": result["response"]})
|
rag_bot.chat_history.append({"role": "assistant", "content": result["response"]})
|
||||||
|
|
||||||
@ -364,33 +365,23 @@ def process_query(message, history, streaming, show_sources, max_images, languag
|
|||||||
if source_info:
|
if source_info:
|
||||||
source_info = "Sources trouvées: " + source_info
|
source_info = "Sources trouvées: " + source_info
|
||||||
|
|
||||||
# Traiter les images et tableaux
|
# Process images for SEPARATE gallery
|
||||||
if show_sources and "images" in result and result["images"]:
|
if show_sources and "images" in result and result["images"]:
|
||||||
images = result["images"][:max_images]
|
for img in result["images"][:max_images]:
|
||||||
for img in images:
|
|
||||||
img_data = img.get("image_data")
|
img_data = img.get("image_data")
|
||||||
if img_data:
|
if img_data:
|
||||||
image = base64_to_image(img_data)
|
image = base64_to_image(img_data)
|
||||||
if image:
|
if image:
|
||||||
|
caption = re.sub(pattern_texte, '', img.get("caption", ""))
|
||||||
|
# Only add to gallery
|
||||||
current_images.append({
|
current_images.append({
|
||||||
"image": image,
|
"image": image,
|
||||||
"caption": img.get("caption", ""),
|
"caption": caption,
|
||||||
"source": img.get("source", ""),
|
"source": img.get("source", ""),
|
||||||
"page": img.get("page", ""),
|
"page": img.get("page", "")
|
||||||
"description": img.get("description", "")
|
|
||||||
})
|
})
|
||||||
|
|
||||||
if show_sources and "tables" in result and result["tables"]:
|
# Final yield with separate displays
|
||||||
tables = result["tables"]
|
|
||||||
for table in tables:
|
|
||||||
current_tables.append({
|
|
||||||
"data": rag_bot.format_table(table.get("table_data", "")),
|
|
||||||
"caption": table.get("caption", ""),
|
|
||||||
"source": table.get("source", ""),
|
|
||||||
"page": table.get("page", ""),
|
|
||||||
"description": table.get("description", "")
|
|
||||||
})
|
|
||||||
|
|
||||||
yield messages_history, source_info, display_images(), display_tables()
|
yield messages_history, source_info, display_images(), display_tables()
|
||||||
|
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
@ -398,8 +389,13 @@ def process_query(message, history, streaming, show_sources, max_images, languag
|
|||||||
traceback_text = traceback.format_exc()
|
traceback_text = traceback.format_exc()
|
||||||
print(error_msg)
|
print(error_msg)
|
||||||
print(traceback_text)
|
print(traceback_text)
|
||||||
history = history + [(message, error_msg)]
|
|
||||||
yield history, "Erreur lors du traitement de la requête", None, None
|
# Formater l'erreur au format message
|
||||||
|
error_history = convert_to_messages_format(history)
|
||||||
|
error_history.append({"role": "user", "content": message})
|
||||||
|
error_history.append({"role": "assistant", "content": error_msg})
|
||||||
|
|
||||||
|
yield error_history, "Erreur lors du traitement de la requête", None, None
|
||||||
|
|
||||||
# Fonction pour réinitialiser la conversation
|
# Fonction pour réinitialiser la conversation
|
||||||
def reset_conversation():
|
def reset_conversation():
|
||||||
@ -410,4 +406,4 @@ def reset_conversation():
|
|||||||
rag_bot.clear_history()
|
rag_bot.clear_history()
|
||||||
|
|
||||||
# Retourner une liste vide au format messages
|
# Retourner une liste vide au format messages
|
||||||
return [], "", None, None
|
return [], "", None, None # Liste vide = pas de messages
|
||||||
@ -73,11 +73,11 @@ def build_interface(
|
|||||||
with gr.Row():
|
with gr.Row():
|
||||||
with gr.Column(scale=2):
|
with gr.Column(scale=2):
|
||||||
chat_interface = gr.Chatbot(
|
chat_interface = gr.Chatbot(
|
||||||
height=600,
|
height=800,
|
||||||
show_label=False,
|
bubble_full_width=False,
|
||||||
layout="bubble",
|
show_copy_button=True,
|
||||||
elem_id="chatbot",
|
type="messages"
|
||||||
type="messages" # Ajoutez cette ligne
|
# likeable=False,
|
||||||
)
|
)
|
||||||
|
|
||||||
with gr.Row():
|
with gr.Row():
|
||||||
@ -144,17 +144,9 @@ def build_interface(
|
|||||||
label=ui_elements['max_images_label']
|
label=ui_elements['max_images_label']
|
||||||
)
|
)
|
||||||
|
|
||||||
gr.Markdown("---")
|
# Ne pas supprimer ces lignes dans ui.py
|
||||||
|
|
||||||
images_title = gr.Markdown(f"### {ui_elements['images_title']}")
|
images_title = gr.Markdown(f"### {ui_elements['images_title']}")
|
||||||
image_gallery = gr.Gallery(
|
image_gallery = gr.Gallery(label="Images")
|
||||||
label=ui_elements['images_title'],
|
|
||||||
show_label=False,
|
|
||||||
columns=2,
|
|
||||||
height=300,
|
|
||||||
object_fit="contain"
|
|
||||||
)
|
|
||||||
|
|
||||||
tables_title = gr.Markdown(f"### {ui_elements['tables_title']}")
|
tables_title = gr.Markdown(f"### {ui_elements['tables_title']}")
|
||||||
tables_display = gr.HTML()
|
tables_display = gr.HTML()
|
||||||
|
|
||||||
@ -190,9 +182,7 @@ def build_interface(
|
|||||||
apply_collection_btn,
|
apply_collection_btn,
|
||||||
streaming,
|
streaming,
|
||||||
show_sources,
|
show_sources,
|
||||||
max_images,
|
max_images
|
||||||
images_title,
|
|
||||||
tables_title
|
|
||||||
]
|
]
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -215,7 +205,7 @@ def build_interface(
|
|||||||
|
|
||||||
clear_btn.click(
|
clear_btn.click(
|
||||||
reset_conversation_fn,
|
reset_conversation_fn,
|
||||||
outputs=[chat_interface, source_info, image_gallery, tables_display]
|
outputs=[chat_interface, source_info] # Retirer image_gallery et tables_display
|
||||||
)
|
)
|
||||||
|
|
||||||
# Connecter le changement de modèle
|
# Connecter le changement de modèle
|
||||||
@ -236,7 +226,7 @@ def build_interface(
|
|||||||
gr.Markdown("""
|
gr.Markdown("""
|
||||||
<style>
|
<style>
|
||||||
.gradio-container {max-width: 1200px !important}
|
.gradio-container {max-width: 1200px !important}
|
||||||
#chatbot {height: 600px; overflow-y: auto;}
|
#chatbot {height: 800px; overflow-y: auto;}
|
||||||
#sources_info {margin-top: 10px; color: #666;}
|
#sources_info {margin-top: 10px; color: #666;}
|
||||||
|
|
||||||
/* Improved styles for equations */
|
/* Improved styles for equations */
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user