Spaces:
Running
Running
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
|