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,955 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Tool Graph Web UI
|
|
3
|
+
|
|
4
|
+
A Flask-based web application for visualizing and exploring the tool composition graph
|
|
5
|
+
generated by ToolGraphComposer.
|
|
6
|
+
"""
|
|
7
|
+
|
|
8
|
+
import json
|
|
9
|
+
import os
|
|
10
|
+
from flask import Flask, render_template, jsonify, request
|
|
11
|
+
import pickle
|
|
12
|
+
from typing import Dict, Any, List, Optional
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
class ToolGraphWebUI:
|
|
16
|
+
"""Web interface for visualizing tool composition graphs."""
|
|
17
|
+
|
|
18
|
+
def __init__(self, graph_data_path: Optional[str] = None):
|
|
19
|
+
self.app = Flask(__name__, template_folder="templates", static_folder="static")
|
|
20
|
+
self.graph_data: Optional[Dict[str, Any]] = None
|
|
21
|
+
self.graph_data_path = graph_data_path
|
|
22
|
+
|
|
23
|
+
self._setup_routes()
|
|
24
|
+
self._load_graph_data()
|
|
25
|
+
|
|
26
|
+
def _setup_routes(self):
|
|
27
|
+
"""Setup Flask routes."""
|
|
28
|
+
|
|
29
|
+
@self.app.route("/")
|
|
30
|
+
def index():
|
|
31
|
+
"""Main graph visualization page."""
|
|
32
|
+
return render_template("tool_graph.html")
|
|
33
|
+
|
|
34
|
+
@self.app.route("/api/graph")
|
|
35
|
+
def get_graph():
|
|
36
|
+
"""API endpoint to get graph data."""
|
|
37
|
+
if not self.graph_data:
|
|
38
|
+
return jsonify({"error": "No graph data loaded"}), 404
|
|
39
|
+
|
|
40
|
+
# Convert to format suitable for D3.js
|
|
41
|
+
d3_data = self._convert_to_d3_format(self.graph_data)
|
|
42
|
+
return jsonify(d3_data)
|
|
43
|
+
|
|
44
|
+
@self.app.route("/api/stats")
|
|
45
|
+
def get_stats():
|
|
46
|
+
"""API endpoint to get graph statistics."""
|
|
47
|
+
if not self.graph_data:
|
|
48
|
+
return jsonify({"error": "No graph data loaded"}), 404
|
|
49
|
+
|
|
50
|
+
return jsonify(self.graph_data.get("metadata", {}))
|
|
51
|
+
|
|
52
|
+
@self.app.route("/api/tool/<tool_id>")
|
|
53
|
+
def get_tool_details(tool_id):
|
|
54
|
+
"""API endpoint to get detailed information about a specific tool."""
|
|
55
|
+
if not self.graph_data:
|
|
56
|
+
return jsonify({"error": "No graph data loaded"}), 404
|
|
57
|
+
|
|
58
|
+
# Find the tool
|
|
59
|
+
tool = None
|
|
60
|
+
for node in self.graph_data.get("nodes", []):
|
|
61
|
+
if node.get("id") == tool_id:
|
|
62
|
+
tool = node
|
|
63
|
+
break
|
|
64
|
+
|
|
65
|
+
if not tool:
|
|
66
|
+
return jsonify({"error": "Tool not found"}), 404
|
|
67
|
+
|
|
68
|
+
# Get connections
|
|
69
|
+
connections = self._get_tool_connections(tool_id)
|
|
70
|
+
|
|
71
|
+
return jsonify({"tool": tool, "connections": connections})
|
|
72
|
+
|
|
73
|
+
@self.app.route("/api/search")
|
|
74
|
+
def search_tools():
|
|
75
|
+
"""API endpoint to search tools."""
|
|
76
|
+
query = request.args.get("q", "").lower()
|
|
77
|
+
category = request.args.get("category", "")
|
|
78
|
+
|
|
79
|
+
if not self.graph_data:
|
|
80
|
+
return jsonify({"error": "No graph data loaded"}), 404
|
|
81
|
+
|
|
82
|
+
results = []
|
|
83
|
+
for node in self.graph_data.get("nodes", []):
|
|
84
|
+
# Search in name and description
|
|
85
|
+
if (
|
|
86
|
+
query in node.get("name", "").lower()
|
|
87
|
+
or query in node.get("description", "").lower()
|
|
88
|
+
):
|
|
89
|
+
if not category or node.get("category") == category:
|
|
90
|
+
results.append(
|
|
91
|
+
{
|
|
92
|
+
"id": node.get("id"),
|
|
93
|
+
"name": node.get("name"),
|
|
94
|
+
"description": node.get("description"),
|
|
95
|
+
"category": node.get("category"),
|
|
96
|
+
}
|
|
97
|
+
)
|
|
98
|
+
|
|
99
|
+
return jsonify(results)
|
|
100
|
+
|
|
101
|
+
@self.app.route("/api/load_graph", methods=["POST"])
|
|
102
|
+
def load_graph():
|
|
103
|
+
"""API endpoint to load a new graph file."""
|
|
104
|
+
data = request.json
|
|
105
|
+
graph_path = data.get("path")
|
|
106
|
+
|
|
107
|
+
if not graph_path or not os.path.exists(graph_path):
|
|
108
|
+
return jsonify({"error": "Invalid graph path"}), 400
|
|
109
|
+
|
|
110
|
+
try:
|
|
111
|
+
self.graph_data_path = graph_path
|
|
112
|
+
self._load_graph_data()
|
|
113
|
+
return jsonify(
|
|
114
|
+
{"status": "success", "message": "Graph loaded successfully"}
|
|
115
|
+
)
|
|
116
|
+
except Exception as e:
|
|
117
|
+
return jsonify({"error": f"Failed to load graph: {str(e)}"}), 500
|
|
118
|
+
|
|
119
|
+
def _load_graph_data(self):
|
|
120
|
+
"""Load graph data from file."""
|
|
121
|
+
if not self.graph_data_path:
|
|
122
|
+
print("No graph data path specified")
|
|
123
|
+
return
|
|
124
|
+
|
|
125
|
+
try:
|
|
126
|
+
# Try to load JSON first
|
|
127
|
+
if self.graph_data_path.endswith(".json"):
|
|
128
|
+
with open(self.graph_data_path, "r", encoding="utf-8") as f:
|
|
129
|
+
self.graph_data = json.load(f)
|
|
130
|
+
# Try pickle format
|
|
131
|
+
elif self.graph_data_path.endswith(".pkl"):
|
|
132
|
+
with open(self.graph_data_path, "rb") as f:
|
|
133
|
+
self.graph_data = pickle.load(f)
|
|
134
|
+
# Handle cached format
|
|
135
|
+
if (
|
|
136
|
+
isinstance(self.graph_data, dict)
|
|
137
|
+
and "graph_data" in self.graph_data
|
|
138
|
+
):
|
|
139
|
+
self.graph_data = self.graph_data["graph_data"]
|
|
140
|
+
else:
|
|
141
|
+
print(f"Unsupported file format: {self.graph_data_path}")
|
|
142
|
+
return
|
|
143
|
+
|
|
144
|
+
print(
|
|
145
|
+
f"Loaded graph with {len(self.graph_data.get('nodes', []))} nodes and {len(self.graph_data.get('edges', []))} edges"
|
|
146
|
+
)
|
|
147
|
+
|
|
148
|
+
except Exception as e:
|
|
149
|
+
print(f"Error loading graph data: {e}")
|
|
150
|
+
self.graph_data = None
|
|
151
|
+
|
|
152
|
+
def _convert_to_d3_format(self, graph_data: Dict[str, Any]) -> Dict[str, Any]:
|
|
153
|
+
"""Convert graph data to D3.js compatible format."""
|
|
154
|
+
|
|
155
|
+
nodes = []
|
|
156
|
+
links = []
|
|
157
|
+
|
|
158
|
+
# Process nodes
|
|
159
|
+
for node in graph_data.get("nodes", []):
|
|
160
|
+
d3_node = {
|
|
161
|
+
"id": node.get("id"),
|
|
162
|
+
"name": node.get("name"),
|
|
163
|
+
"description": (
|
|
164
|
+
node.get("description")[:100] + "..."
|
|
165
|
+
if len(node.get("description", "")) > 100
|
|
166
|
+
else node.get("description", "")
|
|
167
|
+
),
|
|
168
|
+
"category": node.get("category"),
|
|
169
|
+
"type": node.get("type"),
|
|
170
|
+
"group": self._get_category_group(node.get("category")),
|
|
171
|
+
}
|
|
172
|
+
nodes.append(d3_node)
|
|
173
|
+
|
|
174
|
+
# Process edges
|
|
175
|
+
for edge in graph_data.get("edges", []):
|
|
176
|
+
d3_link = {
|
|
177
|
+
"source": edge.get("source"),
|
|
178
|
+
"target": edge.get("target"),
|
|
179
|
+
"value": edge.get("compatibility_score", 50),
|
|
180
|
+
"confidence": edge.get("confidence", 50),
|
|
181
|
+
"automation_ready": edge.get("automation_ready", False),
|
|
182
|
+
"needs_transformation": edge.get("needs_transformation", False),
|
|
183
|
+
}
|
|
184
|
+
links.append(d3_link)
|
|
185
|
+
|
|
186
|
+
return {
|
|
187
|
+
"nodes": nodes,
|
|
188
|
+
"links": links,
|
|
189
|
+
"metadata": graph_data.get("metadata", {}),
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
def _get_category_group(self, category: str) -> int:
|
|
193
|
+
"""Map category to a numeric group for visualization."""
|
|
194
|
+
category_groups = {
|
|
195
|
+
"opentarget": 1,
|
|
196
|
+
"ChEMBL": 2,
|
|
197
|
+
"fda_drug_label": 3,
|
|
198
|
+
"clinical_trials": 4,
|
|
199
|
+
"EuropePMC": 5,
|
|
200
|
+
"semantic_scholar": 6,
|
|
201
|
+
"pubtator": 7,
|
|
202
|
+
"monarch": 8,
|
|
203
|
+
"agents": 9,
|
|
204
|
+
"dataset": 10,
|
|
205
|
+
"special_tools": 11,
|
|
206
|
+
}
|
|
207
|
+
return category_groups.get(category, 0)
|
|
208
|
+
|
|
209
|
+
def _get_tool_connections(self, tool_id: str) -> Dict[str, List]:
|
|
210
|
+
"""Get incoming and outgoing connections for a tool."""
|
|
211
|
+
# Guard against missing graph data
|
|
212
|
+
if not self.graph_data:
|
|
213
|
+
return {"incoming": [], "outgoing": []}
|
|
214
|
+
|
|
215
|
+
incoming: List[Dict[str, Any]] = []
|
|
216
|
+
outgoing: List[Dict[str, Any]] = []
|
|
217
|
+
|
|
218
|
+
for edge in self.graph_data.get("edges", []):
|
|
219
|
+
if edge.get("target") == tool_id:
|
|
220
|
+
incoming.append(
|
|
221
|
+
{
|
|
222
|
+
"source": edge.get("source"),
|
|
223
|
+
"compatibility_score": edge.get("compatibility_score"),
|
|
224
|
+
"automation_ready": edge.get("automation_ready"),
|
|
225
|
+
"needs_transformation": edge.get("needs_transformation"),
|
|
226
|
+
}
|
|
227
|
+
)
|
|
228
|
+
elif edge.get("source") == tool_id:
|
|
229
|
+
outgoing.append(
|
|
230
|
+
{
|
|
231
|
+
"target": edge.get("target"),
|
|
232
|
+
"compatibility_score": edge.get("compatibility_score"),
|
|
233
|
+
"automation_ready": edge.get("automation_ready"),
|
|
234
|
+
"needs_transformation": edge.get("needs_transformation"),
|
|
235
|
+
}
|
|
236
|
+
)
|
|
237
|
+
|
|
238
|
+
return {"incoming": incoming, "outgoing": outgoing}
|
|
239
|
+
|
|
240
|
+
def run(self, host: str = "0.0.0.0", port: int = 5000, debug: bool = True):
|
|
241
|
+
"""Run the web application."""
|
|
242
|
+
print(f"Starting Tool Graph Web UI on http://{host}:{port}")
|
|
243
|
+
self.app.run(host=host, port=port, debug=debug)
|
|
244
|
+
|
|
245
|
+
|
|
246
|
+
def create_web_ui_files():
|
|
247
|
+
"""Create the necessary HTML, CSS, and JS files for the web UI."""
|
|
248
|
+
|
|
249
|
+
# Create directories
|
|
250
|
+
os.makedirs("templates", exist_ok=True)
|
|
251
|
+
os.makedirs("static", exist_ok=True)
|
|
252
|
+
|
|
253
|
+
# HTML template
|
|
254
|
+
html_content = """<!DOCTYPE html>
|
|
255
|
+
<html lang="en">
|
|
256
|
+
<head>
|
|
257
|
+
<meta charset="UTF-8">
|
|
258
|
+
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
259
|
+
<title>ToolUniverse Composition Graph</title>
|
|
260
|
+
<link rel="stylesheet" href="{{ url_for('static', filename='style.css') }}">
|
|
261
|
+
<script src="https://d3js.org/d3.v7.min.js"></script>
|
|
262
|
+
</head>
|
|
263
|
+
<body>
|
|
264
|
+
<div id="app">
|
|
265
|
+
<header>
|
|
266
|
+
<h1>ToolUniverse Composition Graph</h1>
|
|
267
|
+
<div class="controls">
|
|
268
|
+
<div class="search-container">
|
|
269
|
+
<input type="text" id="search" placeholder="Search tools...">
|
|
270
|
+
<select id="categoryFilter">
|
|
271
|
+
<option value="">All Categories</option>
|
|
272
|
+
</select>
|
|
273
|
+
</div>
|
|
274
|
+
<div class="view-controls">
|
|
275
|
+
<button id="resetZoom">Reset Zoom</button>
|
|
276
|
+
<button id="showStats">Statistics</button>
|
|
277
|
+
<label>
|
|
278
|
+
<input type="checkbox" id="showEdgeLabels"> Show Edge Labels
|
|
279
|
+
</label>
|
|
280
|
+
</div>
|
|
281
|
+
</div>
|
|
282
|
+
</header>
|
|
283
|
+
|
|
284
|
+
<main>
|
|
285
|
+
<div id="graph-container">
|
|
286
|
+
<svg id="graph"></svg>
|
|
287
|
+
</div>
|
|
288
|
+
|
|
289
|
+
<div id="sidebar">
|
|
290
|
+
<div id="tool-details" class="panel">
|
|
291
|
+
<h3>Tool Details</h3>
|
|
292
|
+
<div id="tool-info">
|
|
293
|
+
<p>Select a tool to view details</p>
|
|
294
|
+
</div>
|
|
295
|
+
</div>
|
|
296
|
+
|
|
297
|
+
<div id="graph-stats" class="panel" style="display: none;">
|
|
298
|
+
<h3>Graph Statistics</h3>
|
|
299
|
+
<div id="stats-content"></div>
|
|
300
|
+
</div>
|
|
301
|
+
</div>
|
|
302
|
+
</main>
|
|
303
|
+
</div>
|
|
304
|
+
|
|
305
|
+
<script src="{{ url_for('static', filename='graph.js') }}"></script>
|
|
306
|
+
</body>
|
|
307
|
+
</html>"""
|
|
308
|
+
|
|
309
|
+
# CSS styles
|
|
310
|
+
css_content = """
|
|
311
|
+
body {
|
|
312
|
+
margin: 0;
|
|
313
|
+
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
|
|
314
|
+
background-color: #f5f5f5;
|
|
315
|
+
}
|
|
316
|
+
|
|
317
|
+
#app {
|
|
318
|
+
display: flex;
|
|
319
|
+
flex-direction: column;
|
|
320
|
+
height: 100vh;
|
|
321
|
+
}
|
|
322
|
+
|
|
323
|
+
header {
|
|
324
|
+
background: white;
|
|
325
|
+
padding: 1rem;
|
|
326
|
+
border-bottom: 1px solid #ddd;
|
|
327
|
+
box-shadow: 0 2px 4px rgba(0,0,0,0.1);
|
|
328
|
+
}
|
|
329
|
+
|
|
330
|
+
header h1 {
|
|
331
|
+
margin: 0 0 1rem 0;
|
|
332
|
+
color: #333;
|
|
333
|
+
}
|
|
334
|
+
|
|
335
|
+
.controls {
|
|
336
|
+
display: flex;
|
|
337
|
+
justify-content: space-between;
|
|
338
|
+
align-items: center;
|
|
339
|
+
flex-wrap: wrap;
|
|
340
|
+
gap: 1rem;
|
|
341
|
+
}
|
|
342
|
+
|
|
343
|
+
.search-container {
|
|
344
|
+
display: flex;
|
|
345
|
+
gap: 0.5rem;
|
|
346
|
+
}
|
|
347
|
+
|
|
348
|
+
.search-container input, .search-container select {
|
|
349
|
+
padding: 0.5rem;
|
|
350
|
+
border: 1px solid #ddd;
|
|
351
|
+
border-radius: 4px;
|
|
352
|
+
}
|
|
353
|
+
|
|
354
|
+
.view-controls {
|
|
355
|
+
display: flex;
|
|
356
|
+
gap: 1rem;
|
|
357
|
+
align-items: center;
|
|
358
|
+
}
|
|
359
|
+
|
|
360
|
+
.view-controls button {
|
|
361
|
+
padding: 0.5rem 1rem;
|
|
362
|
+
background: #007bff;
|
|
363
|
+
color: white;
|
|
364
|
+
border: none;
|
|
365
|
+
border-radius: 4px;
|
|
366
|
+
cursor: pointer;
|
|
367
|
+
}
|
|
368
|
+
|
|
369
|
+
.view-controls button:hover {
|
|
370
|
+
background: #0056b3;
|
|
371
|
+
}
|
|
372
|
+
|
|
373
|
+
main {
|
|
374
|
+
display: flex;
|
|
375
|
+
flex: 1;
|
|
376
|
+
overflow: hidden;
|
|
377
|
+
}
|
|
378
|
+
|
|
379
|
+
#graph-container {
|
|
380
|
+
flex: 1;
|
|
381
|
+
position: relative;
|
|
382
|
+
}
|
|
383
|
+
|
|
384
|
+
#graph {
|
|
385
|
+
width: 100%;
|
|
386
|
+
height: 100%;
|
|
387
|
+
background: white;
|
|
388
|
+
}
|
|
389
|
+
|
|
390
|
+
#sidebar {
|
|
391
|
+
width: 300px;
|
|
392
|
+
background: white;
|
|
393
|
+
border-left: 1px solid #ddd;
|
|
394
|
+
overflow-y: auto;
|
|
395
|
+
padding: 1rem;
|
|
396
|
+
}
|
|
397
|
+
|
|
398
|
+
.panel {
|
|
399
|
+
margin-bottom: 2rem;
|
|
400
|
+
}
|
|
401
|
+
|
|
402
|
+
.panel h3 {
|
|
403
|
+
margin-top: 0;
|
|
404
|
+
color: #333;
|
|
405
|
+
border-bottom: 1px solid #eee;
|
|
406
|
+
padding-bottom: 0.5rem;
|
|
407
|
+
}
|
|
408
|
+
|
|
409
|
+
/* Graph styling */
|
|
410
|
+
.node {
|
|
411
|
+
stroke: #fff;
|
|
412
|
+
stroke-width: 2px;
|
|
413
|
+
cursor: pointer;
|
|
414
|
+
}
|
|
415
|
+
|
|
416
|
+
.node:hover {
|
|
417
|
+
stroke-width: 3px;
|
|
418
|
+
stroke: #333;
|
|
419
|
+
}
|
|
420
|
+
|
|
421
|
+
.node.selected {
|
|
422
|
+
stroke: #ff6b35;
|
|
423
|
+
stroke-width: 3px;
|
|
424
|
+
}
|
|
425
|
+
|
|
426
|
+
.link {
|
|
427
|
+
stroke: #999;
|
|
428
|
+
stroke-opacity: 0.6;
|
|
429
|
+
fill: none;
|
|
430
|
+
}
|
|
431
|
+
|
|
432
|
+
.link.high-compatibility {
|
|
433
|
+
stroke: #2ecc71;
|
|
434
|
+
stroke-width: 2px;
|
|
435
|
+
}
|
|
436
|
+
|
|
437
|
+
.link.medium-compatibility {
|
|
438
|
+
stroke: #f39c12;
|
|
439
|
+
stroke-width: 1.5px;
|
|
440
|
+
}
|
|
441
|
+
|
|
442
|
+
.link.low-compatibility {
|
|
443
|
+
stroke: #e74c3c;
|
|
444
|
+
stroke-width: 1px;
|
|
445
|
+
}
|
|
446
|
+
|
|
447
|
+
.node-label {
|
|
448
|
+
font-size: 10px;
|
|
449
|
+
font-family: sans-serif;
|
|
450
|
+
pointer-events: none;
|
|
451
|
+
}
|
|
452
|
+
|
|
453
|
+
.edge-label {
|
|
454
|
+
font-size: 8px;
|
|
455
|
+
font-family: sans-serif;
|
|
456
|
+
fill: #666;
|
|
457
|
+
pointer-events: none;
|
|
458
|
+
}
|
|
459
|
+
|
|
460
|
+
/* Tool details styling */
|
|
461
|
+
.tool-info-item {
|
|
462
|
+
margin-bottom: 1rem;
|
|
463
|
+
padding: 0.5rem;
|
|
464
|
+
background: #f8f9fa;
|
|
465
|
+
border-radius: 4px;
|
|
466
|
+
}
|
|
467
|
+
|
|
468
|
+
.tool-info-item strong {
|
|
469
|
+
display: block;
|
|
470
|
+
margin-bottom: 0.25rem;
|
|
471
|
+
color: #495057;
|
|
472
|
+
}
|
|
473
|
+
|
|
474
|
+
.connections {
|
|
475
|
+
margin-top: 1rem;
|
|
476
|
+
}
|
|
477
|
+
|
|
478
|
+
.connection-list {
|
|
479
|
+
max-height: 200px;
|
|
480
|
+
overflow-y: auto;
|
|
481
|
+
}
|
|
482
|
+
|
|
483
|
+
.connection-item {
|
|
484
|
+
padding: 0.25rem;
|
|
485
|
+
border-bottom: 1px solid #eee;
|
|
486
|
+
font-size: 0.9em;
|
|
487
|
+
}
|
|
488
|
+
|
|
489
|
+
.compatibility-score {
|
|
490
|
+
float: right;
|
|
491
|
+
font-weight: bold;
|
|
492
|
+
}
|
|
493
|
+
|
|
494
|
+
.score-high { color: #2ecc71; }
|
|
495
|
+
.score-medium { color: #f39c12; }
|
|
496
|
+
.score-low { color: #e74c3c; }
|
|
497
|
+
|
|
498
|
+
/* Responsive design */
|
|
499
|
+
@media (max-width: 768px) {
|
|
500
|
+
main {
|
|
501
|
+
flex-direction: column;
|
|
502
|
+
}
|
|
503
|
+
|
|
504
|
+
#sidebar {
|
|
505
|
+
width: 100%;
|
|
506
|
+
height: 40%;
|
|
507
|
+
border-left: none;
|
|
508
|
+
border-top: 1px solid #ddd;
|
|
509
|
+
}
|
|
510
|
+
|
|
511
|
+
.controls {
|
|
512
|
+
flex-direction: column;
|
|
513
|
+
align-items: stretch;
|
|
514
|
+
}
|
|
515
|
+
|
|
516
|
+
.view-controls {
|
|
517
|
+
justify-content: center;
|
|
518
|
+
}
|
|
519
|
+
}
|
|
520
|
+
"""
|
|
521
|
+
|
|
522
|
+
# JavaScript for D3.js visualization
|
|
523
|
+
js_content = """
|
|
524
|
+
class ToolGraphVisualizer {
|
|
525
|
+
constructor() {
|
|
526
|
+
this.svg = d3.select("#graph");
|
|
527
|
+
this.width = window.innerWidth - 300; // Account for sidebar
|
|
528
|
+
this.height = window.innerHeight - 100; // Account for header
|
|
529
|
+
this.simulation = null;
|
|
530
|
+
this.nodes = [];
|
|
531
|
+
this.links = [];
|
|
532
|
+
this.selectedNode = null;
|
|
533
|
+
|
|
534
|
+
this.initializeGraph();
|
|
535
|
+
this.loadGraphData();
|
|
536
|
+
this.setupEventListeners();
|
|
537
|
+
|
|
538
|
+
// Resize handler
|
|
539
|
+
window.addEventListener('resize', () => this.handleResize());
|
|
540
|
+
}
|
|
541
|
+
|
|
542
|
+
initializeGraph() {
|
|
543
|
+
this.svg
|
|
544
|
+
.attr("width", this.width)
|
|
545
|
+
.attr("height", this.height);
|
|
546
|
+
|
|
547
|
+
// Add zoom behavior
|
|
548
|
+
const zoom = d3.zoom()
|
|
549
|
+
.scaleExtent([0.1, 10])
|
|
550
|
+
.on("zoom", (event) => {
|
|
551
|
+
this.svg.select("g").attr("transform", event.transform);
|
|
552
|
+
});
|
|
553
|
+
|
|
554
|
+
this.svg.call(zoom);
|
|
555
|
+
|
|
556
|
+
// Create main group for zoomable content
|
|
557
|
+
this.graphGroup = this.svg.append("g");
|
|
558
|
+
|
|
559
|
+
// Create arrow markers for directed edges
|
|
560
|
+
this.svg.append("defs").append("marker")
|
|
561
|
+
.attr("id", "arrow")
|
|
562
|
+
.attr("viewBox", "0 -5 10 10")
|
|
563
|
+
.attr("refX", 20)
|
|
564
|
+
.attr("refY", 0)
|
|
565
|
+
.attr("markerWidth", 6)
|
|
566
|
+
.attr("markerHeight", 6)
|
|
567
|
+
.attr("orient", "auto")
|
|
568
|
+
.append("path")
|
|
569
|
+
.attr("d", "M0,-5L10,0L0,5")
|
|
570
|
+
.attr("fill", "#666");
|
|
571
|
+
}
|
|
572
|
+
|
|
573
|
+
async loadGraphData() {
|
|
574
|
+
try {
|
|
575
|
+
const response = await fetch('/api/graph');
|
|
576
|
+
const data = await response.json();
|
|
577
|
+
|
|
578
|
+
if (data.error) {
|
|
579
|
+
this.showError(data.error);
|
|
580
|
+
return;
|
|
581
|
+
}
|
|
582
|
+
|
|
583
|
+
this.nodes = data.nodes;
|
|
584
|
+
this.links = data.links;
|
|
585
|
+
|
|
586
|
+
this.updateCategoryFilter();
|
|
587
|
+
this.renderGraph();
|
|
588
|
+
|
|
589
|
+
} catch (error) {
|
|
590
|
+
this.showError(`Failed to load graph data: ${error.message}`);
|
|
591
|
+
}
|
|
592
|
+
}
|
|
593
|
+
|
|
594
|
+
updateCategoryFilter() {
|
|
595
|
+
const categories = [...new Set(this.nodes.map(n => n.category))].sort();
|
|
596
|
+
const select = d3.select("#categoryFilter");
|
|
597
|
+
|
|
598
|
+
select.selectAll("option:not(:first-child)").remove();
|
|
599
|
+
|
|
600
|
+
categories.forEach(category => {
|
|
601
|
+
select.append("option")
|
|
602
|
+
.attr("value", category)
|
|
603
|
+
.text(category);
|
|
604
|
+
});
|
|
605
|
+
}
|
|
606
|
+
|
|
607
|
+
renderGraph() {
|
|
608
|
+
// Clear existing graph
|
|
609
|
+
this.graphGroup.selectAll("*").remove();
|
|
610
|
+
|
|
611
|
+
// Create simulation
|
|
612
|
+
this.simulation = d3.forceSimulation(this.nodes)
|
|
613
|
+
.force("link", d3.forceLink(this.links).id(d => d.id).distance(80))
|
|
614
|
+
.force("charge", d3.forceManyBody().strength(-300))
|
|
615
|
+
.force("center", d3.forceCenter(this.width / 2, this.height / 2))
|
|
616
|
+
.force("collision", d3.forceCollide().radius(20));
|
|
617
|
+
|
|
618
|
+
// Create links
|
|
619
|
+
const link = this.graphGroup.append("g")
|
|
620
|
+
.attr("class", "links")
|
|
621
|
+
.selectAll("line")
|
|
622
|
+
.data(this.links)
|
|
623
|
+
.enter().append("line")
|
|
624
|
+
.attr("class", d => `link ${this.getCompatibilityClass(d.value)}`)
|
|
625
|
+
.attr("marker-end", "url(#arrow)");
|
|
626
|
+
|
|
627
|
+
// Create nodes
|
|
628
|
+
const node = this.graphGroup.append("g")
|
|
629
|
+
.attr("class", "nodes")
|
|
630
|
+
.selectAll("circle")
|
|
631
|
+
.data(this.nodes)
|
|
632
|
+
.enter().append("circle")
|
|
633
|
+
.attr("class", "node")
|
|
634
|
+
.attr("r", 8)
|
|
635
|
+
.attr("fill", d => this.getNodeColor(d.group))
|
|
636
|
+
.call(d3.drag()
|
|
637
|
+
.on("start", (event, d) => this.dragStarted(event, d))
|
|
638
|
+
.on("drag", (event, d) => this.dragged(event, d))
|
|
639
|
+
.on("end", (event, d) => this.dragEnded(event, d)))
|
|
640
|
+
.on("click", (event, d) => this.selectNode(d));
|
|
641
|
+
|
|
642
|
+
// Add node labels
|
|
643
|
+
const label = this.graphGroup.append("g")
|
|
644
|
+
.attr("class", "labels")
|
|
645
|
+
.selectAll("text")
|
|
646
|
+
.data(this.nodes)
|
|
647
|
+
.enter().append("text")
|
|
648
|
+
.attr("class", "node-label")
|
|
649
|
+
.attr("dx", 12)
|
|
650
|
+
.attr("dy", 4)
|
|
651
|
+
.text(d => d.name);
|
|
652
|
+
|
|
653
|
+
// Add simulation tick handler
|
|
654
|
+
this.simulation.on("tick", () => {
|
|
655
|
+
link
|
|
656
|
+
.attr("x1", d => d.source.x)
|
|
657
|
+
.attr("y1", d => d.source.y)
|
|
658
|
+
.attr("x2", d => d.target.x)
|
|
659
|
+
.attr("y2", d => d.target.y);
|
|
660
|
+
|
|
661
|
+
node
|
|
662
|
+
.attr("cx", d => d.x)
|
|
663
|
+
.attr("cy", d => d.y);
|
|
664
|
+
|
|
665
|
+
label
|
|
666
|
+
.attr("x", d => d.x)
|
|
667
|
+
.attr("y", d => d.y);
|
|
668
|
+
});
|
|
669
|
+
|
|
670
|
+
// Add tooltips
|
|
671
|
+
node.append("title")
|
|
672
|
+
.text(d => `${d.name}\\n${d.description}\\nCategory: ${d.category}`);
|
|
673
|
+
}
|
|
674
|
+
|
|
675
|
+
getCompatibilityClass(score) {
|
|
676
|
+
if (score >= 80) return "high-compatibility";
|
|
677
|
+
if (score >= 60) return "medium-compatibility";
|
|
678
|
+
return "low-compatibility";
|
|
679
|
+
}
|
|
680
|
+
|
|
681
|
+
getNodeColor(group) {
|
|
682
|
+
const colors = d3.schemeCategory10;
|
|
683
|
+
return colors[group % colors.length];
|
|
684
|
+
}
|
|
685
|
+
|
|
686
|
+
selectNode(node) {
|
|
687
|
+
// Update visual selection
|
|
688
|
+
this.graphGroup.selectAll(".node")
|
|
689
|
+
.classed("selected", false);
|
|
690
|
+
|
|
691
|
+
this.graphGroup.selectAll(".node")
|
|
692
|
+
.filter(d => d.id === node.id)
|
|
693
|
+
.classed("selected", true);
|
|
694
|
+
|
|
695
|
+
this.selectedNode = node;
|
|
696
|
+
this.loadToolDetails(node.id);
|
|
697
|
+
}
|
|
698
|
+
|
|
699
|
+
async loadToolDetails(toolId) {
|
|
700
|
+
try {
|
|
701
|
+
const response = await fetch(`/api/tool/${toolId}`);
|
|
702
|
+
const data = await response.json();
|
|
703
|
+
|
|
704
|
+
if (data.error) {
|
|
705
|
+
this.showError(data.error);
|
|
706
|
+
return;
|
|
707
|
+
}
|
|
708
|
+
|
|
709
|
+
this.displayToolDetails(data.tool, data.connections);
|
|
710
|
+
|
|
711
|
+
} catch (error) {
|
|
712
|
+
this.showError(`Failed to load tool details: ${error.message}`);
|
|
713
|
+
}
|
|
714
|
+
}
|
|
715
|
+
|
|
716
|
+
displayToolDetails(tool, connections) {
|
|
717
|
+
const container = d3.select("#tool-info");
|
|
718
|
+
container.html("");
|
|
719
|
+
|
|
720
|
+
// Basic tool information
|
|
721
|
+
container.append("div")
|
|
722
|
+
.attr("class", "tool-info-item")
|
|
723
|
+
.html(`<strong>Name:</strong> ${tool.name}`);
|
|
724
|
+
|
|
725
|
+
container.append("div")
|
|
726
|
+
.attr("class", "tool-info-item")
|
|
727
|
+
.html(`<strong>Type:</strong> ${tool.type}`);
|
|
728
|
+
|
|
729
|
+
container.append("div")
|
|
730
|
+
.attr("class", "tool-info-item")
|
|
731
|
+
.html(`<strong>Category:</strong> ${tool.category}`);
|
|
732
|
+
|
|
733
|
+
container.append("div")
|
|
734
|
+
.attr("class", "tool-info-item")
|
|
735
|
+
.html(`<strong>Description:</strong> ${tool.description}`);
|
|
736
|
+
|
|
737
|
+
// Connections
|
|
738
|
+
const connectionsDiv = container.append("div")
|
|
739
|
+
.attr("class", "connections");
|
|
740
|
+
|
|
741
|
+
// Incoming connections
|
|
742
|
+
if (connections.incoming.length > 0) {
|
|
743
|
+
connectionsDiv.append("h4").text("Can receive input from:");
|
|
744
|
+
const incomingList = connectionsDiv.append("div")
|
|
745
|
+
.attr("class", "connection-list");
|
|
746
|
+
|
|
747
|
+
connections.incoming.forEach(conn => {
|
|
748
|
+
const item = incomingList.append("div")
|
|
749
|
+
.attr("class", "connection-item");
|
|
750
|
+
|
|
751
|
+
item.append("span").text(conn.source);
|
|
752
|
+
item.append("span")
|
|
753
|
+
.attr("class", `compatibility-score ${this.getScoreClass(conn.compatibility_score)}`)
|
|
754
|
+
.text(`${conn.compatibility_score}%`);
|
|
755
|
+
});
|
|
756
|
+
}
|
|
757
|
+
|
|
758
|
+
// Outgoing connections
|
|
759
|
+
if (connections.outgoing.length > 0) {
|
|
760
|
+
connectionsDiv.append("h4").text("Can provide input to:");
|
|
761
|
+
const outgoingList = connectionsDiv.append("div")
|
|
762
|
+
.attr("class", "connection-list");
|
|
763
|
+
|
|
764
|
+
connections.outgoing.forEach(conn => {
|
|
765
|
+
const item = outgoingList.append("div")
|
|
766
|
+
.attr("class", "connection-item");
|
|
767
|
+
|
|
768
|
+
item.append("span").text(conn.target);
|
|
769
|
+
item.append("span")
|
|
770
|
+
.attr("class", `compatibility-score ${this.getScoreClass(conn.compatibility_score)}`)
|
|
771
|
+
.text(`${conn.compatibility_score}%`);
|
|
772
|
+
});
|
|
773
|
+
}
|
|
774
|
+
}
|
|
775
|
+
|
|
776
|
+
getScoreClass(score) {
|
|
777
|
+
if (score >= 80) return "score-high";
|
|
778
|
+
if (score >= 60) return "score-medium";
|
|
779
|
+
return "score-low";
|
|
780
|
+
}
|
|
781
|
+
|
|
782
|
+
setupEventListeners() {
|
|
783
|
+
// Search functionality
|
|
784
|
+
d3.select("#search").on("input", (event) => {
|
|
785
|
+
this.searchTools(event.target.value);
|
|
786
|
+
});
|
|
787
|
+
|
|
788
|
+
// Category filter
|
|
789
|
+
d3.select("#categoryFilter").on("change", (event) => {
|
|
790
|
+
this.filterByCategory(event.target.value);
|
|
791
|
+
});
|
|
792
|
+
|
|
793
|
+
// Reset zoom
|
|
794
|
+
d3.select("#resetZoom").on("click", () => {
|
|
795
|
+
this.svg.transition().duration(750).call(
|
|
796
|
+
d3.zoom().transform,
|
|
797
|
+
d3.zoomIdentity
|
|
798
|
+
);
|
|
799
|
+
});
|
|
800
|
+
|
|
801
|
+
// Show statistics
|
|
802
|
+
d3.select("#showStats").on("click", () => {
|
|
803
|
+
this.toggleStats();
|
|
804
|
+
});
|
|
805
|
+
|
|
806
|
+
// Edge labels toggle
|
|
807
|
+
d3.select("#showEdgeLabels").on("change", (event) => {
|
|
808
|
+
this.toggleEdgeLabels(event.target.checked);
|
|
809
|
+
});
|
|
810
|
+
}
|
|
811
|
+
|
|
812
|
+
searchTools(query) {
|
|
813
|
+
if (!query) {
|
|
814
|
+
this.graphGroup.selectAll(".node").style("opacity", 1);
|
|
815
|
+
this.graphGroup.selectAll(".node-label").style("opacity", 1);
|
|
816
|
+
return;
|
|
817
|
+
}
|
|
818
|
+
|
|
819
|
+
const queryLower = query.toLowerCase();
|
|
820
|
+
this.graphGroup.selectAll(".node")
|
|
821
|
+
.style("opacity", d =>
|
|
822
|
+
d.name.toLowerCase().includes(queryLower) ||
|
|
823
|
+
d.description.toLowerCase().includes(queryLower) ? 1 : 0.2
|
|
824
|
+
);
|
|
825
|
+
|
|
826
|
+
this.graphGroup.selectAll(".node-label")
|
|
827
|
+
.style("opacity", d =>
|
|
828
|
+
d.name.toLowerCase().includes(queryLower) ||
|
|
829
|
+
d.description.toLowerCase().includes(queryLower) ? 1 : 0.2
|
|
830
|
+
);
|
|
831
|
+
}
|
|
832
|
+
|
|
833
|
+
filterByCategory(category) {
|
|
834
|
+
if (!category) {
|
|
835
|
+
this.graphGroup.selectAll(".node").style("display", "block");
|
|
836
|
+
this.graphGroup.selectAll(".node-label").style("display", "block");
|
|
837
|
+
return;
|
|
838
|
+
}
|
|
839
|
+
|
|
840
|
+
this.graphGroup.selectAll(".node")
|
|
841
|
+
.style("display", d => d.category === category ? "block" : "none");
|
|
842
|
+
|
|
843
|
+
this.graphGroup.selectAll(".node-label")
|
|
844
|
+
.style("display", d => d.category === category ? "block" : "none");
|
|
845
|
+
}
|
|
846
|
+
|
|
847
|
+
async toggleStats() {
|
|
848
|
+
const statsPanel = d3.select("#graph-stats");
|
|
849
|
+
const isVisible = statsPanel.style("display") !== "none";
|
|
850
|
+
|
|
851
|
+
if (isVisible) {
|
|
852
|
+
statsPanel.style("display", "none");
|
|
853
|
+
return;
|
|
854
|
+
}
|
|
855
|
+
|
|
856
|
+
try {
|
|
857
|
+
const response = await fetch('/api/stats');
|
|
858
|
+
const stats = await response.json();
|
|
859
|
+
|
|
860
|
+
this.displayStats(stats);
|
|
861
|
+
statsPanel.style("display", "block");
|
|
862
|
+
|
|
863
|
+
} catch (error) {
|
|
864
|
+
this.showError(`Failed to load statistics: ${error.message}`);
|
|
865
|
+
}
|
|
866
|
+
}
|
|
867
|
+
|
|
868
|
+
displayStats(stats) {
|
|
869
|
+
const container = d3.select("#stats-content");
|
|
870
|
+
container.html("");
|
|
871
|
+
|
|
872
|
+
container.append("p").html(`<strong>Total Tools:</strong> ${stats.total_tools || 'N/A'}`);
|
|
873
|
+
container.append("p").html(`<strong>Analysis Depth:</strong> ${stats.analysis_depth || 'N/A'}`);
|
|
874
|
+
container.append("p").html(`<strong>Min Compatibility Score:</strong> ${stats.min_compatibility_score || 'N/A'}`);
|
|
875
|
+
container.append("p").html(`<strong>Created:</strong> ${stats.creation_time || 'N/A'}`);
|
|
876
|
+
}
|
|
877
|
+
|
|
878
|
+
toggleEdgeLabels(show) {
|
|
879
|
+
// Implementation for edge labels toggle
|
|
880
|
+
// This would require additional data binding for edge labels
|
|
881
|
+
console.log("Edge labels toggle:", show);
|
|
882
|
+
}
|
|
883
|
+
|
|
884
|
+
dragStarted(event, d) {
|
|
885
|
+
if (!event.active) this.simulation.alphaTarget(0.3).restart();
|
|
886
|
+
d.fx = d.x;
|
|
887
|
+
d.fy = d.y;
|
|
888
|
+
}
|
|
889
|
+
|
|
890
|
+
dragged(event, d) {
|
|
891
|
+
d.fx = event.x;
|
|
892
|
+
d.fy = event.y;
|
|
893
|
+
}
|
|
894
|
+
|
|
895
|
+
dragEnded(event, d) {
|
|
896
|
+
if (!event.active) this.simulation.alphaTarget(0);
|
|
897
|
+
d.fx = null;
|
|
898
|
+
d.fy = null;
|
|
899
|
+
}
|
|
900
|
+
|
|
901
|
+
handleResize() {
|
|
902
|
+
this.width = window.innerWidth - 300;
|
|
903
|
+
this.height = window.innerHeight - 100;
|
|
904
|
+
|
|
905
|
+
this.svg
|
|
906
|
+
.attr("width", this.width)
|
|
907
|
+
.attr("height", this.height);
|
|
908
|
+
|
|
909
|
+
if (this.simulation) {
|
|
910
|
+
this.simulation
|
|
911
|
+
.force("center", d3.forceCenter(this.width / 2, this.height / 2))
|
|
912
|
+
.restart();
|
|
913
|
+
}
|
|
914
|
+
}
|
|
915
|
+
|
|
916
|
+
showError(message) {
|
|
917
|
+
console.error(message);
|
|
918
|
+
d3.select("#tool-info").html(`<div style="color: red;">Error: ${message}</div>`);
|
|
919
|
+
}
|
|
920
|
+
}
|
|
921
|
+
|
|
922
|
+
// Initialize the visualizer when the page loads
|
|
923
|
+
document.addEventListener('DOMContentLoaded', () => {
|
|
924
|
+
new ToolGraphVisualizer();
|
|
925
|
+
});
|
|
926
|
+
"""
|
|
927
|
+
|
|
928
|
+
# Write files
|
|
929
|
+
with open("templates/tool_graph.html", "w", encoding="utf-8") as f:
|
|
930
|
+
f.write(html_content)
|
|
931
|
+
|
|
932
|
+
with open("static/style.css", "w", encoding="utf-8") as f:
|
|
933
|
+
f.write(css_content)
|
|
934
|
+
|
|
935
|
+
with open("static/graph.js", "w", encoding="utf-8") as f:
|
|
936
|
+
f.write(js_content)
|
|
937
|
+
|
|
938
|
+
print("Web UI files created successfully!")
|
|
939
|
+
|
|
940
|
+
|
|
941
|
+
if __name__ == "__main__":
|
|
942
|
+
import sys
|
|
943
|
+
|
|
944
|
+
if len(sys.argv) > 1:
|
|
945
|
+
graph_path = sys.argv[1]
|
|
946
|
+
else:
|
|
947
|
+
graph_path = "./tool_composition_graph.json"
|
|
948
|
+
|
|
949
|
+
# Create web UI files if they don't exist
|
|
950
|
+
if not os.path.exists("templates/tool_graph.html"):
|
|
951
|
+
create_web_ui_files()
|
|
952
|
+
|
|
953
|
+
# Start the web UI
|
|
954
|
+
ui = ToolGraphWebUI(graph_path)
|
|
955
|
+
ui.run()
|