MCP_server_Odoo / app.py
Aktraiser
🔧 FIX CRITIQUE: Activation MCP selon Gradio ≥5.27
c5cbd44
#!/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
)