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/events.js
DELETED
|
@@ -1,178 +0,0 @@
|
|
|
1
|
-
// SuperLocalMemory V2 - Live Events (v2.5 — SSE Stream)
|
|
2
|
-
// Depends on: core.js
|
|
3
|
-
// Security: All DOM built with safe methods (createElement/textContent).
|
|
4
|
-
|
|
5
|
-
var _eventSource = null;
|
|
6
|
-
var _eventStreamItems = [];
|
|
7
|
-
var _maxEventStreamItems = 200;
|
|
8
|
-
|
|
9
|
-
function initEventStream() {
|
|
10
|
-
try {
|
|
11
|
-
_eventSource = new EventSource('/events/stream');
|
|
12
|
-
|
|
13
|
-
_eventSource.onopen = function() {
|
|
14
|
-
var badge = document.getElementById('event-connection-status');
|
|
15
|
-
if (badge) {
|
|
16
|
-
badge.textContent = 'Connected';
|
|
17
|
-
badge.className = 'badge bg-success me-2';
|
|
18
|
-
}
|
|
19
|
-
};
|
|
20
|
-
|
|
21
|
-
_eventSource.onmessage = function(e) {
|
|
22
|
-
try {
|
|
23
|
-
appendEventToStream(JSON.parse(e.data));
|
|
24
|
-
} catch (err) { /* keepalive comments */ }
|
|
25
|
-
};
|
|
26
|
-
|
|
27
|
-
_eventSource.onerror = function() {
|
|
28
|
-
var badge = document.getElementById('event-connection-status');
|
|
29
|
-
if (badge) {
|
|
30
|
-
badge.textContent = 'Reconnecting...';
|
|
31
|
-
badge.className = 'badge bg-warning me-2';
|
|
32
|
-
}
|
|
33
|
-
};
|
|
34
|
-
|
|
35
|
-
['memory.created', 'memory.updated', 'memory.deleted', 'memory.recalled',
|
|
36
|
-
'agent.connected', 'agent.disconnected', 'graph.updated', 'pattern.learned'
|
|
37
|
-
].forEach(function(type) {
|
|
38
|
-
_eventSource.addEventListener(type, function(e) {
|
|
39
|
-
try { appendEventToStream(JSON.parse(e.data)); } catch (err) { /* ignore */ }
|
|
40
|
-
});
|
|
41
|
-
});
|
|
42
|
-
} catch (err) {
|
|
43
|
-
console.log('SSE not available:', err);
|
|
44
|
-
var badge = document.getElementById('event-connection-status');
|
|
45
|
-
if (badge) {
|
|
46
|
-
badge.textContent = 'Unavailable';
|
|
47
|
-
badge.className = 'badge bg-secondary me-2';
|
|
48
|
-
}
|
|
49
|
-
}
|
|
50
|
-
}
|
|
51
|
-
|
|
52
|
-
function appendEventToStream(event) {
|
|
53
|
-
var container = document.getElementById('event-stream');
|
|
54
|
-
if (!container) return;
|
|
55
|
-
|
|
56
|
-
if (_eventStreamItems.length === 0) container.textContent = '';
|
|
57
|
-
|
|
58
|
-
_eventStreamItems.push(event);
|
|
59
|
-
if (_eventStreamItems.length > _maxEventStreamItems) _eventStreamItems.shift();
|
|
60
|
-
|
|
61
|
-
var filter = document.getElementById('event-type-filter');
|
|
62
|
-
var filterValue = filter ? filter.value : '';
|
|
63
|
-
if (filterValue && event.event_type !== filterValue) return;
|
|
64
|
-
|
|
65
|
-
var typeColors = {
|
|
66
|
-
'memory.created': 'text-success', 'memory.updated': 'text-info',
|
|
67
|
-
'memory.deleted': 'text-danger', 'memory.recalled': 'text-primary',
|
|
68
|
-
'agent.connected': 'text-warning', 'agent.disconnected': 'text-secondary',
|
|
69
|
-
'graph.updated': 'text-info', 'pattern.learned': 'text-success'
|
|
70
|
-
};
|
|
71
|
-
var typeIcons = {
|
|
72
|
-
'memory.created': 'bi-plus-circle', 'memory.updated': 'bi-pencil',
|
|
73
|
-
'memory.deleted': 'bi-trash', 'memory.recalled': 'bi-search',
|
|
74
|
-
'agent.connected': 'bi-plug', 'agent.disconnected': 'bi-plug',
|
|
75
|
-
'graph.updated': 'bi-diagram-3', 'pattern.learned': 'bi-lightbulb'
|
|
76
|
-
};
|
|
77
|
-
|
|
78
|
-
var colorClass = typeColors[event.event_type] || 'text-muted';
|
|
79
|
-
var iconClass = typeIcons[event.event_type] || 'bi-circle';
|
|
80
|
-
var ts = event.timestamp ? new Date(event.timestamp).toLocaleTimeString() : '';
|
|
81
|
-
var payload = event.payload || {};
|
|
82
|
-
var preview = payload.content_preview || payload.agent_id || payload.agent_name || '';
|
|
83
|
-
if (preview.length > 80) preview = preview.substring(0, 80) + '...';
|
|
84
|
-
|
|
85
|
-
var div = document.createElement('div');
|
|
86
|
-
div.className = 'event-line mb-1 pb-1 border-bottom border-opacity-25';
|
|
87
|
-
|
|
88
|
-
var timeSpan = document.createElement('small');
|
|
89
|
-
timeSpan.className = 'text-muted';
|
|
90
|
-
timeSpan.textContent = ts;
|
|
91
|
-
|
|
92
|
-
var icon = document.createElement('i');
|
|
93
|
-
icon.className = 'bi ' + iconClass + ' ' + colorClass;
|
|
94
|
-
icon.style.marginLeft = '6px';
|
|
95
|
-
|
|
96
|
-
var typeSpan = document.createElement('span');
|
|
97
|
-
typeSpan.className = colorClass + ' fw-bold';
|
|
98
|
-
typeSpan.style.marginLeft = '4px';
|
|
99
|
-
typeSpan.textContent = event.event_type;
|
|
100
|
-
|
|
101
|
-
div.appendChild(timeSpan);
|
|
102
|
-
div.appendChild(document.createTextNode(' '));
|
|
103
|
-
div.appendChild(icon);
|
|
104
|
-
div.appendChild(document.createTextNode(' '));
|
|
105
|
-
div.appendChild(typeSpan);
|
|
106
|
-
div.appendChild(document.createTextNode(' '));
|
|
107
|
-
|
|
108
|
-
if (event.memory_id) {
|
|
109
|
-
var badge = document.createElement('span');
|
|
110
|
-
badge.className = 'badge bg-secondary';
|
|
111
|
-
badge.textContent = '#' + event.memory_id;
|
|
112
|
-
div.appendChild(badge);
|
|
113
|
-
div.appendChild(document.createTextNode(' '));
|
|
114
|
-
}
|
|
115
|
-
|
|
116
|
-
var previewSpan = document.createElement('span');
|
|
117
|
-
previewSpan.className = 'text-muted';
|
|
118
|
-
previewSpan.textContent = preview;
|
|
119
|
-
div.appendChild(previewSpan);
|
|
120
|
-
|
|
121
|
-
container.insertBefore(div, container.firstChild);
|
|
122
|
-
|
|
123
|
-
while (container.children.length > _maxEventStreamItems) {
|
|
124
|
-
container.removeChild(container.lastChild);
|
|
125
|
-
}
|
|
126
|
-
}
|
|
127
|
-
|
|
128
|
-
function filterEvents() {
|
|
129
|
-
var container = document.getElementById('event-stream');
|
|
130
|
-
if (!container) return;
|
|
131
|
-
container.textContent = '';
|
|
132
|
-
|
|
133
|
-
var filter = document.getElementById('event-type-filter');
|
|
134
|
-
var filterValue = filter ? filter.value : '';
|
|
135
|
-
|
|
136
|
-
var filtered = filterValue
|
|
137
|
-
? _eventStreamItems.filter(function(e) { return e.event_type === filterValue; })
|
|
138
|
-
: _eventStreamItems;
|
|
139
|
-
|
|
140
|
-
filtered.forEach(function(event) { appendEventToStream(event); });
|
|
141
|
-
}
|
|
142
|
-
|
|
143
|
-
function clearEventStream() {
|
|
144
|
-
_eventStreamItems = [];
|
|
145
|
-
var container = document.getElementById('event-stream');
|
|
146
|
-
if (container) {
|
|
147
|
-
container.textContent = '';
|
|
148
|
-
var placeholder = document.createElement('div');
|
|
149
|
-
placeholder.className = 'text-muted text-center py-4';
|
|
150
|
-
var pIcon = document.createElement('i');
|
|
151
|
-
pIcon.className = 'bi bi-broadcast';
|
|
152
|
-
pIcon.style.fontSize = '2rem';
|
|
153
|
-
placeholder.appendChild(pIcon);
|
|
154
|
-
var pText = document.createElement('p');
|
|
155
|
-
pText.className = 'mt-2';
|
|
156
|
-
pText.textContent = 'Event stream cleared. Waiting for new events...';
|
|
157
|
-
placeholder.appendChild(pText);
|
|
158
|
-
container.appendChild(placeholder);
|
|
159
|
-
}
|
|
160
|
-
}
|
|
161
|
-
|
|
162
|
-
async function loadEventStats() {
|
|
163
|
-
try {
|
|
164
|
-
var response = await fetch('/api/events/stats');
|
|
165
|
-
var stats = await response.json();
|
|
166
|
-
var el;
|
|
167
|
-
el = document.getElementById('event-stat-total');
|
|
168
|
-
if (el) el.textContent = (stats.total_events || 0).toLocaleString();
|
|
169
|
-
el = document.getElementById('event-stat-24h');
|
|
170
|
-
if (el) el.textContent = (stats.events_last_24h || 0).toLocaleString();
|
|
171
|
-
el = document.getElementById('event-stat-listeners');
|
|
172
|
-
if (el) el.textContent = (stats.listener_count || 0).toLocaleString();
|
|
173
|
-
el = document.getElementById('event-stat-buffer');
|
|
174
|
-
if (el) el.textContent = (stats.buffer_size || 0).toLocaleString();
|
|
175
|
-
} catch (err) {
|
|
176
|
-
console.log('Event stats not available:', err);
|
|
177
|
-
}
|
|
178
|
-
}
|
package/ui/js/fact-detail.js
DELETED
|
@@ -1,92 +0,0 @@
|
|
|
1
|
-
// SuperLocalMemory V3 — Fact Detail View
|
|
2
|
-
// Adds click-to-expand on memory list items to show channel scores and trust data.
|
|
3
|
-
|
|
4
|
-
document.addEventListener('click', function(e) {
|
|
5
|
-
var item = e.target.closest('[data-fact-id]');
|
|
6
|
-
if (!item) return;
|
|
7
|
-
|
|
8
|
-
// Toggle: if detail panel already exists, remove it
|
|
9
|
-
var existingDetail = item.querySelector('.fact-detail-panel');
|
|
10
|
-
if (existingDetail) {
|
|
11
|
-
existingDetail.remove();
|
|
12
|
-
return;
|
|
13
|
-
}
|
|
14
|
-
|
|
15
|
-
// Extract query text from the item (first 100 chars)
|
|
16
|
-
var queryText = (item.textContent || '').substring(0, 100).trim();
|
|
17
|
-
if (!queryText) return;
|
|
18
|
-
|
|
19
|
-
fetch('/api/v3/recall/trace', {
|
|
20
|
-
method: 'POST',
|
|
21
|
-
headers: { 'Content-Type': 'application/json' },
|
|
22
|
-
body: JSON.stringify({ query: queryText, limit: 1 })
|
|
23
|
-
}).then(function(r) {
|
|
24
|
-
return r.json();
|
|
25
|
-
}).then(function(data) {
|
|
26
|
-
var result = (data.results || [])[0];
|
|
27
|
-
if (!result) return;
|
|
28
|
-
|
|
29
|
-
var panel = document.createElement('div');
|
|
30
|
-
panel.className = 'fact-detail-panel card mt-2 mb-2 border-info';
|
|
31
|
-
|
|
32
|
-
var cardBody = document.createElement('div');
|
|
33
|
-
cardBody.className = 'card-body small';
|
|
34
|
-
|
|
35
|
-
// Score / Trust / Confidence row
|
|
36
|
-
var row1 = document.createElement('div');
|
|
37
|
-
row1.className = 'row';
|
|
38
|
-
|
|
39
|
-
var col1 = document.createElement('div');
|
|
40
|
-
col1.className = 'col-md-4';
|
|
41
|
-
var col1Label = document.createElement('strong');
|
|
42
|
-
col1Label.textContent = 'Score: ';
|
|
43
|
-
col1.appendChild(col1Label);
|
|
44
|
-
col1.appendChild(document.createTextNode(result.score || 0));
|
|
45
|
-
row1.appendChild(col1);
|
|
46
|
-
|
|
47
|
-
var col2 = document.createElement('div');
|
|
48
|
-
col2.className = 'col-md-4';
|
|
49
|
-
var col2Label = document.createElement('strong');
|
|
50
|
-
col2Label.textContent = 'Trust: ';
|
|
51
|
-
col2.appendChild(col2Label);
|
|
52
|
-
col2.appendChild(document.createTextNode(result.trust_score || 0));
|
|
53
|
-
row1.appendChild(col2);
|
|
54
|
-
|
|
55
|
-
var col3 = document.createElement('div');
|
|
56
|
-
col3.className = 'col-md-4';
|
|
57
|
-
var col3Label = document.createElement('strong');
|
|
58
|
-
col3Label.textContent = 'Confidence: ';
|
|
59
|
-
col3.appendChild(col3Label);
|
|
60
|
-
col3.appendChild(document.createTextNode(result.confidence || 0));
|
|
61
|
-
row1.appendChild(col3);
|
|
62
|
-
|
|
63
|
-
cardBody.appendChild(row1);
|
|
64
|
-
|
|
65
|
-
// Channel scores section
|
|
66
|
-
var channels = result.channel_scores || {};
|
|
67
|
-
var channelKeys = Object.keys(channels);
|
|
68
|
-
if (channelKeys.length > 0) {
|
|
69
|
-
var label = document.createElement('div');
|
|
70
|
-
label.className = 'mt-2';
|
|
71
|
-
var labelStrong = document.createElement('strong');
|
|
72
|
-
labelStrong.textContent = 'Channel Scores:';
|
|
73
|
-
label.appendChild(labelStrong);
|
|
74
|
-
cardBody.appendChild(label);
|
|
75
|
-
|
|
76
|
-
var row2 = document.createElement('div');
|
|
77
|
-
row2.className = 'row text-muted';
|
|
78
|
-
channelKeys.forEach(function(chKey) {
|
|
79
|
-
var chCol = document.createElement('div');
|
|
80
|
-
chCol.className = 'col-md-3';
|
|
81
|
-
chCol.textContent = chKey + ': ' + channels[chKey];
|
|
82
|
-
row2.appendChild(chCol);
|
|
83
|
-
});
|
|
84
|
-
cardBody.appendChild(row2);
|
|
85
|
-
}
|
|
86
|
-
|
|
87
|
-
panel.appendChild(cardBody);
|
|
88
|
-
item.appendChild(panel);
|
|
89
|
-
}).catch(function(e) {
|
|
90
|
-
console.log('Fact detail error:', e);
|
|
91
|
-
});
|
|
92
|
-
});
|
package/ui/js/feedback.js
DELETED
|
@@ -1,333 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* SuperLocalMemory V2 - Feedback Module (v2.7.4)
|
|
3
|
-
* Copyright (c) 2026 Varun Pratap Bhardwaj
|
|
4
|
-
* Licensed under Elastic License 2.0
|
|
5
|
-
*
|
|
6
|
-
* Collects implicit and explicit feedback signals from dashboard
|
|
7
|
-
* interactions. All data stays 100% local.
|
|
8
|
-
*
|
|
9
|
-
* Signals:
|
|
10
|
-
* thumbs_up - User clicks thumbs up on a memory card
|
|
11
|
-
* thumbs_down - User clicks thumbs down on a memory card
|
|
12
|
-
* pin - User pins/bookmarks a memory
|
|
13
|
-
* dwell_time - Time spent viewing memory detail modal
|
|
14
|
-
* search_click - User clicks a search result (positive for clicked)
|
|
15
|
-
*/
|
|
16
|
-
|
|
17
|
-
// Module state
|
|
18
|
-
var feedbackState = {
|
|
19
|
-
lastSearchQuery: '',
|
|
20
|
-
modalOpenTime: null,
|
|
21
|
-
modalMemoryId: null,
|
|
22
|
-
searchResultIds: [],
|
|
23
|
-
};
|
|
24
|
-
|
|
25
|
-
/**
|
|
26
|
-
* Record explicit feedback (thumbs up/down, pin).
|
|
27
|
-
* Sends POST /api/feedback to backend.
|
|
28
|
-
*/
|
|
29
|
-
function recordFeedback(memoryId, feedbackType, query) {
|
|
30
|
-
if (!memoryId || !feedbackType) return;
|
|
31
|
-
|
|
32
|
-
fetch('/api/feedback', {
|
|
33
|
-
method: 'POST',
|
|
34
|
-
headers: {'Content-Type': 'application/json'},
|
|
35
|
-
body: JSON.stringify({
|
|
36
|
-
memory_id: memoryId,
|
|
37
|
-
feedback_type: feedbackType,
|
|
38
|
-
query: query || feedbackState.lastSearchQuery || '',
|
|
39
|
-
}),
|
|
40
|
-
})
|
|
41
|
-
.then(function(r) { return r.json(); })
|
|
42
|
-
.then(function(data) {
|
|
43
|
-
if (data.success) {
|
|
44
|
-
showFeedbackToast(feedbackType, memoryId);
|
|
45
|
-
refreshFeedbackStats();
|
|
46
|
-
}
|
|
47
|
-
})
|
|
48
|
-
.catch(function() {
|
|
49
|
-
// Silent failure — feedback should never break UI
|
|
50
|
-
});
|
|
51
|
-
}
|
|
52
|
-
|
|
53
|
-
/**
|
|
54
|
-
* Start tracking dwell time when a memory detail modal opens.
|
|
55
|
-
*/
|
|
56
|
-
function startDwellTracking(memoryId) {
|
|
57
|
-
feedbackState.modalOpenTime = Date.now();
|
|
58
|
-
feedbackState.modalMemoryId = memoryId;
|
|
59
|
-
}
|
|
60
|
-
|
|
61
|
-
/**
|
|
62
|
-
* Stop tracking and record dwell time when modal closes.
|
|
63
|
-
*/
|
|
64
|
-
function stopDwellTracking() {
|
|
65
|
-
if (!feedbackState.modalOpenTime || !feedbackState.modalMemoryId) return;
|
|
66
|
-
|
|
67
|
-
var dwellMs = Date.now() - feedbackState.modalOpenTime;
|
|
68
|
-
var dwellSeconds = dwellMs / 1000;
|
|
69
|
-
var memId = feedbackState.modalMemoryId;
|
|
70
|
-
|
|
71
|
-
feedbackState.modalOpenTime = null;
|
|
72
|
-
feedbackState.modalMemoryId = null;
|
|
73
|
-
|
|
74
|
-
// Only record if meaningful (>1s)
|
|
75
|
-
if (dwellSeconds < 1.0) return;
|
|
76
|
-
|
|
77
|
-
fetch('/api/feedback/dwell', {
|
|
78
|
-
method: 'POST',
|
|
79
|
-
headers: {'Content-Type': 'application/json'},
|
|
80
|
-
body: JSON.stringify({
|
|
81
|
-
memory_id: memId,
|
|
82
|
-
dwell_time: dwellSeconds,
|
|
83
|
-
query: feedbackState.lastSearchQuery || '',
|
|
84
|
-
}),
|
|
85
|
-
})
|
|
86
|
-
.then(function(r) { return r.json(); })
|
|
87
|
-
.catch(function() {
|
|
88
|
-
// Silent failure
|
|
89
|
-
});
|
|
90
|
-
}
|
|
91
|
-
|
|
92
|
-
/**
|
|
93
|
-
* Track which search results are displayed.
|
|
94
|
-
*/
|
|
95
|
-
function trackSearchResults(query, resultIds) {
|
|
96
|
-
feedbackState.lastSearchQuery = query;
|
|
97
|
-
feedbackState.searchResultIds = resultIds || [];
|
|
98
|
-
}
|
|
99
|
-
|
|
100
|
-
/**
|
|
101
|
-
* Create feedback buttons (thumbs up/down + pin) for a memory card.
|
|
102
|
-
* Returns a DOM element containing the buttons.
|
|
103
|
-
* Uses safe DOM methods (no innerHTML with user data).
|
|
104
|
-
*/
|
|
105
|
-
function createFeedbackButtons(memoryId, query) {
|
|
106
|
-
var container = document.createElement('div');
|
|
107
|
-
container.className = 'feedback-buttons d-flex gap-1 align-items-center';
|
|
108
|
-
container.setAttribute('role', 'group');
|
|
109
|
-
container.setAttribute('aria-label', 'Feedback for memory ' + memoryId);
|
|
110
|
-
|
|
111
|
-
// Thumbs up
|
|
112
|
-
var upBtn = document.createElement('button');
|
|
113
|
-
upBtn.className = 'btn btn-outline-success btn-sm feedback-btn';
|
|
114
|
-
var upIcon = document.createElement('i');
|
|
115
|
-
upIcon.className = 'bi bi-hand-thumbs-up';
|
|
116
|
-
upBtn.appendChild(upIcon);
|
|
117
|
-
upBtn.title = 'This memory was useful';
|
|
118
|
-
upBtn.setAttribute('aria-label', 'Mark as useful');
|
|
119
|
-
upBtn.onclick = function(e) {
|
|
120
|
-
e.stopPropagation();
|
|
121
|
-
recordFeedback(memoryId, 'thumbs_up', query);
|
|
122
|
-
upBtn.classList.remove('btn-outline-success');
|
|
123
|
-
upBtn.classList.add('btn-success');
|
|
124
|
-
upBtn.disabled = true;
|
|
125
|
-
downBtn.disabled = true;
|
|
126
|
-
};
|
|
127
|
-
|
|
128
|
-
// Thumbs down
|
|
129
|
-
var downBtn = document.createElement('button');
|
|
130
|
-
downBtn.className = 'btn btn-outline-danger btn-sm feedback-btn';
|
|
131
|
-
var downIcon = document.createElement('i');
|
|
132
|
-
downIcon.className = 'bi bi-hand-thumbs-down';
|
|
133
|
-
downBtn.appendChild(downIcon);
|
|
134
|
-
downBtn.title = 'Not useful';
|
|
135
|
-
downBtn.setAttribute('aria-label', 'Mark as not useful');
|
|
136
|
-
downBtn.onclick = function(e) {
|
|
137
|
-
e.stopPropagation();
|
|
138
|
-
recordFeedback(memoryId, 'thumbs_down', query);
|
|
139
|
-
downBtn.classList.remove('btn-outline-danger');
|
|
140
|
-
downBtn.classList.add('btn-danger');
|
|
141
|
-
upBtn.disabled = true;
|
|
142
|
-
downBtn.disabled = true;
|
|
143
|
-
};
|
|
144
|
-
|
|
145
|
-
// Pin/Bookmark
|
|
146
|
-
var pinBtn = document.createElement('button');
|
|
147
|
-
pinBtn.className = 'btn btn-outline-warning btn-sm feedback-btn';
|
|
148
|
-
var pinIcon = document.createElement('i');
|
|
149
|
-
pinIcon.className = 'bi bi-pin-angle';
|
|
150
|
-
pinBtn.appendChild(pinIcon);
|
|
151
|
-
pinBtn.title = 'Pin this memory';
|
|
152
|
-
pinBtn.setAttribute('aria-label', 'Pin memory');
|
|
153
|
-
pinBtn.onclick = function(e) {
|
|
154
|
-
e.stopPropagation();
|
|
155
|
-
recordFeedback(memoryId, 'pin', query);
|
|
156
|
-
pinBtn.classList.remove('btn-outline-warning');
|
|
157
|
-
pinBtn.classList.add('btn-warning');
|
|
158
|
-
pinBtn.disabled = true;
|
|
159
|
-
};
|
|
160
|
-
|
|
161
|
-
container.appendChild(upBtn);
|
|
162
|
-
container.appendChild(downBtn);
|
|
163
|
-
container.appendChild(pinBtn);
|
|
164
|
-
return container;
|
|
165
|
-
}
|
|
166
|
-
|
|
167
|
-
/**
|
|
168
|
-
* Show a small toast notification after feedback.
|
|
169
|
-
*/
|
|
170
|
-
function showFeedbackToast(type, memoryId) {
|
|
171
|
-
var messages = {
|
|
172
|
-
thumbs_up: 'Thanks! This helps improve future results.',
|
|
173
|
-
thumbs_down: 'Noted. Results will improve over time.',
|
|
174
|
-
pin: 'Memory pinned!',
|
|
175
|
-
};
|
|
176
|
-
var msg = messages[type] || 'Feedback recorded';
|
|
177
|
-
|
|
178
|
-
// Use existing showToast if available
|
|
179
|
-
if (typeof showToast === 'function') {
|
|
180
|
-
showToast(msg, 'success');
|
|
181
|
-
}
|
|
182
|
-
}
|
|
183
|
-
|
|
184
|
-
/**
|
|
185
|
-
* Fetch and render feedback stats (progress bar, signal count).
|
|
186
|
-
*/
|
|
187
|
-
function refreshFeedbackStats() {
|
|
188
|
-
fetch('/api/feedback/stats')
|
|
189
|
-
.then(function(r) { return r.json(); })
|
|
190
|
-
.then(function(data) {
|
|
191
|
-
renderFeedbackProgress(data);
|
|
192
|
-
})
|
|
193
|
-
.catch(function() {});
|
|
194
|
-
}
|
|
195
|
-
|
|
196
|
-
/**
|
|
197
|
-
* Render the feedback progress bar using safe DOM methods.
|
|
198
|
-
*/
|
|
199
|
-
function renderFeedbackProgress(stats) {
|
|
200
|
-
var container = document.getElementById('feedback-progress');
|
|
201
|
-
if (!container) return;
|
|
202
|
-
|
|
203
|
-
var total = stats.total_signals || 0;
|
|
204
|
-
var phase = stats.ranking_phase || 'baseline';
|
|
205
|
-
var progress = stats.progress || 0;
|
|
206
|
-
var target = stats.target || 200;
|
|
207
|
-
|
|
208
|
-
var phaseLabels = {
|
|
209
|
-
baseline: 'Baseline (collecting data)',
|
|
210
|
-
rule_based: 'Rule-Based (learning your preferences)',
|
|
211
|
-
ml_model: 'ML-Powered (fully personalized)',
|
|
212
|
-
};
|
|
213
|
-
var phaseColors = {
|
|
214
|
-
baseline: 'bg-secondary',
|
|
215
|
-
rule_based: 'bg-info',
|
|
216
|
-
ml_model: 'bg-success',
|
|
217
|
-
};
|
|
218
|
-
|
|
219
|
-
// Clear container
|
|
220
|
-
while (container.firstChild) container.removeChild(container.firstChild);
|
|
221
|
-
|
|
222
|
-
// Header row
|
|
223
|
-
var headerRow = document.createElement('div');
|
|
224
|
-
headerRow.className = 'd-flex justify-content-between align-items-center mb-1';
|
|
225
|
-
|
|
226
|
-
var label = document.createElement('small');
|
|
227
|
-
label.className = 'text-muted';
|
|
228
|
-
var icon = document.createElement('i');
|
|
229
|
-
icon.className = 'bi bi-graph-up me-1';
|
|
230
|
-
label.appendChild(icon);
|
|
231
|
-
label.appendChild(document.createTextNode('Learning Progress'));
|
|
232
|
-
|
|
233
|
-
var badge = document.createElement('span');
|
|
234
|
-
badge.className = 'badge ' + (phaseColors[phase] || 'bg-secondary');
|
|
235
|
-
badge.textContent = phaseLabels[phase] || phase;
|
|
236
|
-
|
|
237
|
-
headerRow.appendChild(label);
|
|
238
|
-
headerRow.appendChild(badge);
|
|
239
|
-
container.appendChild(headerRow);
|
|
240
|
-
|
|
241
|
-
// Progress bar
|
|
242
|
-
var progressOuter = document.createElement('div');
|
|
243
|
-
progressOuter.className = 'progress';
|
|
244
|
-
progressOuter.style.height = '8px';
|
|
245
|
-
|
|
246
|
-
var progressBar = document.createElement('div');
|
|
247
|
-
progressBar.className = 'progress-bar ' + (phaseColors[phase] || 'bg-secondary');
|
|
248
|
-
progressBar.setAttribute('role', 'progressbar');
|
|
249
|
-
progressBar.style.width = progress + '%';
|
|
250
|
-
progressBar.setAttribute('aria-valuenow', String(total));
|
|
251
|
-
progressBar.setAttribute('aria-valuemin', '0');
|
|
252
|
-
progressBar.setAttribute('aria-valuemax', String(target));
|
|
253
|
-
|
|
254
|
-
progressOuter.appendChild(progressBar);
|
|
255
|
-
container.appendChild(progressOuter);
|
|
256
|
-
|
|
257
|
-
// Count text
|
|
258
|
-
var countText = document.createElement('small');
|
|
259
|
-
countText.className = 'text-muted';
|
|
260
|
-
countText.textContent = total + '/' + target + ' signals';
|
|
261
|
-
container.appendChild(countText);
|
|
262
|
-
}
|
|
263
|
-
|
|
264
|
-
/**
|
|
265
|
-
* Create the privacy notice banner using safe DOM methods.
|
|
266
|
-
*/
|
|
267
|
-
function createPrivacyNotice() {
|
|
268
|
-
var container = document.getElementById('privacy-notice');
|
|
269
|
-
if (!container) return;
|
|
270
|
-
|
|
271
|
-
var alert = document.createElement('div');
|
|
272
|
-
alert.className = 'alert alert-light border d-flex align-items-center py-2 mb-3';
|
|
273
|
-
alert.setAttribute('role', 'alert');
|
|
274
|
-
|
|
275
|
-
var lockIcon = document.createElement('i');
|
|
276
|
-
lockIcon.className = 'bi bi-shield-lock me-2 text-success';
|
|
277
|
-
|
|
278
|
-
var text = document.createElement('small');
|
|
279
|
-
text.appendChild(document.createTextNode('Learning is 100% local. Your behavioral data never leaves this machine. '));
|
|
280
|
-
|
|
281
|
-
var link = document.createElement('a');
|
|
282
|
-
link.href = '#';
|
|
283
|
-
link.className = 'ms-1';
|
|
284
|
-
link.textContent = 'Learn more';
|
|
285
|
-
link.onclick = function(e) {
|
|
286
|
-
e.preventDefault();
|
|
287
|
-
showPrivacyDetails();
|
|
288
|
-
};
|
|
289
|
-
text.appendChild(link);
|
|
290
|
-
|
|
291
|
-
alert.appendChild(lockIcon);
|
|
292
|
-
alert.appendChild(text);
|
|
293
|
-
container.appendChild(alert);
|
|
294
|
-
}
|
|
295
|
-
|
|
296
|
-
/**
|
|
297
|
-
* Show privacy details in an alert (safe, no raw HTML injection).
|
|
298
|
-
*/
|
|
299
|
-
function showPrivacyDetails() {
|
|
300
|
-
var info = 'What data is collected:\n' +
|
|
301
|
-
'- Which memories you find useful (thumbs up/down)\n' +
|
|
302
|
-
'- Time spent viewing memory details\n' +
|
|
303
|
-
'- Search patterns (queries are hashed, never stored as raw text)\n\n' +
|
|
304
|
-
'Where it is stored:\n' +
|
|
305
|
-
'~/.superlocalmemory/learning.db (local SQLite file)\n\n' +
|
|
306
|
-
'How to delete it:\n' +
|
|
307
|
-
'Use "Reset Learning Data" in Settings, or run:\n' +
|
|
308
|
-
'rm ~/.superlocalmemory/learning.db';
|
|
309
|
-
alert(info);
|
|
310
|
-
}
|
|
311
|
-
|
|
312
|
-
/**
|
|
313
|
-
* Reset all learning data.
|
|
314
|
-
*/
|
|
315
|
-
function resetLearningData() {
|
|
316
|
-
if (!confirm('Reset all learning data? Your memories will be preserved.')) return;
|
|
317
|
-
|
|
318
|
-
fetch('/api/learning/reset', {method: 'POST'})
|
|
319
|
-
.then(function(r) { return r.json(); })
|
|
320
|
-
.then(function(data) {
|
|
321
|
-
if (data.success) {
|
|
322
|
-
if (typeof showToast === 'function') showToast('Learning data reset', 'success');
|
|
323
|
-
refreshFeedbackStats();
|
|
324
|
-
}
|
|
325
|
-
})
|
|
326
|
-
.catch(function() {});
|
|
327
|
-
}
|
|
328
|
-
|
|
329
|
-
// Initialize: load stats on page ready
|
|
330
|
-
document.addEventListener('DOMContentLoaded', function() {
|
|
331
|
-
createPrivacyNotice();
|
|
332
|
-
refreshFeedbackStats();
|
|
333
|
-
});
|