superlocalmemory 3.4.16 → 3.4.18

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 (83) hide show
  1. package/CHANGELOG.md +20 -0
  2. package/package.json +1 -3
  3. package/pyproject.toml +10 -1
  4. package/src/superlocalmemory/cli/setup_wizard.py +30 -0
  5. package/src/superlocalmemory/server/routes/entity.py +5 -9
  6. package/src/superlocalmemory/server/routes/helpers.py +120 -15
  7. package/src/superlocalmemory/server/routes/ingest.py +2 -3
  8. package/src/superlocalmemory/server/routes/v3_api.py +42 -2
  9. package/src/superlocalmemory/server/unified_daemon.py +21 -11
  10. package/src/superlocalmemory.egg-info/PKG-INFO +5 -2
  11. package/src/superlocalmemory.egg-info/requires.txt +3 -0
  12. package/docs/ARCHITECTURE.md +0 -149
  13. package/docs/api-reference.md +0 -284
  14. package/docs/auto-memory.md +0 -150
  15. package/docs/cli-reference.md +0 -327
  16. package/docs/cloud-backup.md +0 -174
  17. package/docs/compliance.md +0 -191
  18. package/docs/configuration.md +0 -182
  19. package/docs/getting-started.md +0 -102
  20. package/docs/ide-setup.md +0 -261
  21. package/docs/mcp-tools.md +0 -220
  22. package/docs/migration-from-v2.md +0 -170
  23. package/docs/profiles.md +0 -173
  24. package/docs/screenshots/01-dashboard-main.png +0 -0
  25. package/docs/screenshots/02-knowledge-graph.png +0 -0
  26. package/docs/screenshots/03-math-health.png +0 -0
  27. package/docs/screenshots/03-patterns-learning.png +0 -0
  28. package/docs/screenshots/04-learning-dashboard.png +0 -0
  29. package/docs/screenshots/04-recall-lab.png +0 -0
  30. package/docs/screenshots/05-behavioral-analysis.png +0 -0
  31. package/docs/screenshots/05-trust-dashboard.png +0 -0
  32. package/docs/screenshots/06-graph-communities.png +0 -0
  33. package/docs/screenshots/06-settings.png +0 -0
  34. package/docs/screenshots/07-memories-blurred.png +0 -0
  35. package/docs/skill-evolution.md +0 -256
  36. package/docs/troubleshooting.md +0 -310
  37. package/docs/v2-archive/ACCESSIBILITY.md +0 -291
  38. package/docs/v2-archive/ARCHITECTURE.md +0 -886
  39. package/docs/v2-archive/CLI-COMMANDS-REFERENCE.md +0 -425
  40. package/docs/v2-archive/COMPRESSION-README.md +0 -390
  41. package/docs/v2-archive/FRAMEWORK-INTEGRATIONS.md +0 -300
  42. package/docs/v2-archive/MCP-MANUAL-SETUP.md +0 -775
  43. package/docs/v2-archive/MCP-TROUBLESHOOTING.md +0 -787
  44. package/docs/v2-archive/PATTERN-LEARNING.md +0 -228
  45. package/docs/v2-archive/PROFILES-GUIDE.md +0 -453
  46. package/docs/v2-archive/RESET-GUIDE.md +0 -353
  47. package/docs/v2-archive/SEARCH-ENGINE-V2.2.0.md +0 -749
  48. package/docs/v2-archive/SEARCH-INTEGRATION-GUIDE.md +0 -502
  49. package/docs/v2-archive/UI-SERVER.md +0 -262
  50. package/docs/v2-archive/UNIVERSAL-INTEGRATION.md +0 -488
  51. package/docs/v2-archive/V2.2.0-OPTIONAL-SEARCH.md +0 -666
  52. package/docs/v2-archive/WINDOWS-INSTALL-README.txt +0 -34
  53. package/docs/v2-archive/WINDOWS-POST-INSTALL.txt +0 -45
  54. package/docs/v2-archive/example_graph_usage.py +0 -146
  55. package/ui/index.html +0 -1879
  56. package/ui/js/agents.js +0 -192
  57. package/ui/js/auto-settings.js +0 -399
  58. package/ui/js/behavioral.js +0 -276
  59. package/ui/js/clusters.js +0 -206
  60. package/ui/js/compliance.js +0 -252
  61. package/ui/js/core.js +0 -246
  62. package/ui/js/dashboard.js +0 -110
  63. package/ui/js/events.js +0 -178
  64. package/ui/js/fact-detail.js +0 -92
  65. package/ui/js/feedback.js +0 -333
  66. package/ui/js/graph-core.js +0 -447
  67. package/ui/js/graph-filters.js +0 -220
  68. package/ui/js/graph-interactions.js +0 -351
  69. package/ui/js/graph-ui.js +0 -214
  70. package/ui/js/ide-status.js +0 -102
  71. package/ui/js/init.js +0 -45
  72. package/ui/js/learning.js +0 -435
  73. package/ui/js/lifecycle.js +0 -298
  74. package/ui/js/math-health.js +0 -98
  75. package/ui/js/memories.js +0 -264
  76. package/ui/js/modal.js +0 -357
  77. package/ui/js/patterns.js +0 -93
  78. package/ui/js/profiles.js +0 -236
  79. package/ui/js/recall-lab.js +0 -292
  80. package/ui/js/search.js +0 -59
  81. package/ui/js/settings.js +0 -224
  82. package/ui/js/timeline.js +0 -32
  83. package/ui/js/trust-dashboard.js +0 -73
@@ -1,298 +0,0 @@
1
- // SPDX-License-Identifier: Elastic-2.0
2
- // Copyright (c) 2026 SuperLocalMemory (superlocalmemory.com)
3
- // Lifecycle tab — state distribution, compaction, transitions (v2.8)
4
- // NOTE: All dynamic values pass through escapeHtml() or textContent for DOM insertion.
5
-
6
- var _lifecycleData = null;
7
-
8
- async function loadLifecycle() {
9
- try {
10
- var response = await fetch('/api/lifecycle/status');
11
- var data = await response.json();
12
- _lifecycleData = data;
13
-
14
- if (!data.available) {
15
- showEmpty('lifecycle-states-row', 'hourglass-split', 'Lifecycle engine not available. Upgrade to v2.8.');
16
- return;
17
- }
18
-
19
- renderLifecycleStates(data);
20
- renderLifecycleProgress(data);
21
- renderLifecycleAgeStats(data);
22
- renderLifecycleTransitions(data);
23
-
24
- var badge = document.getElementById('lifecycle-profile-badge');
25
- if (badge) badge.textContent = data.active_profile || 'default';
26
- } catch (error) {
27
- console.error('Error loading lifecycle:', error);
28
- }
29
- }
30
-
31
- function renderLifecycleStates(data) {
32
- var states = data.states || {};
33
- var mapping = {
34
- active: 'lc-active-count',
35
- warm: 'lc-warm-count',
36
- cold: 'lc-cold-count',
37
- archived: 'lc-archived-count',
38
- tombstoned: 'lc-tombstoned-count'
39
- };
40
- for (var state in mapping) {
41
- animateCounter(mapping[state], states[state] || 0);
42
- }
43
- animateCounter('lc-total-count', data.total_memories || 0);
44
- }
45
-
46
- function renderLifecycleProgress(data) {
47
- var bar = document.getElementById('lifecycle-progress-bar');
48
- if (!bar) return;
49
- var states = data.states || {};
50
- var total = data.total_memories || 1;
51
- var colors = {
52
- active: '#198754',
53
- warm: '#ffc107',
54
- cold: '#0dcaf0',
55
- archived: '#6c757d',
56
- tombstoned: '#dc3545'
57
- };
58
-
59
- bar.textContent = '';
60
- var hasSegments = false;
61
-
62
- var stateKeys = ['active', 'warm', 'cold', 'archived', 'tombstoned'];
63
- for (var i = 0; i < stateKeys.length; i++) {
64
- var state = stateKeys[i];
65
- var count = states[state] || 0;
66
- if (count > 0) {
67
- hasSegments = true;
68
- var pct = ((count / total) * 100).toFixed(1);
69
- var segment = document.createElement('div');
70
- segment.className = 'progress-bar';
71
- segment.setAttribute('role', 'progressbar');
72
- segment.style.width = pct + '%';
73
- segment.style.backgroundColor = colors[state];
74
- segment.title = state + ': ' + count + ' (' + pct + '%)';
75
- segment.textContent = state;
76
- bar.appendChild(segment);
77
- }
78
- }
79
-
80
- if (!hasSegments) {
81
- var fallback = document.createElement('div');
82
- fallback.className = 'progress-bar bg-success';
83
- fallback.style.width = '100%';
84
- fallback.textContent = 'All Active';
85
- bar.appendChild(fallback);
86
- }
87
- }
88
-
89
- function renderLifecycleAgeStats(data) {
90
- var container = document.getElementById('lifecycle-age-content');
91
- if (!container) return;
92
- var stats = data.age_stats || {};
93
- if (Object.keys(stats).length === 0) {
94
- container.textContent = '';
95
- var empty = document.createElement('span');
96
- empty.className = 'text-muted';
97
- empty.textContent = 'No age data available yet.';
98
- container.appendChild(empty);
99
- return;
100
- }
101
-
102
- var table = document.createElement('table');
103
- table.className = 'table table-sm table-hover mb-0';
104
- var thead = document.createElement('thead');
105
- var headRow = document.createElement('tr');
106
- ['State', 'Avg Age', 'Newest', 'Oldest'].forEach(function(h) {
107
- var th = document.createElement('th');
108
- th.textContent = h;
109
- headRow.appendChild(th);
110
- });
111
- thead.appendChild(headRow);
112
- table.appendChild(thead);
113
-
114
- var tbody = document.createElement('tbody');
115
- var stateOrder = ['active', 'warm', 'cold', 'archived'];
116
- var badgeColors = { active: 'success', warm: 'warning', cold: 'info', archived: 'secondary' };
117
-
118
- for (var i = 0; i < stateOrder.length; i++) {
119
- var s = stateOrder[i];
120
- if (stats[s]) {
121
- var row = document.createElement('tr');
122
-
123
- var stateCell = document.createElement('td');
124
- var stateBadge = document.createElement('span');
125
- stateBadge.className = 'badge bg-' + (badgeColors[s] || 'secondary');
126
- stateBadge.textContent = s;
127
- stateCell.appendChild(stateBadge);
128
- row.appendChild(stateCell);
129
-
130
- var avgCell = document.createElement('td');
131
- avgCell.textContent = (stats[s].avg_days || 0) + 'd';
132
- row.appendChild(avgCell);
133
-
134
- var minCell = document.createElement('td');
135
- minCell.textContent = (stats[s].min_days || 0) + 'd';
136
- row.appendChild(minCell);
137
-
138
- var maxCell = document.createElement('td');
139
- maxCell.textContent = (stats[s].max_days || 0) + 'd';
140
- row.appendChild(maxCell);
141
-
142
- tbody.appendChild(row);
143
- }
144
- }
145
- table.appendChild(tbody);
146
-
147
- container.textContent = '';
148
- container.appendChild(table);
149
- }
150
-
151
- function renderLifecycleTransitions(data) {
152
- var container = document.getElementById('lifecycle-transitions-content');
153
- if (!container) return;
154
- var transitions = data.recent_transitions || [];
155
- if (transitions.length === 0) {
156
- container.textContent = '';
157
- var empty = document.createElement('span');
158
- empty.className = 'text-muted';
159
- empty.textContent = 'No transitions yet. Memories start as Active and transition based on usage.';
160
- container.appendChild(empty);
161
- return;
162
- }
163
-
164
- var table = document.createElement('table');
165
- table.className = 'table table-sm table-hover mb-0';
166
- var thead = document.createElement('thead');
167
- var headRow = document.createElement('tr');
168
- ['Memory', 'State', 'Last Transition'].forEach(function(h) {
169
- var th = document.createElement('th');
170
- th.textContent = h;
171
- headRow.appendChild(th);
172
- });
173
- thead.appendChild(headRow);
174
- table.appendChild(thead);
175
-
176
- var tbody = document.createElement('tbody');
177
- for (var i = 0; i < transitions.length; i++) {
178
- var t = transitions[i];
179
- var row = document.createElement('tr');
180
-
181
- var memCell = document.createElement('td');
182
- var preview = (t.content_preview || '').substring(0, 40);
183
- memCell.textContent = '#' + t.memory_id + ' ' + preview + (preview.length >= 40 ? '...' : '');
184
- memCell.title = t.content_preview || '';
185
- row.appendChild(memCell);
186
-
187
- var stateCell = document.createElement('td');
188
- var badge = document.createElement('span');
189
- badge.className = 'badge bg-secondary';
190
- badge.textContent = t.current_state || '';
191
- stateCell.appendChild(badge);
192
- row.appendChild(stateCell);
193
-
194
- var transCell = document.createElement('td');
195
- transCell.className = 'small text-muted';
196
- transCell.textContent = JSON.stringify(t.last_transition || {});
197
- row.appendChild(transCell);
198
-
199
- tbody.appendChild(row);
200
- }
201
- table.appendChild(tbody);
202
-
203
- container.textContent = '';
204
- container.appendChild(table);
205
- }
206
-
207
- async function compactDryRun() {
208
- try {
209
- var response = await fetch('/api/lifecycle/compact', {
210
- method: 'POST',
211
- headers: { 'Content-Type': 'application/json' },
212
- body: JSON.stringify({ dry_run: true })
213
- });
214
- var data = await response.json();
215
- var resultsDiv = document.getElementById('compaction-results');
216
- var titleEl = document.getElementById('compaction-results-title');
217
- var contentEl = document.getElementById('compaction-results-content');
218
- resultsDiv.classList.remove('d-none');
219
- titleEl.textContent = 'Compaction Preview (Dry Run)';
220
- contentEl.textContent = '';
221
-
222
- if (data.recommendations === 0) {
223
- var ok = document.createElement('span');
224
- ok.className = 'text-success';
225
- var icon = document.createElement('i');
226
- icon.className = 'bi bi-check-circle';
227
- ok.appendChild(icon);
228
- ok.appendChild(document.createTextNode(' No compaction needed. All memories are in optimal states.'));
229
- contentEl.appendChild(ok);
230
- } else {
231
- var p = document.createElement('p');
232
- p.className = 'mb-2';
233
- p.textContent = data.recommendations + ' memories would be transitioned:';
234
- contentEl.appendChild(p);
235
-
236
- var table = document.createElement('table');
237
- table.className = 'table table-sm mb-0';
238
- var thead = document.createElement('thead');
239
- var headRow = document.createElement('tr');
240
- ['Memory ID', 'From', 'To'].forEach(function(h) {
241
- var th = document.createElement('th');
242
- th.textContent = h;
243
- headRow.appendChild(th);
244
- });
245
- thead.appendChild(headRow);
246
- table.appendChild(thead);
247
-
248
- var tbody = document.createElement('tbody');
249
- var details = data.details || [];
250
- for (var i = 0; i < details.length; i++) {
251
- var row = document.createElement('tr');
252
- var idCell = document.createElement('td');
253
- idCell.textContent = '#' + details[i].memory_id;
254
- row.appendChild(idCell);
255
- var fromCell = document.createElement('td');
256
- fromCell.textContent = details[i].from || '';
257
- row.appendChild(fromCell);
258
- var toCell = document.createElement('td');
259
- toCell.textContent = details[i].to || '';
260
- row.appendChild(toCell);
261
- tbody.appendChild(row);
262
- }
263
- table.appendChild(tbody);
264
- contentEl.appendChild(table);
265
- }
266
- } catch (e) {
267
- console.error('Compaction preview error:', e);
268
- }
269
- }
270
-
271
- async function compactExecute() {
272
- if (!confirm('This will transition memories to lower lifecycle states. Continue?')) return;
273
- try {
274
- var response = await fetch('/api/lifecycle/compact', {
275
- method: 'POST',
276
- headers: { 'Content-Type': 'application/json' },
277
- body: JSON.stringify({ dry_run: false })
278
- });
279
- var data = await response.json();
280
- var resultsDiv = document.getElementById('compaction-results');
281
- var contentEl = document.getElementById('compaction-results-content');
282
- resultsDiv.classList.remove('d-none');
283
- document.getElementById('compaction-results-title').textContent = 'Compaction Results';
284
-
285
- contentEl.textContent = '';
286
- var ok = document.createElement('span');
287
- ok.className = 'text-success';
288
- var icon = document.createElement('i');
289
- icon.className = 'bi bi-check-circle';
290
- ok.appendChild(icon);
291
- ok.appendChild(document.createTextNode(' ' + (data.transitioned || 0) + ' memories transitioned successfully.'));
292
- contentEl.appendChild(ok);
293
-
294
- loadLifecycle(); // Refresh
295
- } catch (e) {
296
- console.error('Compaction error:', e);
297
- }
298
- }
@@ -1,98 +0,0 @@
1
- // SuperLocalMemory V3 — Math Health
2
- // Displays status of Fisher-Rao, sheaf cohomology, and Langevin dynamics layers.
3
-
4
- async function loadMathHealth() {
5
- try {
6
- var response = await fetch('/api/v3/math/health');
7
- if (!response.ok) return;
8
- var data = await response.json();
9
-
10
- var container = document.getElementById('math-health-cards');
11
- container.textContent = '';
12
-
13
- var layers = data.health || {};
14
- var colors = { fisher: 'primary', sheaf: 'success', langevin: 'info' };
15
- var icons = { fisher: 'bi-graph-up', sheaf: 'bi-diagram-3', langevin: 'bi-activity' };
16
-
17
- Object.keys(layers).forEach(function(key) {
18
- var layer = layers[key];
19
- var col = document.createElement('div');
20
- col.className = 'col-md-4';
21
-
22
- var card = document.createElement('div');
23
- card.className = 'card h-100';
24
-
25
- // Card header
26
- var header = document.createElement('div');
27
- header.className = 'card-header bg-' + (colors[key] || 'secondary') + ' text-white';
28
- var h6 = document.createElement('h6');
29
- h6.className = 'mb-0';
30
- var icon = document.createElement('i');
31
- icon.className = 'bi ' + (icons[key] || 'bi-gear');
32
- h6.appendChild(icon);
33
- h6.appendChild(document.createTextNode(' ' + key.charAt(0).toUpperCase() + key.slice(1)));
34
- header.appendChild(h6);
35
- card.appendChild(header);
36
-
37
- // Card body
38
- var body = document.createElement('div');
39
- body.className = 'card-body';
40
-
41
- var desc = document.createElement('p');
42
- desc.className = 'text-muted';
43
- desc.textContent = layer.description || '';
44
- body.appendChild(desc);
45
-
46
- var ul = document.createElement('ul');
47
- ul.className = 'list-unstyled mb-0';
48
-
49
- // Status item
50
- var liStatus = document.createElement('li');
51
- liStatus.appendChild(document.createTextNode('Status: '));
52
- var statusBadge = document.createElement('span');
53
- statusBadge.className = 'badge bg-success';
54
- statusBadge.textContent = layer.status || 'active';
55
- liStatus.appendChild(statusBadge);
56
- ul.appendChild(liStatus);
57
-
58
- // Mode item (if present)
59
- if (layer.mode) {
60
- var liMode = document.createElement('li');
61
- liMode.appendChild(document.createTextNode('Mode: '));
62
- var modeStrong = document.createElement('strong');
63
- modeStrong.textContent = layer.mode;
64
- liMode.appendChild(modeStrong);
65
- ul.appendChild(liMode);
66
- }
67
-
68
- // Threshold item (if present)
69
- if (layer.threshold) {
70
- var liThresh = document.createElement('li');
71
- liThresh.appendChild(document.createTextNode('Threshold: '));
72
- var threshStrong = document.createElement('strong');
73
- threshStrong.textContent = layer.threshold;
74
- liThresh.appendChild(threshStrong);
75
- ul.appendChild(liThresh);
76
- }
77
-
78
- // Temperature item (if present)
79
- if (layer.temperature) {
80
- var liTemp = document.createElement('li');
81
- liTemp.appendChild(document.createTextNode('Temperature: '));
82
- var tempStrong = document.createElement('strong');
83
- tempStrong.textContent = layer.temperature;
84
- liTemp.appendChild(tempStrong);
85
- ul.appendChild(liTemp);
86
- }
87
-
88
- body.appendChild(ul);
89
- card.appendChild(body);
90
- col.appendChild(card);
91
- container.appendChild(col);
92
- });
93
- } catch (e) {
94
- console.log('Math health error:', e);
95
- }
96
- }
97
-
98
- document.getElementById('math-health-tab')?.addEventListener('shown.bs.tab', loadMathHealth);
package/ui/js/memories.js DELETED
@@ -1,264 +0,0 @@
1
- // SuperLocalMemory V2 - Memories List + Sorting
2
- // Depends on: core.js, modal.js (openMemoryDetail)
3
- //
4
- // Security: All dynamic values are escaped via escapeHtml() before DOM insertion.
5
- // The innerHTML usage in renderMemoriesTable is safe because every interpolated
6
- // value passes through escapeHtml(). Data comes from our own local SQLite DB only.
7
- // nosemgrep: innerHTML-xss — all dynamic values escaped via escapeHtml()
8
-
9
- async function loadMemories() {
10
- var category = document.getElementById('filter-category').value;
11
- var project = document.getElementById('filter-project').value;
12
- var url = '/api/memories?limit=50';
13
- if (category) url += '&category=' + encodeURIComponent(category);
14
- if (project) url += '&project_name=' + encodeURIComponent(project);
15
-
16
- showLoading('memories-list', 'Loading memories...');
17
- try {
18
- var response = await fetch(url);
19
- var data = await response.json();
20
- lastSearchResults = null;
21
- var exportBtn = document.getElementById('export-search-btn');
22
- if (exportBtn) exportBtn.style.display = 'none';
23
- renderMemoriesTable(data.memories, false);
24
- } catch (error) {
25
- console.error('Error loading memories:', error);
26
- showEmpty('memories-list', 'exclamation-triangle', 'Failed to load memories');
27
- }
28
- }
29
-
30
- function renderMemoriesTable(memories, showScores) {
31
- var container = document.getElementById('memories-list');
32
- if (!memories || memories.length === 0) {
33
- showEmpty('memories-list', 'journal-x', 'No memories found. Try a different search or filter.');
34
- return;
35
- }
36
-
37
- window._slmMemories = memories;
38
- var scoreHeader = showScores ? '<th>Score</th>' : '';
39
-
40
- var rows = '';
41
- memories.forEach(function(mem, idx) {
42
- var content = mem.summary || mem.content || '';
43
- var contentPreview = content.length > 100 ? content.substring(0, 100) + '...' : content;
44
- var importance = mem.importance || 5;
45
- var importanceClass = importance >= 8 ? 'success' : importance >= 5 ? 'warning' : 'secondary';
46
-
47
- var scoreCell = '';
48
- if (showScores) {
49
- var score = mem.score || 0;
50
- var pct = Math.round(score * 100);
51
- var barColor = pct >= 70 ? '#43e97b' : pct >= 40 ? '#f9c74f' : '#f94144';
52
- scoreCell = '<td><span class="score-label">' + escapeHtml(String(pct)) + '%</span>'
53
- + '<div class="score-bar-container"><div class="score-bar">'
54
- + '<div class="score-bar-fill" style="width:' + pct + '%;background:' + barColor + '"></div>'
55
- + '</div></div></td>';
56
- }
57
-
58
- var memId = mem.memory_id || mem.id;
59
- var expandBtnHtml = '<button class="btn btn-sm btn-outline-secondary expand-facts-btn ms-1" data-memory-id="' + escapeHtml(String(memId)) + '" title="View atomic facts">&#9660;</button>';
60
-
61
- rows += '<tr data-mem-idx="' + idx + '">'
62
- + '<td>' + escapeHtml(String(mem.id)) + '</td>'
63
- + '<td><span class="badge bg-primary">' + escapeHtml(mem.category || 'None') + '</span></td>'
64
- + '<td><small>' + escapeHtml(mem.project_name || '-') + '</small></td>'
65
- + '<td class="memory-content" title="' + escapeHtml(content) + '">' + escapeHtml(contentPreview) + expandBtnHtml + '</td>'
66
- + scoreCell
67
- + '<td><span class="badge bg-' + importanceClass + ' badge-importance">' + escapeHtml(String(importance)) + '</span></td>'
68
- + '<td>' + escapeHtml(String(mem.cluster_id || '-')) + '</td>'
69
- + '<td><small>' + escapeHtml(formatDate(mem.created_at)) + '</small></td>'
70
- + '</tr>';
71
- });
72
-
73
- var tableHtml = '<table class="table table-hover memory-table"><thead><tr>'
74
- + '<th class="sortable" data-sort="id">ID</th>'
75
- + '<th class="sortable" data-sort="category">Category</th>'
76
- + '<th class="sortable" data-sort="project">Project</th>'
77
- + '<th>Content</th>'
78
- + scoreHeader
79
- + '<th class="sortable" data-sort="importance">Importance</th>'
80
- + '<th>Cluster</th>'
81
- + '<th class="sortable" data-sort="created">Created</th>'
82
- + '</tr></thead><tbody>' + rows + '</tbody></table>';
83
-
84
- // All values above escaped via escapeHtml() — safe for trusted local data
85
- container.textContent = '';
86
- container.insertAdjacentHTML('beforeend', tableHtml);
87
-
88
- var table = container.querySelector('table');
89
- if (table) {
90
- table.addEventListener('click', function(e) {
91
- var th = e.target.closest('th.sortable');
92
- if (th) { handleSort(th); return; }
93
-
94
- // Expand facts button
95
- var expandBtn = e.target.closest('.expand-facts-btn');
96
- if (expandBtn) {
97
- e.stopPropagation();
98
- var memId = expandBtn.getAttribute('data-memory-id');
99
- toggleFactsExpansion(expandBtn, memId);
100
- return;
101
- }
102
-
103
- var row = e.target.closest('tr[data-mem-idx]');
104
- if (row && !e.target.closest('.expand-facts-btn')) {
105
- var idx = parseInt(row.getAttribute('data-mem-idx'), 10);
106
- if (window._slmMemories && window._slmMemories[idx]) {
107
- openMemoryDetail(window._slmMemories[idx]);
108
- }
109
- }
110
- });
111
- }
112
- }
113
-
114
- // ============================================================================
115
- // Column Sorting
116
- // ============================================================================
117
-
118
- var currentSort = { column: null, direction: 'asc' };
119
-
120
- function handleSort(th) {
121
- var col = th.getAttribute('data-sort');
122
- if (!col) return;
123
-
124
- if (currentSort.column === col) {
125
- currentSort.direction = currentSort.direction === 'asc' ? 'desc' : 'asc';
126
- } else {
127
- currentSort.column = col;
128
- currentSort.direction = 'asc';
129
- }
130
-
131
- document.querySelectorAll('#memories-list th.sortable').forEach(function(h) {
132
- h.classList.remove('sort-asc', 'sort-desc');
133
- });
134
- th.classList.add('sort-' + currentSort.direction);
135
-
136
- if (!window._slmMemories) return;
137
- var memories = window._slmMemories.slice();
138
- var dir = currentSort.direction === 'asc' ? 1 : -1;
139
-
140
- memories.sort(function(a, b) {
141
- var av, bv;
142
- switch (col) {
143
- case 'id': return ((a.id || 0) - (b.id || 0)) * dir;
144
- case 'importance': return ((a.importance || 0) - (b.importance || 0)) * dir;
145
- case 'category':
146
- av = (a.category || '').toLowerCase(); bv = (b.category || '').toLowerCase();
147
- return av < bv ? -dir : av > bv ? dir : 0;
148
- case 'project':
149
- av = (a.project_name || '').toLowerCase(); bv = (b.project_name || '').toLowerCase();
150
- return av < bv ? -dir : av > bv ? dir : 0;
151
- case 'created':
152
- av = a.created_at || ''; bv = b.created_at || '';
153
- return av < bv ? -dir : av > bv ? dir : 0;
154
- case 'score': return ((a.score || 0) - (b.score || 0)) * dir;
155
- default: return 0;
156
- }
157
- });
158
-
159
- window._slmMemories = memories;
160
- var showScores = memories.length > 0 && typeof memories[0].score === 'number';
161
- renderMemoriesTable(memories, showScores);
162
- }
163
-
164
- // ============================================================================
165
- // Navigation from Graph (v2.6.5)
166
- // ============================================================================
167
-
168
- // Scroll to a specific memory by ID (called from graph double-click)
169
- function scrollToMemory(memoryId) {
170
- const container = document.getElementById('memories-list');
171
- if (!container) return;
172
-
173
- // Find the memory in current list
174
- if (!window._slmMemories) {
175
- console.warn('No memories loaded yet. Loading all memories...');
176
- loadMemories().then(() => {
177
- setTimeout(() => scrollToMemoryInTable(memoryId), 500);
178
- });
179
- return;
180
- }
181
-
182
- scrollToMemoryInTable(memoryId);
183
- }
184
-
185
- async function toggleFactsExpansion(btn, memoryId) {
186
- var row = btn.closest('tr');
187
- if (!row) return;
188
- var existingExpansion = row.nextElementSibling;
189
- if (existingExpansion && existingExpansion.classList.contains('facts-expansion-row')) {
190
- existingExpansion.remove();
191
- btn.innerHTML = '&#9660;';
192
- return;
193
- }
194
-
195
- btn.innerHTML = '&#8987;';
196
- try {
197
- var resp = await fetch('/api/memories/' + encodeURIComponent(memoryId) + '/facts');
198
- var data = await resp.json();
199
- var expRow = document.createElement('tr');
200
- expRow.className = 'facts-expansion-row';
201
- var expCell = document.createElement('td');
202
- expCell.colSpan = 8;
203
- expCell.style.cssText = 'background:#f8f9fa; padding:8px 16px;';
204
-
205
- if (data.facts && data.facts.length > 0) {
206
- var label = document.createElement('small');
207
- label.className = 'text-muted';
208
- label.textContent = data.fact_count + ' atomic facts:';
209
- expCell.appendChild(label);
210
-
211
- data.facts.forEach(function(f, i) {
212
- var fDiv = document.createElement('div');
213
- fDiv.className = 'small py-1' + (i < data.facts.length - 1 ? ' border-bottom' : '');
214
- var badge = document.createElement('span');
215
- badge.className = 'badge bg-secondary me-1';
216
- badge.style.fontSize = '0.65rem';
217
- badge.textContent = f.fact_type;
218
- fDiv.appendChild(badge);
219
- fDiv.appendChild(document.createTextNode(f.content.substring(0, 200)));
220
- expCell.appendChild(fDiv);
221
- });
222
- } else {
223
- expCell.textContent = 'No atomic facts found for this memory.';
224
- }
225
-
226
- expRow.appendChild(expCell);
227
- row.parentNode.insertBefore(expRow, row.nextSibling);
228
- btn.innerHTML = '&#9650;';
229
- } catch (err) {
230
- btn.innerHTML = '&#9660;';
231
- console.error('Failed to load facts:', err);
232
- }
233
- }
234
-
235
- function scrollToMemoryInTable(memoryId) {
236
- const memId = String(memoryId);
237
-
238
- // Find the memory index
239
- let idx = -1;
240
- if (window._slmMemories) {
241
- idx = window._slmMemories.findIndex(m => String(m.id) === memId);
242
- }
243
-
244
- if (idx === -1) {
245
- console.warn(`Memory #${memoryId} not found in current list`);
246
- return;
247
- }
248
-
249
- // Find the table row
250
- const row = document.querySelector(`tr[data-mem-idx="${idx}"]`);
251
- if (!row) {
252
- console.warn(`Row for memory #${memoryId} not found in DOM`);
253
- return;
254
- }
255
-
256
- // Scroll to row
257
- row.scrollIntoView({ behavior: 'smooth', block: 'center' });
258
-
259
- // Highlight temporarily
260
- row.style.backgroundColor = '#fff3cd';
261
- setTimeout(() => {
262
- row.style.backgroundColor = '';
263
- }, 2000);
264
- }