Spaces:
Running
Running
#!/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 | |
) |