Spaces:
Sleeping
Sleeping
Aktraiser
commited on
Commit
·
32b3c4e
1
Parent(s):
0cb8341
🔧 FIX: Serveur MCP stable avec wrappers synchrones
Browse files✅ Corrections pour éliminer 'RuntimeError: Received request before initialization':
- Ajout de wrappers synchrones pour toutes les fonctions MCP
- Correction de la gestion async/sync dans Gradio MCP
- Conservation du paramètre mcp_server=True (méthode correcte)
- Fonctions CRM/Sales/Modal maintenant compatibles MCP
- Plus d'erreurs d'initialisation MCP
- app.py +121 -7
- requirements.txt +11 -1
app.py
CHANGED
|
@@ -29,6 +29,95 @@ except ImportError as e:
|
|
| 29 |
logging.basicConfig(level=logging.INFO)
|
| 30 |
logger = logging.getLogger(__name__)
|
| 31 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 32 |
# =============================================================================
|
| 33 |
# FONCTIONS DE L'INTERFACE GRADIO (Logique UI)
|
| 34 |
# =============================================================================
|
|
@@ -563,17 +652,41 @@ with gr.Blocks(title="Interface Odoo CRM & Sales", theme=theme) as demo:
|
|
| 563 |
# ÉVÉNEMENTS CRM
|
| 564 |
# ========================================================================
|
| 565 |
|
| 566 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 567 |
crm_info_btn.click(fn=get_crm_info, inputs=[], outputs=crm_output, queue=True)
|
| 568 |
-
analyze_leads_btn.click(fn=
|
| 569 |
monitor_crm_btn.click(fn=monitor_crm, inputs=[monitor_hours, monitor_threshold], outputs=crm_output, queue=True)
|
| 570 |
-
search_leads_btn.click(fn=
|
| 571 |
|
| 572 |
# ========================================================================
|
| 573 |
# ÉVÉNEMENTS SALES
|
| 574 |
# ========================================================================
|
| 575 |
|
| 576 |
-
sales_stats_btn.click(fn=
|
| 577 |
sales_info_btn.click(fn=get_sales_info, inputs=[], outputs=sales_output, queue=True)
|
| 578 |
analyze_quotas_btn.click(fn=analyze_quotations, inputs=[quota_domain, quota_limit], outputs=sales_output, queue=True)
|
| 579 |
send_email_btn.click(fn=send_quotation_email, inputs=[email_order_id, email_subject, email_body], outputs=sales_output, queue=True)
|
|
@@ -584,7 +697,7 @@ with gr.Blocks(title="Interface Odoo CRM & Sales", theme=theme) as demo:
|
|
| 584 |
# ÉVÉNEMENTS MODAL ML
|
| 585 |
# ========================================================================
|
| 586 |
|
| 587 |
-
modal_status_btn.click(fn=
|
| 588 |
train_model_btn.click(fn=train_modal_model, inputs=[num_synthetic_leads], outputs=modal_output, queue=True)
|
| 589 |
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)
|
| 590 |
|
|
@@ -592,7 +705,7 @@ with gr.Blocks(title="Interface Odoo CRM & Sales", theme=theme) as demo:
|
|
| 592 |
# ÉVÉNEMENTS RECHERCHE SIMPLE
|
| 593 |
# ========================================================================
|
| 594 |
|
| 595 |
-
search_btn.click(fn=
|
| 596 |
create_btn.click(fn=create_record_simple, inputs=[create_model, create_values], outputs=search_output, queue=True)
|
| 597 |
|
| 598 |
gr.Markdown("""
|
|
@@ -643,8 +756,9 @@ if __name__ == "__main__":
|
|
| 643 |
print("📊 Interface native pour l'analyse CRM & Sales")
|
| 644 |
print("📧 Fonctions d'envoi d'emails intégrées")
|
| 645 |
print("🤖 Intelligence Artificielle Modal pour prédictions")
|
| 646 |
-
print("🤖 Serveur MCP activé pour les outils IA")
|
| 647 |
|
|
|
|
| 648 |
demo.launch(
|
| 649 |
server_name="0.0.0.0",
|
| 650 |
server_port=7860,
|
|
|
|
| 29 |
logging.basicConfig(level=logging.INFO)
|
| 30 |
logger = logging.getLogger(__name__)
|
| 31 |
|
| 32 |
+
# =============================================================================
|
| 33 |
+
# WRAPPERS SYNCHRONES POUR MCP (Gradio MCP nécessite des fonctions sync)
|
| 34 |
+
# =============================================================================
|
| 35 |
+
|
| 36 |
+
def sync_get_crm_stats():
|
| 37 |
+
"""Wrapper synchrone pour get_crm_stats"""
|
| 38 |
+
try:
|
| 39 |
+
result = crm_gradio_tools.get_crm_statistics()
|
| 40 |
+
return result
|
| 41 |
+
except Exception as e:
|
| 42 |
+
return f"❌ Erreur CRM: {str(e)}"
|
| 43 |
+
|
| 44 |
+
def sync_analyze_leads(domain_filter: str = "[]", limit: int = 20):
|
| 45 |
+
"""Wrapper synchrone pour analyze_leads"""
|
| 46 |
+
try:
|
| 47 |
+
result = crm_gradio_tools.analyze_leads_advanced(domain_filter, limit)
|
| 48 |
+
return result
|
| 49 |
+
except Exception as e:
|
| 50 |
+
return f"❌ Erreur analyse: {str(e)}"
|
| 51 |
+
|
| 52 |
+
def sync_search_leads(name: str = "", min_revenue: float = 0, stage: str = "", limit: int = 10):
|
| 53 |
+
"""Wrapper synchrone pour search_leads"""
|
| 54 |
+
try:
|
| 55 |
+
result = crm_gradio_tools.search_leads_by_criteria(name, min_revenue, stage, limit)
|
| 56 |
+
return result
|
| 57 |
+
except Exception as e:
|
| 58 |
+
return f"❌ Erreur recherche: {str(e)}"
|
| 59 |
+
|
| 60 |
+
def sync_get_sales_stats():
|
| 61 |
+
"""Wrapper synchrone pour get_sales_stats"""
|
| 62 |
+
try:
|
| 63 |
+
import asyncio
|
| 64 |
+
loop = asyncio.new_event_loop()
|
| 65 |
+
asyncio.set_event_loop(loop)
|
| 66 |
+
result = loop.run_until_complete(sales_gradio_tools.get_sales_statistics())
|
| 67 |
+
loop.close()
|
| 68 |
+
return result
|
| 69 |
+
except Exception as e:
|
| 70 |
+
return f"❌ Erreur Sales: {str(e)}"
|
| 71 |
+
|
| 72 |
+
def sync_get_modal_status():
|
| 73 |
+
"""Wrapper synchrone pour get_modal_status"""
|
| 74 |
+
try:
|
| 75 |
+
result = modal_gradio_wrapper.gradio_get_model_status()
|
| 76 |
+
return result
|
| 77 |
+
except Exception as e:
|
| 78 |
+
return f"❌ Erreur Modal: {str(e)}"
|
| 79 |
+
|
| 80 |
+
def sync_search_records(model: str = "crm.lead", domain_text: str = "[]", fields_text: str = "name,email_from", limit: int = 10):
|
| 81 |
+
"""Wrapper synchrone pour search_records_simple"""
|
| 82 |
+
try:
|
| 83 |
+
if not config.client.is_connected():
|
| 84 |
+
return "❌ Pas connecté à Odoo - Configurez d'abord la connexion"
|
| 85 |
+
|
| 86 |
+
# Parse domaine
|
| 87 |
+
domain = []
|
| 88 |
+
if domain_text.strip():
|
| 89 |
+
try:
|
| 90 |
+
domain = eval(domain_text.strip())
|
| 91 |
+
except:
|
| 92 |
+
return "❌ Format de domaine invalide. Utilisez: [['field', '=', 'value']]"
|
| 93 |
+
|
| 94 |
+
# Parse champs
|
| 95 |
+
fields = [f.strip() for f in fields_text.split(',') if f.strip()] if fields_text else []
|
| 96 |
+
|
| 97 |
+
# Recherche
|
| 98 |
+
records = config.client.search_read(model, domain, fields, limit)
|
| 99 |
+
|
| 100 |
+
if records:
|
| 101 |
+
result_text = f"✅ **{len(records)} enregistrements trouvés dans {model}**\n\n"
|
| 102 |
+
|
| 103 |
+
for i, record in enumerate(records[:3]):
|
| 104 |
+
result_text += f"**{i+1}.** ID: {record.get('id', 'N/A')}"
|
| 105 |
+
if 'name' in record:
|
| 106 |
+
result_text += f" | Nom: {record['name']}"
|
| 107 |
+
if 'email' in record:
|
| 108 |
+
result_text += f" | Email: {record['email']}"
|
| 109 |
+
result_text += "\n"
|
| 110 |
+
|
| 111 |
+
if len(records) > 3:
|
| 112 |
+
result_text += f"\n... et {len(records) - 3} autres enregistrements"
|
| 113 |
+
|
| 114 |
+
return result_text
|
| 115 |
+
else:
|
| 116 |
+
return f"⚠️ Aucun enregistrement trouvé dans {model}"
|
| 117 |
+
|
| 118 |
+
except Exception as e:
|
| 119 |
+
return f"❌ Erreur: {str(e)}"
|
| 120 |
+
|
| 121 |
# =============================================================================
|
| 122 |
# FONCTIONS DE L'INTERFACE GRADIO (Logique UI)
|
| 123 |
# =============================================================================
|
|
|
|
| 652 |
# ÉVÉNEMENTS CRM
|
| 653 |
# ========================================================================
|
| 654 |
|
| 655 |
+
def crm_stats_wrapper():
|
| 656 |
+
result = sync_get_crm_stats()
|
| 657 |
+
return [{"role": "assistant", "content": result}]
|
| 658 |
+
|
| 659 |
+
def analyze_leads_wrapper(domain_filter, limit):
|
| 660 |
+
result = sync_analyze_leads(domain_filter, limit)
|
| 661 |
+
return [{"role": "assistant", "content": result}]
|
| 662 |
+
|
| 663 |
+
def search_leads_wrapper(name, min_revenue, stage, limit):
|
| 664 |
+
result = sync_search_leads(name, min_revenue, stage, limit)
|
| 665 |
+
return [{"role": "assistant", "content": result}]
|
| 666 |
+
|
| 667 |
+
def sales_stats_wrapper():
|
| 668 |
+
result = sync_get_sales_stats()
|
| 669 |
+
return [{"role": "assistant", "content": result}]
|
| 670 |
+
|
| 671 |
+
def modal_status_wrapper():
|
| 672 |
+
result = sync_get_modal_status()
|
| 673 |
+
return [{"role": "assistant", "content": result}]
|
| 674 |
+
|
| 675 |
+
def search_records_wrapper(model, domain, fields, limit):
|
| 676 |
+
result = sync_search_records(model, domain, fields, limit)
|
| 677 |
+
return [{"role": "assistant", "content": result}]
|
| 678 |
+
|
| 679 |
+
crm_stats_btn.click(fn=crm_stats_wrapper, inputs=[], outputs=crm_output, queue=True)
|
| 680 |
crm_info_btn.click(fn=get_crm_info, inputs=[], outputs=crm_output, queue=True)
|
| 681 |
+
analyze_leads_btn.click(fn=analyze_leads_wrapper, inputs=[leads_domain, leads_limit], outputs=crm_output, queue=True)
|
| 682 |
monitor_crm_btn.click(fn=monitor_crm, inputs=[monitor_hours, monitor_threshold], outputs=crm_output, queue=True)
|
| 683 |
+
search_leads_btn.click(fn=search_leads_wrapper, inputs=[search_name, search_revenue, search_stage, search_limit], outputs=crm_output, queue=True)
|
| 684 |
|
| 685 |
# ========================================================================
|
| 686 |
# ÉVÉNEMENTS SALES
|
| 687 |
# ========================================================================
|
| 688 |
|
| 689 |
+
sales_stats_btn.click(fn=sales_stats_wrapper, inputs=[], outputs=sales_output, queue=True)
|
| 690 |
sales_info_btn.click(fn=get_sales_info, inputs=[], outputs=sales_output, queue=True)
|
| 691 |
analyze_quotas_btn.click(fn=analyze_quotations, inputs=[quota_domain, quota_limit], outputs=sales_output, queue=True)
|
| 692 |
send_email_btn.click(fn=send_quotation_email, inputs=[email_order_id, email_subject, email_body], outputs=sales_output, queue=True)
|
|
|
|
| 697 |
# ÉVÉNEMENTS MODAL ML
|
| 698 |
# ========================================================================
|
| 699 |
|
| 700 |
+
modal_status_btn.click(fn=modal_status_wrapper, inputs=[], outputs=modal_output, queue=True)
|
| 701 |
train_model_btn.click(fn=train_modal_model, inputs=[num_synthetic_leads], outputs=modal_output, queue=True)
|
| 702 |
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)
|
| 703 |
|
|
|
|
| 705 |
# ÉVÉNEMENTS RECHERCHE SIMPLE
|
| 706 |
# ========================================================================
|
| 707 |
|
| 708 |
+
search_btn.click(fn=search_records_wrapper, inputs=[model_input, domain_input, fields_input, limit_input], outputs=search_output, queue=True)
|
| 709 |
create_btn.click(fn=create_record_simple, inputs=[create_model, create_values], outputs=search_output, queue=True)
|
| 710 |
|
| 711 |
gr.Markdown("""
|
|
|
|
| 756 |
print("📊 Interface native pour l'analyse CRM & Sales")
|
| 757 |
print("📧 Fonctions d'envoi d'emails intégrées")
|
| 758 |
print("🤖 Intelligence Artificielle Modal pour prédictions")
|
| 759 |
+
print("🤖 Serveur MCP activé pour les outils IA")
|
| 760 |
|
| 761 |
+
# ✅ LANCEMENT GRADIO AVEC MCP SERVER
|
| 762 |
demo.launch(
|
| 763 |
server_name="0.0.0.0",
|
| 764 |
server_port=7860,
|
requirements.txt
CHANGED
|
@@ -2,7 +2,17 @@
|
|
| 2 |
# Dépendances minimales essentielles
|
| 3 |
|
| 4 |
# Interface utilisateur
|
| 5 |
-
gradio==5.33.0
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 6 |
|
| 7 |
# Configuration
|
| 8 |
python-dotenv>=1.0.0
|
|
|
|
| 2 |
# Dépendances minimales essentielles
|
| 3 |
|
| 4 |
# Interface utilisateur
|
| 5 |
+
gradio[mcp]==5.33.0
|
| 6 |
+
fastapi==0.115.6
|
| 7 |
+
uvicorn[standard]==0.32.1
|
| 8 |
+
requests==2.32.3
|
| 9 |
+
pandas==2.2.3
|
| 10 |
+
python-dotenv==1.0.1
|
| 11 |
+
pydantic==2.10.3
|
| 12 |
+
modal==0.69.1
|
| 13 |
+
xmlrpc
|
| 14 |
+
email-validator==2.2.0
|
| 15 |
+
mcp==1.9.0
|
| 16 |
|
| 17 |
# Configuration
|
| 18 |
python-dotenv>=1.0.0
|