superlocalmemory 3.4.1 → 3.4.4

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 (47) hide show
  1. package/README.md +9 -12
  2. package/package.json +1 -1
  3. package/pyproject.toml +11 -2
  4. package/scripts/postinstall.js +26 -7
  5. package/src/superlocalmemory/cli/commands.py +71 -60
  6. package/src/superlocalmemory/cli/daemon.py +184 -64
  7. package/src/superlocalmemory/cli/main.py +25 -2
  8. package/src/superlocalmemory/cli/service_installer.py +367 -0
  9. package/src/superlocalmemory/cli/setup_wizard.py +150 -9
  10. package/src/superlocalmemory/core/config.py +28 -0
  11. package/src/superlocalmemory/core/consolidation_engine.py +38 -1
  12. package/src/superlocalmemory/core/engine.py +9 -0
  13. package/src/superlocalmemory/core/health_monitor.py +313 -0
  14. package/src/superlocalmemory/core/reranker_worker.py +19 -5
  15. package/src/superlocalmemory/ingestion/__init__.py +13 -0
  16. package/src/superlocalmemory/ingestion/adapter_manager.py +234 -0
  17. package/src/superlocalmemory/ingestion/base_adapter.py +177 -0
  18. package/src/superlocalmemory/ingestion/calendar_adapter.py +340 -0
  19. package/src/superlocalmemory/ingestion/credentials.py +118 -0
  20. package/src/superlocalmemory/ingestion/gmail_adapter.py +369 -0
  21. package/src/superlocalmemory/ingestion/parsers.py +100 -0
  22. package/src/superlocalmemory/ingestion/transcript_adapter.py +156 -0
  23. package/src/superlocalmemory/learning/consolidation_worker.py +47 -1
  24. package/src/superlocalmemory/learning/entity_compiler.py +377 -0
  25. package/src/superlocalmemory/mcp/server.py +32 -3
  26. package/src/superlocalmemory/mcp/tools_mesh.py +249 -0
  27. package/src/superlocalmemory/mesh/__init__.py +12 -0
  28. package/src/superlocalmemory/mesh/broker.py +344 -0
  29. package/src/superlocalmemory/retrieval/entity_channel.py +12 -6
  30. package/src/superlocalmemory/server/api.py +6 -7
  31. package/src/superlocalmemory/server/routes/adapters.py +63 -0
  32. package/src/superlocalmemory/server/routes/entity.py +151 -0
  33. package/src/superlocalmemory/server/routes/ingest.py +110 -0
  34. package/src/superlocalmemory/server/routes/mesh.py +186 -0
  35. package/src/superlocalmemory/server/unified_daemon.py +693 -0
  36. package/src/superlocalmemory/storage/schema_v343.py +229 -0
  37. package/src/superlocalmemory/ui/css/neural-glass.css +1588 -0
  38. package/src/superlocalmemory/ui/index.html +134 -4
  39. package/src/superlocalmemory/ui/js/memory-chat.js +28 -1
  40. package/src/superlocalmemory/ui/js/ng-entities.js +272 -0
  41. package/src/superlocalmemory/ui/js/ng-health.js +208 -0
  42. package/src/superlocalmemory/ui/js/ng-ingestion.js +203 -0
  43. package/src/superlocalmemory/ui/js/ng-mesh.js +311 -0
  44. package/src/superlocalmemory/ui/js/ng-shell.js +471 -0
  45. package/src/superlocalmemory.egg-info/PKG-INFO +18 -14
  46. package/src/superlocalmemory.egg-info/SOURCES.txt +26 -0
  47. package/src/superlocalmemory.egg-info/requires.txt +9 -1
@@ -9,6 +9,10 @@
9
9
  <link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/css/bootstrap.min.css" rel="stylesheet">
10
10
  <!-- Bootstrap Icons -->
11
11
  <link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bootstrap-icons@1.10.0/font/bootstrap-icons.css">
12
+ <!-- Inter Variable (Linear's signature typeface with cv01/ss03) — via jsdelivr (CSP-allowed) -->
13
+ <link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/inter-ui@4.1.0/inter-variable.min.css">
14
+ <!-- Neural Glass Design System v1.0 -->
15
+ <link rel="stylesheet" href="static/css/neural-glass.css?v=344b">
12
16
 
13
17
  <style>
14
18
  :root {
@@ -1643,6 +1647,115 @@
1643
1647
  </div>
1644
1648
  </div>
1645
1649
 
1650
+ <!-- Health Monitor (v3.4.3 — Neural Glass) -->
1651
+ <div class="tab-pane fade" id="health-pane">
1652
+ <div class="ng-content-header">
1653
+ <div>
1654
+ <div class="ng-content-title"><i class="bi bi-heart-pulse"></i> Health Monitor</div>
1655
+ <div class="ng-content-subtitle">Process health, RSS budget, worker heartbeat — real-time</div>
1656
+ </div>
1657
+ <button class="ng-btn" onclick="loadHealthMonitor()"><i class="bi bi-arrow-clockwise"></i> Refresh</button>
1658
+ </div>
1659
+ <div id="health-overview"></div>
1660
+ <div class="row g-4">
1661
+ <div class="col-lg-8">
1662
+ <div class="ng-glass" style="padding:20px">
1663
+ <h6 style="margin-bottom:16px"><i class="bi bi-cpu"></i> Worker Processes</h6>
1664
+ <div id="health-processes">
1665
+ <div class="text-center" style="padding:24px;color:var(--ng-text-tertiary)">
1666
+ <div class="spinner-border" style="color:var(--ng-accent)"></div>
1667
+ <div style="margin-top:8px">Loading processes...</div>
1668
+ </div>
1669
+ </div>
1670
+ </div>
1671
+ </div>
1672
+ <div class="col-lg-4">
1673
+ <div class="ng-glass" style="padding:20px">
1674
+ <h6 style="margin-bottom:16px"><i class="bi bi-sliders"></i> Budget Rules</h6>
1675
+ <div id="health-budget-rules">
1676
+ <div style="color:var(--ng-text-tertiary);font-size:0.8125rem">Loading...</div>
1677
+ </div>
1678
+ </div>
1679
+ </div>
1680
+ </div>
1681
+ </div>
1682
+
1683
+ <!-- Ingestion Status (v3.4.3 — Neural Glass) -->
1684
+ <div class="tab-pane fade" id="ingestion-pane">
1685
+ <div class="ng-content-header">
1686
+ <div>
1687
+ <div class="ng-content-title"><i class="bi bi-cloud-download"></i> Ingestion Status</div>
1688
+ <div class="ng-content-subtitle">Gmail, Calendar, Transcript adapters — all opt-in</div>
1689
+ </div>
1690
+ <button class="ng-btn" onclick="loadIngestionStatus()"><i class="bi bi-arrow-clockwise"></i> Refresh</button>
1691
+ </div>
1692
+ <div id="ingestion-overview"></div>
1693
+ <div id="ingestion-adapters" style="margin-bottom:24px"></div>
1694
+ <div class="ng-glass" style="padding:20px">
1695
+ <h6 style="margin-bottom:16px"><i class="bi bi-journal-text"></i> Recent Ingestion Log</h6>
1696
+ <div id="ingestion-log">
1697
+ <div style="padding:16px;color:var(--ng-text-tertiary);text-align:center;font-size:0.8125rem">Loading...</div>
1698
+ </div>
1699
+ </div>
1700
+ </div>
1701
+
1702
+ <!-- Entity Explorer (v3.4.3 — Neural Glass) -->
1703
+ <div class="tab-pane fade" id="entities-pane">
1704
+ <div class="ng-content-header">
1705
+ <div>
1706
+ <div class="ng-content-title"><i class="bi bi-person-badge"></i> Entity Explorer</div>
1707
+ <div class="ng-content-subtitle">Compiled truth per entity — PageRank + Louvain communities</div>
1708
+ </div>
1709
+ <button class="ng-btn" onclick="loadEntityExplorer()"><i class="bi bi-arrow-clockwise"></i> Refresh</button>
1710
+ </div>
1711
+ <div id="entities-list">
1712
+ <div class="text-center" style="padding:24px;color:var(--ng-text-tertiary)">
1713
+ <div class="spinner-border" style="color:var(--ng-accent)"></div>
1714
+ <div style="margin-top:8px">Loading entities...</div>
1715
+ </div>
1716
+ </div>
1717
+ <div id="entity-detail-panel" style="display:none;margin-top:24px"></div>
1718
+ </div>
1719
+
1720
+ <!-- Mesh Peers (v3.4.3 — Neural Glass) -->
1721
+ <div class="tab-pane fade" id="mesh-pane">
1722
+ <div class="ng-content-header">
1723
+ <div>
1724
+ <div class="ng-content-title"><i class="bi bi-share"></i> Mesh Peers</div>
1725
+ <div class="ng-content-subtitle">P2P agent communication network — real-time status</div>
1726
+ </div>
1727
+ <button class="ng-btn" onclick="loadMeshPeers()"><i class="bi bi-arrow-clockwise"></i> Refresh</button>
1728
+ </div>
1729
+ <div id="mesh-status-cards"></div>
1730
+ <div style="margin-top:24px">
1731
+ <h6 style="margin-bottom:16px"><i class="bi bi-people"></i> Connected Peers</h6>
1732
+ <div id="mesh-peers-list">
1733
+ <div class="text-center" style="padding:24px;color:var(--ng-text-tertiary)">
1734
+ <div class="spinner-border" style="color:var(--ng-accent)"></div>
1735
+ <div style="margin-top:8px">Connecting to mesh broker...</div>
1736
+ </div>
1737
+ </div>
1738
+ </div>
1739
+ <div class="row g-4" style="margin-top:16px">
1740
+ <div class="col-lg-6">
1741
+ <div class="ng-glass" style="padding:20px">
1742
+ <h6 style="margin-bottom:12px"><i class="bi bi-broadcast"></i> Recent Events</h6>
1743
+ <div id="mesh-events-list">
1744
+ <div style="padding:16px;color:var(--ng-text-tertiary);text-align:center;font-size:0.8125rem">Loading...</div>
1745
+ </div>
1746
+ </div>
1747
+ </div>
1748
+ <div class="col-lg-6">
1749
+ <div class="ng-glass" style="padding:20px">
1750
+ <h6 style="margin-bottom:12px"><i class="bi bi-database"></i> Shared State</h6>
1751
+ <div id="mesh-state-list">
1752
+ <div style="padding:16px;color:var(--ng-text-tertiary);text-align:center;font-size:0.8125rem">Loading...</div>
1753
+ </div>
1754
+ </div>
1755
+ </div>
1756
+ </div>
1757
+ </div>
1758
+
1646
1759
  <!-- Math Health (V3) -->
1647
1760
  <div class="tab-pane fade" id="math-health-pane">
1648
1761
  <h5><i class="bi bi-calculator"></i> Mathematical Layer Health</h5>
@@ -1951,7 +2064,7 @@
1951
2064
  <!-- Filter functions (filterByCluster, filterByEntity — used by clusters.js, modal.js) -->
1952
2065
  <script src="static/js/graph-filters.js"></script>
1953
2066
  <!-- v3.4.1: Ask My Memory chat interface -->
1954
- <script src="static/js/memory-chat.js"></script>
2067
+ <script src="static/js/memory-chat.js?v=344b"></script>
1955
2068
  <!-- v3.4.1: Quick Insight Actions (5 one-click intelligence buttons) -->
1956
2069
  <script src="static/js/quick-actions.js"></script>
1957
2070
  <!-- v3.4.1: Memory Timeline (D3 time axis, zoom, trust colors) -->
@@ -1981,9 +2094,26 @@
1981
2094
  <script src="static/js/ide-status.js"></script>
1982
2095
  <script src="static/js/fact-detail.js"></script>
1983
2096
 
1984
- <footer>
1985
- <p>SuperLocalMemory V3 by <a href="https://github.com/varun369">Varun Pratap Bhardwaj</a> · <a href="https://qualixar.com">Qualixar</a></p>
1986
- <p><a href="https://github.com/qualixar/superlocalmemory">GitHub</a> · AGPL-3.0-or-later · <a href="https://github.com/qualixar/superlocalmemory/stargazers">⭐ Star us on GitHub</a></p>
2097
+ <!-- Neural Glass v3.4.4 — Dashboard V2 -->
2098
+ <script src="static/js/ng-health.js?v=344b"></script>
2099
+ <script src="static/js/ng-ingestion.js?v=344b"></script>
2100
+ <script src="static/js/ng-entities.js?v=344b"></script>
2101
+ <script src="static/js/ng-mesh.js?v=344b"></script>
2102
+ <script src="static/js/ng-shell.js?v=344b"></script>
2103
+
2104
+ <footer style="text-align:center; padding:24px; border-top:1px solid var(--bs-border-color); font-size:0.8125rem;">
2105
+ <div style="display:flex; align-items:center; justify-content:center; gap:8px; margin-bottom:8px;">
2106
+ <span style="display:inline-flex; align-items:center; justify-content:center; width:24px; height:24px; border-radius:6px; background:linear-gradient(135deg, #667eea 0%, #764ba2 100%); color:#fff; font-size:0.7rem;">&#x2B26;</span>
2107
+ <strong>SuperLocalMemory</strong> by <a href="https://qualixar.com" style="font-weight:600;">Qualixar</a>
2108
+ </div>
2109
+ <p style="margin:4px 0;">Built by <a href="https://github.com/varun369">Varun Pratap Bhardwaj</a> · <a href="https://qualixar.com">qualixar.com</a></p>
2110
+ <p style="margin:4px 0;">
2111
+ <a href="https://github.com/qualixar/superlocalmemory"><i class="bi bi-github"></i> GitHub</a> ·
2112
+ <a href="https://www.npmjs.com/package/superlocalmemory"><i class="bi bi-box-seam"></i> npm</a> ·
2113
+ <a href="https://pypi.org/project/superlocalmemory/"><i class="bi bi-box"></i> PyPI</a> ·
2114
+ AGPL-3.0-or-later ·
2115
+ <a href="https://github.com/qualixar/superlocalmemory/stargazers">⭐ Star us on GitHub</a>
2116
+ </p>
1987
2117
  </footer>
1988
2118
  </body>
1989
2119
  </html>
@@ -332,7 +332,10 @@ function _loadChatMode() {
332
332
  // ============================================================================
333
333
 
334
334
  document.addEventListener('DOMContentLoaded', function() {
335
- // Delay init until graph tab is shown (panel must exist in DOM)
335
+ // v3.4.4: Robust init works with both Bootstrap tabs AND Neural Glass sidebar.
336
+ // Strategy: poll for graph-pane visibility instead of relying on tab events.
337
+
338
+ // Method 1: Bootstrap tab event (legacy compat)
336
339
  var graphTab = document.getElementById('graph-tab');
337
340
  if (graphTab) {
338
341
  graphTab.addEventListener('shown.bs.tab', function() {
@@ -341,4 +344,28 @@ document.addEventListener('DOMContentLoaded', function() {
341
344
  }
342
345
  });
343
346
  }
347
+
348
+ // Method 2: MutationObserver on graph-pane class changes (Neural Glass sidebar)
349
+ var graphPane = document.getElementById('graph-pane');
350
+ if (graphPane) {
351
+ var observer = new MutationObserver(function(mutations) {
352
+ mutations.forEach(function(m) {
353
+ if (m.attributeName === 'class' && graphPane.classList.contains('active')) {
354
+ if (!document.getElementById('chat-panel')) {
355
+ initMemoryChat();
356
+ }
357
+ }
358
+ });
359
+ });
360
+ observer.observe(graphPane, { attributes: true, attributeFilter: ['class'] });
361
+
362
+ // Method 3: If graph-pane is ALREADY active on page load (e.g. hash navigation)
363
+ if (graphPane.classList.contains('active')) {
364
+ setTimeout(function() {
365
+ if (!document.getElementById('chat-panel')) {
366
+ initMemoryChat();
367
+ }
368
+ }, 500);
369
+ }
370
+ }
344
371
  });
@@ -0,0 +1,272 @@
1
+ // Neural Glass — Entity Explorer Tab
2
+ // Browse entities, their knowledge summaries, and trigger recompilation (v3.4.3)
3
+ // API: /api/entity/list, /api/entity/{name}, /api/entity/{name}/recompile
4
+
5
+ (function() {
6
+ 'use strict';
7
+
8
+ var allEntities = [];
9
+ var currentPage = 0;
10
+ var PAGE_SIZE = 50;
11
+
12
+ window.loadEntityExplorer = function() {
13
+ fetchEntityList();
14
+ };
15
+
16
+ function fetchEntityList(offset) {
17
+ offset = offset || 0;
18
+ fetch('/api/entity/list?limit=' + PAGE_SIZE + '&offset=' + offset)
19
+ .then(function(r) { return r.json(); })
20
+ .then(function(data) {
21
+ allEntities = data.entities || [];
22
+ renderEntityList(allEntities, data.total || 0, offset);
23
+ })
24
+ .catch(function() {
25
+ renderEntityList([], 0, 0);
26
+ });
27
+ }
28
+
29
+ function renderEntityList(entities, total, offset) {
30
+ var el = document.getElementById('entities-list');
31
+ if (!el) return;
32
+
33
+ if (total === 0) {
34
+ el.innerHTML =
35
+ '<div class="text-center" style="padding:40px;color:var(--ng-text-tertiary)">' +
36
+ '<i class="bi bi-person-badge" style="font-size:3rem;display:block;margin-bottom:12px;opacity:0.3"></i>' +
37
+ '<div style="font-size:1rem;margin-bottom:4px">No entities found</div>' +
38
+ '<div style="font-size:0.8125rem">Entity extraction runs during memory consolidation (every 6 hours).<br>' +
39
+ 'Store more memories to build your entity graph.</div>' +
40
+ '</div>';
41
+ return;
42
+ }
43
+
44
+ // Search filter
45
+ var html =
46
+ '<div style="margin-bottom:16px">' +
47
+ '<input type="text" class="form-control" id="entity-search-input" placeholder="Search ' + total + ' entities..." ' +
48
+ 'oninput="filterEntities(this.value)" style="max-width:500px">' +
49
+ '</div>';
50
+
51
+ // Stats summary
52
+ html +=
53
+ '<div class="row g-3 mb-4">' +
54
+ statCard('Total Entities', total, 'bi-people') +
55
+ statCard('Showing', entities.length + ' of ' + total, 'bi-eye') +
56
+ statCard('Top Type', getTopType(entities), 'bi-tag') +
57
+ statCard('Avg Facts', getAvgFacts(entities), 'bi-collection') +
58
+ '</div>';
59
+
60
+ // Entity grid
61
+ html += '<div class="row g-3" id="entity-grid">';
62
+ entities.forEach(function(e) {
63
+ html += renderEntityCard(e);
64
+ });
65
+ html += '</div>';
66
+
67
+ // Pagination
68
+ if (total > PAGE_SIZE) {
69
+ var pages = Math.ceil(total / PAGE_SIZE);
70
+ var currentP = Math.floor(offset / PAGE_SIZE);
71
+ html += '<div style="display:flex;justify-content:center;gap:8px;margin-top:24px">';
72
+ for (var i = 0; i < Math.min(pages, 10); i++) {
73
+ var isActive = i === currentP;
74
+ html += '<button class="ng-btn' + (isActive ? ' ng-btn-accent' : '') + '" ' +
75
+ 'onclick="navigateEntityPage(' + i + ')" style="min-width:36px">' + (i + 1) + '</button>';
76
+ }
77
+ if (pages > 10) html += '<span style="padding:8px;color:var(--ng-text-tertiary)">...</span>';
78
+ html += '</div>';
79
+ }
80
+
81
+ el.innerHTML = html;
82
+ }
83
+
84
+ function renderEntityCard(entity) {
85
+ var typeColor = getTypeColor(entity.type);
86
+ var summaryText = entity.summary_preview || 'No summary yet — click to view details';
87
+ var hasTruth = entity.has_compiled_truth;
88
+
89
+ return '<div class="col-md-6 col-lg-4">' +
90
+ '<div class="ng-glass" style="padding:16px;cursor:pointer;transition:border-color 0.2s" ' +
91
+ 'onclick="showEntityDetail(\'' + escapeAttr(entity.name) + '\')" ' +
92
+ 'onmouseover="this.style.borderColor=\'var(--ng-border-prominent)\'" ' +
93
+ 'onmouseout="this.style.borderColor=\'var(--ng-border-subtle)\'">' +
94
+ '<div style="display:flex;justify-content:space-between;align-items:flex-start;margin-bottom:8px">' +
95
+ '<div style="font-weight:590;font-size:0.9375rem;color:var(--ng-text-primary)">' + escapeHtml(entity.name) + '</div>' +
96
+ '<span class="ng-badge ng-badge-accent">' + escapeHtml(entity.type) + '</span>' +
97
+ '</div>' +
98
+ '<div style="font-size:0.8125rem;color:var(--ng-text-secondary);margin-bottom:8px;' +
99
+ 'display:-webkit-box;-webkit-line-clamp:2;-webkit-box-orient:vertical;overflow:hidden">' +
100
+ escapeHtml(summaryText) +
101
+ '</div>' +
102
+ '<div style="display:flex;justify-content:space-between;align-items:center;font-size:0.75rem;color:var(--ng-text-tertiary)">' +
103
+ '<span>' + entity.fact_count + ' facts</span>' +
104
+ '<span>' + (entity.last_seen ? timeAgo(entity.last_seen) : '') + '</span>' +
105
+ '</div>' +
106
+ '</div>' +
107
+ '</div>';
108
+ }
109
+
110
+ // Show entity detail panel
111
+ window.showEntityDetail = function(entityName) {
112
+ var panel = document.getElementById('entity-detail-panel');
113
+ if (!panel) return;
114
+
115
+ panel.style.display = 'block';
116
+ panel.innerHTML =
117
+ '<div class="ng-glass-elevated" style="padding:24px">' +
118
+ '<div style="display:flex;justify-content:space-between;align-items:center;margin-bottom:16px">' +
119
+ '<h5 style="margin:0"><i class="bi bi-person-badge"></i> ' + escapeHtml(entityName) + '</h5>' +
120
+ '<div style="display:flex;gap:8px">' +
121
+ '<button class="ng-btn" onclick="recompileEntity(\'' + escapeAttr(entityName) + '\')">' +
122
+ '<i class="bi bi-arrow-repeat"></i> Recompile</button>' +
123
+ '<button class="ng-btn" onclick="document.getElementById(\'entity-detail-panel\').style.display=\'none\'">' +
124
+ '<i class="bi bi-x-lg"></i></button>' +
125
+ '</div>' +
126
+ '</div>' +
127
+ '<div id="entity-detail-content"><div class="text-center" style="padding:16px"><div class="spinner-border"></div></div></div>' +
128
+ '</div>';
129
+
130
+ // Fetch detail
131
+ fetch('/api/entity/' + encodeURIComponent(entityName))
132
+ .then(function(r) { return r.json(); })
133
+ .then(function(data) {
134
+ var content = document.getElementById('entity-detail-content');
135
+ if (!content) return;
136
+
137
+ var html = '';
138
+
139
+ // Entity type and confidence
140
+ html += '<div style="margin-bottom:16px">' +
141
+ '<span class="ng-badge ng-badge-accent">' + escapeHtml(data.entity_type || 'unknown') + '</span>' +
142
+ ' <span class="ng-badge ng-badge-neutral">Confidence: ' + ((data.confidence || 0.5) * 100).toFixed(0) + '%</span>' +
143
+ (data.last_compiled_at ? ' <span class="ng-badge ng-badge-neutral">Compiled: ' + timeAgo(data.last_compiled_at) + '</span>' : '') +
144
+ '</div>';
145
+
146
+ // Knowledge summary
147
+ if (data.knowledge_summary) {
148
+ html += '<div style="margin-bottom:16px">' +
149
+ '<h6 style="font-size:0.8125rem;text-transform:uppercase;letter-spacing:0.06em;color:var(--ng-text-tertiary);margin-bottom:8px">Knowledge Summary</h6>' +
150
+ '<div style="font-size:0.875rem;color:var(--ng-text-secondary);line-height:1.6;white-space:pre-wrap;background:var(--ng-bg-glass);padding:12px;border-radius:var(--ng-radius-md)">' +
151
+ escapeHtml(data.knowledge_summary) +
152
+ '</div>' +
153
+ '</div>';
154
+ }
155
+
156
+ // Compiled truth
157
+ if (data.compiled_truth) {
158
+ html += '<div style="margin-bottom:16px">' +
159
+ '<h6 style="font-size:0.8125rem;text-transform:uppercase;letter-spacing:0.06em;color:var(--ng-text-tertiary);margin-bottom:8px">Compiled Truth</h6>' +
160
+ '<div style="font-size:0.875rem;color:var(--ng-text-primary);line-height:1.6;background:var(--ng-bg-glass);padding:12px;border-radius:var(--ng-radius-md)">' +
161
+ escapeHtml(data.compiled_truth) +
162
+ '</div>' +
163
+ '</div>';
164
+ }
165
+
166
+ // Source facts
167
+ if (data.source_fact_ids && data.source_fact_ids.length > 0) {
168
+ html += '<div>' +
169
+ '<h6 style="font-size:0.8125rem;text-transform:uppercase;letter-spacing:0.06em;color:var(--ng-text-tertiary);margin-bottom:8px">' +
170
+ 'Source Facts (' + data.source_fact_ids.length + ')</h6>' +
171
+ '<div style="font-size:0.75rem;color:var(--ng-text-quaternary)">' +
172
+ data.source_fact_ids.slice(0, 10).map(function(id) { return '<code>' + id.substring(0, 12) + '</code>'; }).join(' ') +
173
+ (data.source_fact_ids.length > 10 ? ' + ' + (data.source_fact_ids.length - 10) + ' more' : '') +
174
+ '</div>' +
175
+ '</div>';
176
+ }
177
+
178
+ content.innerHTML = html;
179
+ })
180
+ .catch(function(err) {
181
+ var content = document.getElementById('entity-detail-content');
182
+ if (content) {
183
+ content.innerHTML = '<div style="color:var(--ng-text-tertiary);padding:16px">Could not load entity details: ' + err.message + '</div>';
184
+ }
185
+ });
186
+
187
+ // Scroll to detail panel
188
+ panel.scrollIntoView({ behavior: 'smooth', block: 'start' });
189
+ };
190
+
191
+ // Recompile entity
192
+ window.recompileEntity = function(entityName) {
193
+ fetch('/api/entity/' + encodeURIComponent(entityName) + '/recompile', { method: 'POST' })
194
+ .then(function(r) { return r.json(); })
195
+ .then(function(data) {
196
+ if (data.ok) {
197
+ alert('Entity "' + entityName + '" recompiled successfully.');
198
+ window.showEntityDetail(entityName); // Refresh detail view
199
+ } else {
200
+ alert('Recompilation failed: ' + (data.reason || 'unknown'));
201
+ }
202
+ })
203
+ .catch(function(err) {
204
+ alert('Error: ' + err.message);
205
+ });
206
+ };
207
+
208
+ // Filter entities by search text
209
+ window.filterEntities = function(query) {
210
+ if (!query) {
211
+ fetchEntityList();
212
+ return;
213
+ }
214
+ var q = query.toLowerCase();
215
+ var filtered = allEntities.filter(function(e) {
216
+ return e.name.toLowerCase().indexOf(q) >= 0 ||
217
+ (e.type && e.type.toLowerCase().indexOf(q) >= 0) ||
218
+ (e.summary_preview && e.summary_preview.toLowerCase().indexOf(q) >= 0);
219
+ });
220
+ var grid = document.getElementById('entity-grid');
221
+ if (!grid) return;
222
+ grid.innerHTML = filtered.length > 0
223
+ ? filtered.map(renderEntityCard).join('')
224
+ : '<div class="col-12" style="text-align:center;padding:24px;color:var(--ng-text-tertiary)">No entities match "' + escapeHtml(query) + '"</div>';
225
+ };
226
+
227
+ // Pagination
228
+ window.navigateEntityPage = function(page) {
229
+ currentPage = page;
230
+ fetchEntityList(page * PAGE_SIZE);
231
+ };
232
+
233
+ // Helpers
234
+ function statCard(label, value, icon) {
235
+ return '<div class="col-md-3 col-6"><div class="ng-glass" style="padding:12px;text-align:center">' +
236
+ '<i class="bi ' + icon + '" style="color:var(--ng-accent);font-size:1.125rem;display:block;margin-bottom:4px"></i>' +
237
+ '<div style="font-size:1.25rem;font-weight:590">' + value + '</div>' +
238
+ '<div class="ng-stat-label">' + label + '</div>' +
239
+ '</div></div>';
240
+ }
241
+
242
+ function getTopType(entities) {
243
+ var counts = {};
244
+ entities.forEach(function(e) { counts[e.type] = (counts[e.type] || 0) + 1; });
245
+ var top = Object.entries(counts).sort(function(a, b) { return b[1] - a[1]; })[0];
246
+ return top ? top[0] : 'N/A';
247
+ }
248
+
249
+ function getAvgFacts(entities) {
250
+ if (entities.length === 0) return '0';
251
+ var sum = entities.reduce(function(s, e) { return s + (e.fact_count || 0); }, 0);
252
+ return (sum / entities.length).toFixed(1);
253
+ }
254
+
255
+ function getTypeColor(type) {
256
+ var colors = { person: '#3b82f6', concept: '#10b981', organization: '#f59e0b', location: '#ef4444' };
257
+ return colors[type] || '#7C6AEF';
258
+ }
259
+
260
+ function escapeHtml(s) { var d = document.createElement('div'); d.textContent = s || ''; return d.innerHTML; }
261
+ function escapeAttr(s) { return (s || '').replace(/'/g, "\\'").replace(/"/g, '&quot;'); }
262
+
263
+ function timeAgo(iso) {
264
+ if (!iso) return '';
265
+ var d = (Date.now() - new Date(iso).getTime()) / 1000;
266
+ if (d < 0) d = 0;
267
+ if (d < 60) return Math.floor(d) + 's ago';
268
+ if (d < 3600) return Math.floor(d / 60) + 'm ago';
269
+ if (d < 86400) return Math.floor(d / 3600) + 'h ago';
270
+ return Math.floor(d / 86400) + 'd ago';
271
+ }
272
+ })();