alita-sdk 0.3.257__py3-none-any.whl → 0.3.584__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 alita-sdk might be problematic. Click here for more details.

Files changed (281) hide show
  1. alita_sdk/cli/__init__.py +10 -0
  2. alita_sdk/cli/__main__.py +17 -0
  3. alita_sdk/cli/agent/__init__.py +5 -0
  4. alita_sdk/cli/agent/default.py +258 -0
  5. alita_sdk/cli/agent_executor.py +155 -0
  6. alita_sdk/cli/agent_loader.py +215 -0
  7. alita_sdk/cli/agent_ui.py +228 -0
  8. alita_sdk/cli/agents.py +3794 -0
  9. alita_sdk/cli/callbacks.py +647 -0
  10. alita_sdk/cli/cli.py +168 -0
  11. alita_sdk/cli/config.py +306 -0
  12. alita_sdk/cli/context/__init__.py +30 -0
  13. alita_sdk/cli/context/cleanup.py +198 -0
  14. alita_sdk/cli/context/manager.py +731 -0
  15. alita_sdk/cli/context/message.py +285 -0
  16. alita_sdk/cli/context/strategies.py +289 -0
  17. alita_sdk/cli/context/token_estimation.py +127 -0
  18. alita_sdk/cli/formatting.py +182 -0
  19. alita_sdk/cli/input_handler.py +419 -0
  20. alita_sdk/cli/inventory.py +1073 -0
  21. alita_sdk/cli/mcp_loader.py +315 -0
  22. alita_sdk/cli/toolkit.py +327 -0
  23. alita_sdk/cli/toolkit_loader.py +85 -0
  24. alita_sdk/cli/tools/__init__.py +43 -0
  25. alita_sdk/cli/tools/approval.py +224 -0
  26. alita_sdk/cli/tools/filesystem.py +1751 -0
  27. alita_sdk/cli/tools/planning.py +389 -0
  28. alita_sdk/cli/tools/terminal.py +414 -0
  29. alita_sdk/community/__init__.py +72 -12
  30. alita_sdk/community/inventory/__init__.py +236 -0
  31. alita_sdk/community/inventory/config.py +257 -0
  32. alita_sdk/community/inventory/enrichment.py +2137 -0
  33. alita_sdk/community/inventory/extractors.py +1469 -0
  34. alita_sdk/community/inventory/ingestion.py +3172 -0
  35. alita_sdk/community/inventory/knowledge_graph.py +1457 -0
  36. alita_sdk/community/inventory/parsers/__init__.py +218 -0
  37. alita_sdk/community/inventory/parsers/base.py +295 -0
  38. alita_sdk/community/inventory/parsers/csharp_parser.py +907 -0
  39. alita_sdk/community/inventory/parsers/go_parser.py +851 -0
  40. alita_sdk/community/inventory/parsers/html_parser.py +389 -0
  41. alita_sdk/community/inventory/parsers/java_parser.py +593 -0
  42. alita_sdk/community/inventory/parsers/javascript_parser.py +629 -0
  43. alita_sdk/community/inventory/parsers/kotlin_parser.py +768 -0
  44. alita_sdk/community/inventory/parsers/markdown_parser.py +362 -0
  45. alita_sdk/community/inventory/parsers/python_parser.py +604 -0
  46. alita_sdk/community/inventory/parsers/rust_parser.py +858 -0
  47. alita_sdk/community/inventory/parsers/swift_parser.py +832 -0
  48. alita_sdk/community/inventory/parsers/text_parser.py +322 -0
  49. alita_sdk/community/inventory/parsers/yaml_parser.py +370 -0
  50. alita_sdk/community/inventory/patterns/__init__.py +61 -0
  51. alita_sdk/community/inventory/patterns/ast_adapter.py +380 -0
  52. alita_sdk/community/inventory/patterns/loader.py +348 -0
  53. alita_sdk/community/inventory/patterns/registry.py +198 -0
  54. alita_sdk/community/inventory/presets.py +535 -0
  55. alita_sdk/community/inventory/retrieval.py +1403 -0
  56. alita_sdk/community/inventory/toolkit.py +173 -0
  57. alita_sdk/community/inventory/toolkit_utils.py +176 -0
  58. alita_sdk/community/inventory/visualize.py +1370 -0
  59. alita_sdk/configurations/__init__.py +11 -0
  60. alita_sdk/configurations/ado.py +148 -2
  61. alita_sdk/configurations/azure_search.py +1 -1
  62. alita_sdk/configurations/bigquery.py +1 -1
  63. alita_sdk/configurations/bitbucket.py +94 -2
  64. alita_sdk/configurations/browser.py +18 -0
  65. alita_sdk/configurations/carrier.py +19 -0
  66. alita_sdk/configurations/confluence.py +130 -1
  67. alita_sdk/configurations/delta_lake.py +1 -1
  68. alita_sdk/configurations/figma.py +76 -5
  69. alita_sdk/configurations/github.py +65 -1
  70. alita_sdk/configurations/gitlab.py +81 -0
  71. alita_sdk/configurations/google_places.py +17 -0
  72. alita_sdk/configurations/jira.py +103 -0
  73. alita_sdk/configurations/openapi.py +323 -0
  74. alita_sdk/configurations/postman.py +1 -1
  75. alita_sdk/configurations/qtest.py +72 -3
  76. alita_sdk/configurations/report_portal.py +115 -0
  77. alita_sdk/configurations/salesforce.py +19 -0
  78. alita_sdk/configurations/service_now.py +1 -12
  79. alita_sdk/configurations/sharepoint.py +167 -0
  80. alita_sdk/configurations/sonar.py +18 -0
  81. alita_sdk/configurations/sql.py +20 -0
  82. alita_sdk/configurations/testio.py +101 -0
  83. alita_sdk/configurations/testrail.py +88 -0
  84. alita_sdk/configurations/xray.py +94 -1
  85. alita_sdk/configurations/zephyr_enterprise.py +94 -1
  86. alita_sdk/configurations/zephyr_essential.py +95 -0
  87. alita_sdk/runtime/clients/artifact.py +21 -4
  88. alita_sdk/runtime/clients/client.py +458 -67
  89. alita_sdk/runtime/clients/mcp_discovery.py +342 -0
  90. alita_sdk/runtime/clients/mcp_manager.py +262 -0
  91. alita_sdk/runtime/clients/sandbox_client.py +352 -0
  92. alita_sdk/runtime/langchain/_constants_bkup.py +1318 -0
  93. alita_sdk/runtime/langchain/assistant.py +183 -43
  94. alita_sdk/runtime/langchain/constants.py +647 -1
  95. alita_sdk/runtime/langchain/document_loaders/AlitaDocxMammothLoader.py +315 -3
  96. alita_sdk/runtime/langchain/document_loaders/AlitaExcelLoader.py +209 -31
  97. alita_sdk/runtime/langchain/document_loaders/AlitaImageLoader.py +1 -1
  98. alita_sdk/runtime/langchain/document_loaders/AlitaJSONLinesLoader.py +77 -0
  99. alita_sdk/runtime/langchain/document_loaders/AlitaJSONLoader.py +10 -3
  100. alita_sdk/runtime/langchain/document_loaders/AlitaMarkdownLoader.py +66 -0
  101. alita_sdk/runtime/langchain/document_loaders/AlitaPDFLoader.py +79 -10
  102. alita_sdk/runtime/langchain/document_loaders/AlitaPowerPointLoader.py +52 -15
  103. alita_sdk/runtime/langchain/document_loaders/AlitaPythonLoader.py +9 -0
  104. alita_sdk/runtime/langchain/document_loaders/AlitaTableLoader.py +1 -4
  105. alita_sdk/runtime/langchain/document_loaders/AlitaTextLoader.py +15 -2
  106. alita_sdk/runtime/langchain/document_loaders/ImageParser.py +30 -0
  107. alita_sdk/runtime/langchain/document_loaders/constants.py +189 -41
  108. alita_sdk/runtime/langchain/interfaces/llm_processor.py +4 -2
  109. alita_sdk/runtime/langchain/langraph_agent.py +493 -105
  110. alita_sdk/runtime/langchain/utils.py +118 -8
  111. alita_sdk/runtime/llms/preloaded.py +2 -6
  112. alita_sdk/runtime/models/mcp_models.py +61 -0
  113. alita_sdk/runtime/skills/__init__.py +91 -0
  114. alita_sdk/runtime/skills/callbacks.py +498 -0
  115. alita_sdk/runtime/skills/discovery.py +540 -0
  116. alita_sdk/runtime/skills/executor.py +610 -0
  117. alita_sdk/runtime/skills/input_builder.py +371 -0
  118. alita_sdk/runtime/skills/models.py +330 -0
  119. alita_sdk/runtime/skills/registry.py +355 -0
  120. alita_sdk/runtime/skills/skill_runner.py +330 -0
  121. alita_sdk/runtime/toolkits/__init__.py +28 -0
  122. alita_sdk/runtime/toolkits/application.py +14 -4
  123. alita_sdk/runtime/toolkits/artifact.py +25 -9
  124. alita_sdk/runtime/toolkits/datasource.py +13 -6
  125. alita_sdk/runtime/toolkits/mcp.py +782 -0
  126. alita_sdk/runtime/toolkits/planning.py +178 -0
  127. alita_sdk/runtime/toolkits/skill_router.py +238 -0
  128. alita_sdk/runtime/toolkits/subgraph.py +11 -6
  129. alita_sdk/runtime/toolkits/tools.py +314 -70
  130. alita_sdk/runtime/toolkits/vectorstore.py +11 -5
  131. alita_sdk/runtime/tools/__init__.py +24 -0
  132. alita_sdk/runtime/tools/application.py +16 -4
  133. alita_sdk/runtime/tools/artifact.py +367 -33
  134. alita_sdk/runtime/tools/data_analysis.py +183 -0
  135. alita_sdk/runtime/tools/function.py +100 -4
  136. alita_sdk/runtime/tools/graph.py +81 -0
  137. alita_sdk/runtime/tools/image_generation.py +218 -0
  138. alita_sdk/runtime/tools/llm.py +1032 -177
  139. alita_sdk/runtime/tools/loop.py +3 -1
  140. alita_sdk/runtime/tools/loop_output.py +3 -1
  141. alita_sdk/runtime/tools/mcp_inspect_tool.py +284 -0
  142. alita_sdk/runtime/tools/mcp_remote_tool.py +181 -0
  143. alita_sdk/runtime/tools/mcp_server_tool.py +3 -1
  144. alita_sdk/runtime/tools/planning/__init__.py +36 -0
  145. alita_sdk/runtime/tools/planning/models.py +246 -0
  146. alita_sdk/runtime/tools/planning/wrapper.py +607 -0
  147. alita_sdk/runtime/tools/router.py +2 -1
  148. alita_sdk/runtime/tools/sandbox.py +375 -0
  149. alita_sdk/runtime/tools/skill_router.py +776 -0
  150. alita_sdk/runtime/tools/tool.py +3 -1
  151. alita_sdk/runtime/tools/vectorstore.py +69 -65
  152. alita_sdk/runtime/tools/vectorstore_base.py +163 -90
  153. alita_sdk/runtime/utils/AlitaCallback.py +137 -21
  154. alita_sdk/runtime/utils/constants.py +5 -1
  155. alita_sdk/runtime/utils/mcp_client.py +492 -0
  156. alita_sdk/runtime/utils/mcp_oauth.py +361 -0
  157. alita_sdk/runtime/utils/mcp_sse_client.py +434 -0
  158. alita_sdk/runtime/utils/mcp_tools_discovery.py +124 -0
  159. alita_sdk/runtime/utils/streamlit.py +41 -14
  160. alita_sdk/runtime/utils/toolkit_utils.py +28 -9
  161. alita_sdk/runtime/utils/utils.py +48 -0
  162. alita_sdk/tools/__init__.py +135 -37
  163. alita_sdk/tools/ado/__init__.py +2 -2
  164. alita_sdk/tools/ado/repos/__init__.py +16 -19
  165. alita_sdk/tools/ado/repos/repos_wrapper.py +12 -20
  166. alita_sdk/tools/ado/test_plan/__init__.py +27 -8
  167. alita_sdk/tools/ado/test_plan/test_plan_wrapper.py +56 -28
  168. alita_sdk/tools/ado/wiki/__init__.py +28 -12
  169. alita_sdk/tools/ado/wiki/ado_wrapper.py +114 -40
  170. alita_sdk/tools/ado/work_item/__init__.py +28 -12
  171. alita_sdk/tools/ado/work_item/ado_wrapper.py +95 -11
  172. alita_sdk/tools/advanced_jira_mining/__init__.py +13 -8
  173. alita_sdk/tools/aws/delta_lake/__init__.py +15 -11
  174. alita_sdk/tools/aws/delta_lake/tool.py +5 -1
  175. alita_sdk/tools/azure_ai/search/__init__.py +14 -8
  176. alita_sdk/tools/base/tool.py +5 -1
  177. alita_sdk/tools/base_indexer_toolkit.py +454 -110
  178. alita_sdk/tools/bitbucket/__init__.py +28 -19
  179. alita_sdk/tools/bitbucket/api_wrapper.py +285 -27
  180. alita_sdk/tools/bitbucket/cloud_api_wrapper.py +5 -5
  181. alita_sdk/tools/browser/__init__.py +41 -16
  182. alita_sdk/tools/browser/crawler.py +3 -1
  183. alita_sdk/tools/browser/utils.py +15 -6
  184. alita_sdk/tools/carrier/__init__.py +18 -17
  185. alita_sdk/tools/carrier/backend_reports_tool.py +8 -4
  186. alita_sdk/tools/carrier/excel_reporter.py +8 -4
  187. alita_sdk/tools/chunkers/__init__.py +3 -1
  188. alita_sdk/tools/chunkers/code/codeparser.py +1 -1
  189. alita_sdk/tools/chunkers/sematic/json_chunker.py +2 -1
  190. alita_sdk/tools/chunkers/sematic/markdown_chunker.py +97 -6
  191. alita_sdk/tools/chunkers/sematic/proposal_chunker.py +1 -1
  192. alita_sdk/tools/chunkers/universal_chunker.py +270 -0
  193. alita_sdk/tools/cloud/aws/__init__.py +12 -7
  194. alita_sdk/tools/cloud/azure/__init__.py +12 -7
  195. alita_sdk/tools/cloud/gcp/__init__.py +12 -7
  196. alita_sdk/tools/cloud/k8s/__init__.py +12 -7
  197. alita_sdk/tools/code/linter/__init__.py +10 -8
  198. alita_sdk/tools/code/loaders/codesearcher.py +3 -2
  199. alita_sdk/tools/code/sonar/__init__.py +21 -13
  200. alita_sdk/tools/code_indexer_toolkit.py +199 -0
  201. alita_sdk/tools/confluence/__init__.py +22 -14
  202. alita_sdk/tools/confluence/api_wrapper.py +197 -58
  203. alita_sdk/tools/confluence/loader.py +14 -2
  204. alita_sdk/tools/custom_open_api/__init__.py +12 -5
  205. alita_sdk/tools/elastic/__init__.py +11 -8
  206. alita_sdk/tools/elitea_base.py +546 -64
  207. alita_sdk/tools/figma/__init__.py +60 -11
  208. alita_sdk/tools/figma/api_wrapper.py +1400 -167
  209. alita_sdk/tools/figma/figma_client.py +73 -0
  210. alita_sdk/tools/figma/toon_tools.py +2748 -0
  211. alita_sdk/tools/github/__init__.py +18 -17
  212. alita_sdk/tools/github/api_wrapper.py +9 -26
  213. alita_sdk/tools/github/github_client.py +81 -12
  214. alita_sdk/tools/github/schemas.py +2 -1
  215. alita_sdk/tools/github/tool.py +5 -1
  216. alita_sdk/tools/gitlab/__init__.py +19 -13
  217. alita_sdk/tools/gitlab/api_wrapper.py +256 -80
  218. alita_sdk/tools/gitlab_org/__init__.py +14 -10
  219. alita_sdk/tools/google/bigquery/__init__.py +14 -13
  220. alita_sdk/tools/google/bigquery/tool.py +5 -1
  221. alita_sdk/tools/google_places/__init__.py +21 -11
  222. alita_sdk/tools/jira/__init__.py +22 -11
  223. alita_sdk/tools/jira/api_wrapper.py +315 -168
  224. alita_sdk/tools/keycloak/__init__.py +11 -8
  225. alita_sdk/tools/localgit/__init__.py +9 -3
  226. alita_sdk/tools/localgit/local_git.py +62 -54
  227. alita_sdk/tools/localgit/tool.py +5 -1
  228. alita_sdk/tools/memory/__init__.py +38 -14
  229. alita_sdk/tools/non_code_indexer_toolkit.py +7 -2
  230. alita_sdk/tools/ocr/__init__.py +11 -8
  231. alita_sdk/tools/openapi/__init__.py +491 -106
  232. alita_sdk/tools/openapi/api_wrapper.py +1357 -0
  233. alita_sdk/tools/openapi/tool.py +20 -0
  234. alita_sdk/tools/pandas/__init__.py +20 -12
  235. alita_sdk/tools/pandas/api_wrapper.py +40 -45
  236. alita_sdk/tools/pandas/dataframe/generator/base.py +3 -1
  237. alita_sdk/tools/postman/__init__.py +11 -11
  238. alita_sdk/tools/postman/api_wrapper.py +19 -8
  239. alita_sdk/tools/postman/postman_analysis.py +8 -1
  240. alita_sdk/tools/pptx/__init__.py +11 -10
  241. alita_sdk/tools/qtest/__init__.py +22 -14
  242. alita_sdk/tools/qtest/api_wrapper.py +1784 -88
  243. alita_sdk/tools/rally/__init__.py +13 -10
  244. alita_sdk/tools/report_portal/__init__.py +23 -16
  245. alita_sdk/tools/salesforce/__init__.py +22 -16
  246. alita_sdk/tools/servicenow/__init__.py +21 -16
  247. alita_sdk/tools/servicenow/api_wrapper.py +1 -1
  248. alita_sdk/tools/sharepoint/__init__.py +17 -14
  249. alita_sdk/tools/sharepoint/api_wrapper.py +179 -39
  250. alita_sdk/tools/sharepoint/authorization_helper.py +191 -1
  251. alita_sdk/tools/sharepoint/utils.py +8 -2
  252. alita_sdk/tools/slack/__init__.py +13 -8
  253. alita_sdk/tools/sql/__init__.py +22 -19
  254. alita_sdk/tools/sql/api_wrapper.py +71 -23
  255. alita_sdk/tools/testio/__init__.py +21 -13
  256. alita_sdk/tools/testrail/__init__.py +13 -11
  257. alita_sdk/tools/testrail/api_wrapper.py +214 -46
  258. alita_sdk/tools/utils/__init__.py +28 -4
  259. alita_sdk/tools/utils/content_parser.py +241 -55
  260. alita_sdk/tools/utils/text_operations.py +254 -0
  261. alita_sdk/tools/vector_adapters/VectorStoreAdapter.py +83 -27
  262. alita_sdk/tools/xray/__init__.py +18 -14
  263. alita_sdk/tools/xray/api_wrapper.py +58 -113
  264. alita_sdk/tools/yagmail/__init__.py +9 -3
  265. alita_sdk/tools/zephyr/__init__.py +12 -7
  266. alita_sdk/tools/zephyr_enterprise/__init__.py +16 -9
  267. alita_sdk/tools/zephyr_enterprise/api_wrapper.py +30 -15
  268. alita_sdk/tools/zephyr_essential/__init__.py +16 -10
  269. alita_sdk/tools/zephyr_essential/api_wrapper.py +297 -54
  270. alita_sdk/tools/zephyr_essential/client.py +6 -4
  271. alita_sdk/tools/zephyr_scale/__init__.py +13 -8
  272. alita_sdk/tools/zephyr_scale/api_wrapper.py +39 -31
  273. alita_sdk/tools/zephyr_squad/__init__.py +12 -7
  274. {alita_sdk-0.3.257.dist-info → alita_sdk-0.3.584.dist-info}/METADATA +184 -37
  275. alita_sdk-0.3.584.dist-info/RECORD +452 -0
  276. alita_sdk-0.3.584.dist-info/entry_points.txt +2 -0
  277. alita_sdk/tools/bitbucket/tools.py +0 -304
  278. alita_sdk-0.3.257.dist-info/RECORD +0 -343
  279. {alita_sdk-0.3.257.dist-info → alita_sdk-0.3.584.dist-info}/WHEEL +0 -0
  280. {alita_sdk-0.3.257.dist-info → alita_sdk-0.3.584.dist-info}/licenses/LICENSE +0 -0
  281. {alita_sdk-0.3.257.dist-info → alita_sdk-0.3.584.dist-info}/top_level.txt +0 -0
@@ -0,0 +1,1370 @@
1
+ """
2
+ Knowledge Graph Visualization Utility.
3
+
4
+ Generates an interactive HTML visualization of the knowledge graph
5
+ using D3.js force-directed graph layout.
6
+
7
+ Usage:
8
+ # From CLI
9
+ alita inventory visualize --graph ./graph.json --output ./graph.html
10
+
11
+ # From Python
12
+ from alita_sdk.community.inventory.visualize import generate_visualization
13
+ generate_visualization("./graph.json", "./graph.html")
14
+ """
15
+
16
+ import json
17
+ import logging
18
+ from pathlib import Path
19
+ from typing import Optional, Dict, Any, List
20
+
21
+ logger = logging.getLogger(__name__)
22
+
23
+ # Color palette for entity types based on ENTITY_TAXONOMY layers
24
+ # Each layer has its own distinct hue range to prevent overlap
25
+ TYPE_COLORS = {
26
+ # =========================================================================
27
+ # PRODUCT LAYER - Red/Pink/Orange hues (0-30° and 330-360°)
28
+ # =========================================================================
29
+ "epic": "#D32F2F", # Deep Red
30
+ "feature": "#E53935", # Red
31
+ "user_story": "#F44336", # Lighter Red
32
+ "screen": "#FF5722", # Deep Orange
33
+ "ux_flow": "#FF7043", # Orange
34
+ "ui_component": "#FF8A65", # Light Orange
35
+ "ui_field": "#FFAB91", # Peach
36
+
37
+ # =========================================================================
38
+ # DOMAIN LAYER - Green hues (90-150°)
39
+ # =========================================================================
40
+ "domain_entity": "#2E7D32", # Dark Green
41
+ "attribute": "#388E3C", # Green
42
+ "business_rule": "#43A047", # Medium Green
43
+ "business_event": "#4CAF50", # Light Green
44
+ "glossary_term": "#66BB6A", # Pale Green
45
+ "workflow": "#81C784", # Very Light Green
46
+
47
+ # =========================================================================
48
+ # SERVICE LAYER - Cyan/Teal hues (160-200°)
49
+ # =========================================================================
50
+ "service": "#00695C", # Dark Teal
51
+ "api": "#00796B", # Teal
52
+ "endpoint": "#00897B", # Medium Teal
53
+ "payload": "#009688", # Light Teal
54
+ "integration": "#26A69A", # Pale Teal
55
+
56
+ # =========================================================================
57
+ # CODE LAYER - Blue hues (200-240°)
58
+ # =========================================================================
59
+ "module": "#1565C0", # Dark Blue
60
+ "class": "#1976D2", # Blue
61
+ "function": "#1E88E5", # Medium Blue
62
+ "interface": "#2196F3", # Light Blue
63
+ "constant": "#42A5F5", # Pale Blue
64
+ "configuration": "#64B5F6", # Very Light Blue
65
+
66
+ # =========================================================================
67
+ # DATA LAYER - Brown/Amber hues (30-60°)
68
+ # =========================================================================
69
+ "database": "#E65100", # Dark Orange/Brown
70
+ "table": "#EF6C00", # Orange
71
+ "column": "#F57C00", # Medium Orange
72
+ "constraint": "#FB8C00", # Light Orange
73
+ "index": "#FF9800", # Amber
74
+ "migration": "#FFA726", # Light Amber
75
+ "enum": "#FFB74D", # Pale Amber
76
+
77
+ # =========================================================================
78
+ # TESTING LAYER - Purple/Violet hues (260-290°)
79
+ # =========================================================================
80
+ "test_suite": "#4527A0", # Deep Purple
81
+ "test_case": "#512DA8", # Dark Purple
82
+ "test_step": "#5E35B1", # Purple
83
+ "assertion": "#673AB7", # Medium Purple
84
+ "test_data": "#7E57C2", # Light Purple
85
+ "defect": "#B71C1C", # Dark Red (stands out - critical)
86
+ "incident": "#C62828", # Red (stands out - critical)
87
+
88
+ # =========================================================================
89
+ # DELIVERY LAYER - Lime/Yellow-Green hues (60-90°)
90
+ # =========================================================================
91
+ "release": "#827717", # Dark Lime
92
+ "sprint": "#9E9D24", # Olive
93
+ "commit": "#AFB42B", # Lime
94
+ "pull_request": "#C0CA33", # Light Lime
95
+ "ticket": "#CDDC39", # Yellow-Lime
96
+ "deployment": "#D4E157", # Pale Lime
97
+
98
+ # =========================================================================
99
+ # ORGANIZATION LAYER - Magenta/Pink hues (290-330°)
100
+ # =========================================================================
101
+ "team": "#AD1457", # Dark Magenta
102
+ "owner": "#C2185B", # Magenta
103
+ "stakeholder": "#D81B60", # Pink-Magenta
104
+ "repository": "#E91E63", # Pink
105
+ "documentation": "#EC407A", # Light Pink
106
+
107
+ # =========================================================================
108
+ # GENERIC/COMMON TYPES - Distinct colors for frequently used types
109
+ # =========================================================================
110
+ # Documents & Content
111
+ "document": "#5C6BC0", # Indigo
112
+ "section": "#7986CB", # Light Indigo
113
+ "chapter": "#9FA8DA", # Pale Indigo
114
+ "paragraph": "#C5CAE9", # Very Light Indigo
115
+ "text": "#E8EAF6", # Faint Indigo
116
+
117
+ # Process & Actions
118
+ "process": "#00ACC1", # Cyan
119
+ "action": "#26C6DA", # Light Cyan
120
+ "step": "#4DD0E1", # Pale Cyan
121
+ "task": "#80DEEA", # Very Light Cyan
122
+ "activity": "#B2EBF2", # Faint Cyan
123
+
124
+ # Tools & Scripts
125
+ "tool": "#8D6E63", # Brown
126
+ "toolkit": "#A1887F", # Light Brown
127
+ "script": "#BCAAA4", # Pale Brown
128
+ "utility": "#D7CCC8", # Very Light Brown
129
+ "mcp_server": "#6D4C41", # Dark Brown (MCP)
130
+ "mcp_tool": "#795548", # Medium Brown (MCP)
131
+ "mcp_resource": "#8D6E63", # Brown (MCP)
132
+ "connector": "#5D4037", # Deep Brown
133
+
134
+ # Structure & Organization
135
+ "structure": "#78909C", # Blue Grey
136
+ "component": "#90A4AE", # Light Blue Grey
137
+ "element": "#B0BEC5", # Pale Blue Grey
138
+ "item": "#CFD8DC", # Very Light Blue Grey
139
+
140
+ # Resources & References
141
+ "resource": "#546E7A", # Dark Blue Grey
142
+ "reference": "#607D8B", # Blue Grey
143
+ "link": "#78909C", # Light Blue Grey
144
+
145
+ # Requirements & Specs
146
+ "requirement": "#F06292", # Pink
147
+ "specification": "#F48FB1", # Light Pink
148
+ "criteria": "#F8BBD9", # Pale Pink
149
+
150
+ # Issues & Problems
151
+ "issue": "#EF5350", # Red
152
+ "bug": "#E57373", # Light Red
153
+ "error": "#EF9A9A", # Pale Red
154
+ "problem": "#FFCDD2", # Very Light Red
155
+ "troubleshooting": "#FFEBEE", # Faint Red
156
+
157
+ # States & Status
158
+ "state": "#AB47BC", # Purple
159
+ "status": "#BA68C8", # Light Purple
160
+ "condition": "#CE93D8", # Pale Purple
161
+
162
+ # Misc common types
163
+ "entity": "#26A69A", # Teal
164
+ "object": "#4DB6AC", # Light Teal
165
+ "concept": "#80CBC4", # Pale Teal
166
+ "idea": "#B2DFDB", # Very Light Teal
167
+
168
+ "checklist": "#FFD54F", # Amber
169
+ "list": "#FFE082", # Light Amber
170
+ "collection": "#FFECB3", # Pale Amber
171
+
172
+ "project": "#7B1FA2", # Deep Purple
173
+ "program": "#8E24AA", # Purple
174
+ "initiative": "#9C27B0", # Light Purple
175
+
176
+ "platform": "#0097A7", # Dark Cyan
177
+ "system": "#00ACC1", # Cyan
178
+ "application": "#00BCD4", # Light Cyan
179
+ "app": "#26C6DA", # Pale Cyan
180
+
181
+ "value": "#689F38", # Light Green
182
+ "property": "#7CB342", # Pale Green
183
+ "setting": "#8BC34A", # Very Light Green
184
+
185
+ "agent": "#FF7043", # Deep Orange
186
+ "agent_type": "#FF8A65", # Orange
187
+ "bot": "#FFAB91", # Light Orange
188
+
189
+ "category": "#5D4037", # Brown
190
+ "type": "#6D4C41", # Light Brown
191
+ "kind": "#795548", # Medium Brown
192
+ "group": "#8D6E63", # Pale Brown
193
+
194
+ "file": "#455A64", # Dark Blue Grey
195
+ "folder": "#546E7A", # Blue Grey
196
+ "directory": "#607D8B", # Light Blue Grey
197
+ "path": "#78909C", # Pale Blue Grey
198
+
199
+ "method": "#1E88E5", # Blue (code related)
200
+ "parameter": "#42A5F5", # Light Blue
201
+ "argument": "#64B5F6", # Pale Blue
202
+ "variable": "#90CAF9", # Very Light Blue
203
+
204
+ "event": "#7E57C2", # Purple
205
+ "trigger": "#9575CD", # Light Purple
206
+ "handler": "#B39DDB", # Pale Purple
207
+ "callback": "#D1C4E9", # Very Light Purple
208
+
209
+ "rule": "#43A047", # Green (business related)
210
+ "policy": "#66BB6A", # Light Green
211
+ "guideline": "#81C784", # Pale Green
212
+ "standard": "#A5D6A7", # Very Light Green
213
+
214
+ "user": "#EC407A", # Pink (organization related)
215
+ "role": "#F06292", # Light Pink
216
+ "permission": "#F48FB1", # Pale Pink
217
+ "access": "#F8BBD9", # Very Light Pink
218
+
219
+ # Default fallback
220
+ "default": "#9E9E9E", # Grey
221
+ }
222
+
223
+ # Relation type colors
224
+ RELATION_COLORS = {
225
+ "contains": "#4CAF50",
226
+ "extends": "#2196F3",
227
+ "implements": "#9C27B0",
228
+ "imports": "#FF9800",
229
+ "calls": "#F44336",
230
+ "triggers": "#E91E63",
231
+ "depends_on": "#673AB7",
232
+ "uses": "#00BCD4",
233
+ "stores_in": "#795548",
234
+ "reads_from": "#607D8B",
235
+ "transforms": "#FFC107",
236
+ "maps_to": "#8BC34A",
237
+ "default": "#9E9E9E",
238
+ }
239
+
240
+
241
+ HTML_TEMPLATE = '''<!DOCTYPE html>
242
+ <html lang="en">
243
+ <head>
244
+ <meta charset="UTF-8">
245
+ <meta name="viewport" content="width=device-width, initial-scale=1.0">
246
+ <title>Knowledge Graph Visualization</title>
247
+ <script src="https://d3js.org/d3.v7.min.js"></script>
248
+ <style>
249
+ * {
250
+ margin: 0;
251
+ padding: 0;
252
+ box-sizing: border-box;
253
+ }
254
+
255
+ body {
256
+ font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, sans-serif;
257
+ background: #1a1a2e;
258
+ color: #eee;
259
+ overflow: hidden;
260
+ }
261
+
262
+ #container {
263
+ display: flex;
264
+ height: 100vh;
265
+ }
266
+
267
+ #graph-container {
268
+ flex: 1;
269
+ position: relative;
270
+ }
271
+
272
+ #sidebar {
273
+ width: 350px;
274
+ background: #16213e;
275
+ border-left: 1px solid #0f3460;
276
+ padding: 20px;
277
+ overflow-y: auto;
278
+ }
279
+
280
+ #controls {
281
+ position: absolute;
282
+ top: 20px;
283
+ left: 20px;
284
+ background: rgba(22, 33, 62, 0.95);
285
+ padding: 15px;
286
+ border-radius: 8px;
287
+ border: 1px solid #0f3460;
288
+ z-index: 100;
289
+ }
290
+
291
+ #controls h3 {
292
+ margin-bottom: 10px;
293
+ color: #e94560;
294
+ }
295
+
296
+ #controls label {
297
+ display: block;
298
+ margin: 8px 0;
299
+ font-size: 13px;
300
+ }
301
+
302
+ #controls input[type="range"] {
303
+ width: 150px;
304
+ margin-left: 10px;
305
+ }
306
+
307
+ #controls input[type="text"] {
308
+ width: 200px;
309
+ padding: 5px 10px;
310
+ border: 1px solid #0f3460;
311
+ background: #1a1a2e;
312
+ color: #eee;
313
+ border-radius: 4px;
314
+ }
315
+
316
+ #search-results {
317
+ margin-top: 10px;
318
+ max-height: 200px;
319
+ overflow-y: auto;
320
+ }
321
+
322
+ .search-result {
323
+ padding: 5px 10px;
324
+ cursor: pointer;
325
+ border-radius: 4px;
326
+ margin: 2px 0;
327
+ }
328
+
329
+ .search-result:hover {
330
+ background: #0f3460;
331
+ }
332
+
333
+ #sidebar h2 {
334
+ color: #e94560;
335
+ margin-bottom: 15px;
336
+ border-bottom: 1px solid #0f3460;
337
+ padding-bottom: 10px;
338
+ }
339
+
340
+ #sidebar h3 {
341
+ color: #eee;
342
+ margin: 15px 0 10px;
343
+ font-size: 14px;
344
+ }
345
+
346
+ #entity-details {
347
+ font-size: 13px;
348
+ line-height: 1.6;
349
+ }
350
+
351
+ .detail-label {
352
+ color: #888;
353
+ font-size: 11px;
354
+ text-transform: uppercase;
355
+ margin-top: 10px;
356
+ }
357
+
358
+ .detail-value {
359
+ color: #eee;
360
+ word-break: break-word;
361
+ }
362
+
363
+ .entity-type-badge {
364
+ display: inline-block;
365
+ padding: 3px 8px;
366
+ border-radius: 4px;
367
+ font-size: 11px;
368
+ font-weight: 600;
369
+ text-transform: uppercase;
370
+ }
371
+
372
+ .citation-link {
373
+ color: #64b5f6;
374
+ text-decoration: none;
375
+ font-family: monospace;
376
+ font-size: 12px;
377
+ }
378
+
379
+ .citation-link:hover {
380
+ text-decoration: underline;
381
+ }
382
+
383
+ /* Context Menu */
384
+ #context-menu {
385
+ display: none;
386
+ position: fixed;
387
+ background: #1a1a2e;
388
+ border: 1px solid #0f3460;
389
+ border-radius: 6px;
390
+ padding: 5px 0;
391
+ min-width: 180px;
392
+ box-shadow: 0 4px 15px rgba(0,0,0,0.4);
393
+ z-index: 1000;
394
+ }
395
+
396
+ .context-menu-item {
397
+ padding: 8px 15px;
398
+ cursor: pointer;
399
+ color: #eee;
400
+ font-size: 13px;
401
+ display: flex;
402
+ align-items: center;
403
+ gap: 8px;
404
+ }
405
+
406
+ .context-menu-item:hover {
407
+ background: #0f3460;
408
+ }
409
+
410
+ .context-menu-item.danger {
411
+ color: #e94560;
412
+ }
413
+
414
+ .context-menu-separator {
415
+ height: 1px;
416
+ background: #0f3460;
417
+ margin: 5px 0;
418
+ }
419
+
420
+ /* Focus Mode Styling */
421
+ .node.faded circle {
422
+ opacity: 0.1;
423
+ }
424
+
425
+ .node.faded text {
426
+ opacity: 0.1;
427
+ }
428
+
429
+ .link.faded {
430
+ opacity: 0.05;
431
+ }
432
+
433
+ .node.focused circle {
434
+ stroke: #e94560;
435
+ stroke-width: 3px;
436
+ }
437
+
438
+ .node.neighbor circle {
439
+ stroke: #64b5f6;
440
+ stroke-width: 2px;
441
+ }
442
+
443
+ /* Focus Mode Banner */
444
+ #focus-banner {
445
+ display: none;
446
+ position: fixed;
447
+ top: 10px;
448
+ left: 50%;
449
+ transform: translateX(-50%);
450
+ background: #e94560;
451
+ color: white;
452
+ padding: 8px 20px;
453
+ border-radius: 20px;
454
+ font-size: 13px;
455
+ z-index: 100;
456
+ box-shadow: 0 2px 10px rgba(0,0,0,0.3);
457
+ }
458
+
459
+ #focus-banner button {
460
+ background: white;
461
+ color: #e94560;
462
+ border: none;
463
+ padding: 3px 10px;
464
+ border-radius: 10px;
465
+ margin-left: 10px;
466
+ cursor: pointer;
467
+ font-weight: 600;
468
+ }
469
+
470
+ #legend {
471
+ margin-top: 20px;
472
+ padding-top: 15px;
473
+ border-top: 1px solid #0f3460;
474
+ }
475
+
476
+ .legend-item {
477
+ display: flex;
478
+ align-items: center;
479
+ margin: 5px 0;
480
+ font-size: 12px;
481
+ }
482
+
483
+ .legend-color {
484
+ width: 12px;
485
+ height: 12px;
486
+ border-radius: 50%;
487
+ margin-right: 8px;
488
+ }
489
+
490
+ #stats {
491
+ background: #0f3460;
492
+ padding: 10px 15px;
493
+ border-radius: 6px;
494
+ margin-bottom: 15px;
495
+ }
496
+
497
+ #stats span {
498
+ display: inline-block;
499
+ margin-right: 15px;
500
+ }
501
+
502
+ .stat-value {
503
+ color: #e94560;
504
+ font-weight: 600;
505
+ }
506
+
507
+ svg {
508
+ width: 100%;
509
+ height: 100%;
510
+ }
511
+
512
+ .node {
513
+ cursor: pointer;
514
+ }
515
+
516
+ .node circle {
517
+ stroke: #fff;
518
+ stroke-width: 1.5px;
519
+ transition: r 0.2s;
520
+ }
521
+
522
+ .node:hover circle {
523
+ stroke-width: 3px;
524
+ }
525
+
526
+ .node.selected circle {
527
+ stroke: #e94560;
528
+ stroke-width: 3px;
529
+ }
530
+
531
+ .node text {
532
+ font-size: 10px;
533
+ fill: #ccc;
534
+ pointer-events: none;
535
+ }
536
+
537
+ .link {
538
+ stroke-opacity: 0.4;
539
+ stroke-width: 1.5px;
540
+ }
541
+
542
+ .link:hover {
543
+ stroke-opacity: 1;
544
+ }
545
+
546
+ .link-label {
547
+ font-size: 9px;
548
+ fill: #888;
549
+ }
550
+
551
+ /* Tooltip */
552
+ .tooltip {
553
+ position: absolute;
554
+ background: rgba(22, 33, 62, 0.95);
555
+ border: 1px solid #0f3460;
556
+ border-radius: 6px;
557
+ padding: 10px;
558
+ font-size: 12px;
559
+ pointer-events: none;
560
+ z-index: 1000;
561
+ max-width: 300px;
562
+ }
563
+
564
+ .tooltip h4 {
565
+ color: #e94560;
566
+ margin-bottom: 5px;
567
+ }
568
+
569
+ /* Filter panel */
570
+ #type-filters {
571
+ max-height: 400px;
572
+ overflow-y: auto;
573
+ margin-top: 10px;
574
+ border: 1px solid #333;
575
+ border-radius: 4px;
576
+ padding: 5px;
577
+ }
578
+
579
+ #type-filter-controls {
580
+ display: flex;
581
+ gap: 5px;
582
+ margin-bottom: 8px;
583
+ }
584
+
585
+ #type-filter-controls button {
586
+ flex: 1;
587
+ padding: 4px 8px;
588
+ font-size: 11px;
589
+ cursor: pointer;
590
+ background: #333;
591
+ color: #fff;
592
+ border: 1px solid #555;
593
+ border-radius: 3px;
594
+ }
595
+
596
+ #type-filter-controls button:hover {
597
+ background: #444;
598
+ }
599
+
600
+ #type-filter-search {
601
+ width: 100%;
602
+ padding: 5px;
603
+ margin-bottom: 8px;
604
+ background: #1a1a2e;
605
+ color: #fff;
606
+ border: 1px solid #333;
607
+ border-radius: 3px;
608
+ font-size: 12px;
609
+ }
610
+
611
+ .type-filter {
612
+ display: flex;
613
+ align-items: center;
614
+ margin: 3px 0;
615
+ font-size: 12px;
616
+ }
617
+
618
+ .type-filter input {
619
+ margin-right: 8px;
620
+ }
621
+
622
+ .type-filter .count {
623
+ color: #888;
624
+ margin-left: auto;
625
+ }
626
+
627
+ .type-filter.hidden {
628
+ display: none;
629
+ }
630
+ </style>
631
+ </head>
632
+ <body>
633
+ <div id="container">
634
+ <div id="graph-container">
635
+ <div id="controls">
636
+ <h3>🔍 Search & Filter</h3>
637
+ <input type="text" id="search-input" placeholder="Search entities...">
638
+ <div id="search-results"></div>
639
+
640
+ <h3 style="margin-top: 15px;">⚙️ Layout</h3>
641
+ <label>
642
+ Link Distance
643
+ <input type="range" id="link-distance" min="50" max="300" value="120">
644
+ </label>
645
+ <label>
646
+ Charge Strength
647
+ <input type="range" id="charge-strength" min="-500" max="-50" value="-200">
648
+ </label>
649
+ <label>
650
+ <input type="checkbox" id="show-labels" checked> Show Labels
651
+ </label>
652
+ <label>
653
+ <input type="checkbox" id="show-orphans" checked> Show Orphan Nodes
654
+ </label>
655
+
656
+ <h3 style="margin-top: 15px;">📊 Filter by Type <span id="type-count-display" style="color: #888; font-size: 12px;"></span></h3>
657
+ <input type="text" id="type-filter-search" placeholder="Search types...">
658
+ <div id="type-filter-controls">
659
+ <button id="select-all-types">All</button>
660
+ <button id="select-none-types">None</button>
661
+ <button id="select-top10-types">Top 10</button>
662
+ </div>
663
+ <div id="type-filters"></div>
664
+ </div>
665
+ <svg id="graph"></svg>
666
+ </div>
667
+
668
+ <div id="sidebar">
669
+ <h2>📋 Entity Details</h2>
670
+ <div id="stats">
671
+ <span>Entities: <span class="stat-value" id="entity-count">0</span></span>
672
+ <span>Relations: <span class="stat-value" id="relation-count">0</span></span>
673
+ </div>
674
+ <div id="entity-details">
675
+ <p style="color: #888;">Click on a node to see details</p>
676
+ </div>
677
+ <div id="legend">
678
+ <h3>🎨 Legend</h3>
679
+ <div id="legend-items"></div>
680
+ </div>
681
+ </div>
682
+ </div>
683
+
684
+ <div class="tooltip" id="tooltip" style="display: none;"></div>
685
+
686
+ <!-- Context Menu -->
687
+ <div id="context-menu">
688
+ <div class="context-menu-item" id="ctx-focus-1">🔍 Focus 1 level</div>
689
+ <div class="context-menu-item" id="ctx-focus-2">🔍 Focus 2 levels</div>
690
+ <div class="context-menu-item" id="ctx-focus-3">🔍 Focus 3 levels</div>
691
+ <div class="context-menu-item" id="ctx-focus-5">🔍 Focus 5 levels</div>
692
+ <div class="context-menu-separator"></div>
693
+ <div class="context-menu-item" id="ctx-expand">📈 Expand neighbors</div>
694
+ <div class="context-menu-item" id="ctx-hide">👁️ Hide this node</div>
695
+ <div class="context-menu-separator"></div>
696
+ <div class="context-menu-item danger" id="ctx-reset">✖ Clear focus</div>
697
+ </div>
698
+
699
+ <!-- Focus Mode Banner -->
700
+ <div id="focus-banner">
701
+ <span id="focus-info">Focus mode: showing X levels from "Node"</span>
702
+ <button id="clear-focus">Clear Focus</button>
703
+ </div>
704
+
705
+ <script>
706
+ // Graph data injected from Python
707
+ const graphData = GRAPH_DATA_PLACEHOLDER;
708
+ const typeColors = TYPE_COLORS_PLACEHOLDER;
709
+ const relationColors = RELATION_COLORS_PLACEHOLDER;
710
+
711
+ // Process data
712
+ const nodes = graphData.nodes.map(n => {
713
+ // Handle both 'citations' (list) and legacy 'citation' (single)
714
+ let citations = n.citations || [];
715
+ if (n.citation && !citations.length) {
716
+ citations = [n.citation];
717
+ }
718
+ // Get file_path from first citation if not on node directly
719
+ const file_path = n.file_path || (citations[0] ? citations[0].file_path : null);
720
+
721
+ return {
722
+ id: n.id,
723
+ name: n.name,
724
+ type: n.type,
725
+ citations: citations,
726
+ properties: n.properties || {},
727
+ source_toolkit: n.source_toolkit || (citations[0] ? citations[0].source_toolkit : null),
728
+ file_path: file_path
729
+ };
730
+ });
731
+
732
+ const links = graphData.links.map(l => ({
733
+ source: l.source,
734
+ target: l.target,
735
+ type: l.relation_type || 'related',
736
+ source_toolkit: l.source_toolkit,
737
+ discovered_in_file: l.discovered_in_file,
738
+ ...l
739
+ }));
740
+
741
+ // Update stats
742
+ document.getElementById('entity-count').textContent = nodes.length;
743
+ document.getElementById('relation-count').textContent = links.length;
744
+
745
+ // Count types
746
+ const typeCounts = {};
747
+ nodes.forEach(n => {
748
+ typeCounts[n.type] = (typeCounts[n.type] || 0) + 1;
749
+ });
750
+
751
+ // Build type filters
752
+ const typeFiltersDiv = document.getElementById('type-filters');
753
+ const enabledTypes = new Set(Object.keys(typeCounts));
754
+ const sortedTypes = Object.entries(typeCounts).sort((a, b) => b[1] - a[1]);
755
+
756
+ // Display type count
757
+ document.getElementById('type-count-display').textContent = `(${sortedTypes.length} types)`;
758
+
759
+ sortedTypes.forEach(([type, count]) => {
760
+ const div = document.createElement('div');
761
+ div.className = 'type-filter';
762
+ div.dataset.typeName = type.toLowerCase();
763
+ div.innerHTML = `
764
+ <input type="checkbox" checked data-type="${type}">
765
+ <span class="legend-color" style="background: ${getColor(type)}"></span>
766
+ ${type}
767
+ <span class="count">${count}</span>
768
+ `;
769
+ typeFiltersDiv.appendChild(div);
770
+ });
771
+
772
+ // Type filter search
773
+ document.getElementById('type-filter-search').addEventListener('input', (e) => {
774
+ const searchTerm = e.target.value.toLowerCase();
775
+ document.querySelectorAll('.type-filter').forEach(div => {
776
+ const typeName = div.dataset.typeName;
777
+ div.classList.toggle('hidden', !typeName.includes(searchTerm));
778
+ });
779
+ });
780
+
781
+ // Select all/none/top10 buttons
782
+ function updateGraphVisibility() {
783
+ node.style('display', d => enabledTypes.has(d.type) ? 'block' : 'none');
784
+ link.style('display', d => {
785
+ const sourceType = typeof d.source === 'object' ? d.source.type : nodes.find(n => n.id === d.source)?.type;
786
+ const targetType = typeof d.target === 'object' ? d.target.type : nodes.find(n => n.id === d.target)?.type;
787
+ return enabledTypes.has(sourceType) && enabledTypes.has(targetType) ? 'block' : 'none';
788
+ });
789
+ }
790
+
791
+ document.getElementById('select-all-types').addEventListener('click', () => {
792
+ document.querySelectorAll('.type-filter input[type="checkbox"]').forEach(cb => {
793
+ cb.checked = true;
794
+ enabledTypes.add(cb.dataset.type);
795
+ });
796
+ updateGraphVisibility();
797
+ });
798
+
799
+ document.getElementById('select-none-types').addEventListener('click', () => {
800
+ document.querySelectorAll('.type-filter input[type="checkbox"]').forEach(cb => {
801
+ cb.checked = false;
802
+ enabledTypes.delete(cb.dataset.type);
803
+ });
804
+ updateGraphVisibility();
805
+ });
806
+
807
+ document.getElementById('select-top10-types').addEventListener('click', () => {
808
+ const top10Types = new Set(sortedTypes.slice(0, 10).map(([type]) => type));
809
+ document.querySelectorAll('.type-filter input[type="checkbox"]').forEach(cb => {
810
+ const isTop10 = top10Types.has(cb.dataset.type);
811
+ cb.checked = isTop10;
812
+ if (isTop10) {
813
+ enabledTypes.add(cb.dataset.type);
814
+ } else {
815
+ enabledTypes.delete(cb.dataset.type);
816
+ }
817
+ });
818
+ updateGraphVisibility();
819
+ });
820
+
821
+ // Build legend
822
+ const legendDiv = document.getElementById('legend-items');
823
+ Object.entries(typeCounts)
824
+ .sort((a, b) => b[1] - a[1])
825
+ .slice(0, 10)
826
+ .forEach(([type, count]) => {
827
+ const div = document.createElement('div');
828
+ div.className = 'legend-item';
829
+ div.innerHTML = `
830
+ <span class="legend-color" style="background: ${getColor(type)}"></span>
831
+ ${type} (${count})
832
+ `;
833
+ legendDiv.appendChild(div);
834
+ });
835
+
836
+ // Generate a consistent color from a string hash (fallback for unknown types)
837
+ function stringToColor(str) {
838
+ let hash = 0;
839
+ for (let i = 0; i < str.length; i++) {
840
+ hash = str.charCodeAt(i) + ((hash << 5) - hash);
841
+ }
842
+ // Use golden ratio to spread hues evenly, avoiding muddy colors
843
+ const h = Math.abs(hash * 137.508) % 360;
844
+ const s = 55 + (Math.abs(hash >> 8) % 25); // 55-80% saturation
845
+ const l = 45 + (Math.abs(hash >> 16) % 20); // 45-65% lightness
846
+ return `hsl(${h}, ${s}%, ${l}%)`;
847
+ }
848
+
849
+ function getColor(type) {
850
+ // First try exact match in predefined colors
851
+ if (typeColors[type]) return typeColors[type];
852
+ // Try lowercase match
853
+ const lower = type.toLowerCase();
854
+ if (typeColors[lower]) return typeColors[lower];
855
+ // Try with underscores/spaces/dashes replaced (e.g., "UserStory" -> "userstory")
856
+ const normalized = lower.replace(/[_\\s-]/g, '');
857
+ for (const [key, color] of Object.entries(typeColors)) {
858
+ if (key.toLowerCase().replace(/[_\\s-]/g, '') === normalized) {
859
+ return color;
860
+ }
861
+ }
862
+ // Fallback: generate consistent color from type name
863
+ return stringToColor(type);
864
+ }
865
+
866
+ function getRelationColor(type) {
867
+ return relationColors[type] || relationColors['default'];
868
+ }
869
+
870
+ // SVG setup
871
+ const svg = d3.select('#graph');
872
+ const container = document.getElementById('graph-container');
873
+ const width = container.clientWidth;
874
+ const height = container.clientHeight;
875
+
876
+ svg.attr('viewBox', [0, 0, width, height]);
877
+
878
+ // Create zoom behavior
879
+ const zoom = d3.zoom()
880
+ .scaleExtent([0.1, 4])
881
+ .on('zoom', (event) => {
882
+ g.attr('transform', event.transform);
883
+ });
884
+
885
+ svg.call(zoom);
886
+
887
+ // Main group for zoom/pan
888
+ const g = svg.append('g');
889
+
890
+ // Arrow markers for directed edges
891
+ svg.append('defs').selectAll('marker')
892
+ .data(['arrow'])
893
+ .join('marker')
894
+ .attr('id', d => d)
895
+ .attr('viewBox', '0 -5 10 10')
896
+ .attr('refX', 20)
897
+ .attr('refY', 0)
898
+ .attr('markerWidth', 6)
899
+ .attr('markerHeight', 6)
900
+ .attr('orient', 'auto')
901
+ .append('path')
902
+ .attr('fill', '#888')
903
+ .attr('d', 'M0,-5L10,0L0,5');
904
+
905
+ // Force simulation
906
+ const simulation = d3.forceSimulation(nodes)
907
+ .force('link', d3.forceLink(links).id(d => d.id).distance(120))
908
+ .force('charge', d3.forceManyBody().strength(-200))
909
+ .force('center', d3.forceCenter(width / 2, height / 2))
910
+ .force('collision', d3.forceCollide().radius(30));
911
+
912
+ // Draw links
913
+ const link = g.append('g')
914
+ .attr('class', 'links')
915
+ .selectAll('line')
916
+ .data(links)
917
+ .join('line')
918
+ .attr('class', 'link')
919
+ .attr('stroke', d => getRelationColor(d.type))
920
+ .attr('marker-end', 'url(#arrow)')
921
+ .on('mouseover', function(event, d) {
922
+ // Show tooltip for relations
923
+ tooltip
924
+ .style('display', 'block')
925
+ .style('left', (event.pageX + 10) + 'px')
926
+ .style('top', (event.pageY + 10) + 'px')
927
+ .html(`
928
+ <h4>${d.type}</h4>
929
+ ${d.source_toolkit ? `<div><strong>Source:</strong> ${d.source_toolkit}</div>` : ''}
930
+ ${d.discovered_in_file ? `<div><strong>File:</strong> ${d.discovered_in_file}</div>` : ''}
931
+ ${d.confidence ? `<div><strong>Confidence:</strong> ${(d.confidence * 100).toFixed(0)}%</div>` : ''}
932
+ `);
933
+ // Highlight the link
934
+ d3.select(this).attr('stroke-width', 3).attr('stroke-opacity', 1);
935
+ })
936
+ .on('mouseout', function() {
937
+ tooltip.style('display', 'none');
938
+ // Reset link appearance
939
+ d3.select(this).attr('stroke-width', 1.5).attr('stroke-opacity', 0.4);
940
+ });
941
+
942
+ // Draw nodes
943
+ const node = g.append('g')
944
+ .attr('class', 'nodes')
945
+ .selectAll('g')
946
+ .data(nodes)
947
+ .join('g')
948
+ .attr('class', 'node')
949
+ .call(d3.drag()
950
+ .on('start', dragstarted)
951
+ .on('drag', dragged)
952
+ .on('end', dragended));
953
+
954
+ // Node circles
955
+ node.append('circle')
956
+ .attr('r', d => 8 + Math.min(5, (typeCounts[d.type] || 1) / 2))
957
+ .attr('fill', d => getColor(d.type));
958
+
959
+ // Node labels
960
+ const labels = node.append('text')
961
+ .attr('dx', 12)
962
+ .attr('dy', 4)
963
+ .text(d => d.name.length > 25 ? d.name.substring(0, 25) + '...' : d.name);
964
+
965
+ // Tooltip
966
+ const tooltip = d3.select('#tooltip');
967
+
968
+ node.on('mouseover', (event, d) => {
969
+ tooltip
970
+ .style('display', 'block')
971
+ .style('left', (event.pageX + 10) + 'px')
972
+ .style('top', (event.pageY + 10) + 'px')
973
+ .html(`
974
+ <h4>${d.name}</h4>
975
+ <div><strong>Type:</strong> ${d.type}</div>
976
+ <div><strong>File:</strong> ${d.file_path || 'N/A'}</div>
977
+ ${d.citations && d.citations.length > 0 ? `<div><strong>Files:</strong> ${d.citations.length} reference${d.citations.length > 1 ? 's' : ''}</div>` : ''}
978
+ `);
979
+ })
980
+ .on('mouseout', () => {
981
+ tooltip.style('display', 'none');
982
+ })
983
+ .on('click', (event, d) => {
984
+ // Deselect all
985
+ node.classed('selected', false);
986
+ // Select clicked
987
+ d3.select(event.currentTarget).classed('selected', true);
988
+ // Show details
989
+ showEntityDetails(d);
990
+ })
991
+ .on('contextmenu', (event, d) => {
992
+ event.preventDefault();
993
+ showContextMenu(event, d);
994
+ });
995
+
996
+ // Context Menu
997
+ const contextMenu = document.getElementById('context-menu');
998
+ let contextMenuNode = null;
999
+
1000
+ function showContextMenu(event, d) {
1001
+ contextMenuNode = d;
1002
+ contextMenu.style.display = 'block';
1003
+ contextMenu.style.left = event.pageX + 'px';
1004
+ contextMenu.style.top = event.pageY + 'px';
1005
+ }
1006
+
1007
+ function hideContextMenu() {
1008
+ contextMenu.style.display = 'none';
1009
+ contextMenuNode = null;
1010
+ }
1011
+
1012
+ // Hide context menu on click elsewhere
1013
+ document.addEventListener('click', hideContextMenu);
1014
+ document.addEventListener('contextmenu', (e) => {
1015
+ if (!e.target.closest('.node')) {
1016
+ hideContextMenu();
1017
+ }
1018
+ });
1019
+
1020
+ // Build adjacency list for BFS
1021
+ const adjacency = new Map();
1022
+ nodes.forEach(n => adjacency.set(n.id, new Set()));
1023
+ links.forEach(l => {
1024
+ const srcId = typeof l.source === 'object' ? l.source.id : l.source;
1025
+ const tgtId = typeof l.target === 'object' ? l.target.id : l.target;
1026
+ adjacency.get(srcId)?.add(tgtId);
1027
+ adjacency.get(tgtId)?.add(srcId);
1028
+ });
1029
+
1030
+ // Get nodes within N levels using BFS
1031
+ function getNodesWithinLevels(startId, maxLevels) {
1032
+ const visited = new Map(); // nodeId -> level
1033
+ const queue = [[startId, 0]];
1034
+ visited.set(startId, 0);
1035
+
1036
+ while (queue.length > 0) {
1037
+ const [currentId, level] = queue.shift();
1038
+ if (level >= maxLevels) continue;
1039
+
1040
+ const neighbors = adjacency.get(currentId) || new Set();
1041
+ for (const neighborId of neighbors) {
1042
+ if (!visited.has(neighborId)) {
1043
+ visited.set(neighborId, level + 1);
1044
+ queue.push([neighborId, level + 1]);
1045
+ }
1046
+ }
1047
+ }
1048
+
1049
+ return visited;
1050
+ }
1051
+
1052
+ // Focus on node with N levels
1053
+ function focusOnNode(nodeData, levels) {
1054
+ const focusedNodes = getNodesWithinLevels(nodeData.id, levels);
1055
+
1056
+ // Apply styling
1057
+ node.classed('faded', d => !focusedNodes.has(d.id))
1058
+ .classed('focused', d => d.id === nodeData.id)
1059
+ .classed('neighbor', d => focusedNodes.has(d.id) && d.id !== nodeData.id);
1060
+
1061
+ link.classed('faded', d => {
1062
+ const srcId = typeof d.source === 'object' ? d.source.id : d.source;
1063
+ const tgtId = typeof d.target === 'object' ? d.target.id : d.target;
1064
+ return !focusedNodes.has(srcId) || !focusedNodes.has(tgtId);
1065
+ });
1066
+
1067
+ // Show focus banner
1068
+ const banner = document.getElementById('focus-banner');
1069
+ const info = document.getElementById('focus-info');
1070
+ info.textContent = `Focus: ${levels} level${levels > 1 ? 's' : ''} from "${nodeData.name}" (${focusedNodes.size} nodes)`;
1071
+ banner.style.display = 'block';
1072
+
1073
+ // Select the focused node
1074
+ node.classed('selected', d => d.id === nodeData.id);
1075
+ showEntityDetails(nodeData);
1076
+ }
1077
+
1078
+ // Clear focus
1079
+ function clearFocus() {
1080
+ node.classed('faded', false)
1081
+ .classed('focused', false)
1082
+ .classed('neighbor', false);
1083
+ link.classed('faded', false);
1084
+ document.getElementById('focus-banner').style.display = 'none';
1085
+ }
1086
+
1087
+ // Context menu actions
1088
+ document.getElementById('ctx-focus-1').onclick = () => {
1089
+ if (contextMenuNode) focusOnNode(contextMenuNode, 1);
1090
+ hideContextMenu();
1091
+ };
1092
+ document.getElementById('ctx-focus-2').onclick = () => {
1093
+ if (contextMenuNode) focusOnNode(contextMenuNode, 2);
1094
+ hideContextMenu();
1095
+ };
1096
+ document.getElementById('ctx-focus-3').onclick = () => {
1097
+ if (contextMenuNode) focusOnNode(contextMenuNode, 3);
1098
+ hideContextMenu();
1099
+ };
1100
+ document.getElementById('ctx-focus-5').onclick = () => {
1101
+ if (contextMenuNode) focusOnNode(contextMenuNode, 5);
1102
+ hideContextMenu();
1103
+ };
1104
+ document.getElementById('ctx-expand').onclick = () => {
1105
+ if (contextMenuNode) {
1106
+ // Just clear faded state for this node and neighbors
1107
+ const neighbors = adjacency.get(contextMenuNode.id) || new Set();
1108
+ node.filter(d => d.id === contextMenuNode.id || neighbors.has(d.id))
1109
+ .classed('faded', false);
1110
+ }
1111
+ hideContextMenu();
1112
+ };
1113
+ document.getElementById('ctx-hide').onclick = () => {
1114
+ if (contextMenuNode) {
1115
+ node.filter(d => d.id === contextMenuNode.id).style('display', 'none');
1116
+ link.filter(d => {
1117
+ const srcId = typeof d.source === 'object' ? d.source.id : d.source;
1118
+ const tgtId = typeof d.target === 'object' ? d.target.id : d.target;
1119
+ return srcId === contextMenuNode.id || tgtId === contextMenuNode.id;
1120
+ }).style('display', 'none');
1121
+ }
1122
+ hideContextMenu();
1123
+ };
1124
+ document.getElementById('ctx-reset').onclick = () => {
1125
+ clearFocus();
1126
+ // Also show all hidden nodes
1127
+ node.style('display', 'block');
1128
+ link.style('display', 'block');
1129
+ hideContextMenu();
1130
+ };
1131
+
1132
+ // Clear focus button in banner
1133
+ document.getElementById('clear-focus').onclick = clearFocus;
1134
+
1135
+ // Tick function
1136
+ simulation.on('tick', () => {
1137
+ link
1138
+ .attr('x1', d => d.source.x)
1139
+ .attr('y1', d => d.source.y)
1140
+ .attr('x2', d => d.target.x)
1141
+ .attr('y2', d => d.target.y);
1142
+
1143
+ node.attr('transform', d => `translate(${d.x},${d.y})`);
1144
+ });
1145
+
1146
+ // Drag functions
1147
+ function dragstarted(event, d) {
1148
+ if (!event.active) simulation.alphaTarget(0.3).restart();
1149
+ d.fx = d.x;
1150
+ d.fy = d.y;
1151
+ }
1152
+
1153
+ function dragged(event, d) {
1154
+ d.fx = event.x;
1155
+ d.fy = event.y;
1156
+ }
1157
+
1158
+ function dragended(event, d) {
1159
+ if (!event.active) simulation.alphaTarget(0);
1160
+ d.fx = null;
1161
+ d.fy = null;
1162
+ }
1163
+
1164
+ // Show entity details in sidebar
1165
+ function showEntityDetails(d) {
1166
+ const details = document.getElementById('entity-details');
1167
+ const propsHtml = Object.entries(d.properties || {})
1168
+ .map(([k, v]) => `
1169
+ <div class="detail-label">${k}</div>
1170
+ <div class="detail-value">${typeof v === 'object' ? JSON.stringify(v, null, 2) : v}</div>
1171
+ `).join('');
1172
+
1173
+ details.innerHTML = `
1174
+ <h3>${d.name}</h3>
1175
+ <div style="margin: 10px 0;">
1176
+ <span class="entity-type-badge" style="background: ${getColor(d.type)}">${d.type}</span>
1177
+ </div>
1178
+
1179
+ <div class="detail-label">ID</div>
1180
+ <div class="detail-value" style="font-family: monospace; font-size: 11px;">${d.id}</div>
1181
+
1182
+ ${d.citations && d.citations.length > 0 ? `
1183
+ <div class="detail-label">Citations (${d.citations.length})</div>
1184
+ <div class="detail-value">
1185
+ ${d.citations.map(c => `
1186
+ <div class="citation-link" style="margin-bottom: 4px;">
1187
+ ${c.file_path}${c.line_start ? `:${c.line_start}-${c.line_end}` : ''}
1188
+ </div>
1189
+ `).join('')}
1190
+ </div>
1191
+ ` : ''}
1192
+
1193
+ <div class="detail-label">Source</div>
1194
+ <div class="detail-value">${d.source_toolkit || 'N/A'}</div>
1195
+
1196
+ ${propsHtml ? `
1197
+ <div class="detail-label" style="margin-top: 15px;">Properties</div>
1198
+ ${propsHtml}
1199
+ ` : ''}
1200
+ `;
1201
+ }
1202
+
1203
+ // Search functionality
1204
+ const searchInput = document.getElementById('search-input');
1205
+ const searchResults = document.getElementById('search-results');
1206
+
1207
+ searchInput.addEventListener('input', (e) => {
1208
+ const query = e.target.value.toLowerCase();
1209
+ searchResults.innerHTML = '';
1210
+
1211
+ if (query.length < 2) return;
1212
+
1213
+ const matches = nodes.filter(n =>
1214
+ n.name.toLowerCase().includes(query) ||
1215
+ n.type.toLowerCase().includes(query)
1216
+ ).slice(0, 10);
1217
+
1218
+ matches.forEach(n => {
1219
+ const div = document.createElement('div');
1220
+ div.className = 'search-result';
1221
+ div.innerHTML = `<span class="legend-color" style="background: ${getColor(n.type)}; display: inline-block; vertical-align: middle;"></span> ${n.name}`;
1222
+ div.onclick = () => {
1223
+ // Center on node
1224
+ const transform = d3.zoomIdentity
1225
+ .translate(width / 2 - n.x, height / 2 - n.y);
1226
+ svg.transition().duration(500).call(zoom.transform, transform);
1227
+
1228
+ // Select node
1229
+ node.classed('selected', d => d.id === n.id);
1230
+ showEntityDetails(n);
1231
+ };
1232
+ searchResults.appendChild(div);
1233
+ });
1234
+ });
1235
+
1236
+ // Controls
1237
+ document.getElementById('link-distance').addEventListener('input', (e) => {
1238
+ simulation.force('link').distance(+e.target.value);
1239
+ simulation.alpha(0.3).restart();
1240
+ });
1241
+
1242
+ document.getElementById('charge-strength').addEventListener('input', (e) => {
1243
+ simulation.force('charge').strength(+e.target.value);
1244
+ simulation.alpha(0.3).restart();
1245
+ });
1246
+
1247
+ document.getElementById('show-labels').addEventListener('change', (e) => {
1248
+ labels.style('display', e.target.checked ? 'block' : 'none');
1249
+ });
1250
+
1251
+ document.getElementById('show-orphans').addEventListener('change', (e) => {
1252
+ const connectedIds = new Set();
1253
+ links.forEach(l => {
1254
+ connectedIds.add(typeof l.source === 'object' ? l.source.id : l.source);
1255
+ connectedIds.add(typeof l.target === 'object' ? l.target.id : l.target);
1256
+ });
1257
+
1258
+ node.style('display', d => {
1259
+ if (e.target.checked) return 'block';
1260
+ return connectedIds.has(d.id) ? 'block' : 'none';
1261
+ });
1262
+ });
1263
+
1264
+ // Type filters
1265
+ typeFiltersDiv.addEventListener('change', (e) => {
1266
+ if (e.target.type === 'checkbox') {
1267
+ const type = e.target.dataset.type;
1268
+ if (e.target.checked) {
1269
+ enabledTypes.add(type);
1270
+ } else {
1271
+ enabledTypes.delete(type);
1272
+ }
1273
+
1274
+ node.style('display', d => enabledTypes.has(d.type) ? 'block' : 'none');
1275
+ link.style('display', d => {
1276
+ const sourceType = typeof d.source === 'object' ? d.source.type : nodes.find(n => n.id === d.source)?.type;
1277
+ const targetType = typeof d.target === 'object' ? d.target.type : nodes.find(n => n.id === d.target)?.type;
1278
+ return enabledTypes.has(sourceType) && enabledTypes.has(targetType) ? 'block' : 'none';
1279
+ });
1280
+ }
1281
+ });
1282
+
1283
+ // Initial zoom to fit
1284
+ const initialScale = 0.8;
1285
+ svg.call(zoom.transform, d3.zoomIdentity
1286
+ .translate(width * (1 - initialScale) / 2, height * (1 - initialScale) / 2)
1287
+ .scale(initialScale));
1288
+ </script>
1289
+ </body>
1290
+ </html>
1291
+ '''
1292
+
1293
+
1294
+ def generate_visualization(
1295
+ graph_path: str,
1296
+ output_path: str,
1297
+ title: str = "Knowledge Graph"
1298
+ ) -> str:
1299
+ """
1300
+ Generate an interactive HTML visualization of the knowledge graph.
1301
+
1302
+ Args:
1303
+ graph_path: Path to the knowledge graph JSON file
1304
+ output_path: Path to write the HTML file
1305
+ title: Title for the visualization
1306
+
1307
+ Returns:
1308
+ Path to the generated HTML file
1309
+ """
1310
+ # Load graph
1311
+ graph_path = Path(graph_path)
1312
+ if not graph_path.exists():
1313
+ raise FileNotFoundError(f"Graph not found: {graph_path}")
1314
+
1315
+ with open(graph_path, 'r') as f:
1316
+ graph_data = json.load(f)
1317
+
1318
+ # Handle NetworkX 3.5+ compatibility: may have "edges" instead of "links"
1319
+ if 'edges' in graph_data and 'links' not in graph_data:
1320
+ graph_data['links'] = graph_data.pop('edges')
1321
+
1322
+ # Prepare data for JavaScript - properly escape for embedding in HTML
1323
+ # Convert to JSON strings that will be valid JavaScript
1324
+ graph_json = json.dumps(graph_data, default=str, ensure_ascii=True)
1325
+ type_colors_json = json.dumps(TYPE_COLORS, ensure_ascii=True)
1326
+ relation_colors_json = json.dumps(RELATION_COLORS, ensure_ascii=True)
1327
+
1328
+ # Escape special HTML characters that could break the script tag
1329
+ # This prevents issues with large JSON data containing HTML-like content
1330
+ graph_json = graph_json.replace('</', '<\\/')
1331
+ type_colors_json = type_colors_json.replace('</', '<\\/')
1332
+ relation_colors_json = relation_colors_json.replace('</', '<\\/')
1333
+
1334
+ # Generate HTML - build valid HTML structure first
1335
+ html = HTML_TEMPLATE.replace('<title>Knowledge Graph Visualization</title>',
1336
+ f'<title>{title}</title>')
1337
+
1338
+ # Now inject the properly escaped JSON data into the script placeholders
1339
+ html = html.replace('GRAPH_DATA_PLACEHOLDER', graph_json)
1340
+ html = html.replace('TYPE_COLORS_PLACEHOLDER', type_colors_json)
1341
+ html = html.replace('RELATION_COLORS_PLACEHOLDER', relation_colors_json)
1342
+
1343
+ # Write output
1344
+ output_path = Path(output_path)
1345
+ output_path.parent.mkdir(parents=True, exist_ok=True)
1346
+
1347
+ with open(output_path, 'w', encoding='utf-8') as f:
1348
+ f.write(html)
1349
+
1350
+ logger.info(f"Generated visualization: {output_path} ({len(html)} bytes)")
1351
+ return str(output_path)
1352
+
1353
+
1354
+ def main():
1355
+ """CLI entry point."""
1356
+ import argparse
1357
+
1358
+ parser = argparse.ArgumentParser(description='Generate knowledge graph visualization')
1359
+ parser.add_argument('--graph', '-g', required=True, help='Path to graph JSON file')
1360
+ parser.add_argument('--output', '-o', default='graph.html', help='Output HTML file')
1361
+ parser.add_argument('--title', '-t', default='Knowledge Graph', help='Visualization title')
1362
+
1363
+ args = parser.parse_args()
1364
+
1365
+ output = generate_visualization(args.graph, args.output, args.title)
1366
+ print(f"Generated: {output}")
1367
+
1368
+
1369
+ if __name__ == '__main__':
1370
+ main()