| import gradio as gr |
| import pandas as pd |
| import plotly.express as px |
| import plotly.graph_objects as go |
| from datasets import load_dataset |
|
|
| |
| |
| |
|
|
| 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", |
| }, |
| } |
|
|
| |
| |
| |
|
|
| 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_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 |
|
|
|
|
| |
| |
| |
|
|
| 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(): |
| |
| 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, |
| ) |
|
|
| |
| 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) |
|
|
| |
| 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) |
|
|
| |
| 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() |
|
|