| | import gradio as gr |
| | import pandas as pd |
| | import plotly.express as px |
| | import plotly.graph_objects as go |
| | from datasets import load_dataset |
| |
|
| | |
| | |
| | |
| |
|
| | DATASETS = { |
| | "EN": "AYI-NEDJIMI/ai-code-multimodal-en", |
| | "FR": "AYI-NEDJIMI/ai-code-multimodal-fr", |
| | } |
| |
|
| | _cache: dict[str, pd.DataFrame] = {} |
| |
|
| |
|
| | def get_df(lang: str = "EN") -> pd.DataFrame: |
| | if lang not in _cache: |
| | ds = load_dataset(DATASETS[lang], split="train") |
| | _cache[lang] = ds.to_pandas() |
| | return _cache[lang] |
| |
|
| |
|
| | |
| | for _l in DATASETS: |
| | get_df(_l) |
| |
|
| | COLS_TABLE = ["name", "vendor", "category", "subcategory", "pricing"] |
| | DETAIL_FIELDS = [ |
| | "name", "vendor", "category", "subcategory", "description", |
| | "features", "strengths", "weaknesses", "ide_support", |
| | "privacy_model", "supported_languages", "pricing", "source_url", |
| | ] |
| |
|
| | LABELS = { |
| | "EN": { |
| | "title": "AI Code & Multimodal Explorer", |
| | "tab_explorer": "Tools Explorer", |
| | "tab_details": "Details", |
| | "tab_compare": "Comparison", |
| | "tab_stats": "Statistics", |
| | "search": "Search by name or vendor...", |
| | "category": "Category", |
| | "all_categories": "All categories", |
| | "select_tool": "Select a tool", |
| | "select_tool_a": "Tool A", |
| | "select_tool_b": "Tool B", |
| | "compare_btn": "Compare", |
| | "no_tool": "No tool selected.", |
| | "chart_cat": "Tools by Category", |
| | "chart_vendor": "Top 15 Vendors by Number of Tools", |
| | "chart_pricing": "Pricing Distribution", |
| | "chart_cat_vendor": "Category × Vendor Heatmap", |
| | }, |
| | "FR": { |
| | "title": "AI Code & Multimodal Explorer", |
| | "tab_explorer": "Explorateur", |
| | "tab_details": "Détails", |
| | "tab_compare": "Comparaison", |
| | "tab_stats": "Statistiques", |
| | "search": "Rechercher par nom ou éditeur...", |
| | "category": "Catégorie", |
| | "all_categories": "Toutes les catégories", |
| | "select_tool": "Sélectionner un outil", |
| | "select_tool_a": "Outil A", |
| | "select_tool_b": "Outil B", |
| | "compare_btn": "Comparer", |
| | "no_tool": "Aucun outil sélectionné.", |
| | "chart_cat": "Outils par catégorie", |
| | "chart_vendor": "Top 15 éditeurs par nombre d'outils", |
| | "chart_pricing": "Répartition des tarifs", |
| | "chart_cat_vendor": "Carte thermique Catégorie × Éditeur", |
| | }, |
| | } |
| |
|
| | |
| | |
| | |
| |
|
| | def filter_table(search: str, category: str, lang: str) -> pd.DataFrame: |
| | df = get_df(lang) |
| | if category and category != LABELS[lang]["all_categories"]: |
| | df = df[df["category"] == category] |
| | if search: |
| | mask = ( |
| | df["name"].str.contains(search, case=False, na=False) |
| | | df["vendor"].str.contains(search, case=False, na=False) |
| | ) |
| | df = df[mask] |
| | return df[COLS_TABLE].reset_index(drop=True) |
| |
|
| |
|
| | def get_tool_names(lang: str) -> list[str]: |
| | return sorted(get_df(lang)["name"].dropna().unique().tolist()) |
| |
|
| |
|
| | def get_categories(lang: str) -> list[str]: |
| | cats = sorted(get_df(lang)["category"].dropna().unique().tolist()) |
| | return [LABELS[lang]["all_categories"]] + cats |
| |
|
| |
|
| | def tool_detail_md(name: str | None, lang: str) -> str: |
| | if not name: |
| | return LABELS[lang]["no_tool"] |
| | df = get_df(lang) |
| | rows = df[df["name"] == name] |
| | if rows.empty: |
| | return LABELS[lang]["no_tool"] |
| | r = rows.iloc[0] |
| | lines = [] |
| | for f in DETAIL_FIELDS: |
| | val = r.get(f, "") |
| | if pd.isna(val) or val == "": |
| | val = "—" |
| | label = f.replace("_", " ").title() |
| | if f == "source_url" and val != "—": |
| | lines.append(f"**{label}:** [{val}]({val})") |
| | else: |
| | lines.append(f"**{label}:** {val}") |
| | return "\n\n".join(lines) |
| |
|
| |
|
| | def compare_tools(name_a: str | None, name_b: str | None, lang: str) -> str: |
| | if not name_a or not name_b: |
| | return LABELS[lang]["no_tool"] |
| | md_a = tool_detail_md(name_a, lang) |
| | md_b = tool_detail_md(name_b, lang) |
| | sep = "\n\n---\n\n" |
| | return f"## {name_a}\n\n{md_a}{sep}## {name_b}\n\n{md_b}" |
| |
|
| |
|
| | |
| | |
| | |
| |
|
| | def chart_category(lang: str): |
| | df = get_df(lang) |
| | counts = df["category"].value_counts().reset_index() |
| | counts.columns = ["category", "count"] |
| | fig = px.bar( |
| | counts, x="category", y="count", color="category", |
| | title=LABELS[lang]["chart_cat"], |
| | color_discrete_sequence=px.colors.qualitative.Set2, |
| | ) |
| | fig.update_layout(showlegend=False, template="plotly_white") |
| | return fig |
| |
|
| |
|
| | def chart_vendor(lang: str): |
| | df = get_df(lang) |
| | counts = df["vendor"].value_counts().head(15).reset_index() |
| | counts.columns = ["vendor", "count"] |
| | fig = px.bar( |
| | counts, x="count", y="vendor", orientation="h", |
| | title=LABELS[lang]["chart_vendor"], |
| | color="count", color_continuous_scale="Viridis", |
| | ) |
| | fig.update_layout(yaxis=dict(autorange="reversed"), template="plotly_white") |
| | return fig |
| |
|
| |
|
| | def chart_pricing(lang: str): |
| | df = get_df(lang) |
| | counts = df["pricing"].value_counts().reset_index() |
| | counts.columns = ["pricing", "count"] |
| | fig = px.pie( |
| | counts, names="pricing", values="count", |
| | title=LABELS[lang]["chart_pricing"], |
| | color_discrete_sequence=px.colors.qualitative.Pastel, |
| | ) |
| | fig.update_layout(template="plotly_white") |
| | return fig |
| |
|
| |
|
| | def chart_heatmap(lang: str): |
| | df = get_df(lang) |
| | ct = pd.crosstab(df["category"], df["vendor"]) |
| | top_vendors = df["vendor"].value_counts().head(12).index.tolist() |
| | ct = ct[[v for v in top_vendors if v in ct.columns]] |
| | fig = go.Figure(data=go.Heatmap( |
| | z=ct.values, x=ct.columns.tolist(), y=ct.index.tolist(), |
| | colorscale="YlOrRd", |
| | )) |
| | fig.update_layout( |
| | title=LABELS[lang]["chart_cat_vendor"], |
| | template="plotly_white", height=400, |
| | ) |
| | return fig |
| |
|
| |
|
| | |
| | |
| | |
| |
|
| | FOOTER_HTML = """ |
| | <div style="text-align:center; padding:20px 0 10px 0; margin-top:30px; |
| | border-top:1px solid #444; font-size:0.9em; color:#aaa;"> |
| | <p>Built by <strong>AYI-NEDJIMI Consultants</strong></p> |
| | <p> |
| | <a href="https://ayinedjimi-consultants.fr" target="_blank" style="margin:0 8px;">🌐 Website</a> | |
| | <a href="https://www.linkedin.com/company/ayi-nedjimi" target="_blank" style="margin:0 8px;">LinkedIn</a> | |
| | <a href="https://github.com/AYI-NEDJIMI" target="_blank" style="margin:0 8px;">GitHub</a> | |
| | <a href="https://x.com/AyiNedjimi" target="_blank" style="margin:0 8px;">X / Twitter</a> |
| | </p> |
| | </div> |
| | """ |
| |
|
| | |
| | |
| | |
| |
|
| | theme = gr.themes.Soft( |
| | primary_hue="indigo", |
| | secondary_hue="purple", |
| | font=gr.themes.GoogleFont("Inter"), |
| | ) |
| |
|
| | with gr.Blocks(theme=theme, title="AI Code & Multimodal Explorer") as demo: |
| |
|
| | lang_state = gr.State("EN") |
| |
|
| | gr.Markdown("# AI Code & Multimodal Explorer") |
| |
|
| | with gr.Row(): |
| | lang_toggle = gr.Radio( |
| | choices=["EN", "FR"], value="EN", label="Language / Langue", |
| | interactive=True, |
| | ) |
| |
|
| | |
| | with gr.Tabs() as tabs: |
| | with gr.TabItem("Tools Explorer", id="explorer"): |
| | with gr.Row(): |
| | search_box = gr.Textbox( |
| | label="Search", placeholder="Search by name or vendor...", |
| | scale=3, |
| | ) |
| | cat_filter = gr.Dropdown( |
| | choices=get_categories("EN"), |
| | value=LABELS["EN"]["all_categories"], |
| | label="Category", scale=1, |
| | ) |
| | explorer_table = gr.Dataframe( |
| | value=filter_table("", LABELS["EN"]["all_categories"], "EN"), |
| | headers=COLS_TABLE, |
| | interactive=False, |
| | wrap=True, |
| | ) |
| |
|
| | |
| | with gr.TabItem("Details", id="details"): |
| | tool_select = gr.Dropdown( |
| | choices=get_tool_names("EN"), |
| | label="Select a tool", |
| | interactive=True, |
| | ) |
| | detail_output = gr.Markdown(LABELS["EN"]["no_tool"]) |
| |
|
| | |
| | with gr.TabItem("Comparison", id="compare"): |
| | with gr.Row(): |
| | tool_a = gr.Dropdown( |
| | choices=get_tool_names("EN"), label="Tool A", scale=1, |
| | ) |
| | tool_b = gr.Dropdown( |
| | choices=get_tool_names("EN"), label="Tool B", scale=1, |
| | ) |
| | compare_btn = gr.Button("Compare", variant="primary") |
| | compare_output = gr.Markdown(LABELS["EN"]["no_tool"]) |
| |
|
| | |
| | with gr.TabItem("Statistics", id="stats"): |
| | with gr.Row(): |
| | plot_cat = gr.Plot(value=chart_category("EN")) |
| | plot_vendor = gr.Plot(value=chart_vendor("EN")) |
| | with gr.Row(): |
| | plot_pricing = gr.Plot(value=chart_pricing("EN")) |
| | plot_heatmap = gr.Plot(value=chart_heatmap("EN")) |
| |
|
| | gr.HTML(FOOTER_HTML) |
| |
|
| | |
| |
|
| | def on_lang_change(lang): |
| | cats = get_categories(lang) |
| | names = get_tool_names(lang) |
| | default_cat = cats[0] |
| | table = filter_table("", default_cat, lang) |
| | return ( |
| | lang, |
| | gr.update(choices=cats, value=default_cat), |
| | table, |
| | gr.update(choices=names, value=None), |
| | LABELS[lang]["no_tool"], |
| | gr.update(choices=names, value=None), |
| | gr.update(choices=names, value=None), |
| | LABELS[lang]["no_tool"], |
| | chart_category(lang), |
| | chart_vendor(lang), |
| | chart_pricing(lang), |
| | chart_heatmap(lang), |
| | ) |
| |
|
| | lang_toggle.change( |
| | on_lang_change, inputs=[lang_toggle], |
| | outputs=[ |
| | lang_state, cat_filter, explorer_table, |
| | tool_select, detail_output, |
| | tool_a, tool_b, compare_output, |
| | plot_cat, plot_vendor, plot_pricing, plot_heatmap, |
| | ], |
| | ) |
| |
|
| | def on_filter(search, category, lang): |
| | return filter_table(search, category, lang) |
| |
|
| | search_box.change(on_filter, [search_box, cat_filter, lang_state], explorer_table) |
| | cat_filter.change(on_filter, [search_box, cat_filter, lang_state], explorer_table) |
| |
|
| | tool_select.change( |
| | lambda name, lang: tool_detail_md(name, lang), |
| | [tool_select, lang_state], detail_output, |
| | ) |
| |
|
| | compare_btn.click( |
| | lambda a, b, lang: compare_tools(a, b, lang), |
| | [tool_a, tool_b, lang_state], compare_output, |
| | ) |
| |
|
| | demo.launch() |
| |
|