superlocalmemory 3.4.17 → 3.4.19

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (80) hide show
  1. package/CHANGELOG.md +19 -0
  2. package/package.json +1 -3
  3. package/pyproject.toml +10 -1
  4. package/src/superlocalmemory/cli/setup_wizard.py +30 -0
  5. package/src/superlocalmemory/core/embeddings.py +8 -2
  6. package/src/superlocalmemory/retrieval/reranker.py +4 -2
  7. package/src/superlocalmemory.egg-info/PKG-INFO +4 -1
  8. package/src/superlocalmemory.egg-info/requires.txt +3 -0
  9. package/docs/ARCHITECTURE.md +0 -149
  10. package/docs/api-reference.md +0 -284
  11. package/docs/auto-memory.md +0 -150
  12. package/docs/cli-reference.md +0 -327
  13. package/docs/cloud-backup.md +0 -174
  14. package/docs/compliance.md +0 -191
  15. package/docs/configuration.md +0 -182
  16. package/docs/getting-started.md +0 -102
  17. package/docs/ide-setup.md +0 -261
  18. package/docs/mcp-tools.md +0 -220
  19. package/docs/migration-from-v2.md +0 -170
  20. package/docs/profiles.md +0 -173
  21. package/docs/screenshots/01-dashboard-main.png +0 -0
  22. package/docs/screenshots/02-knowledge-graph.png +0 -0
  23. package/docs/screenshots/03-math-health.png +0 -0
  24. package/docs/screenshots/03-patterns-learning.png +0 -0
  25. package/docs/screenshots/04-learning-dashboard.png +0 -0
  26. package/docs/screenshots/04-recall-lab.png +0 -0
  27. package/docs/screenshots/05-behavioral-analysis.png +0 -0
  28. package/docs/screenshots/05-trust-dashboard.png +0 -0
  29. package/docs/screenshots/06-graph-communities.png +0 -0
  30. package/docs/screenshots/06-settings.png +0 -0
  31. package/docs/screenshots/07-memories-blurred.png +0 -0
  32. package/docs/skill-evolution.md +0 -256
  33. package/docs/troubleshooting.md +0 -310
  34. package/docs/v2-archive/ACCESSIBILITY.md +0 -291
  35. package/docs/v2-archive/ARCHITECTURE.md +0 -886
  36. package/docs/v2-archive/CLI-COMMANDS-REFERENCE.md +0 -425
  37. package/docs/v2-archive/COMPRESSION-README.md +0 -390
  38. package/docs/v2-archive/FRAMEWORK-INTEGRATIONS.md +0 -300
  39. package/docs/v2-archive/MCP-MANUAL-SETUP.md +0 -775
  40. package/docs/v2-archive/MCP-TROUBLESHOOTING.md +0 -787
  41. package/docs/v2-archive/PATTERN-LEARNING.md +0 -228
  42. package/docs/v2-archive/PROFILES-GUIDE.md +0 -453
  43. package/docs/v2-archive/RESET-GUIDE.md +0 -353
  44. package/docs/v2-archive/SEARCH-ENGINE-V2.2.0.md +0 -749
  45. package/docs/v2-archive/SEARCH-INTEGRATION-GUIDE.md +0 -502
  46. package/docs/v2-archive/UI-SERVER.md +0 -262
  47. package/docs/v2-archive/UNIVERSAL-INTEGRATION.md +0 -488
  48. package/docs/v2-archive/V2.2.0-OPTIONAL-SEARCH.md +0 -666
  49. package/docs/v2-archive/WINDOWS-INSTALL-README.txt +0 -34
  50. package/docs/v2-archive/WINDOWS-POST-INSTALL.txt +0 -45
  51. package/docs/v2-archive/example_graph_usage.py +0 -146
  52. package/ui/index.html +0 -1879
  53. package/ui/js/agents.js +0 -192
  54. package/ui/js/auto-settings.js +0 -399
  55. package/ui/js/behavioral.js +0 -276
  56. package/ui/js/clusters.js +0 -206
  57. package/ui/js/compliance.js +0 -252
  58. package/ui/js/core.js +0 -246
  59. package/ui/js/dashboard.js +0 -110
  60. package/ui/js/events.js +0 -178
  61. package/ui/js/fact-detail.js +0 -92
  62. package/ui/js/feedback.js +0 -333
  63. package/ui/js/graph-core.js +0 -447
  64. package/ui/js/graph-filters.js +0 -220
  65. package/ui/js/graph-interactions.js +0 -351
  66. package/ui/js/graph-ui.js +0 -214
  67. package/ui/js/ide-status.js +0 -102
  68. package/ui/js/init.js +0 -45
  69. package/ui/js/learning.js +0 -435
  70. package/ui/js/lifecycle.js +0 -298
  71. package/ui/js/math-health.js +0 -98
  72. package/ui/js/memories.js +0 -264
  73. package/ui/js/modal.js +0 -357
  74. package/ui/js/patterns.js +0 -93
  75. package/ui/js/profiles.js +0 -236
  76. package/ui/js/recall-lab.js +0 -292
  77. package/ui/js/search.js +0 -59
  78. package/ui/js/settings.js +0 -224
  79. package/ui/js/timeline.js +0 -32
  80. package/ui/js/trust-dashboard.js +0 -73
@@ -1,447 +0,0 @@
1
- // SuperLocalMemory V2.6.5 - Interactive Knowledge Graph - Core Rendering 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
- // GLOBAL STATE
7
- // ============================================================================
8
-
9
- var cy = null; // Cytoscape.js instance (global)
10
- var graphData = { nodes: [], links: [] }; // Raw data from API
11
- var originalGraphData = { nodes: [], links: [] }; // Unfiltered data (for reset)
12
- var currentLayout = 'fcose'; // Default layout
13
- var filterState = { cluster_id: null, entity: null }; // Current filters
14
- var isInitialLoad = true; // Track if this is the first graph load
15
- var focusedNodeIndex = 0; // Keyboard navigation: currently focused node
16
- var keyboardNavigationEnabled = false; // Track if keyboard nav is active
17
- var lastFocusedElement = null; // Store last focused element for modal return
18
-
19
- // ============================================================================
20
- // CLUSTER COLORS
21
- // ============================================================================
22
-
23
- const CLUSTER_COLORS = [
24
- '#667eea', '#764ba2', '#43e97b', '#38f9d7',
25
- '#4facfe', '#00f2fe', '#f093fb', '#f5576c',
26
- '#fa709a', '#fee140', '#30cfd0', '#330867'
27
- ];
28
-
29
- function getClusterColor(cluster_id) {
30
- if (!cluster_id) return '#999';
31
- return CLUSTER_COLORS[cluster_id % CLUSTER_COLORS.length];
32
- }
33
-
34
- // ============================================================================
35
- // HTML ESCAPE UTILITY
36
- // ============================================================================
37
-
38
- function escapeHtml(text) {
39
- const div = document.createElement('div');
40
- div.textContent = text;
41
- return div.innerHTML;
42
- }
43
-
44
- // ============================================================================
45
- // GRAPH LOADING
46
- // ============================================================================
47
-
48
- async function loadGraph() {
49
- var maxNodes = document.getElementById('graph-max-nodes').value;
50
- var minImportance = document.getElementById('graph-min-importance')?.value || 1;
51
-
52
- // Get cluster filter from URL params ONLY on initial load
53
- // After that, use filterState (which tab event handler controls)
54
- if (isInitialLoad) {
55
- const urlParams = new URLSearchParams(window.location.search);
56
- const clusterIdParam = urlParams.get('cluster_id');
57
- if (clusterIdParam) {
58
- filterState.cluster_id = parseInt(clusterIdParam);
59
- console.log('[loadGraph] Initial load with cluster filter from URL:', filterState.cluster_id);
60
- }
61
- isInitialLoad = false;
62
- }
63
-
64
- // CRITICAL FIX: When filtering by cluster, fetch MORE nodes to ensure all cluster members are included
65
- // Otherwise only top N memories are fetched and cluster filter fails
66
- const fetchLimit = filterState.cluster_id ? 200 : maxNodes;
67
- console.log('[loadGraph] Fetching with limit:', fetchLimit, 'Cluster filter:', filterState.cluster_id);
68
-
69
- try {
70
- showLoadingSpinner();
71
- const response = await fetch(`/api/graph?max_nodes=${fetchLimit}&min_importance=${minImportance}`);
72
- graphData = await response.json();
73
- originalGraphData = JSON.parse(JSON.stringify(graphData)); // Deep copy
74
-
75
- // Apply filters if set
76
- if (filterState.cluster_id) {
77
- graphData = filterByCluster(originalGraphData, filterState.cluster_id);
78
- }
79
- if (filterState.entity) {
80
- graphData = filterByEntity(originalGraphData, filterState.entity);
81
- }
82
-
83
- renderGraph(graphData);
84
- hideLoadingSpinner();
85
- } catch (error) {
86
- console.error('Error loading graph:', error);
87
- showError('Failed to load graph. Please try again.');
88
- hideLoadingSpinner();
89
- }
90
- }
91
-
92
- // ============================================================================
93
- // DATA TRANSFORMATION
94
- // ============================================================================
95
-
96
- function transformDataForCytoscape(data) {
97
- const elements = [];
98
-
99
- // Add nodes
100
- data.nodes.forEach(node => {
101
- const contentPreview = node.content_preview || node.summary || node.content || '';
102
- // Label: first 4 words of content (readable on node), fallback to category
103
- const contentWords = contentPreview.split(/\s+/).slice(0, 4).join(' ');
104
- const label = contentWords || node.category || `Memory #${node.id}`;
105
- const preview = contentPreview.substring(0, 50) + (contentPreview.length > 50 ? '...' : '');
106
-
107
- elements.push({
108
- group: 'nodes',
109
- data: {
110
- id: String(node.id),
111
- label: label,
112
- content: node.content || '',
113
- summary: node.summary || '',
114
- content_preview: preview,
115
- category: node.category || '',
116
- project_name: node.project_name || '',
117
- cluster_id: node.cluster_id || 0,
118
- importance: node.importance || 5,
119
- tags: node.tags || '',
120
- entities: node.entities || [],
121
- created_at: node.created_at || '',
122
- // For rendering
123
- weight: (node.importance || 5) * 5 // Size multiplier
124
- }
125
- });
126
- });
127
-
128
- // Add edges
129
- data.links.forEach(link => {
130
- const sourceId = String(typeof link.source === 'object' ? link.source.id : link.source);
131
- const targetId = String(typeof link.target === 'object' ? link.target.id : link.target);
132
-
133
- elements.push({
134
- group: 'edges',
135
- data: {
136
- id: `${sourceId}-${targetId}`,
137
- source: sourceId,
138
- target: targetId,
139
- weight: link.weight || 0.5,
140
- relationship_type: link.relationship_type || '',
141
- shared_entities: link.shared_entities || []
142
- }
143
- });
144
- });
145
-
146
- return elements;
147
- }
148
-
149
- // ============================================================================
150
- // GRAPH RENDERING
151
- // ============================================================================
152
-
153
- function renderGraph(data) {
154
- const container = document.getElementById('graph-container');
155
- if (!container) {
156
- console.error('Graph container not found');
157
- return;
158
- }
159
-
160
- // Clear existing graph
161
- if (cy) {
162
- cy.destroy();
163
- }
164
-
165
- // Check node count for performance tier
166
- const nodeCount = data.nodes.length;
167
- let layout = determineBestLayout(nodeCount);
168
-
169
- // Transform data
170
- const elements = transformDataForCytoscape(data);
171
-
172
- if (elements.length === 0) {
173
- const emptyMsg = document.createElement('div');
174
- emptyMsg.style.cssText = 'text-align:center; padding:50px; color:#666;';
175
- emptyMsg.textContent = 'No memories found. Try adjusting filters.';
176
- container.textContent = ''; // Clear safely
177
- container.appendChild(emptyMsg);
178
- return;
179
- }
180
-
181
- // Clear container (remove spinner/old content) - safe approach
182
- container.textContent = '';
183
- console.log('[renderGraph] Container cleared, initializing Cytoscape with', elements.length, 'elements');
184
-
185
- // Initialize Cytoscape WITHOUT running layout yet
186
- try {
187
- cy = cytoscape({
188
- container: container,
189
- elements: elements,
190
- style: getCytoscapeStyles(),
191
- layout: { name: 'preset' }, // Don't run layout yet
192
- minZoom: 0.2,
193
- maxZoom: 3,
194
- wheelSensitivity: 0.05, // Reduced from 0.2 - less sensitive zoom
195
- autoungrabify: false,
196
- autounselectify: false,
197
- // Mobile touch support
198
- touchTapThreshold: 8, // Pixels of movement allowed for tap (vs drag)
199
- desktopTapThreshold: 4, // Desktop click threshold
200
- pixelRatio: 'auto' // Retina/HiDPI support
201
- });
202
- console.log('[renderGraph] Cytoscape initialized successfully, nodes:', cy.nodes().length);
203
- } catch (error) {
204
- console.error('[renderGraph] Cytoscape initialization failed:', error);
205
- showError('Failed to render graph: ' + error.message);
206
- return;
207
- }
208
-
209
- // Add interactions
210
- addCytoscapeInteractions();
211
-
212
- // Add navigator (mini-map) if available
213
- if (cy.navigator && nodeCount > 50) {
214
- cy.navigator({
215
- container: false, // Will be added to DOM separately if needed
216
- viewLiveFramerate: 0,
217
- dblClickDelay: 200,
218
- removeCustomContainer: false,
219
- rerenderDelay: 100
220
- });
221
- }
222
-
223
- // Update UI
224
- updateFilterBadge();
225
- updateGraphStats(data);
226
-
227
- // Announce graph load to screen readers
228
- updateScreenReaderStatus(`Graph loaded with ${data.nodes.length} memories and ${data.links.length} connections`);
229
-
230
- // Check if we should restore saved layout or run fresh layout
231
- const hasSavedLayout = localStorage.getItem('slm_graph_layout');
232
-
233
- if (hasSavedLayout) {
234
- // Restore saved positions, then fit
235
- restoreSavedLayout();
236
- console.log('[renderGraph] Restored saved layout positions');
237
- cy.fit(null, 80);
238
- } else {
239
- // No saved layout - run layout algorithm with fit
240
- console.log('[renderGraph] Running fresh layout:', layout);
241
- const layoutConfig = getLayoutConfig(layout);
242
- const graphLayout = cy.layout(layoutConfig);
243
-
244
- // CRITICAL: Wait for layout to finish, THEN force fit
245
- graphLayout.on('layoutstop', function() {
246
- console.log('[renderGraph] Layout completed, forcing fit to viewport');
247
- cy.fit(null, 80); // Force center with 80px padding
248
- });
249
-
250
- graphLayout.run();
251
- }
252
- }
253
-
254
- // ============================================================================
255
- // LAYOUT ALGORITHMS
256
- // ============================================================================
257
-
258
- function determineBestLayout(nodeCount) {
259
- if (nodeCount <= 500) {
260
- return 'fcose'; // Full interactive graph
261
- } else if (nodeCount <= 2000) {
262
- return 'cose'; // Faster force-directed
263
- } else {
264
- return 'circle'; // Focus mode (circular for large graphs)
265
- }
266
- }
267
-
268
- function getLayoutConfig(layoutName) {
269
- const configs = {
270
- 'fcose': {
271
- name: 'fcose',
272
- quality: 'default',
273
- randomize: false,
274
- animate: false, // Disabled for stability
275
- fit: true,
276
- padding: 80, // Increased padding to keep within bounds
277
- nodeSeparation: 100,
278
- idealEdgeLength: 100,
279
- edgeElasticity: 0.45,
280
- nestingFactor: 0.1,
281
- gravity: 0.25,
282
- numIter: 2500,
283
- tile: true,
284
- tilingPaddingVertical: 10,
285
- tilingPaddingHorizontal: 10,
286
- gravityRangeCompound: 1.5,
287
- gravityCompound: 1.0,
288
- gravityRange: 3.8
289
- },
290
- 'cose': {
291
- name: 'cose',
292
- animate: false, // Disabled for stability
293
- fit: true,
294
- padding: 80, // Increased padding
295
- nodeRepulsion: 8000,
296
- idealEdgeLength: 100,
297
- edgeElasticity: 100,
298
- nestingFactor: 5,
299
- gravity: 80,
300
- numIter: 1000,
301
- randomize: false
302
- },
303
- 'circle': {
304
- name: 'circle',
305
- animate: true,
306
- animationDuration: 1000,
307
- fit: true,
308
- padding: 50,
309
- sort: function(a, b) {
310
- return b.data('importance') - a.data('importance');
311
- }
312
- },
313
- 'grid': {
314
- name: 'grid',
315
- animate: true,
316
- animationDuration: 1000,
317
- fit: true,
318
- padding: 50,
319
- sort: function(a, b) {
320
- return b.data('importance') - a.data('importance');
321
- }
322
- },
323
- 'breadthfirst': {
324
- name: 'breadthfirst',
325
- animate: true,
326
- animationDuration: 1000,
327
- fit: true,
328
- padding: 50,
329
- directed: false,
330
- circle: false,
331
- spacingFactor: 1.5,
332
- sort: function(a, b) {
333
- return b.data('importance') - a.data('importance');
334
- }
335
- },
336
- 'concentric': {
337
- name: 'concentric',
338
- animate: true,
339
- animationDuration: 1000,
340
- fit: true,
341
- padding: 50,
342
- concentric: function(node) {
343
- return node.data('importance');
344
- },
345
- levelWidth: function() {
346
- return 2;
347
- }
348
- }
349
- };
350
-
351
- return configs[layoutName] || configs['fcose'];
352
- }
353
-
354
- // ============================================================================
355
- // CYTOSCAPE STYLES
356
- // ============================================================================
357
-
358
- function getCytoscapeStyles() {
359
- return [
360
- {
361
- selector: 'node',
362
- style: {
363
- 'background-color': function(ele) {
364
- return getClusterColor(ele.data('cluster_id'));
365
- },
366
- 'width': function(ele) {
367
- return Math.max(20, ele.data('weight'));
368
- },
369
- 'height': function(ele) {
370
- return Math.max(20, ele.data('weight'));
371
- },
372
- 'label': 'data(label)',
373
- 'font-size': '10px',
374
- 'text-valign': 'center',
375
- 'text-halign': 'center',
376
- 'color': '#333',
377
- 'text-outline-width': 2,
378
- 'text-outline-color': '#fff',
379
- 'border-width': function(ele) {
380
- // Trust score → border thickness (if available)
381
- return 2;
382
- },
383
- 'border-color': '#555'
384
- }
385
- },
386
- {
387
- selector: 'node:selected',
388
- style: {
389
- 'border-width': 4,
390
- 'border-color': '#667eea',
391
- 'background-color': '#667eea'
392
- }
393
- },
394
- {
395
- selector: 'node.highlighted',
396
- style: {
397
- 'border-width': 4,
398
- 'border-color': '#ff6b6b',
399
- 'box-shadow': '0 0 20px #ff6b6b'
400
- }
401
- },
402
- {
403
- selector: 'node.dimmed',
404
- style: {
405
- 'opacity': 0.3
406
- }
407
- },
408
- {
409
- selector: 'node.keyboard-focused',
410
- style: {
411
- 'border-width': 5,
412
- 'border-color': '#0066ff',
413
- 'border-style': 'solid',
414
- 'box-shadow': '0 0 15px #0066ff'
415
- }
416
- },
417
- {
418
- selector: 'edge',
419
- style: {
420
- 'width': function(ele) {
421
- return Math.max(1, ele.data('weight') * 3);
422
- },
423
- 'line-color': '#ccc',
424
- 'line-style': function(ele) {
425
- return ele.data('weight') > 0.3 ? 'solid' : 'dashed';
426
- },
427
- 'curve-style': 'bezier',
428
- 'target-arrow-shape': 'none',
429
- 'opacity': 0.6
430
- }
431
- },
432
- {
433
- selector: 'edge.highlighted',
434
- style: {
435
- 'line-color': '#667eea',
436
- 'width': 3,
437
- 'opacity': 1
438
- }
439
- },
440
- {
441
- selector: 'edge.dimmed',
442
- style: {
443
- 'opacity': 0.1
444
- }
445
- }
446
- ];
447
- }
@@ -1,220 +0,0 @@
1
- // SuperLocalMemory V2.6.5 - Interactive Knowledge Graph - Filtering 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 LOGIC
7
- // ============================================================================
8
-
9
- function filterByCluster(data, cluster_id) {
10
- console.log('[filterByCluster] Filtering for cluster_id:', cluster_id, 'Type:', typeof cluster_id);
11
- console.log('[filterByCluster] Total nodes in data:', data.nodes.length);
12
-
13
- // Convert to integer for comparison
14
- const targetClusterId = parseInt(cluster_id);
15
- let debugCount = 0;
16
-
17
- const filteredNodes = data.nodes.filter(n => {
18
- // Convert node cluster_id to integer for comparison
19
- const nodeClusterId = n.cluster_id ? parseInt(n.cluster_id) : null;
20
-
21
- // Log first 3 nodes to debug
22
- if (debugCount < 3) {
23
- console.log('[filterByCluster] Sample node:', {
24
- id: n.id,
25
- cluster_id: n.cluster_id,
26
- type: typeof n.cluster_id,
27
- parsed: nodeClusterId,
28
- match: nodeClusterId === targetClusterId
29
- });
30
- debugCount++;
31
- }
32
-
33
- // Use strict equality after type conversion
34
- return nodeClusterId === targetClusterId;
35
- });
36
-
37
- console.log('[filterByCluster] Filtered to', filteredNodes.length, 'nodes');
38
-
39
- const nodeIds = new Set(filteredNodes.map(n => n.id));
40
- const filteredLinks = data.links.filter(l =>
41
- nodeIds.has(l.source) && nodeIds.has(l.target)
42
- );
43
-
44
- console.log('[filterByCluster] Found', filteredLinks.length, 'edges between filtered nodes');
45
-
46
- return { nodes: filteredNodes, links: filteredLinks, clusters: data.clusters };
47
- }
48
-
49
- function filterByEntity(data, entity) {
50
- const filteredNodes = data.nodes.filter(n => {
51
- if (n.entities && Array.isArray(n.entities)) {
52
- return n.entities.includes(entity);
53
- }
54
- if (n.tags && n.tags.includes(entity)) {
55
- return true;
56
- }
57
- return false;
58
- });
59
- const nodeIds = new Set(filteredNodes.map(n => n.id));
60
- const filteredLinks = data.links.filter(l =>
61
- nodeIds.has(l.source) && nodeIds.has(l.target)
62
- );
63
- return { nodes: filteredNodes, links: filteredLinks, clusters: data.clusters };
64
- }
65
-
66
- function clearGraphFilters() {
67
- console.log('[clearGraphFilters] Clearing all filters and reloading full graph');
68
-
69
- // Clear filter state
70
- filterState = { cluster_id: null, entity: null };
71
-
72
- // CRITICAL: Clear URL completely - remove ?cluster_id=X from address bar
73
- const cleanUrl = window.location.origin + window.location.pathname;
74
- window.history.replaceState({}, '', cleanUrl);
75
- console.log('[clearGraphFilters] URL cleaned to:', cleanUrl);
76
-
77
- // CRITICAL: Clear saved layout positions so nodes don't stay in corner
78
- // When switching from filtered to full graph, we want fresh layout
79
- localStorage.removeItem('slm_graph_layout');
80
- console.log('[clearGraphFilters] Cleared saved layout positions');
81
-
82
- // Reload full graph from API (respects dropdown settings)
83
- loadGraph();
84
- }
85
-
86
- // ============================================================================
87
- // EVENT HANDLERS
88
- // ============================================================================
89
-
90
- function setupGraphEventListeners() {
91
- // Load graph when Graph tab is clicked
92
- const graphTab = document.querySelector('a[href="#graph"]');
93
- if (graphTab) {
94
- // CRITICAL FIX: Add click handler to clear filter even when already on KG tab
95
- // Bootstrap's shown.bs.tab only fires when SWITCHING TO tab, not when clicking same tab!
96
- graphTab.addEventListener('click', function(event) {
97
- console.log('[Event] Knowledge Graph tab CLICKED');
98
-
99
- // Check if we're viewing a filtered graph
100
- const hasFilter = filterState.cluster_id || filterState.entity;
101
-
102
- if (hasFilter && cy) {
103
- console.log('[Event] Click detected on KG tab while filter active - clearing filter');
104
-
105
- // Clear filter state
106
- filterState = { cluster_id: null, entity: null };
107
-
108
- // Clear URL completely
109
- const cleanUrl = window.location.origin + window.location.pathname;
110
- window.history.replaceState({}, '', cleanUrl);
111
- console.log('[Event] URL cleaned to:', cleanUrl);
112
-
113
- // CRITICAL: Clear saved layout positions so nodes don't stay in corner
114
- localStorage.removeItem('slm_graph_layout');
115
- console.log('[Event] Cleared saved layout positions');
116
-
117
- // Reload with full graph
118
- loadGraph();
119
- }
120
- });
121
-
122
- // Also keep shown.bs.tab for when switching FROM another tab TO KG tab
123
- graphTab.addEventListener('shown.bs.tab', function(event) {
124
- console.log('[Event] Knowledge Graph tab SHOWN (tab switch)');
125
-
126
- if (cy) {
127
- // Graph already exists - user is returning to KG tab from another tab
128
- // Clear filter and reload to show full graph
129
- console.log('[Event] Returning to KG tab from another tab - clearing filter');
130
-
131
- // Clear filter state
132
- filterState = { cluster_id: null, entity: null };
133
-
134
- // Clear URL completely
135
- const cleanUrl = window.location.origin + window.location.pathname;
136
- window.history.replaceState({}, '', cleanUrl);
137
- console.log('[Event] URL cleaned to:', cleanUrl);
138
-
139
- // CRITICAL: Clear saved layout positions so nodes don't stay in corner
140
- localStorage.removeItem('slm_graph_layout');
141
- console.log('[Event] Cleared saved layout positions');
142
-
143
- // Reload with full graph (respects dropdown settings)
144
- loadGraph();
145
- } else {
146
- // First load - check if cluster filter is in URL (from cluster badge click)
147
- const urlParams = new URLSearchParams(window.location.search);
148
- const clusterIdParam = urlParams.get('cluster_id');
149
-
150
- if (clusterIdParam) {
151
- console.log('[Event] First load with cluster filter:', clusterIdParam);
152
- // Load with filter (will be applied in loadGraph)
153
- } else {
154
- console.log('[Event] First load, no filter');
155
- }
156
-
157
- loadGraph();
158
- }
159
- });
160
- }
161
-
162
- // Reload graph when dropdown settings change
163
- const maxNodesSelect = document.getElementById('graph-max-nodes');
164
- if (maxNodesSelect) {
165
- maxNodesSelect.addEventListener('change', function() {
166
- console.log('[Event] Max nodes changed to:', this.value);
167
-
168
- // Clear any active filter when user manually changes settings
169
- if (filterState.cluster_id || filterState.entity) {
170
- console.log('[Event] Clearing filter due to dropdown change');
171
- filterState = { cluster_id: null, entity: null };
172
-
173
- // Clear URL
174
- const cleanUrl = window.location.origin + window.location.pathname;
175
- window.history.replaceState({}, '', cleanUrl);
176
- }
177
-
178
- // Clear saved layout when changing node count (different # of nodes = different layout)
179
- localStorage.removeItem('slm_graph_layout');
180
- console.log('[Event] Cleared saved layout due to settings change');
181
-
182
- loadGraph();
183
- });
184
- }
185
-
186
- const minImportanceSelect = document.getElementById('graph-min-importance');
187
- if (minImportanceSelect) {
188
- minImportanceSelect.addEventListener('change', function() {
189
- console.log('[Event] Min importance changed to:', this.value);
190
-
191
- // Clear any active filter when user manually changes settings
192
- if (filterState.cluster_id || filterState.entity) {
193
- console.log('[Event] Clearing filter due to dropdown change');
194
- filterState = { cluster_id: null, entity: null };
195
-
196
- // Clear URL
197
- const cleanUrl = window.location.origin + window.location.pathname;
198
- window.history.replaceState({}, '', cleanUrl);
199
- }
200
-
201
- // Clear saved layout when changing importance (different nodes = different layout)
202
- localStorage.removeItem('slm_graph_layout');
203
- console.log('[Event] Cleared saved layout due to settings change');
204
-
205
- loadGraph();
206
- });
207
- }
208
-
209
- console.log('[Init] Graph event listeners setup complete');
210
- }
211
-
212
- // ============================================================================
213
- // INITIALIZATION
214
- // ============================================================================
215
-
216
- if (document.readyState === 'loading') {
217
- document.addEventListener('DOMContentLoaded', setupGraphEventListeners);
218
- } else {
219
- setupGraphEventListeners();
220
- }