#!/usr/bin/env python3 """ Interface Gradio simplifiée pour Odoo ERP avec fonctions CRM natives """ import gradio as gr import logging import json import sys import os # Ajout du répertoire src au path sys.path.insert(0, os.path.abspath('.')) try: # Import de la configuration import config # Import des fonctions CRM natives import crm_gradio_tools # Import des fonctions Sales natives import sales_gradio_tools # Import des fonctions Modal ML import modal_gradio_wrapper except ImportError as e: print(f"❌ Erreur d'importation: {e}") sys.exit(1) # Configuration logging simple logging.basicConfig(level=logging.INFO) logger = logging.getLogger(__name__) # ============================================================================= # FONCTIONS MCP POUR GRADIO (Auto-détectées par le serveur MCP) # ============================================================================= def get_crm_statistics() -> str: """ Récupère les statistiques CRM complètes d'Odoo. Returns: str: Rapport détaillé des statistiques CRM avec métriques et analyses. """ try: result = crm_gradio_tools.get_crm_statistics() return result except Exception as e: return f"❌ Erreur CRM: {str(e)}" def analyze_leads_advanced(domain_filter: str = "[]", limit: int = 20) -> str: """ Analyse avancée des leads CRM avec filtrage et recommandations. Args: domain_filter (str): Filtre de domaine Odoo au format JSON (ex: [['stage_id', '=', 1]]) limit (int): Nombre maximum de leads à analyser Returns: str: Analyse détaillée des leads avec recommandations d'actions. """ try: result = crm_gradio_tools.analyze_leads_advanced(domain_filter, limit) return result except Exception as e: return f"❌ Erreur analyse: {str(e)}" def search_leads_by_criteria(name: str = "", min_revenue: float = 0, stage: str = "", limit: int = 10) -> str: """ Recherche de leads CRM selon des critères spécifiques. Args: name (str): Nom du lead à rechercher min_revenue (float): Revenue minimum attendu stage (str): Étape du pipeline CRM limit (int): Nombre maximum de résultats Returns: str: Liste des leads correspondant aux critères avec détails. """ try: result = crm_gradio_tools.search_leads_by_criteria(name, min_revenue, stage, limit) return result except Exception as e: return f"❌ Erreur recherche: {str(e)}" def get_modal_model_status() -> str: """ Vérifie le statut du modèle d'intelligence artificielle Modal. Returns: str: Statut détaillé du modèle ML (entraîné, prêt, erreurs). """ try: result = modal_gradio_wrapper.gradio_get_model_status() return result except Exception as e: return f"❌ Erreur Modal: {str(e)}" def search_odoo_records(model: str = "crm.lead", domain_text: str = "[]", fields_text: str = "name,email_from", limit: int = 10) -> str: """ Recherche d'enregistrements dans n'importe quel modèle Odoo. Args: model (str): Modèle Odoo à interroger (ex: crm.lead, sale.order) domain_text (str): Filtre de recherche au format JSON fields_text (str): Champs à récupérer séparés par des virgules limit (int): Nombre maximum d'enregistrements Returns: str: Liste des enregistrements trouvés avec leurs détails. """ try: if not config.client.is_connected(): return "❌ Pas connecté à Odoo - Configurez d'abord la connexion" # Parse domaine domain = [] if domain_text.strip(): try: domain = eval(domain_text.strip()) except: return "❌ Format de domaine invalide. Utilisez: [['field', '=', 'value']]" # Parse champs fields = [f.strip() for f in fields_text.split(',') if f.strip()] if fields_text else [] # Recherche records = config.client.search_read(model, domain, fields, limit) if records: result_text = f"✅ **{len(records)} enregistrements trouvés dans {model}**\n\n" for i, record in enumerate(records[:3]): result_text += f"**{i+1}.** ID: {record.get('id', 'N/A')}" if 'name' in record: result_text += f" | Nom: {record['name']}" if 'email' in record: result_text += f" | Email: {record['email']}" result_text += "\n" if len(records) > 3: result_text += f"\n... et {len(records) - 3} autres enregistrements" return result_text else: return f"⚠️ Aucun enregistrement trouvé dans {model}" except Exception as e: return f"❌ Erreur: {str(e)}" # ============================================================================= # WRAPPERS SYNCHRONES POUR MCP (Gradio MCP nécessite des fonctions sync) # ============================================================================= def sync_get_crm_stats(): """Wrapper synchrone pour get_crm_stats""" try: result = crm_gradio_tools.get_crm_statistics() return result except Exception as e: return f"❌ Erreur CRM: {str(e)}" def sync_analyze_leads(domain_filter: str = "[]", limit: int = 20): """Wrapper synchrone pour analyze_leads""" try: result = crm_gradio_tools.analyze_leads_advanced(domain_filter, limit) return result except Exception as e: return f"❌ Erreur analyse: {str(e)}" def sync_search_leads(name: str = "", min_revenue: float = 0, stage: str = "", limit: int = 10): """Wrapper synchrone pour search_leads""" try: result = crm_gradio_tools.search_leads_by_criteria(name, min_revenue, stage, limit) return result except Exception as e: return f"❌ Erreur recherche: {str(e)}" def sync_get_sales_stats(): """Wrapper synchrone pour get_sales_stats""" try: import asyncio loop = asyncio.new_event_loop() asyncio.set_event_loop(loop) result = loop.run_until_complete(sales_gradio_tools.get_sales_statistics()) loop.close() return result except Exception as e: return f"❌ Erreur Sales: {str(e)}" def sync_get_modal_status(): """Wrapper synchrone pour get_modal_status""" try: result = modal_gradio_wrapper.gradio_get_model_status() return result except Exception as e: return f"❌ Erreur Modal: {str(e)}" def sync_search_records(model: str = "crm.lead", domain_text: str = "[]", fields_text: str = "name,email_from", limit: int = 10): """Wrapper synchrone pour search_records_simple""" try: if not config.client.is_connected(): return "❌ Pas connecté à Odoo - Configurez d'abord la connexion" # Parse domaine domain = [] if domain_text.strip(): try: domain = eval(domain_text.strip()) except: return "❌ Format de domaine invalide. Utilisez: [['field', '=', 'value']]" # Parse champs fields = [f.strip() for f in fields_text.split(',') if f.strip()] if fields_text else [] # Recherche records = config.client.search_read(model, domain, fields, limit) if records: result_text = f"✅ **{len(records)} enregistrements trouvés dans {model}**\n\n" for i, record in enumerate(records[:3]): result_text += f"**{i+1}.** ID: {record.get('id', 'N/A')}" if 'name' in record: result_text += f" | Nom: {record['name']}" if 'email' in record: result_text += f" | Email: {record['email']}" result_text += "\n" if len(records) > 3: result_text += f"\n... et {len(records) - 3} autres enregistrements" return result_text else: return f"⚠️ Aucun enregistrement trouvé dans {model}" except Exception as e: return f"❌ Erreur: {str(e)}" # ============================================================================= # FONCTIONS DE L'INTERFACE GRADIO (Logique UI) # ============================================================================= async def test_connection_only(url: str, database: str, username: str, password: str): """TESTE uniquement la connexion sans sauvegarder""" try: if not all([url, database, username, password]): return [{"role": "assistant", "content": "❌ Tous les champs sont requis"}] # Test temporaire temp_client = config.SimpleOdooClient() result = temp_client.connect(url.strip(), database.strip(), username.strip(), password) if result["success"]: return [{"role": "assistant", "content": f"✅ **Test connexion réussi !**\n\n🌐 URL: {url}\n🗄️ Base: {database}\n👤 Utilisateur: {username}\n📋 Version: {result.get('version', 'N/A')}\n\n⚠️ **Credentials non sauvegardés** - Cliquez 'Enregistrer' pour activer les fonctions CRM."}] else: return [{"role": "assistant", "content": f"❌ Erreur de connexion: {result['error']}"}] except Exception as e: return [{"role": "assistant", "content": f"❌ Exception: {str(e)}"}] async def save_credentials(url: str, database: str, username: str, password: str): """SAUVEGARDE les credentials et connecte le client global""" try: if not all([url, database, username, password]): return [{"role": "assistant", "content": "❌ Tous les champs sont requis"}] # ✅ CONNEXION ET SAUVEGARDE DU CLIENT GLOBAL result = config.client.connect(url.strip(), database.strip(), username.strip(), password) if result["success"]: # ✅ SAUVEGARDE EN MÉMOIRE ET FICHIER config.stored_credentials.update({ "url": url.strip(), "database": database.strip(), "username": username.strip(), "password": password }) # ✅ PERSISTANCE FICHIER config.save_credentials_to_file(url.strip(), database.strip(), username.strip(), password) success_msg = f"""✅ **Credentials sauvegardés et connecté !** 🌐 **URL**: {url} 🗄️ **Base**: {database} 👤 **Utilisateur**: {username} 📋 **Version**: {result.get('version', 'N/A')} 💾 **Persistance**: Fichier `odoo_config.json` créé 🤖 **MCP activé** - Claude peut maintenant accéder à votre Odoo ! 🔥 **CRM natif** - Fonctions d'analyse intelligente disponibles !""" return [{"role": "assistant", "content": success_msg}] else: return [{"role": "assistant", "content": f"❌ Erreur de connexion: {result['error']}"}] except Exception as e: return [{"role": "assistant", "content": f"❌ Exception: {str(e)}"}] async def load_saved_credentials(): """Charge les credentials sauvegardés""" config.stored_credentials = config.load_credentials_from_file() if all(config.stored_credentials.values()): # Tentative de connexion automatique result = config.client.connect( config.stored_credentials["url"], config.stored_credentials["database"], config.stored_credentials["username"], config.stored_credentials["password"] ) if result["success"]: return ( config.stored_credentials["url"], config.stored_credentials["database"], config.stored_credentials["username"], config.stored_credentials["password"], [{"role": "assistant", "content": f"✅ **Credentials chargés et connecté !**\n\n🌐 URL: {config.stored_credentials['url']}\n🗄️ Base: {config.stored_credentials['database']}\n👤 Utilisateur: {config.stored_credentials['username']}\n🔥 **CRM natif disponible !**"}] ) else: return ( config.stored_credentials["url"], config.stored_credentials["database"], config.stored_credentials["username"], config.stored_credentials["password"], [{"role": "assistant", "content": f"⚠️ **Credentials chargés mais connexion échouée**: {result['error']}"}] ) else: return ("", "", "", "", [{"role": "assistant", "content": "📂 Aucun credential sauvegardé trouvé"}]) async def search_records_simple(model: str, domain_text: str, fields_text: str, limit: int): """Recherche d'enregistrements simple""" try: if not config.client.is_connected(): return [{"role": "assistant", "content": "❌ Pas connecté à Odoo - Cliquez 'Enregistrer' d'abord"}] # Parse domaine domain = [] if domain_text.strip(): try: domain = eval(domain_text.strip()) except: return [{"role": "assistant", "content": "❌ Format de domaine invalide. Utilisez: [['field', '=', 'value']]"}] # Parse champs fields = [f.strip() for f in fields_text.split(',') if f.strip()] if fields_text else [] # Recherche records = config.client.search_read(model, domain, fields, limit) if records: result_text = f"✅ **{len(records)} enregistrements trouvés dans {model}**\n\n" for i, record in enumerate(records[:3]): result_text += f"**{i+1}.** ID: {record.get('id', 'N/A')}" if 'name' in record: result_text += f" | Nom: {record['name']}" if 'email' in record: result_text += f" | Email: {record['email']}" result_text += "\n" if len(records) > 3: result_text += f"\n... et {len(records) - 3} autres enregistrements" return [{"role": "assistant", "content": result_text}] else: return [{"role": "assistant", "content": f"⚠️ Aucun enregistrement trouvé dans {model}"}] except Exception as e: return [{"role": "assistant", "content": f"❌ Erreur: {str(e)}"}] async def create_record_simple(model: str, values_json: str): """Création d'enregistrement simple""" try: if not config.client.is_connected(): return [{"role": "assistant", "content": "❌ Pas connecté à Odoo - Cliquez 'Enregistrer' d'abord"}] try: values = json.loads(values_json) except json.JSONDecodeError: return [{"role": "assistant", "content": "❌ JSON invalide"}] record_id = config.client.create(model, values) return [{"role": "assistant", "content": f"✅ Enregistrement créé avec l'ID: **{record_id}** dans {model}"}] except Exception as e: return [{"role": "assistant", "content": f"❌ Erreur: {str(e)}"}] def clear_workspace(): """Nettoie l'espace de travail""" config.stored_credentials.clear() config.stored_credentials.update({"url": "", "database": "", "username": "", "password": ""}) return gr.update(value=""), gr.update(value=""), gr.update(value=""), gr.update(value=""), [] # ============================================================================= # FONCTIONS CRM NATIVES POUR GRADIO # ============================================================================= async def get_crm_stats(): """Récupère les statistiques CRM natives""" try: result = crm_gradio_tools.get_crm_statistics() return [{"role": "assistant", "content": result}] except Exception as e: return [{"role": "assistant", "content": f"❌ Erreur CRM: {str(e)}"}] async def analyze_leads(domain_filter: str, limit: int): """Analyse avancée des leads native""" try: result = crm_gradio_tools.analyze_leads_advanced(domain_filter, limit) return [{"role": "assistant", "content": result}] except Exception as e: return [{"role": "assistant", "content": f"❌ Erreur analyse: {str(e)}"}] async def monitor_crm(time_window: int, threshold: float): """Monitoring CRM natif""" try: result = crm_gradio_tools.monitor_crm_performance(time_window, threshold) return [{"role": "assistant", "content": result}] except Exception as e: return [{"role": "assistant", "content": f"❌ Erreur monitoring: {str(e)}"}] async def search_leads(name: str, min_revenue: float, stage: str, limit: int): """Recherche des leads native""" try: result = crm_gradio_tools.search_leads_by_criteria(name, min_revenue, stage, limit) return [{"role": "assistant", "content": result}] except Exception as e: return [{"role": "assistant", "content": f"❌ Erreur recherche: {str(e)}"}] async def get_crm_info(): """Affiche les informations sur les fonctions CRM""" try: result = crm_gradio_tools.get_crm_tools_info() return [{"role": "assistant", "content": result}] except Exception as e: return [{"role": "assistant", "content": f"❌ Erreur info: {str(e)}"}] # ============================================================================= # FONCTIONS SALES NATIVES POUR GRADIO # ============================================================================= async def get_sales_stats(): """Récupère les statistiques Sales natives""" try: result = await sales_gradio_tools.get_sales_statistics() return [{"role": "assistant", "content": result}] except Exception as e: return [{"role": "assistant", "content": f"❌ Erreur Sales: {str(e)}"}] async def analyze_quotations(domain_filter: str, limit: int): """Analyse avancée des devis native""" try: result = await sales_gradio_tools.analyze_quotations_advanced(domain_filter, limit) return [{"role": "assistant", "content": result}] except Exception as e: return [{"role": "assistant", "content": f"❌ Erreur analyse devis: {str(e)}"}] async def send_quotation_email(order_id: int, subject: str, body: str): """Envoi d'email de devis natif""" try: result = await sales_gradio_tools.send_quotation_email_gradio(order_id, subject, body) return [{"role": "assistant", "content": result}] except Exception as e: return [{"role": "assistant", "content": f"❌ Erreur envoi email: {str(e)}"}] async def search_sales_orders_wrapper(name: str, min_amount: float, state: str, limit: int): """Recherche des commandes de vente native""" try: result = await sales_gradio_tools.search_sales_orders(name, min_amount, state, limit) return [{"role": "assistant", "content": result}] except Exception as e: return [{"role": "assistant", "content": f"❌ Erreur recherche commandes: {str(e)}"}] async def monitor_sales(time_window: int, threshold: float): """Monitoring Sales natif""" try: result = await sales_gradio_tools.monitor_sales_performance(time_window, threshold) return [{"role": "assistant", "content": result}] except Exception as e: return [{"role": "assistant", "content": f"❌ Erreur monitoring sales: {str(e)}"}] async def get_sales_info(): """Affiche les informations sur les fonctions Sales""" try: result = await sales_gradio_tools.get_sales_tools_info() return [{"role": "assistant", "content": result}] except Exception as e: return [{"role": "assistant", "content": f"❌ Erreur info sales: {str(e)}"}] # ============================================================================= # FONCTIONS MODAL ML POUR GRADIO # ============================================================================= async def train_modal_model(num_leads: int): """Entraîne le modèle Modal ML""" try: result_msg, status_msg = modal_gradio_wrapper.gradio_train_model(num_leads) return [{"role": "assistant", "content": result_msg}] except Exception as e: return [{"role": "assistant", "content": f"❌ Erreur entraînement Modal: {str(e)}"}] async def predict_modal_lead(name: str, industry: str, company_size: str, budget_range: str, urgency: str, source: str, expected_revenue: float, response_time: float): """Prédiction Modal pour un lead""" try: result = modal_gradio_wrapper.gradio_predict_lead( name, industry, company_size, budget_range, urgency, source, expected_revenue, response_time ) return [{"role": "assistant", "content": result}] except Exception as e: return [{"role": "assistant", "content": f"❌ Erreur prédiction Modal: {str(e)}"}] async def get_modal_status(): """Statut du modèle Modal""" try: result = modal_gradio_wrapper.gradio_get_model_status() return [{"role": "assistant", "content": result}] except Exception as e: return [{"role": "assistant", "content": f"❌ Erreur statut Modal: {str(e)}"}] # Interface Gradio améliorée theme = gr.themes.Soft( primary_hue="blue", secondary_hue="slate", neutral_hue="gray", ) with gr.Blocks(title="Interface Odoo CRM & Sales", theme=theme) as demo: gr.Markdown(""" # 🚀 Interface Odoo CRM & Sales **Interface simplifiée pour la gestion et l'analyse de votre CRM et Sales Odoo.** --- """) # ======================================================================== # SECTION CONNEXION # ======================================================================== with gr.Row(): with gr.Column(scale=1): gr.Markdown("### 🔐 Connexion Odoo") url_input = gr.Textbox( label="🌐 URL Odoo", placeholder="https://votre-instance.odoo.com", value=config.ODOO_URL ) db_input = gr.Textbox( label="🗄️ Base de données", placeholder="votre-db", value=config.ODOO_DB ) username_input = gr.Textbox( label="👤 Utilisateur", placeholder="admin@example.com", value=config.ODOO_USERNAME ) password_input = gr.Textbox( label="🔒 Mot de passe", type="password", value=config.ODOO_PASSWORD ) with gr.Row(): clear_btn = gr.Button("🗑️ Effacer", variant="secondary") load_btn = gr.Button("📂 Charger", variant="secondary") test_btn = gr.Button("🧪 Tester", variant="secondary") save_btn = gr.Button("💾 Enregistrer", variant="primary") with gr.Column(scale=1): chatbot = gr.Chatbot( label="📋 Statut de connexion", type="messages", height=300, avatar_images=(None, "🤖") ) # ======================================================================== # SECTIONS FONCTIONNELLES - ONGLETS # ======================================================================== with gr.Tabs(): # ==================================================================== # ONGLET CRM # ==================================================================== with gr.TabItem("📊 CRM"): gr.Markdown("### 🎯 Fonctions CRM Natives") with gr.Row(): with gr.Column(): crm_stats_btn = gr.Button("📊 Statistiques CRM", variant="primary") crm_info_btn = gr.Button("ℹ️ Info CRM Tools", variant="secondary") with gr.Column(): gr.Markdown("**🔍 Analyse Leads**") leads_domain = gr.Textbox(label="Domaine (JSON)", placeholder="[]", value="[]") leads_limit = gr.Slider(5, 50, 20, label="Limite") analyze_leads_btn = gr.Button("🚀 Analyser Leads") with gr.Column(): gr.Markdown("**📊 Monitoring**") monitor_hours = gr.Slider(1, 168, 24, label="Heures") monitor_threshold = gr.Slider(0.1, 1.0, 0.7, label="Seuil") monitor_crm_btn = gr.Button("📈 Monitor CRM") with gr.Row(): with gr.Column(): gr.Markdown("**🔍 Recherche Leads**") search_name = gr.Textbox(label="Nom", placeholder="") search_revenue = gr.Number(label="Revenue min", value=0) search_stage = gr.Textbox(label="Étape", placeholder="") search_limit = gr.Slider(5, 50, 10, label="Limite") search_leads_btn = gr.Button("🔍 Rechercher") crm_output = gr.Chatbot(label="💬 Résultats CRM", type="messages", height=400) # ==================================================================== # ONGLET SALES # ==================================================================== with gr.TabItem("💰 Sales"): gr.Markdown("### 🎯 Fonctions Sales Natives") with gr.Row(): with gr.Column(): sales_stats_btn = gr.Button("💰 Statistiques Sales", variant="primary") sales_info_btn = gr.Button("ℹ️ Info Sales Tools", variant="secondary") with gr.Column(): gr.Markdown("**📊 Analyse Devis**") quota_domain = gr.Textbox(label="Domaine (JSON)", placeholder="[]", value="[]") quota_limit = gr.Slider(5, 100, 20, label="Limite") analyze_quotas_btn = gr.Button("📋 Analyser Devis") with gr.Column(): gr.Markdown("**📧 Email Devis**") email_order_id = gr.Number(label="ID Commande", value=0) email_subject = gr.Textbox(label="Sujet", placeholder="") email_body = gr.Textbox(label="Corps", placeholder="", lines=3) send_email_btn = gr.Button("📧 Envoyer Email") with gr.Row(): with gr.Column(): gr.Markdown("**🔍 Recherche Commandes**") order_name = gr.Textbox(label="Nom", placeholder="") order_amount = gr.Number(label="Montant min", value=0) order_state = gr.Textbox(label="État", placeholder="") order_limit = gr.Slider(5, 50, 10, label="Limite") search_orders_btn = gr.Button("🔍 Rechercher") with gr.Column(): gr.Markdown("**📊 Monitoring Sales**") sales_hours = gr.Slider(1, 168, 24, label="Heures") sales_threshold = gr.Slider(0.1, 1.0, 0.7, label="Seuil") monitor_sales_btn = gr.Button("📈 Monitor Sales") sales_output = gr.Chatbot(label="💬 Résultats Sales", type="messages", height=400) # ==================================================================== # ONGLET MODAL ML # ==================================================================== with gr.TabItem("🤖 Modal ML"): gr.Markdown("### 🧠 Intelligence Artificielle Modal") with gr.Row(): with gr.Column(): gr.Markdown("**🎯 Modèle ML**") modal_status_btn = gr.Button("📊 Statut Modèle", variant="secondary") num_synthetic_leads = gr.Slider(100, 5000, 1000, label="Leads synthétiques") train_model_btn = gr.Button("🚀 Entraîner Modèle", variant="primary") with gr.Column(): gr.Markdown("**🔮 Prédiction Lead**") pred_name = gr.Textbox(label="Nom du lead", placeholder="Artisans Bernard") pred_industry = gr.Dropdown( choices=["Technology", "Healthcare", "Finance", "Manufacturing", "Retail", "Other"], label="Secteur", value="Technology" ) pred_company_size = gr.Dropdown( choices=["Small", "Medium", "Large", "Enterprise"], label="Taille entreprise", value="Medium" ) with gr.Column(): gr.Markdown("**💰 Détails Lead**") pred_budget = gr.Dropdown( choices=["Low", "Medium", "High", "Very High"], label="Budget", value="Medium" ) pred_urgency = gr.Dropdown( choices=["Low", "Medium", "High", "Critical"], label="Urgence", value="Medium" ) pred_source = gr.Dropdown( choices=["Website", "Email", "Cold Call", "Referral", "Social Media"], label="Source", value="Website" ) with gr.Row(): with gr.Column(): pred_revenue = gr.Number(label="Revenue attendu (€)", value=15000, minimum=0) pred_response_time = gr.Number(label="Temps de réponse (h)", value=2.0, minimum=0.1) predict_btn = gr.Button("🔮 Prédire Conversion", variant="primary") modal_output = gr.Chatbot(label="💬 Résultats Modal ML", type="messages", height=400) # ==================================================================== # ONGLET RECHERCHE SIMPLE # ==================================================================== with gr.TabItem("🔍 Recherche"): gr.Markdown("### 🔍 Recherche Simple Odoo") with gr.Row(): with gr.Column(): model_input = gr.Textbox(label="Modèle", placeholder="crm.lead", value="crm.lead") domain_input = gr.Textbox(label="Domaine", placeholder="[['name', 'ilike', 'test']]", value="[]") fields_input = gr.Textbox(label="Champs", placeholder="name,email_from,phone", value="name,email_from,phone") limit_input = gr.Slider(1, 50, 10, label="Limite") search_btn = gr.Button("🔍 Rechercher", variant="primary") with gr.Column(): gr.Markdown("**➕ Création Simple**") create_model = gr.Textbox(label="Modèle", placeholder="crm.lead") create_values = gr.Textbox(label="Valeurs (JSON)", placeholder='{"name": "Test Lead"}', lines=5) create_btn = gr.Button("➕ Créer", variant="secondary") search_output = gr.Chatbot(label="💬 Résultats Recherche", type="messages", height=400) # ======================================================================== # ÉVÉNEMENTS CONNEXION # ======================================================================== # Connexion test_btn.click( fn=test_connection_only, inputs=[url_input, db_input, username_input, password_input], outputs=chatbot, queue=True, ) save_btn.click( fn=save_credentials, inputs=[url_input, db_input, username_input, password_input], outputs=chatbot, queue=True, ) # Utilitaires clear_btn.click( fn=clear_workspace, inputs=[], outputs=[url_input, db_input, username_input, password_input, chatbot], ) load_btn.click( fn=load_saved_credentials, inputs=[], outputs=[url_input, db_input, username_input, password_input, chatbot], queue=True, ) # ======================================================================== # ÉVÉNEMENTS CRM # ======================================================================== def crm_stats_wrapper(): result = sync_get_crm_stats() return [{"role": "assistant", "content": result}] def analyze_leads_wrapper(domain_filter, limit): result = sync_analyze_leads(domain_filter, limit) return [{"role": "assistant", "content": result}] def search_leads_wrapper(name, min_revenue, stage, limit): result = sync_search_leads(name, min_revenue, stage, limit) return [{"role": "assistant", "content": result}] def sales_stats_wrapper(): result = sync_get_sales_stats() return [{"role": "assistant", "content": result}] def modal_status_wrapper(): result = sync_get_modal_status() return [{"role": "assistant", "content": result}] def search_records_wrapper(model, domain, fields, limit): result = sync_search_records(model, domain, fields, limit) return [{"role": "assistant", "content": result}] crm_stats_btn.click(fn=crm_stats_wrapper, inputs=[], outputs=crm_output, queue=True) crm_info_btn.click(fn=get_crm_info, inputs=[], outputs=crm_output, queue=True) analyze_leads_btn.click(fn=analyze_leads_wrapper, inputs=[leads_domain, leads_limit], outputs=crm_output, queue=True) monitor_crm_btn.click(fn=monitor_crm, inputs=[monitor_hours, monitor_threshold], outputs=crm_output, queue=True) search_leads_btn.click(fn=search_leads_wrapper, inputs=[search_name, search_revenue, search_stage, search_limit], outputs=crm_output, queue=True) # ======================================================================== # ÉVÉNEMENTS SALES # ======================================================================== sales_stats_btn.click(fn=sales_stats_wrapper, inputs=[], outputs=sales_output, queue=True) sales_info_btn.click(fn=get_sales_info, inputs=[], outputs=sales_output, queue=True) analyze_quotas_btn.click(fn=analyze_quotations, inputs=[quota_domain, quota_limit], outputs=sales_output, queue=True) send_email_btn.click(fn=send_quotation_email, inputs=[email_order_id, email_subject, email_body], outputs=sales_output, queue=True) search_orders_btn.click(fn=search_sales_orders_wrapper, inputs=[order_name, order_amount, order_state, order_limit], outputs=sales_output, queue=True) monitor_sales_btn.click(fn=monitor_sales, inputs=[sales_hours, sales_threshold], outputs=sales_output, queue=True) # ======================================================================== # ÉVÉNEMENTS MODAL ML # ======================================================================== modal_status_btn.click(fn=modal_status_wrapper, inputs=[], outputs=modal_output, queue=True) train_model_btn.click(fn=train_modal_model, inputs=[num_synthetic_leads], outputs=modal_output, queue=True) predict_btn.click(fn=predict_modal_lead, inputs=[pred_name, pred_industry, pred_company_size, pred_budget, pred_urgency, pred_source, pred_revenue, pred_response_time], outputs=modal_output, queue=True) # ======================================================================== # ÉVÉNEMENTS RECHERCHE SIMPLE # ======================================================================== search_btn.click(fn=search_records_wrapper, inputs=[model_input, domain_input, fields_input, limit_input], outputs=search_output, queue=True) create_btn.click(fn=create_record_simple, inputs=[create_model, create_values], outputs=search_output, queue=True) gr.Markdown(""" --- ### 🚀 Guide d'Utilisation #### 📶 **1. Connexion** 1. **Remplir** vos informations Odoo 2. **Tester** la connexion (optionnel) 3. **Enregistrer** pour activer toutes les fonctions CRM & Sales #### 📊 **2. Utilisation CRM** - **Statistiques** : Vue d'ensemble du pipeline - **Analyse Leads** : Classification intelligente - **Monitoring** : Surveillance temps réel - **Recherche** : Filtrage multi-critères #### 💰 **3. Utilisation Sales** - **Statistiques** : Métriques commerciales - **Analyse Devis** : Recommandations d'actions - **Email Devis** : Envoi personnalisé - **Recherche** : Gestion des commandes #### 🤖 **4. Intelligence Artificielle Modal** - **Statut Modèle** : Vérifier si le modèle ML est entraîné - **Entraîner Modèle** : Créer un modèle personnalisé (1000+ leads synthétiques) - **Prédire Conversion** : Analyse IA d'un lead spécifique - **Classification** : HOT/WARM/COLD avec probabilités avancées --- ### 💡 Objectif **🔥 Interface CRM & Sales native** : - 📊 Analyse de vos vraies données Odoo - 🎯 Fonctions CRM et Sales opérationnelles - 📈 Reporting et statistiques en temps réel - 📧 Envoi d'emails de devis personnalisés - 🤖 Intelligence Artificielle Modal pour prédictions - 🤖 Serveur MCP pour l'intégration IA --- """) if __name__ == "__main__": print("🚀 Démarrage Interface Odoo CRM & Sales...") print("📊 Interface native pour l'analyse CRM & Sales") print("📧 Fonctions d'envoi d'emails intégrées") print("🤖 Intelligence Artificielle Modal pour prédictions") # ✅ ACTIVATION MCP SELON GRADIO ≥5.27 (variable d'environnement) os.environ["GRADIO_MCP_SERVER"] = "True" print("🤖 Serveur MCP activé via GRADIO_MCP_SERVER=True") # ✅ LANCEMENT GRADIO SANS PARAMÈTRE MCP_SERVER (nouvelle méthode) demo.launch( server_name="0.0.0.0", server_port=7860, share=False, show_error=True )