superlocalmemory 3.4.17 → 3.4.19

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 (80) hide show
  1. package/CHANGELOG.md +19 -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/core/embeddings.py +8 -2
  6. package/src/superlocalmemory/retrieval/reranker.py +4 -2
  7. package/src/superlocalmemory.egg-info/PKG-INFO +4 -1
  8. package/src/superlocalmemory.egg-info/requires.txt +3 -0
  9. package/docs/ARCHITECTURE.md +0 -149
  10. package/docs/api-reference.md +0 -284
  11. package/docs/auto-memory.md +0 -150
  12. package/docs/cli-reference.md +0 -327
  13. package/docs/cloud-backup.md +0 -174
  14. package/docs/compliance.md +0 -191
  15. package/docs/configuration.md +0 -182
  16. package/docs/getting-started.md +0 -102
  17. package/docs/ide-setup.md +0 -261
  18. package/docs/mcp-tools.md +0 -220
  19. package/docs/migration-from-v2.md +0 -170
  20. package/docs/profiles.md +0 -173
  21. package/docs/screenshots/01-dashboard-main.png +0 -0
  22. package/docs/screenshots/02-knowledge-graph.png +0 -0
  23. package/docs/screenshots/03-math-health.png +0 -0
  24. package/docs/screenshots/03-patterns-learning.png +0 -0
  25. package/docs/screenshots/04-learning-dashboard.png +0 -0
  26. package/docs/screenshots/04-recall-lab.png +0 -0
  27. package/docs/screenshots/05-behavioral-analysis.png +0 -0
  28. package/docs/screenshots/05-trust-dashboard.png +0 -0
  29. package/docs/screenshots/06-graph-communities.png +0 -0
  30. package/docs/screenshots/06-settings.png +0 -0
  31. package/docs/screenshots/07-memories-blurred.png +0 -0
  32. package/docs/skill-evolution.md +0 -256
  33. package/docs/troubleshooting.md +0 -310
  34. package/docs/v2-archive/ACCESSIBILITY.md +0 -291
  35. package/docs/v2-archive/ARCHITECTURE.md +0 -886
  36. package/docs/v2-archive/CLI-COMMANDS-REFERENCE.md +0 -425
  37. package/docs/v2-archive/COMPRESSION-README.md +0 -390
  38. package/docs/v2-archive/FRAMEWORK-INTEGRATIONS.md +0 -300
  39. package/docs/v2-archive/MCP-MANUAL-SETUP.md +0 -775
  40. package/docs/v2-archive/MCP-TROUBLESHOOTING.md +0 -787
  41. package/docs/v2-archive/PATTERN-LEARNING.md +0 -228
  42. package/docs/v2-archive/PROFILES-GUIDE.md +0 -453
  43. package/docs/v2-archive/RESET-GUIDE.md +0 -353
  44. package/docs/v2-archive/SEARCH-ENGINE-V2.2.0.md +0 -749
  45. package/docs/v2-archive/SEARCH-INTEGRATION-GUIDE.md +0 -502
  46. package/docs/v2-archive/UI-SERVER.md +0 -262
  47. package/docs/v2-archive/UNIVERSAL-INTEGRATION.md +0 -488
  48. package/docs/v2-archive/V2.2.0-OPTIONAL-SEARCH.md +0 -666
  49. package/docs/v2-archive/WINDOWS-INSTALL-README.txt +0 -34
  50. package/docs/v2-archive/WINDOWS-POST-INSTALL.txt +0 -45
  51. package/docs/v2-archive/example_graph_usage.py +0 -146
  52. package/ui/index.html +0 -1879
  53. package/ui/js/agents.js +0 -192
  54. package/ui/js/auto-settings.js +0 -399
  55. package/ui/js/behavioral.js +0 -276
  56. package/ui/js/clusters.js +0 -206
  57. package/ui/js/compliance.js +0 -252
  58. package/ui/js/core.js +0 -246
  59. package/ui/js/dashboard.js +0 -110
  60. package/ui/js/events.js +0 -178
  61. package/ui/js/fact-detail.js +0 -92
  62. package/ui/js/feedback.js +0 -333
  63. package/ui/js/graph-core.js +0 -447
  64. package/ui/js/graph-filters.js +0 -220
  65. package/ui/js/graph-interactions.js +0 -351
  66. package/ui/js/graph-ui.js +0 -214
  67. package/ui/js/ide-status.js +0 -102
  68. package/ui/js/init.js +0 -45
  69. package/ui/js/learning.js +0 -435
  70. package/ui/js/lifecycle.js +0 -298
  71. package/ui/js/math-health.js +0 -98
  72. package/ui/js/memories.js +0 -264
  73. package/ui/js/modal.js +0 -357
  74. package/ui/js/patterns.js +0 -93
  75. package/ui/js/profiles.js +0 -236
  76. package/ui/js/recall-lab.js +0 -292
  77. package/ui/js/search.js +0 -59
  78. package/ui/js/settings.js +0 -224
  79. package/ui/js/timeline.js +0 -32
  80. 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
- }