diff --git a/.gitignore b/.gitignore index dbc9cfc..adc0f79 100644 --- a/.gitignore +++ b/.gitignore @@ -1 +1,7 @@ -apigit.txt \ No newline at end of file +apigit.txt +*.pyc +*.pyo +*.pyd + +# Dossier de cache +__pycache__/ \ No newline at end of file diff --git a/app.py b/app.py index 680783c..18190dc 100644 --- a/app.py +++ b/app.py @@ -1,22 +1,59 @@ # filepath: f:\Dev\Rag\chat_bot_rag\app.py import gradio as gr +from config.settings import DEFAULT_MODEL, QDRANT_COLLECTION_NAME, AVAILABLE_MODELS from services.rag_service import initialize_rag_bot from components.chatbot import process_query, reset_conversation, change_model, change_collection from components.ui import build_interface, update_ui_language_elements +from translations.lang_mappings import UI_TRANSLATIONS, UI_SUPPORTED_LANGUAGES, LANGUAGE_MAPPING + +def update_ui_language(language): + """Fonction pour mettre à jour la langue de l'interface utilisateur""" + if language not in UI_SUPPORTED_LANGUAGES: + language = "Français" # Langue par défaut + + # Récupérer les traductions pour la langue sélectionnée + translations = UI_TRANSLATIONS[language] + + # Afficher un message de débogage + print(f"Mise à jour de la langue UI : {language}") + print(f"AVAILABLE_MODELS : {AVAILABLE_MODELS}") + + # Retourner les valeurs mises à jour pour tous les éléments de l'interface + return [ + f"# {translations['title']}", # Titre + gr.update(placeholder=translations["placeholder"]), # Placeholder du message + gr.update(value=translations["send_btn"]), # Texte du bouton d'envoi + gr.update(value=translations["clear_btn"]), # Texte du bouton d'effacement + gr.update(label=translations["ui_language_label"], info=translations["ui_language_info"]), # Label sélecteur langue UI + + # IMPORTANT : Conserver les choices=AVAILABLE_MODELS ici + gr.update(label=translations["model_selector"], info=translations["model_info"], choices=AVAILABLE_MODELS), + + f"{translations['model_current']}: **{DEFAULT_MODEL}**", # Statut du modèle + gr.update(label=translations["language_selector"], info=translations["language_info"], choices=list(LANGUAGE_MAPPING.keys())), # Langue réponses + gr.update(label=translations["collection_input"], info=translations["collection_info"]), # Label du champ de collection + f"{translations['collection_current']}: **{QDRANT_COLLECTION_NAME}**", # Statut de la collection + gr.update(value=translations["apply_btn"]), # Texte du bouton d'application + gr.update(label=translations["streaming_label"], info=translations["streaming_info"]), # Label du mode streaming + gr.update(label=translations["sources_label"]), # Label de l'affichage des sources + gr.update(label=translations["max_images_label"]), # Label du nombre max d'images + f"### {translations['images_title']}", # Titre des images + f"### {translations['tables_title']}" # Titre des tableaux + ] def main(): """Main entry point for the chatbot application""" # Initialize the RAG chatbot initialize_rag_bot() - # Construire l'interface + # Dans app.py, corriger l'appel à build_interface interface = build_interface( process_query_fn=process_query, reset_conversation_fn=reset_conversation, change_model_fn=change_model, change_collection_fn=change_collection, - update_ui_language_fn=update_ui_language_elements # Ajout du paramètre manquant + update_ui_language_fn=update_ui_language # Utiliser update_ui_language, pas update_ui_language_elements ) # Lancer l'appli Gradio @@ -28,4 +65,5 @@ def main(): ) if __name__ == "__main__": - main() \ No newline at end of file + main() + diff --git a/components/chatbot.py b/components/chatbot.py index e6433c6..06d89c5 100644 --- a/components/chatbot.py +++ b/components/chatbot.py @@ -155,9 +155,36 @@ def change_collection(collection_name, language="Français"): return f"❌ Erreur: {str(e)}" # Fonction de traitement de requête +def convert_to_messages_format(history): + """Convertit différents formats d'historique au format messages.""" + messages = [] + + # Vérifier si nous avons déjà le format messages + if history and isinstance(history[0], dict) and "role" in history[0]: + return history + + # Format tuples [(user_msg, assistant_msg), ...] + try: + for item in history: + if isinstance(item, tuple) and len(item) == 2: + user_msg, assistant_msg = item + messages.append({"role": "user", "content": user_msg}) + if assistant_msg: # Éviter les messages vides + messages.append({"role": "assistant", "content": assistant_msg}) + except ValueError: + # Journaliser l'erreur pour le débogage + print(f"Format d'historique non reconnu: {history}") + # Retourner un historique vide en cas d'erreur + return [] + + return messages + def process_query(message, history, streaming, show_sources, max_images, language): global current_images, current_tables + # Debug plus clair + print(f"Langue sélectionnée pour la réponse: {language} -> {LANGUAGE_MAPPING.get(language, 'français')}") + if not message.strip(): return history, "", None, None @@ -168,8 +195,10 @@ def process_query(message, history, streaming, show_sources, max_images, languag try: if streaming: - # Version avec streaming dans Gradio - history = history + [(message, "")] + # Convertir history en format messages pour l'affichage + messages_history = convert_to_messages_format(history) + messages_history.append({"role": "user", "content": message}) + messages_history.append({"role": "assistant", "content": ""}) # 1. Récupérer les documents pertinents docs = rag_bot._retrieve_relevant_documents(message) @@ -180,50 +209,39 @@ def process_query(message, history, streaming, show_sources, max_images, languag # 3. Préparer le prompt prompt_template = ChatPromptTemplate.from_template(""" - Tu es un assistant documentaire spécialisé qui utilise toutes les informations disponibles dans le contexte fourni. + Tu es un assistant documentaire spécialisé qui utilise le contexte fourni. - TRÈS IMPORTANT: Tu dois répondre EXCLUSIVEMENT en {language}. Ne réponds JAMAIS dans une autre langue. + ===== INSTRUCTION CRUCIALE SUR LA LANGUE ===== + RÉPONDS UNIQUEMENT EN {language}. C'est une exigence ABSOLUE. + NE RÉPONDS JAMAIS dans une autre langue que {language}, quelle que soit la langue de la question. + ============================================== Instructions spécifiques: - 1. Pour chaque image mentionnée dans le contexte, inclue TOUJOURS dans ta réponse: - - La légende/caption exacte de l'image - - La source et le numéro de page - - Une description brève de ce qu'elle montre - - 2. Pour chaque tableau mentionné dans le contexte, inclue TOUJOURS: - - Le titre/caption exact du tableau - - La source et le numéro de page - - Ce que contient et signifie le tableau - - 3. Lorsque tu cites des équations mathématiques: - - Utilise la syntaxe LaTeX exacte comme dans le document ($...$ ou $$...$$) - - Reproduis-les fidèlement sans modification - - 4. IMPORTANT: Ne pas inventer d'informations - si une donnée n'est pas explicitement fournie dans le contexte, - indique clairement que cette information n'est pas disponible dans les documents fournis. - - 5. Cite précisément les sources pour chaque élément d'information (format: [Source, Page]). - - 6. CRUCIAL: Ta réponse doit être UNIQUEMENT et INTÉGRALEMENT en {language}, quelle que soit la langue de la question. + 1. Pour chaque image mentionnée: inclure la légende, source, page et description + 2. Pour chaque tableau: inclure titre, source, page et signification + 3. Pour les équations: utiliser la syntaxe LaTeX exacte + 4. Ne pas inventer d'informations hors du contexte fourni + 5. Citer précisément les sources Historique de conversation: {chat_history} - Contexte (à utiliser pour répondre): + Contexte: {context} Question: {question} - Réponds de façon structurée et précise en intégrant activement les images, tableaux et équations disponibles dans le contexte. - Ta réponse doit être exclusivement en {language}. + Réponds de façon structurée en intégrant les images, tableaux et équations disponibles. + TA RÉPONSE DOIT ÊTRE UNIQUEMENT ET ENTIÈREMENT EN {language}. CETTE RÈGLE EST ABSOLUE. """) - # 4. Formater les messages pour le LLM + # Assurer que la langue est bien passée dans le format du prompt + selected_language = LANGUAGE_MAPPING.get(language, "français") messages = prompt_template.format_messages( chat_history=history_text, context=context, question=message, - language=LANGUAGE_MAPPING.get(language, "français") + language=selected_language ) # 5. Créer un handler de streaming personnalisé @@ -255,8 +273,9 @@ def process_query(message, history, streaming, show_sources, max_images, languag # Nettoyer la réponse uniquement pour l'affichage (pas pour l'historique interne) clean_response = clean_llm_response(partial_response) - history[-1] = (message, clean_response) - yield history, "", None, None + # Mettre à jour le dernier message (assistant) + messages_history[-1]["content"] = clean_response + yield messages_history, "", None, None except queue.Empty: continue @@ -310,17 +329,25 @@ def process_query(message, history, streaming, show_sources, max_images, languag # 13. Retourner les résultats finaux images_display = display_images() tables_display = display_tables() - yield history, source_info, images_display, tables_display + yield messages_history, source_info, images_display, tables_display else: # Version sans streaming print("Mode non-streaming activé") source_info = "" - result = rag_bot.chat(message, stream=False) + result = rag_bot.chat( + message, + stream=False, + language=LANGUAGE_MAPPING.get(language, "français") # Vérifiez que cette ligne existe + ) # Nettoyer la réponse des balises result["response"] = clean_llm_response(result["response"]) - history = history + [(message, result["response"])] + + # Convertir l'historique au format messages + messages_history = convert_to_messages_format(history) + messages_history.append({"role": "user", "content": message}) + messages_history.append({"role": "assistant", "content": result["response"]}) # Mise à jour de l'historique interne rag_bot.chat_history.append({"role": "user", "content": message}) @@ -364,7 +391,7 @@ def process_query(message, history, streaming, show_sources, max_images, languag "description": table.get("description", "") }) - yield history, source_info, display_images(), display_tables() + yield messages_history, source_info, display_images(), display_tables() except Exception as e: error_msg = f"Une erreur est survenue: {str(e)}" @@ -382,4 +409,5 @@ def reset_conversation(): rag_bot.clear_history() + # Retourner une liste vide au format messages return [], "", None, None \ No newline at end of file diff --git a/components/ui.py b/components/ui.py index 78fb7e3..2f26cbb 100644 --- a/components/ui.py +++ b/components/ui.py @@ -1,11 +1,58 @@ import gradio as gr from config.settings import DEFAULT_MODEL, QDRANT_COLLECTION_NAME, AVAILABLE_MODELS -from translations.lang_mappings import UI_TRANSLATIONS, UI_SUPPORTED_LANGUAGES +from translations.lang_mappings import UI_TRANSLATIONS, UI_SUPPORTED_LANGUAGES, LANGUAGE_MAPPING from utils.katex_script import KATEX_CSS_JS def update_ui_language_elements(language): - """Met à jour les éléments de l'interface utilisateur en fonction de la langue sélectionnée""" - pass # Implémentez selon vos besoins + """Met à jour tous les éléments de l'interface avec la langue sélectionnée""" + + # Vérifier si la langue est supportée par l'interface + if language not in UI_SUPPORTED_LANGUAGES: + language = "Français" # Langue par défaut + + # Récupérer les traductions pour la langue sélectionnée + translations = UI_TRANSLATIONS[language] + + # Créer un dictionnaire pour stocker tous les éléments modifiés + ui_elements = {} + + # Mettre à jour le titre + ui_elements["title"] = translations["title"] + + # Mettre à jour le placeholder et les boutons + ui_elements["placeholder"] = translations["placeholder"] + ui_elements["send_btn"] = translations["send_btn"] + ui_elements["clear_btn"] = translations["clear_btn"] + + # Ajouter les traductions pour la langue de l'interface + ui_elements["ui_language_label"] = translations["ui_language_label"] + ui_elements["ui_language_info"] = translations["ui_language_info"] + + # Mettre à jour les libellés des options + ui_elements["options_label"] = "Options" # Ce texte pourrait aussi être traduit + ui_elements["model_label"] = translations["model_selector"] + ui_elements["model_info"] = translations["model_info"] + ui_elements["model_current_prefix"] = translations["model_current"] + + ui_elements["language_label"] = translations["language_selector"] + ui_elements["language_info"] = translations["language_info"] + + ui_elements["collection_label"] = translations["collection_input"] + ui_elements["collection_info"] = translations["collection_info"] + ui_elements["collection_current_prefix"] = translations["collection_current"] + ui_elements["apply_btn"] = translations["apply_btn"] + + ui_elements["streaming_label"] = translations["streaming_label"] + ui_elements["streaming_info"] = translations["streaming_info"] + ui_elements["sources_label"] = translations["sources_label"] + ui_elements["max_images_label"] = translations["max_images_label"] + + ui_elements["images_title"] = translations["images_title"] + ui_elements["tables_title"] = translations["tables_title"] + + return ui_elements + + def build_interface( process_query_fn, @@ -14,102 +61,139 @@ def build_interface( change_collection_fn, update_ui_language_fn ): - """Construit l'interface utilisateur avec Gradio.""" + """Construit l'interface utilisateur avec Gradio""" + print("Initialisation de l'interface") + print("AVAILABLE_MODELS chargé dans ui.py:", AVAILABLE_MODELS) + # Initialiser avec la langue par défaut (Français) + ui_elements = update_ui_language_elements("Français") + with gr.Blocks(css=KATEX_CSS_JS, theme=gr.themes.Soft(primary_hue="blue")) as interface: - gr.Markdown("# 📚 Assistant documentaire intelligent") + title_md = gr.Markdown(f"# {ui_elements['title']}") with gr.Row(): with gr.Column(scale=2): - # Chatbot principal chat_interface = gr.Chatbot( height=600, show_label=False, layout="bubble", - elem_id="chatbot" + elem_id="chatbot", + type="messages" # Ajoutez cette ligne ) with gr.Row(): msg = gr.Textbox( show_label=False, - placeholder="Posez votre question...", + placeholder=ui_elements['placeholder'], container=False, scale=4 ) - submit_btn = gr.Button("Envoyer", variant="primary", scale=1) + submit_btn = gr.Button(ui_elements['send_btn'], variant="primary", scale=1) - clear_btn = gr.Button("Effacer la conversation") + clear_btn = gr.Button(ui_elements['clear_btn']) source_info = gr.Markdown("", elem_id="sources_info") with gr.Column(scale=1): with gr.Accordion("Options", open=True): - # Sélecteur de modèle + # Sélecteur de langue pour l'interface + language_ui_selector = gr.Dropdown( + choices=UI_SUPPORTED_LANGUAGES, + value="Français", + label=ui_elements['ui_language_label'], # Utiliser une clé différente + info=ui_elements['ui_language_info'] + ) + # Sélecteur de modèle - assurez-vous que cette section est présente model_selector = gr.Dropdown( choices=AVAILABLE_MODELS, value=DEFAULT_MODEL, - label="Modèle Ollama", - info="Choisir le modèle de language à utiliser" + label=ui_elements['model_label'], + info=ui_elements['model_info'] ) - model_status = gr.Markdown(f"Modèle actuel: **{DEFAULT_MODEL}**") + model_status = gr.Markdown(f"{ui_elements['model_current_prefix']}: **{DEFAULT_MODEL}**") - # Sélecteur de langue + # Sélecteur de langue pour les réponses language_selector = gr.Dropdown( - choices=UI_SUPPORTED_LANGUAGES, - value=UI_SUPPORTED_LANGUAGES[0], - label="Langue des réponses", - info="Choisir la langue dans laquelle l'assistant répondra" + choices=list(LANGUAGE_MAPPING.keys()), + value="Français", + label=ui_elements['language_label'], + info=ui_elements['language_info'] ) # Sélecteur de collection Qdrant collection_name_input = gr.Textbox( value=QDRANT_COLLECTION_NAME, - label="Collection Qdrant", - info="Nom de la collection de documents à utiliser" + label=ui_elements['collection_label'], + info=ui_elements['collection_info'] ) - collection_status = gr.Markdown(f"Collection actuelle: **{QDRANT_COLLECTION_NAME}**") + collection_status = gr.Markdown(f"{ui_elements['collection_current_prefix']}: **{QDRANT_COLLECTION_NAME}**") - # Bouton d'application de la collection - apply_collection_btn = gr.Button("Appliquer la collection") + # Bouton pour appliquer la collection + apply_collection_btn = gr.Button(ui_elements['apply_btn']) + # Options de streaming et sources streaming = gr.Checkbox( - label="Mode streaming", + label=ui_elements['streaming_label'], value=True, - info="Voir les réponses s'afficher progressivement" + info=ui_elements['streaming_info'] ) - show_sources = gr.Checkbox(label="Afficher les sources", value=True) + show_sources = gr.Checkbox(label=ui_elements['sources_label'], value=True) max_images = gr.Slider( minimum=1, maximum=10, value=3, step=1, - label="Nombre max d'images" + label=ui_elements['max_images_label'] ) gr.Markdown("---") - gr.Markdown("### 🖼️ Images pertinentes") + images_title = gr.Markdown(f"### {ui_elements['images_title']}") image_gallery = gr.Gallery( - label="Images pertinentes", + label=ui_elements['images_title'], show_label=False, columns=2, height=300, object_fit="contain" ) - gr.Markdown("### 📊 Tableaux") + tables_title = gr.Markdown(f"### {ui_elements['tables_title']}") tables_display = gr.HTML() - # Connecter le changement de modèle - model_selector.change( - fn=change_model_fn, - inputs=model_selector, - outputs=model_status - ) - - # Connecter le changement de collection - apply_collection_btn.click( - fn=change_collection_fn, - inputs=collection_name_input, - outputs=collection_status + # Ajouter cette fonction juste avant de connecter le changement de langue + def preserve_models_wrapper(language): + """Préserve la liste des modèles lors du changement de langue""" + # Obtenir les mises à jour depuis la fonction d'origine + updates = update_ui_language_fn(language) + + # Force la liste complète des modèles disponibles (position 5 dans les sorties) + # Cela garantit que quelles que soient les mises à jour, la liste des modèles reste intacte + if isinstance(updates[5], dict) and "choices" in updates[5]: + print("Préservation de la liste des modèles:", AVAILABLE_MODELS) + updates[5]["choices"] = AVAILABLE_MODELS + + return updates + + # Puis modifier la connexion du language_ui_selector.change comme suit : + language_ui_selector.change( + fn=preserve_models_wrapper, # Utiliser notre wrapper au lieu de la fonction directe + inputs=language_ui_selector, + outputs=[ + title_md, + msg, + submit_btn, + clear_btn, + language_ui_selector, + model_selector, + model_status, + language_selector, + collection_name_input, + collection_status, + apply_collection_btn, + streaming, + show_sources, + max_images, + images_title, + tables_title + ] ) # Fonction pour effacer l'entrée @@ -134,6 +218,20 @@ def build_interface( outputs=[chat_interface, source_info, image_gallery, tables_display] ) + # Connecter le changement de modèle + model_selector.change( + fn=change_model_fn, + inputs=model_selector, + outputs=model_status + ) + + # Connecter le changement de collection + apply_collection_btn.click( + fn=change_collection_fn, + inputs=collection_name_input, + outputs=collection_status + ) + # Style KaTeX et amélioration du design gr.Markdown("""