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.
- package/CHANGELOG.md +20 -0
- package/package.json +1 -3
- package/pyproject.toml +10 -1
- package/src/superlocalmemory/cli/setup_wizard.py +30 -0
- package/src/superlocalmemory/server/routes/entity.py +5 -9
- package/src/superlocalmemory/server/routes/helpers.py +120 -15
- package/src/superlocalmemory/server/routes/ingest.py +2 -3
- package/src/superlocalmemory/server/routes/v3_api.py +42 -2
- package/src/superlocalmemory/server/unified_daemon.py +21 -11
- package/src/superlocalmemory.egg-info/PKG-INFO +5 -2
- package/src/superlocalmemory.egg-info/requires.txt +3 -0
- package/docs/ARCHITECTURE.md +0 -149
- package/docs/api-reference.md +0 -284
- package/docs/auto-memory.md +0 -150
- package/docs/cli-reference.md +0 -327
- package/docs/cloud-backup.md +0 -174
- package/docs/compliance.md +0 -191
- package/docs/configuration.md +0 -182
- package/docs/getting-started.md +0 -102
- package/docs/ide-setup.md +0 -261
- package/docs/mcp-tools.md +0 -220
- package/docs/migration-from-v2.md +0 -170
- package/docs/profiles.md +0 -173
- package/docs/screenshots/01-dashboard-main.png +0 -0
- package/docs/screenshots/02-knowledge-graph.png +0 -0
- package/docs/screenshots/03-math-health.png +0 -0
- package/docs/screenshots/03-patterns-learning.png +0 -0
- package/docs/screenshots/04-learning-dashboard.png +0 -0
- package/docs/screenshots/04-recall-lab.png +0 -0
- package/docs/screenshots/05-behavioral-analysis.png +0 -0
- package/docs/screenshots/05-trust-dashboard.png +0 -0
- package/docs/screenshots/06-graph-communities.png +0 -0
- package/docs/screenshots/06-settings.png +0 -0
- package/docs/screenshots/07-memories-blurred.png +0 -0
- package/docs/skill-evolution.md +0 -256
- package/docs/troubleshooting.md +0 -310
- package/docs/v2-archive/ACCESSIBILITY.md +0 -291
- package/docs/v2-archive/ARCHITECTURE.md +0 -886
- package/docs/v2-archive/CLI-COMMANDS-REFERENCE.md +0 -425
- package/docs/v2-archive/COMPRESSION-README.md +0 -390
- package/docs/v2-archive/FRAMEWORK-INTEGRATIONS.md +0 -300
- package/docs/v2-archive/MCP-MANUAL-SETUP.md +0 -775
- package/docs/v2-archive/MCP-TROUBLESHOOTING.md +0 -787
- package/docs/v2-archive/PATTERN-LEARNING.md +0 -228
- package/docs/v2-archive/PROFILES-GUIDE.md +0 -453
- package/docs/v2-archive/RESET-GUIDE.md +0 -353
- package/docs/v2-archive/SEARCH-ENGINE-V2.2.0.md +0 -749
- package/docs/v2-archive/SEARCH-INTEGRATION-GUIDE.md +0 -502
- package/docs/v2-archive/UI-SERVER.md +0 -262
- package/docs/v2-archive/UNIVERSAL-INTEGRATION.md +0 -488
- package/docs/v2-archive/V2.2.0-OPTIONAL-SEARCH.md +0 -666
- package/docs/v2-archive/WINDOWS-INSTALL-README.txt +0 -34
- package/docs/v2-archive/WINDOWS-POST-INSTALL.txt +0 -45
- package/docs/v2-archive/example_graph_usage.py +0 -146
- package/ui/index.html +0 -1879
- package/ui/js/agents.js +0 -192
- package/ui/js/auto-settings.js +0 -399
- package/ui/js/behavioral.js +0 -276
- package/ui/js/clusters.js +0 -206
- package/ui/js/compliance.js +0 -252
- package/ui/js/core.js +0 -246
- package/ui/js/dashboard.js +0 -110
- package/ui/js/events.js +0 -178
- package/ui/js/fact-detail.js +0 -92
- package/ui/js/feedback.js +0 -333
- package/ui/js/graph-core.js +0 -447
- package/ui/js/graph-filters.js +0 -220
- package/ui/js/graph-interactions.js +0 -351
- package/ui/js/graph-ui.js +0 -214
- package/ui/js/ide-status.js +0 -102
- package/ui/js/init.js +0 -45
- package/ui/js/learning.js +0 -435
- package/ui/js/lifecycle.js +0 -298
- package/ui/js/math-health.js +0 -98
- package/ui/js/memories.js +0 -264
- package/ui/js/modal.js +0 -357
- package/ui/js/patterns.js +0 -93
- package/ui/js/profiles.js +0 -236
- package/ui/js/recall-lab.js +0 -292
- package/ui/js/search.js +0 -59
- package/ui/js/settings.js +0 -224
- package/ui/js/timeline.js +0 -32
- package/ui/js/trust-dashboard.js +0 -73
package/ui/js/modal.js
DELETED
|
@@ -1,357 +0,0 @@
|
|
|
1
|
-
// SuperLocalMemory V2 - Memory Detail Modal + Copy/Export
|
|
2
|
-
// Depends on: core.js
|
|
3
|
-
//
|
|
4
|
-
// Security: All dynamic values escaped via escapeHtml(). Data from local DB only.
|
|
5
|
-
// nosemgrep: innerHTML-xss — all dynamic values escaped
|
|
6
|
-
|
|
7
|
-
var currentMemoryDetail = null;
|
|
8
|
-
|
|
9
|
-
function openMemoryDetail(mem, source) {
|
|
10
|
-
// source: 'graph', 'recall', 'memories', or undefined
|
|
11
|
-
currentMemoryDetail = mem;
|
|
12
|
-
var fromGraph = source === 'graph';
|
|
13
|
-
var fromRecall = source === 'recall';
|
|
14
|
-
var body = document.getElementById('memory-detail-body');
|
|
15
|
-
if (!mem) {
|
|
16
|
-
body.textContent = 'No memory data';
|
|
17
|
-
return;
|
|
18
|
-
}
|
|
19
|
-
|
|
20
|
-
// Store last focused element (for keyboard nav return)
|
|
21
|
-
if (!window.lastFocusedElement) {
|
|
22
|
-
window.lastFocusedElement = document.activeElement;
|
|
23
|
-
}
|
|
24
|
-
|
|
25
|
-
var content = mem.content || mem.summary || '(no content)';
|
|
26
|
-
var tags = mem.tags || '';
|
|
27
|
-
var importance = mem.importance || 5;
|
|
28
|
-
var importanceClass = importance >= 8 ? 'success' : importance >= 5 ? 'warning' : 'secondary';
|
|
29
|
-
|
|
30
|
-
// Build detail using DOM nodes for safety
|
|
31
|
-
body.textContent = '';
|
|
32
|
-
|
|
33
|
-
var contentDiv = document.createElement('div');
|
|
34
|
-
contentDiv.className = 'memory-detail-content';
|
|
35
|
-
contentDiv.textContent = content;
|
|
36
|
-
body.appendChild(contentDiv);
|
|
37
|
-
|
|
38
|
-
body.appendChild(document.createElement('hr'));
|
|
39
|
-
|
|
40
|
-
var dl = document.createElement('dl');
|
|
41
|
-
dl.className = 'memory-detail-meta row';
|
|
42
|
-
|
|
43
|
-
// Left column
|
|
44
|
-
var col1 = document.createElement('div');
|
|
45
|
-
col1.className = 'col-md-6';
|
|
46
|
-
addDetailRow(col1, 'ID', String(mem.id || '-'));
|
|
47
|
-
addDetailBadgeRow(col1, 'Category', mem.category || 'None', 'bg-primary');
|
|
48
|
-
addDetailRow(col1, 'Project', mem.project_name || '-');
|
|
49
|
-
addDetailTagsRow(col1, 'Tags', tags);
|
|
50
|
-
dl.appendChild(col1);
|
|
51
|
-
|
|
52
|
-
// Right column
|
|
53
|
-
var col2 = document.createElement('div');
|
|
54
|
-
col2.className = 'col-md-6';
|
|
55
|
-
addDetailBadgeRow(col2, 'Importance', importance + '/10', 'bg-' + importanceClass);
|
|
56
|
-
addDetailRow(col2, 'Cluster', String(mem.cluster_id || '-'));
|
|
57
|
-
addDetailRow(col2, 'Created', formatDateFull(mem.created_at));
|
|
58
|
-
if (mem.updated_at) addDetailRow(col2, 'Updated', formatDateFull(mem.updated_at));
|
|
59
|
-
|
|
60
|
-
if (typeof mem.score === 'number') {
|
|
61
|
-
var pct = Math.round(mem.score * 100);
|
|
62
|
-
addDetailRow(col2, 'Relevance Score', pct + '%');
|
|
63
|
-
}
|
|
64
|
-
dl.appendChild(col2);
|
|
65
|
-
|
|
66
|
-
body.appendChild(dl);
|
|
67
|
-
|
|
68
|
-
// Context-aware action buttons
|
|
69
|
-
if (mem.id) {
|
|
70
|
-
body.appendChild(document.createElement('hr'));
|
|
71
|
-
|
|
72
|
-
var actionsDiv = document.createElement('div');
|
|
73
|
-
actionsDiv.className = 'memory-detail-graph-actions';
|
|
74
|
-
actionsDiv.style.cssText = 'display:flex; gap:10px; flex-wrap:wrap;';
|
|
75
|
-
|
|
76
|
-
// "View Original Memory" — shown on Recall Lab + Memories, hidden on Graph
|
|
77
|
-
// (On Graph the node IS the memory; on Recall Lab we have a fact, not the original)
|
|
78
|
-
if (!fromGraph) {
|
|
79
|
-
var viewBtn = document.createElement('button');
|
|
80
|
-
viewBtn.className = 'btn btn-primary btn-sm';
|
|
81
|
-
viewBtn.innerHTML = '<i class="bi bi-journal-text"></i> View Original Memory';
|
|
82
|
-
viewBtn.onclick = function() {
|
|
83
|
-
var mid = mem.memory_id || mem.id;
|
|
84
|
-
viewBtn.disabled = true;
|
|
85
|
-
viewBtn.textContent = 'Loading...';
|
|
86
|
-
fetch('/api/memories/' + encodeURIComponent(mid) + '/facts')
|
|
87
|
-
.then(function(r) { return r.json(); })
|
|
88
|
-
.then(function(data) {
|
|
89
|
-
if (data.ok && data.original_content) {
|
|
90
|
-
contentDiv.textContent = '';
|
|
91
|
-
var origLabel = document.createElement('small');
|
|
92
|
-
origLabel.className = 'text-muted d-block mb-1';
|
|
93
|
-
origLabel.textContent = 'Original memory (' + (data.fact_count || 0) + ' atomic facts extracted):';
|
|
94
|
-
contentDiv.appendChild(origLabel);
|
|
95
|
-
var origText = document.createElement('div');
|
|
96
|
-
origText.style.cssText = 'white-space:pre-wrap;background:#f8f9fa;padding:10px;border-radius:6px;margin-bottom:8px;';
|
|
97
|
-
origText.textContent = data.original_content;
|
|
98
|
-
contentDiv.appendChild(origText);
|
|
99
|
-
if (data.facts && data.facts.length > 0) {
|
|
100
|
-
var toggle = document.createElement('button');
|
|
101
|
-
toggle.className = 'btn btn-sm btn-outline-secondary mb-2';
|
|
102
|
-
toggle.textContent = 'Show atomic facts (' + data.facts.length + ')';
|
|
103
|
-
var factsDiv = document.createElement('div');
|
|
104
|
-
factsDiv.style.display = 'none';
|
|
105
|
-
data.facts.forEach(function(f) {
|
|
106
|
-
var fDiv = document.createElement('div');
|
|
107
|
-
fDiv.className = 'small py-1 border-bottom';
|
|
108
|
-
var badge = document.createElement('span');
|
|
109
|
-
badge.className = 'badge bg-secondary me-1';
|
|
110
|
-
badge.style.fontSize = '0.6rem';
|
|
111
|
-
badge.textContent = f.fact_type;
|
|
112
|
-
fDiv.appendChild(badge);
|
|
113
|
-
fDiv.appendChild(document.createTextNode(f.content));
|
|
114
|
-
factsDiv.appendChild(fDiv);
|
|
115
|
-
});
|
|
116
|
-
toggle.onclick = function() {
|
|
117
|
-
var hidden = factsDiv.style.display === 'none';
|
|
118
|
-
factsDiv.style.display = hidden ? 'block' : 'none';
|
|
119
|
-
toggle.textContent = hidden ? 'Hide atomic facts' : 'Show atomic facts (' + data.facts.length + ')';
|
|
120
|
-
};
|
|
121
|
-
contentDiv.appendChild(toggle);
|
|
122
|
-
contentDiv.appendChild(factsDiv);
|
|
123
|
-
}
|
|
124
|
-
viewBtn.textContent = 'Showing original';
|
|
125
|
-
} else {
|
|
126
|
-
viewBtn.textContent = 'Not available';
|
|
127
|
-
}
|
|
128
|
-
}).catch(function() {
|
|
129
|
-
viewBtn.textContent = 'Failed to load';
|
|
130
|
-
viewBtn.disabled = false;
|
|
131
|
-
});
|
|
132
|
-
};
|
|
133
|
-
actionsDiv.appendChild(viewBtn);
|
|
134
|
-
}
|
|
135
|
-
|
|
136
|
-
// "Expand Neighbors" — shown on Graph, hidden elsewhere (no graph context)
|
|
137
|
-
if (fromGraph) {
|
|
138
|
-
var expandBtn = document.createElement('button');
|
|
139
|
-
expandBtn.className = 'btn btn-outline-secondary btn-sm';
|
|
140
|
-
expandBtn.innerHTML = '<i class="bi bi-diagram-3"></i> Expand Neighbors';
|
|
141
|
-
expandBtn.onclick = function() {
|
|
142
|
-
modal.hide();
|
|
143
|
-
setTimeout(function() {
|
|
144
|
-
if (typeof expandNeighbors === 'function') expandNeighbors(mem.id);
|
|
145
|
-
}, 300);
|
|
146
|
-
};
|
|
147
|
-
actionsDiv.appendChild(expandBtn);
|
|
148
|
-
}
|
|
149
|
-
|
|
150
|
-
// "Filter to Cluster" — always available if cluster exists
|
|
151
|
-
if (mem.cluster_id) {
|
|
152
|
-
var filterBtn = document.createElement('button');
|
|
153
|
-
filterBtn.className = 'btn btn-outline-info btn-sm';
|
|
154
|
-
var filterIcon = document.createElement('i');
|
|
155
|
-
filterIcon.className = 'bi bi-funnel';
|
|
156
|
-
filterBtn.appendChild(filterIcon);
|
|
157
|
-
filterBtn.appendChild(document.createTextNode(' Filter to Cluster ' + mem.cluster_id));
|
|
158
|
-
filterBtn.onclick = function() {
|
|
159
|
-
modal.hide();
|
|
160
|
-
// Switch to Graph tab
|
|
161
|
-
const graphTab = document.querySelector('a[href="#graph"]');
|
|
162
|
-
if (graphTab) graphTab.click();
|
|
163
|
-
// Apply cluster filter after a delay
|
|
164
|
-
setTimeout(function() {
|
|
165
|
-
if (typeof filterState !== 'undefined' && typeof filterByCluster === 'function' && typeof renderGraph === 'function') {
|
|
166
|
-
filterState.cluster_id = mem.cluster_id;
|
|
167
|
-
const filtered = filterByCluster(originalGraphData, mem.cluster_id);
|
|
168
|
-
renderGraph(filtered);
|
|
169
|
-
// Update URL
|
|
170
|
-
const url = new URL(window.location);
|
|
171
|
-
url.searchParams.set('cluster_id', mem.cluster_id);
|
|
172
|
-
window.history.replaceState({}, '', url);
|
|
173
|
-
}
|
|
174
|
-
}, 500);
|
|
175
|
-
};
|
|
176
|
-
actionsDiv.appendChild(filterBtn);
|
|
177
|
-
}
|
|
178
|
-
|
|
179
|
-
// Edit button — always available
|
|
180
|
-
var editBtn = document.createElement('button');
|
|
181
|
-
editBtn.className = 'btn btn-outline-warning btn-sm';
|
|
182
|
-
editBtn.innerHTML = '<i class="bi bi-pencil"></i> Edit';
|
|
183
|
-
editBtn.onclick = function() {
|
|
184
|
-
var currentText = contentDiv.textContent;
|
|
185
|
-
var textarea = document.createElement('textarea');
|
|
186
|
-
textarea.className = 'form-control mb-2';
|
|
187
|
-
textarea.rows = 4;
|
|
188
|
-
textarea.value = currentText;
|
|
189
|
-
contentDiv.textContent = '';
|
|
190
|
-
contentDiv.appendChild(textarea);
|
|
191
|
-
var saveBtn = document.createElement('button');
|
|
192
|
-
saveBtn.className = 'btn btn-sm btn-success me-1';
|
|
193
|
-
saveBtn.textContent = 'Save';
|
|
194
|
-
saveBtn.onclick = function() {
|
|
195
|
-
var newContent = textarea.value.trim();
|
|
196
|
-
if (!newContent) return;
|
|
197
|
-
fetch('/api/memories/' + encodeURIComponent(mem.id), {
|
|
198
|
-
method: 'PATCH',
|
|
199
|
-
headers: {'Content-Type': 'application/json'},
|
|
200
|
-
body: JSON.stringify({content: newContent})
|
|
201
|
-
}).then(function(r) { return r.json(); }).then(function(d) {
|
|
202
|
-
if (d.success) {
|
|
203
|
-
contentDiv.textContent = newContent;
|
|
204
|
-
mem.content = newContent;
|
|
205
|
-
if (typeof showToast === 'function') showToast('Memory updated');
|
|
206
|
-
}
|
|
207
|
-
});
|
|
208
|
-
};
|
|
209
|
-
var cancelBtn = document.createElement('button');
|
|
210
|
-
cancelBtn.className = 'btn btn-sm btn-secondary';
|
|
211
|
-
cancelBtn.textContent = 'Cancel';
|
|
212
|
-
cancelBtn.onclick = function() { contentDiv.textContent = currentText; };
|
|
213
|
-
contentDiv.appendChild(saveBtn);
|
|
214
|
-
contentDiv.appendChild(cancelBtn);
|
|
215
|
-
};
|
|
216
|
-
actionsDiv.appendChild(editBtn);
|
|
217
|
-
|
|
218
|
-
// Delete button — always available
|
|
219
|
-
var deleteBtn = document.createElement('button');
|
|
220
|
-
deleteBtn.className = 'btn btn-outline-danger btn-sm';
|
|
221
|
-
deleteBtn.innerHTML = '<i class="bi bi-trash"></i> Delete';
|
|
222
|
-
deleteBtn.onclick = function() {
|
|
223
|
-
if (!confirm('Delete this memory? This cannot be undone.')) return;
|
|
224
|
-
fetch('/api/memories/' + encodeURIComponent(mem.id), {method: 'DELETE'})
|
|
225
|
-
.then(function(r) { return r.json(); })
|
|
226
|
-
.then(function(d) {
|
|
227
|
-
if (d.success) {
|
|
228
|
-
modal.hide();
|
|
229
|
-
if (typeof showToast === 'function') showToast('Memory deleted');
|
|
230
|
-
if (typeof loadMemories === 'function') setTimeout(loadMemories, 300);
|
|
231
|
-
}
|
|
232
|
-
});
|
|
233
|
-
};
|
|
234
|
-
actionsDiv.appendChild(deleteBtn);
|
|
235
|
-
|
|
236
|
-
body.appendChild(actionsDiv);
|
|
237
|
-
}
|
|
238
|
-
|
|
239
|
-
// v2.7.4: Add feedback buttons to modal body
|
|
240
|
-
if (typeof createFeedbackButtons === 'function' && mem && mem.id) {
|
|
241
|
-
var feedbackDiv = document.createElement('div');
|
|
242
|
-
feedbackDiv.className = 'mt-3 pt-2 border-top';
|
|
243
|
-
var feedbackLabel = document.createElement('small');
|
|
244
|
-
feedbackLabel.className = 'text-muted d-block mb-1';
|
|
245
|
-
feedbackLabel.textContent = 'Was this memory useful?';
|
|
246
|
-
feedbackDiv.appendChild(feedbackLabel);
|
|
247
|
-
feedbackDiv.appendChild(createFeedbackButtons(mem.id));
|
|
248
|
-
body.appendChild(feedbackDiv);
|
|
249
|
-
}
|
|
250
|
-
|
|
251
|
-
var modalEl = document.getElementById('memoryDetailModal');
|
|
252
|
-
var modal = new bootstrap.Modal(modalEl);
|
|
253
|
-
|
|
254
|
-
// v2.7.4: Start dwell time tracking
|
|
255
|
-
if (typeof startDwellTracking === 'function' && mem && mem.id) {
|
|
256
|
-
startDwellTracking(mem.id);
|
|
257
|
-
}
|
|
258
|
-
|
|
259
|
-
// Focus first interactive element when modal opens
|
|
260
|
-
modalEl.addEventListener('shown.bs.modal', function() {
|
|
261
|
-
const firstButton = modalEl.querySelector('button, a[href]');
|
|
262
|
-
if (firstButton) {
|
|
263
|
-
firstButton.focus();
|
|
264
|
-
}
|
|
265
|
-
}, { once: true });
|
|
266
|
-
|
|
267
|
-
// Return focus when modal closes + stop dwell tracking
|
|
268
|
-
modalEl.addEventListener('hidden.bs.modal', function() {
|
|
269
|
-
// v2.7.4: Stop dwell time tracking
|
|
270
|
-
if (typeof stopDwellTracking === 'function') {
|
|
271
|
-
stopDwellTracking();
|
|
272
|
-
}
|
|
273
|
-
if (window.lastFocusedElement && typeof window.lastFocusedElement.focus === 'function') {
|
|
274
|
-
window.lastFocusedElement.focus();
|
|
275
|
-
window.lastFocusedElement = null;
|
|
276
|
-
}
|
|
277
|
-
}, { once: true });
|
|
278
|
-
|
|
279
|
-
modal.show();
|
|
280
|
-
}
|
|
281
|
-
|
|
282
|
-
function addDetailRow(parent, label, value) {
|
|
283
|
-
var dt = document.createElement('dt');
|
|
284
|
-
dt.textContent = label;
|
|
285
|
-
parent.appendChild(dt);
|
|
286
|
-
var dd = document.createElement('dd');
|
|
287
|
-
dd.textContent = value;
|
|
288
|
-
parent.appendChild(dd);
|
|
289
|
-
}
|
|
290
|
-
|
|
291
|
-
function addDetailBadgeRow(parent, label, value, badgeClass) {
|
|
292
|
-
var dt = document.createElement('dt');
|
|
293
|
-
dt.textContent = label;
|
|
294
|
-
parent.appendChild(dt);
|
|
295
|
-
var dd = document.createElement('dd');
|
|
296
|
-
var badge = document.createElement('span');
|
|
297
|
-
badge.className = 'badge ' + badgeClass;
|
|
298
|
-
badge.textContent = value;
|
|
299
|
-
dd.appendChild(badge);
|
|
300
|
-
parent.appendChild(dd);
|
|
301
|
-
}
|
|
302
|
-
|
|
303
|
-
function addDetailTagsRow(parent, label, tags) {
|
|
304
|
-
var dt = document.createElement('dt');
|
|
305
|
-
dt.textContent = label;
|
|
306
|
-
parent.appendChild(dt);
|
|
307
|
-
var dd = document.createElement('dd');
|
|
308
|
-
var tagList = typeof tags === 'string' ? tags.split(',') : (tags || []);
|
|
309
|
-
if (tagList.length === 0 || (tagList.length === 1 && !tagList[0].trim())) {
|
|
310
|
-
dd.className = 'text-muted';
|
|
311
|
-
dd.textContent = 'None';
|
|
312
|
-
} else {
|
|
313
|
-
tagList.forEach(function(t) {
|
|
314
|
-
var tag = t.trim();
|
|
315
|
-
if (tag) {
|
|
316
|
-
var badge = document.createElement('span');
|
|
317
|
-
badge.className = 'badge bg-secondary me-1';
|
|
318
|
-
badge.textContent = tag;
|
|
319
|
-
dd.appendChild(badge);
|
|
320
|
-
}
|
|
321
|
-
});
|
|
322
|
-
}
|
|
323
|
-
parent.appendChild(dd);
|
|
324
|
-
}
|
|
325
|
-
|
|
326
|
-
function copyMemoryToClipboard() {
|
|
327
|
-
if (!currentMemoryDetail) return;
|
|
328
|
-
var text = currentMemoryDetail.content || currentMemoryDetail.summary || '';
|
|
329
|
-
navigator.clipboard.writeText(text).then(function() {
|
|
330
|
-
showToast('Copied to clipboard');
|
|
331
|
-
}).catch(function() {
|
|
332
|
-
var ta = document.createElement('textarea');
|
|
333
|
-
ta.value = text;
|
|
334
|
-
document.body.appendChild(ta);
|
|
335
|
-
ta.select();
|
|
336
|
-
document.execCommand('copy');
|
|
337
|
-
document.body.removeChild(ta);
|
|
338
|
-
showToast('Copied to clipboard');
|
|
339
|
-
});
|
|
340
|
-
}
|
|
341
|
-
|
|
342
|
-
function exportMemoryAsMarkdown() {
|
|
343
|
-
if (!currentMemoryDetail) return;
|
|
344
|
-
var mem = currentMemoryDetail;
|
|
345
|
-
var md = '# Memory #' + (mem.id || 'unknown') + '\n\n';
|
|
346
|
-
md += '**Category:** ' + (mem.category || 'None') + ' \n';
|
|
347
|
-
md += '**Project:** ' + (mem.project_name || '-') + ' \n';
|
|
348
|
-
md += '**Importance:** ' + (mem.importance || 5) + '/10 \n';
|
|
349
|
-
md += '**Tags:** ' + (mem.tags || 'None') + ' \n';
|
|
350
|
-
md += '**Created:** ' + (mem.created_at || '-') + ' \n';
|
|
351
|
-
if (mem.cluster_id) md += '**Cluster:** ' + mem.cluster_id + ' \n';
|
|
352
|
-
md += '\n---\n\n';
|
|
353
|
-
md += mem.content || mem.summary || '(no content)';
|
|
354
|
-
md += '\n\n---\n*Exported from SuperLocalMemory V2*\n';
|
|
355
|
-
|
|
356
|
-
downloadFile('memory-' + (mem.id || 'export') + '.md', md, 'text/markdown');
|
|
357
|
-
}
|
package/ui/js/patterns.js
DELETED
|
@@ -1,93 +0,0 @@
|
|
|
1
|
-
// SuperLocalMemory V2 - Patterns View (Layer 4)
|
|
2
|
-
// Depends on: core.js
|
|
3
|
-
|
|
4
|
-
async function loadPatterns() {
|
|
5
|
-
showLoading('patterns-list', 'Loading patterns...');
|
|
6
|
-
try {
|
|
7
|
-
var response = await fetch('/api/patterns');
|
|
8
|
-
var data = await response.json();
|
|
9
|
-
renderPatterns(data.patterns);
|
|
10
|
-
} catch (error) {
|
|
11
|
-
console.error('Error loading patterns:', error);
|
|
12
|
-
showEmpty('patterns-list', 'puzzle', 'Failed to load patterns');
|
|
13
|
-
}
|
|
14
|
-
}
|
|
15
|
-
|
|
16
|
-
function renderPatterns(patterns) {
|
|
17
|
-
var container = document.getElementById('patterns-list');
|
|
18
|
-
if (!patterns || Object.keys(patterns).length === 0) {
|
|
19
|
-
showEmpty('patterns-list', 'puzzle', 'No patterns learned yet. Use SuperLocalMemory for a while to build patterns.');
|
|
20
|
-
return;
|
|
21
|
-
}
|
|
22
|
-
|
|
23
|
-
var typeIcons = { preference: 'heart', style: 'palette', terminology: 'code-slash' };
|
|
24
|
-
var typeLabels = { preference: 'Preferences', style: 'Coding Style', terminology: 'Terminology' };
|
|
25
|
-
|
|
26
|
-
container.textContent = '';
|
|
27
|
-
|
|
28
|
-
for (var type in patterns) {
|
|
29
|
-
if (!patterns.hasOwnProperty(type)) continue;
|
|
30
|
-
var items = patterns[type];
|
|
31
|
-
|
|
32
|
-
var header = document.createElement('h6');
|
|
33
|
-
header.className = 'mt-3 mb-2';
|
|
34
|
-
var icon = document.createElement('i');
|
|
35
|
-
icon.className = 'bi bi-' + (typeIcons[type] || 'puzzle') + ' me-1';
|
|
36
|
-
header.appendChild(icon);
|
|
37
|
-
header.appendChild(document.createTextNode(typeLabels[type] || type));
|
|
38
|
-
var countBadge = document.createElement('span');
|
|
39
|
-
countBadge.className = 'badge bg-secondary ms-2';
|
|
40
|
-
countBadge.textContent = items.length;
|
|
41
|
-
header.appendChild(countBadge);
|
|
42
|
-
container.appendChild(header);
|
|
43
|
-
|
|
44
|
-
var group = document.createElement('div');
|
|
45
|
-
group.className = 'list-group mb-3';
|
|
46
|
-
|
|
47
|
-
items.forEach(function(pattern) {
|
|
48
|
-
var pct = Math.round(pattern.confidence * 100);
|
|
49
|
-
var barColor = pct >= 60 ? '#43e97b' : pct >= 40 ? '#f9c74f' : '#6c757d';
|
|
50
|
-
var badgeClass = pct >= 60 ? 'bg-success' : pct >= 40 ? 'bg-warning text-dark' : 'bg-secondary';
|
|
51
|
-
|
|
52
|
-
var item = document.createElement('div');
|
|
53
|
-
item.className = 'list-group-item';
|
|
54
|
-
|
|
55
|
-
var topRow = document.createElement('div');
|
|
56
|
-
topRow.className = 'd-flex justify-content-between align-items-center';
|
|
57
|
-
var keyEl = document.createElement('strong');
|
|
58
|
-
keyEl.textContent = pattern.key;
|
|
59
|
-
var badge = document.createElement('span');
|
|
60
|
-
badge.className = 'badge ' + badgeClass;
|
|
61
|
-
badge.textContent = pct + '%';
|
|
62
|
-
topRow.appendChild(keyEl);
|
|
63
|
-
topRow.appendChild(badge);
|
|
64
|
-
item.appendChild(topRow);
|
|
65
|
-
|
|
66
|
-
var barContainer = document.createElement('div');
|
|
67
|
-
barContainer.className = 'confidence-bar';
|
|
68
|
-
var barFill = document.createElement('div');
|
|
69
|
-
barFill.className = 'confidence-fill';
|
|
70
|
-
barFill.style.width = pct + '%';
|
|
71
|
-
barFill.style.background = barColor;
|
|
72
|
-
barContainer.appendChild(barFill);
|
|
73
|
-
item.appendChild(barContainer);
|
|
74
|
-
|
|
75
|
-
var valueEl = document.createElement('div');
|
|
76
|
-
valueEl.className = 'mt-1';
|
|
77
|
-
var valueSmall = document.createElement('small');
|
|
78
|
-
valueSmall.className = 'text-muted';
|
|
79
|
-
valueSmall.textContent = typeof pattern.value === 'string' ? pattern.value : JSON.stringify(pattern.value);
|
|
80
|
-
valueEl.appendChild(valueSmall);
|
|
81
|
-
item.appendChild(valueEl);
|
|
82
|
-
|
|
83
|
-
var evidenceEl = document.createElement('small');
|
|
84
|
-
evidenceEl.className = 'text-muted';
|
|
85
|
-
evidenceEl.textContent = 'Evidence: ' + (pattern.evidence_count || '?') + ' memories';
|
|
86
|
-
item.appendChild(evidenceEl);
|
|
87
|
-
|
|
88
|
-
group.appendChild(item);
|
|
89
|
-
});
|
|
90
|
-
|
|
91
|
-
container.appendChild(group);
|
|
92
|
-
}
|
|
93
|
-
}
|
package/ui/js/profiles.js
DELETED
|
@@ -1,236 +0,0 @@
|
|
|
1
|
-
// SuperLocalMemory V2 - Profile Management
|
|
2
|
-
// Depends on: core.js
|
|
3
|
-
|
|
4
|
-
async function loadProfiles() {
|
|
5
|
-
try {
|
|
6
|
-
var response = await fetch('/api/profiles');
|
|
7
|
-
var data = await response.json();
|
|
8
|
-
var select = document.getElementById('profile-select');
|
|
9
|
-
select.textContent = '';
|
|
10
|
-
var profiles = data.profiles || [];
|
|
11
|
-
var active = data.active_profile || 'default';
|
|
12
|
-
|
|
13
|
-
if (profiles.length === 0) {
|
|
14
|
-
// Fresh install fallback — show default profile
|
|
15
|
-
var opt = document.createElement('option');
|
|
16
|
-
opt.value = 'default';
|
|
17
|
-
opt.textContent = 'default (0)';
|
|
18
|
-
opt.selected = true;
|
|
19
|
-
select.appendChild(opt);
|
|
20
|
-
} else {
|
|
21
|
-
profiles.forEach(function(p) {
|
|
22
|
-
var opt = document.createElement('option');
|
|
23
|
-
opt.value = p.name;
|
|
24
|
-
opt.textContent = p.name + ' (' + (p.memory_count || 0) + ')';
|
|
25
|
-
if (p.name === active) opt.selected = true;
|
|
26
|
-
select.appendChild(opt);
|
|
27
|
-
});
|
|
28
|
-
}
|
|
29
|
-
} catch (error) {
|
|
30
|
-
console.error('Error loading profiles:', error);
|
|
31
|
-
// On error, ensure dropdown shows at least 'default'
|
|
32
|
-
var select = document.getElementById('profile-select');
|
|
33
|
-
if (select && select.options.length === 0) {
|
|
34
|
-
select.textContent = '';
|
|
35
|
-
var opt = document.createElement('option');
|
|
36
|
-
opt.value = 'default';
|
|
37
|
-
opt.textContent = 'default';
|
|
38
|
-
opt.selected = true;
|
|
39
|
-
select.appendChild(opt);
|
|
40
|
-
}
|
|
41
|
-
}
|
|
42
|
-
}
|
|
43
|
-
|
|
44
|
-
async function createProfile(nameOverride) {
|
|
45
|
-
var name = nameOverride || document.getElementById('new-profile-name').value.trim();
|
|
46
|
-
if (!name) {
|
|
47
|
-
name = prompt('Enter new profile name:');
|
|
48
|
-
if (!name || !name.trim()) return;
|
|
49
|
-
name = name.trim();
|
|
50
|
-
}
|
|
51
|
-
|
|
52
|
-
if (!/^[a-zA-Z0-9_-]+$/.test(name)) {
|
|
53
|
-
showToast('Invalid name. Use letters, numbers, dashes, underscores.');
|
|
54
|
-
return;
|
|
55
|
-
}
|
|
56
|
-
|
|
57
|
-
try {
|
|
58
|
-
var response = await fetch('/api/profiles/create', {
|
|
59
|
-
method: 'POST',
|
|
60
|
-
headers: { 'Content-Type': 'application/json' },
|
|
61
|
-
body: JSON.stringify({ profile_name: name })
|
|
62
|
-
});
|
|
63
|
-
var data = await response.json();
|
|
64
|
-
if (response.status === 409) {
|
|
65
|
-
showToast('Profile "' + name + '" already exists');
|
|
66
|
-
return;
|
|
67
|
-
}
|
|
68
|
-
if (!response.ok) {
|
|
69
|
-
showToast(data.detail || 'Failed to create profile');
|
|
70
|
-
return;
|
|
71
|
-
}
|
|
72
|
-
showToast('Profile "' + name + '" created');
|
|
73
|
-
var input = document.getElementById('new-profile-name');
|
|
74
|
-
if (input) input.value = '';
|
|
75
|
-
// Force reload with small delay to ensure backend has persisted
|
|
76
|
-
setTimeout(function() {
|
|
77
|
-
loadProfiles();
|
|
78
|
-
if (typeof loadProfilesTable === 'function') loadProfilesTable();
|
|
79
|
-
}, 300);
|
|
80
|
-
} catch (error) {
|
|
81
|
-
console.error('Error creating profile:', error);
|
|
82
|
-
showToast('Error creating profile');
|
|
83
|
-
}
|
|
84
|
-
}
|
|
85
|
-
|
|
86
|
-
async function deleteProfile(name) {
|
|
87
|
-
if (name === 'default') {
|
|
88
|
-
showToast('Cannot delete the default profile');
|
|
89
|
-
return;
|
|
90
|
-
}
|
|
91
|
-
if (!confirm('Delete profile "' + name + '"?\nIts memories will be moved to the default profile.')) {
|
|
92
|
-
return;
|
|
93
|
-
}
|
|
94
|
-
try {
|
|
95
|
-
var response = await fetch('/api/profiles/' + encodeURIComponent(name), {
|
|
96
|
-
method: 'DELETE'
|
|
97
|
-
});
|
|
98
|
-
var data = await response.json();
|
|
99
|
-
if (!response.ok) {
|
|
100
|
-
showToast(data.detail || 'Failed to delete profile');
|
|
101
|
-
return;
|
|
102
|
-
}
|
|
103
|
-
showToast(data.message || 'Profile deleted');
|
|
104
|
-
loadProfiles();
|
|
105
|
-
loadProfilesTable();
|
|
106
|
-
loadStats();
|
|
107
|
-
} catch (error) {
|
|
108
|
-
console.error('Error deleting profile:', error);
|
|
109
|
-
showToast('Error deleting profile');
|
|
110
|
-
}
|
|
111
|
-
}
|
|
112
|
-
|
|
113
|
-
async function loadProfilesTable() {
|
|
114
|
-
var container = document.getElementById('profiles-table');
|
|
115
|
-
if (!container) return;
|
|
116
|
-
try {
|
|
117
|
-
var response = await fetch('/api/profiles');
|
|
118
|
-
var data = await response.json();
|
|
119
|
-
var profiles = data.profiles || [];
|
|
120
|
-
var active = data.active_profile || 'default';
|
|
121
|
-
|
|
122
|
-
if (profiles.length === 0) {
|
|
123
|
-
showEmpty('profiles-table', 'people', 'No profiles found.');
|
|
124
|
-
return;
|
|
125
|
-
}
|
|
126
|
-
|
|
127
|
-
var table = document.createElement('table');
|
|
128
|
-
table.className = 'table table-sm mb-0';
|
|
129
|
-
var thead = document.createElement('thead');
|
|
130
|
-
var headRow = document.createElement('tr');
|
|
131
|
-
['Name', 'Memories', 'Status', 'Actions'].forEach(function(h) {
|
|
132
|
-
var th = document.createElement('th');
|
|
133
|
-
th.textContent = h;
|
|
134
|
-
headRow.appendChild(th);
|
|
135
|
-
});
|
|
136
|
-
thead.appendChild(headRow);
|
|
137
|
-
table.appendChild(thead);
|
|
138
|
-
|
|
139
|
-
var tbody = document.createElement('tbody');
|
|
140
|
-
profiles.forEach(function(p) {
|
|
141
|
-
var row = document.createElement('tr');
|
|
142
|
-
|
|
143
|
-
var nameCell = document.createElement('td');
|
|
144
|
-
var nameIcon = document.createElement('i');
|
|
145
|
-
nameIcon.className = 'bi bi-person me-1';
|
|
146
|
-
nameCell.appendChild(nameIcon);
|
|
147
|
-
nameCell.appendChild(document.createTextNode(p.name));
|
|
148
|
-
row.appendChild(nameCell);
|
|
149
|
-
|
|
150
|
-
var countCell = document.createElement('td');
|
|
151
|
-
countCell.textContent = (p.memory_count || 0) + ' memories';
|
|
152
|
-
row.appendChild(countCell);
|
|
153
|
-
|
|
154
|
-
var statusCell = document.createElement('td');
|
|
155
|
-
if (p.name === active) {
|
|
156
|
-
var badge = document.createElement('span');
|
|
157
|
-
badge.className = 'badge bg-success';
|
|
158
|
-
badge.textContent = 'Active';
|
|
159
|
-
statusCell.appendChild(badge);
|
|
160
|
-
} else {
|
|
161
|
-
var switchBtn = document.createElement('button');
|
|
162
|
-
switchBtn.className = 'btn btn-sm btn-outline-primary';
|
|
163
|
-
switchBtn.textContent = 'Switch';
|
|
164
|
-
switchBtn.addEventListener('click', (function(n) {
|
|
165
|
-
return function() { switchProfile(n); };
|
|
166
|
-
})(p.name));
|
|
167
|
-
statusCell.appendChild(switchBtn);
|
|
168
|
-
}
|
|
169
|
-
row.appendChild(statusCell);
|
|
170
|
-
|
|
171
|
-
var actionsCell = document.createElement('td');
|
|
172
|
-
if (p.name !== 'default') {
|
|
173
|
-
var delBtn = document.createElement('button');
|
|
174
|
-
delBtn.className = 'btn btn-sm btn-outline-danger btn-delete-profile';
|
|
175
|
-
delBtn.title = 'Delete profile';
|
|
176
|
-
var delIcon = document.createElement('i');
|
|
177
|
-
delIcon.className = 'bi bi-trash';
|
|
178
|
-
delBtn.appendChild(delIcon);
|
|
179
|
-
delBtn.addEventListener('click', (function(n) {
|
|
180
|
-
return function() { deleteProfile(n); };
|
|
181
|
-
})(p.name));
|
|
182
|
-
actionsCell.appendChild(delBtn);
|
|
183
|
-
} else {
|
|
184
|
-
var protectedBadge = document.createElement('span');
|
|
185
|
-
protectedBadge.className = 'badge bg-secondary';
|
|
186
|
-
protectedBadge.textContent = 'Protected';
|
|
187
|
-
actionsCell.appendChild(protectedBadge);
|
|
188
|
-
}
|
|
189
|
-
row.appendChild(actionsCell);
|
|
190
|
-
|
|
191
|
-
tbody.appendChild(row);
|
|
192
|
-
});
|
|
193
|
-
table.appendChild(tbody);
|
|
194
|
-
|
|
195
|
-
container.textContent = '';
|
|
196
|
-
container.appendChild(table);
|
|
197
|
-
} catch (error) {
|
|
198
|
-
console.error('Error loading profiles table:', error);
|
|
199
|
-
showEmpty('profiles-table', 'exclamation-triangle', 'Failed to load profiles');
|
|
200
|
-
}
|
|
201
|
-
}
|
|
202
|
-
|
|
203
|
-
async function switchProfile(profileName) {
|
|
204
|
-
try {
|
|
205
|
-
var response = await fetch('/api/profiles/' + encodeURIComponent(profileName) + '/switch', {
|
|
206
|
-
method: 'POST'
|
|
207
|
-
});
|
|
208
|
-
var data = await response.json();
|
|
209
|
-
if (data.success || data.active_profile) {
|
|
210
|
-
showToast('Switched to profile: ' + profileName);
|
|
211
|
-
loadProfiles();
|
|
212
|
-
loadStats();
|
|
213
|
-
loadGraph();
|
|
214
|
-
loadProfilesTable();
|
|
215
|
-
// v2.7.4: Reload ALL tabs for new profile
|
|
216
|
-
if (typeof loadLearning === 'function') loadLearning();
|
|
217
|
-
if (typeof refreshFeedbackStats === 'function') refreshFeedbackStats();
|
|
218
|
-
if (typeof loadLearningDataStats === 'function') loadLearningDataStats();
|
|
219
|
-
if (typeof loadAgents === 'function') loadAgents();
|
|
220
|
-
if (typeof loadMemories === 'function') loadMemories();
|
|
221
|
-
if (typeof loadTimeline === 'function') loadTimeline();
|
|
222
|
-
if (typeof loadEvents === 'function') loadEvents();
|
|
223
|
-
// v2.8 tabs
|
|
224
|
-
if (typeof loadLifecycle === 'function') loadLifecycle();
|
|
225
|
-
if (typeof loadBehavioral === 'function') loadBehavioral();
|
|
226
|
-
if (typeof loadCompliance === 'function') loadCompliance();
|
|
227
|
-
var activeTab = document.querySelector('#mainTabs .nav-link.active');
|
|
228
|
-
if (activeTab) activeTab.click();
|
|
229
|
-
} else {
|
|
230
|
-
showToast('Failed to switch profile');
|
|
231
|
-
}
|
|
232
|
-
} catch (error) {
|
|
233
|
-
console.error('Error switching profile:', error);
|
|
234
|
-
showToast('Error switching profile');
|
|
235
|
-
}
|
|
236
|
-
}
|