import streamlit as st from PIL import Image import os import folium from streamlit_folium import st_folium # --- NEW IMPORTS FOR CHATBOT FUNCTIONALITY --- # COMMENTED OUT: from groq import Groq # COMMENTED OUT: from dotenv import load_dotenv import base64 import tempfile # Removed pydub import as it's not needed if audio recording/conversion is removed. # from pydub import AudioSegment # --- NOUVEAUX IMPORTS POUR LA FONCTIONNALITÉ D'ANALYSE DE PLANTE --- import tensorflow as tf import numpy as np # --- CUSTOM AUDIO RECORDER COMPONENT PLACEHOLDER --- # Keep the declaration for now, in case it's used elsewhere or for future re-addition, # but its usage is removed from show_chatbot_view. try: import streamlit.components.v1 as components _audio_recorder_component = components.declare_component( "audio_recorder_component", path="./audio_recorder_component/frontend/build" # Adjust this path to your component's build folder ) def audio_recorder_component(): return _audio_recorder_component(key="audio_recorder_widget") except Exception as e: st.warning(f"Could not load custom audio recorder component. Audio recording functionality might be limited: {e}") def audio_recorder_component(): st.info("Audio recording component not loaded. Please ensure it's installed correctly and ffmpeg/ffprobe are available.") return None # --- Configuration de la page Streamlit --- st.set_page_config( page_title="GreenField Pro - Dashboard", page_icon=":seedling:", layout="wide", initial_sidebar_state="expanded" ) # --- Chemins des ressources statiques et du modèle --- LOGO_PATH = os.path.join("static", "images", "Green Plant and Agriculture Logo (2).png") CSS_PATH = "style.css" # CHANGED: Use Hugging Face Hub for model loading HF_REPO_ID = "mopaoleonel/plante_tedection" HF_MODEL_FILENAME = "mon_modele.keras" # --- Dictionnaire des classes de maladies --- CLASS_NAMES = { 'Apple___Apple_scab': 0, 'Apple___Black_rot': 1, 'Apple___Cedar_apple_rust': 2, 'Apple___healthy': 3, 'Blueberry___healthy': 4, 'Cherry_(including_sour)___Powdery_mildew': 5, 'Cherry_(including_sour)___healthy': 6, 'Corn_(maize)___Cercospora_leaf_spot Gray_leaf_spot': 7, 'Corn_(maize)___Common_rust_': 8, 'Corn_(maize)___Northern_Leaf_Blight': 9, 'Corn_(maize)___healthy': 10, 'Grape___Black_rot': 11, 'Grape___Esca_(Black_Measles)': 12, 'Grape___Leaf_blight_(Isariopsis_Leaf_Spot)': 13, 'Grape___healthy': 14, 'Orange___Haunglongbing_(Citrus_greening)': 15, 'Peach___Bacterial_spot': 16, 'Peach___healthy': 17, 'Pepper,_bell___Bacterial_spot': 18, 'Pepper,_bell___healthy': 19, 'Potato___Early_blight': 20, 'Potato___Late_blight': 21, 'Potato___healthy': 22, 'Raspberry___healthy': 23, 'Soybean___healthy': 24, 'Squash___Powdery_mildew': 25, 'Strawberry___Leaf_scorch': 26, 'Strawberry___healthy': 27, 'Tomato___Bacterial_spot': 28, 'Tomato___Early_blight': 29, 'Tomato___Late_blight': 30, 'Tomato___Leaf_Mold': 31, 'Tomato___Septoria_leaf_spot': 32, 'Tomato___Spider_mites Two-spotted_spider_mite': 33, 'Tomato___Target_Spot': 34, 'Tomato___Tomato_Yellow_Leaf_Curl_Virus': 35, 'Tomato___Tomato_mosaic_virus': 36, 'Tomato___healthy': 37 } # Inverser le dictionnaire pour un accès facile par index CLASS_NAMES_INV = {v: k for k, v in CLASS_NAMES.items()} # Taille d'entrée attendue par votre modèle IMG_HEIGHT = 64 IMG_WIDTH = 64 IMG_CHANNELS = 3 # RGB # --- Fonctions utilitaires et de chargement --- def load_css(css_file): """Charge un fichier CSS externe.""" try: with open(css_file) as f: st.markdown(f"", unsafe_allow_html=True) except FileNotFoundError: st.warning(f"Fichier CSS non trouvé à : {css_file}. L'interface utilisera les styles par défaut.") except Exception as e: st.error(f"Erreur lors du chargement du CSS : {e}") # MODIFIED: Use hf_hub_download to get the model @st.cache_resource # Met en cache le modèle pour éviter les rechargements coûteux def load_keras_model_from_hub(repo_id, filename): """Charge le modèle Keras depuis Hugging Face Hub.""" try: from huggingface_hub import hf_hub_download model_path = hf_hub_download(repo_id=repo_id, filename=filename) st.info(f"Modèle téléchargé depuis Hugging Face: {model_path}") model = tf.keras.models.load_model(model_path) # NOUVELLES LIGNES DE DÉBOGAGE (vous pouvez les supprimer après résolution) st.info(f"Vérification de la version de TensorFlow sur Streamlit Cloud...") st.info(f"TensorFlow version sur Streamlit Cloud: {tf.__version__}") st.info(f"Keras version sur Streamlit Cloud: {tf.keras.__version__}") # FIN DES LIGNES DE DÉBOGAGE st.success("Modèle Keras chargé avec succès !") return model except Exception as e: st.error(f"Erreur Critique : Impossible de télécharger ou charger le modèle depuis Hugging Face Hub: {e}") st.info(f"Assurez-vous que le dépôt '{repo_id}' et le fichier '{filename}' existent et sont accessibles. Vérifiez également que votre modèle a été sauvegardé avec TensorFlow {tf.__version__}.") return None # --- Fonctions pour les vues spécifiques --- def show_dashboard_view(): """Affiche le tableau de bord principal avec les métriques et la carte.""" st.markdown("

Tableau de Bord

", unsafe_allow_html=True) col1, col2, col3, col4 = st.columns(4) with col1: st.markdown("""
24°C
Température
""", unsafe_allow_html=True) with col2: st.markdown("""
42.5%
Humidité
""", unsafe_allow_html=True) with col3: st.markdown("""
3.0mm
Précipitation
""", unsafe_allow_html=True) with col4: st.markdown("""
3.5m/s
Vent
""", unsafe_allow_html=True) st.markdown("---") st.markdown("

Carte des Plantations

", unsafe_allow_html=True) center_lat = 5.4746 center_lon = 10.4243 m = folium.Map(location=[center_lat, center_lon], zoom_start=10, control_scale=True) folium.Marker( location=[center_lat, center_lon], popup="Bafoussam
Zone de Plantation Principale", tooltip="Bafoussam" ).add_to(m) folium.Marker( location=[5.62, 10.05], popup="Zone Ouest
Quelques fermes ici.", icon=folium.Icon(color="green", icon="leaf") ).add_to(m) st_folium(m, width='100%', height=500) st.markdown("""

Visualisation des emplacements de vos plantations et des points d'intérêt.

""", unsafe_allow_html=True) def show_plant_analysis_view(groq_client, model): """Affiche la page d'analyse de maladie des plantes.""" st.markdown("

Analyser ma plante

", unsafe_allow_html=True) st.markdown("Téléchargez une image de la feuille de votre plante pour obtenir un diagnostic instantané et des recommandations.") if not model: st.error("Le service d'analyse est indisponible car le modèle de prédiction n'a pas pu être chargé. Veuillez vérifier le chemin du modèle.") return uploaded_file = st.file_uploader("Choisissez une image...", type=["jpg", "jpeg", "png"], label_visibility="collapsed", key="plant_analysis_uploader") if uploaded_file is not None: col1, col2 = st.columns([0.8, 1.2]) # Donner plus de place aux résultats with col1: st.image(uploaded_file, caption="Image téléchargée", use_container_width=True) with col2: with st.spinner("Analyse de l'image en cours..."): try: image = Image.open(uploaded_file).convert('RGB') # REDIMENSIONNEMENT À LA TAILLE ATTENDUE PAR VOTRE MODÈLE img_resized = image.resize((IMG_WIDTH, IMG_HEIGHT)) img_array = tf.keras.preprocessing.image.img_to_array(img_resized) img_array = tf.expand_dims(img_array, 0) # Ajoute une dimension de batch # NORMALISATION: Assurez-vous que c'est la même que celle utilisée lors de l'entraînement # Si votre modèle attend des valeurs entre 0 et 1 img_array = tf.cast(img_array, tf.float32) / 255.0 # Si votre modèle gère la normalisation en interne (ex: Rescaling layer), supprimez la ligne ci-dessus. prediction = model.predict(img_array) predicted_class_index = np.argmax(prediction) predicted_class_name = CLASS_NAMES_INV[predicted_class_index] # Formatage du nom pour une meilleure lisibilité parts = predicted_class_name.split('___') plant_name = parts[0].replace('_', ' ') disease_name = parts[1].replace('_', ' ') except Exception as e: st.error(f"Erreur lors du traitement de l'image ou de la prédiction : {e}") st.info("Assurez-vous que l'image est valide et que le modèle est compatible.") return # Arrête le traitement si erreur st.subheader("Résultats de l'analyse") if 'healthy' in disease_name.lower(): st.success(f"**Diagnostic :** La plante ({plant_name}) semble **saine**.") st.balloons() st.markdown("
", unsafe_allow_html=True) st.subheader("Conseils pour une plante saine") # MODIFIED: Simulate Groq response for healthy plant st.markdown(f""" ### 🌱 Conseils pour une Plante Saine - **Arrosage Optimal :** Assurez-vous que votre plante {plant_name} reçoit la juste quantité d'eau, en évitant l'excès ou le manque. - **Lumière Adéquate :** Positionnez-la là où elle bénéficiera de l'exposition au soleil idéale pour son type. - **Nutriments Essentiels :** Apportez les engrais nécessaires selon les cycles de croissance et les besoins spécifiques de la plante. - **Surveillance Continue :** Inspectez régulièrement les feuilles et les tiges pour détecter tout signe inhabituel ou parasite. *Votre {plant_name} est en pleine forme ! Continuez d'appliquer ces bons gestes pour une croissance vigoureuse.* """) else: st.warning(f"**Diagnostic :** {plant_name} - **{disease_name}**") # MODIFIED: Simulate Groq response for diseased plant st.markdown("
", unsafe_allow_html=True) st.subheader("Recommandations") st.markdown(f""" ### 🧐 Causes Principales - **Conditions Environnementales :** Souvent liée à une humidité excessive, un drainage insuffisant, ou des variations de température. - **Manque de Nutriments :** Une carence spécifique peut affaiblir la {plant_name} et la rendre susceptible à la {disease_name}. - **Agents Pathogènes :** La présence de champignons, bactéries ou virus, souvent propagés par le vent, l'eau ou les outils. ### 🛡️ Solutions et Traitements **Prévention :** - **Hygiène des Outils :** Nettoyez et désinfectez régulièrement vos outils de jardinage. - **Rotation des Cultures :** Évitez de replanter la même espèce au même endroit pour rompre le cycle des maladies. - **Ventilation :** Assurez une bonne circulation de l'air autour de vos plantes pour réduire l'humidité. **Traitements Biologiques :** - **Pulvérisations Naturelles :** Des solutions à base de neem, de bicarbonate de soude ou de purin d'ortie peuvent aider à contrôler la {disease_name}. - **Introduction de Prédateurs :** Dans certains cas, l'utilisation d'insectes bénéfiques peut aider à gérer les vecteurs de la maladie. **Traitements Chimiques :** - **Fongicides/Bactéricides Spécifiques :** Si la maladie est sévère, utilisez des produits homologués pour la {disease_name}, en respectant scrupuleusement les instructions et les délais avant récolte. *Agissez rapidement pour donner à votre {plant_name} les meilleures chances de se rétablir. Courage !* """) def show_chatbot_view(groq_client): # Keep groq_client in signature for consistency, but won't be used """Affiche la page de l'assistant virtuel (chatbot).""" st.markdown("

Assistant Virtuel

", unsafe_allow_html=True) st.markdown("Posez-moi n'importe quelle question sur l'agriculture, vos cultures, ou les résultats de vos analyses.") # MODIFIED: No longer check for groq_client to enable/disable, but provide info st.info("L'assistant virtuel est actuellement en mode de démonstration. Il ne se connecte pas à une IA réelle.") # Bouton pour effacer l'historique dans la barre latérale with st.sidebar: if st.button("Effacer l'historique du Chat", use_container_width=True, key="clear_chat_button"): st.session_state.messages = [{"role": "bot", "content": "Bonjour ! Je suis votre assistant virtuel GreenField. Comment puis-je vous aider aujourd'hui ?"}] st.rerun() # Initialisation de l'historique du chat if "messages" not in st.session_state: st.session_state.messages = [{"role": "bot", "content": "Bonjour ! Je suis votre assistant virtuel GreenField. Comment puis-je vous aider aujourd'hui ?"}] # Affichage des messages for message in st.session_state.messages: with st.chat_message(message["role"], avatar='🧑‍🌾' if message["role"] == 'user' else '🤖'): st.markdown(message["content"]) st.markdown("
", unsafe_allow_html=True) # --- Form for text input and file uploader --- with st.form("chat_input_form", clear_on_submit=True): message_input = st.text_input("💬 Entrez votre message ici :", "", key="chat_text_input", autocomplete="off") # COMMENTED OUT: Removed audio file uploader as it required Groq Whisper # audio_file_uploader = st.file_uploader("📢 Téléversez un message audio (format m4a, mp3, wav)", type=["m4a", "mp3", "wav"], key="chat_audio_uploader") send_button = st.form_submit_button("✉️ Envoyer Message", type="primary") processed_message_content = "" # Logique de traitement du formulaire soumis (texte seulement maintenant) if send_button: # COMMENTED OUT: Logic for audio file uploader, as it uses Groq Whisper # if audio_file_uploader: # st.info("Traitement du fichier audio téléversé...") # filename = None # try: # filename = tempfile.NamedTemporaryFile(delete=False, suffix=f".{audio_file_uploader.name.split('.')[-1]}").name # with open(filename, "wb") as f: # f.write(audio_file_uploader.getvalue()) # with open(filename, "rb") as file_to_transcribe: # with st.spinner("Transcription audio en cours..."): # # transcription = groq_client.audio.transcriptions.create(...) # This line is now commented out # processed_message_content = "Ceci est une transcription simulée de votre message audio." # st.session_state.messages.append({"role": "user", "content": processed_message_content}) # except Exception as e: # st.error(f"Erreur Transcription (Fichier Téléversé): {e}") # processed_message_content = "" # finally: # if filename and os.path.exists(filename): # os.remove(filename) if message_input: # If text input is provided processed_message_content = message_input st.session_state.messages.append({"role": "user", "content": processed_message_content}) # Si un message a été traité avec succès (texte seulement) if processed_message_content: with st.spinner("Le chatbot réfléchit..."): # MODIFIED: Simulate Groq response simulated_response = "Bonjour ! Je suis votre assistant virtuel GreenField. Pour l'instant, je ne peux que simuler des réponses, mais je suis là pour vous aider à explorer les fonctionnalités de l'application. N'hésitez pas à naviguer !" st.session_state.messages.append({"role": "bot", "content": simulated_response}) st.rerun() # Force rerun to update chat history and show response # --- LOGIQUE PRINCIPALE DE L'APPLICATION --- # Chargement du CSS et Font Awesome load_css(CSS_PATH) st.markdown("""""", unsafe_allow_html=True) # COMMENTED OUT: Initialisation du client Groq groq_client = None # Set to None directly to indicate it's not active # try: # load_dotenv() # Charge les variables d'environnement du fichier .env # groq_api_key = os.environ.get("GROQ_API_KEY") # if groq_api_key: # groq_client = Groq(api_key=groq_api_key) # else: # st.sidebar.warning("GROQ_API_KEY non trouvée. Le chatbot et les recommandations d'analyse ne seront pas disponibles. Veuillez l'ajouter à votre fichier `.env`.") # except Exception as e: # st.sidebar.error(f"Erreur d'initialisation Groq : {e}. Le chatbot et les recommandations seront désactivés.") # Chargement du modèle Keras (mise en cache automatique par @st.cache_resource) # MODIFIED: Call the new function for loading from Hugging Face Hub keras_model = load_keras_model_from_hub(HF_REPO_ID, HF_MODEL_FILENAME) # Configuration de la barre latérale with st.sidebar: try: logo_image = Image.open(LOGO_PATH) st.image(logo_image, use_container_width=True) except FileNotFoundError: st.warning(f"Logo non trouvé à {LOGO_PATH}. L'image par défaut sera utilisée.") except Exception as e: st.warning(f"Erreur lors du chargement du logo : {e}") st.markdown("

Menu Principal

", unsafe_allow_html=True) # Titre du menu # Boutons de navigation if st.button("📊 Tableau de Bord", use_container_width=True, key="nav_dashboard"): st.session_state.current_view = "dashboard" st.rerun() # Bouton pour la nouvelle vue d'analyse de plante if st.button("🌱 Analyser ma plante", use_container_width=True, key="nav_analysis"): st.session_state.current_view = "plant_analysis" # Nouvelle vue st.rerun() if st.button("💬 Assistant Virtuel", use_container_width=True, key="nav_chat"): st.session_state.current_view = "chat" st.rerun() st.markdown("
", unsafe_allow_html=True) # Séparateur # Bouton de déconnexion (simulé) if st.button("🚪 Déconnexion", use_container_width=True, key="nav_logout"): st.warning("Déconnexion simulée. Ajoutez votre logique de déconnexion ici.") st.session_state.current_view = "dashboard" # Redirige vers le tableau de bord après déconnexion st.rerun() # Pour rafraîchir la page après la déconnexion simulée st.markdown("
© 2025 GreenField Pro. Tous droits réservés.
", unsafe_allow_html=True) # Logique de routage pour afficher la bonne vue if "current_view" not in st.session_state: st.session_state.current_view = "dashboard" # Vue par défaut if st.session_state.current_view == "dashboard": show_dashboard_view() elif st.session_state.current_view == "plant_analysis": # Appel à la nouvelle vue show_plant_analysis_view(groq_client, keras_model) # groq_client est passé mais est None et n'est pas utilisé elif st.session_state.current_view == "chat": show_chatbot_view(groq_client) # groq_client est passé mais est None et n'est pas utilisé else: # Fallback pour toute valeur inattendue show_dashboard_view()