Sidoineko commited on
Commit
94ac403
·
1 Parent(s): eb97296

fix: version stable sans boucle infinie pour le chargement du modèle Hugging Face

Browse files
Files changed (1) hide show
  1. src/streamlit_app_stable.py +594 -0
src/streamlit_app_stable.py ADDED
@@ -0,0 +1,594 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import streamlit as st
2
+ import os
3
+ import io
4
+ from PIL import Image
5
+ import requests
6
+ import torch
7
+ import google.generativeai as genai
8
+ import gc
9
+ import time
10
+ import sys
11
+ import psutil
12
+
13
+ # Configuration de la page
14
+ st.set_page_config(
15
+ page_title="AgriLens AI - Analyse de Plantes",
16
+ page_icon="🌱",
17
+ layout="wide",
18
+ initial_sidebar_state="expanded"
19
+ )
20
+
21
+ # Initialisation des variables de session
22
+ if 'model_loaded' not in st.session_state:
23
+ st.session_state.model_loaded = False
24
+ if 'model' not in st.session_state:
25
+ st.session_state.model = None
26
+ if 'processor' not in st.session_state:
27
+ st.session_state.processor = None
28
+ if 'model_status' not in st.session_state:
29
+ st.session_state.model_status = "Non chargé"
30
+ if 'model_load_time' not in st.session_state:
31
+ st.session_state.model_load_time = None
32
+ if 'language' not in st.session_state:
33
+ st.session_state.language = "fr"
34
+ if 'load_attempt_count' not in st.session_state:
35
+ st.session_state.load_attempt_count = 0
36
+
37
+ def check_model_health():
38
+ """Vérifie si le modèle est fonctionnel"""
39
+ try:
40
+ return (st.session_state.model is not None and
41
+ st.session_state.processor is not None and
42
+ hasattr(st.session_state.model, 'device'))
43
+ except Exception:
44
+ return False
45
+
46
+ def diagnose_loading_issues():
47
+ """Diagnostique les problèmes potentiels"""
48
+ issues = []
49
+
50
+ ram_gb = psutil.virtual_memory().total / (1024**3)
51
+ if ram_gb < 8:
52
+ issues.append(f"⚠️ RAM faible: {ram_gb:.1f}GB (recommandé: 8GB+)")
53
+
54
+ disk_usage = psutil.disk_usage('/')
55
+ disk_gb = disk_usage.free / (1024**3)
56
+ if disk_gb < 10:
57
+ issues.append(f"⚠️ Espace disque faible: {disk_gb:.1f}GB libre")
58
+
59
+ try:
60
+ requests.get("https://huggingface.co", timeout=5)
61
+ except:
62
+ issues.append("⚠️ Problème de connexion à Hugging Face")
63
+
64
+ if torch.cuda.is_available():
65
+ gpu_memory = torch.cuda.get_device_properties(0).total_memory / (1024**3)
66
+ if gpu_memory < 4:
67
+ issues.append(f"⚠️ GPU mémoire faible: {gpu_memory:.1f}GB")
68
+ else:
69
+ issues.append("ℹ️ CUDA non disponible - CPU uniquement")
70
+
71
+ return issues
72
+
73
+ def resize_image_if_needed(image, max_size=(800, 800)):
74
+ """Redimensionne l'image si nécessaire"""
75
+ original_size = image.size
76
+ if image.size[0] > max_size[0] or image.size[1] > max_size[1]:
77
+ image.thumbnail(max_size, Image.Resampling.LANCZOS)
78
+ return image, True
79
+ return image, False
80
+
81
+ def afficher_ram_disponible(context=""):
82
+ """Affiche l'utilisation de la RAM"""
83
+ ram = psutil.virtual_memory()
84
+ ram_used_gb = ram.used / (1024**3)
85
+ ram_total_gb = ram.total / (1024**3)
86
+ ram_percent = ram.percent
87
+ st.write(f"💾 RAM {context}: {ram_used_gb:.1f}GB / {ram_total_gb:.1f}GB ({ram_percent:.1f}%)")
88
+
89
+ # Traductions
90
+ def t(key):
91
+ translations = {
92
+ "fr": {
93
+ "title": "🌱 AgriLens AI - Assistant d'Analyse de Plantes",
94
+ "subtitle": "Analysez vos plantes avec l'IA pour détecter les maladies",
95
+ "tabs": ["📸 Analyse d'Image", "📝 Analyse de Texte", "⚙️ Configuration", "ℹ️ À Propos"],
96
+ "image_analysis_title": "📸 Analyse d'Image de Plante",
97
+ "image_analysis_desc": "Téléchargez ou capturez une image de votre plante",
98
+ "choose_image": "Choisissez une image de plante...",
99
+ "text_analysis_title": "📝 Analyse de Description Textuelle",
100
+ "text_analysis_desc": "Décrivez les symptômes de votre plante",
101
+ "enter_description": "Décrivez les symptômes de votre plante...",
102
+ "config_title": "⚙️ Configuration",
103
+ "about_title": "ℹ️ À Propos",
104
+ "load_model": "Charger le Modèle",
105
+ "model_status": "Statut du Modèle"
106
+ },
107
+ "en": {
108
+ "title": "🌱 AgriLens AI - Plant Analysis Assistant",
109
+ "subtitle": "Analyze your plants with AI to detect diseases",
110
+ "tabs": ["📸 Image Analysis", "📝 Text Analysis", "⚙️ Configuration", "ℹ️ About"],
111
+ "image_analysis_title": "📸 Plant Image Analysis",
112
+ "image_analysis_desc": "Upload or capture an image of your plant",
113
+ "choose_image": "Choose a plant image...",
114
+ "text_analysis_title": "📝 Textual Description Analysis",
115
+ "text_analysis_desc": "Describe your plant symptoms",
116
+ "enter_description": "Describe your plant symptoms...",
117
+ "config_title": "⚙️ Configuration",
118
+ "about_title": "ℹ️ About",
119
+ "load_model": "Load Model",
120
+ "model_status": "Model Status"
121
+ }
122
+ }
123
+ return translations[st.session_state.language].get(key, key)
124
+
125
+ def load_model():
126
+ """Charge le modèle avec gestion d'erreurs améliorée"""
127
+ try:
128
+ from transformers import AutoProcessor, Gemma3nForConditionalGeneration
129
+
130
+ # Limiter les tentatives de chargement
131
+ if st.session_state.load_attempt_count >= 3:
132
+ st.error("🔄 Trop de tentatives de chargement. Redémarrez l'application.")
133
+ return None, None
134
+
135
+ st.session_state.load_attempt_count += 1
136
+
137
+ # Diagnostic
138
+ st.info("🔍 Diagnostic de l'environnement...")
139
+ issues = diagnose_loading_issues()
140
+ if issues:
141
+ with st.expander("📊 Diagnostic système", expanded=False):
142
+ for issue in issues:
143
+ st.write(issue)
144
+
145
+ # Nettoyer la mémoire
146
+ gc.collect()
147
+ if torch.cuda.is_available():
148
+ torch.cuda.empty_cache()
149
+
150
+ # Détecter l'environnement
151
+ is_local = os.path.exists("models/gemma-3n-transformers-gemma-3n-e2b-it-v1")
152
+
153
+ if is_local:
154
+ # Mode LOCAL
155
+ st.info("Chargement du modèle depuis le dossier local...")
156
+ model_path = "models/gemma-3n-transformers-gemma-3n-e2b-it-v1"
157
+
158
+ processor = AutoProcessor.from_pretrained(
159
+ model_path,
160
+ trust_remote_code=True
161
+ )
162
+
163
+ model = Gemma3nForConditionalGeneration.from_pretrained(
164
+ model_path,
165
+ torch_dtype=torch.bfloat16,
166
+ trust_remote_code=True,
167
+ low_cpu_mem_usage=True
168
+ )
169
+
170
+ st.success("✅ Modèle chargé avec succès (local)")
171
+
172
+ else:
173
+ # Mode HUGGING FACE avec timeout
174
+ st.info("Chargement du modèle depuis Hugging Face...")
175
+ model_id = "google/gemma-3n-E4B-it"
176
+
177
+ # Charger le processeur avec timeout
178
+ try:
179
+ processor = AutoProcessor.from_pretrained(
180
+ model_id,
181
+ trust_remote_code=True,
182
+ timeout=60
183
+ )
184
+ st.success("✅ Processeur téléchargé")
185
+ except Exception as e:
186
+ st.error(f"❌ Erreur processeur: {e}")
187
+ return None, None
188
+
189
+ # Charger le modèle avec timeout
190
+ try:
191
+ model = Gemma3nForConditionalGeneration.from_pretrained(
192
+ model_id,
193
+ torch_dtype=torch.bfloat16,
194
+ trust_remote_code=True,
195
+ low_cpu_mem_usage=True,
196
+ timeout=120
197
+ )
198
+ st.success("✅ Modèle téléchargé")
199
+ except Exception as e:
200
+ st.error(f"❌ Erreur modèle: {e}")
201
+ return None, None
202
+
203
+ # Stocker dans session_state
204
+ st.session_state.model = model
205
+ st.session_state.processor = processor
206
+ st.session_state.model_loaded = True
207
+ st.session_state.model_status = "Chargé"
208
+ st.session_state.model_load_time = time.time()
209
+ st.session_state.load_attempt_count = 0 # Reset counter
210
+
211
+ return model, processor
212
+
213
+ except Exception as e:
214
+ st.error(f"❌ Erreur lors du chargement: {e}")
215
+ return None, None
216
+
217
+ def analyze_image_multilingual(image, prompt=""):
218
+ """Analyse une image avec le modèle Gemma"""
219
+ try:
220
+ if not st.session_state.model_loaded or not check_model_health():
221
+ st.error("❌ Modèle non chargé ou corrompu")
222
+ return None
223
+
224
+ # Préparer l'image
225
+ if image.mode != 'RGB':
226
+ image = image.convert('RGB')
227
+
228
+ # Préparer le prompt
229
+ if not prompt:
230
+ prompt = """Analysez cette image de plante et identifiez :
231
+ 1. L'état de santé général de la plante
232
+ 2. Les maladies ou problèmes visibles
233
+ 3. Les recommandations de traitement
234
+ 4. Les mesures préventives
235
+
236
+ Répondez en français de manière claire et structurée."""
237
+
238
+ # Encoder l'image
239
+ inputs = st.session_state.processor(
240
+ images=image,
241
+ text=prompt,
242
+ return_tensors="pt"
243
+ )
244
+
245
+ # Générer la réponse
246
+ with st.spinner("🔍 Analyse en cours..."):
247
+ outputs = st.session_state.model.generate(
248
+ **inputs,
249
+ max_new_tokens=512,
250
+ do_sample=True,
251
+ temperature=0.7,
252
+ top_p=0.9
253
+ )
254
+
255
+ # Décoder la réponse
256
+ response = st.session_state.processor.decode(outputs[0], skip_special_tokens=True)
257
+
258
+ # Extraire seulement la partie générée
259
+ if prompt in response:
260
+ response = response.split(prompt)[-1].strip()
261
+
262
+ return response
263
+
264
+ except Exception as e:
265
+ st.error(f"❌ Erreur lors de l'analyse: {e}")
266
+ return None
267
+
268
+ def analyze_text_multilingual(text):
269
+ """Analyse un texte descriptif avec le modèle Gemma"""
270
+ try:
271
+ if not st.session_state.model_loaded or not check_model_health():
272
+ st.error("❌ Modèle non chargé ou corrompu")
273
+ return None
274
+
275
+ # Préparer le prompt
276
+ prompt = f"""Analysez cette description de symptômes de plante et fournissez un diagnostic :
277
+
278
+ Description : {text}
279
+
280
+ Veuillez analyser et fournir :
281
+ 1. Diagnostic probable
282
+ 2. Causes possibles
283
+ 3. Traitements recommandés
284
+ 4. Mesures préventives
285
+
286
+ Répondez en français de manière claire et structurée."""
287
+
288
+ # Encoder le texte
289
+ inputs = st.session_state.processor(
290
+ text=prompt,
291
+ return_tensors="pt"
292
+ )
293
+
294
+ # Générer la réponse
295
+ with st.spinner("🔍 Analyse en cours..."):
296
+ outputs = st.session_state.model.generate(
297
+ **inputs,
298
+ max_new_tokens=512,
299
+ do_sample=True,
300
+ temperature=0.7,
301
+ top_p=0.9
302
+ )
303
+
304
+ # Décoder la réponse
305
+ response = st.session_state.processor.decode(outputs[0], skip_special_tokens=True)
306
+
307
+ # Extraire seulement la partie générée
308
+ if prompt in response:
309
+ response = response.split(prompt)[-1].strip()
310
+
311
+ return response
312
+
313
+ except Exception as e:
314
+ st.error(f"❌ Erreur lors de l'analyse: {e}")
315
+ return None
316
+
317
+ # Interface principale
318
+ st.title(t("title"))
319
+ st.markdown(t("subtitle"))
320
+
321
+ # Sidebar pour la configuration
322
+ with st.sidebar:
323
+ st.header("⚙️ Configuration")
324
+
325
+ # Sélection de langue
326
+ language = st.selectbox(
327
+ "🌐 Langue / Language",
328
+ ["Français", "English"],
329
+ index=0 if st.session_state.language == "fr" else 1
330
+ )
331
+ st.session_state.language = "fr" if language == "Français" else "en"
332
+
333
+ st.divider()
334
+
335
+ # Gestion du modèle
336
+ st.header(t("model_status"))
337
+
338
+ if st.session_state.model_loaded and check_model_health():
339
+ st.success("✅ Modèle chargé et fonctionnel")
340
+ st.write(f"**Statut :** {st.session_state.model_status}")
341
+ if st.session_state.model_load_time:
342
+ load_time_str = time.strftime('%H:%M:%S', time.localtime(st.session_state.model_load_time))
343
+ st.write(f"**Heure de chargement :** {load_time_str}")
344
+
345
+ # Bouton de rechargement (sans rerun automatique)
346
+ if st.button("🔄 Recharger le modèle", type="secondary"):
347
+ st.session_state.model_loaded = False
348
+ st.session_state.model = None
349
+ st.session_state.processor = None
350
+ st.session_state.load_attempt_count = 0
351
+ st.info("🔄 Modèle déchargé. Cliquez sur 'Charger le modèle' pour recharger.")
352
+ else:
353
+ st.warning("⚠️ Modèle non chargé")
354
+
355
+ # Bouton de chargement
356
+ if st.button(t("load_model"), type="primary"):
357
+ with st.spinner("🔄 Chargement du modèle..."):
358
+ model, processor = load_model()
359
+ if model is not None and processor is not None:
360
+ st.success("✅ Modèle chargé avec succès!")
361
+ st.rerun()
362
+ else:
363
+ st.error("❌ Échec du chargement du modèle")
364
+
365
+ # Onglets principaux
366
+ tab1, tab2, tab3, tab4 = st.tabs(t("tabs"))
367
+
368
+ with tab1:
369
+ st.header(t("image_analysis_title"))
370
+ st.markdown(t("image_analysis_desc"))
371
+
372
+ # Options de capture d'image
373
+ capture_option = st.radio(
374
+ "Choisissez votre méthode :" if st.session_state.language == "fr" else "Choose your method:",
375
+ ["📁 Upload d'image" if st.session_state.language == "fr" else "📁 Upload Image",
376
+ "📷 Capture par webcam" if st.session_state.language == "fr" else "📷 Webcam Capture"],
377
+ horizontal=True
378
+ )
379
+
380
+ uploaded_file = None
381
+ captured_image = None
382
+
383
+ if capture_option == "📁 Upload d'image" or capture_option == "📁 Upload Image":
384
+ uploaded_file = st.file_uploader(
385
+ t("choose_image"),
386
+ type=['png', 'jpg', 'jpeg'],
387
+ help="Formats acceptés : PNG, JPG, JPEG (max 200MB)" if st.session_state.language == "fr" else "Accepted formats: PNG, JPG, JPEG (max 200MB)"
388
+ )
389
+ else:
390
+ st.markdown("**📷 Capture d'image par webcam**" if st.session_state.language == "fr" else "**📷 Webcam Image Capture**")
391
+ st.info("💡 Positionnez votre plante malade devant la webcam et cliquez sur 'Prendre une photo'" if st.session_state.language == "fr" else "💡 Position your diseased plant in front of the webcam and click 'Take Photo'")
392
+
393
+ captured_image = st.camera_input(
394
+ "Prendre une photo de la plante" if st.session_state.language == "fr" else "Take a photo of the plant"
395
+ )
396
+
397
+ # Traitement de l'image
398
+ image = None
399
+ if uploaded_file is not None:
400
+ try:
401
+ image = Image.open(uploaded_file)
402
+ except Exception as e:
403
+ st.error(f"❌ Erreur lors du traitement de l'image : {e}")
404
+ elif captured_image is not None:
405
+ try:
406
+ image = Image.open(captured_image)
407
+ except Exception as e:
408
+ st.error(f"❌ Erreur lors du traitement de l'image : {e}")
409
+
410
+ if image is not None:
411
+ # Redimensionner l'image si nécessaire
412
+ original_size = image.size
413
+ image, was_resized = resize_image_if_needed(image, max_size=(800, 800))
414
+
415
+ col1, col2 = st.columns([1, 1])
416
+ with col1:
417
+ st.image(image, caption="Image à analyser", use_container_width=True)
418
+ if was_resized:
419
+ st.info(f"ℹ️ Image redimensionnée de {original_size} à {image.size}")
420
+
421
+ with col2:
422
+ if st.session_state.model_loaded and check_model_health():
423
+ # Options d'analyse
424
+ analysis_type = st.selectbox(
425
+ "Type d'analyse :" if st.session_state.language == "fr" else "Analysis type:",
426
+ ["Analyse générale" if st.session_state.language == "fr" else "General analysis",
427
+ "Diagnostic maladie" if st.session_state.language == "fr" else "Disease diagnosis",
428
+ "Conseils de soins" if st.session_state.language == "fr" else "Care advice"]
429
+ )
430
+
431
+ custom_prompt = st.text_area(
432
+ "Prompt personnalisé (optionnel) :" if st.session_state.language == "fr" else "Custom prompt (optional):",
433
+ value="",
434
+ height=100
435
+ )
436
+
437
+ if st.button("🔍 Analyser l'image", type="primary"):
438
+ # Préparer le prompt selon le type d'analyse
439
+ if analysis_type == "Analyse générale" or analysis_type == "General analysis":
440
+ prompt = """Analysez cette image de plante et identifiez :
441
+ 1. L'état de santé général de la plante
442
+ 2. Les maladies ou problèmes visibles
443
+ 3. Les recommandations de traitement
444
+ 4. Les mesures préventives
445
+
446
+ Répondez en français de manière claire et structurée."""
447
+ elif analysis_type == "Diagnostic maladie" or analysis_type == "Disease diagnosis":
448
+ prompt = """Diagnostiquez cette plante en vous concentrant sur les maladies :
449
+
450
+ 1. Identifiez les symptômes visibles
451
+ 2. Déterminez la maladie probable
452
+ 3. Expliquez les causes
453
+ 4. Proposez un traitement spécifique
454
+
455
+ Répondez en français de manière structurée."""
456
+ else: # Conseils de soins
457
+ prompt = """Analysez cette plante et donnez des conseils de soins :
458
+
459
+ 1. État général de la plante
460
+ 2. Besoins en eau et lumière
461
+ 3. Conseils d'entretien
462
+ 4. Améliorations recommandées
463
+
464
+ Répondez en français de manière structurée."""
465
+
466
+ # Utiliser le prompt personnalisé si fourni
467
+ if custom_prompt.strip():
468
+ prompt = custom_prompt
469
+
470
+ # Analyser l'image
471
+ result = analyze_image_multilingual(image, prompt)
472
+
473
+ if result:
474
+ st.success("✅ Analyse terminée !")
475
+ st.markdown("### 📋 Résultats de l'analyse")
476
+ st.markdown(result)
477
+ else:
478
+ st.error("❌ Échec de l'analyse")
479
+ else:
480
+ st.warning("⚠️ Modèle non chargé. Chargez le modèle dans la sidebar pour analyser l'image.")
481
+
482
+ with tab2:
483
+ st.header(t("text_analysis_title"))
484
+ st.markdown(t("text_analysis_desc"))
485
+
486
+ # Zone de texte pour la description
487
+ text_input = st.text_area(
488
+ t("enter_description"),
489
+ height=200,
490
+ placeholder="Exemple : Les feuilles de ma plante deviennent jaunes et tombent. Il y a des taches brunes sur les feuilles..." if st.session_state.language == "fr" else "Example: My plant leaves are turning yellow and falling. There are brown spots on the leaves..."
491
+ )
492
+
493
+ if st.button("🔍 Analyser la description", type="primary"):
494
+ if text_input.strip():
495
+ if st.session_state.model_loaded and check_model_health():
496
+ result = analyze_text_multilingual(text_input)
497
+
498
+ if result:
499
+ st.success("✅ Analyse terminée !")
500
+ st.markdown("### 📋 Résultats de l'analyse")
501
+ st.markdown(result)
502
+ else:
503
+ st.error("❌ Échec de l'analyse")
504
+ else:
505
+ st.warning("⚠️ Modèle non chargé. Chargez le modèle dans la sidebar pour analyser le texte.")
506
+ else:
507
+ st.warning("⚠️ Veuillez entrer une description de la plante.")
508
+
509
+ with tab3:
510
+ st.header(t("config_title"))
511
+
512
+ col1, col2 = st.columns(2)
513
+
514
+ with col1:
515
+ st.subheader("🔧 Paramètres système")
516
+
517
+ # Affichage des informations système
518
+ ram = psutil.virtual_memory()
519
+ st.write(f"**RAM totale :** {ram.total / (1024**3):.1f} GB")
520
+ st.write(f"**RAM utilisée :** {ram.used / (1024**3):.1f} GB ({ram.percent:.1f}%)")
521
+
522
+ disk = psutil.disk_usage('/')
523
+ st.write(f"**Espace disque libre :** {disk.free / (1024**3):.1f} GB")
524
+
525
+ if torch.cuda.is_available():
526
+ st.write(f"**GPU :** {torch.cuda.get_device_name(0)}")
527
+ st.write(f"**Mémoire GPU :** {torch.cuda.get_device_properties(0).total_memory / (1024**3):.1f} GB")
528
+ else:
529
+ st.write("**GPU :** Non disponible (CPU uniquement)")
530
+
531
+ with col2:
532
+ st.subheader("📊 Statistiques du modèle")
533
+
534
+ if st.session_state.model_loaded and check_model_health():
535
+ st.write("**Statut :** ✅ Chargé et fonctionnel")
536
+ st.write(f"**Type :** {type(st.session_state.model).__name__}")
537
+ if hasattr(st.session_state.model, 'device'):
538
+ st.write(f"**Device :** {st.session_state.model.device}")
539
+ if st.session_state.model_load_time:
540
+ load_time_str = time.strftime('%H:%M:%S', time.localtime(st.session_state.model_load_time))
541
+ st.write(f"**Heure de chargement :** {load_time_str}")
542
+ else:
543
+ st.write("**Statut :** ❌ Non chargé")
544
+ st.write("**Type :** N/A")
545
+ st.write("**Device :** N/A")
546
+
547
+ with tab4:
548
+ st.header(t("about_title"))
549
+
550
+ st.markdown("""
551
+ ## 🌱 AgriLens AI
552
+
553
+ **AgriLens AI** est un assistant intelligent pour l'analyse de plantes utilisant l'intelligence artificielle.
554
+
555
+ ### 🚀 Fonctionnalités
556
+
557
+ - **Analyse d'images** : Détection automatique des maladies de plantes
558
+ - **Analyse textuelle** : Diagnostic basé sur les descriptions de symptômes
559
+ - **Recommandations** : Conseils de traitement et prévention
560
+ - **Interface multilingue** : Français et Anglais
561
+
562
+ ### 🤖 Modèles utilisés
563
+
564
+ - **Google Gemma 3n E4B IT** : Modèle de vision et langage
565
+ - **Hugging Face Transformers** : Framework d'IA
566
+ - **Streamlit** : Interface utilisateur
567
+
568
+ ### 📝 Utilisation
569
+
570
+ 1. Chargez le modèle dans la sidebar
571
+ 2. Uploadez une image ou capturez avec la webcam
572
+ 3. Obtenez une analyse détaillée de votre plante
573
+ 4. Suivez les recommandations de traitement
574
+
575
+ ### 🔧 Support technique
576
+
577
+ - **Mode local** : Modèles téléchargés localement
578
+ - **Mode en ligne** : Modèles depuis Hugging Face
579
+ - **Gestion mémoire** : Optimisation automatique
580
+
581
+ ---
582
+
583
+ *Développé avec ❤️ pour les amoureux des plantes*
584
+ """)
585
+
586
+ # Footer
587
+ st.divider()
588
+ st.markdown("""
589
+ <div style='text-align: center; color: #666;'>
590
+ 🌱 AgriLens AI - Assistant d'Analyse de Plantes |
591
+ <a href='#' target='_blank'>Documentation</a> |
592
+ <a href='#' target='_blank'>Support</a>
593
+ </div>
594
+ """, unsafe_allow_html=True)