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

Files changed (2) hide show
  1. app.py +121 -7
  2. 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
- crm_stats_btn.click(fn=get_crm_stats, inputs=[], outputs=crm_output, queue=True)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
567
  crm_info_btn.click(fn=get_crm_info, inputs=[], outputs=crm_output, queue=True)
568
- analyze_leads_btn.click(fn=analyze_leads, inputs=[leads_domain, leads_limit], outputs=crm_output, queue=True)
569
  monitor_crm_btn.click(fn=monitor_crm, inputs=[monitor_hours, monitor_threshold], outputs=crm_output, queue=True)
570
- search_leads_btn.click(fn=search_leads, inputs=[search_name, search_revenue, search_stage, search_limit], outputs=crm_output, queue=True)
571
 
572
  # ========================================================================
573
  # ÉVÉNEMENTS SALES
574
  # ========================================================================
575
 
576
- sales_stats_btn.click(fn=get_sales_stats, inputs=[], outputs=sales_output, queue=True)
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=get_modal_status, inputs=[], outputs=modal_output, queue=True)
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=search_records_simple, inputs=[model_input, domain_input, fields_input, limit_input], outputs=search_output, queue=True)
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