Spaces:
Sleeping
Sleeping
streamlit_app.py
Browse files- streamlit_app.py +0 -1751
streamlit_app.py
DELETED
@@ -1,1751 +0,0 @@
|
|
1 |
-
import streamlit as st
|
2 |
-
import requests
|
3 |
-
import json
|
4 |
-
import pandas as pd
|
5 |
-
import matplotlib.pyplot as plt
|
6 |
-
import time
|
7 |
-
import numpy as np
|
8 |
-
import altair as alt
|
9 |
-
from PIL import Image
|
10 |
-
import plotly.graph_objects as go
|
11 |
-
import plotly.express as px
|
12 |
-
from streamlit_lottie import st_lottie
|
13 |
-
import requests
|
14 |
-
import random
|
15 |
-
import plotly.graph_objects as go
|
16 |
-
from streamlit_lottie import st_lottie
|
17 |
-
|
18 |
-
# Configuration de la page
|
19 |
-
st.set_page_config(
|
20 |
-
page_title="API NLU Darija - Mohammed MEDIANI",
|
21 |
-
page_icon="🚀",
|
22 |
-
layout="wide",
|
23 |
-
initial_sidebar_state="expanded"
|
24 |
-
)
|
25 |
-
|
26 |
-
# Fonctions utilitaires
|
27 |
-
def call_api(text):
|
28 |
-
"""Appelle l'API NLU Darija et retourne le résultat"""
|
29 |
-
api_url = "https://mediani-darija-aicc-api.hf.space/predict"
|
30 |
-
|
31 |
-
try:
|
32 |
-
start_time = time.time()
|
33 |
-
response = requests.post(
|
34 |
-
api_url,
|
35 |
-
headers={"Content-Type": "application/json"},
|
36 |
-
data=json.dumps({"text": text})
|
37 |
-
)
|
38 |
-
response_time = (time.time() - start_time) * 1000 # en ms
|
39 |
-
|
40 |
-
if response.status_code == 200:
|
41 |
-
result = response.json()
|
42 |
-
return result, response_time
|
43 |
-
else:
|
44 |
-
return None, response_time
|
45 |
-
except Exception as e:
|
46 |
-
st.error(f"Erreur de connexion: {str(e)}")
|
47 |
-
return None, 0
|
48 |
-
|
49 |
-
def get_intent_description(intent):
|
50 |
-
"""Retourne la description d'une intention"""
|
51 |
-
descriptions = {
|
52 |
-
"consulter_solde": "L'utilisateur souhaite connaître son solde ou crédit restant sur son compte.",
|
53 |
-
"reclamer_facture": "L'utilisateur signale un problème avec sa facture ou conteste un montant facturé.",
|
54 |
-
"declarer_panne": "L'utilisateur signale un dysfonctionnement technique avec son service ou équipement.",
|
55 |
-
"info_forfait": "L'utilisateur demande des informations sur un forfait existant ou nouveau.",
|
56 |
-
"recuperer_mot_de_passe": "L'utilisateur a besoin d'aide pour récupérer ou réinitialiser son mot de passe.",
|
57 |
-
"salutations": "L'utilisateur salue le service client ou initie une conversation.",
|
58 |
-
"remerciements": "L'utilisateur exprime sa gratitude pour l'aide reçue.",
|
59 |
-
"demander_agent_humain": "L'utilisateur souhaite être mis en relation avec un conseiller humain.",
|
60 |
-
"hors_scope": "La demande ne correspond à aucune intention prédéfinie dans notre système."
|
61 |
-
}
|
62 |
-
return descriptions.get(intent, "Description non disponible")
|
63 |
-
|
64 |
-
def get_intent_icon(intent):
|
65 |
-
"""Retourne une icône associée à une intention"""
|
66 |
-
icons = {
|
67 |
-
"consulter_solde": "💰",
|
68 |
-
"reclamer_facture": "📄",
|
69 |
-
"declarer_panne": "🔧",
|
70 |
-
"info_forfait": "ℹ️",
|
71 |
-
"recuperer_mot_de_passe": "🔑",
|
72 |
-
"salutations": "👋",
|
73 |
-
"remerciements": "🙏",
|
74 |
-
"demander_agent_humain": "👨💼",
|
75 |
-
"hors_scope": "❓"
|
76 |
-
}
|
77 |
-
return icons.get(intent, "🔍")
|
78 |
-
|
79 |
-
def get_intent_color(intent):
|
80 |
-
"""Retourne une couleur associée à une intention"""
|
81 |
-
colors = {
|
82 |
-
"consulter_solde": "#1f77b4",
|
83 |
-
"reclamer_facture": "#ff7f0e",
|
84 |
-
"declarer_panne": "#d62728",
|
85 |
-
"info_forfait": "#2ca02c",
|
86 |
-
"recuperer_mot_de_passe": "#9467bd",
|
87 |
-
"salutations": "#8c564b",
|
88 |
-
"remerciements": "#e377c2",
|
89 |
-
"demander_agent_humain": "#7f7f7f",
|
90 |
-
"hors_scope": "#bcbd22"
|
91 |
-
}
|
92 |
-
return colors.get(intent, "#17becf")
|
93 |
-
|
94 |
-
def load_lottie(url):
|
95 |
-
"""Charge une animation Lottie depuis une URL"""
|
96 |
-
try:
|
97 |
-
r = requests.get(url)
|
98 |
-
if r.status_code != 200:
|
99 |
-
return None
|
100 |
-
return r.json()
|
101 |
-
except:
|
102 |
-
return None
|
103 |
-
|
104 |
-
# Charger les animations
|
105 |
-
lottie_ai = load_lottie("https://assets8.lottiefiles.com/packages/lf20_ikvz7qhc.json")
|
106 |
-
lottie_process = load_lottie("https://assets6.lottiefiles.com/packages/lf20_khzniaya.json")
|
107 |
-
|
108 |
-
# Style CSS personnalisé
|
109 |
-
st.markdown("""
|
110 |
-
<style>
|
111 |
-
/* Style général */
|
112 |
-
.main-title {
|
113 |
-
font-size: 2.8rem !important;
|
114 |
-
color: #1E3A8A;
|
115 |
-
padding-bottom: 0.5rem;
|
116 |
-
border-bottom: 3px solid #3B82F6;
|
117 |
-
font-weight: 700;
|
118 |
-
text-shadow: 0px 2px 2px rgba(0,0,0,0.1);
|
119 |
-
margin-bottom: 1.5rem;
|
120 |
-
}
|
121 |
-
|
122 |
-
.sub-title {
|
123 |
-
font-size: 1.8rem !important;
|
124 |
-
color: #1E3A8A;
|
125 |
-
margin-top: 1.5rem;
|
126 |
-
margin-bottom: 1rem;
|
127 |
-
font-weight: 600;
|
128 |
-
}
|
129 |
-
|
130 |
-
.section-title {
|
131 |
-
font-size: 1.4rem !important;
|
132 |
-
color: #2563EB;
|
133 |
-
margin-top: 1.2rem;
|
134 |
-
margin-bottom: 0.8rem;
|
135 |
-
font-weight: 500;
|
136 |
-
}
|
137 |
-
|
138 |
-
/* Boîtes d'information */
|
139 |
-
.info-box {
|
140 |
-
background-color: #F8FAFC;
|
141 |
-
padding: 1.5rem;
|
142 |
-
border-radius: 0.75rem;
|
143 |
-
border: 2px solid #E2E8F0;
|
144 |
-
margin-bottom: 1.5rem;
|
145 |
-
box-shadow: 0 4px 12px rgba(0,0,0,0.08);
|
146 |
-
color: #1A202C;
|
147 |
-
font-size: 16px;
|
148 |
-
line-height: 1.7;
|
149 |
-
}
|
150 |
-
|
151 |
-
.info-box p {
|
152 |
-
margin-bottom: 12px;
|
153 |
-
font-weight: 500;
|
154 |
-
}
|
155 |
-
|
156 |
-
.info-box strong {
|
157 |
-
color: #1E3A8A;
|
158 |
-
font-weight: 700;
|
159 |
-
}
|
160 |
-
|
161 |
-
.info-box ul {
|
162 |
-
margin-left: 20px;
|
163 |
-
color: #4A5568;
|
164 |
-
}
|
165 |
-
|
166 |
-
.info-box li {
|
167 |
-
margin-bottom: 8px;
|
168 |
-
font-weight: 500;
|
169 |
-
}
|
170 |
-
|
171 |
-
.warning-box {
|
172 |
-
background-color: #FEF3C7;
|
173 |
-
padding: 1.2rem;
|
174 |
-
border-radius: 0.5rem;
|
175 |
-
border-left: 5px solid #F59E0B;
|
176 |
-
margin-bottom: 1.5rem;
|
177 |
-
box-shadow: 0 4px 6px rgba(0,0,0,0.05);
|
178 |
-
}
|
179 |
-
|
180 |
-
.success-box {
|
181 |
-
background-color: #ECFDF5;
|
182 |
-
padding: 1.2rem;
|
183 |
-
border-radius: 0.5rem;
|
184 |
-
border-left: 5px solid #10B981;
|
185 |
-
margin-bottom: 1.5rem;
|
186 |
-
box-shadow: 0 4px 6px rgba(0,0,0,0.05);
|
187 |
-
}
|
188 |
-
|
189 |
-
/* Boutons */
|
190 |
-
.stButton>button {
|
191 |
-
border-radius: 0.5rem;
|
192 |
-
font-weight: 500;
|
193 |
-
transition: all 0.3s ease;
|
194 |
-
}
|
195 |
-
|
196 |
-
.stButton>button:hover {
|
197 |
-
transform: translateY(-2px);
|
198 |
-
box-shadow: 0 4px 8px rgba(0,0,0,0.1);
|
199 |
-
}
|
200 |
-
|
201 |
-
.example-button {
|
202 |
-
margin: 0.3rem;
|
203 |
-
}
|
204 |
-
|
205 |
-
/* Tags et badges */
|
206 |
-
.intent-tag {
|
207 |
-
background-color: #1E3A8A;
|
208 |
-
color: white;
|
209 |
-
padding: 0.4rem 1rem;
|
210 |
-
border-radius: 2rem;
|
211 |
-
font-weight: bold;
|
212 |
-
display: inline-block;
|
213 |
-
margin-bottom: 0.8rem;
|
214 |
-
box-shadow: 0 2px 4px rgba(0,0,0,0.1);
|
215 |
-
}
|
216 |
-
|
217 |
-
.badge {
|
218 |
-
padding: 0.2rem 0.6rem;
|
219 |
-
border-radius: 2rem;
|
220 |
-
font-size: 0.8rem;
|
221 |
-
font-weight: bold;
|
222 |
-
margin-left: 0.5rem;
|
223 |
-
}
|
224 |
-
|
225 |
-
/* Conteneurs */
|
226 |
-
.glass-container {
|
227 |
-
background: rgba(255, 255, 255, 0.7);
|
228 |
-
backdrop-filter: blur(10px);
|
229 |
-
border-radius: 10px;
|
230 |
-
border: 1px solid rgba(255, 255, 255, 0.18);
|
231 |
-
padding: 1.5rem;
|
232 |
-
box-shadow: 0 8px 32px 0 rgba(31, 38, 135, 0.37);
|
233 |
-
}
|
234 |
-
|
235 |
-
/* Animations */
|
236 |
-
@keyframes fadeIn {
|
237 |
-
from { opacity: 0; }
|
238 |
-
to { opacity: 1; }
|
239 |
-
}
|
240 |
-
|
241 |
-
.fade-in {
|
242 |
-
animation: fadeIn 0.5s ease-in-out;
|
243 |
-
}
|
244 |
-
|
245 |
-
@keyframes slideInFromLeft {
|
246 |
-
0% {
|
247 |
-
transform: translateX(-30px);
|
248 |
-
opacity: 0;
|
249 |
-
}
|
250 |
-
100% {
|
251 |
-
transform: translateX(0);
|
252 |
-
opacity: 1;
|
253 |
-
}
|
254 |
-
}
|
255 |
-
|
256 |
-
.slide-in {
|
257 |
-
animation: slideInFromLeft 0.5s ease-out;
|
258 |
-
}
|
259 |
-
|
260 |
-
/* Mise en page de l'en-tête */
|
261 |
-
.header-content {
|
262 |
-
display: flex;
|
263 |
-
align-items: center;
|
264 |
-
margin-bottom: 1.5rem;
|
265 |
-
}
|
266 |
-
|
267 |
-
.description-container {
|
268 |
-
flex: 3;
|
269 |
-
padding-right: 1.5rem;
|
270 |
-
}
|
271 |
-
|
272 |
-
.image-container {
|
273 |
-
flex: 2;
|
274 |
-
display: flex;
|
275 |
-
justify-content: center;
|
276 |
-
align-items: center;
|
277 |
-
}
|
278 |
-
|
279 |
-
/* Responsive design */
|
280 |
-
@media (max-width: 768px) {
|
281 |
-
.main-title { font-size: 2rem !important; }
|
282 |
-
.sub-title { font-size: 1.5rem !important; }
|
283 |
-
.section-title { font-size: 1.2rem !important; }
|
284 |
-
.header-content { flex-direction: column; }
|
285 |
-
.description-container { padding-right: 0; padding-bottom: 1.5rem; }
|
286 |
-
}
|
287 |
-
|
288 |
-
/* Table des performances */
|
289 |
-
.styled-table {
|
290 |
-
width: 100%;
|
291 |
-
border-collapse: collapse;
|
292 |
-
margin: 1.5rem 0;
|
293 |
-
border-radius: 8px;
|
294 |
-
overflow: hidden;
|
295 |
-
box-shadow: 0 0 20px rgba(0, 0, 0, 0.1);
|
296 |
-
}
|
297 |
-
|
298 |
-
.styled-table thead tr {
|
299 |
-
background-color: #1E3A8A;
|
300 |
-
color: white;
|
301 |
-
text-align: left;
|
302 |
-
}
|
303 |
-
|
304 |
-
.styled-table th,
|
305 |
-
.styled-table td {
|
306 |
-
padding: 12px 15px;
|
307 |
-
}
|
308 |
-
|
309 |
-
.styled-table tbody tr {
|
310 |
-
border-bottom: 1px solid #dddddd;
|
311 |
-
}
|
312 |
-
|
313 |
-
.styled-table tbody tr:nth-of-type(even) {
|
314 |
-
background-color: #f9fafb;
|
315 |
-
}
|
316 |
-
|
317 |
-
.styled-table tbody tr:last-of-type {
|
318 |
-
border-bottom: 2px solid #1E3A8A;
|
319 |
-
}
|
320 |
-
|
321 |
-
/* Masquer les éléments par défaut de Streamlit qu'on ne veut pas voir */
|
322 |
-
#MainMenu {visibility: hidden;}
|
323 |
-
footer {visibility: hidden;}
|
324 |
-
.viewerBadge_container__r5tak {display: none;}
|
325 |
-
</style>
|
326 |
-
""", unsafe_allow_html=True)
|
327 |
-
|
328 |
-
# Configuration des colonnes principales
|
329 |
-
header_col1, header_col2 = st.columns([2, 5])
|
330 |
-
|
331 |
-
# Logo et informations de base
|
332 |
-
with header_col1:
|
333 |
-
try:
|
334 |
-
st.image("logo_est_nador.png", width=200)
|
335 |
-
except:
|
336 |
-
st.markdown("<h3>EST Nador</h3>", unsafe_allow_html=True)
|
337 |
-
st.warning("Logo non trouvé. Placez 'logo_est_nador.png' dans le dossier du projet.")
|
338 |
-
|
339 |
-
st.markdown('<div class="slide-in">', unsafe_allow_html=True)
|
340 |
-
st.markdown("### Stage de Fin d'Études")
|
341 |
-
st.markdown("**Étudiant:** Mohammed MEDIANI")
|
342 |
-
st.markdown("**Filière:** IAID - EST Nador")
|
343 |
-
st.markdown("**Date:** Juin 2025")
|
344 |
-
st.markdown("</div>", unsafe_allow_html=True)
|
345 |
-
|
346 |
-
st.markdown("---")
|
347 |
-
|
348 |
-
st.markdown('<div class="fade-in">', unsafe_allow_html=True)
|
349 |
-
st.markdown("### Encadrement")
|
350 |
-
st.markdown("- **Pr. ACHSAS SANAE** (Académique)")
|
351 |
-
st.markdown("- **Mme. Aya BENNANI** (Professionnelle)")
|
352 |
-
st.markdown("</div>", unsafe_allow_html=True)
|
353 |
-
|
354 |
-
st.markdown("---")
|
355 |
-
|
356 |
-
st.markdown('<div class="fade-in">', unsafe_allow_html=True)
|
357 |
-
st.markdown("### Informations sur l'API")
|
358 |
-
st.markdown("**URL:** [mediani-darija-aicc-api.hf.space](https://mediani-darija-aicc-api.hf.space)")
|
359 |
-
st.markdown("**Documentation:** [API Docs](https://mediani-darija-aicc-api.hf.space/docs)")
|
360 |
-
st.markdown("</div>", unsafe_allow_html=True)
|
361 |
-
|
362 |
-
# Titre principal et description
|
363 |
-
with header_col2:
|
364 |
-
st.markdown('<h1 class="main-title">API de NLU pour le Dialecte Marocain (Darija)</h1>', unsafe_allow_html=True)
|
365 |
-
|
366 |
-
# Description du projet - Style raffiné et concis pour équilibrer avec la grande animation
|
367 |
-
st.markdown("""
|
368 |
-
<div class="info-box fade-in" style="margin-bottom: 15px; padding: 1.2rem; border: 1px solid #E2E8F0;">
|
369 |
-
<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>
|
370 |
-
<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>
|
371 |
-
<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>
|
372 |
-
</div>
|
373 |
-
""", unsafe_allow_html=True)
|
374 |
-
|
375 |
-
# Animation Lottie centrée sous le texte - Taille agrandie pour remplir l'espace vertical
|
376 |
-
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)
|
377 |
-
if lottie_ai:
|
378 |
-
st_lottie(lottie_ai, height=400, width=680, key="ai_animation", quality="high")
|
379 |
-
st.markdown('</div>', unsafe_allow_html=True)
|
380 |
-
|
381 |
-
# Espace réduit car l'animation est plus grande et remplit déjà bien l'espace
|
382 |
-
st.markdown("<div style='height: 10px;'></div>", unsafe_allow_html=True)
|
383 |
-
|
384 |
-
# Onglets principaux avec icônes
|
385 |
-
tab1, tab2, tab3 = st.tabs(["🔍 Démonstration", "📊 Performances", "🏗️ Architecture"])
|
386 |
-
|
387 |
-
# Onglet Démonstration
|
388 |
-
with tab1:
|
389 |
-
st.markdown('<h2 class="sub-title slide-in">Testez l\'API en direct</h2>', unsafe_allow_html=True)
|
390 |
-
|
391 |
-
# Description du service
|
392 |
-
st.info("""
|
393 |
-
**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.**
|
394 |
-
|
395 |
-
**Instructions:**
|
396 |
-
1. Entrez un texte en Darija ou sélectionnez un exemple prédéfini
|
397 |
-
2. Cliquez sur le bouton "Analyser l'intention"
|
398 |
-
3. Observez les résultats de la détection d'intention
|
399 |
-
|
400 |
-
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).
|
401 |
-
""")
|
402 |
-
|
403 |
-
# Exemples prédéfinis
|
404 |
-
st.markdown('<h3 class="section-title">Exemples à tester</h3>', unsafe_allow_html=True)
|
405 |
-
|
406 |
-
# Organisation des exemples par catégories
|
407 |
-
with st.expander("🔄 Exemples par catégorie d'intention", expanded=True):
|
408 |
-
tab_expl1, tab_expl2, tab_expl3 = st.tabs(["Requêtes techniques", "Interactions", "Code-switching"])
|
409 |
-
|
410 |
-
with tab_expl1:
|
411 |
-
exemples_tech = {
|
412 |
-
"Consulter solde": "بغيت نعرف شحال باقي ليا في رصيدي",
|
413 |
-
"Déclarer panne": "ماكيخدمش عندي لانترنيت هاذي شي سيمانة",
|
414 |
-
"Réclamation facture": "فاكتورة هاد الشهر غالية بزاف، بغيت نشوف علاش",
|
415 |
-
"Info forfait": "شنو هوما لوفر ديال لانترنيت لي كاينين دابا",
|
416 |
-
"Récupérer mot de passe": "نسيت mon mot de passe ديالي واش يمكن تساعدني؟"
|
417 |
-
}
|
418 |
-
|
419 |
-
cols = st.columns(3)
|
420 |
-
for i, (label, exemple) in enumerate(exemples_tech.items()):
|
421 |
-
with cols[i % 3]:
|
422 |
-
if st.button(f"{label}", key=f"tech_btn_{i}", help=exemple):
|
423 |
-
st.session_state["user_input"] = exemple
|
424 |
-
st.rerun()
|
425 |
-
|
426 |
-
with tab_expl2:
|
427 |
-
exemples_inter = {
|
428 |
-
"Salutations": "salam 3lik bkhir",
|
429 |
-
"Remerciement": "شكرا بزاف على المساعدة ديالكم، كنتو مزيانين معايا",
|
430 |
-
"Demander agent": "Brit nhdar m3a service client ma bghitch robot",
|
431 |
-
"Hors scope": "واش كاين شي طريقة باش نلعب تينيس فهاد الويكاند؟"
|
432 |
-
}
|
433 |
-
|
434 |
-
cols = st.columns(2)
|
435 |
-
for i, (label, exemple) in enumerate(exemples_inter.items()):
|
436 |
-
with cols[i % 2]:
|
437 |
-
if st.button(f"{label}", key=f"inter_btn_{i}", help=exemple):
|
438 |
-
st.session_state["user_input"] = exemple
|
439 |
-
st.rerun()
|
440 |
-
|
441 |
-
with tab_expl3:
|
442 |
-
exemples_code = {
|
443 |
-
"Solde (code-switching)": "بغيت نعرف le solde ديالي شحال باقي",
|
444 |
-
"Panne (code-switching)": "عندي problème فالفاكتورة ديالي",
|
445 |
-
"Mot de passe (code-switching)": "نسيت mon mot de passe ديالي واش يمكن تساعدني؟",
|
446 |
-
"Salutations (code-switching)": "bonjour صاحبي، كيفاش يمكن لي نساعدك؟"
|
447 |
-
}
|
448 |
-
|
449 |
-
cols = st.columns(2)
|
450 |
-
for i, (label, exemple) in enumerate(exemples_code.items()):
|
451 |
-
with cols[i % 2]:
|
452 |
-
if st.button(f"{label}", key=f"code_btn_{i}", help=exemple):
|
453 |
-
st.session_state["user_input"] = exemple
|
454 |
-
st.rerun()
|
455 |
-
|
456 |
-
# Zone de texte pour l'entrée utilisateur
|
457 |
-
st.markdown('<h3 class="section-title">Votre requête</h3>', unsafe_allow_html=True)
|
458 |
-
|
459 |
-
if "user_input" not in st.session_state:
|
460 |
-
st.session_state["user_input"] = "بغيت نعرف شحال باقي ليا في رصيدي"
|
461 |
-
|
462 |
-
user_input = st.text_area("Entrez un texte en Darija:",
|
463 |
-
value=st.session_state["user_input"],
|
464 |
-
height=100,
|
465 |
-
key="input_area",
|
466 |
-
help="Vous pouvez entrer du texte en Darija pure ou mélangé avec du français")
|
467 |
-
|
468 |
-
# Bouton d'analyse avec animation de chargement
|
469 |
-
col1, col2, col3 = st.columns([1, 1, 1])
|
470 |
-
with col2:
|
471 |
-
analyze_btn = st.button("🔍 Analyser l'intention",
|
472 |
-
key="analyze_btn",
|
473 |
-
type="primary",
|
474 |
-
help="Cliquez pour analyser le texte")
|
475 |
-
|
476 |
-
# Analyser le texte si le bouton est cliqué
|
477 |
-
if analyze_btn:
|
478 |
-
with st.spinner("Analyse en cours..."):
|
479 |
-
# Afficher l'animation de traitement pendant l'appel à l'API
|
480 |
-
if lottie_process:
|
481 |
-
placeholder = st.empty()
|
482 |
-
with placeholder.container():
|
483 |
-
st_lottie(lottie_process, height=120, key="process_animation")
|
484 |
-
|
485 |
-
result, response_time = call_api(user_input)
|
486 |
-
|
487 |
-
# Supprimer l'animation une fois le résultat obtenu
|
488 |
-
if lottie_process:
|
489 |
-
placeholder.empty()
|
490 |
-
|
491 |
-
if result:
|
492 |
-
# Afficher les résultats dans un cadre
|
493 |
-
st.markdown("---")
|
494 |
-
st.markdown('<h3 class="section-title fade-in">Résultats de l\'analyse</h3>', unsafe_allow_html=True)
|
495 |
-
|
496 |
-
# Créer un conteneur de style "glass" pour les résultats
|
497 |
-
st.markdown('<div class="glass-container">', unsafe_allow_html=True)
|
498 |
-
|
499 |
-
# Créer deux colonnes pour les résultats
|
500 |
-
res_col1, res_col2 = st.columns([1, 1])
|
501 |
-
|
502 |
-
with res_col1:
|
503 |
-
# Icône et tag d'intention
|
504 |
-
intent_icon = get_intent_icon(result["intent"])
|
505 |
-
st.markdown(f'<div class="intent-tag" style="background-color: {get_intent_color(result["intent"])};">{intent_icon} {result["intent"]}</div>', unsafe_allow_html=True)
|
506 |
-
|
507 |
-
# Description de l'intention
|
508 |
-
st.markdown(f"**Description:** {get_intent_description(result['intent'])}")
|
509 |
-
|
510 |
-
# Temps de réponse avec badge coloré
|
511 |
-
speed_class = "success" if response_time < 200 else "warning" if response_time < 500 else "danger"
|
512 |
-
st.markdown(f"""
|
513 |
-
<div>
|
514 |
-
<span>⏱️ Temps de réponse:</span>
|
515 |
-
<span class="badge" style="background-color: {'#10B981' if speed_class == 'success' else '#F59E0B' if speed_class == 'warning' else '#EF4444'};">
|
516 |
-
{response_time:.2f} ms
|
517 |
-
</span>
|
518 |
-
</div>
|
519 |
-
""", unsafe_allow_html=True)
|
520 |
-
|
521 |
-
with res_col2:
|
522 |
-
# Utiliser Plotly pour un graphique interactif de confiance
|
523 |
-
fig = go.Figure()
|
524 |
-
|
525 |
-
# Ajouter la barre de fond
|
526 |
-
fig.add_trace(go.Bar(
|
527 |
-
x=[1],
|
528 |
-
y=['Confiance'],
|
529 |
-
orientation='h',
|
530 |
-
marker=dict(color='rgba(240, 240, 240, 0.5)'),
|
531 |
-
width=0.5,
|
532 |
-
hoverinfo='skip',
|
533 |
-
showlegend=False
|
534 |
-
))
|
535 |
-
|
536 |
-
# Ajouter la barre principale
|
537 |
-
fig.add_trace(go.Bar(
|
538 |
-
x=[result["confidence"]],
|
539 |
-
y=['Confiance'],
|
540 |
-
orientation='h',
|
541 |
-
marker=dict(color=get_intent_color(result["intent"])),
|
542 |
-
width=0.5,
|
543 |
-
hovertemplate=f'Confiance: {result["confidence"]*100:.1f}%<extra></extra>'
|
544 |
-
))
|
545 |
-
|
546 |
-
# Configuration de la mise en page
|
547 |
-
fig.update_layout(
|
548 |
-
title=f"Score: {result['confidence']*100:.1f}%",
|
549 |
-
height=150,
|
550 |
-
margin=dict(l=20, r=20, t=40, b=20),
|
551 |
-
xaxis=dict(
|
552 |
-
range=[0, 1],
|
553 |
-
tickvals=[0, 0.25, 0.5, 0.75, 1],
|
554 |
-
ticktext=['0%', '25%', '50%', '75%', '100%'],
|
555 |
-
gridcolor='rgba(0, 0, 0, 0.1)'
|
556 |
-
),
|
557 |
-
barmode='overlay',
|
558 |
-
bargap=0.1,
|
559 |
-
showlegend=False
|
560 |
-
)
|
561 |
-
|
562 |
-
st.plotly_chart(fig, use_container_width=True)
|
563 |
-
|
564 |
-
# Fermer le conteneur en verre
|
565 |
-
st.markdown('</div>', unsafe_allow_html=True)
|
566 |
-
|
567 |
-
# Explication technique
|
568 |
-
with st.expander("🔬 Explication technique", expanded=False):
|
569 |
-
st.markdown("""
|
570 |
-
Le texte passe par plusieurs étapes de traitement dans notre API:
|
571 |
-
|
572 |
-
1. **Prétraitement**:
|
573 |
-
- Normalisation du texte arabe (alif, yaa, etc.)
|
574 |
-
- Gestion spéciale des caractères non-arabes
|
575 |
-
- Traitement du code-switching Darija-Français
|
576 |
-
|
577 |
-
2. **Tokenisation**:
|
578 |
-
- Conversion en tokens avec le tokenizer de MARBERTv2
|
579 |
-
- Support des tokens spéciaux pour la Darija
|
580 |
-
|
581 |
-
3. **Inférence**:
|
582 |
-
- Passage dans le modèle fine-tuné sur notre corpus personnalisé
|
583 |
-
- Application d'une couche linéaire de classification
|
584 |
-
|
585 |
-
4. **Post-traitement**:
|
586 |
-
- Détermination de l'intention la plus probable
|
587 |
-
- Calcul du score de confiance via softmax
|
588 |
-
|
589 |
-
Le système utilise un modèle de type Transformer spécifiquement optimisé pour la Darija marocaine et ses spécificités dialectales.
|
590 |
-
""")
|
591 |
-
|
592 |
-
# Afficher le payload JSON avec coloration syntaxique
|
593 |
-
st.markdown("#### Requête et réponse JSON:")
|
594 |
-
col1, col2 = st.columns(2)
|
595 |
-
with col1:
|
596 |
-
st.code(json.dumps({"text": user_input}, indent=2, ensure_ascii=False), language="json")
|
597 |
-
with col2:
|
598 |
-
st.code(json.dumps(result, indent=2, ensure_ascii=False), language="json")
|
599 |
-
|
600 |
-
# Exemples similaires
|
601 |
-
with st.expander("📚 Exemples similaires", expanded=False):
|
602 |
-
st.markdown(f"### Autres exemples pour l'intention: {result['intent']}")
|
603 |
-
|
604 |
-
# Dictionnaire d'exemples par intention
|
605 |
-
exemples_par_intention = {
|
606 |
-
"consulter_solde": [
|
607 |
-
"شحال عندي في لكارط ديالي؟",
|
608 |
-
"بقا ليا شحال في الكريدي؟",
|
609 |
-
"واش ممكن تشوف ليا رصيدي؟",
|
610 |
-
"فين يمكن لي نراقب الكريدي ديالي؟",
|
611 |
-
"بغيت نعرف le solde ديالي شحال باقي"
|
612 |
-
],
|
613 |
-
"reclamer_facture": [
|
614 |
-
"عندي مشكل في الفاكتورة",
|
615 |
-
"الفاتورة ديال هاد الشهر مضاعفة على الشهر لي فات!",
|
616 |
-
"كينقصوني فلوس بزاف ف الفاكتورة",
|
617 |
-
"مكنستهلكش هاد القدر ديال الميكا، كاين خطأ",
|
618 |
-
"عندي problème فالفاكتورة ديالي"
|
619 |
-
],
|
620 |
-
"declarer_panne": [
|
621 |
-
"ماكيخدمش عندي لانترنيت هاذي شي سيمانة",
|
622 |
-
"التيليفون ما كيشارجيش، عيطو ليا بسرعة",
|
623 |
-
"عندي بروبليم فلانترنيت ديالي، كيقطع بزاف",
|
624 |
-
"ماكيدوزش عندي لابيل ديال التيليفزيون",
|
625 |
-
"j'ai un problème تقطع عليا الضو ديال مودام الويفي"
|
626 |
-
],
|
627 |
-
"info_forfait": [
|
628 |
-
"بغيت نبدل الفورفيه ديالي لشي وحدة أحسن",
|
629 |
-
"واش كاين شي فورفي ديال سوشيال ميديا؟",
|
630 |
-
"بغيت نخلص باش نزيد ف لانترنت ديالي",
|
631 |
-
"أشنو هو أحسن فورفيه عندكم؟",
|
632 |
-
"je cherche un forfait مزيان للانترنت"
|
633 |
-
],
|
634 |
-
"recuperer_mot_de_passe": [
|
635 |
-
"نسيت mon mot de passe ديالي واش يمكن تساعدني؟",
|
636 |
-
"كيفاش نقدر نسترجع كلمة السر؟",
|
637 |
-
"نسيت الكود ديالي ديال الكونيكسيون",
|
638 |
-
"بغيت نبدل لو دو باس ديالي",
|
639 |
-
"j'ai oublié لو دو باس ديال l'application"
|
640 |
-
],
|
641 |
-
"salutations": [
|
642 |
-
"صباح الخير، كيفاش يمكن لي نتواصل معاكم؟",
|
643 |
-
"السلام عليكم، بغيت نسولكم واحد السؤال",
|
644 |
-
"مرحبا، شكون لي كيهضر؟",
|
645 |
-
"آلو، واش نتا روبوت ولا بنادم حقيقي؟",
|
646 |
-
"bonjour صاحبي، كيفاش يمكن لي نساعدك؟"
|
647 |
-
],
|
648 |
-
"remerciements": [
|
649 |
-
"شكرا بزاف على المساعدة ديالكم",
|
650 |
-
"باراكا لاهو فيك، راك عاونتيني بزاف",
|
651 |
-
"ميرسي بزاف، ربي يجازيك بخير",
|
652 |
-
"متشكر على الوقت ديالك",
|
653 |
-
"merci بزاف على المساعدة ديالك"
|
654 |
-
],
|
655 |
-
"demander_agent_humain": [
|
656 |
-
"بغيت نتكلم مع شي واحد حقيقي ماشي روبو",
|
657 |
-
"واش ممكن تعاوني نهضر مع شي كونسيي؟",
|
658 |
-
"بغيت شي واحد يتواصل معايا هاتفيا",
|
659 |
-
"هادشي ماشي هو هداك لي كنبغي، خاصني بنادم نهضر معاه",
|
660 |
-
"je veux parler à un conseiller حقيقي"
|
661 |
-
],
|
662 |
-
"hors_scope": [
|
663 |
-
"واش كاين شي طريقة باش نلعب تينيس فهاد الويكاند؟",
|
664 |
-
"كيفاش طقس غدا فالرباط؟",
|
665 |
-
"شنو الأفلام الجديدة فالسينما؟",
|
666 |
-
"فين نقدر نلقى دواء بارسيتامول فالحي ديالي؟",
|
667 |
-
"je cherche un restaurant قريب من هنا"
|
668 |
-
]
|
669 |
-
}
|
670 |
-
|
671 |
-
# Afficher les exemples pour l'intention détectée
|
672 |
-
if result["intent"] in exemples_par_intention:
|
673 |
-
examples = exemples_par_intention[result["intent"]]
|
674 |
-
for ex in examples:
|
675 |
-
st.markdown(f"- `{ex}`")
|
676 |
-
|
677 |
-
# Bouton pour tester un exemple aléatoire
|
678 |
-
if st.button("🎲 Tester un exemple aléatoire", key="random_example"):
|
679 |
-
st.session_state["user_input"] = random.choice(examples)
|
680 |
-
st.rerun()
|
681 |
-
else:
|
682 |
-
st.write("Pas d'exemples disponibles pour cette intention.")
|
683 |
-
|
684 |
-
# Onglet Performances
|
685 |
-
with tab2:
|
686 |
-
st.markdown('<h2 class="sub-title slide-in">Performance du modèle</h2>', unsafe_allow_html=True)
|
687 |
-
|
688 |
-
# Description des performances
|
689 |
-
st.info("""
|
690 |
-
**Cette section présente les performances du modèle MARBERTv2 fine-tuné sur notre corpus de Darija.**
|
691 |
-
Les métriques ont été calculées sur un ensemble de test représentatif contenant 1 192 exemples issus de conversations réelles.
|
692 |
-
|
693 |
-
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.
|
694 |
-
""")
|
695 |
-
|
696 |
-
# Métriques globales
|
697 |
-
st.markdown('<h3 class="section-title">Métriques globales</h3>', unsafe_allow_html=True)
|
698 |
-
|
699 |
-
# Créer des colonnes pour les métriques avec des indicateurs visuels
|
700 |
-
metric_cols = st.columns(4)
|
701 |
-
|
702 |
-
# Créer des graphiques Gauge pour chaque métrique
|
703 |
-
with metric_cols[0]:
|
704 |
-
fig = go.Figure(go.Indicator(
|
705 |
-
mode = "gauge+number",
|
706 |
-
value = 92.8,
|
707 |
-
domain = {'x': [0, 1], 'y': [0, 1]},
|
708 |
-
title = {'text': "Accuracy", 'font': {'size': 24}},
|
709 |
-
gauge = {
|
710 |
-
'axis': {'range': [None, 100], 'tickwidth': 1, 'tickcolor': "darkblue"},
|
711 |
-
'bar': {'color': "#1E3A8A"},
|
712 |
-
'bgcolor': "white",
|
713 |
-
'borderwidth': 2,
|
714 |
-
'bordercolor': "gray",
|
715 |
-
'steps': [
|
716 |
-
{'range': [0, 70], 'color': '#FFEDD5'},
|
717 |
-
{'range': [70, 85], 'color': '#FEF3C7'},
|
718 |
-
{'range': [85, 100], 'color': '#ECFDF5'}],
|
719 |
-
}
|
720 |
-
))
|
721 |
-
|
722 |
-
fig.update_layout(height=200, margin=dict(l=20, r=20, t=50, b=20))
|
723 |
-
st.plotly_chart(fig, use_container_width=True)
|
724 |
-
|
725 |
-
with metric_cols[1]:
|
726 |
-
fig = go.Figure(go.Indicator(
|
727 |
-
mode = "gauge+number",
|
728 |
-
value = 93.1,
|
729 |
-
domain = {'x': [0, 1], 'y': [0, 1]},
|
730 |
-
title = {'text': "Precision", 'font': {'size': 24}},
|
731 |
-
gauge = {
|
732 |
-
'axis': {'range': [None, 100], 'tickwidth': 1, 'tickcolor': "darkblue"},
|
733 |
-
'bar': {'color': "#1E40AF"},
|
734 |
-
'bgcolor': "white",
|
735 |
-
'borderwidth': 2,
|
736 |
-
'bordercolor': "gray",
|
737 |
-
'steps': [
|
738 |
-
{'range': [0, 70], 'color': '#FFEDD5'},
|
739 |
-
{'range': [70, 85], 'color': '#FEF3C7'},
|
740 |
-
{'range': [85, 100], 'color': '#ECFDF5'}],
|
741 |
-
}
|
742 |
-
))
|
743 |
-
|
744 |
-
fig.update_layout(height=200, margin=dict(l=20, r=20, t=50, b=20))
|
745 |
-
st.plotly_chart(fig, use_container_width=True)
|
746 |
-
|
747 |
-
with metric_cols[2]:
|
748 |
-
fig = go.Figure(go.Indicator(
|
749 |
-
mode = "gauge+number",
|
750 |
-
value = 92.8,
|
751 |
-
domain = {'x': [0, 1], 'y': [0, 1]},
|
752 |
-
title = {'text': "Recall", 'font': {'size': 24}},
|
753 |
-
gauge = {
|
754 |
-
'axis': {'range': [None, 100], 'tickwidth': 1, 'tickcolor': "darkblue"},
|
755 |
-
'bar': {'color': "#2563EB"},
|
756 |
-
'bgcolor': "white",
|
757 |
-
'borderwidth': 2,
|
758 |
-
'bordercolor': "gray",
|
759 |
-
'steps': [
|
760 |
-
{'range': [0, 70], 'color': '#FFEDD5'},
|
761 |
-
{'range': [70, 85], 'color': '#FEF3C7'},
|
762 |
-
{'range': [85, 100], 'color': '#ECFDF5'}],
|
763 |
-
}
|
764 |
-
))
|
765 |
-
|
766 |
-
fig.update_layout(height=200, margin=dict(l=20, r=20, t=50, b=20))
|
767 |
-
st.plotly_chart(fig, use_container_width=True)
|
768 |
-
|
769 |
-
with metric_cols[3]:
|
770 |
-
fig = go.Figure(go.Indicator(
|
771 |
-
mode = "gauge+number",
|
772 |
-
value = 92.9,
|
773 |
-
domain = {'x': [0, 1], 'y': [0, 1]},
|
774 |
-
title = {'text': "F1-Score", 'font': {'size': 24}},
|
775 |
-
gauge = {
|
776 |
-
'axis': {'range': [None, 100], 'tickwidth': 1, 'tickcolor': "darkblue"},
|
777 |
-
'bar': {'color': "#3B82F6"},
|
778 |
-
'bgcolor': "white",
|
779 |
-
'borderwidth': 2,
|
780 |
-
'bordercolor': "gray",
|
781 |
-
'steps': [
|
782 |
-
{'range': [0, 70], 'color': '#FFEDD5'},
|
783 |
-
{'range': [70, 85], 'color': '#FEF3C7'},
|
784 |
-
{'range': [85, 100], 'color': '#ECFDF5'}],
|
785 |
-
}
|
786 |
-
))
|
787 |
-
|
788 |
-
fig.update_layout(height=200, margin=dict(l=20, r=20, t=50, b=20))
|
789 |
-
st.plotly_chart(fig, use_container_width=True)
|
790 |
-
|
791 |
-
# Matrice de confusion
|
792 |
-
st.markdown('<h3 class="section-title">Matrice de confusion</h3>', unsafe_allow_html=True)
|
793 |
-
|
794 |
-
# Créer les onglets pour choisir le type de visualisation
|
795 |
-
matrix_tab1, matrix_tab2 = st.tabs(["Heatmap interactive", "Image statique"])
|
796 |
-
|
797 |
-
with matrix_tab1:
|
798 |
-
# Créer une matrice de confusion fictive (similaire à celle montrée dans le rapport)
|
799 |
-
intent_labels = [
|
800 |
-
"consulter_solde", "reclamer_facture", "declarer_panne",
|
801 |
-
"info_forfait", "recuperer_mot_de_passe", "salutations",
|
802 |
-
"remerciements", "demander_agent_humain", "hors_scope"
|
803 |
-
]
|
804 |
-
|
805 |
-
# Matrice fictive (similaire à celle montrée dans le rapport)
|
806 |
-
conf_matrix = np.array([
|
807 |
-
[184, 0, 0, 3, 0, 0, 0, 0, 8],
|
808 |
-
[1, 130, 0, 2, 0, 0, 0, 2, 3],
|
809 |
-
[0, 2, 118, 0, 0, 0, 0, 6, 5],
|
810 |
-
[2, 3, 0, 121, 0, 0, 0, 0, 2],
|
811 |
-
[0, 0, 0, 0, 121, 0, 0, 2, 2],
|
812 |
-
[1, 0, 0, 0, 0, 109, 5, 0, 7],
|
813 |
-
[0, 0, 0, 0, 0, 3, 133, 0, 0],
|
814 |
-
[0, 0, 7, 0, 3, 0, 0, 111, 4],
|
815 |
-
[4, 2, 4, 0, 2, 9, 0, 5, 107]
|
816 |
-
])
|
817 |
-
|
818 |
-
# Créer un DataFrame pour Plotly
|
819 |
-
matrix_data = []
|
820 |
-
for i in range(len(intent_labels)):
|
821 |
-
for j in range(len(intent_labels)):
|
822 |
-
matrix_data.append({
|
823 |
-
'Réelle': intent_labels[i],
|
824 |
-
'Prédite': intent_labels[j],
|
825 |
-
'Valeur': conf_matrix[i, j]
|
826 |
-
})
|
827 |
-
|
828 |
-
df_conf = pd.DataFrame(matrix_data)
|
829 |
-
|
830 |
-
# Créer la heatmap avec Plotly
|
831 |
-
fig = px.density_heatmap(
|
832 |
-
df_conf,
|
833 |
-
x='Prédite',
|
834 |
-
y='Réelle',
|
835 |
-
z='Valeur',
|
836 |
-
color_continuous_scale='Blues',
|
837 |
-
text_auto=True
|
838 |
-
)
|
839 |
-
|
840 |
-
fig.update_layout(
|
841 |
-
title='Matrice de Confusion Interactive',
|
842 |
-
width=800,
|
843 |
-
height=600,
|
844 |
-
xaxis_title='Intention Prédite',
|
845 |
-
yaxis_title='Intention Réelle'
|
846 |
-
)
|
847 |
-
|
848 |
-
st.plotly_chart(fig, use_container_width=True)
|
849 |
-
|
850 |
-
with matrix_tab2:
|
851 |
-
try:
|
852 |
-
confusion_img = Image.open("images/image8.jpg")
|
853 |
-
st.image(confusion_img, caption="Matrice de Confusion pour la Classification d'Intents en Darija")
|
854 |
-
except:
|
855 |
-
st.warning("Image de matrice de confusion non trouvée. Placez 'image8.jpg' dans le dossier 'images/'.")
|
856 |
-
|
857 |
-
# Créer une heatmap avec Matplotlib
|
858 |
-
fig, ax = plt.subplots(figsize=(10, 8))
|
859 |
-
im = ax.imshow(conf_matrix, cmap='Blues')
|
860 |
-
|
861 |
-
# Étiquettes des axes
|
862 |
-
ax.set_xticks(np.arange(len(intent_labels)))
|
863 |
-
ax.set_yticks(np.arange(len(intent_labels)))
|
864 |
-
ax.set_xticklabels(intent_labels, rotation=45, ha="right")
|
865 |
-
ax.set_yticklabels(intent_labels)
|
866 |
-
|
867 |
-
# Ajout des valeurs dans les cellules
|
868 |
-
for i in range(len(intent_labels)):
|
869 |
-
for j in range(len(intent_labels)):
|
870 |
-
text = ax.text(j, i, conf_matrix[i, j],
|
871 |
-
ha="center", va="center", color="black" if conf_matrix[i, j] < 100 else "white")
|
872 |
-
|
873 |
-
ax.set_xlabel('Intention prédite')
|
874 |
-
ax.set_ylabel('Intention réelle')
|
875 |
-
ax.set_title('Matrice de Confusion')
|
876 |
-
fig.tight_layout()
|
877 |
-
|
878 |
-
st.pyplot(fig)
|
879 |
-
|
880 |
-
# Performance par intention
|
881 |
-
st.markdown('<h3 class="section-title">Performance par intention</h3>', unsafe_allow_html=True)
|
882 |
-
|
883 |
-
perf_tab1, perf_tab2 = st.tabs(["Graphique interactif", "Image statique"])
|
884 |
-
|
885 |
-
with perf_tab1:
|
886 |
-
# Créer un graphique interactif avec Plotly
|
887 |
-
intents = [
|
888 |
-
"consulter_solde", "reclamer_facture", "declarer_panne",
|
889 |
-
"info_forfait", "recuperer_mot_de_passe", "salutations",
|
890 |
-
"remerciements", "demander_agent_humain", "hors_scope"
|
891 |
-
]
|
892 |
-
|
893 |
-
# Données (similaires à celles du rapport)
|
894 |
-
precision = [0.981, 0.949, 0.907, 0.887, 0.947, 0.906, 0.964, 0.867, 0.847]
|
895 |
-
recall = [0.943, 0.944, 0.904, 0.945, 0.967, 0.890, 0.978, 0.931, 0.807]
|
896 |
-
f1 = [0.962, 0.946, 0.905, 0.915, 0.957, 0.898, 0.971, 0.898, 0.827]
|
897 |
-
|
898 |
-
# Créer un DataFrame pour Plotly
|
899 |
-
df_perf = pd.DataFrame({
|
900 |
-
'Intention': intents * 3,
|
901 |
-
'Métrique': ['Précision'] * len(intents) + ['Rappel'] * len(intents) + ['F1-Score'] * len(intents),
|
902 |
-
'Valeur': precision + recall + f1
|
903 |
-
})
|
904 |
-
|
905 |
-
# Créer le graphique avec Plotly
|
906 |
-
fig = px.bar(
|
907 |
-
df_perf,
|
908 |
-
x='Intention',
|
909 |
-
y='Valeur',
|
910 |
-
color='Métrique',
|
911 |
-
barmode='group',
|
912 |
-
color_discrete_map={
|
913 |
-
'Précision': '#1f77b4',
|
914 |
-
'Rappel': '#ff7f0e',
|
915 |
-
'F1-Score': '#2ca02c'
|
916 |
-
},
|
917 |
-
hover_data={'Intention': True, 'Métrique': True, 'Valeur': ':.3f'},
|
918 |
-
title='Performance par intention'
|
919 |
-
)
|
920 |
-
|
921 |
-
fig.update_layout(
|
922 |
-
yaxis=dict(
|
923 |
-
title='Score',
|
924 |
-
range=[0.7, 1]
|
925 |
-
),
|
926 |
-
xaxis_title='',
|
927 |
-
legend_title='Métrique',
|
928 |
-
height=500
|
929 |
-
)
|
930 |
-
|
931 |
-
st.plotly_chart(fig, use_container_width=True)
|
932 |
-
|
933 |
-
with perf_tab2:
|
934 |
-
try:
|
935 |
-
perf_img = Image.open("images/image11.jpg")
|
936 |
-
st.image(perf_img, caption="Performance du Modèle par Intent (Précision, Rappel, F1-Score)")
|
937 |
-
except:
|
938 |
-
st.warning("Image de performance par intent non trouvée. Placez 'image11.jpg' dans le dossier 'images/'.")
|
939 |
-
|
940 |
-
# Créer un graphique avec Matplotlib
|
941 |
-
fig, ax = plt.subplots(figsize=(12, 6))
|
942 |
-
|
943 |
-
x = np.arange(len(intents))
|
944 |
-
width = 0.25
|
945 |
-
|
946 |
-
ax.bar(x - width, precision, width, label='Précision', color='#1f77b4')
|
947 |
-
ax.bar(x, recall, width, label='Rappel', color='#ff7f0e')
|
948 |
-
ax.bar(x + width, f1, width, label='F1-Score', color='#2ca02c')
|
949 |
-
|
950 |
-
ax.set_ylabel('Score')
|
951 |
-
ax.set_title('Performance par intention')
|
952 |
-
ax.set_xticks(x)
|
953 |
-
ax.set_xticklabels(intents, rotation=45, ha='right')
|
954 |
-
ax.legend()
|
955 |
-
ax.set_ylim([0.7, 1])
|
956 |
-
|
957 |
-
fig.tight_layout()
|
958 |
-
|
959 |
-
st.pyplot(fig)
|
960 |
-
|
961 |
-
# Évolution de l'entraînement
|
962 |
-
st.markdown('<h3 class="section-title">Évolution de l\'entraînement</h3>', unsafe_allow_html=True)
|
963 |
-
|
964 |
-
train_tab1, train_tab2 = st.tabs(["Graphique interactif", "Image statique"])
|
965 |
-
|
966 |
-
with train_tab1:
|
967 |
-
# Créer un graphique interactif avec Plotly
|
968 |
-
steps = list(range(0, 1001, 50))
|
969 |
-
train_loss = [4.5] + [4.5 * np.exp(-0.005 * step) + 0.3 + 0.1 * np.random.random() for step in steps[1:]]
|
970 |
-
val_loss = [4.2] + [4.2 * np.exp(-0.005 * step) + 0.35 + 0.15 * np.random.random() for step in steps[1:]]
|
971 |
-
|
972 |
-
# Créer un DataFrame
|
973 |
-
df_loss = pd.DataFrame({
|
974 |
-
'Step': steps,
|
975 |
-
'Train Loss': train_loss,
|
976 |
-
'Val Loss': val_loss
|
977 |
-
})
|
978 |
-
|
979 |
-
# Convertir en format long pour Plotly
|
980 |
-
df_loss_long = pd.melt(
|
981 |
-
df_loss,
|
982 |
-
id_vars=['Step'],
|
983 |
-
value_vars=['Train Loss', 'Val Loss'],
|
984 |
-
var_name='Type',
|
985 |
-
value_name='Loss'
|
986 |
-
)
|
987 |
-
|
988 |
-
# Créer le graphique avec Plotly
|
989 |
-
fig = px.line(
|
990 |
-
df_loss_long,
|
991 |
-
x='Step',
|
992 |
-
y='Loss',
|
993 |
-
color='Type',
|
994 |
-
title='Évolution de la perte durant l\'entraînement',
|
995 |
-
color_discrete_map={
|
996 |
-
'Train Loss': 'blue',
|
997 |
-
'Val Loss': 'orange'
|
998 |
-
}
|
999 |
-
)
|
1000 |
-
|
1001 |
-
fig.update_layout(
|
1002 |
-
xaxis_title='Étapes',
|
1003 |
-
yaxis_title='Perte (Loss)',
|
1004 |
-
height=400
|
1005 |
-
)
|
1006 |
-
|
1007 |
-
st.plotly_chart(fig, use_container_width=True)
|
1008 |
-
|
1009 |
-
with train_tab2:
|
1010 |
-
try:
|
1011 |
-
training_img = Image.open("images/image5.jpg")
|
1012 |
-
st.image(training_img, caption="Évolution de la Perte (Loss) durant l'Entraînement")
|
1013 |
-
except:
|
1014 |
-
st.warning("Image d'évolution de l'entraînement non trouvée. Placez 'image5.jpg' dans le dossier 'images/'.")
|
1015 |
-
|
1016 |
-
# Créer un graphique avec Matplotlib
|
1017 |
-
fig, ax = plt.subplots(figsize=(10, 5))
|
1018 |
-
|
1019 |
-
ax.plot(steps, train_loss, label='Train Loss', color='blue')
|
1020 |
-
ax.plot(steps, val_loss, label='Val Loss', color='orange')
|
1021 |
-
|
1022 |
-
ax.set_xlabel('Étapes')
|
1023 |
-
ax.set_ylabel('Perte (Loss)')
|
1024 |
-
ax.set_title('Évolution de la perte durant l\'entraînement')
|
1025 |
-
ax.legend()
|
1026 |
-
ax.grid(True, linestyle='--', alpha=0.7)
|
1027 |
-
|
1028 |
-
fig.tight_layout()
|
1029 |
-
|
1030 |
-
st.pyplot(fig)
|
1031 |
-
|
1032 |
-
# Benchmarks et comparaisons
|
1033 |
-
st.markdown('<h3 class="section-title">Benchmarks et comparaisons</h3>', unsafe_allow_html=True)
|
1034 |
-
|
1035 |
-
st.markdown("""
|
1036 |
-
<table class="styled-table">
|
1037 |
-
<thead>
|
1038 |
-
<tr>
|
1039 |
-
<th>Modèle</th>
|
1040 |
-
<th>Accuracy</th>
|
1041 |
-
<th>F1-Score</th>
|
1042 |
-
<th>Temps de réponse</th>
|
1043 |
-
<th>Taille</th>
|
1044 |
-
</tr>
|
1045 |
-
</thead>
|
1046 |
-
<tbody>
|
1047 |
-
<tr>
|
1048 |
-
<td><strong>MARBERTv2 (notre approche)</strong></td>
|
1049 |
-
<td>92.8%</td>
|
1050 |
-
<td>92.9%</td>
|
1051 |
-
<td>127ms</td>
|
1052 |
-
<td>470MB</td>
|
1053 |
-
</tr>
|
1054 |
-
<tr>
|
1055 |
-
<td>AraBERT</td>
|
1056 |
-
<td>89.3%</td>
|
1057 |
-
<td>89.1%</td>
|
1058 |
-
<td>132ms</td>
|
1059 |
-
<td>543MB</td>
|
1060 |
-
</tr>
|
1061 |
-
<tr>
|
1062 |
-
<td>QARiB</td>
|
1063 |
-
<td>87.5%</td>
|
1064 |
-
<td>87.2%</td>
|
1065 |
-
<td>145ms</td>
|
1066 |
-
<td>420MB</td>
|
1067 |
-
</tr>
|
1068 |
-
<tr>
|
1069 |
-
<td>BERT Multilingue</td>
|
1070 |
-
<td>85.1%</td>
|
1071 |
-
<td>84.9%</td>
|
1072 |
-
<td>121ms</td>
|
1073 |
-
<td>680MB</td>
|
1074 |
-
</tr>
|
1075 |
-
<tr>
|
1076 |
-
<td>SVM + TF-IDF</td>
|
1077 |
-
<td>78.6%</td>
|
1078 |
-
<td>77.9%</td>
|
1079 |
-
<td>65ms</td>
|
1080 |
-
<td>25MB</td>
|
1081 |
-
</tr>
|
1082 |
-
</tbody>
|
1083 |
-
</table>
|
1084 |
-
""", unsafe_allow_html=True)
|
1085 |
-
|
1086 |
-
st.markdown("""
|
1087 |
-
<div class="info-box" style="background-color: #F8FAFC; border: 2px solid #E2E8F0; color: #1A202C; font-size: 16px; line-height: 1.6;">
|
1088 |
-
<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>
|
1089 |
-
|
1090 |
-
<p style="margin-bottom: 10px; font-weight: 600; color: #2D3748;">Les avantages de notre approche:</p>
|
1091 |
-
<ul style="margin-left: 20px; color: #4A5568;">
|
1092 |
-
<li style="margin-bottom: 8px; font-weight: 500;">Meilleure gestion des variations dialectales régionales</li>
|
1093 |
-
<li style="margin-bottom: 8px; font-weight: 500;">Support du code-switching entre Darija et Français</li>
|
1094 |
-
<li style="margin-bottom: 8px; font-weight: 500;">Bonne performance sur les expressions idiomatiques spécifiques</li>
|
1095 |
-
<li style="margin-bottom: 8px; font-weight: 500;">Équilibre optimal entre performance et temps de réponse</li>
|
1096 |
-
</ul>
|
1097 |
-
</div>
|
1098 |
-
""", unsafe_allow_html=True)
|
1099 |
-
|
1100 |
-
# Onglet Architecture
|
1101 |
-
with tab3:
|
1102 |
-
st.markdown('<h2 class="sub-title slide-in">Architecture de la solution</h2>', unsafe_allow_html=True)
|
1103 |
-
|
1104 |
-
# Description de l'architecture
|
1105 |
-
st.info("""
|
1106 |
-
**Cette section présente l'architecture technique de notre solution et son intégration avec la plateforme AICC.**
|
1107 |
-
|
1108 |
-
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.
|
1109 |
-
""")
|
1110 |
-
|
1111 |
-
# Architecture globale
|
1112 |
-
st.markdown('<h3 class="section-title">Architecture globale</h3>', unsafe_allow_html=True)
|
1113 |
-
|
1114 |
-
arch_tab1, arch_tab2 = st.tabs(["Diagramme interactif", "Image statique"])
|
1115 |
-
|
1116 |
-
with arch_tab1:
|
1117 |
-
# Créer un diagramme d'architecture professionnel avec Plotly
|
1118 |
-
fig = go.Figure()
|
1119 |
-
|
1120 |
-
# Définir une palette de couleurs professionnelle (dégradé de bleus)
|
1121 |
-
colors = {
|
1122 |
-
"Client": "#1E40AF", # Bleu foncé
|
1123 |
-
"AICC": "#2563EB", # Bleu royal
|
1124 |
-
"API Darija NLU": "#3B82F6", # Bleu moyen
|
1125 |
-
"MARBERTv2": "#60A5FA", # Bleu clair
|
1126 |
-
"Agents": "#1D4ED8", # Bleu profond
|
1127 |
-
"Call Center": "#1E3A8A" # Bleu très foncé
|
1128 |
-
}
|
1129 |
-
|
1130 |
-
# Repositionner les nœuds pour une meilleure présentation
|
1131 |
-
nodes = [
|
1132 |
-
{"name": "Client", "x": 0, "y": 0, "size": 80, "icon": "👤"},
|
1133 |
-
{"name": "AICC\nPlateforme", "x": 2, "y": 0, "size": 90, "icon": "🏢"},
|
1134 |
-
{"name": "API Darija\nNLU", "x": 4, "y": 0, "size": 85, "icon": "🔗"},
|
1135 |
-
{"name": "MARBERTv2\nModèle", "x": 6, "y": 0, "size": 75, "icon": "🧠"},
|
1136 |
-
{"name": "Agents\nHumains", "x": 2, "y": -1.5, "size": 70, "icon": "👨💼"},
|
1137 |
-
{"name": "Call Center\nSupport", "x": 3, "y": -2.5, "size": 65, "icon": "📞"}
|
1138 |
-
]
|
1139 |
-
|
1140 |
-
# Ajouter les nœuds avec des styles améliorés
|
1141 |
-
for i, node in enumerate(nodes):
|
1142 |
-
# Simplifier la logique de couleur
|
1143 |
-
node_name = node["name"].replace("\n", " ")
|
1144 |
-
if "Client" in node_name:
|
1145 |
-
node_color = colors["Client"]
|
1146 |
-
elif "AICC" in node_name:
|
1147 |
-
node_color = colors["AICC"]
|
1148 |
-
elif "API" in node_name or "Darija" in node_name:
|
1149 |
-
node_color = colors["API Darija NLU"]
|
1150 |
-
elif "MARBERT" in node_name or "Modèle" in node_name:
|
1151 |
-
node_color = colors["MARBERTv2"]
|
1152 |
-
elif "Agents" in node_name:
|
1153 |
-
node_color = colors["Agents"]
|
1154 |
-
elif "Call" in node_name or "Center" in node_name:
|
1155 |
-
node_color = colors["Call Center"]
|
1156 |
-
else:
|
1157 |
-
node_color = "#3B82F6" # Couleur par défaut
|
1158 |
-
|
1159 |
-
fig.add_trace(go.Scatter(
|
1160 |
-
x=[node["x"]],
|
1161 |
-
y=[node["y"]],
|
1162 |
-
mode="markers+text",
|
1163 |
-
marker=dict(
|
1164 |
-
size=node["size"],
|
1165 |
-
color=node_color,
|
1166 |
-
line=dict(width=3, color="white"),
|
1167 |
-
opacity=0.9
|
1168 |
-
),
|
1169 |
-
text=f'{node["icon"]}<br>{node["name"]}',
|
1170 |
-
textposition="middle center",
|
1171 |
-
textfont=dict(color="white", size=11, family="Arial Black"),
|
1172 |
-
hoverinfo="text",
|
1173 |
-
hovertext=f"<b>{node['name']}</b><br>Composant de l'architecture",
|
1174 |
-
hoverlabel=dict(bgcolor="rgba(0,0,0,0.8)", font_color="white"),
|
1175 |
-
showlegend=False
|
1176 |
-
))
|
1177 |
-
|
1178 |
-
# Définir les connexions avec des descriptions plus détaillées
|
1179 |
-
edges = [
|
1180 |
-
{"from": 0, "to": 1, "label": "Requête client\n(Darija)", "color": "#2563EB", "style": "solid"},
|
1181 |
-
{"from": 1, "to": 2, "label": "API Call\n(HTTPS/POST)", "color": "#3B82F6", "style": "solid"},
|
1182 |
-
{"from": 2, "to": 3, "label": "Inférence ML\n(Tokenization)", "color": "#60A5FA", "style": "solid"},
|
1183 |
-
{"from": 3, "to": 2, "label": "Prédiction\n(Intent + Score)", "color": "#60A5FA", "style": "dash"},
|
1184 |
-
{"from": 2, "to": 1, "label": "Réponse JSON\n(Structured)", "color": "#3B82F6", "style": "dash"},
|
1185 |
-
{"from": 1, "to": 0, "label": "Réponse adaptée\n(Interface)", "color": "#2563EB", "style": "dash"},
|
1186 |
-
{"from": 1, "to": 4, "label": "Transfert\n(Si nécessaire)", "color": "#1D4ED8", "style": "dot"},
|
1187 |
-
{"from": 4, "to": 5, "label": "Escalade\n(Support)", "color": "#1E3A8A", "style": "solid"},
|
1188 |
-
{"from": 5, "to": 0, "label": "Support avancé\n(Humain)", "color": "#1E3A8A", "style": "solid"}
|
1189 |
-
]
|
1190 |
-
|
1191 |
-
# Ajouter les connexions avec des styles variés
|
1192 |
-
for edge in edges:
|
1193 |
-
fig.add_shape(
|
1194 |
-
type="line",
|
1195 |
-
x0=nodes[edge["from"]]["x"],
|
1196 |
-
y0=nodes[edge["from"]]["y"],
|
1197 |
-
x1=nodes[edge["to"]]["x"],
|
1198 |
-
y1=nodes[edge["to"]]["y"],
|
1199 |
-
line=dict(
|
1200 |
-
color=edge["color"],
|
1201 |
-
width=3,
|
1202 |
-
dash=edge["style"]
|
1203 |
-
),
|
1204 |
-
xref="x",
|
1205 |
-
yref="y"
|
1206 |
-
)
|
1207 |
-
|
1208 |
-
# Ajouter des flèches pour indiquer la direction
|
1209 |
-
if edge["style"] != "dot": # Pas de flèche pour les connexions conditionnelles
|
1210 |
-
# Calculer la position de la flèche
|
1211 |
-
x0, y0 = nodes[edge["from"]]["x"], nodes[edge["from"]]["y"]
|
1212 |
-
x1, y1 = nodes[edge["to"]]["x"], nodes[edge["to"]]["y"]
|
1213 |
-
|
1214 |
-
# Position de la flèche (75% du chemin)
|
1215 |
-
arrow_x = x0 + 0.75 * (x1 - x0)
|
1216 |
-
arrow_y = y0 + 0.75 * (y1 - y0)
|
1217 |
-
|
1218 |
-
fig.add_trace(go.Scatter(
|
1219 |
-
x=[arrow_x],
|
1220 |
-
y=[arrow_y],
|
1221 |
-
mode="markers",
|
1222 |
-
marker=dict(
|
1223 |
-
symbol="arrow-right",
|
1224 |
-
size=15,
|
1225 |
-
color=edge["color"],
|
1226 |
-
line=dict(width=1, color="white")
|
1227 |
-
),
|
1228 |
-
hoverinfo="skip",
|
1229 |
-
showlegend=False
|
1230 |
-
))
|
1231 |
-
|
1232 |
-
# Ajouter les étiquettes des connexions
|
1233 |
-
midpoint_x = (nodes[edge["from"]]["x"] + nodes[edge["to"]]["x"]) / 2
|
1234 |
-
midpoint_y = (nodes[edge["from"]]["y"] + nodes[edge["to"]]["y"]) / 2
|
1235 |
-
|
1236 |
-
# Ajouter les étiquettes des connexions sans fond
|
1237 |
-
fig.add_trace(go.Scatter(
|
1238 |
-
x=[midpoint_x],
|
1239 |
-
y=[midpoint_y],
|
1240 |
-
mode="text",
|
1241 |
-
text=edge["label"],
|
1242 |
-
textposition="middle center",
|
1243 |
-
textfont=dict(
|
1244 |
-
size=9,
|
1245 |
-
color=edge["color"],
|
1246 |
-
family="Arial"
|
1247 |
-
),
|
1248 |
-
hoverinfo="text",
|
1249 |
-
hovertext=f"<b>Flux:</b> {edge['label']}",
|
1250 |
-
hoverlabel=dict(bgcolor="rgba(0,0,0,0.8)", font_color="white"),
|
1251 |
-
showlegend=False
|
1252 |
-
))
|
1253 |
-
|
1254 |
-
# Configuration avancée de la mise en page
|
1255 |
-
fig.update_layout(
|
1256 |
-
title={
|
1257 |
-
'text': "🏗️ Architecture d'Intégration - API NLU Darija avec AICC",
|
1258 |
-
'x': 0.5,
|
1259 |
-
'xanchor': 'center',
|
1260 |
-
'font': {'size': 18, 'color': '#1E3A8A', 'family': 'Arial Black'}
|
1261 |
-
},
|
1262 |
-
showlegend=False,
|
1263 |
-
hovermode="closest",
|
1264 |
-
height=500,
|
1265 |
-
margin=dict(t=80, b=40, l=40, r=40),
|
1266 |
-
xaxis=dict(
|
1267 |
-
showgrid=False,
|
1268 |
-
zeroline=False,
|
1269 |
-
showticklabels=False,
|
1270 |
-
range=[-0.5, 6.5]
|
1271 |
-
),
|
1272 |
-
yaxis=dict(
|
1273 |
-
showgrid=False,
|
1274 |
-
zeroline=False,
|
1275 |
-
showticklabels=False,
|
1276 |
-
range=[-3, 1]
|
1277 |
-
),
|
1278 |
-
plot_bgcolor="rgba(248,250,252,0.3)",
|
1279 |
-
paper_bgcolor="white",
|
1280 |
-
font=dict(family="Arial, sans-serif")
|
1281 |
-
)
|
1282 |
-
|
1283 |
-
# Ajouter une légende personnalisée
|
1284 |
-
fig.add_annotation(
|
1285 |
-
text="<b>Légende:</b><br>" +
|
1286 |
-
"━━━ Flux principal<br>" +
|
1287 |
-
"┄┄┄ Réponse<br>" +
|
1288 |
-
"••••• Transfert conditionnel",
|
1289 |
-
xref="paper", yref="paper",
|
1290 |
-
x=0.02, y=0.02,
|
1291 |
-
xanchor="left", yanchor="bottom",
|
1292 |
-
showarrow=False,
|
1293 |
-
font=dict(size=10, color="#1E3A8A"),
|
1294 |
-
bgcolor="rgba(255,255,255,0.9)",
|
1295 |
-
bordercolor="#E5E7EB",
|
1296 |
-
borderwidth=1
|
1297 |
-
)
|
1298 |
-
|
1299 |
-
st.plotly_chart(fig, use_container_width=True)
|
1300 |
-
|
1301 |
-
# Ajouter des métriques de performance de l'architecture
|
1302 |
-
st.markdown("### 📊 Métriques de Performance de l'Architecture")
|
1303 |
-
|
1304 |
-
perf_cols = st.columns(4)
|
1305 |
-
with perf_cols[0]:
|
1306 |
-
st.metric(
|
1307 |
-
label="⚡ Latence Moyenne",
|
1308 |
-
value="127ms",
|
1309 |
-
delta="-23ms vs baseline",
|
1310 |
-
delta_color="inverse"
|
1311 |
-
)
|
1312 |
-
|
1313 |
-
with perf_cols[1]:
|
1314 |
-
st.metric(
|
1315 |
-
label="🎯 Disponibilité",
|
1316 |
-
value="99.8%",
|
1317 |
-
delta="+0.3% ce mois",
|
1318 |
-
delta_color="normal"
|
1319 |
-
)
|
1320 |
-
|
1321 |
-
with perf_cols[2]:
|
1322 |
-
st.metric(
|
1323 |
-
label="🔄 Requêtes/sec",
|
1324 |
-
value="1,250",
|
1325 |
-
delta="+15% capacité",
|
1326 |
-
delta_color="normal"
|
1327 |
-
)
|
1328 |
-
|
1329 |
-
with perf_cols[3]:
|
1330 |
-
st.metric(
|
1331 |
-
label="🛡️ Taux d'erreur",
|
1332 |
-
value="0.2%",
|
1333 |
-
delta="-0.1% amélioration",
|
1334 |
-
delta_color="inverse"
|
1335 |
-
)
|
1336 |
-
|
1337 |
-
with arch_tab2:
|
1338 |
-
try:
|
1339 |
-
arch_img = Image.open("images/image17.jpg")
|
1340 |
-
st.image(arch_img, caption="Diagramme d'architecture de l'intégration avec AICC")
|
1341 |
-
except:
|
1342 |
-
st.warning("Image d'architecture non trouvée. Placez 'image17.jpg' dans le dossier 'images/'.")
|
1343 |
-
st.markdown("""
|
1344 |
-
```
|
1345 |
-
┌───────────────┐ ┌───────────────┐ ┌───────────────┐
|
1346 |
-
│ Client │ │ Plateforme │ │ API NLU │
|
1347 |
-
│ (Mobile/Web)│◄────►│ AICC Huawei │◄────►│ Darija │
|
1348 |
-
└───────────────┘ └───────────────┘ └───────────────┘
|
1349 |
-
▲ ▲
|
1350 |
-
│ │
|
1351 |
-
▼ │
|
1352 |
-
┌────────────┐ │
|
1353 |
-
│ Agents │ │
|
1354 |
-
│ Humains │ │
|
1355 |
-
└────────────┘ │
|
1356 |
-
▲ │
|
1357 |
-
│ │
|
1358 |
-
▼ ▼
|
1359 |
-
┌─────────────────────────────────┐
|
1360 |
-
│ Système de Gestion de │
|
1361 |
-
│ Centre de Contact │
|
1362 |
-
└─────────────────────────────────┘
|
1363 |
-
```
|
1364 |
-
""")
|
1365 |
-
|
1366 |
-
# Flux de traitement
|
1367 |
-
st.markdown('<h3 class="section-title">Flux de traitement</h3>', unsafe_allow_html=True)
|
1368 |
-
|
1369 |
-
flow_tab1, flow_tab2 = st.tabs(["Séquence interactive", "Image statique"])
|
1370 |
-
|
1371 |
-
with flow_tab1:
|
1372 |
-
# Créer un diagramme de séquence professionnel
|
1373 |
-
sequence_steps = [
|
1374 |
-
{"from": "Client", "to": "AICC", "message": "📱 Message Darija\n(Requête utilisateur)", "time": 1, "type": "request"},
|
1375 |
-
{"from": "AICC", "to": "API NLU", "message": "🔗 API Call HTTPS\n(POST /predict)", "time": 2, "type": "api"},
|
1376 |
-
{"from": "API NLU", "to": "MARBERTv2", "message": "🧠 Inférence ML\n(Tokenization)", "time": 3, "type": "ml"},
|
1377 |
-
{"from": "MARBERTv2", "to": "API NLU", "message": "🎯 Prédiction\n(Intent + Confidence)", "time": 4, "type": "response"},
|
1378 |
-
{"from": "API NLU", "to": "AICC", "message": "📊 Réponse JSON\n(Structured Data)", "time": 5, "type": "response"},
|
1379 |
-
{"from": "AICC", "to": "Client", "message": "✅ Réponse adaptée\n(Interface utilisateur)", "time": 6, "type": "response"}
|
1380 |
-
]
|
1381 |
-
|
1382 |
-
# Liste des acteurs avec couleurs et icônes
|
1383 |
-
actors = [
|
1384 |
-
{"name": "Client", "color": "#1E40AF", "icon": "👤"},
|
1385 |
-
{"name": "AICC Platform", "color": "#2563EB", "icon": "🏢"},
|
1386 |
-
{"name": "API NLU Darija", "color": "#3B82F6", "icon": "🔗"},
|
1387 |
-
{"name": "MARBERTv2 Model", "color": "#60A5FA", "icon": "🧠"}
|
1388 |
-
]
|
1389 |
-
|
1390 |
-
# Création du diagramme de séquence
|
1391 |
-
fig = go.Figure()
|
1392 |
-
|
1393 |
-
# Couleurs selon le type de message
|
1394 |
-
message_colors = {
|
1395 |
-
"request": "#1E40AF",
|
1396 |
-
"api": "#2563EB",
|
1397 |
-
"ml": "#3B82F6",
|
1398 |
-
"response": "#60A5FA"
|
1399 |
-
}
|
1400 |
-
|
1401 |
-
# Lignes de vie avec style professionnel
|
1402 |
-
for i, actor in enumerate(actors):
|
1403 |
-
# Ligne de vie
|
1404 |
-
fig.add_trace(go.Scatter(
|
1405 |
-
x=[i, i],
|
1406 |
-
y=[0.5, -7],
|
1407 |
-
mode="lines",
|
1408 |
-
line=dict(color=actor["color"], width=3, dash="dot"),
|
1409 |
-
opacity=0.6,
|
1410 |
-
hoverinfo="none",
|
1411 |
-
showlegend=False
|
1412 |
-
))
|
1413 |
-
|
1414 |
-
# En-tête des acteurs avec style moderne
|
1415 |
-
fig.add_trace(go.Scatter(
|
1416 |
-
x=[i],
|
1417 |
-
y=[0.5],
|
1418 |
-
mode="markers+text",
|
1419 |
-
marker=dict(
|
1420 |
-
size=60,
|
1421 |
-
color=actor["color"],
|
1422 |
-
line=dict(width=3, color="white"),
|
1423 |
-
opacity=0.9
|
1424 |
-
),
|
1425 |
-
text=f'{actor["icon"]}<br><b>{actor["name"]}</b>',
|
1426 |
-
textposition="middle center",
|
1427 |
-
textfont=dict(color="white", size=10, family="Arial Bold"),
|
1428 |
-
hoverinfo="text",
|
1429 |
-
hovertext=f"<b>{actor['name']}</b><br>Composant système",
|
1430 |
-
hoverlabel=dict(bgcolor="rgba(0,0,0,0.8)", font_color="white"),
|
1431 |
-
showlegend=False
|
1432 |
-
))
|
1433 |
-
|
1434 |
-
# Ajouter les messages avec styles différenciés
|
1435 |
-
for step in sequence_steps:
|
1436 |
-
# Trouver l'index des acteurs correspondants de manière plus flexible
|
1437 |
-
from_idx = -1
|
1438 |
-
to_idx = -1
|
1439 |
-
|
1440 |
-
# Recherche plus robuste pour les acteurs sources et destinations
|
1441 |
-
for i, actor in enumerate(actors):
|
1442 |
-
actor_name = actor["name"]
|
1443 |
-
# Vérifier si l'acteur correspond à l'acteur source
|
1444 |
-
if step["from"] in actor_name or actor_name.split()[0] == step["from"]:
|
1445 |
-
from_idx = i
|
1446 |
-
|
1447 |
-
# Vérifier si l'acteur correspond à l'acteur destination
|
1448 |
-
if step["to"] in actor_name or actor_name.split()[0] == step["to"]:
|
1449 |
-
to_idx = i
|
1450 |
-
|
1451 |
-
# Si on n'a pas trouvé les acteurs, utiliser une approche plus générique
|
1452 |
-
if from_idx == -1:
|
1453 |
-
from_idx = 0 # Utiliser le premier acteur par défaut
|
1454 |
-
print(f"Acteur source non trouvé pour {step['from']}")
|
1455 |
-
|
1456 |
-
if to_idx == -1:
|
1457 |
-
to_idx = 1 # Utiliser le deuxième acteur par défaut
|
1458 |
-
print(f"Acteur destination non trouvé pour {step['to']}")
|
1459 |
-
|
1460 |
-
time_y = -step["time"]
|
1461 |
-
color = message_colors[step["type"]]
|
1462 |
-
|
1463 |
-
# Flèche du message avec direction
|
1464 |
-
if from_idx < to_idx: # Message vers la droite
|
1465 |
-
arrow_symbol = "triangle-right"
|
1466 |
-
x_positions = [from_idx + 0.1, to_idx - 0.1]
|
1467 |
-
else: # Message vers la gauche
|
1468 |
-
arrow_symbol = "triangle-left"
|
1469 |
-
x_positions = [from_idx - 0.1, to_idx + 0.1]
|
1470 |
-
|
1471 |
-
# Ligne de message
|
1472 |
-
fig.add_shape(
|
1473 |
-
type="line",
|
1474 |
-
x0=x_positions[0],
|
1475 |
-
y0=time_y,
|
1476 |
-
x1=x_positions[1],
|
1477 |
-
y1=time_y,
|
1478 |
-
line=dict(color=color, width=3),
|
1479 |
-
xref="x",
|
1480 |
-
yref="y"
|
1481 |
-
)
|
1482 |
-
|
1483 |
-
# Flèche de direction
|
1484 |
-
fig.add_trace(go.Scatter(
|
1485 |
-
x=[x_positions[1]],
|
1486 |
-
y=[time_y],
|
1487 |
-
mode="markers",
|
1488 |
-
marker=dict(
|
1489 |
-
symbol=arrow_symbol,
|
1490 |
-
size=12,
|
1491 |
-
color=color,
|
1492 |
-
line=dict(width=1, color="white")
|
1493 |
-
),
|
1494 |
-
hoverinfo="skip",
|
1495 |
-
showlegend=False
|
1496 |
-
))
|
1497 |
-
|
1498 |
-
# Étiquette du message avec fond
|
1499 |
-
mid_x = (x_positions[0] + x_positions[1]) / 2
|
1500 |
-
fig.add_trace(go.Scatter(
|
1501 |
-
x=[mid_x],
|
1502 |
-
y=[time_y + 0.15],
|
1503 |
-
mode="text",
|
1504 |
-
text=step["message"],
|
1505 |
-
textposition="middle center",
|
1506 |
-
textfont=dict(
|
1507 |
-
size=9,
|
1508 |
-
color=color,
|
1509 |
-
family="Arial"
|
1510 |
-
),
|
1511 |
-
hoverinfo="text",
|
1512 |
-
hovertext=f"<b>Étape {step['time']}:</b><br>{step['message']}",
|
1513 |
-
hoverlabel=dict(bgcolor="rgba(0,0,0,0.8)", font_color="white"),
|
1514 |
-
showlegend=False
|
1515 |
-
))
|
1516 |
-
|
1517 |
-
# Ajouter un indicateur temporel
|
1518 |
-
fig.add_trace(go.Scatter(
|
1519 |
-
x=[-0.3],
|
1520 |
-
y=[time_y],
|
1521 |
-
mode="markers+text",
|
1522 |
-
marker=dict(size=20, color="#E5E7EB", line=dict(width=1, color="#9CA3AF")),
|
1523 |
-
text=f"{step['time']}",
|
1524 |
-
textposition="middle center",
|
1525 |
-
textfont=dict(size=10, color="#374151", family="Arial Bold"),
|
1526 |
-
hoverinfo="text",
|
1527 |
-
hovertext=f"Séquence {step['time']}",
|
1528 |
-
showlegend=False
|
1529 |
-
))
|
1530 |
-
|
1531 |
-
# Configuration avancée de la mise en page
|
1532 |
-
fig.update_layout(
|
1533 |
-
title={
|
1534 |
-
'text': "🔄 Diagramme de Séquence - Flux de Traitement NLU",
|
1535 |
-
'x': 0.5,
|
1536 |
-
'xanchor': 'center',
|
1537 |
-
'font': {'size': 18, 'color': '#1E3A8A', 'family': 'Arial Black'}
|
1538 |
-
},
|
1539 |
-
showlegend=False,
|
1540 |
-
hovermode="closest",
|
1541 |
-
height=600,
|
1542 |
-
margin=dict(t=80, b=40, l=80, r=40),
|
1543 |
-
xaxis=dict(
|
1544 |
-
showgrid=False,
|
1545 |
-
zeroline=False,
|
1546 |
-
showticklabels=False,
|
1547 |
-
range=[-0.8, len(actors) - 0.2]
|
1548 |
-
),
|
1549 |
-
yaxis=dict(
|
1550 |
-
showgrid=True,
|
1551 |
-
gridcolor="rgba(0,0,0,0.1)",
|
1552 |
-
zeroline=False,
|
1553 |
-
showticklabels=False,
|
1554 |
-
range=[-7.5, 1]
|
1555 |
-
),
|
1556 |
-
plot_bgcolor="rgba(248,250,252,0.3)",
|
1557 |
-
paper_bgcolor="white",
|
1558 |
-
font=dict(family="Arial, sans-serif")
|
1559 |
-
)
|
1560 |
-
|
1561 |
-
# Ajouter une légende temporelle
|
1562 |
-
fig.add_annotation(
|
1563 |
-
text="<b>Chronologie:</b><br>" +
|
1564 |
-
"① Requête initiale<br>" +
|
1565 |
-
"② Appel API<br>" +
|
1566 |
-
"③ Traitement ML<br>" +
|
1567 |
-
"④ Résultat modèle<br>" +
|
1568 |
-
"⑤ Réponse structurée<br>" +
|
1569 |
-
"⑥ Interface utilisateur",
|
1570 |
-
xref="paper", yref="paper",
|
1571 |
-
x=0.02, y=0.98,
|
1572 |
-
xanchor="left", yanchor="top",
|
1573 |
-
showarrow=False,
|
1574 |
-
font=dict(size=10, color="#1E3A8A"),
|
1575 |
-
bgcolor="rgba(255,255,255,0.95)",
|
1576 |
-
bordercolor="#E5E7EB",
|
1577 |
-
borderwidth=1
|
1578 |
-
)
|
1579 |
-
|
1580 |
-
st.plotly_chart(fig, use_container_width=True)
|
1581 |
-
|
1582 |
-
# Ajouter des informations sur la latence
|
1583 |
-
st.markdown("### ⏱️ Analyse de Performance par Étape")
|
1584 |
-
|
1585 |
-
latency_cols = st.columns(6)
|
1586 |
-
latencies = ["12ms", "8ms", "95ms", "5ms", "6ms", "1ms"]
|
1587 |
-
steps_names = ["Requête", "Routage", "Inférence", "Post-process", "Réponse", "Affichage"]
|
1588 |
-
|
1589 |
-
for i, (col, latency, step_name) in enumerate(zip(latency_cols, latencies, steps_names)):
|
1590 |
-
with col:
|
1591 |
-
st.metric(
|
1592 |
-
label=f"Étape {i+1}",
|
1593 |
-
value=latency,
|
1594 |
-
help=f"Latence moyenne pour: {step_name}"
|
1595 |
-
)
|
1596 |
-
|
1597 |
-
with arch_tab2:
|
1598 |
-
try:
|
1599 |
-
arch_img = Image.open("images/image17.jpg")
|
1600 |
-
st.image(arch_img, caption="Diagramme d'architecture de l'intégration avec AICC")
|
1601 |
-
except:
|
1602 |
-
st.warning("Image d'architecture non trouvée. Placez 'image17.jpg' dans le dossier 'images/'.")
|
1603 |
-
|
1604 |
-
# Structure de l'API
|
1605 |
-
st.markdown('<h3 class="section-title">Structure de l\'API</h3>', unsafe_allow_html=True)
|
1606 |
-
|
1607 |
-
# Détails d'implémentation dans un expander
|
1608 |
-
with st.expander("Détails d'implémentation", expanded=True):
|
1609 |
-
st.markdown("#### FastAPI - Endpoint principal")
|
1610 |
-
|
1611 |
-
code_fastapi = '''
|
1612 |
-
@app.post("/predict", response_model=PredictionResponse, tags=["Prediction"])
|
1613 |
-
async def predict_intent(input_data: TextInput):
|
1614 |
-
"""
|
1615 |
-
Prédit l'intention d'un texte en Darija.
|
1616 |
-
"""
|
1617 |
-
try:
|
1618 |
-
text = input_data.text.strip()
|
1619 |
-
|
1620 |
-
# Validation des entrées
|
1621 |
-
if not text or len(text) < 2:
|
1622 |
-
raise HTTPException(
|
1623 |
-
status_code=400,
|
1624 |
-
detail="Le texte d'entrée est vide ou trop court"
|
1625 |
-
)
|
1626 |
-
|
1627 |
-
# Appel au service NLU
|
1628 |
-
intent, confidence = await NLU_service.predict_intent(text)
|
1629 |
-
|
1630 |
-
return PredictionResponse(
|
1631 |
-
intent=intent,
|
1632 |
-
confidence=float(confidence)
|
1633 |
-
)
|
1634 |
-
|
1635 |
-
except Exception as e:
|
1636 |
-
# Gestion des erreurs
|
1637 |
-
if isinstance(e, HTTPException):
|
1638 |
-
raise e
|
1639 |
-
else:
|
1640 |
-
raise HTTPException(
|
1641 |
-
status_code=500,
|
1642 |
-
detail=f"Erreur lors de la prédiction: {str(e)}"
|
1643 |
-
)
|
1644 |
-
'''
|
1645 |
-
|
1646 |
-
st.code(code_fastapi, language="python")
|
1647 |
-
|
1648 |
-
st.markdown("#### Dockerfile")
|
1649 |
-
|
1650 |
-
code_dockerfile = '''
|
1651 |
-
# Étape 1: Utiliser une image de base Python officielle
|
1652 |
-
FROM python:3.9-slim
|
1653 |
-
|
1654 |
-
# Étape 2: Définir le répertoire de travail dans le container
|
1655 |
-
WORKDIR /app
|
1656 |
-
|
1657 |
-
# Étape 3: Copier le fichier des dépendances
|
1658 |
-
COPY requirements.txt requirements.txt
|
1659 |
-
|
1660 |
-
# Étape 4: Installer les dépendances
|
1661 |
-
# --no-cache-dir pour garder l'image légère
|
1662 |
-
RUN pip install --no-cache-dir --upgrade pip
|
1663 |
-
RUN pip install --no-cache-dir -r requirements.txt
|
1664 |
-
|
1665 |
-
# Étape 5: Copier tout le reste de votre projet dans le container
|
1666 |
-
COPY . .
|
1667 |
-
|
1668 |
-
# Étape 6: Exposer le port que votre API utilise
|
1669 |
-
EXPOSE 8000
|
1670 |
-
|
1671 |
-
# Étape 7: La commande pour lancer l'API quand le container démarre
|
1672 |
-
CMD ["uvicorn", "main:app", "--host", "0.0.0.0", "--port", "8000"]
|
1673 |
-
'''
|
1674 |
-
|
1675 |
-
st.code(code_dockerfile, language="dockerfile")
|
1676 |
-
|
1677 |
-
# Section Déploiement
|
1678 |
-
st.markdown('<h3 class="section-title">Déploiement</h3>', unsafe_allow_html=True)
|
1679 |
-
|
1680 |
-
deployment_tabs = st.tabs(["Hugging Face Spaces", "Intégration AICC", "Monitoring"])
|
1681 |
-
|
1682 |
-
with deployment_tabs[0]:
|
1683 |
-
st.markdown("""
|
1684 |
-
Notre API est déployée sur Hugging Face Spaces qui offre:
|
1685 |
-
|
1686 |
-
- Infrastructure évolutive
|
1687 |
-
- Monitoring intégré
|
1688 |
-
- Haute disponibilité
|
1689 |
-
- Intégration CI/CD via Git
|
1690 |
-
|
1691 |
-
Le déploiement est automatiquement effectué à chaque push sur le dépôt GitHub.
|
1692 |
-
""")
|
1693 |
-
|
1694 |
-
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.")
|
1695 |
-
|
1696 |
-
st.markdown("#### URL de l'API déployée")
|
1697 |
-
st.markdown("[https://mediani-darija-aicc-api.hf.space](https://mediani-darija-aicc-api.hf.space)")
|
1698 |
-
|
1699 |
-
st.markdown("#### Documentation Swagger")
|
1700 |
-
st.markdown("[https://mediani-darija-aicc-api.hf.space/docs](https://mediani-darija-aicc-api.hf.space/docs)")
|
1701 |
-
|
1702 |
-
with deployment_tabs[1]:
|
1703 |
-
st.markdown("""
|
1704 |
-
**L'intégration avec la plateforme AICC de Huawei comprend:**
|
1705 |
-
|
1706 |
-
1. Configuration des webhooks pour les appels API
|
1707 |
-
2. Adaptation des réponses JSON au format AICC
|
1708 |
-
3. Mise en place d'une authentification sécurisée
|
1709 |
-
4. Calibration des timeouts et des retry policies
|
1710 |
-
|
1711 |
-
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.
|
1712 |
-
""")
|
1713 |
-
|
1714 |
-
with deployment_tabs[2]:
|
1715 |
-
st.markdown("""
|
1716 |
-
### Le monitoring de notre API inclut:
|
1717 |
-
""")
|
1718 |
-
|
1719 |
-
monitoring_cols = st.columns(2)
|
1720 |
-
|
1721 |
-
with monitoring_cols[0]:
|
1722 |
-
st.metric(
|
1723 |
-
label="Temps de réponse",
|
1724 |
-
value="127ms",
|
1725 |
-
delta="-5ms"
|
1726 |
-
)
|
1727 |
-
|
1728 |
-
st.metric(
|
1729 |
-
label="Taux de disponibilité",
|
1730 |
-
value="99.97%",
|
1731 |
-
delta="+0.2%"
|
1732 |
-
)
|
1733 |
-
|
1734 |
-
with monitoring_cols[1]:
|
1735 |
-
st.metric(
|
1736 |
-
label="Distribution des intentions",
|
1737 |
-
value="9 catégories",
|
1738 |
-
help="Répartition équilibrée entre les différentes intentions"
|
1739 |
-
)
|
1740 |
-
|
1741 |
-
st.metric(
|
1742 |
-
label="Alertes en cas d'anomalies",
|
1743 |
-
value="Activées",
|
1744 |
-
help="Système de détection d'anomalies en temps réel"
|
1745 |
-
)
|
1746 |
-
|
1747 |
-
st.info("Les métriques sont collectées en temps réel et disponibles via un tableau de bord dédié.")
|
1748 |
-
|
1749 |
-
# Pied de page
|
1750 |
-
st.markdown("---")
|
1751 |
-
st.markdown("© 2025 Mohammed MEDIANI - Université Mohammed Premier - École Supérieure de Technologie de Nador")
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|