tooluniverse 0.2.0__py3-none-any.whl → 1.0.1__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.

Files changed (186) hide show
  1. tooluniverse/__init__.py +340 -4
  2. tooluniverse/admetai_tool.py +84 -0
  3. tooluniverse/agentic_tool.py +563 -0
  4. tooluniverse/alphafold_tool.py +96 -0
  5. tooluniverse/base_tool.py +129 -6
  6. tooluniverse/boltz_tool.py +207 -0
  7. tooluniverse/chem_tool.py +192 -0
  8. tooluniverse/compose_scripts/__init__.py +1 -0
  9. tooluniverse/compose_scripts/biomarker_discovery.py +293 -0
  10. tooluniverse/compose_scripts/comprehensive_drug_discovery.py +186 -0
  11. tooluniverse/compose_scripts/drug_safety_analyzer.py +89 -0
  12. tooluniverse/compose_scripts/literature_tool.py +34 -0
  13. tooluniverse/compose_scripts/output_summarizer.py +279 -0
  14. tooluniverse/compose_scripts/tool_description_optimizer.py +681 -0
  15. tooluniverse/compose_scripts/tool_discover.py +705 -0
  16. tooluniverse/compose_scripts/tool_graph_composer.py +448 -0
  17. tooluniverse/compose_tool.py +371 -0
  18. tooluniverse/ctg_tool.py +1002 -0
  19. tooluniverse/custom_tool.py +81 -0
  20. tooluniverse/dailymed_tool.py +108 -0
  21. tooluniverse/data/admetai_tools.json +155 -0
  22. tooluniverse/data/adverse_event_tools.json +108 -0
  23. tooluniverse/data/agentic_tools.json +1156 -0
  24. tooluniverse/data/alphafold_tools.json +87 -0
  25. tooluniverse/data/boltz_tools.json +9 -0
  26. tooluniverse/data/chembl_tools.json +16 -0
  27. tooluniverse/data/clinicaltrials_gov_tools.json +326 -0
  28. tooluniverse/data/compose_tools.json +202 -0
  29. tooluniverse/data/dailymed_tools.json +70 -0
  30. tooluniverse/data/dataset_tools.json +646 -0
  31. tooluniverse/data/disease_target_score_tools.json +712 -0
  32. tooluniverse/data/efo_tools.json +17 -0
  33. tooluniverse/data/embedding_tools.json +319 -0
  34. tooluniverse/data/enrichr_tools.json +31 -0
  35. tooluniverse/data/europe_pmc_tools.json +22 -0
  36. tooluniverse/data/expert_feedback_tools.json +10 -0
  37. tooluniverse/data/fda_drug_adverse_event_tools.json +491 -0
  38. tooluniverse/data/fda_drug_labeling_tools.json +1 -1
  39. tooluniverse/data/fda_drugs_with_brand_generic_names_for_tool.py +76929 -148860
  40. tooluniverse/data/finder_tools.json +209 -0
  41. tooluniverse/data/gene_ontology_tools.json +113 -0
  42. tooluniverse/data/gwas_tools.json +1082 -0
  43. tooluniverse/data/hpa_tools.json +333 -0
  44. tooluniverse/data/humanbase_tools.json +47 -0
  45. tooluniverse/data/idmap_tools.json +74 -0
  46. tooluniverse/data/mcp_client_tools_example.json +113 -0
  47. tooluniverse/data/mcpautoloadertool_defaults.json +28 -0
  48. tooluniverse/data/medlineplus_tools.json +141 -0
  49. tooluniverse/data/monarch_tools.json +1 -1
  50. tooluniverse/data/openalex_tools.json +36 -0
  51. tooluniverse/data/opentarget_tools.json +1 -1
  52. tooluniverse/data/output_summarization_tools.json +101 -0
  53. tooluniverse/data/packages/bioinformatics_core_tools.json +1756 -0
  54. tooluniverse/data/packages/categorized_tools.txt +206 -0
  55. tooluniverse/data/packages/cheminformatics_tools.json +347 -0
  56. tooluniverse/data/packages/earth_sciences_tools.json +74 -0
  57. tooluniverse/data/packages/genomics_tools.json +776 -0
  58. tooluniverse/data/packages/image_processing_tools.json +38 -0
  59. tooluniverse/data/packages/machine_learning_tools.json +789 -0
  60. tooluniverse/data/packages/neuroscience_tools.json +62 -0
  61. tooluniverse/data/packages/original_tools.txt +0 -0
  62. tooluniverse/data/packages/physics_astronomy_tools.json +62 -0
  63. tooluniverse/data/packages/scientific_computing_tools.json +560 -0
  64. tooluniverse/data/packages/single_cell_tools.json +453 -0
  65. tooluniverse/data/packages/structural_biology_tools.json +396 -0
  66. tooluniverse/data/packages/visualization_tools.json +399 -0
  67. tooluniverse/data/pubchem_tools.json +215 -0
  68. tooluniverse/data/pubtator_tools.json +68 -0
  69. tooluniverse/data/rcsb_pdb_tools.json +1332 -0
  70. tooluniverse/data/reactome_tools.json +19 -0
  71. tooluniverse/data/semantic_scholar_tools.json +26 -0
  72. tooluniverse/data/special_tools.json +2 -25
  73. tooluniverse/data/tool_composition_tools.json +88 -0
  74. tooluniverse/data/toolfinderkeyword_defaults.json +34 -0
  75. tooluniverse/data/txagent_client_tools.json +9 -0
  76. tooluniverse/data/uniprot_tools.json +211 -0
  77. tooluniverse/data/url_fetch_tools.json +94 -0
  78. tooluniverse/data/uspto_downloader_tools.json +9 -0
  79. tooluniverse/data/uspto_tools.json +811 -0
  80. tooluniverse/data/xml_tools.json +3275 -0
  81. tooluniverse/dataset_tool.py +296 -0
  82. tooluniverse/default_config.py +165 -0
  83. tooluniverse/efo_tool.py +42 -0
  84. tooluniverse/embedding_database.py +630 -0
  85. tooluniverse/embedding_sync.py +396 -0
  86. tooluniverse/enrichr_tool.py +266 -0
  87. tooluniverse/europe_pmc_tool.py +52 -0
  88. tooluniverse/execute_function.py +1775 -95
  89. tooluniverse/extended_hooks.py +444 -0
  90. tooluniverse/gene_ontology_tool.py +194 -0
  91. tooluniverse/graphql_tool.py +158 -36
  92. tooluniverse/gwas_tool.py +358 -0
  93. tooluniverse/hpa_tool.py +1645 -0
  94. tooluniverse/humanbase_tool.py +389 -0
  95. tooluniverse/logging_config.py +254 -0
  96. tooluniverse/mcp_client_tool.py +764 -0
  97. tooluniverse/mcp_integration.py +413 -0
  98. tooluniverse/mcp_tool_registry.py +925 -0
  99. tooluniverse/medlineplus_tool.py +337 -0
  100. tooluniverse/openalex_tool.py +228 -0
  101. tooluniverse/openfda_adv_tool.py +283 -0
  102. tooluniverse/openfda_tool.py +393 -160
  103. tooluniverse/output_hook.py +1122 -0
  104. tooluniverse/package_tool.py +195 -0
  105. tooluniverse/pubchem_tool.py +158 -0
  106. tooluniverse/pubtator_tool.py +168 -0
  107. tooluniverse/rcsb_pdb_tool.py +38 -0
  108. tooluniverse/reactome_tool.py +108 -0
  109. tooluniverse/remote/boltz/boltz_mcp_server.py +50 -0
  110. tooluniverse/remote/depmap_24q2/depmap_24q2_mcp_tool.py +442 -0
  111. tooluniverse/remote/expert_feedback/human_expert_mcp_tools.py +2013 -0
  112. tooluniverse/remote/expert_feedback/simple_test.py +23 -0
  113. tooluniverse/remote/expert_feedback/start_web_interface.py +188 -0
  114. tooluniverse/remote/expert_feedback/web_only_interface.py +0 -0
  115. tooluniverse/remote/immune_compass/compass_tool.py +327 -0
  116. tooluniverse/remote/pinnacle/pinnacle_tool.py +328 -0
  117. tooluniverse/remote/transcriptformer/transcriptformer_tool.py +586 -0
  118. tooluniverse/remote/uspto_downloader/uspto_downloader_mcp_server.py +61 -0
  119. tooluniverse/remote/uspto_downloader/uspto_downloader_tool.py +120 -0
  120. tooluniverse/remote_tool.py +99 -0
  121. tooluniverse/restful_tool.py +53 -30
  122. tooluniverse/scripts/generate_tool_graph.py +408 -0
  123. tooluniverse/scripts/visualize_tool_graph.py +829 -0
  124. tooluniverse/semantic_scholar_tool.py +62 -0
  125. tooluniverse/smcp.py +2452 -0
  126. tooluniverse/smcp_server.py +975 -0
  127. tooluniverse/test/mcp_server_test.py +0 -0
  128. tooluniverse/test/test_admetai_tool.py +370 -0
  129. tooluniverse/test/test_agentic_tool.py +129 -0
  130. tooluniverse/test/test_alphafold_tool.py +71 -0
  131. tooluniverse/test/test_chem_tool.py +37 -0
  132. tooluniverse/test/test_compose_lieraturereview.py +63 -0
  133. tooluniverse/test/test_compose_tool.py +448 -0
  134. tooluniverse/test/test_dailymed.py +69 -0
  135. tooluniverse/test/test_dataset_tool.py +200 -0
  136. tooluniverse/test/test_disease_target_score.py +56 -0
  137. tooluniverse/test/test_drugbank_filter_examples.py +179 -0
  138. tooluniverse/test/test_efo.py +31 -0
  139. tooluniverse/test/test_enrichr_tool.py +21 -0
  140. tooluniverse/test/test_europe_pmc_tool.py +20 -0
  141. tooluniverse/test/test_fda_adv.py +95 -0
  142. tooluniverse/test/test_fda_drug_labeling.py +91 -0
  143. tooluniverse/test/test_gene_ontology_tools.py +66 -0
  144. tooluniverse/test/test_gwas_tool.py +139 -0
  145. tooluniverse/test/test_hpa.py +625 -0
  146. tooluniverse/test/test_humanbase_tool.py +20 -0
  147. tooluniverse/test/test_idmap_tools.py +61 -0
  148. tooluniverse/test/test_mcp_server.py +211 -0
  149. tooluniverse/test/test_mcp_tool.py +247 -0
  150. tooluniverse/test/test_medlineplus.py +220 -0
  151. tooluniverse/test/test_openalex_tool.py +32 -0
  152. tooluniverse/test/test_opentargets.py +28 -0
  153. tooluniverse/test/test_pubchem_tool.py +116 -0
  154. tooluniverse/test/test_pubtator_tool.py +37 -0
  155. tooluniverse/test/test_rcsb_pdb_tool.py +86 -0
  156. tooluniverse/test/test_reactome.py +54 -0
  157. tooluniverse/test/test_semantic_scholar_tool.py +24 -0
  158. tooluniverse/test/test_software_tools.py +147 -0
  159. tooluniverse/test/test_tool_description_optimizer.py +49 -0
  160. tooluniverse/test/test_tool_finder.py +26 -0
  161. tooluniverse/test/test_tool_finder_llm.py +252 -0
  162. tooluniverse/test/test_tools_find.py +195 -0
  163. tooluniverse/test/test_uniprot_tools.py +74 -0
  164. tooluniverse/test/test_uspto_tool.py +72 -0
  165. tooluniverse/test/test_xml_tool.py +113 -0
  166. tooluniverse/tool_finder_embedding.py +267 -0
  167. tooluniverse/tool_finder_keyword.py +693 -0
  168. tooluniverse/tool_finder_llm.py +699 -0
  169. tooluniverse/tool_graph_web_ui.py +955 -0
  170. tooluniverse/tool_registry.py +416 -0
  171. tooluniverse/uniprot_tool.py +155 -0
  172. tooluniverse/url_tool.py +253 -0
  173. tooluniverse/uspto_tool.py +240 -0
  174. tooluniverse/utils.py +369 -41
  175. tooluniverse/xml_tool.py +369 -0
  176. tooluniverse-1.0.1.dist-info/METADATA +387 -0
  177. tooluniverse-1.0.1.dist-info/RECORD +182 -0
  178. tooluniverse-1.0.1.dist-info/entry_points.txt +9 -0
  179. tooluniverse/generate_mcp_tools.py +0 -113
  180. tooluniverse/mcp_server.py +0 -3340
  181. tooluniverse-0.2.0.dist-info/METADATA +0 -139
  182. tooluniverse-0.2.0.dist-info/RECORD +0 -21
  183. tooluniverse-0.2.0.dist-info/entry_points.txt +0 -4
  184. {tooluniverse-0.2.0.dist-info → tooluniverse-1.0.1.dist-info}/WHEEL +0 -0
  185. {tooluniverse-0.2.0.dist-info → tooluniverse-1.0.1.dist-info}/licenses/LICENSE +0 -0
  186. {tooluniverse-0.2.0.dist-info → tooluniverse-1.0.1.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()