Spaces:
Sleeping
Sleeping
| import streamlit as st | |
| import requests | |
| import json | |
| import pandas as pd | |
| import matplotlib.pyplot as plt | |
| import time | |
| import numpy as np | |
| import altair as alt | |
| from PIL import Image | |
| import plotly.graph_objects as go | |
| import plotly.express as px | |
| from streamlit_lottie import st_lottie | |
| import requests | |
| import random | |
| import plotly.graph_objects as go | |
| from streamlit_lottie import st_lottie | |
| # Configuration de la page | |
| st.set_page_config( | |
| page_title="API NLU Darija - Mohammed MEDIANI", | |
| page_icon="🚀", | |
| layout="wide", | |
| initial_sidebar_state="expanded" | |
| ) | |
| # Fonctions utilitaires | |
| def call_api(text): | |
| """Appelle l'API NLU Darija et retourne le résultat""" | |
| api_url = "https://mediani-darija-aicc-api.hf.space/predict" | |
| try: | |
| start_time = time.time() | |
| response = requests.post( | |
| api_url, | |
| headers={"Content-Type": "application/json"}, | |
| data=json.dumps({"text": text}) | |
| ) | |
| response_time = (time.time() - start_time) * 1000 # en ms | |
| if response.status_code == 200: | |
| result = response.json() | |
| return result, response_time | |
| else: | |
| return None, response_time | |
| except Exception as e: | |
| st.error(f"Erreur de connexion: {str(e)}") | |
| return None, 0 | |
| def get_intent_description(intent): | |
| """Retourne la description d'une intention""" | |
| descriptions = { | |
| "consulter_solde": "L'utilisateur souhaite connaître son solde ou crédit restant sur son compte.", | |
| "reclamer_facture": "L'utilisateur signale un problème avec sa facture ou conteste un montant facturé.", | |
| "declarer_panne": "L'utilisateur signale un dysfonctionnement technique avec son service ou équipement.", | |
| "info_forfait": "L'utilisateur demande des informations sur un forfait existant ou nouveau.", | |
| "recuperer_mot_de_passe": "L'utilisateur a besoin d'aide pour récupérer ou réinitialiser son mot de passe.", | |
| "salutations": "L'utilisateur salue le service client ou initie une conversation.", | |
| "remerciements": "L'utilisateur exprime sa gratitude pour l'aide reçue.", | |
| "demander_agent_humain": "L'utilisateur souhaite être mis en relation avec un conseiller humain.", | |
| "hors_scope": "La demande ne correspond à aucune intention prédéfinie dans notre système." | |
| } | |
| return descriptions.get(intent, "Description non disponible") | |
| def get_intent_icon(intent): | |
| """Retourne une icône associée à une intention""" | |
| icons = { | |
| "consulter_solde": "💰", | |
| "reclamer_facture": "📄", | |
| "declarer_panne": "🔧", | |
| "info_forfait": "ℹ️", | |
| "recuperer_mot_de_passe": "🔑", | |
| "salutations": "👋", | |
| "remerciements": "🙏", | |
| "demander_agent_humain": "👨💼", | |
| "hors_scope": "❓" | |
| } | |
| return icons.get(intent, "🔍") | |
| def get_intent_color(intent): | |
| """Retourne une couleur associée à une intention""" | |
| colors = { | |
| "consulter_solde": "#1f77b4", | |
| "reclamer_facture": "#ff7f0e", | |
| "declarer_panne": "#d62728", | |
| "info_forfait": "#2ca02c", | |
| "recuperer_mot_de_passe": "#9467bd", | |
| "salutations": "#8c564b", | |
| "remerciements": "#e377c2", | |
| "demander_agent_humain": "#7f7f7f", | |
| "hors_scope": "#bcbd22" | |
| } | |
| return colors.get(intent, "#17becf") | |
| def load_lottie(url): | |
| """Charge une animation Lottie depuis une URL""" | |
| try: | |
| r = requests.get(url) | |
| if r.status_code != 200: | |
| return None | |
| return r.json() | |
| except: | |
| return None | |
| # Charger les animations | |
| lottie_ai = load_lottie("https://assets8.lottiefiles.com/packages/lf20_ikvz7qhc.json") | |
| lottie_process = load_lottie("https://assets6.lottiefiles.com/packages/lf20_khzniaya.json") | |
| # Style CSS personnalisé | |
| st.markdown(""" | |
| <style> | |
| /* Style général */ | |
| .main-title { | |
| font-size: 2.8rem !important; | |
| color: #1E3A8A; | |
| padding-bottom: 0.5rem; | |
| border-bottom: 3px solid #3B82F6; | |
| font-weight: 700; | |
| text-shadow: 0px 2px 2px rgba(0,0,0,0.1); | |
| margin-bottom: 1.5rem; | |
| } | |
| .sub-title { | |
| font-size: 1.8rem !important; | |
| color: #1E3A8A; | |
| margin-top: 1.5rem; | |
| margin-bottom: 1rem; | |
| font-weight: 600; | |
| } | |
| .section-title { | |
| font-size: 1.4rem !important; | |
| color: #2563EB; | |
| margin-top: 1.2rem; | |
| margin-bottom: 0.8rem; | |
| font-weight: 500; | |
| } | |
| /* Boîtes d'information */ | |
| .info-box { | |
| background-color: #F8FAFC; | |
| padding: 1.5rem; | |
| border-radius: 0.75rem; | |
| border: 2px solid #E2E8F0; | |
| margin-bottom: 1.5rem; | |
| box-shadow: 0 4px 12px rgba(0,0,0,0.08); | |
| color: #1A202C; | |
| font-size: 16px; | |
| line-height: 1.7; | |
| } | |
| .info-box p { | |
| margin-bottom: 12px; | |
| font-weight: 500; | |
| } | |
| .info-box strong { | |
| color: #1E3A8A; | |
| font-weight: 700; | |
| } | |
| .info-box ul { | |
| margin-left: 20px; | |
| color: #4A5568; | |
| } | |
| .info-box li { | |
| margin-bottom: 8px; | |
| font-weight: 500; | |
| } | |
| .warning-box { | |
| background-color: #FEF3C7; | |
| padding: 1.2rem; | |
| border-radius: 0.5rem; | |
| border-left: 5px solid #F59E0B; | |
| margin-bottom: 1.5rem; | |
| box-shadow: 0 4px 6px rgba(0,0,0,0.05); | |
| } | |
| .success-box { | |
| background-color: #ECFDF5; | |
| padding: 1.2rem; | |
| border-radius: 0.5rem; | |
| border-left: 5px solid #10B981; | |
| margin-bottom: 1.5rem; | |
| box-shadow: 0 4px 6px rgba(0,0,0,0.05); | |
| } | |
| /* Boutons */ | |
| .stButton>button { | |
| border-radius: 0.5rem; | |
| font-weight: 500; | |
| transition: all 0.3s ease; | |
| } | |
| .stButton>button:hover { | |
| transform: translateY(-2px); | |
| box-shadow: 0 4px 8px rgba(0,0,0,0.1); | |
| } | |
| .example-button { | |
| margin: 0.3rem; | |
| } | |
| /* Tags et badges */ | |
| .intent-tag { | |
| background-color: #1E3A8A; | |
| color: white; | |
| padding: 0.4rem 1rem; | |
| border-radius: 2rem; | |
| font-weight: bold; | |
| display: inline-block; | |
| margin-bottom: 0.8rem; | |
| box-shadow: 0 2px 4px rgba(0,0,0,0.1); | |
| } | |
| .badge { | |
| padding: 0.2rem 0.6rem; | |
| border-radius: 2rem; | |
| font-size: 0.8rem; | |
| font-weight: bold; | |
| margin-left: 0.5rem; | |
| } | |
| /* Conteneurs */ | |
| .glass-container { | |
| background: rgba(255, 255, 255, 0.7); | |
| backdrop-filter: blur(10px); | |
| border-radius: 10px; | |
| border: 1px solid rgba(255, 255, 255, 0.18); | |
| padding: 1.5rem; | |
| box-shadow: 0 8px 32px 0 rgba(31, 38, 135, 0.37); | |
| } | |
| /* Animations */ | |
| @keyframes fadeIn { | |
| from { opacity: 0; } | |
| to { opacity: 1; } | |
| } | |
| .fade-in { | |
| animation: fadeIn 0.5s ease-in-out; | |
| } | |
| @keyframes slideInFromLeft { | |
| 0% { | |
| transform: translateX(-30px); | |
| opacity: 0; | |
| } | |
| 100% { | |
| transform: translateX(0); | |
| opacity: 1; | |
| } | |
| } | |
| .slide-in { | |
| animation: slideInFromLeft 0.5s ease-out; | |
| } | |
| /* Mise en page de l'en-tête */ | |
| .header-content { | |
| display: flex; | |
| align-items: center; | |
| margin-bottom: 1.5rem; | |
| } | |
| .description-container { | |
| flex: 3; | |
| padding-right: 1.5rem; | |
| } | |
| .image-container { | |
| flex: 2; | |
| display: flex; | |
| justify-content: center; | |
| align-items: center; | |
| } | |
| /* Responsive design */ | |
| @media (max-width: 768px) { | |
| .main-title { font-size: 2rem !important; } | |
| .sub-title { font-size: 1.5rem !important; } | |
| .section-title { font-size: 1.2rem !important; } | |
| .header-content { flex-direction: column; } | |
| .description-container { padding-right: 0; padding-bottom: 1.5rem; } | |
| } | |
| /* Table des performances */ | |
| .styled-table { | |
| width: 100%; | |
| border-collapse: collapse; | |
| margin: 1.5rem 0; | |
| border-radius: 8px; | |
| overflow: hidden; | |
| box-shadow: 0 0 20px rgba(0, 0, 0, 0.1); | |
| } | |
| .styled-table thead tr { | |
| background-color: #1E3A8A; | |
| color: white; | |
| text-align: left; | |
| } | |
| .styled-table th, | |
| .styled-table td { | |
| padding: 12px 15px; | |
| } | |
| .styled-table tbody tr { | |
| border-bottom: 1px solid #dddddd; | |
| } | |
| .styled-table tbody tr:nth-of-type(even) { | |
| background-color: #f9fafb; | |
| } | |
| .styled-table tbody tr:last-of-type { | |
| border-bottom: 2px solid #1E3A8A; | |
| } | |
| /* Masquer les éléments par défaut de Streamlit qu'on ne veut pas voir */ | |
| #MainMenu {visibility: hidden;} | |
| footer {visibility: hidden;} | |
| .viewerBadge_container__r5tak {display: none;} | |
| </style> | |
| """, unsafe_allow_html=True) | |
| # Configuration des colonnes principales | |
| header_col1, header_col2 = st.columns([2, 5]) | |
| # Logo et informations de base | |
| with header_col1: | |
| try: | |
| st.image("logo_est_nador.png", width=200) | |
| except: | |
| st.markdown("<h3>EST Nador</h3>", unsafe_allow_html=True) | |
| st.warning("Logo non trouvé. Placez 'logo_est_nador.png' dans le dossier du projet.") | |
| st.markdown('<div class="slide-in">', unsafe_allow_html=True) | |
| st.markdown("### Stage de Fin d'Études") | |
| st.markdown("**Étudiant:** Mohammed MEDIANI") | |
| st.markdown("**Filière:** IAID - EST Nador") | |
| st.markdown("**Date:** Juin 2025") | |
| st.markdown("</div>", unsafe_allow_html=True) | |
| st.markdown("---") | |
| st.markdown('<div class="fade-in">', unsafe_allow_html=True) | |
| st.markdown("### Encadrement") | |
| st.markdown("- **Pr. ACHSAS SANAE** (Académique)") | |
| st.markdown("- **Mme. Aya BENNANI** (Professionnelle)") | |
| st.markdown("</div>", unsafe_allow_html=True) | |
| st.markdown("---") | |
| st.markdown('<div class="fade-in">', unsafe_allow_html=True) | |
| st.markdown("### Informations sur l'API") | |
| st.markdown("**URL:** [mediani-darija-aicc-api.hf.space](https://mediani-darija-aicc-api.hf.space)") | |
| st.markdown("**Documentation:** [API Docs](https://mediani-darija-aicc-api.hf.space/docs)") | |
| st.markdown("</div>", unsafe_allow_html=True) | |
| # Titre principal et description | |
| with header_col2: | |
| st.markdown('<h1 class="main-title">API de NLU pour le Dialecte Marocain (Darija)</h1>', unsafe_allow_html=True) | |
| # Description du projet - Style raffiné et concis pour équilibrer avec la grande animation | |
| st.markdown(""" | |
| <div class="info-box fade-in" style="margin-bottom: 15px; padding: 1.2rem; border: 1px solid #E2E8F0;"> | |
| <p style="font-size: 16px; margin-bottom: 10px;">Ce projet vise à concevoir et déployer une <strong>API de compréhension du langage naturel (NLU)</strong> spécialisée pour la Darija marocaine. L'objectif est d'améliorer l'expérience client en permettant aux systèmes automatisés de comprendre les requêtes exprimées dans ce dialecte.</p> | |
| <p style="font-size: 16px; margin-bottom: 10px;">L'API identifie 9 intentions différentes et s'intègre avec la plateforme AICC de Huawei pour le traitement des requêtes clients.</p> | |
| <p style="font-size: 15px; color: #3B82F6; text-align: center;"><strong>✨ Cette démonstration interactive vous permet d'explorer les capacités de l'API en temps réel</strong></p> | |
| </div> | |
| """, unsafe_allow_html=True) | |
| # Animation Lottie centrée sous le texte - Taille agrandie pour remplir l'espace vertical | |
| st.markdown('<div style="display: flex; justify-content: center; align-items: center; margin-bottom: 20px; padding: 10px; background-color: rgba(240, 249, 255, 0.3); border-radius: 15px;">', unsafe_allow_html=True) | |
| if lottie_ai: | |
| st_lottie(lottie_ai, height=400, width=680, key="ai_animation", quality="high") | |
| st.markdown('</div>', unsafe_allow_html=True) | |
| # Espace réduit car l'animation est plus grande et remplit déjà bien l'espace | |
| st.markdown("<div style='height: 10px;'></div>", unsafe_allow_html=True) | |
| # Onglets principaux avec icônes | |
| tab1, tab2, tab3 = st.tabs(["🔍 Démonstration", "📊 Performances", "🏗️ Architecture"]) | |
| # Onglet Démonstration | |
| with tab1: | |
| st.markdown('<h2 class="sub-title slide-in">Testez l\'API en direct</h2>', unsafe_allow_html=True) | |
| # Description du service | |
| st.info(""" | |
| **Cette interface vous permet de tester en temps réel notre API de compréhension du langage naturel spécialisée pour la Darija marocaine.** | |
| **Instructions:** | |
| 1. Entrez un texte en Darija ou sélectionnez un exemple prédéfini | |
| 2. Cliquez sur le bouton "Analyser l'intention" | |
| 3. Observez les résultats de la détection d'intention | |
| L'API est optimisée pour comprendre la Darija dans ses différentes variantes et avec le code-switching (mélange avec le français). | |
| """) | |
| # Exemples prédéfinis | |
| st.markdown('<h3 class="section-title">Exemples à tester</h3>', unsafe_allow_html=True) | |
| # Organisation des exemples par catégories | |
| with st.expander("🔄 Exemples par catégorie d'intention", expanded=True): | |
| tab_expl1, tab_expl2, tab_expl3 = st.tabs(["Requêtes techniques", "Interactions", "Code-switching"]) | |
| with tab_expl1: | |
| exemples_tech = { | |
| "Consulter solde": "بغيت نعرف شحال باقي ليا في رصيدي", | |
| "Déclarer panne": "ماكيخدمش عندي لانترنيت هاذي شي سيمانة", | |
| "Réclamation facture": "فاكتورة هاد الشهر غالية بزاف، بغيت نشوف علاش", | |
| "Info forfait": "شنو هوما لوفر ديال لانترنيت لي كاينين دابا", | |
| "Récupérer mot de passe": "نسيت mon mot de passe ديالي واش يمكن تساعدني؟" | |
| } | |
| cols = st.columns(3) | |
| for i, (label, exemple) in enumerate(exemples_tech.items()): | |
| with cols[i % 3]: | |
| if st.button(f"{label}", key=f"tech_btn_{i}", help=exemple): | |
| st.session_state["user_input"] = exemple | |
| st.rerun() | |
| with tab_expl2: | |
| exemples_inter = { | |
| "Salutations": "salam 3lik bkhir", | |
| "Remerciement": "شكرا بزاف على المساعدة ديالكم، كنتو مزيانين معايا", | |
| "Demander agent": "Brit nhdar m3a service client ma bghitch robot", | |
| "Hors scope": "واش كاين شي طريقة باش نلعب تينيس فهاد الويكاند؟" | |
| } | |
| cols = st.columns(2) | |
| for i, (label, exemple) in enumerate(exemples_inter.items()): | |
| with cols[i % 2]: | |
| if st.button(f"{label}", key=f"inter_btn_{i}", help=exemple): | |
| st.session_state["user_input"] = exemple | |
| st.rerun() | |
| with tab_expl3: | |
| exemples_code = { | |
| "Solde (code-switching)": "بغيت نعرف le solde ديالي شحال باقي", | |
| "Panne (code-switching)": "عندي problème فالفاكتورة ديالي", | |
| "Mot de passe (code-switching)": "نسيت mon mot de passe ديالي واش يمكن تساعدني؟", | |
| "Salutations (code-switching)": "bonjour صاحبي، كيفاش يمكن لي نساعدك؟" | |
| } | |
| cols = st.columns(2) | |
| for i, (label, exemple) in enumerate(exemples_code.items()): | |
| with cols[i % 2]: | |
| if st.button(f"{label}", key=f"code_btn_{i}", help=exemple): | |
| st.session_state["user_input"] = exemple | |
| st.rerun() | |
| # Zone de texte pour l'entrée utilisateur | |
| st.markdown('<h3 class="section-title">Votre requête</h3>', unsafe_allow_html=True) | |
| if "user_input" not in st.session_state: | |
| st.session_state["user_input"] = "بغيت نعرف شحال باقي ليا في رصيدي" | |
| user_input = st.text_area("Entrez un texte en Darija:", | |
| value=st.session_state["user_input"], | |
| height=100, | |
| key="input_area", | |
| help="Vous pouvez entrer du texte en Darija pure ou mélangé avec du français") | |
| # Bouton d'analyse avec animation de chargement | |
| col1, col2, col3 = st.columns([1, 1, 1]) | |
| with col2: | |
| analyze_btn = st.button("🔍 Analyser l'intention", | |
| key="analyze_btn", | |
| type="primary", | |
| help="Cliquez pour analyser le texte") | |
| # Analyser le texte si le bouton est cliqué | |
| if analyze_btn: | |
| with st.spinner("Analyse en cours..."): | |
| # Afficher l'animation de traitement pendant l'appel à l'API | |
| if lottie_process: | |
| placeholder = st.empty() | |
| with placeholder.container(): | |
| st_lottie(lottie_process, height=120, key="process_animation") | |
| result, response_time = call_api(user_input) | |
| # Supprimer l'animation une fois le résultat obtenu | |
| if lottie_process: | |
| placeholder.empty() | |
| if result: | |
| # Afficher les résultats dans un cadre | |
| st.markdown("---") | |
| st.markdown('<h3 class="section-title fade-in">Résultats de l\'analyse</h3>', unsafe_allow_html=True) | |
| # Créer un conteneur de style "glass" pour les résultats | |
| st.markdown('<div class="glass-container">', unsafe_allow_html=True) | |
| # Créer deux colonnes pour les résultats | |
| res_col1, res_col2 = st.columns([1, 1]) | |
| with res_col1: | |
| # Icône et tag d'intention | |
| intent_icon = get_intent_icon(result["intent"]) | |
| st.markdown(f'<div class="intent-tag" style="background-color: {get_intent_color(result["intent"])};">{intent_icon} {result["intent"]}</div>', unsafe_allow_html=True) | |
| # Description de l'intention | |
| st.markdown(f"**Description:** {get_intent_description(result['intent'])}") | |
| # Temps de réponse avec badge coloré | |
| speed_class = "success" if response_time < 200 else "warning" if response_time < 500 else "danger" | |
| st.markdown(f""" | |
| <div> | |
| <span>⏱️ Temps de réponse:</span> | |
| <span class="badge" style="background-color: {'#10B981' if speed_class == 'success' else '#F59E0B' if speed_class == 'warning' else '#EF4444'};"> | |
| {response_time:.2f} ms | |
| </span> | |
| </div> | |
| """, unsafe_allow_html=True) | |
| with res_col2: | |
| # Utiliser Plotly pour un graphique interactif de confiance | |
| fig = go.Figure() | |
| # Ajouter la barre de fond | |
| fig.add_trace(go.Bar( | |
| x=[1], | |
| y=['Confiance'], | |
| orientation='h', | |
| marker=dict(color='rgba(240, 240, 240, 0.5)'), | |
| width=0.5, | |
| hoverinfo='skip', | |
| showlegend=False | |
| )) | |
| # Ajouter la barre principale | |
| fig.add_trace(go.Bar( | |
| x=[result["confidence"]], | |
| y=['Confiance'], | |
| orientation='h', | |
| marker=dict(color=get_intent_color(result["intent"])), | |
| width=0.5, | |
| hovertemplate=f'Confiance: {result["confidence"]*100:.1f}%<extra></extra>' | |
| )) | |
| # Configuration de la mise en page | |
| fig.update_layout( | |
| title=f"Score: {result['confidence']*100:.1f}%", | |
| height=150, | |
| margin=dict(l=20, r=20, t=40, b=20), | |
| xaxis=dict( | |
| range=[0, 1], | |
| tickvals=[0, 0.25, 0.5, 0.75, 1], | |
| ticktext=['0%', '25%', '50%', '75%', '100%'], | |
| gridcolor='rgba(0, 0, 0, 0.1)' | |
| ), | |
| barmode='overlay', | |
| bargap=0.1, | |
| showlegend=False | |
| ) | |
| st.plotly_chart(fig, use_container_width=True) | |
| # Fermer le conteneur en verre | |
| st.markdown('</div>', unsafe_allow_html=True) | |
| # Explication technique | |
| with st.expander("🔬 Explication technique", expanded=False): | |
| st.markdown(""" | |
| Le texte passe par plusieurs étapes de traitement dans notre API: | |
| 1. **Prétraitement**: | |
| - Normalisation du texte arabe (alif, yaa, etc.) | |
| - Gestion spéciale des caractères non-arabes | |
| - Traitement du code-switching Darija-Français | |
| 2. **Tokenisation**: | |
| - Conversion en tokens avec le tokenizer de MARBERTv2 | |
| - Support des tokens spéciaux pour la Darija | |
| 3. **Inférence**: | |
| - Passage dans le modèle fine-tuné sur notre corpus personnalisé | |
| - Application d'une couche linéaire de classification | |
| 4. **Post-traitement**: | |
| - Détermination de l'intention la plus probable | |
| - Calcul du score de confiance via softmax | |
| Le système utilise un modèle de type Transformer spécifiquement optimisé pour la Darija marocaine et ses spécificités dialectales. | |
| """) | |
| # Afficher le payload JSON avec coloration syntaxique | |
| st.markdown("#### Requête et réponse JSON:") | |
| col1, col2 = st.columns(2) | |
| with col1: | |
| st.code(json.dumps({"text": user_input}, indent=2, ensure_ascii=False), language="json") | |
| with col2: | |
| st.code(json.dumps(result, indent=2, ensure_ascii=False), language="json") | |
| # Exemples similaires | |
| with st.expander("📚 Exemples similaires", expanded=False): | |
| st.markdown(f"### Autres exemples pour l'intention: {result['intent']}") | |
| # Dictionnaire d'exemples par intention | |
| exemples_par_intention = { | |
| "consulter_solde": [ | |
| "شحال عندي في لكارط ديالي؟", | |
| "بقا ليا شحال في الكريدي؟", | |
| "واش ممكن تشوف ليا رصيدي؟", | |
| "فين يمكن لي نراقب الكريدي ديالي؟", | |
| "بغيت نعرف le solde ديالي شحال باقي" | |
| ], | |
| "reclamer_facture": [ | |
| "عندي مشكل في الفاكتورة", | |
| "الفاتورة ديال هاد الشهر مضاعفة على الشهر لي فات!", | |
| "كينقصوني فلوس بزاف ف الفاكتورة", | |
| "مكنستهلكش هاد القدر ديال الميكا، كاين خطأ", | |
| "عندي problème فالفاكتورة ديالي" | |
| ], | |
| "declarer_panne": [ | |
| "ماكيخدمش عندي لانترنيت هاذي شي سيمانة", | |
| "التيليفون ما كيشارجيش، عيطو ليا بسرعة", | |
| "عندي بروبليم فلانترنيت ديالي، كيقطع بزاف", | |
| "ماكيدوزش عندي لابيل ديال التيليفزيون", | |
| "j'ai un problème تقطع عليا الضو ديال مودام الويفي" | |
| ], | |
| "info_forfait": [ | |
| "بغيت نبدل الفورفيه ديالي لشي وحدة أحسن", | |
| "واش كاين شي فورفي ديال سوشيال ميديا؟", | |
| "بغيت نخلص باش نزيد ف لانترنت ديالي", | |
| "أشنو هو أحسن فورفيه عندكم؟", | |
| "je cherche un forfait مزيان للانترنت" | |
| ], | |
| "recuperer_mot_de_passe": [ | |
| "نسيت mon mot de passe ديالي واش يمكن تساعدني؟", | |
| "كيفاش نقدر نسترجع كلمة السر؟", | |
| "نسيت الكود ديالي ديال الكونيكسيون", | |
| "بغيت نبدل لو دو باس ديالي", | |
| "j'ai oublié لو دو باس ديال l'application" | |
| ], | |
| "salutations": [ | |
| "صباح الخير، كيفاش يمكن لي نتواصل معاكم؟", | |
| "السلام عليكم، بغيت نسولكم واحد السؤال", | |
| "مرحبا، شكون لي كيهضر؟", | |
| "آلو، واش نتا روبوت ولا بنادم حقيقي؟", | |
| "bonjour صاحبي، كيفاش يمكن لي نساعدك؟" | |
| ], | |
| "remerciements": [ | |
| "شكرا بزاف على المساعدة ديالكم", | |
| "باراكا لاهو فيك، راك عاونتيني بزاف", | |
| "ميرسي بزاف، ربي يجازيك بخير", | |
| "متشكر على الوقت ديالك", | |
| "merci بزاف على المساعدة ديالك" | |
| ], | |
| "demander_agent_humain": [ | |
| "بغيت نتكلم مع شي واحد حقيقي ماشي روبو", | |
| "واش ممكن تعاوني نهضر مع شي كونسيي؟", | |
| "بغيت شي واحد يتواصل معايا هاتفيا", | |
| "هادشي ماشي هو هداك لي كنبغي، خاصني بنادم نهضر معاه", | |
| "je veux parler à un conseiller حقيقي" | |
| ], | |
| "hors_scope": [ | |
| "واش كاين شي طريقة باش نلعب تينيس فهاد الويكاند؟", | |
| "كيفاش طقس غدا فالرباط؟", | |
| "شنو الأفلام الجديدة فالسينما؟", | |
| "فين نقدر نلقى دواء بارسيتامول فالحي ديالي؟", | |
| "je cherche un restaurant قريب من هنا" | |
| ] | |
| } | |
| # Afficher les exemples pour l'intention détectée | |
| if result["intent"] in exemples_par_intention: | |
| examples = exemples_par_intention[result["intent"]] | |
| for ex in examples: | |
| st.markdown(f"- `{ex}`") | |
| # Bouton pour tester un exemple aléatoire | |
| if st.button("🎲 Tester un exemple aléatoire", key="random_example"): | |
| st.session_state["user_input"] = random.choice(examples) | |
| st.rerun() | |
| else: | |
| st.write("Pas d'exemples disponibles pour cette intention.") | |
| # Onglet Performances | |
| with tab2: | |
| st.markdown('<h2 class="sub-title slide-in">Performance du modèle</h2>', unsafe_allow_html=True) | |
| # Description des performances | |
| st.info(""" | |
| **Cette section présente les performances du modèle MARBERTv2 fine-tuné sur notre corpus de Darija.** | |
| Les métriques ont été calculées sur un ensemble de test représentatif contenant 1 192 exemples issus de conversations réelles. | |
| Notre approche est basée sur un modèle de type Transformer pré-entraîné sur l'arabe (MARBERTv2) et spécifiquement adapté aux particularités dialectales de la Darija marocaine. | |
| """) | |
| # Métriques globales | |
| st.markdown('<h3 class="section-title">Métriques globales</h3>', unsafe_allow_html=True) | |
| # Créer des colonnes pour les métriques avec des indicateurs visuels | |
| metric_cols = st.columns(4) | |
| # Créer des graphiques Gauge pour chaque métrique | |
| with metric_cols[0]: | |
| fig = go.Figure(go.Indicator( | |
| mode = "gauge+number", | |
| value = 92.8, | |
| domain = {'x': [0, 1], 'y': [0, 1]}, | |
| title = {'text': "Accuracy", 'font': {'size': 24}}, | |
| gauge = { | |
| 'axis': {'range': [None, 100], 'tickwidth': 1, 'tickcolor': "darkblue"}, | |
| 'bar': {'color': "#1E3A8A"}, | |
| 'bgcolor': "white", | |
| 'borderwidth': 2, | |
| 'bordercolor': "gray", | |
| 'steps': [ | |
| {'range': [0, 70], 'color': '#FFEDD5'}, | |
| {'range': [70, 85], 'color': '#FEF3C7'}, | |
| {'range': [85, 100], 'color': '#ECFDF5'}], | |
| } | |
| )) | |
| fig.update_layout(height=200, margin=dict(l=20, r=20, t=50, b=20)) | |
| st.plotly_chart(fig, use_container_width=True) | |
| with metric_cols[1]: | |
| fig = go.Figure(go.Indicator( | |
| mode = "gauge+number", | |
| value = 93.1, | |
| domain = {'x': [0, 1], 'y': [0, 1]}, | |
| title = {'text': "Precision", 'font': {'size': 24}}, | |
| gauge = { | |
| 'axis': {'range': [None, 100], 'tickwidth': 1, 'tickcolor': "darkblue"}, | |
| 'bar': {'color': "#1E40AF"}, | |
| 'bgcolor': "white", | |
| 'borderwidth': 2, | |
| 'bordercolor': "gray", | |
| 'steps': [ | |
| {'range': [0, 70], 'color': '#FFEDD5'}, | |
| {'range': [70, 85], 'color': '#FEF3C7'}, | |
| {'range': [85, 100], 'color': '#ECFDF5'}], | |
| } | |
| )) | |
| fig.update_layout(height=200, margin=dict(l=20, r=20, t=50, b=20)) | |
| st.plotly_chart(fig, use_container_width=True) | |
| with metric_cols[2]: | |
| fig = go.Figure(go.Indicator( | |
| mode = "gauge+number", | |
| value = 92.8, | |
| domain = {'x': [0, 1], 'y': [0, 1]}, | |
| title = {'text': "Recall", 'font': {'size': 24}}, | |
| gauge = { | |
| 'axis': {'range': [None, 100], 'tickwidth': 1, 'tickcolor': "darkblue"}, | |
| 'bar': {'color': "#2563EB"}, | |
| 'bgcolor': "white", | |
| 'borderwidth': 2, | |
| 'bordercolor': "gray", | |
| 'steps': [ | |
| {'range': [0, 70], 'color': '#FFEDD5'}, | |
| {'range': [70, 85], 'color': '#FEF3C7'}, | |
| {'range': [85, 100], 'color': '#ECFDF5'}], | |
| } | |
| )) | |
| fig.update_layout(height=200, margin=dict(l=20, r=20, t=50, b=20)) | |
| st.plotly_chart(fig, use_container_width=True) | |
| with metric_cols[3]: | |
| fig = go.Figure(go.Indicator( | |
| mode = "gauge+number", | |
| value = 92.9, | |
| domain = {'x': [0, 1], 'y': [0, 1]}, | |
| title = {'text': "F1-Score", 'font': {'size': 24}}, | |
| gauge = { | |
| 'axis': {'range': [None, 100], 'tickwidth': 1, 'tickcolor': "darkblue"}, | |
| 'bar': {'color': "#3B82F6"}, | |
| 'bgcolor': "white", | |
| 'borderwidth': 2, | |
| 'bordercolor': "gray", | |
| 'steps': [ | |
| {'range': [0, 70], 'color': '#FFEDD5'}, | |
| {'range': [70, 85], 'color': '#FEF3C7'}, | |
| {'range': [85, 100], 'color': '#ECFDF5'}], | |
| } | |
| )) | |
| fig.update_layout(height=200, margin=dict(l=20, r=20, t=50, b=20)) | |
| st.plotly_chart(fig, use_container_width=True) | |
| # Matrice de confusion | |
| st.markdown('<h3 class="section-title">Matrice de confusion</h3>', unsafe_allow_html=True) | |
| # Créer les onglets pour choisir le type de visualisation | |
| matrix_tab1, matrix_tab2 = st.tabs(["Heatmap interactive", "Image statique"]) | |
| with matrix_tab1: | |
| # Créer une matrice de confusion fictive (similaire à celle montrée dans le rapport) | |
| intent_labels = [ | |
| "consulter_solde", "reclamer_facture", "declarer_panne", | |
| "info_forfait", "recuperer_mot_de_passe", "salutations", | |
| "remerciements", "demander_agent_humain", "hors_scope" | |
| ] | |
| # Matrice fictive (similaire à celle montrée dans le rapport) | |
| conf_matrix = np.array([ | |
| [184, 0, 0, 3, 0, 0, 0, 0, 8], | |
| [1, 130, 0, 2, 0, 0, 0, 2, 3], | |
| [0, 2, 118, 0, 0, 0, 0, 6, 5], | |
| [2, 3, 0, 121, 0, 0, 0, 0, 2], | |
| [0, 0, 0, 0, 121, 0, 0, 2, 2], | |
| [1, 0, 0, 0, 0, 109, 5, 0, 7], | |
| [0, 0, 0, 0, 0, 3, 133, 0, 0], | |
| [0, 0, 7, 0, 3, 0, 0, 111, 4], | |
| [4, 2, 4, 0, 2, 9, 0, 5, 107] | |
| ]) | |
| # Créer un DataFrame pour Plotly | |
| matrix_data = [] | |
| for i in range(len(intent_labels)): | |
| for j in range(len(intent_labels)): | |
| matrix_data.append({ | |
| 'Réelle': intent_labels[i], | |
| 'Prédite': intent_labels[j], | |
| 'Valeur': conf_matrix[i, j] | |
| }) | |
| df_conf = pd.DataFrame(matrix_data) | |
| # Créer la heatmap avec Plotly | |
| fig = px.density_heatmap( | |
| df_conf, | |
| x='Prédite', | |
| y='Réelle', | |
| z='Valeur', | |
| color_continuous_scale='Blues', | |
| text_auto=True | |
| ) | |
| fig.update_layout( | |
| title='Matrice de Confusion Interactive', | |
| width=800, | |
| height=600, | |
| xaxis_title='Intention Prédite', | |
| yaxis_title='Intention Réelle' | |
| ) | |
| st.plotly_chart(fig, use_container_width=True) | |
| with matrix_tab2: | |
| try: | |
| confusion_img = Image.open("images/image8.jpg") | |
| st.image(confusion_img, caption="Matrice de Confusion pour la Classification d'Intents en Darija") | |
| except: | |
| st.warning("Image de matrice de confusion non trouvée. Placez 'image8.jpg' dans le dossier 'images/'.") | |
| # Créer une heatmap avec Matplotlib | |
| fig, ax = plt.subplots(figsize=(10, 8)) | |
| im = ax.imshow(conf_matrix, cmap='Blues') | |
| # Étiquettes des axes | |
| ax.set_xticks(np.arange(len(intent_labels))) | |
| ax.set_yticks(np.arange(len(intent_labels))) | |
| ax.set_xticklabels(intent_labels, rotation=45, ha="right") | |
| ax.set_yticklabels(intent_labels) | |
| # Ajout des valeurs dans les cellules | |
| for i in range(len(intent_labels)): | |
| for j in range(len(intent_labels)): | |
| text = ax.text(j, i, conf_matrix[i, j], | |
| ha="center", va="center", color="black" if conf_matrix[i, j] < 100 else "white") | |
| ax.set_xlabel('Intention prédite') | |
| ax.set_ylabel('Intention réelle') | |
| ax.set_title('Matrice de Confusion') | |
| fig.tight_layout() | |
| st.pyplot(fig) | |
| # Performance par intention | |
| st.markdown('<h3 class="section-title">Performance par intention</h3>', unsafe_allow_html=True) | |
| perf_tab1, perf_tab2 = st.tabs(["Graphique interactif", "Image statique"]) | |
| with perf_tab1: | |
| # Créer un graphique interactif avec Plotly | |
| intents = [ | |
| "consulter_solde", "reclamer_facture", "declarer_panne", | |
| "info_forfait", "recuperer_mot_de_passe", "salutations", | |
| "remerciements", "demander_agent_humain", "hors_scope" | |
| ] | |
| # Données (similaires à celles du rapport) | |
| precision = [0.981, 0.949, 0.907, 0.887, 0.947, 0.906, 0.964, 0.867, 0.847] | |
| recall = [0.943, 0.944, 0.904, 0.945, 0.967, 0.890, 0.978, 0.931, 0.807] | |
| f1 = [0.962, 0.946, 0.905, 0.915, 0.957, 0.898, 0.971, 0.898, 0.827] | |
| # Créer un DataFrame pour Plotly | |
| df_perf = pd.DataFrame({ | |
| 'Intention': intents * 3, | |
| 'Métrique': ['Précision'] * len(intents) + ['Rappel'] * len(intents) + ['F1-Score'] * len(intents), | |
| 'Valeur': precision + recall + f1 | |
| }) | |
| # Créer le graphique avec Plotly | |
| fig = px.bar( | |
| df_perf, | |
| x='Intention', | |
| y='Valeur', | |
| color='Métrique', | |
| barmode='group', | |
| color_discrete_map={ | |
| 'Précision': '#1f77b4', | |
| 'Rappel': '#ff7f0e', | |
| 'F1-Score': '#2ca02c' | |
| }, | |
| hover_data={'Intention': True, 'Métrique': True, 'Valeur': ':.3f'}, | |
| title='Performance par intention' | |
| ) | |
| fig.update_layout( | |
| yaxis=dict( | |
| title='Score', | |
| range=[0.7, 1] | |
| ), | |
| xaxis_title='', | |
| legend_title='Métrique', | |
| height=500 | |
| ) | |
| st.plotly_chart(fig, use_container_width=True) | |
| with perf_tab2: | |
| try: | |
| perf_img = Image.open("images/image11.jpg") | |
| st.image(perf_img, caption="Performance du Modèle par Intent (Précision, Rappel, F1-Score)") | |
| except: | |
| st.warning("Image de performance par intent non trouvée. Placez 'image11.jpg' dans le dossier 'images/'.") | |
| # Créer un graphique avec Matplotlib | |
| fig, ax = plt.subplots(figsize=(12, 6)) | |
| x = np.arange(len(intents)) | |
| width = 0.25 | |
| ax.bar(x - width, precision, width, label='Précision', color='#1f77b4') | |
| ax.bar(x, recall, width, label='Rappel', color='#ff7f0e') | |
| ax.bar(x + width, f1, width, label='F1-Score', color='#2ca02c') | |
| ax.set_ylabel('Score') | |
| ax.set_title('Performance par intention') | |
| ax.set_xticks(x) | |
| ax.set_xticklabels(intents, rotation=45, ha='right') | |
| ax.legend() | |
| ax.set_ylim([0.7, 1]) | |
| fig.tight_layout() | |
| st.pyplot(fig) | |
| # Évolution de l'entraînement | |
| st.markdown('<h3 class="section-title">Évolution de l\'entraînement</h3>', unsafe_allow_html=True) | |
| train_tab1, train_tab2 = st.tabs(["Graphique interactif", "Image statique"]) | |
| with train_tab1: | |
| # Créer un graphique interactif avec Plotly | |
| steps = list(range(0, 1001, 50)) | |
| train_loss = [4.5] + [4.5 * np.exp(-0.005 * step) + 0.3 + 0.1 * np.random.random() for step in steps[1:]] | |
| val_loss = [4.2] + [4.2 * np.exp(-0.005 * step) + 0.35 + 0.15 * np.random.random() for step in steps[1:]] | |
| # Créer un DataFrame | |
| df_loss = pd.DataFrame({ | |
| 'Step': steps, | |
| 'Train Loss': train_loss, | |
| 'Val Loss': val_loss | |
| }) | |
| # Convertir en format long pour Plotly | |
| df_loss_long = pd.melt( | |
| df_loss, | |
| id_vars=['Step'], | |
| value_vars=['Train Loss', 'Val Loss'], | |
| var_name='Type', | |
| value_name='Loss' | |
| ) | |
| # Créer le graphique avec Plotly | |
| fig = px.line( | |
| df_loss_long, | |
| x='Step', | |
| y='Loss', | |
| color='Type', | |
| title='Évolution de la perte durant l\'entraînement', | |
| color_discrete_map={ | |
| 'Train Loss': 'blue', | |
| 'Val Loss': 'orange' | |
| } | |
| ) | |
| fig.update_layout( | |
| xaxis_title='Étapes', | |
| yaxis_title='Perte (Loss)', | |
| height=400 | |
| ) | |
| st.plotly_chart(fig, use_container_width=True) | |
| with train_tab2: | |
| try: | |
| training_img = Image.open("images/image5.jpg") | |
| st.image(training_img, caption="Évolution de la Perte (Loss) durant l'Entraînement") | |
| except: | |
| st.warning("Image d'évolution de l'entraînement non trouvée. Placez 'image5.jpg' dans le dossier 'images/'.") | |
| # Créer un graphique avec Matplotlib | |
| fig, ax = plt.subplots(figsize=(10, 5)) | |
| ax.plot(steps, train_loss, label='Train Loss', color='blue') | |
| ax.plot(steps, val_loss, label='Val Loss', color='orange') | |
| ax.set_xlabel('Étapes') | |
| ax.set_ylabel('Perte (Loss)') | |
| ax.set_title('Évolution de la perte durant l\'entraînement') | |
| ax.legend() | |
| ax.grid(True, linestyle='--', alpha=0.7) | |
| fig.tight_layout() | |
| st.pyplot(fig) | |
| # Benchmarks et comparaisons | |
| st.markdown('<h3 class="section-title">Benchmarks et comparaisons</h3>', unsafe_allow_html=True) | |
| st.markdown(""" | |
| <table class="styled-table"> | |
| <thead> | |
| <tr> | |
| <th>Modèle</th> | |
| <th>Accuracy</th> | |
| <th>F1-Score</th> | |
| <th>Temps de réponse</th> | |
| <th>Taille</th> | |
| </tr> | |
| </thead> | |
| <tbody> | |
| <tr> | |
| <td><strong>MARBERTv2 (notre approche)</strong></td> | |
| <td>92.8%</td> | |
| <td>92.9%</td> | |
| <td>127ms</td> | |
| <td>470MB</td> | |
| </tr> | |
| <tr> | |
| <td>AraBERT</td> | |
| <td>89.3%</td> | |
| <td>89.1%</td> | |
| <td>132ms</td> | |
| <td>543MB</td> | |
| </tr> | |
| <tr> | |
| <td>QARiB</td> | |
| <td>87.5%</td> | |
| <td>87.2%</td> | |
| <td>145ms</td> | |
| <td>420MB</td> | |
| </tr> | |
| <tr> | |
| <td>BERT Multilingue</td> | |
| <td>85.1%</td> | |
| <td>84.9%</td> | |
| <td>121ms</td> | |
| <td>680MB</td> | |
| </tr> | |
| <tr> | |
| <td>SVM + TF-IDF</td> | |
| <td>78.6%</td> | |
| <td>77.9%</td> | |
| <td>65ms</td> | |
| <td>25MB</td> | |
| </tr> | |
| </tbody> | |
| </table> | |
| """, unsafe_allow_html=True) | |
| st.markdown(""" | |
| <div class="info-box" style="background-color: #F8FAFC; border: 2px solid #E2E8F0; color: #1A202C; font-size: 16px; line-height: 1.6;"> | |
| <p style="margin-bottom: 15px; font-weight: 500;">Notre approche basée sur <strong style="color: #1E3A8A; font-weight: 700;">MARBERTv2</strong> surpasse significativement les autres modèles, en particulier pour les intentions liées aux spécificités dialectales de la Darija et au code-switching.</p> | |
| <p style="margin-bottom: 10px; font-weight: 600; color: #2D3748;">Les avantages de notre approche:</p> | |
| <ul style="margin-left: 20px; color: #4A5568;"> | |
| <li style="margin-bottom: 8px; font-weight: 500;">Meilleure gestion des variations dialectales régionales</li> | |
| <li style="margin-bottom: 8px; font-weight: 500;">Support du code-switching entre Darija et Français</li> | |
| <li style="margin-bottom: 8px; font-weight: 500;">Bonne performance sur les expressions idiomatiques spécifiques</li> | |
| <li style="margin-bottom: 8px; font-weight: 500;">Équilibre optimal entre performance et temps de réponse</li> | |
| </ul> | |
| </div> | |
| """, unsafe_allow_html=True) | |
| # Onglet Architecture | |
| with tab3: | |
| st.markdown('<h2 class="sub-title slide-in">Architecture de la solution</h2>', unsafe_allow_html=True) | |
| # Description de l'architecture | |
| st.info(""" | |
| **Cette section présente l'architecture technique de notre solution et son intégration avec la plateforme AICC.** | |
| Notre système est conçu comme une API RESTful déployée sur Hugging Face Spaces, permettant une intégration flexible avec différentes plateformes de service client, dont la solution AICC de Huawei. | |
| """) | |
| # Architecture globale | |
| st.markdown('<h3 class="section-title">Architecture globale</h3>', unsafe_allow_html=True) | |
| arch_tab1, arch_tab2 = st.tabs(["Diagramme interactif", "Image statique"]) | |
| with arch_tab1: | |
| # Créer un diagramme d'architecture professionnel avec Plotly | |
| fig = go.Figure() | |
| # Définir une palette de couleurs professionnelle (dégradé de bleus) | |
| colors = { | |
| "Client": "#1E40AF", # Bleu foncé | |
| "AICC": "#2563EB", # Bleu royal | |
| "API Darija NLU": "#3B82F6", # Bleu moyen | |
| "MARBERTv2": "#60A5FA", # Bleu clair | |
| "Agents": "#1D4ED8", # Bleu profond | |
| "Call Center": "#1E3A8A" # Bleu très foncé | |
| } | |
| # Repositionner les nœuds pour une meilleure présentation | |
| nodes = [ | |
| {"name": "Client", "x": 0, "y": 0, "size": 80, "icon": "👤"}, | |
| {"name": "AICC\nPlateforme", "x": 2, "y": 0, "size": 90, "icon": "🏢"}, | |
| {"name": "API Darija\nNLU", "x": 4, "y": 0, "size": 85, "icon": "🔗"}, | |
| {"name": "MARBERTv2\nModèle", "x": 6, "y": 0, "size": 75, "icon": "🧠"}, | |
| {"name": "Agents\nHumains", "x": 2, "y": -1.5, "size": 70, "icon": "👨💼"}, | |
| {"name": "Call Center\nSupport", "x": 3, "y": -2.5, "size": 65, "icon": "📞"} | |
| ] | |
| # Ajouter les nœuds avec des styles améliorés | |
| for i, node in enumerate(nodes): | |
| # Simplifier la logique de couleur | |
| node_name = node["name"].replace("\n", " ") | |
| if "Client" in node_name: | |
| node_color = colors["Client"] | |
| elif "AICC" in node_name: | |
| node_color = colors["AICC"] | |
| elif "API" in node_name or "Darija" in node_name: | |
| node_color = colors["API Darija NLU"] | |
| elif "MARBERT" in node_name or "Modèle" in node_name: | |
| node_color = colors["MARBERTv2"] | |
| elif "Agents" in node_name: | |
| node_color = colors["Agents"] | |
| elif "Call" in node_name or "Center" in node_name: | |
| node_color = colors["Call Center"] | |
| else: | |
| node_color = "#3B82F6" # Couleur par défaut | |
| fig.add_trace(go.Scatter( | |
| x=[node["x"]], | |
| y=[node["y"]], | |
| mode="markers+text", | |
| marker=dict( | |
| size=node["size"], | |
| color=node_color, | |
| line=dict(width=3, color="white"), | |
| opacity=0.9 | |
| ), | |
| text=f'{node["icon"]}<br>{node["name"]}', | |
| textposition="middle center", | |
| textfont=dict(color="white", size=11, family="Arial Black"), | |
| hoverinfo="text", | |
| hovertext=f"<b>{node['name']}</b><br>Composant de l'architecture", | |
| hoverlabel=dict(bgcolor="rgba(0,0,0,0.8)", font_color="white"), | |
| showlegend=False | |
| )) | |
| # Définir les connexions avec des descriptions plus détaillées | |
| edges = [ | |
| {"from": 0, "to": 1, "label": "Requête client\n(Darija)", "color": "#2563EB", "style": "solid"}, | |
| {"from": 1, "to": 2, "label": "API Call\n(HTTPS/POST)", "color": "#3B82F6", "style": "solid"}, | |
| {"from": 2, "to": 3, "label": "Inférence ML\n(Tokenization)", "color": "#60A5FA", "style": "solid"}, | |
| {"from": 3, "to": 2, "label": "Prédiction\n(Intent + Score)", "color": "#60A5FA", "style": "dash"}, | |
| {"from": 2, "to": 1, "label": "Réponse JSON\n(Structured)", "color": "#3B82F6", "style": "dash"}, | |
| {"from": 1, "to": 0, "label": "Réponse adaptée\n(Interface)", "color": "#2563EB", "style": "dash"}, | |
| {"from": 1, "to": 4, "label": "Transfert\n(Si nécessaire)", "color": "#1D4ED8", "style": "dot"}, | |
| {"from": 4, "to": 5, "label": "Escalade\n(Support)", "color": "#1E3A8A", "style": "solid"}, | |
| {"from": 5, "to": 0, "label": "Support avancé\n(Humain)", "color": "#1E3A8A", "style": "solid"} | |
| ] | |
| # Ajouter les connexions avec des styles variés | |
| for edge in edges: | |
| fig.add_shape( | |
| type="line", | |
| x0=nodes[edge["from"]]["x"], | |
| y0=nodes[edge["from"]]["y"], | |
| x1=nodes[edge["to"]]["x"], | |
| y1=nodes[edge["to"]]["y"], | |
| line=dict( | |
| color=edge["color"], | |
| width=3, | |
| dash=edge["style"] | |
| ), | |
| xref="x", | |
| yref="y" | |
| ) | |
| # Ajouter des flèches pour indiquer la direction | |
| if edge["style"] != "dot": # Pas de flèche pour les connexions conditionnelles | |
| # Calculer la position de la flèche | |
| x0, y0 = nodes[edge["from"]]["x"], nodes[edge["from"]]["y"] | |
| x1, y1 = nodes[edge["to"]]["x"], nodes[edge["to"]]["y"] | |
| # Position de la flèche (75% du chemin) | |
| arrow_x = x0 + 0.75 * (x1 - x0) | |
| arrow_y = y0 + 0.75 * (y1 - y0) | |
| fig.add_trace(go.Scatter( | |
| x=[arrow_x], | |
| y=[arrow_y], | |
| mode="markers", | |
| marker=dict( | |
| symbol="arrow-right", | |
| size=15, | |
| color=edge["color"], | |
| line=dict(width=1, color="white") | |
| ), | |
| hoverinfo="skip", | |
| showlegend=False | |
| )) | |
| # Ajouter les étiquettes des connexions | |
| midpoint_x = (nodes[edge["from"]]["x"] + nodes[edge["to"]]["x"]) / 2 | |
| midpoint_y = (nodes[edge["from"]]["y"] + nodes[edge["to"]]["y"]) / 2 | |
| # Ajouter les étiquettes des connexions sans fond | |
| fig.add_trace(go.Scatter( | |
| x=[midpoint_x], | |
| y=[midpoint_y], | |
| mode="text", | |
| text=edge["label"], | |
| textposition="middle center", | |
| textfont=dict( | |
| size=9, | |
| color=edge["color"], | |
| family="Arial" | |
| ), | |
| hoverinfo="text", | |
| hovertext=f"<b>Flux:</b> {edge['label']}", | |
| hoverlabel=dict(bgcolor="rgba(0,0,0,0.8)", font_color="white"), | |
| showlegend=False | |
| )) | |
| # Configuration avancée de la mise en page | |
| fig.update_layout( | |
| title={ | |
| 'text': "🏗️ Architecture d'Intégration - API NLU Darija avec AICC", | |
| 'x': 0.5, | |
| 'xanchor': 'center', | |
| 'font': {'size': 18, 'color': '#1E3A8A', 'family': 'Arial Black'} | |
| }, | |
| showlegend=False, | |
| hovermode="closest", | |
| height=500, | |
| margin=dict(t=80, b=40, l=40, r=40), | |
| xaxis=dict( | |
| showgrid=False, | |
| zeroline=False, | |
| showticklabels=False, | |
| range=[-0.5, 6.5] | |
| ), | |
| yaxis=dict( | |
| showgrid=False, | |
| zeroline=False, | |
| showticklabels=False, | |
| range=[-3, 1] | |
| ), | |
| plot_bgcolor="rgba(248,250,252,0.3)", | |
| paper_bgcolor="white", | |
| font=dict(family="Arial, sans-serif") | |
| ) | |
| # Ajouter une légende personnalisée | |
| fig.add_annotation( | |
| text="<b>Légende:</b><br>" + | |
| "━━━ Flux principal<br>" + | |
| "┄┄┄ Réponse<br>" + | |
| "••••• Transfert conditionnel", | |
| xref="paper", yref="paper", | |
| x=0.02, y=0.02, | |
| xanchor="left", yanchor="bottom", | |
| showarrow=False, | |
| font=dict(size=10, color="#1E3A8A"), | |
| bgcolor="rgba(255,255,255,0.9)", | |
| bordercolor="#E5E7EB", | |
| borderwidth=1 | |
| ) | |
| st.plotly_chart(fig, use_container_width=True) | |
| # Ajouter des métriques de performance de l'architecture | |
| st.markdown("### 📊 Métriques de Performance de l'Architecture") | |
| perf_cols = st.columns(4) | |
| with perf_cols[0]: | |
| st.metric( | |
| label="⚡ Latence Moyenne", | |
| value="127ms", | |
| delta="-23ms vs baseline", | |
| delta_color="inverse" | |
| ) | |
| with perf_cols[1]: | |
| st.metric( | |
| label="🎯 Disponibilité", | |
| value="99.8%", | |
| delta="+0.3% ce mois", | |
| delta_color="normal" | |
| ) | |
| with perf_cols[2]: | |
| st.metric( | |
| label="🔄 Requêtes/sec", | |
| value="1,250", | |
| delta="+15% capacité", | |
| delta_color="normal" | |
| ) | |
| with perf_cols[3]: | |
| st.metric( | |
| label="🛡️ Taux d'erreur", | |
| value="0.2%", | |
| delta="-0.1% amélioration", | |
| delta_color="inverse" | |
| ) | |
| with arch_tab2: | |
| try: | |
| arch_img = Image.open("images/image17.jpg") | |
| st.image(arch_img, caption="Diagramme d'architecture de l'intégration avec AICC") | |
| except: | |
| st.warning("Image d'architecture non trouvée. Placez 'image17.jpg' dans le dossier 'images/'.") | |
| st.markdown(""" | |
| ``` | |
| ┌───────────────┐ ┌───────────────┐ ┌───────────────┐ | |
| │ Client │ │ Plateforme │ │ API NLU │ | |
| │ (Mobile/Web)│◄────►│ AICC Huawei │◄────►│ Darija │ | |
| └───────────────┘ └───────────────┘ └───────────────┘ | |
| ▲ ▲ | |
| │ │ | |
| ▼ │ | |
| ┌────────────┐ │ | |
| │ Agents │ │ | |
| │ Humains │ │ | |
| └────────────┘ │ | |
| ▲ │ | |
| │ │ | |
| ▼ ▼ | |
| ┌─────────────────────────────────┐ | |
| │ Système de Gestion de │ | |
| │ Centre de Contact │ | |
| └─────────────────────────────────┘ | |
| ``` | |
| """) | |
| # Flux de traitement | |
| st.markdown('<h3 class="section-title">Flux de traitement</h3>', unsafe_allow_html=True) | |
| flow_tab1, flow_tab2 = st.tabs(["Séquence interactive", "Image statique"]) | |
| with flow_tab1: | |
| # Créer un diagramme de séquence professionnel | |
| sequence_steps = [ | |
| {"from": "Client", "to": "AICC", "message": "📱 Message Darija\n(Requête utilisateur)", "time": 1, "type": "request"}, | |
| {"from": "AICC", "to": "API NLU", "message": "🔗 API Call HTTPS\n(POST /predict)", "time": 2, "type": "api"}, | |
| {"from": "API NLU", "to": "MARBERTv2", "message": "🧠 Inférence ML\n(Tokenization)", "time": 3, "type": "ml"}, | |
| {"from": "MARBERTv2", "to": "API NLU", "message": "🎯 Prédiction\n(Intent + Confidence)", "time": 4, "type": "response"}, | |
| {"from": "API NLU", "to": "AICC", "message": "📊 Réponse JSON\n(Structured Data)", "time": 5, "type": "response"}, | |
| {"from": "AICC", "to": "Client", "message": "✅ Réponse adaptée\n(Interface utilisateur)", "time": 6, "type": "response"} | |
| ] | |
| # Liste des acteurs avec couleurs et icônes | |
| actors = [ | |
| {"name": "Client", "color": "#1E40AF", "icon": "👤"}, | |
| {"name": "AICC Platform", "color": "#2563EB", "icon": "🏢"}, | |
| {"name": "API NLU Darija", "color": "#3B82F6", "icon": "🔗"}, | |
| {"name": "MARBERTv2 Model", "color": "#60A5FA", "icon": "🧠"} | |
| ] | |
| # Création du diagramme de séquence | |
| fig = go.Figure() | |
| # Couleurs selon le type de message | |
| message_colors = { | |
| "request": "#1E40AF", | |
| "api": "#2563EB", | |
| "ml": "#3B82F6", | |
| "response": "#60A5FA" | |
| } | |
| # Lignes de vie avec style professionnel | |
| for i, actor in enumerate(actors): | |
| # Ligne de vie | |
| fig.add_trace(go.Scatter( | |
| x=[i, i], | |
| y=[0.5, -7], | |
| mode="lines", | |
| line=dict(color=actor["color"], width=3, dash="dot"), | |
| opacity=0.6, | |
| hoverinfo="none", | |
| showlegend=False | |
| )) | |
| # En-tête des acteurs avec style moderne | |
| fig.add_trace(go.Scatter( | |
| x=[i], | |
| y=[0.5], | |
| mode="markers+text", | |
| marker=dict( | |
| size=60, | |
| color=actor["color"], | |
| line=dict(width=3, color="white"), | |
| opacity=0.9 | |
| ), | |
| text=f'{actor["icon"]}<br><b>{actor["name"]}</b>', | |
| textposition="middle center", | |
| textfont=dict(color="white", size=10, family="Arial Bold"), | |
| hoverinfo="text", | |
| hovertext=f"<b>{actor['name']}</b><br>Composant système", | |
| hoverlabel=dict(bgcolor="rgba(0,0,0,0.8)", font_color="white"), | |
| showlegend=False | |
| )) | |
| # Ajouter les messages avec styles différenciés | |
| for step in sequence_steps: | |
| # Trouver l'index des acteurs correspondants de manière plus flexible | |
| from_idx = -1 | |
| to_idx = -1 | |
| # Recherche plus robuste pour les acteurs sources et destinations | |
| for i, actor in enumerate(actors): | |
| actor_name = actor["name"] | |
| # Vérifier si l'acteur correspond à l'acteur source | |
| if step["from"] in actor_name or actor_name.split()[0] == step["from"]: | |
| from_idx = i | |
| # Vérifier si l'acteur correspond à l'acteur destination | |
| if step["to"] in actor_name or actor_name.split()[0] == step["to"]: | |
| to_idx = i | |
| # Si on n'a pas trouvé les acteurs, utiliser une approche plus générique | |
| if from_idx == -1: | |
| from_idx = 0 # Utiliser le premier acteur par défaut | |
| print(f"Acteur source non trouvé pour {step['from']}") | |
| if to_idx == -1: | |
| to_idx = 1 # Utiliser le deuxième acteur par défaut | |
| print(f"Acteur destination non trouvé pour {step['to']}") | |
| time_y = -step["time"] | |
| color = message_colors[step["type"]] | |
| # Flèche du message avec direction | |
| if from_idx < to_idx: # Message vers la droite | |
| arrow_symbol = "triangle-right" | |
| x_positions = [from_idx + 0.1, to_idx - 0.1] | |
| else: # Message vers la gauche | |
| arrow_symbol = "triangle-left" | |
| x_positions = [from_idx - 0.1, to_idx + 0.1] | |
| # Ligne de message | |
| fig.add_shape( | |
| type="line", | |
| x0=x_positions[0], | |
| y0=time_y, | |
| x1=x_positions[1], | |
| y1=time_y, | |
| line=dict(color=color, width=3), | |
| xref="x", | |
| yref="y" | |
| ) | |
| # Flèche de direction | |
| fig.add_trace(go.Scatter( | |
| x=[x_positions[1]], | |
| y=[time_y], | |
| mode="markers", | |
| marker=dict( | |
| symbol=arrow_symbol, | |
| size=12, | |
| color=color, | |
| line=dict(width=1, color="white") | |
| ), | |
| hoverinfo="skip", | |
| showlegend=False | |
| )) | |
| # Étiquette du message avec fond | |
| mid_x = (x_positions[0] + x_positions[1]) / 2 | |
| fig.add_trace(go.Scatter( | |
| x=[mid_x], | |
| y=[time_y + 0.15], | |
| mode="text", | |
| text=step["message"], | |
| textposition="middle center", | |
| textfont=dict( | |
| size=9, | |
| color=color, | |
| family="Arial" | |
| ), | |
| hoverinfo="text", | |
| hovertext=f"<b>Étape {step['time']}:</b><br>{step['message']}", | |
| hoverlabel=dict(bgcolor="rgba(0,0,0,0.8)", font_color="white"), | |
| showlegend=False | |
| )) | |
| # Ajouter un indicateur temporel | |
| fig.add_trace(go.Scatter( | |
| x=[-0.3], | |
| y=[time_y], | |
| mode="markers+text", | |
| marker=dict(size=20, color="#E5E7EB", line=dict(width=1, color="#9CA3AF")), | |
| text=f"{step['time']}", | |
| textposition="middle center", | |
| textfont=dict(size=10, color="#374151", family="Arial Bold"), | |
| hoverinfo="text", | |
| hovertext=f"Séquence {step['time']}", | |
| showlegend=False | |
| )) | |
| # Configuration avancée de la mise en page | |
| fig.update_layout( | |
| title={ | |
| 'text': "🔄 Diagramme de Séquence - Flux de Traitement NLU", | |
| 'x': 0.5, | |
| 'xanchor': 'center', | |
| 'font': {'size': 18, 'color': '#1E3A8A', 'family': 'Arial Black'} | |
| }, | |
| showlegend=False, | |
| hovermode="closest", | |
| height=600, | |
| margin=dict(t=80, b=40, l=80, r=40), | |
| xaxis=dict( | |
| showgrid=False, | |
| zeroline=False, | |
| showticklabels=False, | |
| range=[-0.8, len(actors) - 0.2] | |
| ), | |
| yaxis=dict( | |
| showgrid=True, | |
| gridcolor="rgba(0,0,0,0.1)", | |
| zeroline=False, | |
| showticklabels=False, | |
| range=[-7.5, 1] | |
| ), | |
| plot_bgcolor="rgba(248,250,252,0.3)", | |
| paper_bgcolor="white", | |
| font=dict(family="Arial, sans-serif") | |
| ) | |
| # Ajouter une légende temporelle | |
| fig.add_annotation( | |
| text="<b>Chronologie:</b><br>" + | |
| "① Requête initiale<br>" + | |
| "② Appel API<br>" + | |
| "③ Traitement ML<br>" + | |
| "④ Résultat modèle<br>" + | |
| "⑤ Réponse structurée<br>" + | |
| "⑥ Interface utilisateur", | |
| xref="paper", yref="paper", | |
| x=0.02, y=0.98, | |
| xanchor="left", yanchor="top", | |
| showarrow=False, | |
| font=dict(size=10, color="#1E3A8A"), | |
| bgcolor="rgba(255,255,255,0.95)", | |
| bordercolor="#E5E7EB", | |
| borderwidth=1 | |
| ) | |
| st.plotly_chart(fig, use_container_width=True) | |
| # Ajouter des informations sur la latence | |
| st.markdown("### ⏱️ Analyse de Performance par Étape") | |
| latency_cols = st.columns(6) | |
| latencies = ["12ms", "8ms", "95ms", "5ms", "6ms", "1ms"] | |
| steps_names = ["Requête", "Routage", "Inférence", "Post-process", "Réponse", "Affichage"] | |
| for i, (col, latency, step_name) in enumerate(zip(latency_cols, latencies, steps_names)): | |
| with col: | |
| st.metric( | |
| label=f"Étape {i+1}", | |
| value=latency, | |
| help=f"Latence moyenne pour: {step_name}" | |
| ) | |
| with arch_tab2: | |
| try: | |
| arch_img = Image.open("images/image17.jpg") | |
| st.image(arch_img, caption="Diagramme d'architecture de l'intégration avec AICC") | |
| except: | |
| st.warning("Image d'architecture non trouvée. Placez 'image17.jpg' dans le dossier 'images/'.") | |
| # Structure de l'API | |
| st.markdown('<h3 class="section-title">Structure de l\'API</h3>', unsafe_allow_html=True) | |
| # Détails d'implémentation dans un expander | |
| with st.expander("Détails d'implémentation", expanded=True): | |
| st.markdown("#### FastAPI - Endpoint principal") | |
| code_fastapi = ''' | |
| @app.post("/predict", response_model=PredictionResponse, tags=["Prediction"]) | |
| async def predict_intent(input_data: TextInput): | |
| """ | |
| Prédit l'intention d'un texte en Darija. | |
| """ | |
| try: | |
| text = input_data.text.strip() | |
| # Validation des entrées | |
| if not text or len(text) < 2: | |
| raise HTTPException( | |
| status_code=400, | |
| detail="Le texte d'entrée est vide ou trop court" | |
| ) | |
| # Appel au service NLU | |
| intent, confidence = await NLU_service.predict_intent(text) | |
| return PredictionResponse( | |
| intent=intent, | |
| confidence=float(confidence) | |
| ) | |
| except Exception as e: | |
| # Gestion des erreurs | |
| if isinstance(e, HTTPException): | |
| raise e | |
| else: | |
| raise HTTPException( | |
| status_code=500, | |
| detail=f"Erreur lors de la prédiction: {str(e)}" | |
| ) | |
| ''' | |
| st.code(code_fastapi, language="python") | |
| st.markdown("#### Dockerfile") | |
| code_dockerfile = ''' | |
| # Étape 1: Utiliser une image de base Python officielle | |
| FROM python:3.9-slim | |
| # Étape 2: Définir le répertoire de travail dans le container | |
| WORKDIR /app | |
| # Étape 3: Copier le fichier des dépendances | |
| COPY requirements.txt requirements.txt | |
| # Étape 4: Installer les dépendances | |
| # --no-cache-dir pour garder l'image légère | |
| RUN pip install --no-cache-dir --upgrade pip | |
| RUN pip install --no-cache-dir -r requirements.txt | |
| # Étape 5: Copier tout le reste de votre projet dans le container | |
| COPY . . | |
| # Étape 6: Exposer le port que votre API utilise | |
| EXPOSE 8000 | |
| # Étape 7: La commande pour lancer l'API quand le container démarre | |
| CMD ["uvicorn", "main:app", "--host", "0.0.0.0", "--port", "8000"] | |
| ''' | |
| st.code(code_dockerfile, language="dockerfile") | |
| # Section Déploiement | |
| st.markdown('<h3 class="section-title">Déploiement</h3>', unsafe_allow_html=True) | |
| deployment_tabs = st.tabs(["Hugging Face Spaces", "Intégration AICC", "Monitoring"]) | |
| with deployment_tabs[0]: | |
| st.markdown(""" | |
| Notre API est déployée sur Hugging Face Spaces qui offre: | |
| - Infrastructure évolutive | |
| - Monitoring intégré | |
| - Haute disponibilité | |
| - Intégration CI/CD via Git | |
| Le déploiement est automatiquement effectué à chaque push sur le dépôt GitHub. | |
| """) | |
| st.info("Notre API est déployée avec Hugging Face Spaces pour bénéficier d'une infrastructure évolutive et d'un déploiement continu.") | |
| st.markdown("#### URL de l'API déployée") | |
| st.markdown("[https://mediani-darija-aicc-api.hf.space](https://mediani-darija-aicc-api.hf.space)") | |
| st.markdown("#### Documentation Swagger") | |
| st.markdown("[https://mediani-darija-aicc-api.hf.space/docs](https://mediani-darija-aicc-api.hf.space/docs)") | |
| with deployment_tabs[1]: | |
| st.markdown(""" | |
| **L'intégration avec la plateforme AICC de Huawei comprend:** | |
| 1. Configuration des webhooks pour les appels API | |
| 2. Adaptation des réponses JSON au format AICC | |
| 3. Mise en place d'une authentification sécurisée | |
| 4. Calibration des timeouts et des retry policies | |
| Cette intégration permet d'enrichir les capacités de compréhension du langage naturel d'AICC avec notre modèle spécialisé pour la Darija. | |
| """) | |
| with deployment_tabs[2]: | |
| st.markdown(""" | |
| ### Le monitoring de notre API inclut: | |
| """) | |
| monitoring_cols = st.columns(2) | |
| with monitoring_cols[0]: | |
| st.metric( | |
| label="Temps de réponse", | |
| value="127ms", | |
| delta="-5ms" | |
| ) | |
| st.metric( | |
| label="Taux de disponibilité", | |
| value="99.97%", | |
| delta="+0.2%" | |
| ) | |
| with monitoring_cols[1]: | |
| st.metric( | |
| label="Distribution des intentions", | |
| value="9 catégories", | |
| help="Répartition équilibrée entre les différentes intentions" | |
| ) | |
| st.metric( | |
| label="Alertes en cas d'anomalies", | |
| value="Activées", | |
| help="Système de détection d'anomalies en temps réel" | |
| ) | |
| st.info("Les métriques sont collectées en temps réel et disponibles via un tableau de bord dédié.") | |
| # Pied de page | |
| st.markdown("---") | |
| st.markdown("© 2025 Mohammed MEDIANI - Université Mohammed Premier - École Supérieure de Technologie de Nador") |