tooluniverse 0.2.0__py3-none-any.whl ā 1.0.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Potentially problematic release.
This version of tooluniverse might be problematic. Click here for more details.
- tooluniverse/__init__.py +340 -4
- tooluniverse/admetai_tool.py +84 -0
- tooluniverse/agentic_tool.py +563 -0
- tooluniverse/alphafold_tool.py +96 -0
- tooluniverse/base_tool.py +129 -6
- tooluniverse/boltz_tool.py +207 -0
- tooluniverse/chem_tool.py +192 -0
- tooluniverse/compose_scripts/__init__.py +1 -0
- tooluniverse/compose_scripts/biomarker_discovery.py +293 -0
- tooluniverse/compose_scripts/comprehensive_drug_discovery.py +186 -0
- tooluniverse/compose_scripts/drug_safety_analyzer.py +89 -0
- tooluniverse/compose_scripts/literature_tool.py +34 -0
- tooluniverse/compose_scripts/output_summarizer.py +279 -0
- tooluniverse/compose_scripts/tool_description_optimizer.py +681 -0
- tooluniverse/compose_scripts/tool_discover.py +705 -0
- tooluniverse/compose_scripts/tool_graph_composer.py +448 -0
- tooluniverse/compose_tool.py +371 -0
- tooluniverse/ctg_tool.py +1002 -0
- tooluniverse/custom_tool.py +81 -0
- tooluniverse/dailymed_tool.py +108 -0
- tooluniverse/data/admetai_tools.json +155 -0
- tooluniverse/data/agentic_tools.json +1156 -0
- tooluniverse/data/alphafold_tools.json +87 -0
- tooluniverse/data/boltz_tools.json +9 -0
- tooluniverse/data/chembl_tools.json +16 -0
- tooluniverse/data/clait_tools.json +108 -0
- tooluniverse/data/clinicaltrials_gov_tools.json +326 -0
- tooluniverse/data/compose_tools.json +202 -0
- tooluniverse/data/dailymed_tools.json +70 -0
- tooluniverse/data/dataset_tools.json +646 -0
- tooluniverse/data/disease_target_score_tools.json +712 -0
- tooluniverse/data/efo_tools.json +17 -0
- tooluniverse/data/embedding_tools.json +319 -0
- tooluniverse/data/enrichr_tools.json +31 -0
- tooluniverse/data/europe_pmc_tools.json +22 -0
- tooluniverse/data/expert_feedback_tools.json +10 -0
- tooluniverse/data/fda_drug_adverse_event_tools.json +491 -0
- tooluniverse/data/fda_drug_labeling_tools.json +1 -1
- tooluniverse/data/fda_drugs_with_brand_generic_names_for_tool.py +76929 -148860
- tooluniverse/data/finder_tools.json +209 -0
- tooluniverse/data/gene_ontology_tools.json +113 -0
- tooluniverse/data/gwas_tools.json +1082 -0
- tooluniverse/data/hpa_tools.json +333 -0
- tooluniverse/data/humanbase_tools.json +47 -0
- tooluniverse/data/idmap_tools.json +74 -0
- tooluniverse/data/mcp_client_tools_example.json +113 -0
- tooluniverse/data/mcpautoloadertool_defaults.json +28 -0
- tooluniverse/data/medlineplus_tools.json +141 -0
- tooluniverse/data/monarch_tools.json +1 -1
- tooluniverse/data/openalex_tools.json +36 -0
- tooluniverse/data/opentarget_tools.json +1 -1
- tooluniverse/data/output_summarization_tools.json +101 -0
- tooluniverse/data/packages/bioinformatics_core_tools.json +1756 -0
- tooluniverse/data/packages/categorized_tools.txt +206 -0
- tooluniverse/data/packages/cheminformatics_tools.json +347 -0
- tooluniverse/data/packages/earth_sciences_tools.json +74 -0
- tooluniverse/data/packages/genomics_tools.json +776 -0
- tooluniverse/data/packages/image_processing_tools.json +38 -0
- tooluniverse/data/packages/machine_learning_tools.json +789 -0
- tooluniverse/data/packages/neuroscience_tools.json +62 -0
- tooluniverse/data/packages/original_tools.txt +0 -0
- tooluniverse/data/packages/physics_astronomy_tools.json +62 -0
- tooluniverse/data/packages/scientific_computing_tools.json +560 -0
- tooluniverse/data/packages/single_cell_tools.json +453 -0
- tooluniverse/data/packages/software_tools.json +4954 -0
- tooluniverse/data/packages/structural_biology_tools.json +396 -0
- tooluniverse/data/packages/visualization_tools.json +399 -0
- tooluniverse/data/pubchem_tools.json +215 -0
- tooluniverse/data/pubtator_tools.json +68 -0
- tooluniverse/data/rcsb_pdb_tools.json +1332 -0
- tooluniverse/data/reactome_tools.json +19 -0
- tooluniverse/data/semantic_scholar_tools.json +26 -0
- tooluniverse/data/special_tools.json +2 -25
- tooluniverse/data/tool_composition_tools.json +88 -0
- tooluniverse/data/toolfinderkeyword_defaults.json +34 -0
- tooluniverse/data/txagent_client_tools.json +9 -0
- tooluniverse/data/uniprot_tools.json +211 -0
- tooluniverse/data/url_fetch_tools.json +94 -0
- tooluniverse/data/uspto_downloader_tools.json +9 -0
- tooluniverse/data/uspto_tools.json +811 -0
- tooluniverse/data/xml_tools.json +3275 -0
- tooluniverse/dataset_tool.py +296 -0
- tooluniverse/default_config.py +165 -0
- tooluniverse/efo_tool.py +42 -0
- tooluniverse/embedding_database.py +630 -0
- tooluniverse/embedding_sync.py +396 -0
- tooluniverse/enrichr_tool.py +266 -0
- tooluniverse/europe_pmc_tool.py +52 -0
- tooluniverse/execute_function.py +1775 -95
- tooluniverse/extended_hooks.py +444 -0
- tooluniverse/gene_ontology_tool.py +194 -0
- tooluniverse/graphql_tool.py +158 -36
- tooluniverse/gwas_tool.py +358 -0
- tooluniverse/hpa_tool.py +1645 -0
- tooluniverse/humanbase_tool.py +389 -0
- tooluniverse/logging_config.py +254 -0
- tooluniverse/mcp_client_tool.py +764 -0
- tooluniverse/mcp_integration.py +413 -0
- tooluniverse/mcp_tool_registry.py +925 -0
- tooluniverse/medlineplus_tool.py +337 -0
- tooluniverse/openalex_tool.py +228 -0
- tooluniverse/openfda_adv_tool.py +283 -0
- tooluniverse/openfda_tool.py +393 -160
- tooluniverse/output_hook.py +1122 -0
- tooluniverse/package_tool.py +195 -0
- tooluniverse/pubchem_tool.py +158 -0
- tooluniverse/pubtator_tool.py +168 -0
- tooluniverse/rcsb_pdb_tool.py +38 -0
- tooluniverse/reactome_tool.py +108 -0
- tooluniverse/remote/boltz/boltz_mcp_server.py +50 -0
- tooluniverse/remote/depmap_24q2/depmap_24q2_mcp_tool.py +442 -0
- tooluniverse/remote/expert_feedback/human_expert_mcp_tools.py +2013 -0
- tooluniverse/remote/expert_feedback/simple_test.py +23 -0
- tooluniverse/remote/expert_feedback/start_web_interface.py +188 -0
- tooluniverse/remote/expert_feedback/web_only_interface.py +0 -0
- tooluniverse/remote/expert_feedback_mcp/human_expert_mcp_server.py +1611 -0
- tooluniverse/remote/expert_feedback_mcp/simple_test.py +34 -0
- tooluniverse/remote/expert_feedback_mcp/start_web_interface.py +91 -0
- tooluniverse/remote/immune_compass/compass_tool.py +327 -0
- tooluniverse/remote/pinnacle/pinnacle_tool.py +328 -0
- tooluniverse/remote/transcriptformer/transcriptformer_tool.py +586 -0
- tooluniverse/remote/uspto_downloader/uspto_downloader_mcp_server.py +61 -0
- tooluniverse/remote/uspto_downloader/uspto_downloader_tool.py +120 -0
- tooluniverse/remote_tool.py +99 -0
- tooluniverse/restful_tool.py +53 -30
- tooluniverse/scripts/generate_tool_graph.py +408 -0
- tooluniverse/scripts/visualize_tool_graph.py +829 -0
- tooluniverse/semantic_scholar_tool.py +62 -0
- tooluniverse/smcp.py +2452 -0
- tooluniverse/smcp_server.py +975 -0
- tooluniverse/test/mcp_server_test.py +0 -0
- tooluniverse/test/test_admetai_tool.py +370 -0
- tooluniverse/test/test_agentic_tool.py +129 -0
- tooluniverse/test/test_alphafold_tool.py +71 -0
- tooluniverse/test/test_chem_tool.py +37 -0
- tooluniverse/test/test_compose_lieraturereview.py +63 -0
- tooluniverse/test/test_compose_tool.py +448 -0
- tooluniverse/test/test_dailymed.py +69 -0
- tooluniverse/test/test_dataset_tool.py +200 -0
- tooluniverse/test/test_disease_target_score.py +56 -0
- tooluniverse/test/test_drugbank_filter_examples.py +179 -0
- tooluniverse/test/test_efo.py +31 -0
- tooluniverse/test/test_enrichr_tool.py +21 -0
- tooluniverse/test/test_europe_pmc_tool.py +20 -0
- tooluniverse/test/test_fda_adv.py +95 -0
- tooluniverse/test/test_fda_drug_labeling.py +91 -0
- tooluniverse/test/test_gene_ontology_tools.py +66 -0
- tooluniverse/test/test_gwas_tool.py +139 -0
- tooluniverse/test/test_hpa.py +625 -0
- tooluniverse/test/test_humanbase_tool.py +20 -0
- tooluniverse/test/test_idmap_tools.py +61 -0
- tooluniverse/test/test_mcp_server.py +211 -0
- tooluniverse/test/test_mcp_tool.py +247 -0
- tooluniverse/test/test_medlineplus.py +220 -0
- tooluniverse/test/test_openalex_tool.py +32 -0
- tooluniverse/test/test_opentargets.py +28 -0
- tooluniverse/test/test_pubchem_tool.py +116 -0
- tooluniverse/test/test_pubtator_tool.py +37 -0
- tooluniverse/test/test_rcsb_pdb_tool.py +86 -0
- tooluniverse/test/test_reactome.py +54 -0
- tooluniverse/test/test_semantic_scholar_tool.py +24 -0
- tooluniverse/test/test_software_tools.py +147 -0
- tooluniverse/test/test_tool_description_optimizer.py +49 -0
- tooluniverse/test/test_tool_finder.py +26 -0
- tooluniverse/test/test_tool_finder_llm.py +252 -0
- tooluniverse/test/test_tools_find.py +195 -0
- tooluniverse/test/test_uniprot_tools.py +74 -0
- tooluniverse/test/test_uspto_tool.py +72 -0
- tooluniverse/test/test_xml_tool.py +113 -0
- tooluniverse/tool_finder_embedding.py +267 -0
- tooluniverse/tool_finder_keyword.py +693 -0
- tooluniverse/tool_finder_llm.py +699 -0
- tooluniverse/tool_graph_web_ui.py +955 -0
- tooluniverse/tool_registry.py +416 -0
- tooluniverse/uniprot_tool.py +155 -0
- tooluniverse/url_tool.py +253 -0
- tooluniverse/uspto_tool.py +240 -0
- tooluniverse/utils.py +369 -41
- tooluniverse/xml_tool.py +369 -0
- tooluniverse-1.0.0.dist-info/METADATA +377 -0
- tooluniverse-1.0.0.dist-info/RECORD +186 -0
- tooluniverse-1.0.0.dist-info/entry_points.txt +9 -0
- tooluniverse/generate_mcp_tools.py +0 -113
- tooluniverse/mcp_server.py +0 -3340
- tooluniverse-0.2.0.dist-info/METADATA +0 -139
- tooluniverse-0.2.0.dist-info/RECORD +0 -21
- tooluniverse-0.2.0.dist-info/entry_points.txt +0 -4
- {tooluniverse-0.2.0.dist-info ā tooluniverse-1.0.0.dist-info}/WHEEL +0 -0
- {tooluniverse-0.2.0.dist-info ā tooluniverse-1.0.0.dist-info}/licenses/LICENSE +0 -0
- {tooluniverse-0.2.0.dist-info ā tooluniverse-1.0.0.dist-info}/top_level.txt +0 -0
|
@@ -0,0 +1,2013 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
"""
|
|
3
|
+
Human Expert MCP Tools - Refactored with @register_mcp_tool
|
|
4
|
+
==========================================================
|
|
5
|
+
|
|
6
|
+
This module contains human expert consultation tools that have been refactored
|
|
7
|
+
to use the new @register_mcp_tool decorator system instead of FastMCP.
|
|
8
|
+
|
|
9
|
+
Tools available:
|
|
10
|
+
- consult_human_expert: Submit questions to human scientific experts
|
|
11
|
+
- get_expert_response: Check for expert responses
|
|
12
|
+
- list_pending_expert_requests: View pending requests (for experts)
|
|
13
|
+
- submit_expert_response: Submit expert responses (for experts)
|
|
14
|
+
- get_expert_status: Get system status
|
|
15
|
+
|
|
16
|
+
Usage:
|
|
17
|
+
from tooluniverse.mcp_tool_registry import start_mcp_server
|
|
18
|
+
# Tools are automatically registered and available on startup
|
|
19
|
+
"""
|
|
20
|
+
|
|
21
|
+
import threading
|
|
22
|
+
import time
|
|
23
|
+
import uuid
|
|
24
|
+
import argparse
|
|
25
|
+
import sys
|
|
26
|
+
import webbrowser
|
|
27
|
+
from concurrent.futures import ThreadPoolExecutor
|
|
28
|
+
from datetime import datetime
|
|
29
|
+
from threading import Timer
|
|
30
|
+
from typing import Dict, List, Optional, Any
|
|
31
|
+
|
|
32
|
+
# Import the new registration system
|
|
33
|
+
from tooluniverse.mcp_tool_registry import register_mcp_tool, start_mcp_server
|
|
34
|
+
import requests
|
|
35
|
+
|
|
36
|
+
# Check Flask availability for web interface
|
|
37
|
+
try:
|
|
38
|
+
from flask import Flask, render_template_string, request, jsonify, redirect, url_for
|
|
39
|
+
|
|
40
|
+
FLASK_AVAILABLE = True
|
|
41
|
+
except ImportError:
|
|
42
|
+
FLASK_AVAILABLE = False
|
|
43
|
+
|
|
44
|
+
# =============================================================================
|
|
45
|
+
# HUMAN EXPERT SYSTEM (Same as original)
|
|
46
|
+
# =============================================================================
|
|
47
|
+
|
|
48
|
+
|
|
49
|
+
class HumanExpertSystem:
|
|
50
|
+
"""
|
|
51
|
+
Expert consultation system for human-in-the-loop scientific decision support.
|
|
52
|
+
|
|
53
|
+
This system manages the flow of expert consultation requests:
|
|
54
|
+
1. Receives consultation requests from tools/agents
|
|
55
|
+
2. Queues requests for human expert review
|
|
56
|
+
3. Provides interfaces for experts to respond
|
|
57
|
+
4. Returns expert responses to requesting tools
|
|
58
|
+
"""
|
|
59
|
+
|
|
60
|
+
def __init__(
|
|
61
|
+
self,
|
|
62
|
+
expert_name: str = "Scientific Expert",
|
|
63
|
+
specialty: str = "General Medicine",
|
|
64
|
+
):
|
|
65
|
+
"""Initialize the expert system"""
|
|
66
|
+
|
|
67
|
+
# Expert information
|
|
68
|
+
self.expert_info = {
|
|
69
|
+
"name": expert_name,
|
|
70
|
+
"specialty": specialty,
|
|
71
|
+
"status": "available",
|
|
72
|
+
"last_activity": datetime.now().isoformat(),
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
# Request management
|
|
76
|
+
self.pending_requests: List[Dict] = [] # Requests waiting for expert response
|
|
77
|
+
self.responses: Dict[str, Dict] = {} # Completed expert responses
|
|
78
|
+
self.request_status: Dict[str, str] = {} # Request status tracking
|
|
79
|
+
|
|
80
|
+
# Thread safety
|
|
81
|
+
self.lock = threading.Lock()
|
|
82
|
+
|
|
83
|
+
print("š§āāļø Human Expert System Initialized")
|
|
84
|
+
print(f" šØāāļø Expert: {expert_name} ({specialty})")
|
|
85
|
+
print(" š Status: Ready for consultation requests")
|
|
86
|
+
|
|
87
|
+
def submit_request(
|
|
88
|
+
self, request_id: str, question: str, context: Optional[Dict] = None
|
|
89
|
+
) -> None:
|
|
90
|
+
"""Submit a new expert consultation request"""
|
|
91
|
+
|
|
92
|
+
request_data = {
|
|
93
|
+
"id": request_id,
|
|
94
|
+
"question": question,
|
|
95
|
+
"context": context or {},
|
|
96
|
+
"timestamp": datetime.now().isoformat(),
|
|
97
|
+
"status": "pending",
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
with self.lock:
|
|
101
|
+
self.pending_requests.append(request_data)
|
|
102
|
+
self.request_status[request_id] = "pending"
|
|
103
|
+
|
|
104
|
+
print(f"š New expert request submitted: {request_id}")
|
|
105
|
+
print(f" ā Question: {question[:100]}{'...' if len(question) > 100 else ''}")
|
|
106
|
+
|
|
107
|
+
if context:
|
|
108
|
+
print(f" šÆ Specialty: {context.get('specialty', 'general')}")
|
|
109
|
+
print(f" ā” Priority: {context.get('priority', 'normal')}")
|
|
110
|
+
|
|
111
|
+
def get_pending_requests(self) -> List[Dict]:
|
|
112
|
+
"""Get all pending expert consultation requests"""
|
|
113
|
+
with self.lock:
|
|
114
|
+
return self.pending_requests.copy()
|
|
115
|
+
|
|
116
|
+
def submit_response(self, request_id: str, response: str) -> bool:
|
|
117
|
+
"""Submit expert response to a consultation request"""
|
|
118
|
+
|
|
119
|
+
with self.lock:
|
|
120
|
+
# Find and remove the request from pending
|
|
121
|
+
for i, req in enumerate(self.pending_requests):
|
|
122
|
+
if req["id"] == request_id:
|
|
123
|
+
request_data = self.pending_requests.pop(i)
|
|
124
|
+
|
|
125
|
+
# Store the response
|
|
126
|
+
self.responses[request_id] = {
|
|
127
|
+
"request": request_data,
|
|
128
|
+
"response": response,
|
|
129
|
+
"expert": self.expert_info["name"],
|
|
130
|
+
"timestamp": datetime.now().isoformat(),
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
# Update status
|
|
134
|
+
self.request_status[request_id] = "completed"
|
|
135
|
+
self.expert_info["last_activity"] = datetime.now().isoformat()
|
|
136
|
+
|
|
137
|
+
print(f"ā
Expert response submitted for request: {request_id}")
|
|
138
|
+
return True
|
|
139
|
+
|
|
140
|
+
return False
|
|
141
|
+
|
|
142
|
+
def get_response(
|
|
143
|
+
self, request_id: str, timeout_seconds: int = 300
|
|
144
|
+
) -> Optional[Dict]:
|
|
145
|
+
"""Wait for and retrieve expert response"""
|
|
146
|
+
|
|
147
|
+
start_time = time.time()
|
|
148
|
+
|
|
149
|
+
while time.time() - start_time < timeout_seconds:
|
|
150
|
+
with self.lock:
|
|
151
|
+
if request_id in self.responses:
|
|
152
|
+
return self.responses[request_id]
|
|
153
|
+
|
|
154
|
+
time.sleep(1) # Check every second
|
|
155
|
+
|
|
156
|
+
return None # Timeout
|
|
157
|
+
|
|
158
|
+
|
|
159
|
+
# Initialize global expert system
|
|
160
|
+
expert_system = HumanExpertSystem()
|
|
161
|
+
|
|
162
|
+
# Thread executor for async operations
|
|
163
|
+
executor = ThreadPoolExecutor(max_workers=4)
|
|
164
|
+
|
|
165
|
+
# =============================================================================
|
|
166
|
+
# š§° MCP TOOL REGISTRATION (Following tutorial standards)
|
|
167
|
+
# =============================================================================
|
|
168
|
+
|
|
169
|
+
|
|
170
|
+
# Register Human Expert Consultation Tool
|
|
171
|
+
@register_mcp_tool(
|
|
172
|
+
tool_type_name="consult_human_expert",
|
|
173
|
+
config={
|
|
174
|
+
"description": "Consult a human expert for complex scientific questions requiring human judgment",
|
|
175
|
+
"parameter_schema": {
|
|
176
|
+
"type": "object",
|
|
177
|
+
"properties": {
|
|
178
|
+
"question": {
|
|
179
|
+
"type": "string",
|
|
180
|
+
"description": "The scientific question or case requiring expert consultation",
|
|
181
|
+
},
|
|
182
|
+
"specialty": {
|
|
183
|
+
"type": "string",
|
|
184
|
+
"default": "general",
|
|
185
|
+
"description": "Area of expertise needed (e.g., 'cardiology', 'oncology', 'pharmacology')",
|
|
186
|
+
},
|
|
187
|
+
"priority": {
|
|
188
|
+
"type": "string",
|
|
189
|
+
"default": "normal",
|
|
190
|
+
"enum": ["low", "normal", "high", "urgent"],
|
|
191
|
+
"description": "Request priority",
|
|
192
|
+
},
|
|
193
|
+
"context": {
|
|
194
|
+
"type": "string",
|
|
195
|
+
"default": "",
|
|
196
|
+
"description": "Additional context or background information",
|
|
197
|
+
},
|
|
198
|
+
"timeout_minutes": {
|
|
199
|
+
"type": "integer",
|
|
200
|
+
"default": 5,
|
|
201
|
+
"description": "How long to wait for expert response (default: 5 minutes)",
|
|
202
|
+
},
|
|
203
|
+
},
|
|
204
|
+
"required": ["question"],
|
|
205
|
+
},
|
|
206
|
+
},
|
|
207
|
+
mcp_config={
|
|
208
|
+
"server_name": "Human Expert Consultation Server",
|
|
209
|
+
"host": "0.0.0.0",
|
|
210
|
+
"port": 9876,
|
|
211
|
+
},
|
|
212
|
+
)
|
|
213
|
+
class ConsultHumanExpertTool:
|
|
214
|
+
"""
|
|
215
|
+
Consult a human expert for complex scientific questions requiring human judgment.
|
|
216
|
+
|
|
217
|
+
This tool submits questions to human scientific experts who can provide:
|
|
218
|
+
- Clinical decision support
|
|
219
|
+
- Drug interaction analysis validation
|
|
220
|
+
- Treatment recommendation review
|
|
221
|
+
- Complex case interpretation
|
|
222
|
+
- Quality assurance for AI recommendations
|
|
223
|
+
"""
|
|
224
|
+
|
|
225
|
+
def run(self, arguments: Dict[str, Any]) -> Dict[str, Any]:
|
|
226
|
+
"""Execute human expert consultation"""
|
|
227
|
+
|
|
228
|
+
# Extract parameters
|
|
229
|
+
question_any = arguments.get("question")
|
|
230
|
+
if not isinstance(question_any, str) or not question_any:
|
|
231
|
+
return {"status": "error", "error": "'question' must be a non-empty string"}
|
|
232
|
+
question = question_any
|
|
233
|
+
specialty = arguments.get("specialty", "general")
|
|
234
|
+
priority = arguments.get("priority", "normal")
|
|
235
|
+
context = arguments.get("context", "")
|
|
236
|
+
timeout_minutes = arguments.get("timeout_minutes", 5)
|
|
237
|
+
|
|
238
|
+
request_id = str(uuid.uuid4())[:8]
|
|
239
|
+
timeout_seconds = timeout_minutes * 60
|
|
240
|
+
|
|
241
|
+
print(f"\nš EXPERT CONSULTATION REQUEST [{request_id}]")
|
|
242
|
+
print(f"šÆ Specialty: {specialty}")
|
|
243
|
+
print(f"ā” Priority: {priority}")
|
|
244
|
+
print(f"ā±ļø Timeout: {timeout_minutes} minutes")
|
|
245
|
+
|
|
246
|
+
try:
|
|
247
|
+
# Submit request to expert system
|
|
248
|
+
context_data = {
|
|
249
|
+
"specialty": specialty,
|
|
250
|
+
"priority": priority,
|
|
251
|
+
"context": context,
|
|
252
|
+
}
|
|
253
|
+
|
|
254
|
+
expert_system.submit_request(request_id, question, context_data)
|
|
255
|
+
|
|
256
|
+
# Wait for expert response
|
|
257
|
+
print(f"ā³ Waiting for expert response (max {timeout_minutes} minutes)...")
|
|
258
|
+
|
|
259
|
+
response_data = expert_system.get_response(request_id, timeout_seconds)
|
|
260
|
+
|
|
261
|
+
if response_data:
|
|
262
|
+
return {
|
|
263
|
+
"status": "completed",
|
|
264
|
+
"expert_response": response_data["response"],
|
|
265
|
+
"expert_name": response_data["expert"],
|
|
266
|
+
"response_time": response_data["timestamp"],
|
|
267
|
+
"request_id": request_id,
|
|
268
|
+
"specialty": specialty,
|
|
269
|
+
"priority": priority,
|
|
270
|
+
}
|
|
271
|
+
else:
|
|
272
|
+
return {
|
|
273
|
+
"status": "timeout",
|
|
274
|
+
"message": f"No expert response received within {timeout_minutes} minutes",
|
|
275
|
+
"request_id": request_id,
|
|
276
|
+
"note": "Request may still be processed. Check with get_expert_response tool later.",
|
|
277
|
+
}
|
|
278
|
+
|
|
279
|
+
except Exception as e:
|
|
280
|
+
print(f"ā Expert consultation failed: {str(e)}")
|
|
281
|
+
return {
|
|
282
|
+
"status": "error",
|
|
283
|
+
"error": f"Expert consultation failed: {str(e)}",
|
|
284
|
+
"request_id": request_id,
|
|
285
|
+
}
|
|
286
|
+
|
|
287
|
+
|
|
288
|
+
# Register Get Expert Response Tool
|
|
289
|
+
@register_mcp_tool(
|
|
290
|
+
tool_type_name="get_expert_response",
|
|
291
|
+
config={
|
|
292
|
+
"description": "Check if an expert response is available for a previous request",
|
|
293
|
+
"parameter_schema": {
|
|
294
|
+
"type": "object",
|
|
295
|
+
"properties": {
|
|
296
|
+
"request_id": {
|
|
297
|
+
"type": "string",
|
|
298
|
+
"description": "The ID of the expert consultation request",
|
|
299
|
+
}
|
|
300
|
+
},
|
|
301
|
+
"required": ["request_id"],
|
|
302
|
+
},
|
|
303
|
+
},
|
|
304
|
+
mcp_config={"port": 9876}, # Same server as consultation tool
|
|
305
|
+
)
|
|
306
|
+
class GetExpertResponseTool:
|
|
307
|
+
"""Tool to check if an expert response is available for a previous request."""
|
|
308
|
+
|
|
309
|
+
def run(self, arguments: Dict[str, Any]) -> Dict[str, Any]:
|
|
310
|
+
"""Check for expert response"""
|
|
311
|
+
|
|
312
|
+
request_id = arguments.get("request_id")
|
|
313
|
+
|
|
314
|
+
try:
|
|
315
|
+
with expert_system.lock:
|
|
316
|
+
if request_id in expert_system.responses:
|
|
317
|
+
response_data = expert_system.responses[request_id]
|
|
318
|
+
return {
|
|
319
|
+
"status": "completed",
|
|
320
|
+
"expert_response": response_data["response"],
|
|
321
|
+
"expert_name": response_data["expert"],
|
|
322
|
+
"response_time": response_data["timestamp"],
|
|
323
|
+
"request_id": request_id,
|
|
324
|
+
}
|
|
325
|
+
elif request_id in expert_system.request_status:
|
|
326
|
+
status = expert_system.request_status[request_id]
|
|
327
|
+
return {
|
|
328
|
+
"status": status,
|
|
329
|
+
"message": f"Request {request_id} is {status}",
|
|
330
|
+
"request_id": request_id,
|
|
331
|
+
}
|
|
332
|
+
else:
|
|
333
|
+
return {
|
|
334
|
+
"status": "not_found",
|
|
335
|
+
"message": f"Request {request_id} not found",
|
|
336
|
+
"request_id": request_id,
|
|
337
|
+
}
|
|
338
|
+
|
|
339
|
+
except Exception as e:
|
|
340
|
+
return {
|
|
341
|
+
"status": "error",
|
|
342
|
+
"error": f"Failed to check expert response: {str(e)}",
|
|
343
|
+
"request_id": request_id,
|
|
344
|
+
}
|
|
345
|
+
|
|
346
|
+
|
|
347
|
+
# Register List Pending Expert Requests Tool
|
|
348
|
+
@register_mcp_tool(
|
|
349
|
+
tool_type_name="list_pending_expert_requests",
|
|
350
|
+
config={
|
|
351
|
+
"description": "List all pending expert consultation requests (for expert use)",
|
|
352
|
+
"parameter_schema": {"type": "object", "properties": {}, "required": []},
|
|
353
|
+
},
|
|
354
|
+
mcp_config={"port": 9876}, # Same server
|
|
355
|
+
)
|
|
356
|
+
class ListPendingExpertRequestsTool:
|
|
357
|
+
"""Tool to list all pending expert consultation requests (for expert use)."""
|
|
358
|
+
|
|
359
|
+
def run(self, arguments: Dict[str, Any]) -> Dict[str, Any]:
|
|
360
|
+
"""List pending expert requests"""
|
|
361
|
+
|
|
362
|
+
try:
|
|
363
|
+
pending = expert_system.get_pending_requests()
|
|
364
|
+
|
|
365
|
+
if not pending:
|
|
366
|
+
return {
|
|
367
|
+
"status": "no_requests",
|
|
368
|
+
"message": "No pending expert requests",
|
|
369
|
+
"count": 0,
|
|
370
|
+
}
|
|
371
|
+
|
|
372
|
+
requests_summary = []
|
|
373
|
+
for req in pending:
|
|
374
|
+
age_seconds = (
|
|
375
|
+
datetime.now() - datetime.fromisoformat(req["timestamp"])
|
|
376
|
+
).total_seconds()
|
|
377
|
+
requests_summary.append(
|
|
378
|
+
{
|
|
379
|
+
"request_id": req["id"],
|
|
380
|
+
"question": req["question"],
|
|
381
|
+
"specialty": req.get("context", {}).get("specialty", "general"),
|
|
382
|
+
"priority": req.get("context", {}).get("priority", "normal"),
|
|
383
|
+
"age_minutes": round(age_seconds / 60, 1),
|
|
384
|
+
"timestamp": req["timestamp"],
|
|
385
|
+
}
|
|
386
|
+
)
|
|
387
|
+
|
|
388
|
+
return {
|
|
389
|
+
"status": "success",
|
|
390
|
+
"pending_requests": requests_summary,
|
|
391
|
+
"count": len(requests_summary),
|
|
392
|
+
"expert_info": expert_system.expert_info,
|
|
393
|
+
}
|
|
394
|
+
|
|
395
|
+
except Exception as e:
|
|
396
|
+
return {
|
|
397
|
+
"status": "error",
|
|
398
|
+
"error": f"Failed to list pending requests: {str(e)}",
|
|
399
|
+
}
|
|
400
|
+
|
|
401
|
+
|
|
402
|
+
# Register Submit Expert Response Tool
|
|
403
|
+
@register_mcp_tool(
|
|
404
|
+
tool_type_name="submit_expert_response",
|
|
405
|
+
config={
|
|
406
|
+
"description": "Submit expert response to a consultation request (for expert use)",
|
|
407
|
+
"parameter_schema": {
|
|
408
|
+
"type": "object",
|
|
409
|
+
"properties": {
|
|
410
|
+
"request_id": {
|
|
411
|
+
"type": "string",
|
|
412
|
+
"description": "The ID of the request to respond to",
|
|
413
|
+
},
|
|
414
|
+
"response": {
|
|
415
|
+
"type": "string",
|
|
416
|
+
"description": "The expert's response and recommendations",
|
|
417
|
+
},
|
|
418
|
+
},
|
|
419
|
+
"required": ["request_id", "response"],
|
|
420
|
+
},
|
|
421
|
+
},
|
|
422
|
+
mcp_config={"port": 9876}, # Same server
|
|
423
|
+
)
|
|
424
|
+
class SubmitExpertResponseTool:
|
|
425
|
+
"""Tool to submit expert response to a consultation request (for expert use)."""
|
|
426
|
+
|
|
427
|
+
def run(self, arguments: Dict[str, Any]) -> Dict[str, Any]:
|
|
428
|
+
"""Submit expert response"""
|
|
429
|
+
|
|
430
|
+
request_id_any = arguments.get("request_id")
|
|
431
|
+
response_any = arguments.get("response")
|
|
432
|
+
if not isinstance(request_id_any, str) or not isinstance(response_any, str):
|
|
433
|
+
return {
|
|
434
|
+
"status": "error",
|
|
435
|
+
"error": "'request_id' and 'response' must be strings",
|
|
436
|
+
}
|
|
437
|
+
request_id = request_id_any
|
|
438
|
+
response = response_any
|
|
439
|
+
|
|
440
|
+
try:
|
|
441
|
+
success = expert_system.submit_response(request_id, response)
|
|
442
|
+
|
|
443
|
+
if success:
|
|
444
|
+
return {
|
|
445
|
+
"status": "success",
|
|
446
|
+
"message": f"Expert response submitted for request {request_id}",
|
|
447
|
+
"request_id": request_id,
|
|
448
|
+
"expert": expert_system.expert_info["name"],
|
|
449
|
+
"timestamp": datetime.now().isoformat(),
|
|
450
|
+
}
|
|
451
|
+
else:
|
|
452
|
+
return {
|
|
453
|
+
"status": "failed",
|
|
454
|
+
"message": f"Request {request_id} not found or already completed",
|
|
455
|
+
"request_id": request_id,
|
|
456
|
+
}
|
|
457
|
+
|
|
458
|
+
except Exception as e:
|
|
459
|
+
return {
|
|
460
|
+
"status": "error",
|
|
461
|
+
"error": f"Failed to submit expert response: {str(e)}",
|
|
462
|
+
"request_id": request_id,
|
|
463
|
+
}
|
|
464
|
+
|
|
465
|
+
|
|
466
|
+
# Register Get Expert Status Tool
|
|
467
|
+
@register_mcp_tool(
|
|
468
|
+
tool_type_name="get_expert_status",
|
|
469
|
+
config={
|
|
470
|
+
"description": "Get current expert system status and statistics",
|
|
471
|
+
"parameter_schema": {"type": "object", "properties": {}, "required": []},
|
|
472
|
+
},
|
|
473
|
+
mcp_config={"port": 9876}, # Same server
|
|
474
|
+
)
|
|
475
|
+
class GetExpertStatusTool:
|
|
476
|
+
"""Tool to get current expert system status and statistics."""
|
|
477
|
+
|
|
478
|
+
def run(self, arguments: Dict[str, Any]) -> Dict[str, Any]:
|
|
479
|
+
"""Get expert system status"""
|
|
480
|
+
|
|
481
|
+
try:
|
|
482
|
+
pending = expert_system.get_pending_requests()
|
|
483
|
+
|
|
484
|
+
with expert_system.lock:
|
|
485
|
+
total_responses = len(expert_system.responses)
|
|
486
|
+
total_requests = len(expert_system.request_status)
|
|
487
|
+
|
|
488
|
+
return {
|
|
489
|
+
"status": "active",
|
|
490
|
+
"expert_info": expert_system.expert_info,
|
|
491
|
+
"statistics": {
|
|
492
|
+
"pending_requests": len(pending),
|
|
493
|
+
"total_requests": total_requests,
|
|
494
|
+
"completed_responses": total_responses,
|
|
495
|
+
"response_rate": round(
|
|
496
|
+
total_responses / max(total_requests, 1) * 100, 1
|
|
497
|
+
),
|
|
498
|
+
},
|
|
499
|
+
"system_time": datetime.now().isoformat(),
|
|
500
|
+
"mcp_server_port": 9876,
|
|
501
|
+
}
|
|
502
|
+
|
|
503
|
+
except Exception as e:
|
|
504
|
+
return {
|
|
505
|
+
"status": "error",
|
|
506
|
+
"error": f"Failed to get expert status: {str(e)}",
|
|
507
|
+
}
|
|
508
|
+
|
|
509
|
+
|
|
510
|
+
# =============================================================================
|
|
511
|
+
# š HTTP API SERVER (Independent from Web Interface)
|
|
512
|
+
# =============================================================================
|
|
513
|
+
|
|
514
|
+
|
|
515
|
+
def create_http_api_server():
|
|
516
|
+
"""Create independent HTTP API server for expert system communication"""
|
|
517
|
+
if not FLASK_AVAILABLE:
|
|
518
|
+
return None
|
|
519
|
+
|
|
520
|
+
api_app = Flask(__name__)
|
|
521
|
+
|
|
522
|
+
@api_app.route("/health", methods=["GET"])
|
|
523
|
+
def health_check():
|
|
524
|
+
"""Health check endpoint"""
|
|
525
|
+
return {
|
|
526
|
+
"status": "healthy",
|
|
527
|
+
"timestamp": datetime.now().isoformat(),
|
|
528
|
+
"expert_system": "active",
|
|
529
|
+
}
|
|
530
|
+
|
|
531
|
+
@api_app.route("/api/requests", methods=["GET"])
|
|
532
|
+
def get_pending_requests():
|
|
533
|
+
"""Get all pending expert requests via HTTP API"""
|
|
534
|
+
try:
|
|
535
|
+
pending = expert_system.get_pending_requests()
|
|
536
|
+
|
|
537
|
+
requests_summary = []
|
|
538
|
+
for req in pending:
|
|
539
|
+
age_seconds = (
|
|
540
|
+
datetime.now() - datetime.fromisoformat(req["timestamp"])
|
|
541
|
+
).total_seconds()
|
|
542
|
+
requests_summary.append(
|
|
543
|
+
{
|
|
544
|
+
"request_id": req["id"],
|
|
545
|
+
"question": req["question"],
|
|
546
|
+
"specialty": req.get("context", {}).get("specialty", "general"),
|
|
547
|
+
"priority": req.get("context", {}).get("priority", "normal"),
|
|
548
|
+
"age_minutes": round(age_seconds / 60, 1),
|
|
549
|
+
"timestamp": req["timestamp"],
|
|
550
|
+
"context": req.get("context", {}),
|
|
551
|
+
}
|
|
552
|
+
)
|
|
553
|
+
|
|
554
|
+
with expert_system.lock:
|
|
555
|
+
total_responses = len(expert_system.responses)
|
|
556
|
+
total_requests = len(expert_system.request_status)
|
|
557
|
+
|
|
558
|
+
return {
|
|
559
|
+
"status": "success",
|
|
560
|
+
"pending_requests": requests_summary,
|
|
561
|
+
"count": len(requests_summary),
|
|
562
|
+
"expert_info": expert_system.expert_info,
|
|
563
|
+
"statistics": {
|
|
564
|
+
"pending_requests": len(pending),
|
|
565
|
+
"total_requests": total_requests,
|
|
566
|
+
"completed_responses": total_responses,
|
|
567
|
+
"response_rate": round(
|
|
568
|
+
total_responses / max(total_requests, 1) * 100, 1
|
|
569
|
+
),
|
|
570
|
+
},
|
|
571
|
+
"timestamp": datetime.now().isoformat(),
|
|
572
|
+
}
|
|
573
|
+
|
|
574
|
+
except Exception as e:
|
|
575
|
+
return {"status": "error", "error": str(e)}, 500
|
|
576
|
+
|
|
577
|
+
@api_app.route("/api/requests/<request_id>/respond", methods=["POST"])
|
|
578
|
+
def submit_expert_response_api(request_id):
|
|
579
|
+
"""Submit expert response via HTTP API"""
|
|
580
|
+
try:
|
|
581
|
+
data = request.get_json()
|
|
582
|
+
response_text = data.get("response", "").strip()
|
|
583
|
+
|
|
584
|
+
if not response_text:
|
|
585
|
+
return {"status": "error", "error": "Response text is required"}, 400
|
|
586
|
+
|
|
587
|
+
success = expert_system.submit_response(request_id, response_text)
|
|
588
|
+
|
|
589
|
+
if success:
|
|
590
|
+
return {
|
|
591
|
+
"status": "success",
|
|
592
|
+
"message": f"Expert response submitted for request {request_id}",
|
|
593
|
+
"request_id": request_id,
|
|
594
|
+
"expert": expert_system.expert_info["name"],
|
|
595
|
+
"timestamp": datetime.now().isoformat(),
|
|
596
|
+
}
|
|
597
|
+
else:
|
|
598
|
+
return {
|
|
599
|
+
"status": "failed",
|
|
600
|
+
"error": f"Request {request_id} not found or already completed",
|
|
601
|
+
}, 404
|
|
602
|
+
|
|
603
|
+
except Exception as e:
|
|
604
|
+
return {"status": "error", "error": str(e)}, 500
|
|
605
|
+
|
|
606
|
+
@api_app.route("/api/status", methods=["GET"])
|
|
607
|
+
def get_system_status():
|
|
608
|
+
"""Get expert system status via HTTP API"""
|
|
609
|
+
try:
|
|
610
|
+
pending = expert_system.get_pending_requests()
|
|
611
|
+
|
|
612
|
+
with expert_system.lock:
|
|
613
|
+
total_responses = len(expert_system.responses)
|
|
614
|
+
total_requests = len(expert_system.request_status)
|
|
615
|
+
|
|
616
|
+
return {
|
|
617
|
+
"status": "active",
|
|
618
|
+
"expert_info": expert_system.expert_info,
|
|
619
|
+
"statistics": {
|
|
620
|
+
"pending_requests": len(pending),
|
|
621
|
+
"total_requests": total_requests,
|
|
622
|
+
"completed_responses": total_responses,
|
|
623
|
+
"response_rate": round(
|
|
624
|
+
total_responses / max(total_requests, 1) * 100, 1
|
|
625
|
+
),
|
|
626
|
+
},
|
|
627
|
+
"system_time": datetime.now().isoformat(),
|
|
628
|
+
"mcp_server_port": 9876,
|
|
629
|
+
"api_server_port": 9877,
|
|
630
|
+
}
|
|
631
|
+
|
|
632
|
+
except Exception as e:
|
|
633
|
+
return {"status": "error", "error": str(e)}, 500
|
|
634
|
+
|
|
635
|
+
return api_app
|
|
636
|
+
|
|
637
|
+
|
|
638
|
+
# =============================================================================
|
|
639
|
+
# š WEB INTERFACE (Modified to use HTTP API)
|
|
640
|
+
# =============================================================================
|
|
641
|
+
|
|
642
|
+
|
|
643
|
+
def create_web_app():
|
|
644
|
+
"""Create Flask web application for expert interface"""
|
|
645
|
+
if not FLASK_AVAILABLE:
|
|
646
|
+
return None
|
|
647
|
+
|
|
648
|
+
app = Flask(__name__)
|
|
649
|
+
|
|
650
|
+
# Web interface HTML template with modern UI improvements
|
|
651
|
+
WEB_TEMPLATE = """
|
|
652
|
+
<!DOCTYPE html>
|
|
653
|
+
<html lang="en">
|
|
654
|
+
<head>
|
|
655
|
+
<title>š§āāļø ToolUniverse Human Expert Interface</title>
|
|
656
|
+
<meta name="viewport" content="width=device-width, initial-scale=1">
|
|
657
|
+
<meta charset="UTF-8">
|
|
658
|
+
<link href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.4.0/css/all.min.css" rel="stylesheet">
|
|
659
|
+
<style>
|
|
660
|
+
* { margin: 0; padding: 0; box-sizing: border-box; }
|
|
661
|
+
|
|
662
|
+
body {
|
|
663
|
+
font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
|
|
664
|
+
line-height: 1.6;
|
|
665
|
+
color: #333;
|
|
666
|
+
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
|
|
667
|
+
min-height: 100vh;
|
|
668
|
+
padding: 20px;
|
|
669
|
+
}
|
|
670
|
+
|
|
671
|
+
.container {
|
|
672
|
+
max-width: 1400px;
|
|
673
|
+
margin: 0 auto;
|
|
674
|
+
background: white;
|
|
675
|
+
border-radius: 20px;
|
|
676
|
+
box-shadow: 0 20px 40px rgba(0,0,0,0.1);
|
|
677
|
+
overflow: hidden;
|
|
678
|
+
animation: slideUp 0.8s ease-out;
|
|
679
|
+
}
|
|
680
|
+
|
|
681
|
+
@keyframes slideUp {
|
|
682
|
+
from { transform: translateY(30px); opacity: 0; }
|
|
683
|
+
to { transform: translateY(0); opacity: 1; }
|
|
684
|
+
}
|
|
685
|
+
|
|
686
|
+
.header {
|
|
687
|
+
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
|
|
688
|
+
color: white;
|
|
689
|
+
padding: 30px;
|
|
690
|
+
text-align: center;
|
|
691
|
+
position: relative;
|
|
692
|
+
overflow: hidden;
|
|
693
|
+
}
|
|
694
|
+
|
|
695
|
+
.header::before {
|
|
696
|
+
content: '';
|
|
697
|
+
position: absolute;
|
|
698
|
+
top: -50%;
|
|
699
|
+
left: -50%;
|
|
700
|
+
width: 200%;
|
|
701
|
+
height: 200%;
|
|
702
|
+
background: radial-gradient(circle, rgba(255,255,255,0.1) 0%, transparent 70%);
|
|
703
|
+
animation: pulse 4s ease-in-out infinite;
|
|
704
|
+
}
|
|
705
|
+
|
|
706
|
+
@keyframes pulse {
|
|
707
|
+
0%, 100% { transform: scale(1); opacity: 0.5; }
|
|
708
|
+
50% { transform: scale(1.1); opacity: 0.8; }
|
|
709
|
+
}
|
|
710
|
+
|
|
711
|
+
.header h1 {
|
|
712
|
+
font-size: 2.5em;
|
|
713
|
+
margin-bottom: 10px;
|
|
714
|
+
text-shadow: 2px 2px 4px rgba(0,0,0,0.3);
|
|
715
|
+
position: relative;
|
|
716
|
+
z-index: 1;
|
|
717
|
+
}
|
|
718
|
+
|
|
719
|
+
.header p {
|
|
720
|
+
font-size: 1.2em;
|
|
721
|
+
opacity: 0.9;
|
|
722
|
+
position: relative;
|
|
723
|
+
z-index: 1;
|
|
724
|
+
}
|
|
725
|
+
|
|
726
|
+
.status-bar {
|
|
727
|
+
background: #f8f9fa;
|
|
728
|
+
padding: 15px 30px;
|
|
729
|
+
border-bottom: 1px solid #e9ecef;
|
|
730
|
+
display: flex;
|
|
731
|
+
justify-content: space-between;
|
|
732
|
+
align-items: center;
|
|
733
|
+
flex-wrap: wrap;
|
|
734
|
+
gap: 15px;
|
|
735
|
+
}
|
|
736
|
+
|
|
737
|
+
.status-indicator {
|
|
738
|
+
display: flex;
|
|
739
|
+
align-items: center;
|
|
740
|
+
gap: 10px;
|
|
741
|
+
padding: 8px 15px;
|
|
742
|
+
background: white;
|
|
743
|
+
border-radius: 25px;
|
|
744
|
+
box-shadow: 0 2px 8px rgba(0,0,0,0.1);
|
|
745
|
+
transition: transform 0.2s;
|
|
746
|
+
}
|
|
747
|
+
|
|
748
|
+
.status-indicator:hover {
|
|
749
|
+
transform: translateY(-2px);
|
|
750
|
+
}
|
|
751
|
+
|
|
752
|
+
.status-dot {
|
|
753
|
+
width: 12px;
|
|
754
|
+
height: 12px;
|
|
755
|
+
border-radius: 50%;
|
|
756
|
+
background: #28a745;
|
|
757
|
+
animation: heartbeat 2s ease-in-out infinite;
|
|
758
|
+
}
|
|
759
|
+
|
|
760
|
+
.status-dot.online {
|
|
761
|
+
background: #28a745;
|
|
762
|
+
animation: heartbeat 2s ease-in-out infinite;
|
|
763
|
+
}
|
|
764
|
+
|
|
765
|
+
.status-dot.offline {
|
|
766
|
+
background: #ffc107;
|
|
767
|
+
animation: blink 1.5s ease-in-out infinite;
|
|
768
|
+
}
|
|
769
|
+
|
|
770
|
+
@keyframes heartbeat {
|
|
771
|
+
0%, 100% { transform: scale(1); }
|
|
772
|
+
50% { transform: scale(1.1); }
|
|
773
|
+
}
|
|
774
|
+
|
|
775
|
+
@keyframes blink {
|
|
776
|
+
0%, 100% { opacity: 1; }
|
|
777
|
+
50% { opacity: 0.5; }
|
|
778
|
+
}
|
|
779
|
+
|
|
780
|
+
.auto-refresh {
|
|
781
|
+
display: flex;
|
|
782
|
+
align-items: center;
|
|
783
|
+
gap: 8px;
|
|
784
|
+
font-size: 0.9em;
|
|
785
|
+
color: #6c757d;
|
|
786
|
+
}
|
|
787
|
+
|
|
788
|
+
.refresh-countdown {
|
|
789
|
+
background: #667eea;
|
|
790
|
+
color: white;
|
|
791
|
+
padding: 4px 8px;
|
|
792
|
+
border-radius: 12px;
|
|
793
|
+
font-weight: bold;
|
|
794
|
+
min-width: 30px;
|
|
795
|
+
text-align: center;
|
|
796
|
+
}
|
|
797
|
+
|
|
798
|
+
.main-content {
|
|
799
|
+
padding: 30px;
|
|
800
|
+
}
|
|
801
|
+
|
|
802
|
+
.section {
|
|
803
|
+
margin: 30px 0;
|
|
804
|
+
padding: 25px;
|
|
805
|
+
border: 1px solid #e9ecef;
|
|
806
|
+
border-radius: 15px;
|
|
807
|
+
background: white;
|
|
808
|
+
box-shadow: 0 4px 15px rgba(0,0,0,0.05);
|
|
809
|
+
transition: transform 0.2s, box-shadow 0.2s;
|
|
810
|
+
}
|
|
811
|
+
|
|
812
|
+
.section:hover {
|
|
813
|
+
transform: translateY(-2px);
|
|
814
|
+
box-shadow: 0 8px 25px rgba(0,0,0,0.1);
|
|
815
|
+
}
|
|
816
|
+
|
|
817
|
+
.section h2 {
|
|
818
|
+
margin-bottom: 20px;
|
|
819
|
+
color: #495057;
|
|
820
|
+
border-bottom: 2px solid #667eea;
|
|
821
|
+
padding-bottom: 10px;
|
|
822
|
+
display: flex;
|
|
823
|
+
align-items: center;
|
|
824
|
+
gap: 10px;
|
|
825
|
+
}
|
|
826
|
+
|
|
827
|
+
.stats {
|
|
828
|
+
display: grid;
|
|
829
|
+
grid-template-columns: repeat(auto-fit, minmax(200px, 1fr));
|
|
830
|
+
gap: 20px;
|
|
831
|
+
margin: 25px 0;
|
|
832
|
+
}
|
|
833
|
+
|
|
834
|
+
.stat {
|
|
835
|
+
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
|
|
836
|
+
color: white;
|
|
837
|
+
padding: 25px;
|
|
838
|
+
border-radius: 15px;
|
|
839
|
+
text-align: center;
|
|
840
|
+
box-shadow: 0 8px 20px rgba(102, 126, 234, 0.3);
|
|
841
|
+
transition: transform 0.3s, box-shadow 0.3s;
|
|
842
|
+
position: relative;
|
|
843
|
+
overflow: hidden;
|
|
844
|
+
}
|
|
845
|
+
|
|
846
|
+
.stat::before {
|
|
847
|
+
content: '';
|
|
848
|
+
position: absolute;
|
|
849
|
+
top: -50%;
|
|
850
|
+
left: -50%;
|
|
851
|
+
width: 200%;
|
|
852
|
+
height: 200%;
|
|
853
|
+
background: radial-gradient(circle, rgba(255,255,255,0.1) 0%, transparent 70%);
|
|
854
|
+
transform: scale(0);
|
|
855
|
+
transition: transform 0.3s;
|
|
856
|
+
}
|
|
857
|
+
|
|
858
|
+
.stat:hover::before {
|
|
859
|
+
transform: scale(1);
|
|
860
|
+
}
|
|
861
|
+
|
|
862
|
+
.stat:hover {
|
|
863
|
+
transform: translateY(-5px) scale(1.02);
|
|
864
|
+
box-shadow: 0 15px 30px rgba(102, 126, 234, 0.4);
|
|
865
|
+
}
|
|
866
|
+
|
|
867
|
+
.stat h3 {
|
|
868
|
+
font-size: 2.5em;
|
|
869
|
+
margin-bottom: 10px;
|
|
870
|
+
position: relative;
|
|
871
|
+
z-index: 1;
|
|
872
|
+
}
|
|
873
|
+
|
|
874
|
+
.stat p {
|
|
875
|
+
font-size: 1.1em;
|
|
876
|
+
opacity: 0.9;
|
|
877
|
+
position: relative;
|
|
878
|
+
z-index: 1;
|
|
879
|
+
}
|
|
880
|
+
|
|
881
|
+
.expert-info {
|
|
882
|
+
background: linear-gradient(135deg, #28a745 0%, #20c997 100%);
|
|
883
|
+
color: white;
|
|
884
|
+
padding: 20px;
|
|
885
|
+
border-radius: 15px;
|
|
886
|
+
margin-top: 20px;
|
|
887
|
+
display: flex;
|
|
888
|
+
align-items: center;
|
|
889
|
+
gap: 15px;
|
|
890
|
+
}
|
|
891
|
+
|
|
892
|
+
.expert-avatar {
|
|
893
|
+
width: 60px;
|
|
894
|
+
height: 60px;
|
|
895
|
+
background: rgba(255,255,255,0.2);
|
|
896
|
+
border-radius: 50%;
|
|
897
|
+
display: flex;
|
|
898
|
+
align-items: center;
|
|
899
|
+
justify-content: center;
|
|
900
|
+
font-size: 1.5em;
|
|
901
|
+
}
|
|
902
|
+
|
|
903
|
+
.request {
|
|
904
|
+
background: linear-gradient(135deg, #f8f9ff 0%, #ffffff 100%);
|
|
905
|
+
margin: 20px 0;
|
|
906
|
+
padding: 25px;
|
|
907
|
+
border-left: 5px solid #667eea;
|
|
908
|
+
border-radius: 15px;
|
|
909
|
+
box-shadow: 0 5px 15px rgba(0,0,0,0.08);
|
|
910
|
+
transition: all 0.3s ease;
|
|
911
|
+
position: relative;
|
|
912
|
+
overflow: hidden;
|
|
913
|
+
}
|
|
914
|
+
|
|
915
|
+
.request::before {
|
|
916
|
+
content: '';
|
|
917
|
+
position: absolute;
|
|
918
|
+
top: 0;
|
|
919
|
+
left: 0;
|
|
920
|
+
right: 0;
|
|
921
|
+
height: 3px;
|
|
922
|
+
background: linear-gradient(90deg, #667eea, #764ba2);
|
|
923
|
+
opacity: 0;
|
|
924
|
+
transition: opacity 0.3s;
|
|
925
|
+
}
|
|
926
|
+
|
|
927
|
+
.request:hover::before {
|
|
928
|
+
opacity: 1;
|
|
929
|
+
}
|
|
930
|
+
|
|
931
|
+
.request:hover {
|
|
932
|
+
transform: translateY(-3px);
|
|
933
|
+
box-shadow: 0 10px 25px rgba(102, 126, 234, 0.15);
|
|
934
|
+
}
|
|
935
|
+
|
|
936
|
+
.priority-high { border-left-color: #fd7e14; }
|
|
937
|
+
.priority-urgent {
|
|
938
|
+
border-left-color: #dc3545;
|
|
939
|
+
background: linear-gradient(135deg, #fff5f5 0%, #ffffff 100%);
|
|
940
|
+
animation: urgentPulse 2s infinite;
|
|
941
|
+
}
|
|
942
|
+
|
|
943
|
+
@keyframes urgentPulse {
|
|
944
|
+
0%, 100% { box-shadow: 0 5px 15px rgba(0,0,0,0.08); }
|
|
945
|
+
50% { box-shadow: 0 10px 25px rgba(220, 53, 69, 0.2); }
|
|
946
|
+
}
|
|
947
|
+
|
|
948
|
+
.question {
|
|
949
|
+
font-weight: 600;
|
|
950
|
+
margin: 15px 0;
|
|
951
|
+
font-size: 1.1em;
|
|
952
|
+
color: #495057;
|
|
953
|
+
line-height: 1.5;
|
|
954
|
+
}
|
|
955
|
+
|
|
956
|
+
.context {
|
|
957
|
+
color: #6c757d;
|
|
958
|
+
margin: 15px 0;
|
|
959
|
+
padding: 15px;
|
|
960
|
+
background: rgba(108, 117, 125, 0.05);
|
|
961
|
+
border-radius: 10px;
|
|
962
|
+
display: flex;
|
|
963
|
+
flex-wrap: wrap;
|
|
964
|
+
gap: 15px;
|
|
965
|
+
align-items: center;
|
|
966
|
+
}
|
|
967
|
+
|
|
968
|
+
.context-item {
|
|
969
|
+
display: flex;
|
|
970
|
+
align-items: center;
|
|
971
|
+
gap: 5px;
|
|
972
|
+
padding: 5px 10px;
|
|
973
|
+
background: white;
|
|
974
|
+
border-radius: 15px;
|
|
975
|
+
font-size: 0.9em;
|
|
976
|
+
box-shadow: 0 2px 5px rgba(0,0,0,0.1);
|
|
977
|
+
}
|
|
978
|
+
|
|
979
|
+
.timestamp {
|
|
980
|
+
color: #adb5bd;
|
|
981
|
+
font-size: 0.85em;
|
|
982
|
+
margin-top: 10px;
|
|
983
|
+
display: flex;
|
|
984
|
+
align-items: center;
|
|
985
|
+
gap: 5px;
|
|
986
|
+
}
|
|
987
|
+
|
|
988
|
+
.response-form {
|
|
989
|
+
margin-top: 20px;
|
|
990
|
+
padding: 20px;
|
|
991
|
+
background: rgba(102, 126, 234, 0.02);
|
|
992
|
+
border-radius: 12px;
|
|
993
|
+
border: 1px dashed #667eea;
|
|
994
|
+
}
|
|
995
|
+
|
|
996
|
+
textarea {
|
|
997
|
+
width: 100%;
|
|
998
|
+
min-height: 120px;
|
|
999
|
+
padding: 15px;
|
|
1000
|
+
border: 2px solid #e9ecef;
|
|
1001
|
+
border-radius: 12px;
|
|
1002
|
+
resize: vertical;
|
|
1003
|
+
font-family: inherit;
|
|
1004
|
+
font-size: 1em;
|
|
1005
|
+
line-height: 1.5;
|
|
1006
|
+
transition: border-color 0.3s, box-shadow 0.3s;
|
|
1007
|
+
}
|
|
1008
|
+
|
|
1009
|
+
textarea:focus {
|
|
1010
|
+
outline: none;
|
|
1011
|
+
border-color: #667eea;
|
|
1012
|
+
box-shadow: 0 0 0 3px rgba(102, 126, 234, 0.1);
|
|
1013
|
+
}
|
|
1014
|
+
|
|
1015
|
+
button {
|
|
1016
|
+
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
|
|
1017
|
+
color: white;
|
|
1018
|
+
border: none;
|
|
1019
|
+
padding: 12px 25px;
|
|
1020
|
+
border-radius: 25px;
|
|
1021
|
+
cursor: pointer;
|
|
1022
|
+
font-size: 1em;
|
|
1023
|
+
font-weight: 600;
|
|
1024
|
+
transition: all 0.3s ease;
|
|
1025
|
+
display: inline-flex;
|
|
1026
|
+
align-items: center;
|
|
1027
|
+
gap: 8px;
|
|
1028
|
+
box-shadow: 0 4px 15px rgba(102, 126, 234, 0.3);
|
|
1029
|
+
}
|
|
1030
|
+
|
|
1031
|
+
button:hover {
|
|
1032
|
+
transform: translateY(-2px);
|
|
1033
|
+
box-shadow: 0 8px 25px rgba(102, 126, 234, 0.4);
|
|
1034
|
+
}
|
|
1035
|
+
|
|
1036
|
+
button:active {
|
|
1037
|
+
transform: translateY(0);
|
|
1038
|
+
}
|
|
1039
|
+
|
|
1040
|
+
.no-requests {
|
|
1041
|
+
text-align: center;
|
|
1042
|
+
padding: 60px 20px;
|
|
1043
|
+
color: #6c757d;
|
|
1044
|
+
}
|
|
1045
|
+
|
|
1046
|
+
.no-requests i {
|
|
1047
|
+
font-size: 4em;
|
|
1048
|
+
margin-bottom: 20px;
|
|
1049
|
+
color: #28a745;
|
|
1050
|
+
}
|
|
1051
|
+
|
|
1052
|
+
.instructions {
|
|
1053
|
+
background: linear-gradient(135deg, #17a2b8 0%, #138496 100%);
|
|
1054
|
+
color: white;
|
|
1055
|
+
border-radius: 15px;
|
|
1056
|
+
padding: 25px;
|
|
1057
|
+
}
|
|
1058
|
+
|
|
1059
|
+
.instructions ul {
|
|
1060
|
+
list-style: none;
|
|
1061
|
+
padding: 0;
|
|
1062
|
+
}
|
|
1063
|
+
|
|
1064
|
+
.instructions li {
|
|
1065
|
+
padding: 8px 0;
|
|
1066
|
+
display: flex;
|
|
1067
|
+
align-items: center;
|
|
1068
|
+
gap: 10px;
|
|
1069
|
+
}
|
|
1070
|
+
|
|
1071
|
+
.instructions li::before {
|
|
1072
|
+
content: 'ā';
|
|
1073
|
+
background: rgba(255,255,255,0.2);
|
|
1074
|
+
width: 24px;
|
|
1075
|
+
height: 24px;
|
|
1076
|
+
border-radius: 50%;
|
|
1077
|
+
display: flex;
|
|
1078
|
+
align-items: center;
|
|
1079
|
+
justify-content: center;
|
|
1080
|
+
font-weight: bold;
|
|
1081
|
+
}
|
|
1082
|
+
|
|
1083
|
+
.loading {
|
|
1084
|
+
display: none;
|
|
1085
|
+
text-align: center;
|
|
1086
|
+
padding: 20px;
|
|
1087
|
+
color: #6c757d;
|
|
1088
|
+
}
|
|
1089
|
+
|
|
1090
|
+
.loading i {
|
|
1091
|
+
animation: spin 1s linear infinite;
|
|
1092
|
+
}
|
|
1093
|
+
|
|
1094
|
+
@keyframes spin {
|
|
1095
|
+
from { transform: rotate(0deg); }
|
|
1096
|
+
to { transform: rotate(360deg); }
|
|
1097
|
+
}
|
|
1098
|
+
|
|
1099
|
+
/* Responsive design */
|
|
1100
|
+
@media (max-width: 768px) {
|
|
1101
|
+
body { padding: 10px; }
|
|
1102
|
+
.container { border-radius: 10px; }
|
|
1103
|
+
.header { padding: 20px; }
|
|
1104
|
+
.header h1 { font-size: 2em; }
|
|
1105
|
+
.main-content { padding: 20px; }
|
|
1106
|
+
.section { padding: 15px; }
|
|
1107
|
+
.stats { grid-template-columns: 1fr; }
|
|
1108
|
+
.context { flex-direction: column; align-items: flex-start; }
|
|
1109
|
+
.status-bar { flex-direction: column; }
|
|
1110
|
+
}
|
|
1111
|
+
|
|
1112
|
+
/* Notification styles */
|
|
1113
|
+
.notification {
|
|
1114
|
+
position: fixed;
|
|
1115
|
+
top: 20px;
|
|
1116
|
+
right: 20px;
|
|
1117
|
+
background: #28a745;
|
|
1118
|
+
color: white;
|
|
1119
|
+
padding: 15px 20px;
|
|
1120
|
+
border-radius: 10px;
|
|
1121
|
+
box-shadow: 0 5px 15px rgba(0,0,0,0.2);
|
|
1122
|
+
transform: translateX(400px);
|
|
1123
|
+
transition: transform 0.3s;
|
|
1124
|
+
z-index: 1000;
|
|
1125
|
+
}
|
|
1126
|
+
|
|
1127
|
+
.notification.show {
|
|
1128
|
+
transform: translateX(0);
|
|
1129
|
+
}
|
|
1130
|
+
</style>
|
|
1131
|
+
<script>
|
|
1132
|
+
let countdownInterval;
|
|
1133
|
+
let refreshInterval = 15; // 15 seconds instead of 30
|
|
1134
|
+
let currentCountdown = refreshInterval;
|
|
1135
|
+
|
|
1136
|
+
function updateCountdown() {
|
|
1137
|
+
const countdownEl = document.getElementById('countdown');
|
|
1138
|
+
if (countdownEl) {
|
|
1139
|
+
countdownEl.textContent = currentCountdown;
|
|
1140
|
+
currentCountdown--;
|
|
1141
|
+
|
|
1142
|
+
if (currentCountdown < 0) {
|
|
1143
|
+
currentCountdown = refreshInterval;
|
|
1144
|
+
refreshPage();
|
|
1145
|
+
}
|
|
1146
|
+
}
|
|
1147
|
+
}
|
|
1148
|
+
|
|
1149
|
+
function refreshPage() {
|
|
1150
|
+
const loadingEl = document.querySelector('.loading');
|
|
1151
|
+
if (loadingEl) {
|
|
1152
|
+
loadingEl.style.display = 'block';
|
|
1153
|
+
}
|
|
1154
|
+
|
|
1155
|
+
// Add a small delay to show loading animation
|
|
1156
|
+
setTimeout(() => {
|
|
1157
|
+
location.reload();
|
|
1158
|
+
}, 500);
|
|
1159
|
+
}
|
|
1160
|
+
|
|
1161
|
+
function startAutoRefresh() {
|
|
1162
|
+
// Update countdown every second
|
|
1163
|
+
countdownInterval = setInterval(updateCountdown, 1000);
|
|
1164
|
+
|
|
1165
|
+
// Show notification on first load
|
|
1166
|
+
showNotification('Auto-refresh enabled: Updates every ' + refreshInterval + ' seconds', 'info');
|
|
1167
|
+
}
|
|
1168
|
+
|
|
1169
|
+
function showNotification(message, type = 'success') {
|
|
1170
|
+
const notification = document.createElement('div');
|
|
1171
|
+
notification.className = 'notification';
|
|
1172
|
+
notification.innerHTML = `<i class="fas fa-${type === 'success' ? 'check-circle' : 'info-circle'}"></i> ${message}`;
|
|
1173
|
+
document.body.appendChild(notification);
|
|
1174
|
+
|
|
1175
|
+
setTimeout(() => notification.classList.add('show'), 100);
|
|
1176
|
+
setTimeout(() => {
|
|
1177
|
+
notification.classList.remove('show');
|
|
1178
|
+
setTimeout(() => notification.remove(), 300);
|
|
1179
|
+
}, 3000);
|
|
1180
|
+
}
|
|
1181
|
+
|
|
1182
|
+
function submitResponse(form) {
|
|
1183
|
+
const submitBtn = form.querySelector('button[type="submit"]');
|
|
1184
|
+
const originalText = submitBtn.innerHTML;
|
|
1185
|
+
|
|
1186
|
+
submitBtn.disabled = true;
|
|
1187
|
+
submitBtn.innerHTML = '<i class="fas fa-spinner fa-spin"></i> Submitting...';
|
|
1188
|
+
|
|
1189
|
+
// Let the form submit naturally
|
|
1190
|
+
return true;
|
|
1191
|
+
}
|
|
1192
|
+
|
|
1193
|
+
// Initialize when page loads
|
|
1194
|
+
window.addEventListener('load', function() {
|
|
1195
|
+
startAutoRefresh();
|
|
1196
|
+
|
|
1197
|
+
// Add click handlers to forms
|
|
1198
|
+
document.querySelectorAll('.response-form').forEach(form => {
|
|
1199
|
+
form.addEventListener('submit', function() {
|
|
1200
|
+
return submitResponse(this);
|
|
1201
|
+
});
|
|
1202
|
+
});
|
|
1203
|
+
});
|
|
1204
|
+
|
|
1205
|
+
// Handle page visibility changes (pause refresh when tab is not active)
|
|
1206
|
+
document.addEventListener('visibilitychange', function() {
|
|
1207
|
+
if (document.hidden) {
|
|
1208
|
+
clearInterval(countdownInterval);
|
|
1209
|
+
} else {
|
|
1210
|
+
currentCountdown = refreshInterval;
|
|
1211
|
+
startAutoRefresh();
|
|
1212
|
+
}
|
|
1213
|
+
});
|
|
1214
|
+
</script>
|
|
1215
|
+
</head>
|
|
1216
|
+
<body>
|
|
1217
|
+
<div class="container">
|
|
1218
|
+
<div class="header">
|
|
1219
|
+
<h1><i class="fas fa-user-md"></i> Human Expert Interface</h1>
|
|
1220
|
+
<p>ToolUniverse Scientific Consultation Response System</p>
|
|
1221
|
+
</div>
|
|
1222
|
+
|
|
1223
|
+
<div class="status-bar">
|
|
1224
|
+
<div class="status-indicator">
|
|
1225
|
+
<div class="status-dot"></div>
|
|
1226
|
+
<span><strong>System Active</strong></span>
|
|
1227
|
+
</div>
|
|
1228
|
+
<div class="status-indicator">
|
|
1229
|
+
<div class="status-dot {{ 'online' if mcp_info.connected else 'offline' }}"></div>
|
|
1230
|
+
<span><strong>MCP: {{ mcp_info.host }}:{{ mcp_info.port }}</strong></span>
|
|
1231
|
+
<span class="status-text">({{ 'Connected' if mcp_info.connected else 'Local Mode' }})</span>
|
|
1232
|
+
</div>
|
|
1233
|
+
<div class="auto-refresh">
|
|
1234
|
+
<i class="fas fa-sync-alt"></i>
|
|
1235
|
+
<span>Auto-refresh in</span>
|
|
1236
|
+
<span class="refresh-countdown" id="countdown">{{ refreshInterval|default(15) }}</span>
|
|
1237
|
+
<span>seconds</span>
|
|
1238
|
+
</div>
|
|
1239
|
+
<div class="status-indicator">
|
|
1240
|
+
<i class="fas fa-clock"></i>
|
|
1241
|
+
<span id="current-time"></span>
|
|
1242
|
+
</div>
|
|
1243
|
+
</div>
|
|
1244
|
+
|
|
1245
|
+
<div class="main-content">
|
|
1246
|
+
<div class="section">
|
|
1247
|
+
<h2><i class="fas fa-chart-bar"></i> System Status</h2>
|
|
1248
|
+
<div class="stats">
|
|
1249
|
+
<div class="stat">
|
|
1250
|
+
<h3>{{ status.statistics.pending_requests }}</h3>
|
|
1251
|
+
<p>Pending Requests</p>
|
|
1252
|
+
</div>
|
|
1253
|
+
<div class="stat">
|
|
1254
|
+
<h3>{{ status.statistics.total_requests }}</h3>
|
|
1255
|
+
<p>Total Requests</p>
|
|
1256
|
+
</div>
|
|
1257
|
+
<div class="stat">
|
|
1258
|
+
<h3>{{ status.statistics.response_rate }}%</h3>
|
|
1259
|
+
<p>Response Rate</p>
|
|
1260
|
+
</div>
|
|
1261
|
+
</div>
|
|
1262
|
+
<div class="expert-info">
|
|
1263
|
+
<div class="expert-avatar">
|
|
1264
|
+
<i class="fas fa-user-md"></i>
|
|
1265
|
+
</div>
|
|
1266
|
+
<div>
|
|
1267
|
+
<strong>{{ status.expert_info.name }}</strong><br>
|
|
1268
|
+
<span>{{ status.expert_info.specialty }}</span>
|
|
1269
|
+
</div>
|
|
1270
|
+
</div>
|
|
1271
|
+
</div>
|
|
1272
|
+
|
|
1273
|
+
<div class="section">
|
|
1274
|
+
<h2><i class="fas fa-clipboard-list"></i> Pending Consultation Requests</h2>
|
|
1275
|
+
{% if requests.count == 0 %}
|
|
1276
|
+
<div class="no-requests">
|
|
1277
|
+
<i class="fas fa-check-circle"></i>
|
|
1278
|
+
<h3>All caught up!</h3>
|
|
1279
|
+
<p>No pending requests at this time</p>
|
|
1280
|
+
</div>
|
|
1281
|
+
{% else %}
|
|
1282
|
+
<p><strong>{{ requests.count }}</strong> request(s) waiting for expert response:</p>
|
|
1283
|
+
{% for req in requests.pending_requests %}
|
|
1284
|
+
<div class="request {% if req.priority == 'high' %}priority-high{% elif req.priority == 'urgent' %}priority-urgent{% endif %}">
|
|
1285
|
+
<div class="question">
|
|
1286
|
+
<i class="fas fa-question-circle"></i> {{ req.question }}
|
|
1287
|
+
</div>
|
|
1288
|
+
<div class="context">
|
|
1289
|
+
<div class="context-item">
|
|
1290
|
+
<i class="fas fa-stethoscope"></i>
|
|
1291
|
+
<strong>Specialty:</strong> {{ req.specialty }}
|
|
1292
|
+
</div>
|
|
1293
|
+
<div class="context-item">
|
|
1294
|
+
<i class="fas fa-exclamation-triangle"></i>
|
|
1295
|
+
<strong>Priority:</strong> {{ req.priority }}
|
|
1296
|
+
</div>
|
|
1297
|
+
<div class="context-item">
|
|
1298
|
+
<i class="fas fa-clock"></i>
|
|
1299
|
+
<strong>Age:</strong> {{ req.age_minutes }} minutes
|
|
1300
|
+
</div>
|
|
1301
|
+
</div>
|
|
1302
|
+
<div class="timestamp">
|
|
1303
|
+
<i class="fas fa-calendar-alt"></i>
|
|
1304
|
+
{{ req.timestamp }}
|
|
1305
|
+
</div>
|
|
1306
|
+
|
|
1307
|
+
<form method="POST" action="/submit_response" class="response-form">
|
|
1308
|
+
<input type="hidden" name="request_id" value="{{ req.request_id }}">
|
|
1309
|
+
<textarea name="response" placeholder="Enter your expert response and clinical recommendations here..." required></textarea>
|
|
1310
|
+
<button type="submit">
|
|
1311
|
+
<i class="fas fa-paper-plane"></i>
|
|
1312
|
+
Submit Expert Response
|
|
1313
|
+
</button>
|
|
1314
|
+
</form>
|
|
1315
|
+
</div>
|
|
1316
|
+
{% endfor %}
|
|
1317
|
+
{% endif %}
|
|
1318
|
+
</div>
|
|
1319
|
+
|
|
1320
|
+
<div class="section instructions">
|
|
1321
|
+
<h2><i class="fas fa-info-circle"></i> Instructions</h2>
|
|
1322
|
+
<ul>
|
|
1323
|
+
<li>This page auto-refreshes every 15 seconds for real-time updates</li>
|
|
1324
|
+
<li>Review each consultation request carefully</li>
|
|
1325
|
+
<li>Provide detailed clinical recommendations</li>
|
|
1326
|
+
<li>Prioritize urgent and high-priority requests</li>
|
|
1327
|
+
<li>All responses are logged and timestamped</li>
|
|
1328
|
+
</ul>
|
|
1329
|
+
</div>
|
|
1330
|
+
|
|
1331
|
+
<div class="loading">
|
|
1332
|
+
<i class="fas fa-spinner fa-spin"></i>
|
|
1333
|
+
<p>Refreshing data...</p>
|
|
1334
|
+
</div>
|
|
1335
|
+
</div>
|
|
1336
|
+
</div>
|
|
1337
|
+
|
|
1338
|
+
<script>
|
|
1339
|
+
// Update current time
|
|
1340
|
+
function updateTime() {
|
|
1341
|
+
const now = new Date();
|
|
1342
|
+
const timeStr = now.toLocaleTimeString();
|
|
1343
|
+
const timeEl = document.getElementById('current-time');
|
|
1344
|
+
if (timeEl) {
|
|
1345
|
+
timeEl.textContent = timeStr;
|
|
1346
|
+
}
|
|
1347
|
+
}
|
|
1348
|
+
|
|
1349
|
+
// Update time immediately and then every second
|
|
1350
|
+
updateTime();
|
|
1351
|
+
setInterval(updateTime, 1000);
|
|
1352
|
+
</script>
|
|
1353
|
+
</body>
|
|
1354
|
+
</html>
|
|
1355
|
+
"""
|
|
1356
|
+
|
|
1357
|
+
@app.route("/")
|
|
1358
|
+
def index():
|
|
1359
|
+
"""Main expert interface page"""
|
|
1360
|
+
try:
|
|
1361
|
+
# Get HTTP API server URL from environment or default to localhost
|
|
1362
|
+
import os
|
|
1363
|
+
|
|
1364
|
+
api_host = os.getenv("EXPERT_FEEDBACK_API_HOST", "localhost")
|
|
1365
|
+
api_port = os.getenv("EXPERT_FEEDBACK_API_PORT", "9877")
|
|
1366
|
+
api_url = f"http://{api_host}:{api_port}/api/requests"
|
|
1367
|
+
api_connected = False
|
|
1368
|
+
|
|
1369
|
+
try:
|
|
1370
|
+
# Get pending requests from HTTP API server
|
|
1371
|
+
|
|
1372
|
+
response = requests.get(api_url, timeout=5)
|
|
1373
|
+
|
|
1374
|
+
if response.status_code == 200:
|
|
1375
|
+
api_data = response.json()
|
|
1376
|
+
api_connected = True
|
|
1377
|
+
|
|
1378
|
+
status = {
|
|
1379
|
+
"expert_info": api_data.get(
|
|
1380
|
+
"expert_info",
|
|
1381
|
+
{
|
|
1382
|
+
"name": "Scientific Expert",
|
|
1383
|
+
"specialty": "General Medicine",
|
|
1384
|
+
},
|
|
1385
|
+
),
|
|
1386
|
+
"statistics": api_data.get(
|
|
1387
|
+
"statistics",
|
|
1388
|
+
{
|
|
1389
|
+
"pending_requests": 0,
|
|
1390
|
+
"total_requests": 0,
|
|
1391
|
+
"completed_responses": 0,
|
|
1392
|
+
"response_rate": 0.0,
|
|
1393
|
+
},
|
|
1394
|
+
),
|
|
1395
|
+
}
|
|
1396
|
+
|
|
1397
|
+
requests_data = {
|
|
1398
|
+
"pending_requests": api_data.get("pending_requests", []),
|
|
1399
|
+
"count": api_data.get("count", 0),
|
|
1400
|
+
}
|
|
1401
|
+
|
|
1402
|
+
else:
|
|
1403
|
+
print(f"ā ļø HTTP API returned status {response.status_code}")
|
|
1404
|
+
api_connected = False
|
|
1405
|
+
|
|
1406
|
+
except requests.exceptions.ConnectinError:
|
|
1407
|
+
print(f"ā ļø Cannot connect to HTTP API server at {api_host}:{api_port}")
|
|
1408
|
+
api_connected = False
|
|
1409
|
+
except requests.exceptions.Timeout:
|
|
1410
|
+
print(f"ā ļø HTTP API server timeout at {api_host}:{api_port}")
|
|
1411
|
+
api_connected = False
|
|
1412
|
+
except Exception as e:
|
|
1413
|
+
print(f"ā ļø Error connecting to HTTP API server: {e}")
|
|
1414
|
+
api_connected = False
|
|
1415
|
+
|
|
1416
|
+
# If API connection failed, show error page
|
|
1417
|
+
if not api_connected:
|
|
1418
|
+
status = {
|
|
1419
|
+
"expert_info": {
|
|
1420
|
+
"name": "Connection Error",
|
|
1421
|
+
"specialty": f"Cannot connect to API server at {api_host}:{api_port}",
|
|
1422
|
+
},
|
|
1423
|
+
"statistics": {
|
|
1424
|
+
"pending_requests": 0,
|
|
1425
|
+
"total_requests": 0,
|
|
1426
|
+
"completed_responses": 0,
|
|
1427
|
+
"response_rate": 0.0,
|
|
1428
|
+
},
|
|
1429
|
+
}
|
|
1430
|
+
|
|
1431
|
+
requests_data = {
|
|
1432
|
+
"pending_requests": [],
|
|
1433
|
+
"count": 0,
|
|
1434
|
+
"connection_error": True,
|
|
1435
|
+
}
|
|
1436
|
+
|
|
1437
|
+
# Add API connection info
|
|
1438
|
+
api_info = {
|
|
1439
|
+
"host": api_host,
|
|
1440
|
+
"port": api_port,
|
|
1441
|
+
"url": api_url,
|
|
1442
|
+
"connected": api_connected,
|
|
1443
|
+
}
|
|
1444
|
+
|
|
1445
|
+
return render_template_string(
|
|
1446
|
+
WEB_TEMPLATE, status=status, requests=requests_data, mcp_info=api_info
|
|
1447
|
+
)
|
|
1448
|
+
|
|
1449
|
+
except Exception as e:
|
|
1450
|
+
return f"Error loading interface: {str(e)}", 500
|
|
1451
|
+
|
|
1452
|
+
@app.route("/submit_response", methods=["POST"])
|
|
1453
|
+
def submit_response():
|
|
1454
|
+
"""Handle expert response submission via HTTP API"""
|
|
1455
|
+
try:
|
|
1456
|
+
import os
|
|
1457
|
+
|
|
1458
|
+
request_id = request.form.get("request_id")
|
|
1459
|
+
response_text = request.form.get("response")
|
|
1460
|
+
|
|
1461
|
+
if not request_id or not response_text:
|
|
1462
|
+
return "Missing request ID or response", 400
|
|
1463
|
+
|
|
1464
|
+
# Get HTTP API server URL
|
|
1465
|
+
api_host = os.getenv("EXPERT_FEEDBACK_API_HOST", "localhost")
|
|
1466
|
+
api_port = os.getenv("EXPERT_FEEDBACK_API_PORT", "9877")
|
|
1467
|
+
api_url = f"http://{api_host}:{api_port}/api/requests/{request_id}/respond"
|
|
1468
|
+
|
|
1469
|
+
# Submit response via HTTP API
|
|
1470
|
+
payload = {"response": response_text}
|
|
1471
|
+
headers = {"Content-Type": "application/json"}
|
|
1472
|
+
|
|
1473
|
+
try:
|
|
1474
|
+
api_response = requests.post(
|
|
1475
|
+
api_url, json=payload, headers=headers, timeout=10
|
|
1476
|
+
)
|
|
1477
|
+
|
|
1478
|
+
if api_response.status_code == 200:
|
|
1479
|
+
print(
|
|
1480
|
+
f"ā
Web interface: Expert response submitted via API for {request_id}"
|
|
1481
|
+
)
|
|
1482
|
+
return redirect(url_for("index"))
|
|
1483
|
+
else:
|
|
1484
|
+
print(
|
|
1485
|
+
f"ā ļø API submission failed: {api_response.status_code} - {api_response.text}"
|
|
1486
|
+
)
|
|
1487
|
+
return f"API Error: {api_response.status_code}", 500
|
|
1488
|
+
|
|
1489
|
+
except requests.exceptions.ConnectinError:
|
|
1490
|
+
return "Cannot connect to API server", 503
|
|
1491
|
+
except requests.exceptions.Timeout:
|
|
1492
|
+
return "API server timeout", 504
|
|
1493
|
+
except Exception as e:
|
|
1494
|
+
print(f"ā ļø Error submitting via API: {e}")
|
|
1495
|
+
return f"Submission failed: {str(e)}", 500
|
|
1496
|
+
|
|
1497
|
+
except Exception as e:
|
|
1498
|
+
return f"Error: {str(e)}", 500
|
|
1499
|
+
|
|
1500
|
+
@app.route("/api/status")
|
|
1501
|
+
def api_status():
|
|
1502
|
+
"""API endpoint for status information"""
|
|
1503
|
+
try:
|
|
1504
|
+
pending = expert_system.get_pending_requests()
|
|
1505
|
+
|
|
1506
|
+
with expert_system.lock:
|
|
1507
|
+
total_responses = len(expert_system.responses)
|
|
1508
|
+
total_requests = len(expert_system.request_status)
|
|
1509
|
+
|
|
1510
|
+
return jsonify(
|
|
1511
|
+
{
|
|
1512
|
+
"status": "active",
|
|
1513
|
+
"expert_info": expert_system.expert_info,
|
|
1514
|
+
"statistics": {
|
|
1515
|
+
"pending_requests": len(pending),
|
|
1516
|
+
"total_requests": total_requests,
|
|
1517
|
+
"completed_responses": total_responses,
|
|
1518
|
+
"response_rate": round(
|
|
1519
|
+
total_responses / max(total_requests, 1) * 100, 1
|
|
1520
|
+
),
|
|
1521
|
+
},
|
|
1522
|
+
"system_time": datetime.now().isoformat(),
|
|
1523
|
+
}
|
|
1524
|
+
)
|
|
1525
|
+
|
|
1526
|
+
except Exception as e:
|
|
1527
|
+
return jsonify({"error": str(e)}), 500
|
|
1528
|
+
|
|
1529
|
+
return app
|
|
1530
|
+
|
|
1531
|
+
|
|
1532
|
+
# =============================================================================
|
|
1533
|
+
# š» TERMINAL INTERFACE (Same as original)
|
|
1534
|
+
# =============================================================================
|
|
1535
|
+
|
|
1536
|
+
|
|
1537
|
+
class ExpertInterface:
|
|
1538
|
+
"""Terminal-based expert interface for responding to consultation requests"""
|
|
1539
|
+
|
|
1540
|
+
def __init__(self):
|
|
1541
|
+
self.running = True
|
|
1542
|
+
|
|
1543
|
+
def display_banner(self):
|
|
1544
|
+
"""Display welcome banner"""
|
|
1545
|
+
print("\n" + "=" * 80)
|
|
1546
|
+
print("š§āāļø HUMAN EXPERT TERMINAL INTERFACE")
|
|
1547
|
+
print("=" * 80)
|
|
1548
|
+
print(f"šØāāļø Expert: {expert_system.expert_info['name']}")
|
|
1549
|
+
print(f"šÆ Specialty: {expert_system.expert_info['specialty']}")
|
|
1550
|
+
print(f"š System Time: {datetime.now().strftime('%Y-%m-%d %H:%M:%S')}")
|
|
1551
|
+
print("=" * 80)
|
|
1552
|
+
|
|
1553
|
+
def display_menu(self):
|
|
1554
|
+
"""Display main menu options"""
|
|
1555
|
+
print("\nš EXPERT ACTIONS:")
|
|
1556
|
+
print(" 1ļøā£ View Pending Consultation Requests")
|
|
1557
|
+
print(" 2ļøā£ Respond to Specific Request")
|
|
1558
|
+
print(" 3ļøā£ View System Status")
|
|
1559
|
+
print(" 4ļøā£ View Recent Responses")
|
|
1560
|
+
print(" š Auto-refresh (every 30 seconds)")
|
|
1561
|
+
print(" ā Quit")
|
|
1562
|
+
print("-" * 50)
|
|
1563
|
+
|
|
1564
|
+
def view_pending_requests(self):
|
|
1565
|
+
"""Display all pending consultation requests"""
|
|
1566
|
+
pending = expert_system.get_pending_requests()
|
|
1567
|
+
|
|
1568
|
+
if not pending:
|
|
1569
|
+
print("\nā
No pending consultation requests!")
|
|
1570
|
+
return
|
|
1571
|
+
|
|
1572
|
+
print(f"\nš PENDING CONSULTATION REQUESTS ({len(pending)}):")
|
|
1573
|
+
print("=" * 80)
|
|
1574
|
+
|
|
1575
|
+
for i, req in enumerate(pending, 1):
|
|
1576
|
+
age_seconds = (
|
|
1577
|
+
datetime.now() - datetime.fromisoformat(req["timestamp"])
|
|
1578
|
+
).total_seconds()
|
|
1579
|
+
age_minutes = round(age_seconds / 60, 1)
|
|
1580
|
+
|
|
1581
|
+
specialty = req.get("context", {}).get("specialty", "general")
|
|
1582
|
+
priority = req.get("context", {}).get("priority", "normal")
|
|
1583
|
+
|
|
1584
|
+
print(f"\n[{i}] REQUEST ID: {req['id']}")
|
|
1585
|
+
print(
|
|
1586
|
+
f"šÆ Specialty: {specialty} | ā” Priority: {priority} | ā° Age: {age_minutes} min"
|
|
1587
|
+
)
|
|
1588
|
+
print(f"ā Question: {req['question']}")
|
|
1589
|
+
|
|
1590
|
+
if req.get("context", {}).get("context"):
|
|
1591
|
+
print(f"š Context: {req['context']['context']}")
|
|
1592
|
+
|
|
1593
|
+
print(f"š
Submitted: {req['timestamp']}")
|
|
1594
|
+
print("-" * 50)
|
|
1595
|
+
|
|
1596
|
+
def respond_to_request(self):
|
|
1597
|
+
"""Handle expert response to a specific request"""
|
|
1598
|
+
pending = expert_system.get_pending_requests()
|
|
1599
|
+
|
|
1600
|
+
if not pending:
|
|
1601
|
+
print("\nā
No pending requests to respond to!")
|
|
1602
|
+
return
|
|
1603
|
+
|
|
1604
|
+
# Show pending requests
|
|
1605
|
+
self.view_pending_requests()
|
|
1606
|
+
|
|
1607
|
+
# Get request selection
|
|
1608
|
+
try:
|
|
1609
|
+
choice = input(f"\nSelect request (1-{len(pending)}) or 'back': ").strip()
|
|
1610
|
+
|
|
1611
|
+
if choice.lower() == "back":
|
|
1612
|
+
return
|
|
1613
|
+
|
|
1614
|
+
request_idx = int(choice) - 1
|
|
1615
|
+
if request_idx < 0 or request_idx >= len(pending):
|
|
1616
|
+
print("ā Invalid selection!")
|
|
1617
|
+
return
|
|
1618
|
+
|
|
1619
|
+
selected_request = pending[request_idx]
|
|
1620
|
+
request_id = selected_request["id"]
|
|
1621
|
+
|
|
1622
|
+
print(f"\nš RESPONDING TO REQUEST: {request_id}")
|
|
1623
|
+
print(f"ā Question: {selected_request['question']}")
|
|
1624
|
+
print(
|
|
1625
|
+
"\nš” Enter your expert response (type 'END' on a new line when finished):"
|
|
1626
|
+
)
|
|
1627
|
+
|
|
1628
|
+
# Multi-line response input
|
|
1629
|
+
response_lines = []
|
|
1630
|
+
while True:
|
|
1631
|
+
line = input()
|
|
1632
|
+
if line.strip() == "END":
|
|
1633
|
+
break
|
|
1634
|
+
response_lines.append(line)
|
|
1635
|
+
|
|
1636
|
+
response = "\n".join(response_lines).strip()
|
|
1637
|
+
|
|
1638
|
+
if not response:
|
|
1639
|
+
print("ā Empty response! Response not submitted.")
|
|
1640
|
+
return
|
|
1641
|
+
|
|
1642
|
+
# Confirm before submitting
|
|
1643
|
+
print("\nš RESPONSE PREVIEW:")
|
|
1644
|
+
print("-" * 40)
|
|
1645
|
+
print(response)
|
|
1646
|
+
print("-" * 40)
|
|
1647
|
+
|
|
1648
|
+
confirm = input("\nSubmit this response? (y/N): ").strip().lower()
|
|
1649
|
+
|
|
1650
|
+
if confirm == "y":
|
|
1651
|
+
success = expert_system.submit_response(request_id, response)
|
|
1652
|
+
if success:
|
|
1653
|
+
print(
|
|
1654
|
+
f"ā
Response submitted successfully for request {request_id}"
|
|
1655
|
+
)
|
|
1656
|
+
else:
|
|
1657
|
+
print(f"ā Failed to submit response for request {request_id}")
|
|
1658
|
+
else:
|
|
1659
|
+
print("ā Response cancelled.")
|
|
1660
|
+
|
|
1661
|
+
except ValueError:
|
|
1662
|
+
print("ā Invalid input! Please enter a number.")
|
|
1663
|
+
except KeyboardInterrupt:
|
|
1664
|
+
print("\nā Response cancelled.")
|
|
1665
|
+
|
|
1666
|
+
def view_system_status(self):
|
|
1667
|
+
"""Display system status and statistics"""
|
|
1668
|
+
pending = expert_system.get_pending_requests()
|
|
1669
|
+
|
|
1670
|
+
with expert_system.lock:
|
|
1671
|
+
total_responses = len(expert_system.responses)
|
|
1672
|
+
total_requests = len(expert_system.request_status)
|
|
1673
|
+
|
|
1674
|
+
print("\nš EXPERT SYSTEM STATUS:")
|
|
1675
|
+
print("=" * 50)
|
|
1676
|
+
print(f"šØāāļø Expert: {expert_system.expert_info['name']}")
|
|
1677
|
+
print(f"šÆ Specialty: {expert_system.expert_info['specialty']}")
|
|
1678
|
+
print(f"š Pending Requests: {len(pending)}")
|
|
1679
|
+
print(f"š Total Requests: {total_requests}")
|
|
1680
|
+
print(f"ā
Completed Responses: {total_responses}")
|
|
1681
|
+
print(
|
|
1682
|
+
f"š Response Rate: {round(total_responses / max(total_requests, 1) * 100, 1)}%"
|
|
1683
|
+
)
|
|
1684
|
+
print(f"š System Time: {datetime.now().isoformat()}")
|
|
1685
|
+
print("=" * 50)
|
|
1686
|
+
|
|
1687
|
+
def view_recent_responses(self):
|
|
1688
|
+
"""Display recent expert responses"""
|
|
1689
|
+
with expert_system.lock:
|
|
1690
|
+
responses = list(expert_system.responses.values())
|
|
1691
|
+
|
|
1692
|
+
if not responses:
|
|
1693
|
+
print("\nš No responses submitted yet!")
|
|
1694
|
+
return
|
|
1695
|
+
|
|
1696
|
+
# Sort by timestamp (most recent first)
|
|
1697
|
+
responses.sort(key=lambda x: x["timestamp"], reverse=True)
|
|
1698
|
+
recent = responses[:5] # Show last 5 responses
|
|
1699
|
+
|
|
1700
|
+
print(f"\nš¬ RECENT EXPERT RESPONSES ({len(recent)} of {len(responses)}):")
|
|
1701
|
+
print("=" * 80)
|
|
1702
|
+
|
|
1703
|
+
for i, resp in enumerate(recent, 1):
|
|
1704
|
+
req = resp["request"]
|
|
1705
|
+
print(f"\n[{i}] REQUEST ID: {req['id']}")
|
|
1706
|
+
print(
|
|
1707
|
+
f"ā Question: {req['question'][:100]}{'...' if len(req['question']) > 100 else ''}"
|
|
1708
|
+
)
|
|
1709
|
+
print(
|
|
1710
|
+
f"ā
Response: {resp['response'][:150]}{'...' if len(resp['response']) > 150 else ''}"
|
|
1711
|
+
)
|
|
1712
|
+
print(f"šØāāļø Expert: {resp['expert']}")
|
|
1713
|
+
print(f"š
Responded: {resp['timestamp']}")
|
|
1714
|
+
print("-" * 50)
|
|
1715
|
+
|
|
1716
|
+
def auto_refresh_loop(self):
|
|
1717
|
+
"""Auto-refresh pending requests every 30 seconds"""
|
|
1718
|
+
print("\nš Auto-refresh mode (Ctrl+C to stop)")
|
|
1719
|
+
print("=" * 50)
|
|
1720
|
+
|
|
1721
|
+
try:
|
|
1722
|
+
while self.running:
|
|
1723
|
+
print(
|
|
1724
|
+
f"\nš {datetime.now().strftime('%H:%M:%S')} - Checking for new requests..."
|
|
1725
|
+
)
|
|
1726
|
+
|
|
1727
|
+
pending = expert_system.get_pending_requests()
|
|
1728
|
+
if pending:
|
|
1729
|
+
print(f"š {len(pending)} pending request(s) found:")
|
|
1730
|
+
for req in pending:
|
|
1731
|
+
age_seconds = (
|
|
1732
|
+
datetime.now() - datetime.fromisoformat(req["timestamp"])
|
|
1733
|
+
).total_seconds()
|
|
1734
|
+
print(
|
|
1735
|
+
f" ⢠{req['id']}: {req['question'][:60]}{'...' if len(req['question']) > 60 else ''} ({round(age_seconds/60, 1)} min old)"
|
|
1736
|
+
)
|
|
1737
|
+
else:
|
|
1738
|
+
print("ā
No pending requests")
|
|
1739
|
+
|
|
1740
|
+
print(" (Press Ctrl+C to return to menu)")
|
|
1741
|
+
time.sleep(30)
|
|
1742
|
+
|
|
1743
|
+
except KeyboardInterrupt:
|
|
1744
|
+
print("\nš Auto-refresh stopped")
|
|
1745
|
+
|
|
1746
|
+
def run(self):
|
|
1747
|
+
"""Main interface loop"""
|
|
1748
|
+
self.display_banner()
|
|
1749
|
+
|
|
1750
|
+
try:
|
|
1751
|
+
while self.running:
|
|
1752
|
+
self.display_menu()
|
|
1753
|
+
choice = input("Select option: ").strip()
|
|
1754
|
+
|
|
1755
|
+
if choice == "1":
|
|
1756
|
+
self.view_pending_requests()
|
|
1757
|
+
elif choice == "2":
|
|
1758
|
+
self.respond_to_request()
|
|
1759
|
+
elif choice == "3":
|
|
1760
|
+
self.view_system_status()
|
|
1761
|
+
elif choice == "4":
|
|
1762
|
+
self.view_recent_responses()
|
|
1763
|
+
elif choice.lower() in ["refresh", "r", "š"]:
|
|
1764
|
+
self.auto_refresh_loop()
|
|
1765
|
+
elif choice.lower() in ["quit", "q", "exit", "ā"]:
|
|
1766
|
+
self.running = False
|
|
1767
|
+
print("\nš Expert interface closed. Have a great day!")
|
|
1768
|
+
break
|
|
1769
|
+
else:
|
|
1770
|
+
print("ā Invalid option! Please try again.")
|
|
1771
|
+
|
|
1772
|
+
if self.running:
|
|
1773
|
+
input("\nPress Enter to continue...")
|
|
1774
|
+
|
|
1775
|
+
except KeyboardInterrupt:
|
|
1776
|
+
print("\n\nš Expert interface closed. Have a great day!")
|
|
1777
|
+
|
|
1778
|
+
|
|
1779
|
+
# =============================================================================
|
|
1780
|
+
# š STARTUP FUNCTIONS
|
|
1781
|
+
# =============================================================================
|
|
1782
|
+
|
|
1783
|
+
|
|
1784
|
+
def start_http_api_server(port=9877):
|
|
1785
|
+
"""Start the HTTP API server for expert system communication"""
|
|
1786
|
+
if not FLASK_AVAILABLE:
|
|
1787
|
+
print("ā ļø Cannot start HTTP API server: Flask not installed")
|
|
1788
|
+
return
|
|
1789
|
+
|
|
1790
|
+
api_app = create_http_api_server()
|
|
1791
|
+
if api_app is None:
|
|
1792
|
+
print("ā ļø Failed to create HTTP API server")
|
|
1793
|
+
return
|
|
1794
|
+
|
|
1795
|
+
import threading
|
|
1796
|
+
|
|
1797
|
+
def run_api_server():
|
|
1798
|
+
print(f"š Starting HTTP API server on port {port}")
|
|
1799
|
+
print(f"š” API endpoints available at http://0.0.0.0:{port}/api/")
|
|
1800
|
+
try:
|
|
1801
|
+
api_app.run(host="0.0.0.0", port=port, debug=False, use_reloader=False)
|
|
1802
|
+
except Exception as e:
|
|
1803
|
+
print(f"ā HTTP API server failed: {e}")
|
|
1804
|
+
|
|
1805
|
+
# Start API server in background thread
|
|
1806
|
+
api_thread = threading.Thread(target=run_api_server, daemon=True)
|
|
1807
|
+
api_thread.start()
|
|
1808
|
+
|
|
1809
|
+
# Give the server a moment to start
|
|
1810
|
+
import time
|
|
1811
|
+
|
|
1812
|
+
time.sleep(1)
|
|
1813
|
+
|
|
1814
|
+
return api_thread
|
|
1815
|
+
|
|
1816
|
+
|
|
1817
|
+
def start_web_server():
|
|
1818
|
+
"""Start the Flask web server for expert interface"""
|
|
1819
|
+
if not FLASK_AVAILABLE:
|
|
1820
|
+
print("ā Cannot start web interface: Flask not installed")
|
|
1821
|
+
print(" Install with: pip install flask")
|
|
1822
|
+
return
|
|
1823
|
+
|
|
1824
|
+
app = create_web_app()
|
|
1825
|
+
if app:
|
|
1826
|
+
print("š Starting web interface on http://localhost:8090")
|
|
1827
|
+
app.run(host="0.0.0.0", port=8090, debug=False, use_reloader=False)
|
|
1828
|
+
|
|
1829
|
+
|
|
1830
|
+
def run_expert_interface():
|
|
1831
|
+
"""Run the terminal-based expert interface"""
|
|
1832
|
+
interface = ExpertInterface()
|
|
1833
|
+
interface.run()
|
|
1834
|
+
|
|
1835
|
+
|
|
1836
|
+
def open_web_interface():
|
|
1837
|
+
"""Open web interface in default browser"""
|
|
1838
|
+
if not FLASK_AVAILABLE:
|
|
1839
|
+
print("ā ļø Web interface not available (Flask not installed)")
|
|
1840
|
+
return
|
|
1841
|
+
|
|
1842
|
+
def open_browser():
|
|
1843
|
+
try:
|
|
1844
|
+
webbrowser.open("http://localhost:8090")
|
|
1845
|
+
except Exception as e:
|
|
1846
|
+
print(f"Could not open browser automatically: {str(e)}")
|
|
1847
|
+
print("Please manually open: http://localhost:8090")
|
|
1848
|
+
|
|
1849
|
+
# Delay browser opening to allow server to start
|
|
1850
|
+
Timer(2.0, open_browser).start()
|
|
1851
|
+
|
|
1852
|
+
|
|
1853
|
+
def start_monitoring_thread():
|
|
1854
|
+
"""Start background monitoring thread for system health"""
|
|
1855
|
+
|
|
1856
|
+
def monitor():
|
|
1857
|
+
while True:
|
|
1858
|
+
try:
|
|
1859
|
+
# Basic system monitoring
|
|
1860
|
+
pending_count = len(expert_system.get_pending_requests())
|
|
1861
|
+
if pending_count > 0:
|
|
1862
|
+
print(
|
|
1863
|
+
f"š {pending_count} pending expert request(s) - experts needed!"
|
|
1864
|
+
)
|
|
1865
|
+
time.sleep(30) # Check every 30 seconds
|
|
1866
|
+
except Exception as e:
|
|
1867
|
+
print(f"Monitoring error: {e}")
|
|
1868
|
+
time.sleep(60)
|
|
1869
|
+
|
|
1870
|
+
monitor_thread = threading.Thread(target=monitor, daemon=True)
|
|
1871
|
+
monitor_thread.start()
|
|
1872
|
+
|
|
1873
|
+
|
|
1874
|
+
# =============================================================================
|
|
1875
|
+
# šÆ MAIN ENTRY POINT
|
|
1876
|
+
# =============================================================================
|
|
1877
|
+
|
|
1878
|
+
|
|
1879
|
+
def main():
|
|
1880
|
+
"""Main entry point for console script"""
|
|
1881
|
+
parser = argparse.ArgumentParser(
|
|
1882
|
+
description="Human Expert Tools - MCP Registration System"
|
|
1883
|
+
)
|
|
1884
|
+
parser.add_argument(
|
|
1885
|
+
"--interface-only",
|
|
1886
|
+
action="store_true",
|
|
1887
|
+
help="Start only the expert terminal interface",
|
|
1888
|
+
)
|
|
1889
|
+
parser.add_argument(
|
|
1890
|
+
"--web-only", action="store_true", help="Start only the web interface"
|
|
1891
|
+
)
|
|
1892
|
+
parser.add_argument(
|
|
1893
|
+
"--no-browser",
|
|
1894
|
+
action="store_true",
|
|
1895
|
+
help="Do not automatically open browser for web interface",
|
|
1896
|
+
)
|
|
1897
|
+
parser.add_argument(
|
|
1898
|
+
"--start-server",
|
|
1899
|
+
action="store_true",
|
|
1900
|
+
help="Start the MCP server with registered tools",
|
|
1901
|
+
)
|
|
1902
|
+
parser.add_argument(
|
|
1903
|
+
"--port",
|
|
1904
|
+
type=int,
|
|
1905
|
+
default=None,
|
|
1906
|
+
help="Specify the MCP server port (default: random available port)",
|
|
1907
|
+
)
|
|
1908
|
+
parser.add_argument(
|
|
1909
|
+
"--mcp-host",
|
|
1910
|
+
type=str,
|
|
1911
|
+
default="localhost",
|
|
1912
|
+
help="MCP server host for web interface to connect to (default: localhost)",
|
|
1913
|
+
)
|
|
1914
|
+
parser.add_argument(
|
|
1915
|
+
"--mcp-port",
|
|
1916
|
+
type=int,
|
|
1917
|
+
default=9876,
|
|
1918
|
+
help="MCP server port for web interface to connect to (default: 9876)",
|
|
1919
|
+
)
|
|
1920
|
+
args = parser.parse_args()
|
|
1921
|
+
|
|
1922
|
+
# Set environment variables for API server connection (used by web interface)
|
|
1923
|
+
import os
|
|
1924
|
+
|
|
1925
|
+
if args.mcp_host != "localhost":
|
|
1926
|
+
os.environ["EXPERT_FEEDBACK_API_HOST"] = args.mcp_host
|
|
1927
|
+
# Map MCP port to API port for backward compatibility
|
|
1928
|
+
api_port = args.mcp_port + 1 if args.mcp_port != 9876 else 9877
|
|
1929
|
+
os.environ["EXPERT_FEEDBACK_API_PORT"] = str(api_port)
|
|
1930
|
+
|
|
1931
|
+
if args.interface_only:
|
|
1932
|
+
# Run only the terminal expert interface
|
|
1933
|
+
print("š» Starting Expert Terminal Interface...")
|
|
1934
|
+
run_expert_interface()
|
|
1935
|
+
elif args.web_only:
|
|
1936
|
+
# Run only the web interface
|
|
1937
|
+
if not FLASK_AVAILABLE:
|
|
1938
|
+
print("ā Cannot start web interface: Flask not installed")
|
|
1939
|
+
print(" Install with: pip install flask")
|
|
1940
|
+
sys.exit(1)
|
|
1941
|
+
|
|
1942
|
+
print("š Starting Human Expert Web Interface...")
|
|
1943
|
+
print(f"š Will connect to API server at: {args.mcp_host}:{api_port}")
|
|
1944
|
+
print("š” To connect to a different API server, use:")
|
|
1945
|
+
print(
|
|
1946
|
+
" tooluniverse-expert-feedback --web-only --mcp-host <api-host> --mcp-port <mcp-port>"
|
|
1947
|
+
)
|
|
1948
|
+
print(" (API port will be automatically calculated as MCP port + 1)")
|
|
1949
|
+
if not args.no_browser:
|
|
1950
|
+
open_web_interface()
|
|
1951
|
+
start_web_server()
|
|
1952
|
+
elif args.start_server:
|
|
1953
|
+
# Determine the port to use
|
|
1954
|
+
port = args.port if args.port is not None else 9876
|
|
1955
|
+
|
|
1956
|
+
# Tools are already registered via decorators at module import time
|
|
1957
|
+
print("š Starting MCP Server with Expert Tools...")
|
|
1958
|
+
print(f"š MCP Server Port: {port}")
|
|
1959
|
+
print("š Registered tools:")
|
|
1960
|
+
print(" - consult_human_expert: Submit questions to human experts")
|
|
1961
|
+
print(" - get_expert_response: Check for expert responses")
|
|
1962
|
+
print(" - list_pending_expert_requests: View pending requests (for experts)")
|
|
1963
|
+
print(" - submit_expert_response: Submit expert responses (for experts)")
|
|
1964
|
+
print(" - get_expert_status: Get system status")
|
|
1965
|
+
|
|
1966
|
+
print("\nš” Starting HTTP API Server...")
|
|
1967
|
+
api_port = 9877 # Default API port
|
|
1968
|
+
try:
|
|
1969
|
+
start_http_api_server(api_port)
|
|
1970
|
+
print(f"ā
HTTP API server started on port {api_port}")
|
|
1971
|
+
print(f"š API endpoints: http://localhost:{api_port}/api/")
|
|
1972
|
+
except Exception as e:
|
|
1973
|
+
print(f"ā ļø HTTP API server failed to start: {e}")
|
|
1974
|
+
|
|
1975
|
+
print("\nš Starting background monitoring...")
|
|
1976
|
+
start_monitoring_thread()
|
|
1977
|
+
|
|
1978
|
+
print("\nšÆ Expert Interface Options:")
|
|
1979
|
+
print(
|
|
1980
|
+
f" š Web Interface: tooluniverse-expert-feedback --web-only --mcp-host localhost --mcp-port {api_port}"
|
|
1981
|
+
)
|
|
1982
|
+
print(" š» Terminal Interface: tooluniverse-expert-feedback --interface-only")
|
|
1983
|
+
print("\nš” For remote experts, use:")
|
|
1984
|
+
print(" export EXPERT_FEEDBACK_API_HOST=<this-server-ip>")
|
|
1985
|
+
print(f" export EXPERT_FEEDBACK_API_PORT={api_port}")
|
|
1986
|
+
|
|
1987
|
+
# Start the MCP server using the standard method
|
|
1988
|
+
print(f"\nā
Starting MCP server on port {port}...")
|
|
1989
|
+
start_mcp_server()
|
|
1990
|
+
else:
|
|
1991
|
+
# Default: show usage information
|
|
1992
|
+
print("š§āāļø Human Expert MCP Tools - Console Script")
|
|
1993
|
+
print("=" * 50)
|
|
1994
|
+
print("This tool provides human expert consultation through MCP protocol.")
|
|
1995
|
+
print()
|
|
1996
|
+
print("Usage options:")
|
|
1997
|
+
print(" --start-server Start MCP server with expert tools")
|
|
1998
|
+
print(" --port PORT Specify MCP server port (default: 9876)")
|
|
1999
|
+
print(" --interface-only Start expert terminal interface")
|
|
2000
|
+
print(" --web-only Start expert web interface")
|
|
2001
|
+
print(" --no-browser Don't auto-open browser (with --web-only)")
|
|
2002
|
+
print()
|
|
2003
|
+
print("Examples:")
|
|
2004
|
+
print(" tooluniverse-expert-feedback --start-server")
|
|
2005
|
+
print(" tooluniverse-expert-feedback --start-server --port 8000")
|
|
2006
|
+
print(" tooluniverse-expert-feedback --web-only")
|
|
2007
|
+
print()
|
|
2008
|
+
print("The tools are automatically registered and available when imported")
|
|
2009
|
+
print("by ToolUniverse or when the MCP server is started.")
|
|
2010
|
+
|
|
2011
|
+
|
|
2012
|
+
if __name__ == "__main__":
|
|
2013
|
+
main()
|