shieldcortex 2.0.0

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.
Files changed (377) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +282 -0
  3. package/dashboard/components.json +22 -0
  4. package/dashboard/eslint.config.mjs +42 -0
  5. package/dashboard/next.config.ts +7 -0
  6. package/dashboard/package-lock.json +8053 -0
  7. package/dashboard/package.json +44 -0
  8. package/dashboard/postcss.config.mjs +7 -0
  9. package/dashboard/public/file.svg +1 -0
  10. package/dashboard/public/globe.svg +1 -0
  11. package/dashboard/public/next.svg +1 -0
  12. package/dashboard/public/vercel.svg +1 -0
  13. package/dashboard/public/window.svg +1 -0
  14. package/dashboard/scripts/ensure-api.mjs +76 -0
  15. package/dashboard/src/app/error.tsx +49 -0
  16. package/dashboard/src/app/favicon.ico +0 -0
  17. package/dashboard/src/app/globals.css +130 -0
  18. package/dashboard/src/app/layout.tsx +35 -0
  19. package/dashboard/src/app/page.tsx +364 -0
  20. package/dashboard/src/components/Providers.tsx +27 -0
  21. package/dashboard/src/components/brain/ActivityPulseSystem.tsx +229 -0
  22. package/dashboard/src/components/brain/BrainMesh.tsx +133 -0
  23. package/dashboard/src/components/brain/BrainRegions.tsx +254 -0
  24. package/dashboard/src/components/brain/BrainScene.tsx +255 -0
  25. package/dashboard/src/components/brain/CategoryLabels.tsx +103 -0
  26. package/dashboard/src/components/brain/CoreSphere.tsx +215 -0
  27. package/dashboard/src/components/brain/DataFlowParticles.tsx +123 -0
  28. package/dashboard/src/components/brain/DataStreamRings.tsx +161 -0
  29. package/dashboard/src/components/brain/ElectronFlow.tsx +323 -0
  30. package/dashboard/src/components/brain/HolographicGrid.tsx +235 -0
  31. package/dashboard/src/components/brain/MemoryLinks.tsx +271 -0
  32. package/dashboard/src/components/brain/MemoryNode.tsx +245 -0
  33. package/dashboard/src/components/brain/NeuralPathways.tsx +441 -0
  34. package/dashboard/src/components/brain/SynapseNodes.tsx +312 -0
  35. package/dashboard/src/components/brain/TimelineControls.tsx +205 -0
  36. package/dashboard/src/components/chip/ChipScene.tsx +497 -0
  37. package/dashboard/src/components/chip/ChipSubstrate.tsx +238 -0
  38. package/dashboard/src/components/chip/CortexCore.tsx +210 -0
  39. package/dashboard/src/components/chip/DataBus.tsx +416 -0
  40. package/dashboard/src/components/chip/MemoryCell.tsx +225 -0
  41. package/dashboard/src/components/chip/MemoryGrid.tsx +328 -0
  42. package/dashboard/src/components/chip/QuantumCell.tsx +316 -0
  43. package/dashboard/src/components/chip/SectionLabel.tsx +113 -0
  44. package/dashboard/src/components/chip/index.ts +14 -0
  45. package/dashboard/src/components/controls/ControlPanel.tsx +106 -0
  46. package/dashboard/src/components/controls/VersionPanel.tsx +185 -0
  47. package/dashboard/src/components/dashboard/StatsPanel.tsx +164 -0
  48. package/dashboard/src/components/debug/ActivityLog.tsx +250 -0
  49. package/dashboard/src/components/debug/DebugPanel.tsx +101 -0
  50. package/dashboard/src/components/debug/QueryTester.tsx +192 -0
  51. package/dashboard/src/components/debug/RelationshipGraph.tsx +403 -0
  52. package/dashboard/src/components/debug/SqlConsole.tsx +319 -0
  53. package/dashboard/src/components/graph/KnowledgeGraph.tsx +230 -0
  54. package/dashboard/src/components/graph/OntologyGraph.tsx +631 -0
  55. package/dashboard/src/components/insights/ActivityHeatmap.tsx +131 -0
  56. package/dashboard/src/components/insights/InsightsView.tsx +46 -0
  57. package/dashboard/src/components/insights/KnowledgeMapPanel.tsx +80 -0
  58. package/dashboard/src/components/insights/QualityPanel.tsx +116 -0
  59. package/dashboard/src/components/memories/MemoriesView.tsx +150 -0
  60. package/dashboard/src/components/memories/MemoryCard.tsx +103 -0
  61. package/dashboard/src/components/memory/MemoryDetail.tsx +325 -0
  62. package/dashboard/src/components/nav/NavRail.tsx +54 -0
  63. package/dashboard/src/components/ui/button.tsx +62 -0
  64. package/dashboard/src/components/ui/card.tsx +92 -0
  65. package/dashboard/src/components/ui/input.tsx +21 -0
  66. package/dashboard/src/hooks/useDebouncedValue.ts +24 -0
  67. package/dashboard/src/hooks/useMemories.ts +458 -0
  68. package/dashboard/src/hooks/useSuggestions.ts +46 -0
  69. package/dashboard/src/lib/category-colors.ts +84 -0
  70. package/dashboard/src/lib/position-algorithm.ts +177 -0
  71. package/dashboard/src/lib/simplex-noise.ts +217 -0
  72. package/dashboard/src/lib/store.ts +88 -0
  73. package/dashboard/src/lib/utils.ts +6 -0
  74. package/dashboard/src/lib/websocket.ts +249 -0
  75. package/dashboard/src/types/memory.ts +73 -0
  76. package/dashboard/tsconfig.json +34 -0
  77. package/dist/__tests__/consolidation-merge.test.d.ts +9 -0
  78. package/dist/__tests__/consolidation-merge.test.d.ts.map +1 -0
  79. package/dist/__tests__/consolidation-merge.test.js +137 -0
  80. package/dist/__tests__/consolidation-merge.test.js.map +1 -0
  81. package/dist/__tests__/contradictions.test.d.ts +8 -0
  82. package/dist/__tests__/contradictions.test.d.ts.map +1 -0
  83. package/dist/__tests__/contradictions.test.js +78 -0
  84. package/dist/__tests__/contradictions.test.js.map +1 -0
  85. package/dist/__tests__/salience-evolution.test.d.ts +7 -0
  86. package/dist/__tests__/salience-evolution.test.d.ts.map +1 -0
  87. package/dist/__tests__/salience-evolution.test.js +151 -0
  88. package/dist/__tests__/salience-evolution.test.js.map +1 -0
  89. package/dist/__tests__/store.test.d.ts +7 -0
  90. package/dist/__tests__/store.test.d.ts.map +1 -0
  91. package/dist/__tests__/store.test.js +582 -0
  92. package/dist/__tests__/store.test.js.map +1 -0
  93. package/dist/api/control.d.ts +27 -0
  94. package/dist/api/control.d.ts.map +1 -0
  95. package/dist/api/control.js +60 -0
  96. package/dist/api/control.js.map +1 -0
  97. package/dist/api/events.d.ts +159 -0
  98. package/dist/api/events.d.ts.map +1 -0
  99. package/dist/api/events.js +155 -0
  100. package/dist/api/events.js.map +1 -0
  101. package/dist/api/version.d.ts +36 -0
  102. package/dist/api/version.d.ts.map +1 -0
  103. package/dist/api/version.js +146 -0
  104. package/dist/api/version.js.map +1 -0
  105. package/dist/api/visualization-server.d.ts +11 -0
  106. package/dist/api/visualization-server.d.ts.map +1 -0
  107. package/dist/api/visualization-server.js +1186 -0
  108. package/dist/api/visualization-server.js.map +1 -0
  109. package/dist/context/project-context.d.ts +57 -0
  110. package/dist/context/project-context.d.ts.map +1 -0
  111. package/dist/context/project-context.js +135 -0
  112. package/dist/context/project-context.js.map +1 -0
  113. package/dist/database/init.d.ts +49 -0
  114. package/dist/database/init.d.ts.map +1 -0
  115. package/dist/database/init.js +567 -0
  116. package/dist/database/init.js.map +1 -0
  117. package/dist/defence/__tests__/firewall.test.d.ts +8 -0
  118. package/dist/defence/__tests__/firewall.test.d.ts.map +1 -0
  119. package/dist/defence/__tests__/firewall.test.js +123 -0
  120. package/dist/defence/__tests__/firewall.test.js.map +1 -0
  121. package/dist/defence/__tests__/fragmentation.test.d.ts +7 -0
  122. package/dist/defence/__tests__/fragmentation.test.d.ts.map +1 -0
  123. package/dist/defence/__tests__/fragmentation.test.js +51 -0
  124. package/dist/defence/__tests__/fragmentation.test.js.map +1 -0
  125. package/dist/defence/__tests__/pipeline.test.d.ts +8 -0
  126. package/dist/defence/__tests__/pipeline.test.d.ts.map +1 -0
  127. package/dist/defence/__tests__/pipeline.test.js +61 -0
  128. package/dist/defence/__tests__/pipeline.test.js.map +1 -0
  129. package/dist/defence/__tests__/sensitivity.test.d.ts +7 -0
  130. package/dist/defence/__tests__/sensitivity.test.d.ts.map +1 -0
  131. package/dist/defence/__tests__/sensitivity.test.js +61 -0
  132. package/dist/defence/__tests__/sensitivity.test.js.map +1 -0
  133. package/dist/defence/__tests__/trust.test.d.ts +7 -0
  134. package/dist/defence/__tests__/trust.test.d.ts.map +1 -0
  135. package/dist/defence/__tests__/trust.test.js +49 -0
  136. package/dist/defence/__tests__/trust.test.js.map +1 -0
  137. package/dist/defence/audit/index.d.ts +4 -0
  138. package/dist/defence/audit/index.d.ts.map +1 -0
  139. package/dist/defence/audit/index.js +3 -0
  140. package/dist/defence/audit/index.js.map +1 -0
  141. package/dist/defence/audit/logger.d.ts +14 -0
  142. package/dist/defence/audit/logger.d.ts.map +1 -0
  143. package/dist/defence/audit/logger.js +54 -0
  144. package/dist/defence/audit/logger.js.map +1 -0
  145. package/dist/defence/audit/queries.d.ts +33 -0
  146. package/dist/defence/audit/queries.d.ts.map +1 -0
  147. package/dist/defence/audit/queries.js +103 -0
  148. package/dist/defence/audit/queries.js.map +1 -0
  149. package/dist/defence/firewall/anomaly-scorer.d.ts +8 -0
  150. package/dist/defence/firewall/anomaly-scorer.d.ts.map +1 -0
  151. package/dist/defence/firewall/anomaly-scorer.js +58 -0
  152. package/dist/defence/firewall/anomaly-scorer.js.map +1 -0
  153. package/dist/defence/firewall/encoding-detector.d.ts +13 -0
  154. package/dist/defence/firewall/encoding-detector.d.ts.map +1 -0
  155. package/dist/defence/firewall/encoding-detector.js +120 -0
  156. package/dist/defence/firewall/encoding-detector.js.map +1 -0
  157. package/dist/defence/firewall/index.d.ts +21 -0
  158. package/dist/defence/firewall/index.d.ts.map +1 -0
  159. package/dist/defence/firewall/index.js +133 -0
  160. package/dist/defence/firewall/index.js.map +1 -0
  161. package/dist/defence/firewall/instruction-detector.d.ts +12 -0
  162. package/dist/defence/firewall/instruction-detector.d.ts.map +1 -0
  163. package/dist/defence/firewall/instruction-detector.js +99 -0
  164. package/dist/defence/firewall/instruction-detector.js.map +1 -0
  165. package/dist/defence/firewall/privilege-detector.d.ts +13 -0
  166. package/dist/defence/firewall/privilege-detector.d.ts.map +1 -0
  167. package/dist/defence/firewall/privilege-detector.js +89 -0
  168. package/dist/defence/firewall/privilege-detector.js.map +1 -0
  169. package/dist/defence/fragmentation/assembly-detector.d.ts +18 -0
  170. package/dist/defence/fragmentation/assembly-detector.d.ts.map +1 -0
  171. package/dist/defence/fragmentation/assembly-detector.js +72 -0
  172. package/dist/defence/fragmentation/assembly-detector.js.map +1 -0
  173. package/dist/defence/fragmentation/entity-extractor.d.ts +19 -0
  174. package/dist/defence/fragmentation/entity-extractor.d.ts.map +1 -0
  175. package/dist/defence/fragmentation/entity-extractor.js +86 -0
  176. package/dist/defence/fragmentation/entity-extractor.js.map +1 -0
  177. package/dist/defence/fragmentation/index.d.ts +23 -0
  178. package/dist/defence/fragmentation/index.d.ts.map +1 -0
  179. package/dist/defence/fragmentation/index.js +49 -0
  180. package/dist/defence/fragmentation/index.js.map +1 -0
  181. package/dist/defence/fragmentation/temporal-analyzer.d.ts +28 -0
  182. package/dist/defence/fragmentation/temporal-analyzer.d.ts.map +1 -0
  183. package/dist/defence/fragmentation/temporal-analyzer.js +41 -0
  184. package/dist/defence/fragmentation/temporal-analyzer.js.map +1 -0
  185. package/dist/defence/index.d.ts +12 -0
  186. package/dist/defence/index.d.ts.map +1 -0
  187. package/dist/defence/index.js +18 -0
  188. package/dist/defence/index.js.map +1 -0
  189. package/dist/defence/pipeline.d.ts +9 -0
  190. package/dist/defence/pipeline.d.ts.map +1 -0
  191. package/dist/defence/pipeline.js +115 -0
  192. package/dist/defence/pipeline.js.map +1 -0
  193. package/dist/defence/scanner/index.d.ts +5 -0
  194. package/dist/defence/scanner/index.d.ts.map +1 -0
  195. package/dist/defence/scanner/index.js +5 -0
  196. package/dist/defence/scanner/index.js.map +1 -0
  197. package/dist/defence/scanner/scan-existing.d.ts +34 -0
  198. package/dist/defence/scanner/scan-existing.d.ts.map +1 -0
  199. package/dist/defence/scanner/scan-existing.js +136 -0
  200. package/dist/defence/scanner/scan-existing.js.map +1 -0
  201. package/dist/defence/sensitivity/classifier.d.ts +6 -0
  202. package/dist/defence/sensitivity/classifier.d.ts.map +1 -0
  203. package/dist/defence/sensitivity/classifier.js +50 -0
  204. package/dist/defence/sensitivity/classifier.js.map +1 -0
  205. package/dist/defence/sensitivity/index.d.ts +11 -0
  206. package/dist/defence/sensitivity/index.d.ts.map +1 -0
  207. package/dist/defence/sensitivity/index.js +13 -0
  208. package/dist/defence/sensitivity/index.js.map +1 -0
  209. package/dist/defence/sensitivity/patterns.d.ts +14 -0
  210. package/dist/defence/sensitivity/patterns.d.ts.map +1 -0
  211. package/dist/defence/sensitivity/patterns.js +67 -0
  212. package/dist/defence/sensitivity/patterns.js.map +1 -0
  213. package/dist/defence/sensitivity/redaction.d.ts +17 -0
  214. package/dist/defence/sensitivity/redaction.d.ts.map +1 -0
  215. package/dist/defence/sensitivity/redaction.js +47 -0
  216. package/dist/defence/sensitivity/redaction.js.map +1 -0
  217. package/dist/defence/trust/index.d.ts +3 -0
  218. package/dist/defence/trust/index.d.ts.map +1 -0
  219. package/dist/defence/trust/index.js +3 -0
  220. package/dist/defence/trust/index.js.map +1 -0
  221. package/dist/defence/trust/recall-filter.d.ts +10 -0
  222. package/dist/defence/trust/recall-filter.d.ts.map +1 -0
  223. package/dist/defence/trust/recall-filter.js +38 -0
  224. package/dist/defence/trust/recall-filter.js.map +1 -0
  225. package/dist/defence/trust/source-scorer.d.ts +6 -0
  226. package/dist/defence/trust/source-scorer.d.ts.map +1 -0
  227. package/dist/defence/trust/source-scorer.js +34 -0
  228. package/dist/defence/trust/source-scorer.js.map +1 -0
  229. package/dist/defence/types.d.ts +88 -0
  230. package/dist/defence/types.d.ts.map +1 -0
  231. package/dist/defence/types.js +15 -0
  232. package/dist/defence/types.js.map +1 -0
  233. package/dist/embeddings/generator.d.ts +20 -0
  234. package/dist/embeddings/generator.d.ts.map +1 -0
  235. package/dist/embeddings/generator.js +83 -0
  236. package/dist/embeddings/generator.js.map +1 -0
  237. package/dist/embeddings/index.d.ts +2 -0
  238. package/dist/embeddings/index.d.ts.map +1 -0
  239. package/dist/embeddings/index.js +2 -0
  240. package/dist/embeddings/index.js.map +1 -0
  241. package/dist/errors.d.ts +74 -0
  242. package/dist/errors.d.ts.map +1 -0
  243. package/dist/errors.js +131 -0
  244. package/dist/errors.js.map +1 -0
  245. package/dist/graph/backfill.d.ts +6 -0
  246. package/dist/graph/backfill.d.ts.map +1 -0
  247. package/dist/graph/backfill.js +33 -0
  248. package/dist/graph/backfill.js.map +1 -0
  249. package/dist/graph/extract.d.ts +21 -0
  250. package/dist/graph/extract.d.ts.map +1 -0
  251. package/dist/graph/extract.js +231 -0
  252. package/dist/graph/extract.js.map +1 -0
  253. package/dist/graph/resolve.d.ts +6 -0
  254. package/dist/graph/resolve.d.ts.map +1 -0
  255. package/dist/graph/resolve.js +126 -0
  256. package/dist/graph/resolve.js.map +1 -0
  257. package/dist/index.d.ts +31 -0
  258. package/dist/index.d.ts.map +1 -0
  259. package/dist/index.js +248 -0
  260. package/dist/index.js.map +1 -0
  261. package/dist/memory/activation.d.ts +69 -0
  262. package/dist/memory/activation.d.ts.map +1 -0
  263. package/dist/memory/activation.js +168 -0
  264. package/dist/memory/activation.js.map +1 -0
  265. package/dist/memory/consolidate.d.ts +98 -0
  266. package/dist/memory/consolidate.d.ts.map +1 -0
  267. package/dist/memory/consolidate.js +511 -0
  268. package/dist/memory/consolidate.js.map +1 -0
  269. package/dist/memory/contradiction.d.ts +69 -0
  270. package/dist/memory/contradiction.d.ts.map +1 -0
  271. package/dist/memory/contradiction.js +286 -0
  272. package/dist/memory/contradiction.js.map +1 -0
  273. package/dist/memory/decay.d.ts +62 -0
  274. package/dist/memory/decay.d.ts.map +1 -0
  275. package/dist/memory/decay.js +184 -0
  276. package/dist/memory/decay.js.map +1 -0
  277. package/dist/memory/salience.d.ts +36 -0
  278. package/dist/memory/salience.d.ts.map +1 -0
  279. package/dist/memory/salience.js +216 -0
  280. package/dist/memory/salience.js.map +1 -0
  281. package/dist/memory/similarity.d.ts +57 -0
  282. package/dist/memory/similarity.d.ts.map +1 -0
  283. package/dist/memory/similarity.js +114 -0
  284. package/dist/memory/similarity.js.map +1 -0
  285. package/dist/memory/store.d.ts +179 -0
  286. package/dist/memory/store.d.ts.map +1 -0
  287. package/dist/memory/store.js +1184 -0
  288. package/dist/memory/store.js.map +1 -0
  289. package/dist/memory/types.d.ts +97 -0
  290. package/dist/memory/types.d.ts.map +1 -0
  291. package/dist/memory/types.js +30 -0
  292. package/dist/memory/types.js.map +1 -0
  293. package/dist/server.d.ts +12 -0
  294. package/dist/server.d.ts.map +1 -0
  295. package/dist/server.js +568 -0
  296. package/dist/server.js.map +1 -0
  297. package/dist/service/install.d.ts +15 -0
  298. package/dist/service/install.d.ts.map +1 -0
  299. package/dist/service/install.js +178 -0
  300. package/dist/service/install.js.map +1 -0
  301. package/dist/service/templates.d.ts +13 -0
  302. package/dist/service/templates.d.ts.map +1 -0
  303. package/dist/service/templates.js +58 -0
  304. package/dist/service/templates.js.map +1 -0
  305. package/dist/setup/claude-md.d.ts +12 -0
  306. package/dist/setup/claude-md.d.ts.map +1 -0
  307. package/dist/setup/claude-md.js +68 -0
  308. package/dist/setup/claude-md.js.map +1 -0
  309. package/dist/setup/clawdbot.d.ts +15 -0
  310. package/dist/setup/clawdbot.d.ts.map +1 -0
  311. package/dist/setup/clawdbot.js +118 -0
  312. package/dist/setup/clawdbot.js.map +1 -0
  313. package/dist/setup/doctor.d.ts +5 -0
  314. package/dist/setup/doctor.d.ts.map +1 -0
  315. package/dist/setup/doctor.js +141 -0
  316. package/dist/setup/doctor.js.map +1 -0
  317. package/dist/setup/hooks.d.ts +6 -0
  318. package/dist/setup/hooks.d.ts.map +1 -0
  319. package/dist/setup/hooks.js +36 -0
  320. package/dist/setup/hooks.js.map +1 -0
  321. package/dist/setup/migrate.d.ts +16 -0
  322. package/dist/setup/migrate.d.ts.map +1 -0
  323. package/dist/setup/migrate.js +164 -0
  324. package/dist/setup/migrate.js.map +1 -0
  325. package/dist/setup/settings-hooks.d.ts +7 -0
  326. package/dist/setup/settings-hooks.d.ts.map +1 -0
  327. package/dist/setup/settings-hooks.js +83 -0
  328. package/dist/setup/settings-hooks.js.map +1 -0
  329. package/dist/setup/uninstall.d.ts +12 -0
  330. package/dist/setup/uninstall.d.ts.map +1 -0
  331. package/dist/setup/uninstall.js +125 -0
  332. package/dist/setup/uninstall.js.map +1 -0
  333. package/dist/tools/context.d.ts +135 -0
  334. package/dist/tools/context.d.ts.map +1 -0
  335. package/dist/tools/context.js +273 -0
  336. package/dist/tools/context.js.map +1 -0
  337. package/dist/tools/forget.d.ts +53 -0
  338. package/dist/tools/forget.d.ts.map +1 -0
  339. package/dist/tools/forget.js +179 -0
  340. package/dist/tools/forget.js.map +1 -0
  341. package/dist/tools/graph.d.ts +46 -0
  342. package/dist/tools/graph.d.ts.map +1 -0
  343. package/dist/tools/graph.js +206 -0
  344. package/dist/tools/graph.js.map +1 -0
  345. package/dist/tools/recall.d.ts +79 -0
  346. package/dist/tools/recall.d.ts.map +1 -0
  347. package/dist/tools/recall.js +156 -0
  348. package/dist/tools/recall.js.map +1 -0
  349. package/dist/tools/remember.d.ts +83 -0
  350. package/dist/tools/remember.d.ts.map +1 -0
  351. package/dist/tools/remember.js +151 -0
  352. package/dist/tools/remember.js.map +1 -0
  353. package/dist/worker/brain-worker.d.ts +100 -0
  354. package/dist/worker/brain-worker.d.ts.map +1 -0
  355. package/dist/worker/brain-worker.js +283 -0
  356. package/dist/worker/brain-worker.js.map +1 -0
  357. package/dist/worker/link-discovery.d.ts +47 -0
  358. package/dist/worker/link-discovery.d.ts.map +1 -0
  359. package/dist/worker/link-discovery.js +103 -0
  360. package/dist/worker/link-discovery.js.map +1 -0
  361. package/dist/worker/predictive-consolidation.d.ts +46 -0
  362. package/dist/worker/predictive-consolidation.d.ts.map +1 -0
  363. package/dist/worker/predictive-consolidation.js +110 -0
  364. package/dist/worker/predictive-consolidation.js.map +1 -0
  365. package/dist/worker/types.d.ts +91 -0
  366. package/dist/worker/types.d.ts.map +1 -0
  367. package/dist/worker/types.js +22 -0
  368. package/dist/worker/types.js.map +1 -0
  369. package/hooks/clawdbot/cortex-memory/HOOK.md +71 -0
  370. package/hooks/clawdbot/cortex-memory/handler.js +279 -0
  371. package/package.json +73 -0
  372. package/scripts/pre-compact-hook.mjs +716 -0
  373. package/scripts/session-end-hook.mjs +548 -0
  374. package/scripts/session-start-hook.mjs +221 -0
  375. package/scripts/start-dashboard.sh +41 -0
  376. package/scripts/stop-dashboard.sh +21 -0
  377. package/scripts/stop-hook.mjs +163 -0
@@ -0,0 +1,631 @@
1
+ 'use client';
2
+
3
+ import { useCallback, useEffect, useMemo, useRef, useState } from 'react';
4
+ import dynamic from 'next/dynamic';
5
+ import { Search, X } from 'lucide-react';
6
+
7
+ const ForceGraph2D = dynamic(() => import('react-force-graph-2d'), { ssr: false });
8
+
9
+ const API_BASE = process.env.NEXT_PUBLIC_API_URL || 'http://localhost:3001';
10
+
11
+ // Entity type color map
12
+ const ENTITY_COLORS: Record<string, string> = {
13
+ tool: '#22d3ee',
14
+ person: '#34d399',
15
+ concept: '#f59e0b',
16
+ language: '#a78bfa',
17
+ file: '#64748b',
18
+ service: '#f472b6',
19
+ pattern: '#fb923c',
20
+ };
21
+
22
+ const DEFAULT_COLOR = '#94a3b8';
23
+
24
+ // Predicate colors for edges
25
+ const PREDICATE_COLORS: Record<string, string> = {
26
+ uses: '#22d3ee',
27
+ implements: '#34d399',
28
+ depends_on: '#f59e0b',
29
+ related_to: '#94a3b8',
30
+ part_of: '#a78bfa',
31
+ created_by: '#f472b6',
32
+ extends: '#fb923c',
33
+ };
34
+
35
+ const DEFAULT_EDGE_COLOR = '#475569';
36
+
37
+ interface Entity {
38
+ id: number;
39
+ name: string;
40
+ type: string;
41
+ memoryCount: number;
42
+ aliases: string[];
43
+ }
44
+
45
+ interface Triple {
46
+ id: number;
47
+ subject_id: number;
48
+ object_id: number;
49
+ predicate: string;
50
+ subject_name: string;
51
+ subject_type: string;
52
+ object_name: string;
53
+ object_type: string;
54
+ }
55
+
56
+ interface GraphNode {
57
+ id: number;
58
+ name: string;
59
+ entityType: string;
60
+ memoryCount: number;
61
+ val: number;
62
+ }
63
+
64
+ interface GraphLink {
65
+ source: number;
66
+ target: number;
67
+ predicate: string;
68
+ }
69
+
70
+ interface GraphData {
71
+ nodes: GraphNode[];
72
+ links: GraphLink[];
73
+ }
74
+
75
+ interface LinkedMemory {
76
+ id: number;
77
+ title: string;
78
+ type: string;
79
+ category: string;
80
+ salience: number;
81
+ created_at: string;
82
+ }
83
+
84
+ interface EntityDetail {
85
+ entity: Entity;
86
+ triples: Triple[];
87
+ memories: LinkedMemory[];
88
+ }
89
+
90
+ const ALL_TYPES = ['tool', 'person', 'concept', 'language', 'file', 'service', 'pattern'];
91
+
92
+ export default function OntologyGraph() {
93
+ const containerRef = useRef<HTMLDivElement>(null);
94
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
95
+ const graphRef = useRef<any>(null);
96
+ const [dimensions, setDimensions] = useState({ width: 800, height: 600 });
97
+ const [entities, setEntities] = useState<Entity[]>([]);
98
+ const [triples, setTriples] = useState<Triple[]>([]);
99
+ const [loading, setLoading] = useState(true);
100
+ const [error, setError] = useState<string | null>(null);
101
+ const [selectedEntity, setSelectedEntity] = useState<EntityDetail | null>(null);
102
+ const [searchQuery, setSearchQuery] = useState('');
103
+ const [activeTypes, setActiveTypes] = useState<Set<string>>(new Set(ALL_TYPES));
104
+
105
+ // Resize observer
106
+ useEffect(() => {
107
+ const el = containerRef.current;
108
+ if (!el) return;
109
+ const update = () => setDimensions({ width: el.clientWidth, height: el.clientHeight });
110
+ update();
111
+ const observer = new ResizeObserver(update);
112
+ observer.observe(el);
113
+ return () => observer.disconnect();
114
+ }, []);
115
+
116
+ // Fetch data
117
+ useEffect(() => {
118
+ let cancelled = false;
119
+
120
+ async function fetchData() {
121
+ setLoading(true);
122
+ setError(null);
123
+ try {
124
+ const [entRes, triRes] = await Promise.all([
125
+ fetch(`${API_BASE}/api/graph/entities?limit=500`).then(r => r.json()),
126
+ fetch(`${API_BASE}/api/graph/triples?limit=500`).then(r => r.json()),
127
+ ]);
128
+ if (cancelled) return;
129
+ setEntities(entRes.entities || []);
130
+ setTriples(triRes.triples || []);
131
+ setLoading(false);
132
+ } catch (err) {
133
+ if (cancelled) return;
134
+ setError((err as Error).message);
135
+ setLoading(false);
136
+ }
137
+ }
138
+
139
+ fetchData();
140
+ return () => { cancelled = true; };
141
+ }, []);
142
+
143
+ // Filter entities
144
+ const filteredEntities = useMemo(() => {
145
+ return entities.filter(e => {
146
+ if (!activeTypes.has(e.type)) return false;
147
+ if (searchQuery && !e.name.toLowerCase().includes(searchQuery.toLowerCase())) return false;
148
+ return true;
149
+ });
150
+ }, [entities, activeTypes, searchQuery]);
151
+
152
+ // Build graph data
153
+ const graphData: GraphData = useMemo(() => {
154
+ const nodeIds = new Set(filteredEntities.map(e => e.id));
155
+ const maxCount = Math.max(1, ...filteredEntities.map(e => e.memoryCount));
156
+
157
+ return {
158
+ nodes: filteredEntities.map(e => ({
159
+ id: e.id,
160
+ name: e.name,
161
+ entityType: e.type,
162
+ memoryCount: e.memoryCount,
163
+ val: 2 + (e.memoryCount / maxCount) * 10,
164
+ })),
165
+ links: triples
166
+ .filter(t => nodeIds.has(t.subject_id) && nodeIds.has(t.object_id))
167
+ .map(t => ({
168
+ source: t.subject_id,
169
+ target: t.object_id,
170
+ predicate: t.predicate,
171
+ })),
172
+ };
173
+ }, [filteredEntities, triples]);
174
+
175
+ // Computed stats
176
+ const typeBreakdown = useMemo(() => {
177
+ const counts: Record<string, number> = {};
178
+ for (const e of entities) {
179
+ counts[e.type] = (counts[e.type] || 0) + 1;
180
+ }
181
+ return ALL_TYPES
182
+ .map(t => ({ type: t, count: counts[t] || 0 }))
183
+ .filter(t => t.count > 0)
184
+ .sort((a, b) => b.count - a.count);
185
+ }, [entities]);
186
+
187
+ const topEntities = useMemo(() => {
188
+ return [...entities]
189
+ .sort((a, b) => b.memoryCount - a.memoryCount)
190
+ .slice(0, 15);
191
+ }, [entities]);
192
+
193
+ const predicateBreakdown = useMemo(() => {
194
+ const counts: Record<string, number> = {};
195
+ for (const t of triples) {
196
+ counts[t.predicate] = (counts[t.predicate] || 0) + 1;
197
+ }
198
+ return Object.entries(counts)
199
+ .map(([predicate, count]) => ({ predicate, count }))
200
+ .sort((a, b) => b.count - a.count);
201
+ }, [triples]);
202
+
203
+ const graphStats = useMemo(() => {
204
+ const typesPresent = new Set(entities.map(e => e.type)).size;
205
+ const connectionCounts: Record<number, number> = {};
206
+ for (const t of triples) {
207
+ connectionCounts[t.subject_id] = (connectionCounts[t.subject_id] || 0) + 1;
208
+ connectionCounts[t.object_id] = (connectionCounts[t.object_id] || 0) + 1;
209
+ }
210
+ const connValues = Object.values(connectionCounts);
211
+ const avgConnections = connValues.length > 0
212
+ ? (connValues.reduce((a, b) => a + b, 0) / connValues.length).toFixed(1)
213
+ : '0';
214
+ let mostConnected: Entity | null = null;
215
+ let maxConn = 0;
216
+ for (const [idStr, count] of Object.entries(connectionCounts)) {
217
+ if (count > maxConn) {
218
+ maxConn = count;
219
+ mostConnected = entities.find(e => e.id === Number(idStr)) || null;
220
+ }
221
+ }
222
+ return { typesPresent, avgConnections, mostConnected, maxConn };
223
+ }, [entities, triples]);
224
+
225
+ // Fetch entity detail (triples + memories)
226
+ const fetchEntityDetail = useCallback((entity: Entity) => {
227
+ Promise.all([
228
+ fetch(`${API_BASE}/api/graph/entities/${entity.id}/triples`).then(r => r.json()),
229
+ fetch(`${API_BASE}/api/graph/entities/${entity.id}/memories`).then(r => r.json()),
230
+ ])
231
+ .then(([triData, memData]) => {
232
+ setSelectedEntity({
233
+ entity,
234
+ triples: triData.triples || [],
235
+ memories: memData.memories || [],
236
+ });
237
+ })
238
+ .catch(() => {});
239
+ }, []);
240
+
241
+ // Click node -> fetch detail
242
+ const handleNodeClick = useCallback((node: GraphNode) => {
243
+ const entity = entities.find(e => e.id === node.id);
244
+ if (entity) fetchEntityDetail(entity);
245
+ }, [entities, fetchEntityDetail]);
246
+
247
+ const handleEntityClick = useCallback((entity: Entity) => {
248
+ fetchEntityDetail(entity);
249
+ }, [fetchEntityDetail]);
250
+
251
+ const toggleType = (type: string) => {
252
+ setActiveTypes(prev => {
253
+ const next = new Set(prev);
254
+ if (next.has(type)) next.delete(type);
255
+ else next.add(type);
256
+ return next;
257
+ });
258
+ };
259
+
260
+ // Canvas rendering
261
+ const nodeCanvasObject = useCallback(
262
+ (node: GraphNode, ctx: CanvasRenderingContext2D, globalScale: number) => {
263
+ const x = (node as unknown as { x: number }).x;
264
+ const y = (node as unknown as { y: number }).y;
265
+ if (x == null || y == null) return;
266
+
267
+ const radius = Math.max(3, node.val);
268
+ const color = ENTITY_COLORS[node.entityType] || DEFAULT_COLOR;
269
+ const isSelected = selectedEntity?.entity.id === node.id;
270
+
271
+ ctx.beginPath();
272
+ ctx.arc(x, y, radius, 0, 2 * Math.PI);
273
+ ctx.fillStyle = color;
274
+ ctx.fill();
275
+
276
+ if (isSelected) {
277
+ ctx.lineWidth = 2;
278
+ ctx.strokeStyle = '#ffffff';
279
+ ctx.stroke();
280
+ }
281
+
282
+ // Label when zoomed
283
+ if (globalScale > 3) {
284
+ const maxChars = 30;
285
+ const label = node.name.length > maxChars ? node.name.slice(0, maxChars) + '...' : node.name;
286
+ const fontSize = Math.max(10, 12 / globalScale);
287
+ ctx.font = `${fontSize}px Sans-Serif`;
288
+ ctx.textAlign = 'center';
289
+ ctx.textBaseline = 'top';
290
+
291
+ const textWidth = ctx.measureText(label).width;
292
+ const textY = y + radius + 3;
293
+ const padding = 2;
294
+
295
+ ctx.fillStyle = 'rgba(2, 6, 23, 0.85)';
296
+ ctx.beginPath();
297
+ ctx.roundRect(
298
+ x - textWidth / 2 - padding,
299
+ textY - padding,
300
+ textWidth + padding * 2,
301
+ fontSize + padding * 2,
302
+ 3,
303
+ );
304
+ ctx.fill();
305
+
306
+ ctx.fillStyle = '#e2e8f0';
307
+ ctx.fillText(label, x, textY);
308
+ }
309
+ },
310
+ [selectedEntity],
311
+ );
312
+
313
+ const linkCanvasObject = useCallback(
314
+ (link: GraphLink, ctx: CanvasRenderingContext2D) => {
315
+ const source = link.source as unknown as { x: number; y: number };
316
+ const target = link.target as unknown as { x: number; y: number };
317
+ if (!source?.x || !target?.x) return;
318
+
319
+ ctx.beginPath();
320
+ ctx.moveTo(source.x, source.y);
321
+ ctx.lineTo(target.x, target.y);
322
+ ctx.strokeStyle = PREDICATE_COLORS[link.predicate] || DEFAULT_EDGE_COLOR;
323
+ ctx.lineWidth = 1.5;
324
+ ctx.stroke();
325
+ },
326
+ [],
327
+ );
328
+
329
+ const linkLabel = useCallback((link: GraphLink) => link.predicate, []);
330
+
331
+ const nodeLabel = useCallback(
332
+ (node: GraphNode) =>
333
+ `${node.name}\nType: ${node.entityType}\nMemories: ${node.memoryCount}`,
334
+ [],
335
+ );
336
+
337
+ // Recent entities for bottom panel (excluding files for readability)
338
+ const recentEntities = useMemo(() => {
339
+ return [...entities]
340
+ .filter(e => e.type !== 'file')
341
+ .sort((a, b) => b.memoryCount - a.memoryCount)
342
+ .slice(0, 30);
343
+ }, [entities]);
344
+
345
+ if (loading) {
346
+ return (
347
+ <div className="w-full h-full flex items-center justify-center">
348
+ <div className="text-slate-400 animate-pulse">Loading ontology graph...</div>
349
+ </div>
350
+ );
351
+ }
352
+
353
+ if (error) {
354
+ return (
355
+ <div className="w-full h-full flex items-center justify-center">
356
+ <div className="text-red-400">Failed to load ontology: {error}</div>
357
+ </div>
358
+ );
359
+ }
360
+
361
+ const maxTypeCount = Math.max(1, ...typeBreakdown.map(t => t.count));
362
+
363
+ return (
364
+ <div style={{ width: '100%', height: '100%', display: 'flex', flexDirection: 'column', overflow: 'hidden' }}>
365
+ {/* Controls bar */}
366
+ <div className="flex items-center gap-3 px-4 py-2 border-b border-slate-800 bg-slate-900/50 shrink-0">
367
+ {/* Search */}
368
+ <div className="relative">
369
+ <Search size={14} className="absolute left-2 top-1/2 -translate-y-1/2 text-slate-500" />
370
+ <input
371
+ type="text"
372
+ placeholder="Filter entities..."
373
+ value={searchQuery}
374
+ onChange={e => setSearchQuery(e.target.value)}
375
+ className="pl-7 pr-2 py-1 text-sm bg-slate-800 border border-slate-700 rounded text-white placeholder:text-slate-500 w-48 focus:outline-none focus:ring-1 focus:ring-cyan-500"
376
+ />
377
+ </div>
378
+
379
+ {/* Type filters */}
380
+ <div className="flex items-center gap-1">
381
+ {ALL_TYPES.map(type => (
382
+ <button
383
+ key={type}
384
+ onClick={() => toggleType(type)}
385
+ className={`px-2 py-0.5 text-xs rounded-full border transition-colors ${
386
+ activeTypes.has(type)
387
+ ? 'border-transparent text-white'
388
+ : 'border-slate-600 text-slate-500 bg-transparent'
389
+ }`}
390
+ style={activeTypes.has(type) ? { backgroundColor: ENTITY_COLORS[type] + '40', color: ENTITY_COLORS[type] } : {}}
391
+ >
392
+ {type}
393
+ </button>
394
+ ))}
395
+ </div>
396
+
397
+ <span className="text-xs text-slate-500 ml-auto">
398
+ {filteredEntities.length} entities, {graphData.links.length} triples
399
+ </span>
400
+ </div>
401
+
402
+ {/* Main content: graph + sidebar */}
403
+ <div style={{ flex: 1, display: 'flex', minHeight: 0, overflow: 'hidden' }}>
404
+ {/* Force graph */}
405
+ <div ref={containerRef} style={{ flex: 3, minHeight: 0, minWidth: 0, position: 'relative' }}>
406
+ {dimensions.width > 0 && (
407
+ <ForceGraph2D
408
+ ref={graphRef as never}
409
+ graphData={graphData}
410
+ width={dimensions.width}
411
+ height={dimensions.height}
412
+ backgroundColor="rgba(0,0,0,0)"
413
+ nodeCanvasObject={nodeCanvasObject as never}
414
+ nodeLabel={nodeLabel as never}
415
+ onNodeClick={handleNodeClick as never}
416
+ linkCanvasObject={linkCanvasObject as never}
417
+ linkLabel={linkLabel as never}
418
+ linkDirectionalArrowLength={4}
419
+ linkDirectionalArrowRelPos={0.9}
420
+ d3AlphaDecay={0.02}
421
+ d3VelocityDecay={0.3}
422
+ warmupTicks={100}
423
+ cooldownTicks={200}
424
+ />
425
+ )}
426
+ </div>
427
+
428
+ {/* Right panel: detail or stats */}
429
+ <div className="border-l border-slate-800 bg-slate-900/30 overflow-y-auto" style={{ flex: 2, minWidth: 240, maxWidth: 400 }}>
430
+ {selectedEntity ? (
431
+ /* Entity detail */
432
+ <div className="p-4">
433
+ <div className="flex items-center justify-between mb-3">
434
+ <h3 className="text-sm font-semibold text-white truncate">{selectedEntity.entity.name}</h3>
435
+ <button onClick={() => setSelectedEntity(null)} className="text-slate-500 hover:text-white">
436
+ <X size={16} />
437
+ </button>
438
+ </div>
439
+
440
+ <div className="flex items-center gap-2 mb-4">
441
+ <span
442
+ className="text-xs px-2 py-0.5 rounded-full"
443
+ style={{ backgroundColor: (ENTITY_COLORS[selectedEntity.entity.type] || DEFAULT_COLOR) + '30', color: ENTITY_COLORS[selectedEntity.entity.type] || DEFAULT_COLOR }}
444
+ >
445
+ {selectedEntity.entity.type}
446
+ </span>
447
+ <span className="text-xs text-slate-500">{selectedEntity.entity.memoryCount} memories</span>
448
+ </div>
449
+
450
+ {selectedEntity.entity.aliases.length > 0 && (
451
+ <div className="mb-4">
452
+ <div className="text-xs text-slate-400 mb-1">Aliases</div>
453
+ <div className="flex flex-wrap gap-1">
454
+ {selectedEntity.entity.aliases.map((a, i) => (
455
+ <span key={i} className="text-xs bg-slate-800 text-slate-300 px-1.5 py-0.5 rounded">{a}</span>
456
+ ))}
457
+ </div>
458
+ </div>
459
+ )}
460
+
461
+ <div className="text-xs text-slate-400 mb-2">Relationships ({selectedEntity.triples.length})</div>
462
+ <div className="space-y-2 mb-4">
463
+ {selectedEntity.triples.map(t => (
464
+ <div key={t.id} className="text-xs bg-slate-800/50 rounded p-2">
465
+ <span className="text-cyan-400">{t.subject_name}</span>
466
+ <span className="text-slate-500 mx-1">{t.predicate}</span>
467
+ <span className="text-amber-400">{t.object_name}</span>
468
+ </div>
469
+ ))}
470
+ {selectedEntity.triples.length === 0 && (
471
+ <div className="text-xs text-slate-600">No relationships found</div>
472
+ )}
473
+ </div>
474
+
475
+ <div className="text-xs text-slate-400 mb-2">Linked Memories ({selectedEntity.memories.length})</div>
476
+ <div className="space-y-1.5">
477
+ {selectedEntity.memories.map(m => (
478
+ <div key={m.id} className="text-xs bg-slate-800/50 rounded p-2">
479
+ <div className="text-slate-200 mb-1">{m.title}</div>
480
+ <div className="flex items-center gap-2">
481
+ <span className="text-slate-500">{m.category}</span>
482
+ <span className="text-slate-600">{m.type.replace('_', '-')}</span>
483
+ <span className="text-slate-600 ml-auto">{new Date(m.created_at).toLocaleDateString()}</span>
484
+ </div>
485
+ </div>
486
+ ))}
487
+ {selectedEntity.memories.length === 0 && (
488
+ <div className="text-xs text-slate-600">No linked memories</div>
489
+ )}
490
+ </div>
491
+ </div>
492
+ ) : (
493
+ /* Stats panels */
494
+ <div className="p-4 space-y-6">
495
+ {/* Graph Stats */}
496
+ <div>
497
+ <h3 className="text-xs font-semibold text-slate-400 uppercase tracking-wider mb-3">Overview</h3>
498
+ <div className="grid grid-cols-2 gap-2">
499
+ <div className="bg-slate-800/50 rounded p-2.5">
500
+ <div className="text-lg font-bold text-white">{entities.length}</div>
501
+ <div className="text-xs text-slate-500">Entities</div>
502
+ </div>
503
+ <div className="bg-slate-800/50 rounded p-2.5">
504
+ <div className="text-lg font-bold text-white">{triples.length}</div>
505
+ <div className="text-xs text-slate-500">Triples</div>
506
+ </div>
507
+ <div className="bg-slate-800/50 rounded p-2.5">
508
+ <div className="text-lg font-bold text-white">{graphStats.typesPresent}</div>
509
+ <div className="text-xs text-slate-500">Types</div>
510
+ </div>
511
+ <div className="bg-slate-800/50 rounded p-2.5">
512
+ <div className="text-lg font-bold text-white">{graphStats.avgConnections}</div>
513
+ <div className="text-xs text-slate-500">Avg connections</div>
514
+ </div>
515
+ </div>
516
+ {graphStats.mostConnected && (
517
+ <div className="mt-2 text-xs text-slate-500">
518
+ Most connected: <span className="text-white">{graphStats.mostConnected.name}</span> ({graphStats.maxConn})
519
+ </div>
520
+ )}
521
+ </div>
522
+
523
+ {/* Entity Type Breakdown */}
524
+ <div>
525
+ <h3 className="text-xs font-semibold text-slate-400 uppercase tracking-wider mb-3">Entity Types</h3>
526
+ <div className="space-y-1.5">
527
+ {typeBreakdown.map(({ type, count }) => (
528
+ <button
529
+ key={type}
530
+ onClick={() => toggleType(type)}
531
+ className="w-full flex items-center gap-2 group"
532
+ >
533
+ <div
534
+ className="w-2.5 h-2.5 rounded-full shrink-0"
535
+ style={{ backgroundColor: ENTITY_COLORS[type] || DEFAULT_COLOR, opacity: activeTypes.has(type) ? 1 : 0.3 }}
536
+ />
537
+ <span className={`text-xs flex-1 text-left ${activeTypes.has(type) ? 'text-slate-300' : 'text-slate-600'}`}>
538
+ {type}
539
+ </span>
540
+ <div className="flex-1 h-1.5 bg-slate-800 rounded-full overflow-hidden">
541
+ <div
542
+ className="h-full rounded-full transition-all"
543
+ style={{
544
+ width: `${(count / maxTypeCount) * 100}%`,
545
+ backgroundColor: ENTITY_COLORS[type] || DEFAULT_COLOR,
546
+ opacity: activeTypes.has(type) ? 0.7 : 0.2,
547
+ }}
548
+ />
549
+ </div>
550
+ <span className={`text-xs tabular-nums w-6 text-right ${activeTypes.has(type) ? 'text-slate-400' : 'text-slate-600'}`}>
551
+ {count}
552
+ </span>
553
+ </button>
554
+ ))}
555
+ </div>
556
+ </div>
557
+
558
+ {/* Predicate Breakdown */}
559
+ <div>
560
+ <h3 className="text-xs font-semibold text-slate-400 uppercase tracking-wider mb-3">Relationships</h3>
561
+ {predicateBreakdown.length > 0 ? (
562
+ <div className="space-y-1.5">
563
+ {predicateBreakdown.map(({ predicate, count }) => (
564
+ <div key={predicate} className="flex items-center gap-2">
565
+ <div
566
+ className="w-2.5 h-2.5 rounded-full shrink-0"
567
+ style={{ backgroundColor: PREDICATE_COLORS[predicate] || DEFAULT_EDGE_COLOR }}
568
+ />
569
+ <span className="text-xs text-slate-300 flex-1">{predicate}</span>
570
+ <span className="text-xs text-slate-400 tabular-nums">{count}</span>
571
+ </div>
572
+ ))}
573
+ </div>
574
+ ) : (
575
+ <div className="text-xs text-slate-600">No relationships extracted yet</div>
576
+ )}
577
+ </div>
578
+
579
+ {/* Top Entities */}
580
+ <div>
581
+ <h3 className="text-xs font-semibold text-slate-400 uppercase tracking-wider mb-3">Top Entities</h3>
582
+ <div className="space-y-1">
583
+ {topEntities.map(entity => (
584
+ <button
585
+ key={entity.id}
586
+ onClick={() => handleEntityClick(entity)}
587
+ className="w-full flex items-center gap-2 py-1 px-1.5 rounded hover:bg-slate-800/50 transition-colors group"
588
+ >
589
+ <div
590
+ className="w-2 h-2 rounded-full shrink-0"
591
+ style={{ backgroundColor: ENTITY_COLORS[entity.type] || DEFAULT_COLOR }}
592
+ />
593
+ <span className="text-xs text-slate-300 flex-1 text-left truncate group-hover:text-white">
594
+ {entity.name}
595
+ </span>
596
+ <span className="text-xs text-slate-600 tabular-nums shrink-0">
597
+ {entity.memoryCount}
598
+ </span>
599
+ </button>
600
+ ))}
601
+ </div>
602
+ </div>
603
+ </div>
604
+ )}
605
+ </div>
606
+ </div>
607
+
608
+ {/* Bottom panel: entity tags */}
609
+ <div className="border-t border-slate-800 bg-slate-900/50 px-4 py-2.5 shrink-0">
610
+ <div className="flex items-center gap-2 overflow-x-auto scrollbar-hide" style={{ scrollbarWidth: 'none' }}>
611
+ <span className="text-xs text-slate-500 shrink-0">Known:</span>
612
+ {recentEntities.map(entity => (
613
+ <button
614
+ key={entity.id}
615
+ onClick={() => handleEntityClick(entity)}
616
+ className="shrink-0 flex items-center gap-1.5 px-2 py-1 rounded-full text-xs transition-colors hover:bg-slate-800"
617
+ style={{ color: ENTITY_COLORS[entity.type] || DEFAULT_COLOR }}
618
+ >
619
+ <div
620
+ className="w-1.5 h-1.5 rounded-full"
621
+ style={{ backgroundColor: ENTITY_COLORS[entity.type] || DEFAULT_COLOR }}
622
+ />
623
+ {entity.name}
624
+ <span className="text-slate-600">{entity.memoryCount}</span>
625
+ </button>
626
+ ))}
627
+ </div>
628
+ </div>
629
+ </div>
630
+ );
631
+ }