mediani commited on
Commit
6310f66
·
verified ·
1 Parent(s): dcd5ea8

streamlit_app.py

Browse files
Files changed (1) hide show
  1. 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")