superlocalmemory 3.0.17 → 3.0.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.
- package/bin/slm-npm +8 -0
- package/package.json +1 -1
- package/pyproject.toml +1 -1
- package/src/superlocalmemory/cli/commands.py +68 -0
- package/src/superlocalmemory/cli/main.py +20 -2
- package/src/superlocalmemory/core/embedding_worker.py +120 -0
- package/src/superlocalmemory/core/embeddings.py +156 -240
- package/src/superlocalmemory/core/recall_worker.py +267 -0
- package/src/superlocalmemory/core/summarizer.py +182 -0
- package/src/superlocalmemory/core/worker_pool.py +217 -0
- package/src/superlocalmemory/mcp/server.py +9 -0
- package/src/superlocalmemory/mcp/tools_core.py +82 -8
- package/src/superlocalmemory/server/routes/helpers.py +21 -0
- package/src/superlocalmemory/server/routes/memories.py +107 -33
- package/src/superlocalmemory/server/routes/v3_api.py +195 -43
- package/src/superlocalmemory/server/ui.py +15 -14
- package/src/superlocalmemory/storage/database.py +49 -0
- package/src/superlocalmemory.egg-info/PKG-INFO +1 -1
- package/src/superlocalmemory.egg-info/SOURCES.txt +4 -0
- package/ui/index.html +77 -21
- package/ui/js/auto-settings.js +330 -1
- package/ui/js/clusters.js +11 -0
- package/ui/js/graph-interactions.js +2 -5
- package/ui/js/memories.js +65 -2
- package/ui/js/modal.js +137 -43
- package/ui/js/recall-lab.js +98 -46
package/ui/js/memories.js
CHANGED
|
@@ -55,11 +55,14 @@ function renderMemoriesTable(memories, showScores) {
|
|
|
55
55
|
+ '</div></div></td>';
|
|
56
56
|
}
|
|
57
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">▼</button>';
|
|
60
|
+
|
|
58
61
|
rows += '<tr data-mem-idx="' + idx + '">'
|
|
59
62
|
+ '<td>' + escapeHtml(String(mem.id)) + '</td>'
|
|
60
63
|
+ '<td><span class="badge bg-primary">' + escapeHtml(mem.category || 'None') + '</span></td>'
|
|
61
64
|
+ '<td><small>' + escapeHtml(mem.project_name || '-') + '</small></td>'
|
|
62
|
-
+ '<td class="memory-content" title="' + escapeHtml(content) + '">' + escapeHtml(contentPreview) + '</td>'
|
|
65
|
+
+ '<td class="memory-content" title="' + escapeHtml(content) + '">' + escapeHtml(contentPreview) + expandBtnHtml + '</td>'
|
|
63
66
|
+ scoreCell
|
|
64
67
|
+ '<td><span class="badge bg-' + importanceClass + ' badge-importance">' + escapeHtml(String(importance)) + '</span></td>'
|
|
65
68
|
+ '<td>' + escapeHtml(String(mem.cluster_id || '-')) + '</td>'
|
|
@@ -87,8 +90,18 @@ function renderMemoriesTable(memories, showScores) {
|
|
|
87
90
|
table.addEventListener('click', function(e) {
|
|
88
91
|
var th = e.target.closest('th.sortable');
|
|
89
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
|
+
|
|
90
103
|
var row = e.target.closest('tr[data-mem-idx]');
|
|
91
|
-
if (row) {
|
|
104
|
+
if (row && !e.target.closest('.expand-facts-btn')) {
|
|
92
105
|
var idx = parseInt(row.getAttribute('data-mem-idx'), 10);
|
|
93
106
|
if (window._slmMemories && window._slmMemories[idx]) {
|
|
94
107
|
openMemoryDetail(window._slmMemories[idx]);
|
|
@@ -169,6 +182,56 @@ function scrollToMemory(memoryId) {
|
|
|
169
182
|
scrollToMemoryInTable(memoryId);
|
|
170
183
|
}
|
|
171
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 = '▼';
|
|
192
|
+
return;
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
btn.innerHTML = '⌛';
|
|
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 = '▲';
|
|
229
|
+
} catch (err) {
|
|
230
|
+
btn.innerHTML = '▼';
|
|
231
|
+
console.error('Failed to load facts:', err);
|
|
232
|
+
}
|
|
233
|
+
}
|
|
234
|
+
|
|
172
235
|
function scrollToMemoryInTable(memoryId) {
|
|
173
236
|
const memId = String(memoryId);
|
|
174
237
|
|
package/ui/js/modal.js
CHANGED
|
@@ -6,8 +6,11 @@
|
|
|
6
6
|
|
|
7
7
|
var currentMemoryDetail = null;
|
|
8
8
|
|
|
9
|
-
function openMemoryDetail(mem) {
|
|
9
|
+
function openMemoryDetail(mem, source) {
|
|
10
|
+
// source: 'graph', 'recall', 'memories', or undefined
|
|
10
11
|
currentMemoryDetail = mem;
|
|
12
|
+
var fromGraph = source === 'graph';
|
|
13
|
+
var fromRecall = source === 'recall';
|
|
11
14
|
var body = document.getElementById('memory-detail-body');
|
|
12
15
|
if (!mem) {
|
|
13
16
|
body.textContent = 'No memory data';
|
|
@@ -62,55 +65,89 @@ function openMemoryDetail(mem) {
|
|
|
62
65
|
|
|
63
66
|
body.appendChild(dl);
|
|
64
67
|
|
|
65
|
-
//
|
|
66
|
-
if (mem.
|
|
68
|
+
// Context-aware action buttons
|
|
69
|
+
if (mem.id) {
|
|
67
70
|
body.appendChild(document.createElement('hr'));
|
|
68
71
|
|
|
69
72
|
var actionsDiv = document.createElement('div');
|
|
70
73
|
actionsDiv.className = 'memory-detail-graph-actions';
|
|
71
74
|
actionsDiv.style.cssText = 'display:flex; gap:10px; flex-wrap:wrap;';
|
|
72
75
|
|
|
73
|
-
//
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
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
|
+
}
|
|
112
135
|
|
|
113
|
-
//
|
|
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
|
|
114
151
|
if (mem.cluster_id) {
|
|
115
152
|
var filterBtn = document.createElement('button');
|
|
116
153
|
filterBtn.className = 'btn btn-outline-info btn-sm';
|
|
@@ -139,6 +176,63 @@ function openMemoryDetail(mem) {
|
|
|
139
176
|
actionsDiv.appendChild(filterBtn);
|
|
140
177
|
}
|
|
141
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
|
+
|
|
142
236
|
body.appendChild(actionsDiv);
|
|
143
237
|
}
|
|
144
238
|
|
package/ui/js/recall-lab.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
// SuperLocalMemory V3 — Recall Lab with Pagination
|
|
1
|
+
// SuperLocalMemory V3 — Recall Lab with Dual-Display + Pagination
|
|
2
2
|
// Part of Qualixar | https://superlocalmemory.com
|
|
3
3
|
|
|
4
4
|
var recallLabState = {
|
|
@@ -6,6 +6,7 @@ var recallLabState = {
|
|
|
6
6
|
page: 0,
|
|
7
7
|
perPage: 10,
|
|
8
8
|
query: '',
|
|
9
|
+
synthesis: '',
|
|
9
10
|
};
|
|
10
11
|
|
|
11
12
|
document.getElementById('recall-lab-search')?.addEventListener('click', function() {
|
|
@@ -16,13 +17,13 @@ document.getElementById('recall-lab-search')?.addEventListener('click', function
|
|
|
16
17
|
recallLabState.page = 0;
|
|
17
18
|
var perPageEl = document.getElementById('recall-lab-per-page');
|
|
18
19
|
recallLabState.perPage = perPageEl ? parseInt(perPageEl.value) : 10;
|
|
19
|
-
var fetchLimit = Math.max(recallLabState.perPage * 5, 50);
|
|
20
|
+
var fetchLimit = Math.max(recallLabState.perPage * 5, 50);
|
|
20
21
|
|
|
21
22
|
var resultsDiv = document.getElementById('recall-lab-results');
|
|
22
23
|
var metaDiv = document.getElementById('recall-lab-meta');
|
|
23
24
|
resultsDiv.textContent = '';
|
|
24
25
|
var spinner = document.createElement('div');
|
|
25
|
-
spinner.className = 'text-center';
|
|
26
|
+
spinner.className = 'text-center py-4';
|
|
26
27
|
var spinnerInner = document.createElement('div');
|
|
27
28
|
spinnerInner.className = 'spinner-border text-primary';
|
|
28
29
|
spinner.appendChild(spinnerInner);
|
|
@@ -31,7 +32,7 @@ document.getElementById('recall-lab-search')?.addEventListener('click', function
|
|
|
31
32
|
fetch('/api/v3/recall/trace', {
|
|
32
33
|
method: 'POST',
|
|
33
34
|
headers: {'Content-Type': 'application/json'},
|
|
34
|
-
body: JSON.stringify({query: query, limit: fetchLimit})
|
|
35
|
+
body: JSON.stringify({query: query, limit: fetchLimit, synthesize: true})
|
|
35
36
|
}).then(function(r) { return r.json(); }).then(function(data) {
|
|
36
37
|
if (data.error) {
|
|
37
38
|
resultsDiv.textContent = '';
|
|
@@ -50,6 +51,7 @@ document.getElementById('recall-lab-search')?.addEventListener('click', function
|
|
|
50
51
|
appendMetaField(metaDiv, 'Time: ', (data.retrieval_time_ms || 0).toFixed(0) + 'ms');
|
|
51
52
|
|
|
52
53
|
recallLabState.allResults = data.results || [];
|
|
54
|
+
recallLabState.synthesis = data.synthesis || '';
|
|
53
55
|
|
|
54
56
|
if (recallLabState.allResults.length === 0) {
|
|
55
57
|
resultsDiv.textContent = '';
|
|
@@ -80,6 +82,20 @@ function renderRecallPage() {
|
|
|
80
82
|
var pageResults = results.slice(start, end);
|
|
81
83
|
var totalPages = Math.ceil(results.length / recallLabState.perPage);
|
|
82
84
|
|
|
85
|
+
// Synthesis banner (Mode B/C only)
|
|
86
|
+
if (recallLabState.synthesis && recallLabState.page === 0) {
|
|
87
|
+
var synBanner = document.createElement('div');
|
|
88
|
+
synBanner.className = 'alert alert-light border-start border-4 border-primary mb-3';
|
|
89
|
+
var synTitle = document.createElement('strong');
|
|
90
|
+
synTitle.textContent = 'AI Summary';
|
|
91
|
+
synBanner.appendChild(synTitle);
|
|
92
|
+
synBanner.appendChild(document.createElement('br'));
|
|
93
|
+
var synText = document.createElement('span');
|
|
94
|
+
synText.textContent = recallLabState.synthesis;
|
|
95
|
+
synBanner.appendChild(synText);
|
|
96
|
+
resultsDiv.appendChild(synBanner);
|
|
97
|
+
}
|
|
98
|
+
|
|
83
99
|
var listGroup = document.createElement('div');
|
|
84
100
|
listGroup.className = 'list-group';
|
|
85
101
|
|
|
@@ -87,60 +103,108 @@ function renderRecallPage() {
|
|
|
87
103
|
var globalIndex = start + i;
|
|
88
104
|
var channels = r.channel_scores || {};
|
|
89
105
|
var maxChannel = Math.max(channels.semantic || 0, channels.bm25 || 0, channels.entity_graph || 0, channels.temporal || 0) || 1;
|
|
106
|
+
var hasSource = r.source_content && r.source_content.length > 0;
|
|
107
|
+
var displayText = hasSource ? r.source_content : r.content;
|
|
90
108
|
|
|
91
109
|
var item = document.createElement('div');
|
|
92
|
-
item.className = 'list-group-item
|
|
110
|
+
item.className = 'list-group-item';
|
|
111
|
+
|
|
112
|
+
// Score badge row
|
|
113
|
+
var scoreRow = document.createElement('div');
|
|
114
|
+
scoreRow.className = 'd-flex justify-content-between align-items-center mb-1';
|
|
115
|
+
var numLabel = document.createElement('strong');
|
|
116
|
+
numLabel.textContent = '#' + (globalIndex + 1);
|
|
117
|
+
scoreRow.appendChild(numLabel);
|
|
118
|
+
var scoreBadges = document.createElement('div');
|
|
119
|
+
scoreBadges.innerHTML = '<span class="badge bg-primary me-1">Score: ' + r.score + '</span>' +
|
|
120
|
+
'<span class="badge bg-secondary me-1">Trust: ' + r.trust_score + '</span>' +
|
|
121
|
+
'<span class="badge bg-outline-info" style="border:1px solid #0dcaf0;color:#0dcaf0;">Conf: ' + r.confidence + '</span>';
|
|
122
|
+
scoreRow.appendChild(scoreBadges);
|
|
123
|
+
item.appendChild(scoreRow);
|
|
124
|
+
|
|
125
|
+
// Original memory text (primary display)
|
|
126
|
+
var contentDiv = document.createElement('div');
|
|
127
|
+
contentDiv.className = 'mb-2';
|
|
128
|
+
contentDiv.style.cssText = 'white-space:pre-wrap; line-height:1.5;';
|
|
129
|
+
var truncated = displayText.length > 500 ? displayText.substring(0, 500) + '...' : displayText;
|
|
130
|
+
contentDiv.textContent = truncated;
|
|
131
|
+
item.appendChild(contentDiv);
|
|
132
|
+
|
|
133
|
+
// Expandable atomic fact section (only if source differs from content)
|
|
134
|
+
if (hasSource && r.content !== r.source_content) {
|
|
135
|
+
var expandBtn = document.createElement('button');
|
|
136
|
+
expandBtn.className = 'btn btn-sm btn-outline-secondary mb-2';
|
|
137
|
+
expandBtn.textContent = 'Show matched fact + channels';
|
|
138
|
+
var factSection = document.createElement('div');
|
|
139
|
+
factSection.style.display = 'none';
|
|
140
|
+
factSection.className = 'border-top pt-2 mt-1';
|
|
141
|
+
|
|
142
|
+
var factLabel = document.createElement('small');
|
|
143
|
+
factLabel.className = 'text-muted d-block mb-1';
|
|
144
|
+
factLabel.textContent = 'Matched atomic fact:';
|
|
145
|
+
factSection.appendChild(factLabel);
|
|
146
|
+
|
|
147
|
+
var factContent = document.createElement('div');
|
|
148
|
+
factContent.className = 'small bg-light p-2 rounded mb-2';
|
|
149
|
+
factContent.textContent = r.content;
|
|
150
|
+
factSection.appendChild(factContent);
|
|
151
|
+
|
|
152
|
+
// Channel bars
|
|
153
|
+
factSection.appendChild(buildChannelBar('Semantic', channels.semantic || 0, maxChannel, 'primary'));
|
|
154
|
+
factSection.appendChild(buildChannelBar('BM25', channels.bm25 || 0, maxChannel, 'success'));
|
|
155
|
+
factSection.appendChild(buildChannelBar('Entity', channels.entity_graph || 0, maxChannel, 'info'));
|
|
156
|
+
factSection.appendChild(buildChannelBar('Temporal', channels.temporal || 0, maxChannel, 'warning'));
|
|
157
|
+
|
|
158
|
+
expandBtn.addEventListener('click', function() {
|
|
159
|
+
var visible = factSection.style.display !== 'none';
|
|
160
|
+
factSection.style.display = visible ? 'none' : 'block';
|
|
161
|
+
expandBtn.textContent = visible ? 'Show matched fact + channels' : 'Hide matched fact';
|
|
162
|
+
});
|
|
163
|
+
|
|
164
|
+
item.appendChild(expandBtn);
|
|
165
|
+
item.appendChild(factSection);
|
|
166
|
+
} else {
|
|
167
|
+
// No source_content — show channel bars inline
|
|
168
|
+
var barsDiv = document.createElement('div');
|
|
169
|
+
barsDiv.className = 'mt-1';
|
|
170
|
+
barsDiv.appendChild(buildChannelBar('Semantic', channels.semantic || 0, maxChannel, 'primary'));
|
|
171
|
+
barsDiv.appendChild(buildChannelBar('BM25', channels.bm25 || 0, maxChannel, 'success'));
|
|
172
|
+
barsDiv.appendChild(buildChannelBar('Entity', channels.entity_graph || 0, maxChannel, 'info'));
|
|
173
|
+
barsDiv.appendChild(buildChannelBar('Temporal', channels.temporal || 0, maxChannel, 'warning'));
|
|
174
|
+
item.appendChild(barsDiv);
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
// Click for detail modal
|
|
93
178
|
item.style.cursor = 'pointer';
|
|
94
|
-
item.title = 'Click to view full memory';
|
|
95
179
|
(function(result) {
|
|
96
|
-
|
|
180
|
+
contentDiv.addEventListener('click', function() {
|
|
97
181
|
if (typeof openMemoryDetail === 'function') {
|
|
98
182
|
openMemoryDetail({
|
|
99
183
|
id: result.fact_id,
|
|
100
|
-
|
|
184
|
+
memory_id: result.memory_id,
|
|
185
|
+
content: result.source_content || result.content,
|
|
101
186
|
score: result.score,
|
|
102
187
|
importance: Math.round((result.confidence || 0.5) * 10),
|
|
103
188
|
category: 'recall',
|
|
104
|
-
tags: Object.keys(result.channel_scores || {}).join(', '),
|
|
105
189
|
created_at: null,
|
|
106
190
|
trust_score: result.trust_score,
|
|
107
191
|
channel_scores: result.channel_scores
|
|
108
|
-
});
|
|
192
|
+
}, 'recall'); // source='recall': show View Original, hide Expand Neighbors
|
|
109
193
|
}
|
|
110
194
|
});
|
|
111
195
|
})(r);
|
|
112
196
|
|
|
113
|
-
var header = document.createElement('h6');
|
|
114
|
-
header.className = 'mb-1';
|
|
115
|
-
header.textContent = (globalIndex + 1) + '. ' + (r.content || '').substring(0, 200);
|
|
116
|
-
item.appendChild(header);
|
|
117
|
-
|
|
118
|
-
var meta = document.createElement('small');
|
|
119
|
-
meta.className = 'text-muted';
|
|
120
|
-
meta.textContent = 'Score: ' + r.score + ' | Trust: ' + r.trust_score + ' | Confidence: ' + r.confidence;
|
|
121
|
-
item.appendChild(meta);
|
|
122
|
-
|
|
123
|
-
var barsDiv = document.createElement('div');
|
|
124
|
-
barsDiv.className = 'mt-2';
|
|
125
|
-
barsDiv.appendChild(buildChannelBar('Semantic', channels.semantic || 0, maxChannel, 'primary'));
|
|
126
|
-
barsDiv.appendChild(buildChannelBar('BM25', channels.bm25 || 0, maxChannel, 'success'));
|
|
127
|
-
barsDiv.appendChild(buildChannelBar('Entity', channels.entity_graph || 0, maxChannel, 'info'));
|
|
128
|
-
barsDiv.appendChild(buildChannelBar('Temporal', channels.temporal || 0, maxChannel, 'warning'));
|
|
129
|
-
item.appendChild(barsDiv);
|
|
130
|
-
|
|
131
197
|
listGroup.appendChild(item);
|
|
132
198
|
});
|
|
133
199
|
resultsDiv.appendChild(listGroup);
|
|
134
200
|
|
|
135
|
-
// Pagination
|
|
201
|
+
// Pagination
|
|
136
202
|
if (totalPages > 1) {
|
|
137
203
|
var nav = document.createElement('nav');
|
|
138
204
|
nav.className = 'mt-3';
|
|
139
|
-
nav.setAttribute('aria-label', 'Recall results pagination');
|
|
140
205
|
var ul = document.createElement('ul');
|
|
141
206
|
ul.className = 'pagination justify-content-center';
|
|
142
207
|
|
|
143
|
-
// Prev
|
|
144
208
|
var prevLi = document.createElement('li');
|
|
145
209
|
prevLi.className = 'page-item' + (recallLabState.page === 0 ? ' disabled' : '');
|
|
146
210
|
var prevA = document.createElement('a');
|
|
@@ -149,15 +213,11 @@ function renderRecallPage() {
|
|
|
149
213
|
prevA.textContent = 'Previous';
|
|
150
214
|
prevA.addEventListener('click', function(e) {
|
|
151
215
|
e.preventDefault();
|
|
152
|
-
if (recallLabState.page > 0) {
|
|
153
|
-
recallLabState.page--;
|
|
154
|
-
renderRecallPage();
|
|
155
|
-
}
|
|
216
|
+
if (recallLabState.page > 0) { recallLabState.page--; renderRecallPage(); }
|
|
156
217
|
});
|
|
157
218
|
prevLi.appendChild(prevA);
|
|
158
219
|
ul.appendChild(prevLi);
|
|
159
220
|
|
|
160
|
-
// Page numbers
|
|
161
221
|
for (var p = 0; p < totalPages; p++) {
|
|
162
222
|
var li = document.createElement('li');
|
|
163
223
|
li.className = 'page-item' + (p === recallLabState.page ? ' active' : '');
|
|
@@ -176,7 +236,6 @@ function renderRecallPage() {
|
|
|
176
236
|
ul.appendChild(li);
|
|
177
237
|
}
|
|
178
238
|
|
|
179
|
-
// Next
|
|
180
239
|
var nextLi = document.createElement('li');
|
|
181
240
|
nextLi.className = 'page-item' + (recallLabState.page >= totalPages - 1 ? ' disabled' : '');
|
|
182
241
|
var nextA = document.createElement('a');
|
|
@@ -185,18 +244,13 @@ function renderRecallPage() {
|
|
|
185
244
|
nextA.textContent = 'Next';
|
|
186
245
|
nextA.addEventListener('click', function(e) {
|
|
187
246
|
e.preventDefault();
|
|
188
|
-
if (recallLabState.page < totalPages - 1) {
|
|
189
|
-
recallLabState.page++;
|
|
190
|
-
renderRecallPage();
|
|
191
|
-
}
|
|
247
|
+
if (recallLabState.page < totalPages - 1) { recallLabState.page++; renderRecallPage(); }
|
|
192
248
|
});
|
|
193
249
|
nextLi.appendChild(nextA);
|
|
194
250
|
ul.appendChild(nextLi);
|
|
195
|
-
|
|
196
251
|
nav.appendChild(ul);
|
|
197
252
|
resultsDiv.appendChild(nav);
|
|
198
253
|
|
|
199
|
-
// Page info
|
|
200
254
|
var info = document.createElement('div');
|
|
201
255
|
info.className = 'text-center text-muted small';
|
|
202
256
|
info.textContent = 'Showing ' + (start + 1) + '-' + end + ' of ' + results.length + ' results';
|
|
@@ -205,8 +259,7 @@ function renderRecallPage() {
|
|
|
205
259
|
}
|
|
206
260
|
|
|
207
261
|
function appendMetaField(parent, label, value) {
|
|
208
|
-
|
|
209
|
-
parent.appendChild(text);
|
|
262
|
+
parent.appendChild(document.createTextNode(label));
|
|
210
263
|
var strong = document.createElement('strong');
|
|
211
264
|
strong.textContent = value;
|
|
212
265
|
parent.appendChild(strong);
|
|
@@ -234,7 +287,6 @@ function buildChannelBar(name, score, max, color) {
|
|
|
234
287
|
return row;
|
|
235
288
|
}
|
|
236
289
|
|
|
237
|
-
// Enter key support
|
|
238
290
|
document.getElementById('recall-lab-query')?.addEventListener('keydown', function(e) {
|
|
239
291
|
if (e.key === 'Enter') document.getElementById('recall-lab-search')?.click();
|
|
240
292
|
});
|