polyglot-rag / app.py
nvikou's picture
Deploy PolyglotRAG v1.1
2697b4c verified
Raw History Blame Contribute Delete
7.48 kB
"""
PolyglotRAG — assistant documentaire multilingue cross-lingue.
Point d'entrée de l'application Gradio (Hugging Face Spaces, SDK docker).
Onglets :
- Assistant : conversation multilingue avec citations et analyse.
- Architecture : schéma du pipeline complet.
- Administration: métriques du RAG (volume, latence, langues, abstention).
L'interface elle-même est traduite dans les 6 langues du projet
(sélecteur en haut à droite ; choix mémorisé dans le navigateur, détection
automatique de la langue du navigateur, paramètre d'URL `?lang=xx`).
"""
from __future__ import annotations
import json
import os
from pathlib import Path
import gradio as gr
from config import settings
from core.retrieval.vector_store import get_vector_store
from core.ui.admin_tab import admin_i18n_updates, build_admin_tab
from core.ui.architecture_tab import architecture_i18n_updates, build_architecture_tab
from core.ui.chat_tab import build_chat_tab, chat_i18n_updates
from core.ui.components import (
render_brand,
render_config_banner,
render_footer,
render_status,
)
from core.ui.i18n import UI_LANGUAGES, normalize_lang, t, ui_language_choices
from core.ui.theme import CUSTOM_CSS, HEAD_HTML, build_theme
ASSETS_DIR = Path(__file__).resolve().parent / "core" / "assets"
AVATAR_PATH = ASSETS_DIR / "assistant.svg"
DEFAULT_LANG = normalize_lang(settings.default_ui_language)
_TAB_KEYS = ("tab_chat", "tab_arch", "tab_admin")
_TAB_LABELS_JSON = json.dumps(
{code: [t(code, k) for k in _TAB_KEYS] for code in UI_LANGUAGES}, ensure_ascii=False
)
_SUPPORTED_JSON = json.dumps(list(UI_LANGUAGES))
# Applique côté navigateur : sens d'écriture (RTL pour l'arabe), attribut
# `lang` du document, libellés des onglets et mémorisation du choix.
APPLY_LANG_JS = f"""
(lang) => {{
const labels = {_TAB_LABELS_JSON};
const root = document.querySelector('.gradio-container');
if (root) root.setAttribute('dir', lang === 'ar' ? 'rtl' : 'ltr');
document.documentElement.setAttribute('lang', lang);
try {{ localStorage.setItem('pg-ui-lang', lang); }} catch (e) {{}}
const tabs = document.querySelectorAll('#pg-tabs button[role="tab"]');
if (labels[lang] && tabs.length === labels[lang].length) {{
tabs.forEach((b, i) => {{ b.textContent = labels[lang][i]; }});
}}
return lang;
}}
"""
# Au chargement : ?lang=xx > choix mémorisé > langue du navigateur > défaut.
INIT_LANG_JS = f"""
() => {{
const supported = {_SUPPORTED_JSON};
let lang = new URLSearchParams(window.location.search).get('lang');
if (!lang) {{ try {{ lang = localStorage.getItem('pg-ui-lang'); }} catch (e) {{}} }}
if (!lang) lang = (navigator.language || '').slice(0, 2).toLowerCase();
if (!supported.includes(lang)) lang = '{DEFAULT_LANG}';
const root = document.querySelector('.gradio-container');
if (root) root.setAttribute('dir', lang === 'ar' ? 'rtl' : 'ltr');
document.documentElement.setAttribute('lang', lang);
return lang;
}}
"""
def _config_ok() -> bool:
return not settings.validate()
def status_html(lang: str) -> str:
try:
corpus = get_vector_store().corpus_stats()
except Exception: # noqa: BLE001 - l'en-tête ne doit jamais casser l'app
corpus = {"total_chunks": 0, "documents": 0}
return render_status(normalize_lang(lang), corpus, _config_ok())
def build_app() -> gr.Blocks:
lang = DEFAULT_LANG
config_ok = _config_ok()
with gr.Blocks(
title="PolyglotRAG — Multilingual Document Assistant",
theme=build_theme(),
css=CUSTOM_CSS,
head=HEAD_HTML,
analytics_enabled=False,
) as demo:
# ------------------------------ En-tête ------------------------------
with gr.Row(elem_id="pg-header", equal_height=True):
with gr.Column(scale=1, min_width=320):
brand = gr.HTML(render_brand(lang), elem_classes=["pg-html-flush"])
with gr.Column(scale=0, min_width=240, elem_id="pg-header-right"):
ui_lang_dd = gr.Dropdown(
choices=ui_language_choices(),
value=lang,
label=t(lang, "ui_language"),
show_label=False,
container=False,
interactive=True,
elem_id="pg-ui-lang",
)
status = gr.HTML(
render_status(lang, {"total_chunks": 0}, config_ok) if not config_ok else "",
elem_classes=["pg-html-flush"],
)
banner = gr.HTML(render_config_banner(lang, config_ok), elem_classes=["pg-html-flush"])
# ------------------------------ Onglets ------------------------------
with gr.Tabs(elem_id="pg-tabs"):
with gr.Tab(t(lang, "tab_chat"), id="chat") as tab_chat:
chat = build_chat_tab(
lang, ui_lang_dd, str(AVATAR_PATH) if AVATAR_PATH.exists() else None
)
with gr.Tab(t(lang, "tab_arch"), id="architecture") as tab_arch:
arch = build_architecture_tab(lang)
with gr.Tab(t(lang, "tab_admin"), id="admin") as tab_admin:
admin = build_admin_tab(lang, ui_lang_dd)
footer = gr.HTML(render_footer(lang), elem_classes=["pg-html-flush"])
# ------------------------ Changement de langue ------------------------
def apply_language(new_lang, last, admin_state, response_value, scope_value):
new_lang = normalize_lang(new_lang)
return [
render_brand(new_lang),
status_html(new_lang),
render_config_banner(new_lang, _config_ok()),
gr.update(label=t(new_lang, "tab_chat")),
gr.update(label=t(new_lang, "tab_arch")),
gr.update(label=t(new_lang, "tab_admin")),
render_footer(new_lang),
*chat_i18n_updates(new_lang, last, response_value, scope_value),
*architecture_i18n_updates(new_lang),
*admin_i18n_updates(new_lang, admin_state),
]
i18n_outputs = [
brand,
status,
banner,
tab_chat,
tab_arch,
tab_admin,
footer,
*chat["i18n_outputs"],
*arch["i18n_outputs"],
*admin["i18n_outputs"],
]
ui_lang_dd.change(None, inputs=[ui_lang_dd], outputs=None, js=APPLY_LANG_JS)
ui_lang_dd.change(
apply_language,
inputs=[ui_lang_dd, chat["last_state"], admin["state"], chat["response_lang"], chat["scope"]],
outputs=i18n_outputs,
queue=False,
)
# ------------------------------ Chargement ----------------------------
demo.load(None, inputs=None, outputs=[ui_lang_dd], js=INIT_LANG_JS)
demo.load(status_html, inputs=[ui_lang_dd], outputs=[status])
return demo
demo = build_app()
if __name__ == "__main__":
# 0.0.0.0 + $PORT (par défaut 7860) : fonctionne à l'identique en local,
# dans le conteneur Docker fourni, et sur un Hugging Face Space.
demo.queue(default_concurrency_limit=8).launch(
server_name="0.0.0.0",
server_port=int(os.environ.get("PORT", 7860)),
show_api=False,
allowed_paths=[str(ASSETS_DIR)],
favicon_path=str(ASSETS_DIR / "favicon.svg") if (ASSETS_DIR / "favicon.svg").exists() else None,
)