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
|
@@ -1,351 +0,0 @@
|
|
|
1
|
-
// SuperLocalMemory V2.6.5 - Interactive Knowledge Graph - Interactions Module
|
|
2
|
-
// Copyright (c) 2026 Varun Pratap Bhardwaj — Elastic License 2.0
|
|
3
|
-
// Part of modular graph visualization system (split from monolithic graph-cytoscape.js)
|
|
4
|
-
|
|
5
|
-
// ============================================================================
|
|
6
|
-
// CYTOSCAPE INTERACTIONS
|
|
7
|
-
// ============================================================================
|
|
8
|
-
|
|
9
|
-
function addCytoscapeInteractions() {
|
|
10
|
-
if (!cy) return;
|
|
11
|
-
|
|
12
|
-
// Hover: Show tooltip
|
|
13
|
-
cy.on('mouseover', 'node', function(evt) {
|
|
14
|
-
const node = evt.target;
|
|
15
|
-
const pos = evt.renderedPosition;
|
|
16
|
-
showTooltip(node, pos.x, pos.y);
|
|
17
|
-
|
|
18
|
-
// Highlight connected nodes
|
|
19
|
-
node.addClass('highlighted');
|
|
20
|
-
node.connectedEdges().addClass('highlighted');
|
|
21
|
-
node.neighborhood('node').addClass('highlighted');
|
|
22
|
-
|
|
23
|
-
// Dim others
|
|
24
|
-
cy.nodes().not(node).not(node.neighborhood('node')).addClass('dimmed');
|
|
25
|
-
cy.edges().not(node.connectedEdges()).addClass('dimmed');
|
|
26
|
-
});
|
|
27
|
-
|
|
28
|
-
cy.on('mouseout', 'node', function(evt) {
|
|
29
|
-
hideTooltip();
|
|
30
|
-
|
|
31
|
-
// Remove highlighting
|
|
32
|
-
cy.elements().removeClass('highlighted').removeClass('dimmed');
|
|
33
|
-
});
|
|
34
|
-
|
|
35
|
-
// Single click: Open modal preview (source='graph' for context-aware buttons)
|
|
36
|
-
cy.on('tap', 'node', function(evt) {
|
|
37
|
-
const node = evt.target;
|
|
38
|
-
openMemoryModal(node);
|
|
39
|
-
});
|
|
40
|
-
|
|
41
|
-
// Double click: Navigate to Memories tab
|
|
42
|
-
cy.on('dbltap', 'node', function(evt) {
|
|
43
|
-
const node = evt.target;
|
|
44
|
-
navigateToMemoryTab(node.data('id'));
|
|
45
|
-
});
|
|
46
|
-
|
|
47
|
-
// Enable node dragging
|
|
48
|
-
cy.on('drag', 'node', function(evt) {
|
|
49
|
-
// Node position is automatically updated by Cytoscape
|
|
50
|
-
});
|
|
51
|
-
|
|
52
|
-
// Save layout when drag ends
|
|
53
|
-
cy.on('dragfree', 'node', function(evt) {
|
|
54
|
-
saveLayoutPositions();
|
|
55
|
-
});
|
|
56
|
-
|
|
57
|
-
// Pan & zoom events (for performance monitoring)
|
|
58
|
-
let panZoomTimeout;
|
|
59
|
-
cy.on('pan zoom', function() {
|
|
60
|
-
clearTimeout(panZoomTimeout);
|
|
61
|
-
panZoomTimeout = setTimeout(() => {
|
|
62
|
-
saveLayoutPositions();
|
|
63
|
-
}, 1000);
|
|
64
|
-
});
|
|
65
|
-
|
|
66
|
-
// Add keyboard navigation
|
|
67
|
-
setupKeyboardNavigation();
|
|
68
|
-
}
|
|
69
|
-
|
|
70
|
-
// ============================================================================
|
|
71
|
-
// TOOLTIP
|
|
72
|
-
// ============================================================================
|
|
73
|
-
|
|
74
|
-
let tooltipTimeout;
|
|
75
|
-
function showTooltip(node, x, y) {
|
|
76
|
-
clearTimeout(tooltipTimeout);
|
|
77
|
-
tooltipTimeout = setTimeout(() => {
|
|
78
|
-
let tooltip = document.getElementById('graph-tooltip');
|
|
79
|
-
if (!tooltip) {
|
|
80
|
-
tooltip = document.createElement('div');
|
|
81
|
-
tooltip.id = 'graph-tooltip';
|
|
82
|
-
tooltip.style.cssText = 'position:fixed; background:#333; color:#fff; padding:10px; border-radius:6px; font-size:12px; max-width:300px; z-index:10000; pointer-events:none; box-shadow: 0 4px 12px rgba(0,0,0,0.3);';
|
|
83
|
-
document.body.appendChild(tooltip);
|
|
84
|
-
}
|
|
85
|
-
|
|
86
|
-
// Build tooltip content safely (no innerHTML)
|
|
87
|
-
tooltip.textContent = ''; // Clear
|
|
88
|
-
const data = node.data();
|
|
89
|
-
|
|
90
|
-
const title = document.createElement('strong');
|
|
91
|
-
title.textContent = data.label;
|
|
92
|
-
tooltip.appendChild(title);
|
|
93
|
-
|
|
94
|
-
tooltip.appendChild(document.createElement('br'));
|
|
95
|
-
|
|
96
|
-
const meta = document.createElement('span');
|
|
97
|
-
meta.style.color = '#aaa';
|
|
98
|
-
meta.textContent = `Cluster ${data.cluster_id} • Importance ${data.importance}`;
|
|
99
|
-
tooltip.appendChild(meta);
|
|
100
|
-
|
|
101
|
-
tooltip.appendChild(document.createElement('br'));
|
|
102
|
-
|
|
103
|
-
const preview = document.createElement('span');
|
|
104
|
-
preview.style.cssText = 'font-size:11px; color:#ccc;';
|
|
105
|
-
preview.textContent = data.content_preview;
|
|
106
|
-
tooltip.appendChild(preview);
|
|
107
|
-
|
|
108
|
-
tooltip.style.display = 'block';
|
|
109
|
-
tooltip.style.left = (x + 20) + 'px';
|
|
110
|
-
tooltip.style.top = (y - 20) + 'px';
|
|
111
|
-
}, 200);
|
|
112
|
-
}
|
|
113
|
-
|
|
114
|
-
function hideTooltip() {
|
|
115
|
-
clearTimeout(tooltipTimeout);
|
|
116
|
-
const tooltip = document.getElementById('graph-tooltip');
|
|
117
|
-
if (tooltip) {
|
|
118
|
-
tooltip.style.display = 'none';
|
|
119
|
-
}
|
|
120
|
-
}
|
|
121
|
-
|
|
122
|
-
// ============================================================================
|
|
123
|
-
// MODAL INTEGRATION
|
|
124
|
-
// ============================================================================
|
|
125
|
-
|
|
126
|
-
function openMemoryModal(node) {
|
|
127
|
-
const memoryData = {
|
|
128
|
-
id: node.data('id'),
|
|
129
|
-
content: node.data('content'),
|
|
130
|
-
summary: node.data('summary'),
|
|
131
|
-
category: node.data('category'),
|
|
132
|
-
project_name: node.data('project_name'),
|
|
133
|
-
cluster_id: node.data('cluster_id'),
|
|
134
|
-
importance: node.data('importance'),
|
|
135
|
-
tags: node.data('tags'),
|
|
136
|
-
created_at: node.data('created_at')
|
|
137
|
-
};
|
|
138
|
-
|
|
139
|
-
if (typeof openMemoryDetail === 'function') {
|
|
140
|
-
openMemoryDetail(memoryData, 'graph'); // source='graph': show Expand Neighbors, hide View Original
|
|
141
|
-
}
|
|
142
|
-
}
|
|
143
|
-
|
|
144
|
-
function navigateToMemoryTab(memoryId) {
|
|
145
|
-
// Switch to Memories tab
|
|
146
|
-
const memoriesTab = document.querySelector('a[href="#memories"]');
|
|
147
|
-
if (memoriesTab) {
|
|
148
|
-
memoriesTab.click();
|
|
149
|
-
}
|
|
150
|
-
|
|
151
|
-
// Scroll to memory after a short delay (for tab to load)
|
|
152
|
-
setTimeout(() => {
|
|
153
|
-
if (typeof scrollToMemory === 'function') {
|
|
154
|
-
scrollToMemory(memoryId);
|
|
155
|
-
} else {
|
|
156
|
-
console.warn('scrollToMemory function not found in memories.js');
|
|
157
|
-
}
|
|
158
|
-
}, 300);
|
|
159
|
-
}
|
|
160
|
-
|
|
161
|
-
// ============================================================================
|
|
162
|
-
// KEYBOARD NAVIGATION
|
|
163
|
-
// ============================================================================
|
|
164
|
-
|
|
165
|
-
function setupKeyboardNavigation() {
|
|
166
|
-
if (!cy) return;
|
|
167
|
-
|
|
168
|
-
const container = document.getElementById('graph-container');
|
|
169
|
-
if (!container) return;
|
|
170
|
-
|
|
171
|
-
// Make container focusable
|
|
172
|
-
container.setAttribute('tabindex', '0');
|
|
173
|
-
|
|
174
|
-
// Focus handler - enable keyboard nav when container is focused
|
|
175
|
-
container.addEventListener('focus', function() {
|
|
176
|
-
keyboardNavigationEnabled = true;
|
|
177
|
-
if (cy.nodes().length > 0) {
|
|
178
|
-
focusNodeAtIndex(0);
|
|
179
|
-
}
|
|
180
|
-
});
|
|
181
|
-
|
|
182
|
-
container.addEventListener('blur', function() {
|
|
183
|
-
keyboardNavigationEnabled = false;
|
|
184
|
-
cy.nodes().removeClass('keyboard-focused');
|
|
185
|
-
});
|
|
186
|
-
|
|
187
|
-
// Keyboard event handler
|
|
188
|
-
container.addEventListener('keydown', function(e) {
|
|
189
|
-
if (!keyboardNavigationEnabled || !cy) return;
|
|
190
|
-
|
|
191
|
-
const nodes = cy.nodes();
|
|
192
|
-
if (nodes.length === 0) return;
|
|
193
|
-
|
|
194
|
-
const currentNode = nodes[focusedNodeIndex];
|
|
195
|
-
|
|
196
|
-
switch(e.key) {
|
|
197
|
-
case 'Tab':
|
|
198
|
-
e.preventDefault();
|
|
199
|
-
if (e.shiftKey) {
|
|
200
|
-
// Shift+Tab: previous node
|
|
201
|
-
focusedNodeIndex = (focusedNodeIndex - 1 + nodes.length) % nodes.length;
|
|
202
|
-
} else {
|
|
203
|
-
// Tab: next node
|
|
204
|
-
focusedNodeIndex = (focusedNodeIndex + 1) % nodes.length;
|
|
205
|
-
}
|
|
206
|
-
focusNodeAtIndex(focusedNodeIndex);
|
|
207
|
-
announceNode(nodes[focusedNodeIndex]);
|
|
208
|
-
break;
|
|
209
|
-
|
|
210
|
-
case 'Enter':
|
|
211
|
-
case ' ':
|
|
212
|
-
e.preventDefault();
|
|
213
|
-
if (currentNode) {
|
|
214
|
-
lastFocusedElement = container;
|
|
215
|
-
openMemoryModal(currentNode);
|
|
216
|
-
}
|
|
217
|
-
break;
|
|
218
|
-
|
|
219
|
-
case 'ArrowRight':
|
|
220
|
-
e.preventDefault();
|
|
221
|
-
moveToAdjacentNode('right', currentNode);
|
|
222
|
-
break;
|
|
223
|
-
|
|
224
|
-
case 'ArrowLeft':
|
|
225
|
-
e.preventDefault();
|
|
226
|
-
moveToAdjacentNode('left', currentNode);
|
|
227
|
-
break;
|
|
228
|
-
|
|
229
|
-
case 'ArrowDown':
|
|
230
|
-
e.preventDefault();
|
|
231
|
-
moveToAdjacentNode('down', currentNode);
|
|
232
|
-
break;
|
|
233
|
-
|
|
234
|
-
case 'ArrowUp':
|
|
235
|
-
e.preventDefault();
|
|
236
|
-
moveToAdjacentNode('up', currentNode);
|
|
237
|
-
break;
|
|
238
|
-
|
|
239
|
-
case 'Escape':
|
|
240
|
-
e.preventDefault();
|
|
241
|
-
if (filterState.cluster_id || filterState.entity) {
|
|
242
|
-
clearGraphFilters();
|
|
243
|
-
updateScreenReaderStatus('Filters cleared, showing all memories');
|
|
244
|
-
} else {
|
|
245
|
-
container.blur();
|
|
246
|
-
keyboardNavigationEnabled = false;
|
|
247
|
-
}
|
|
248
|
-
break;
|
|
249
|
-
|
|
250
|
-
case 'Home':
|
|
251
|
-
e.preventDefault();
|
|
252
|
-
focusedNodeIndex = 0;
|
|
253
|
-
focusNodeAtIndex(0);
|
|
254
|
-
announceNode(nodes[0]);
|
|
255
|
-
break;
|
|
256
|
-
|
|
257
|
-
case 'End':
|
|
258
|
-
e.preventDefault();
|
|
259
|
-
focusedNodeIndex = nodes.length - 1;
|
|
260
|
-
focusNodeAtIndex(focusedNodeIndex);
|
|
261
|
-
announceNode(nodes[focusedNodeIndex]);
|
|
262
|
-
break;
|
|
263
|
-
}
|
|
264
|
-
});
|
|
265
|
-
}
|
|
266
|
-
|
|
267
|
-
function focusNodeAtIndex(index) {
|
|
268
|
-
if (!cy) return;
|
|
269
|
-
|
|
270
|
-
const nodes = cy.nodes();
|
|
271
|
-
if (index < 0 || index >= nodes.length) return;
|
|
272
|
-
|
|
273
|
-
// Remove focus from all nodes
|
|
274
|
-
cy.nodes().removeClass('keyboard-focused');
|
|
275
|
-
|
|
276
|
-
// Add focus to target node
|
|
277
|
-
const node = nodes[index];
|
|
278
|
-
node.addClass('keyboard-focused');
|
|
279
|
-
|
|
280
|
-
// Center node in viewport with smooth animation
|
|
281
|
-
cy.animate({
|
|
282
|
-
center: { eles: node },
|
|
283
|
-
zoom: Math.max(cy.zoom(), 1.0),
|
|
284
|
-
duration: 300,
|
|
285
|
-
easing: 'ease-in-out'
|
|
286
|
-
});
|
|
287
|
-
}
|
|
288
|
-
|
|
289
|
-
function moveToAdjacentNode(direction, currentNode) {
|
|
290
|
-
if (!currentNode) return;
|
|
291
|
-
|
|
292
|
-
const nodes = cy.nodes();
|
|
293
|
-
const currentPos = currentNode.position();
|
|
294
|
-
let bestNode = null;
|
|
295
|
-
let bestScore = Infinity;
|
|
296
|
-
|
|
297
|
-
// Find adjacent nodes based on direction
|
|
298
|
-
nodes.forEach((node, index) => {
|
|
299
|
-
if (node.id() === currentNode.id()) return;
|
|
300
|
-
|
|
301
|
-
const pos = node.position();
|
|
302
|
-
const dx = pos.x - currentPos.x;
|
|
303
|
-
const dy = pos.y - currentPos.y;
|
|
304
|
-
const distance = Math.sqrt(dx * dx + dy * dy);
|
|
305
|
-
|
|
306
|
-
let isCorrectDirection = false;
|
|
307
|
-
let directionScore = 0;
|
|
308
|
-
|
|
309
|
-
switch(direction) {
|
|
310
|
-
case 'right':
|
|
311
|
-
isCorrectDirection = dx > 0;
|
|
312
|
-
directionScore = dx;
|
|
313
|
-
break;
|
|
314
|
-
case 'left':
|
|
315
|
-
isCorrectDirection = dx < 0;
|
|
316
|
-
directionScore = -dx;
|
|
317
|
-
break;
|
|
318
|
-
case 'down':
|
|
319
|
-
isCorrectDirection = dy > 0;
|
|
320
|
-
directionScore = dy;
|
|
321
|
-
break;
|
|
322
|
-
case 'up':
|
|
323
|
-
isCorrectDirection = dy < 0;
|
|
324
|
-
directionScore = -dy;
|
|
325
|
-
break;
|
|
326
|
-
}
|
|
327
|
-
|
|
328
|
-
// Combine distance with direction preference
|
|
329
|
-
if (isCorrectDirection) {
|
|
330
|
-
const score = distance - (directionScore * 0.5);
|
|
331
|
-
if (score < bestScore) {
|
|
332
|
-
bestScore = score;
|
|
333
|
-
bestNode = node;
|
|
334
|
-
focusedNodeIndex = index;
|
|
335
|
-
}
|
|
336
|
-
}
|
|
337
|
-
});
|
|
338
|
-
|
|
339
|
-
if (bestNode) {
|
|
340
|
-
focusNodeAtIndex(focusedNodeIndex);
|
|
341
|
-
announceNode(bestNode);
|
|
342
|
-
}
|
|
343
|
-
}
|
|
344
|
-
|
|
345
|
-
function announceNode(node) {
|
|
346
|
-
if (!node) return;
|
|
347
|
-
|
|
348
|
-
const data = node.data();
|
|
349
|
-
const message = `Memory ${data.id}: ${data.label}, Cluster ${data.cluster_id}, Importance ${data.importance} out of 10`;
|
|
350
|
-
updateScreenReaderStatus(message);
|
|
351
|
-
}
|
package/ui/js/graph-ui.js
DELETED
|
@@ -1,214 +0,0 @@
|
|
|
1
|
-
// SuperLocalMemory V2.6.5 - Interactive Knowledge Graph - UI Elements Module
|
|
2
|
-
// Copyright (c) 2026 Varun Pratap Bhardwaj — Elastic License 2.0
|
|
3
|
-
// Part of modular graph visualization system (split from monolithic graph-cytoscape.js)
|
|
4
|
-
|
|
5
|
-
// ============================================================================
|
|
6
|
-
// FILTER BADGE UI
|
|
7
|
-
// ============================================================================
|
|
8
|
-
|
|
9
|
-
function updateFilterBadge() {
|
|
10
|
-
const statusFull = document.getElementById('graph-status-full');
|
|
11
|
-
const statusFiltered = document.getElementById('graph-status-filtered');
|
|
12
|
-
const filterDescription = document.getElementById('graph-filter-description');
|
|
13
|
-
const filterCount = document.getElementById('graph-filter-count');
|
|
14
|
-
const statusFullText = document.getElementById('graph-status-full-text');
|
|
15
|
-
|
|
16
|
-
const hasFilter = filterState.cluster_id || filterState.entity;
|
|
17
|
-
|
|
18
|
-
if (hasFilter) {
|
|
19
|
-
// FILTERED STATE - Show prominent alert with "Show All Memories" button
|
|
20
|
-
if (statusFull) statusFull.style.display = 'none';
|
|
21
|
-
if (statusFiltered) statusFiltered.style.display = 'block';
|
|
22
|
-
|
|
23
|
-
// Update filter description
|
|
24
|
-
if (filterDescription) {
|
|
25
|
-
const text = filterState.cluster_id
|
|
26
|
-
? `Viewing Cluster ${filterState.cluster_id}`
|
|
27
|
-
: `Viewing: ${filterState.entity}`;
|
|
28
|
-
filterDescription.textContent = text;
|
|
29
|
-
}
|
|
30
|
-
|
|
31
|
-
// Update count (will be set after graph renders)
|
|
32
|
-
if (filterCount && graphData && graphData.nodes) {
|
|
33
|
-
filterCount.textContent = `(${graphData.nodes.length} ${graphData.nodes.length === 1 ? 'memory' : 'memories'})`;
|
|
34
|
-
}
|
|
35
|
-
|
|
36
|
-
console.log('[updateFilterBadge] Showing FILTERED state');
|
|
37
|
-
} else {
|
|
38
|
-
// FULL GRAPH STATE - Show normal status with refresh button
|
|
39
|
-
if (statusFull) statusFull.style.display = 'block';
|
|
40
|
-
if (statusFiltered) statusFiltered.style.display = 'none';
|
|
41
|
-
|
|
42
|
-
// Update count
|
|
43
|
-
if (statusFullText && graphData && graphData.nodes) {
|
|
44
|
-
const maxNodes = document.getElementById('graph-max-nodes')?.value || 50;
|
|
45
|
-
statusFullText.textContent = `Showing ${graphData.nodes.length} of ${maxNodes} memories`;
|
|
46
|
-
}
|
|
47
|
-
|
|
48
|
-
console.log('[updateFilterBadge] Showing FULL state');
|
|
49
|
-
}
|
|
50
|
-
}
|
|
51
|
-
|
|
52
|
-
// ============================================================================
|
|
53
|
-
// GRAPH STATS
|
|
54
|
-
// ============================================================================
|
|
55
|
-
|
|
56
|
-
function updateGraphStats(data) {
|
|
57
|
-
const statsEl = document.getElementById('graph-stats');
|
|
58
|
-
if (statsEl) {
|
|
59
|
-
// Clear and rebuild safely
|
|
60
|
-
statsEl.textContent = '';
|
|
61
|
-
|
|
62
|
-
const nodeBadge = document.createElement('span');
|
|
63
|
-
nodeBadge.className = 'badge bg-primary';
|
|
64
|
-
nodeBadge.textContent = `${data.nodes.length} nodes`;
|
|
65
|
-
statsEl.appendChild(nodeBadge);
|
|
66
|
-
|
|
67
|
-
const edgeBadge = document.createElement('span');
|
|
68
|
-
edgeBadge.className = 'badge bg-secondary';
|
|
69
|
-
edgeBadge.textContent = `${data.links.length} edges`;
|
|
70
|
-
statsEl.appendChild(document.createTextNode(' '));
|
|
71
|
-
statsEl.appendChild(edgeBadge);
|
|
72
|
-
|
|
73
|
-
const clusterBadge = document.createElement('span');
|
|
74
|
-
clusterBadge.className = 'badge bg-info';
|
|
75
|
-
clusterBadge.textContent = `${data.clusters?.length || 0} clusters`;
|
|
76
|
-
statsEl.appendChild(document.createTextNode(' '));
|
|
77
|
-
statsEl.appendChild(clusterBadge);
|
|
78
|
-
}
|
|
79
|
-
}
|
|
80
|
-
|
|
81
|
-
// ============================================================================
|
|
82
|
-
// LOADING SPINNER
|
|
83
|
-
// ============================================================================
|
|
84
|
-
|
|
85
|
-
function showLoadingSpinner() {
|
|
86
|
-
const container = document.getElementById('graph-container');
|
|
87
|
-
if (container) {
|
|
88
|
-
container.textContent = ''; // Clear safely
|
|
89
|
-
|
|
90
|
-
const wrapper = document.createElement('div');
|
|
91
|
-
wrapper.style.cssText = 'text-align:center; padding:100px;';
|
|
92
|
-
|
|
93
|
-
const spinner = document.createElement('div');
|
|
94
|
-
spinner.className = 'spinner-border text-primary';
|
|
95
|
-
spinner.setAttribute('role', 'status');
|
|
96
|
-
wrapper.appendChild(spinner);
|
|
97
|
-
|
|
98
|
-
const text = document.createElement('p');
|
|
99
|
-
text.style.marginTop = '20px';
|
|
100
|
-
text.textContent = 'Loading graph...';
|
|
101
|
-
wrapper.appendChild(text);
|
|
102
|
-
|
|
103
|
-
container.appendChild(wrapper);
|
|
104
|
-
}
|
|
105
|
-
}
|
|
106
|
-
|
|
107
|
-
function hideLoadingSpinner() {
|
|
108
|
-
// Do nothing - renderGraph() already cleared the spinner
|
|
109
|
-
// If we clear here, we destroy the Cytoscape canvas!
|
|
110
|
-
console.log('[hideLoadingSpinner] Graph already rendered, spinner cleared by renderGraph()');
|
|
111
|
-
}
|
|
112
|
-
|
|
113
|
-
function showError(message) {
|
|
114
|
-
const container = document.getElementById('graph-container');
|
|
115
|
-
if (container) {
|
|
116
|
-
container.textContent = ''; // Clear safely
|
|
117
|
-
|
|
118
|
-
const alert = document.createElement('div');
|
|
119
|
-
alert.className = 'alert alert-danger';
|
|
120
|
-
alert.setAttribute('role', 'alert');
|
|
121
|
-
alert.style.margin = '50px';
|
|
122
|
-
alert.textContent = message;
|
|
123
|
-
container.appendChild(alert);
|
|
124
|
-
}
|
|
125
|
-
}
|
|
126
|
-
|
|
127
|
-
// ============================================================================
|
|
128
|
-
// LAYOUT MANAGEMENT
|
|
129
|
-
// ============================================================================
|
|
130
|
-
|
|
131
|
-
function saveLayoutPositions() {
|
|
132
|
-
if (!cy) return;
|
|
133
|
-
|
|
134
|
-
const positions = {};
|
|
135
|
-
cy.nodes().forEach(node => {
|
|
136
|
-
positions[node.id()] = node.position();
|
|
137
|
-
});
|
|
138
|
-
|
|
139
|
-
try {
|
|
140
|
-
localStorage.setItem('slm_graph_layout', JSON.stringify(positions));
|
|
141
|
-
} catch (e) {
|
|
142
|
-
console.warn('Failed to save graph layout:', e);
|
|
143
|
-
}
|
|
144
|
-
}
|
|
145
|
-
|
|
146
|
-
function restoreSavedLayout() {
|
|
147
|
-
if (!cy) return;
|
|
148
|
-
|
|
149
|
-
try {
|
|
150
|
-
const saved = localStorage.getItem('slm_graph_layout');
|
|
151
|
-
if (saved) {
|
|
152
|
-
const positions = JSON.parse(saved);
|
|
153
|
-
cy.nodes().forEach(node => {
|
|
154
|
-
const pos = positions[node.id()];
|
|
155
|
-
if (pos) {
|
|
156
|
-
node.position(pos);
|
|
157
|
-
}
|
|
158
|
-
});
|
|
159
|
-
}
|
|
160
|
-
} catch (e) {
|
|
161
|
-
console.warn('Failed to restore graph layout:', e);
|
|
162
|
-
}
|
|
163
|
-
}
|
|
164
|
-
|
|
165
|
-
function changeGraphLayout(layoutName) {
|
|
166
|
-
if (!cy) return;
|
|
167
|
-
|
|
168
|
-
currentLayout = layoutName;
|
|
169
|
-
const layout = cy.layout(getLayoutConfig(layoutName));
|
|
170
|
-
layout.run();
|
|
171
|
-
|
|
172
|
-
// Save preference
|
|
173
|
-
localStorage.setItem('slm_graph_layout_preference', layoutName);
|
|
174
|
-
}
|
|
175
|
-
|
|
176
|
-
// ============================================================================
|
|
177
|
-
// EXPAND NEIGHBORS
|
|
178
|
-
// ============================================================================
|
|
179
|
-
|
|
180
|
-
function expandNeighbors(memoryId) {
|
|
181
|
-
if (!cy) return;
|
|
182
|
-
|
|
183
|
-
const node = cy.getElementById(String(memoryId));
|
|
184
|
-
if (!node || node.length === 0) return;
|
|
185
|
-
|
|
186
|
-
// Hide all nodes and edges
|
|
187
|
-
cy.elements().addClass('dimmed');
|
|
188
|
-
|
|
189
|
-
// Show target node + neighbors + connecting edges
|
|
190
|
-
node.removeClass('dimmed');
|
|
191
|
-
node.neighborhood().removeClass('dimmed');
|
|
192
|
-
node.connectedEdges().removeClass('dimmed');
|
|
193
|
-
|
|
194
|
-
// Fit view to visible elements
|
|
195
|
-
cy.fit(node.neighborhood().union(node), 50);
|
|
196
|
-
}
|
|
197
|
-
|
|
198
|
-
// ============================================================================
|
|
199
|
-
// SCREEN READER STATUS
|
|
200
|
-
// ============================================================================
|
|
201
|
-
|
|
202
|
-
function updateScreenReaderStatus(message) {
|
|
203
|
-
let statusRegion = document.getElementById('graph-sr-status');
|
|
204
|
-
if (!statusRegion) {
|
|
205
|
-
statusRegion = document.createElement('div');
|
|
206
|
-
statusRegion.id = 'graph-sr-status';
|
|
207
|
-
statusRegion.setAttribute('role', 'status');
|
|
208
|
-
statusRegion.setAttribute('aria-live', 'polite');
|
|
209
|
-
statusRegion.setAttribute('aria-atomic', 'true');
|
|
210
|
-
statusRegion.style.cssText = 'position:absolute; left:-10000px; width:1px; height:1px; overflow:hidden;';
|
|
211
|
-
document.body.appendChild(statusRegion);
|
|
212
|
-
}
|
|
213
|
-
statusRegion.textContent = message;
|
|
214
|
-
}
|
package/ui/js/ide-status.js
DELETED
|
@@ -1,102 +0,0 @@
|
|
|
1
|
-
// SuperLocalMemory V3 — IDE Connections
|
|
2
|
-
// Displays detected IDEs and allows connecting them to SLM.
|
|
3
|
-
|
|
4
|
-
async function loadIDEStatus() {
|
|
5
|
-
try {
|
|
6
|
-
var response = await fetch('/api/v3/ide/status');
|
|
7
|
-
if (!response.ok) return;
|
|
8
|
-
var data = await response.json();
|
|
9
|
-
|
|
10
|
-
var tbody = document.getElementById('ide-list-body');
|
|
11
|
-
tbody.textContent = '';
|
|
12
|
-
(data.ides || []).forEach(function(ide) {
|
|
13
|
-
var tr = document.createElement('tr');
|
|
14
|
-
|
|
15
|
-
// IDE name cell
|
|
16
|
-
var tdName = document.createElement('td');
|
|
17
|
-
var strong = document.createElement('strong');
|
|
18
|
-
strong.textContent = ide.name;
|
|
19
|
-
tdName.appendChild(strong);
|
|
20
|
-
tr.appendChild(tdName);
|
|
21
|
-
|
|
22
|
-
// Installed status cell
|
|
23
|
-
var tdInstalled = document.createElement('td');
|
|
24
|
-
var badge = document.createElement('span');
|
|
25
|
-
if (ide.installed) {
|
|
26
|
-
badge.className = 'badge bg-success';
|
|
27
|
-
badge.textContent = 'Installed';
|
|
28
|
-
} else {
|
|
29
|
-
badge.className = 'badge bg-secondary';
|
|
30
|
-
badge.textContent = 'Not Found';
|
|
31
|
-
}
|
|
32
|
-
tdInstalled.appendChild(badge);
|
|
33
|
-
tr.appendChild(tdInstalled);
|
|
34
|
-
|
|
35
|
-
// Config path cell
|
|
36
|
-
var tdPath = document.createElement('td');
|
|
37
|
-
tdPath.className = 'text-muted small';
|
|
38
|
-
tdPath.textContent = ide.config_path || '';
|
|
39
|
-
tr.appendChild(tdPath);
|
|
40
|
-
|
|
41
|
-
// Action cell
|
|
42
|
-
var tdAction = document.createElement('td');
|
|
43
|
-
if (ide.installed) {
|
|
44
|
-
var btn = document.createElement('button');
|
|
45
|
-
btn.className = 'btn btn-sm btn-outline-primary ide-connect-btn';
|
|
46
|
-
btn.dataset.ide = ide.id;
|
|
47
|
-
btn.textContent = 'Connect';
|
|
48
|
-
tdAction.appendChild(btn);
|
|
49
|
-
}
|
|
50
|
-
tr.appendChild(tdAction);
|
|
51
|
-
|
|
52
|
-
tbody.appendChild(tr);
|
|
53
|
-
});
|
|
54
|
-
} catch (e) {
|
|
55
|
-
console.log('IDE status error:', e);
|
|
56
|
-
}
|
|
57
|
-
}
|
|
58
|
-
|
|
59
|
-
// Delegate click handler for individual IDE connect buttons
|
|
60
|
-
document.addEventListener('click', function(e) {
|
|
61
|
-
var btn = e.target.closest('.ide-connect-btn');
|
|
62
|
-
if (!btn) return;
|
|
63
|
-
|
|
64
|
-
var ideId = btn.dataset.ide;
|
|
65
|
-
fetch('/api/v3/ide/connect', {
|
|
66
|
-
method: 'POST',
|
|
67
|
-
headers: { 'Content-Type': 'application/json' },
|
|
68
|
-
body: JSON.stringify({ ide: ideId })
|
|
69
|
-
}).then(function(r) {
|
|
70
|
-
return r.json();
|
|
71
|
-
}).then(function(data) {
|
|
72
|
-
alert(data.success ? 'Connected: ' + ideId : 'Failed to connect');
|
|
73
|
-
loadIDEStatus();
|
|
74
|
-
}).catch(function(e) {
|
|
75
|
-
console.log('IDE connect error:', e);
|
|
76
|
-
});
|
|
77
|
-
});
|
|
78
|
-
|
|
79
|
-
// Connect all detected IDEs
|
|
80
|
-
var connectAllBtn = document.getElementById('ide-connect-all-btn');
|
|
81
|
-
if (connectAllBtn) {
|
|
82
|
-
connectAllBtn.addEventListener('click', function() {
|
|
83
|
-
fetch('/api/v3/ide/connect', {
|
|
84
|
-
method: 'POST',
|
|
85
|
-
headers: { 'Content-Type': 'application/json' },
|
|
86
|
-
body: JSON.stringify({})
|
|
87
|
-
}).then(function(r) {
|
|
88
|
-
return r.json();
|
|
89
|
-
}).then(function(data) {
|
|
90
|
-
var results = data.results || {};
|
|
91
|
-
var count = Object.values(results).filter(function(s) {
|
|
92
|
-
return s === 'connected';
|
|
93
|
-
}).length;
|
|
94
|
-
alert('Connected ' + count + ' IDEs');
|
|
95
|
-
loadIDEStatus();
|
|
96
|
-
}).catch(function(e) {
|
|
97
|
-
console.log('IDE connect-all error:', e);
|
|
98
|
-
});
|
|
99
|
-
});
|
|
100
|
-
}
|
|
101
|
-
|
|
102
|
-
document.getElementById('ide-tab')?.addEventListener('shown.bs.tab', loadIDEStatus);
|