AYI-NEDJIMI's picture
Initial RAG & LangChain Explorer Space with Gradio 5.50.0
30984ac verified
Raw
History Blame
11.1 kB
import gradio as gr
import pandas as pd
import plotly.express as px
import plotly.graph_objects as go
from datasets import load_dataset
# ---------------------------------------------------------------------------
# Data loading
# ---------------------------------------------------------------------------
COLUMNS = [
"id", "type", "category", "name", "content",
"details", "pros", "cons", "tools", "source_url", "language",
]
def load_data():
"""Load both FR and EN datasets and return as DataFrames."""
try:
ds_fr = load_dataset("AYI-NEDJIMI/rag-langchain-fr", split="train")
df_fr = ds_fr.to_pandas()
except Exception:
df_fr = pd.DataFrame(columns=COLUMNS)
try:
ds_en = load_dataset("AYI-NEDJIMI/rag-langchain-en", split="train")
df_en = ds_en.to_pandas()
except Exception:
df_en = pd.DataFrame(columns=COLUMNS)
return df_fr, df_en
DF_FR, DF_EN = load_data()
CATEGORIES = [
"document_loader", "text_splitter", "embedding_model",
"vector_store", "retriever", "chain",
]
LABELS = {
"FR": {
"title": "RAG & LangChain Explorer",
"explorer": "Explorateur",
"details": "Détails",
"qna": "Q&R",
"statistics": "Statistiques",
"search": "Rechercher…",
"category": "Catégorie",
"all": "Toutes",
"select_item": "Sélectionner un élément",
"no_results": "Aucun résultat.",
"by_category": "Répartition par catégorie",
"by_type": "Répartition par type",
"items_per_cat": "Nombre d'éléments par catégorie",
"ask": "Posez votre question sur RAG / LangChain",
"answer": "Réponse",
"qa_placeholder": "Ex : Qu'est-ce qu'un Text Splitter ?",
"no_match": "Aucune correspondance trouvée. Essayez un autre terme.",
"name_col": "Nom",
"type_col": "Type",
"category_col": "Catégorie",
"content_lbl": "Contenu",
"details_lbl": "Détails",
"pros_lbl": "Avantages",
"cons_lbl": "Inconvénients",
"tools_lbl": "Outils",
"source_lbl": "Source",
},
"EN": {
"title": "RAG & LangChain Explorer",
"explorer": "Explorer",
"details": "Details",
"qna": "Q&A",
"statistics": "Statistics",
"search": "Search…",
"category": "Category",
"all": "All",
"select_item": "Select an item",
"no_results": "No results.",
"by_category": "Distribution by category",
"by_type": "Distribution by type",
"items_per_cat": "Number of items per category",
"ask": "Ask a question about RAG / LangChain",
"answer": "Answer",
"qa_placeholder": "E.g. What is a Text Splitter?",
"no_match": "No match found. Try another term.",
"name_col": "Name",
"type_col": "Type",
"category_col": "Category",
"content_lbl": "Content",
"details_lbl": "Details",
"pros_lbl": "Pros",
"cons_lbl": "Cons",
"tools_lbl": "Tools",
"source_lbl": "Source",
},
}
# ---------------------------------------------------------------------------
# Helpers
# ---------------------------------------------------------------------------
def get_df(lang: str) -> pd.DataFrame:
return DF_FR.copy() if lang == "FR" else DF_EN.copy()
def filter_table(search: str, category: str, lang: str):
df = get_df(lang)
if df.empty:
return pd.DataFrame()
if category and category not in ("Toutes", "All"):
df = df[df["category"] == category]
if search:
mask = df.apply(
lambda r: search.lower() in " ".join(r.astype(str)).lower(), axis=1
)
df = df[mask]
display_cols = ["name", "type", "category"]
available = [c for c in display_cols if c in df.columns]
return df[available].reset_index(drop=True)
def get_item_names(lang: str):
df = get_df(lang)
if df.empty:
return []
return sorted(df["name"].dropna().unique().tolist())
def get_item_details(name: str, lang: str):
L = LABELS[lang]
df = get_df(lang)
if df.empty or not name:
return L["no_results"]
row = df[df["name"] == name]
if row.empty:
return L["no_results"]
r = row.iloc[0]
parts = []
parts.append(f"## {r.get('name', '')}")
parts.append(f"**{L['category_col']}**: {r.get('category', '')}")
parts.append(f"**{L['type_col']}**: {r.get('type', '')}")
if pd.notna(r.get("content")):
parts.append(f"\n### {L['content_lbl']}\n{r['content']}")
if pd.notna(r.get("details")):
parts.append(f"\n### {L['details_lbl']}\n{r['details']}")
if pd.notna(r.get("pros")):
parts.append(f"\n### {L['pros_lbl']}\n{r['pros']}")
if pd.notna(r.get("cons")):
parts.append(f"\n### {L['cons_lbl']}\n{r['cons']}")
if pd.notna(r.get("tools")):
parts.append(f"\n### {L['tools_lbl']}\n{r['tools']}")
if pd.notna(r.get("source_url")):
parts.append(f"\n### {L['source_lbl']}\n[Link]({r['source_url']})")
return "\n".join(parts)
def answer_question(question: str, lang: str):
L = LABELS[lang]
if not question or not question.strip():
return ""
df = get_df(lang)
if df.empty:
return L["no_match"]
q = question.lower()
# Search across content-heavy columns
search_cols = ["name", "content", "details", "category", "type"]
scores = []
for _, row in df.iterrows():
text = " ".join(str(row.get(c, "")) for c in search_cols).lower()
score = sum(1 for w in q.split() if w in text)
scores.append(score)
df = df.copy()
df["_score"] = scores
best = df.sort_values("_score", ascending=False).head(3)
best = best[best["_score"] > 0]
if best.empty:
return L["no_match"]
parts = []
for _, r in best.iterrows():
parts.append(f"### {r.get('name', '')}")
parts.append(f"**{L['category_col']}**: {r.get('category', '')}")
if pd.notna(r.get("content")):
parts.append(f"{r['content'][:500]}")
parts.append("---")
return "\n".join(parts)
def make_category_chart(lang: str):
L = LABELS[lang]
df = get_df(lang)
if df.empty:
return go.Figure()
counts = df["category"].value_counts().reset_index()
counts.columns = ["category", "count"]
fig = px.bar(
counts, x="category", y="count",
title=L["items_per_cat"],
color="category",
color_discrete_sequence=px.colors.qualitative.Set2,
)
fig.update_layout(showlegend=False, xaxis_title="", yaxis_title="")
return fig
def make_type_chart(lang: str):
L = LABELS[lang]
df = get_df(lang)
if df.empty:
return go.Figure()
counts = df["type"].value_counts().reset_index()
counts.columns = ["type", "count"]
fig = px.pie(
counts, names="type", values="count",
title=L["by_type"],
color_discrete_sequence=px.colors.qualitative.Pastel,
)
return fig
def make_category_pie(lang: str):
L = LABELS[lang]
df = get_df(lang)
if df.empty:
return go.Figure()
counts = df["category"].value_counts().reset_index()
counts.columns = ["category", "count"]
fig = px.pie(
counts, names="category", values="count",
title=L["by_category"],
color_discrete_sequence=px.colors.qualitative.Set2,
)
return fig
# ---------------------------------------------------------------------------
# UI
# ---------------------------------------------------------------------------
FOOTER_HTML = """
<div style="text-align:center; padding:20px; margin-top:30px; border-top:1px solid #444; color:#888; font-size:0.9em;">
Built by <a href="https://ayinedjimi-consultants.fr" target="_blank"
style="color:#7c8aff; text-decoration:none;">AYI-NEDJIMI Consultants</a>
</div>
"""
with gr.Blocks(
title="RAG & LangChain Explorer",
theme=gr.themes.Soft(primary_hue="blue", secondary_hue="purple"),
) as demo:
gr.Markdown("# RAG & LangChain Explorer")
lang_toggle = gr.Radio(
choices=["FR", "EN"], value="FR", label="Language / Langue",
interactive=True,
)
with gr.Tabs():
# ---- Explorer tab ----
with gr.Tab("Explorer / Explorateur"):
with gr.Row():
search_box = gr.Textbox(
label="Search…", placeholder="Search…", scale=3,
)
cat_filter = gr.Dropdown(
choices=["Toutes"] + CATEGORIES,
value="Toutes",
label="Category",
scale=1,
)
table_output = gr.Dataframe(
value=filter_table("", "Toutes", "FR"),
interactive=False,
)
search_box.change(
filter_table, [search_box, cat_filter, lang_toggle], table_output,
)
cat_filter.change(
filter_table, [search_box, cat_filter, lang_toggle], table_output,
)
lang_toggle.change(
filter_table, [search_box, cat_filter, lang_toggle], table_output,
)
# ---- Details tab ----
with gr.Tab("Details / Détails"):
item_dropdown = gr.Dropdown(
choices=get_item_names("FR"),
label="Select an item / Sélectionner un élément",
interactive=True,
)
detail_output = gr.Markdown()
item_dropdown.change(
get_item_details, [item_dropdown, lang_toggle], detail_output,
)
def refresh_names(lang):
return gr.update(choices=get_item_names(lang), value=None)
lang_toggle.change(refresh_names, lang_toggle, item_dropdown)
# ---- Q&A tab ----
with gr.Tab("Q&A / Q&R"):
qa_input = gr.Textbox(
label="Ask a question / Posez votre question",
placeholder="E.g. What is a Text Splitter?",
lines=2,
)
qa_btn = gr.Button("Search / Rechercher")
qa_output = gr.Markdown()
qa_btn.click(answer_question, [qa_input, lang_toggle], qa_output)
qa_input.submit(answer_question, [qa_input, lang_toggle], qa_output)
# ---- Statistics tab ----
with gr.Tab("Statistics / Statistiques"):
with gr.Row():
cat_bar = gr.Plot(value=make_category_chart("FR"))
cat_pie = gr.Plot(value=make_category_pie("FR"))
with gr.Row():
type_pie = gr.Plot(value=make_type_chart("FR"))
def refresh_stats(lang):
return (
make_category_chart(lang),
make_category_pie(lang),
make_type_chart(lang),
)
lang_toggle.change(refresh_stats, lang_toggle, [cat_bar, cat_pie, type_pie])
gr.HTML(FOOTER_HTML)
if __name__ == "__main__":
demo.launch()