superlocalmemory 3.4.3 → 3.4.5

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.
@@ -0,0 +1,471 @@
1
+ // Neural Glass Shell — Dashboard V2 "Neural Glass"
2
+ // Injects sidebar, restructures DOM, handles navigation
3
+ // Progressive enhancement: if this fails, Bootstrap tabs still work
4
+
5
+ (function() {
6
+ 'use strict';
7
+
8
+ // ── Sidebar Navigation Config ──────────────────────────────
9
+ var NAV_SECTIONS = [
10
+ {
11
+ label: 'Memory',
12
+ items: [
13
+ { id: 'dashboard-pane', icon: 'bi-speedometer2', text: 'Dashboard' },
14
+ { id: 'graph-pane', icon: 'bi-diagram-3', text: 'Knowledge Graph' },
15
+ { id: 'memories-pane', icon: 'bi-list-ul', text: 'Memories' },
16
+ { id: 'recall-lab-pane', icon: 'bi-search-heart', text: 'Recall Lab' },
17
+ { id: 'timeline-pane', icon: 'bi-clock-history', text: 'Timeline' }
18
+ ]
19
+ },
20
+ {
21
+ label: 'Intelligence',
22
+ items: [
23
+ { id: 'clusters-pane', icon: 'bi-collection', text: 'Clusters' },
24
+ { id: 'patterns-pane', icon: 'bi-puzzle', text: 'Patterns' },
25
+ { id: 'learning-pane', icon: 'bi-mortarboard', text: 'Learning' },
26
+ { id: 'behavioral-pane', icon: 'bi-lightbulb', text: 'Behavioral' }
27
+ ]
28
+ },
29
+ {
30
+ label: 'System',
31
+ items: [
32
+ { id: 'events-pane', icon: 'bi-broadcast', text: 'Live Events' },
33
+ { id: 'agents-pane', icon: 'bi-robot', text: 'Agents' },
34
+ { id: 'trust-pane', icon: 'bi-shield-check', text: 'Trust' },
35
+ { id: 'lifecycle-pane', icon: 'bi-hourglass-split', text: 'Lifecycle' },
36
+ { id: 'compliance-pane', icon: 'bi-shield-lock', text: 'Compliance' },
37
+ { id: 'math-health-pane', icon: 'bi-calculator', text: 'Math Health' },
38
+ { id: 'ide-pane', icon: 'bi-plug', text: 'IDEs' }
39
+ ]
40
+ },
41
+ {
42
+ label: 'v3.4.3',
43
+ items: [
44
+ { id: 'health-pane', icon: 'bi-heart-pulse', text: 'Health Monitor', badge: 'NEW' },
45
+ { id: 'ingestion-pane', icon: 'bi-cloud-download', text: 'Ingestion', badge: 'NEW' },
46
+ { id: 'entities-pane', icon: 'bi-person-badge', text: 'Entity Explorer', badge: 'NEW' },
47
+ { id: 'mesh-pane', icon: 'bi-share', text: 'Mesh Peers', badge: 'NEW' }
48
+ ]
49
+ },
50
+ {
51
+ label: 'Config',
52
+ items: [
53
+ { id: 'settings-pane', icon: 'bi-gear', text: 'Settings' }
54
+ ]
55
+ }
56
+ ];
57
+
58
+ // ── Build Sidebar HTML ─────────────────────────────────────
59
+ function buildSidebar() {
60
+ var sidebar = document.createElement('aside');
61
+ sidebar.className = 'ng-sidebar';
62
+ sidebar.id = 'ng-sidebar';
63
+ sidebar.setAttribute('role', 'navigation');
64
+ sidebar.setAttribute('aria-label', 'Main navigation');
65
+
66
+ // Header
67
+ var header = document.createElement('div');
68
+ header.className = 'ng-sidebar-header';
69
+ header.innerHTML =
70
+ '<div class="ng-sidebar-brand">' +
71
+ '<div class="ng-sidebar-brand-icon">' +
72
+ '<i class="bi bi-diamond-fill" style="font-size:0.875rem"></i>' +
73
+ '</div>' +
74
+ '<div>' +
75
+ '<div class="ng-sidebar-brand-text">SuperLocalMemory</div>' +
76
+ '<div class="ng-sidebar-brand-version" id="ng-version">v3.4.4</div>' +
77
+ '</div>' +
78
+ '</div>';
79
+ sidebar.appendChild(header);
80
+
81
+ // Nav
82
+ var nav = document.createElement('nav');
83
+ nav.className = 'ng-sidebar-nav';
84
+
85
+ NAV_SECTIONS.forEach(function(section) {
86
+ var sDiv = document.createElement('div');
87
+ sDiv.className = 'ng-sidebar-section';
88
+
89
+ var label = document.createElement('div');
90
+ label.className = 'ng-sidebar-section-label';
91
+ label.textContent = section.label;
92
+ sDiv.appendChild(label);
93
+
94
+ section.items.forEach(function(item) {
95
+ var a = document.createElement('a');
96
+ a.className = 'ng-sidebar-item';
97
+ a.setAttribute('data-target', item.id);
98
+ a.setAttribute('role', 'tab');
99
+ a.setAttribute('aria-controls', item.id);
100
+ a.setAttribute('tabindex', '0');
101
+ if (item.id === 'dashboard-pane') a.classList.add('active');
102
+
103
+ a.innerHTML =
104
+ '<i class="bi ' + item.icon + ' ng-sidebar-icon"></i>' +
105
+ '<span>' + item.text + '</span>' +
106
+ (item.badge ? '<span class="ng-sidebar-badge">' + item.badge + '</span>' : '');
107
+
108
+ a.addEventListener('click', function(e) {
109
+ e.preventDefault();
110
+ activateTab(item.id);
111
+ });
112
+
113
+ a.addEventListener('keydown', function(e) {
114
+ if (e.key === 'Enter' || e.key === ' ') {
115
+ e.preventDefault();
116
+ activateTab(item.id);
117
+ }
118
+ });
119
+
120
+ sDiv.appendChild(a);
121
+ });
122
+
123
+ nav.appendChild(sDiv);
124
+ });
125
+
126
+ sidebar.appendChild(nav);
127
+
128
+ // Footer
129
+ var footer = document.createElement('div');
130
+ footer.className = 'ng-sidebar-footer';
131
+
132
+ // Move profile selector from navbar to sidebar footer
133
+ var profileSelect = document.getElementById('profile-select');
134
+ var addProfileBtn = document.getElementById('add-profile-btn');
135
+
136
+ footer.innerHTML =
137
+ '<div style="margin-bottom:8px">' +
138
+ '<div class="ng-sidebar-section-label" style="padding:0 0 4px">Profile</div>' +
139
+ '<div style="display:flex;gap:4px;align-items:center" id="ng-profile-container"></div>' +
140
+ '</div>' +
141
+ '<div style="display:flex;gap:4px;align-items:center">' +
142
+ '<button class="ng-btn" id="ng-refresh-btn" title="Refresh" style="flex:1;justify-content:center">' +
143
+ '<i class="bi bi-arrow-clockwise"></i>' +
144
+ '</button>' +
145
+ '<button class="ng-btn" id="ng-theme-toggle" title="Toggle theme" onclick="toggleDarkMode()" style="flex:1;justify-content:center">' +
146
+ '<i class="bi bi-sun-fill" id="ng-theme-icon"></i>' +
147
+ '</button>' +
148
+ '<button class="ng-btn" id="ng-privacy-btn" title="Privacy blur (for screen recording)" onclick="togglePrivacyBlur()" style="flex:1;justify-content:center">' +
149
+ '<i class="bi bi-eye-slash" id="ng-privacy-icon"></i>' +
150
+ '</button>' +
151
+ '<a href="https://github.com/qualixar/superlocalmemory" target="_blank" class="ng-btn" title="Star on GitHub" style="flex:1;justify-content:center">' +
152
+ '<i class="bi bi-github"></i>' +
153
+ '</a>' +
154
+ '</div>';
155
+
156
+ sidebar.appendChild(footer);
157
+
158
+ return sidebar;
159
+ }
160
+
161
+ // ── Restructure DOM ────────────────────────────────────────
162
+ function restructureDOM() {
163
+ var body = document.body;
164
+ // Target the MAIN container-fluid (direct child of body), not the one inside navbar
165
+ var containers = document.querySelectorAll('body > .container-fluid');
166
+ var container = containers.length > 0 ? containers[containers.length - 1] : null;
167
+ if (!container) {
168
+ // Fallback: find the container that holds the tab-content
169
+ container = document.querySelector('.tab-content')?.closest('.container-fluid');
170
+ }
171
+ if (!container) return;
172
+
173
+ // Create shell wrapper
174
+ var shell = document.createElement('div');
175
+ shell.className = 'ng-shell';
176
+
177
+ // Build and insert sidebar
178
+ var sidebar = buildSidebar();
179
+ shell.appendChild(sidebar);
180
+
181
+ // Create content wrapper
182
+ var content = document.createElement('main');
183
+ content.className = 'ng-content';
184
+ var inner = document.createElement('div');
185
+ inner.className = 'ng-content-inner';
186
+
187
+ // Move container content into inner
188
+ while (container.firstChild) {
189
+ inner.appendChild(container.firstChild);
190
+ }
191
+ content.appendChild(inner);
192
+ shell.appendChild(content);
193
+
194
+ // Move footer into content
195
+ var existingFooter = document.querySelector('footer');
196
+ if (existingFooter) {
197
+ content.appendChild(existingFooter);
198
+ }
199
+
200
+ // Replace container with shell
201
+ container.parentNode.replaceChild(shell, container);
202
+
203
+ // Move dashboard-only elements INTO the dashboard-pane so they scroll with it
204
+ var dashboardPane = document.getElementById('dashboard-pane');
205
+ if (dashboardPane) {
206
+ ['stats-container', 'privacy-notice', 'feedback-progress'].forEach(function(id) {
207
+ var el = document.getElementById(id);
208
+ if (el) dashboardPane.insertBefore(el, dashboardPane.firstChild);
209
+ });
210
+ }
211
+
212
+ // Move profile selector to sidebar footer
213
+ var profileContainer = document.getElementById('ng-profile-container');
214
+ var profileSelect = document.getElementById('profile-select');
215
+ var addProfileBtn = document.getElementById('add-profile-btn');
216
+ if (profileContainer && profileSelect) {
217
+ profileSelect.classList.remove('profile-select');
218
+ profileSelect.style.cssText = 'flex:1;font-size:0.8125rem;';
219
+ profileContainer.appendChild(profileSelect);
220
+ if (addProfileBtn) {
221
+ addProfileBtn.className = 'ng-btn';
222
+ addProfileBtn.style.cssText = 'padding:4px 8px;';
223
+ profileContainer.appendChild(addProfileBtn);
224
+ }
225
+ }
226
+
227
+ // Wire refresh button
228
+ var refreshBtn = document.getElementById('ng-refresh-btn');
229
+ if (refreshBtn && typeof refreshDashboard === 'function') {
230
+ refreshBtn.addEventListener('click', refreshDashboard);
231
+ }
232
+
233
+ // Sync version from dashboard
234
+ setTimeout(function() {
235
+ var dashVer = document.getElementById('dashboard-version');
236
+ var ngVer = document.getElementById('ng-version');
237
+ if (dashVer && ngVer && dashVer.textContent !== '...') {
238
+ ngVer.textContent = 'v' + dashVer.textContent;
239
+ }
240
+ }, 1500);
241
+ }
242
+
243
+ // ── Tab Activation ─────────────────────────────────────────
244
+ function activateTab(targetId) {
245
+ // Deactivate all sidebar items
246
+ document.querySelectorAll('.ng-sidebar-item').forEach(function(item) {
247
+ item.classList.remove('active');
248
+ item.setAttribute('aria-selected', 'false');
249
+ });
250
+
251
+ // Activate clicked sidebar item
252
+ var activeItem = document.querySelector('.ng-sidebar-item[data-target="' + targetId + '"]');
253
+ if (activeItem) {
254
+ activeItem.classList.add('active');
255
+ activeItem.setAttribute('aria-selected', 'true');
256
+ }
257
+
258
+ // Deactivate all tab panes
259
+ document.querySelectorAll('.tab-pane').forEach(function(pane) {
260
+ pane.classList.remove('show', 'active');
261
+ });
262
+
263
+ // Activate target pane
264
+ var targetPane = document.getElementById(targetId);
265
+ if (targetPane) {
266
+ targetPane.classList.add('show', 'active');
267
+ }
268
+
269
+ // Dispatch Bootstrap tab event for backward compat
270
+ var tabButton = document.getElementById(targetId.replace('-pane', '-tab'));
271
+ if (tabButton) {
272
+ try {
273
+ var event = new Event('shown.bs.tab', { bubbles: true });
274
+ event.target = tabButton;
275
+ event.relatedTarget = null;
276
+ tabButton.dispatchEvent(event);
277
+ } catch (e) {
278
+ // Ignore if event dispatch fails
279
+ }
280
+ }
281
+
282
+ // Update URL hash via replaceState (avoids auto-scroll to hash target)
283
+ history.replaceState(null, '', '#' + targetId);
284
+
285
+ // Scroll content to top on tab switch
286
+ window.scrollTo({ top: 0, behavior: 'instant' });
287
+ var contentEl = document.querySelector('.ng-content');
288
+ if (contentEl) contentEl.scrollTo({ top: 0, behavior: 'instant' });
289
+
290
+ // Dashboard-only elements are inside dashboard-pane, so they auto-hide/show with the tab
291
+
292
+ // Trigger data loading for lazy-loaded tabs (immediate + deferred for async-heavy tabs)
293
+ triggerTabLoad(targetId);
294
+ // Deferred retry for tabs that need API data to populate
295
+ setTimeout(function() { triggerTabLoad(targetId); }, 500);
296
+
297
+ // Scroll sidebar item into view
298
+ if (activeItem) {
299
+ activeItem.scrollIntoView({ behavior: 'smooth', inline: 'center', block: 'nearest' });
300
+ }
301
+ }
302
+
303
+ // ── Lazy Load Tab Data ─────────────────────────────────────
304
+ function triggerTabLoad(tabId) {
305
+ switch(tabId) {
306
+ case 'graph-pane':
307
+ if (typeof loadGraph === 'function') loadGraph();
308
+ // v3.4.4: Initialize chat panel if not already present
309
+ if (typeof initMemoryChat === 'function' && !document.getElementById('chat-panel')) {
310
+ initMemoryChat();
311
+ }
312
+ break;
313
+ case 'memories-pane':
314
+ if (typeof loadMemories === 'function') loadMemories();
315
+ break;
316
+ case 'clusters-pane':
317
+ if (typeof loadClusters === 'function') loadClusters();
318
+ break;
319
+ case 'patterns-pane':
320
+ if (typeof loadPatterns === 'function') loadPatterns();
321
+ break;
322
+ case 'timeline-pane':
323
+ if (typeof loadTimeline === 'function') loadTimeline();
324
+ break;
325
+ case 'events-pane':
326
+ if (typeof initEventStream === 'function') initEventStream();
327
+ if (typeof loadEventStats === 'function') loadEventStats();
328
+ break;
329
+ case 'agents-pane':
330
+ if (typeof loadAgents === 'function') loadAgents();
331
+ break;
332
+ case 'learning-pane':
333
+ if (typeof loadLearning === 'function') loadLearning();
334
+ break;
335
+ case 'trust-pane':
336
+ if (typeof loadTrustDashboard === 'function') loadTrustDashboard();
337
+ break;
338
+ case 'lifecycle-pane':
339
+ if (typeof loadLifecycle === 'function') loadLifecycle();
340
+ break;
341
+ case 'behavioral-pane':
342
+ if (typeof loadBehavioral === 'function') loadBehavioral();
343
+ break;
344
+ case 'compliance-pane':
345
+ if (typeof loadCompliance === 'function') loadCompliance();
346
+ break;
347
+ case 'math-health-pane':
348
+ if (typeof loadMathHealth === 'function') loadMathHealth();
349
+ break;
350
+ case 'ide-pane':
351
+ if (typeof loadIDEStatus === 'function') loadIDEStatus();
352
+ break;
353
+ case 'health-pane':
354
+ if (typeof loadHealthMonitor === 'function') loadHealthMonitor();
355
+ break;
356
+ case 'ingestion-pane':
357
+ if (typeof loadIngestionStatus === 'function') loadIngestionStatus();
358
+ break;
359
+ case 'entities-pane':
360
+ if (typeof loadEntityExplorer === 'function') loadEntityExplorer();
361
+ break;
362
+ case 'mesh-pane':
363
+ if (typeof loadMeshPeers === 'function') loadMeshPeers();
364
+ break;
365
+ case 'settings-pane':
366
+ if (typeof loadSettings === 'function') loadSettings();
367
+ if (typeof loadModeSettings === 'function') loadModeSettings();
368
+ if (typeof loadAutoSettings === 'function') loadAutoSettings();
369
+ if (typeof updateModeUI === 'function') updateModeUI();
370
+ break;
371
+ }
372
+ }
373
+
374
+ // ── Hash-based Routing ─────────────────────────────────────
375
+ function handleHash() {
376
+ var hash = window.location.hash.replace('#', '');
377
+ if (hash && document.getElementById(hash)) {
378
+ activateTab(hash);
379
+ }
380
+ }
381
+
382
+ // ── Theme Application ───────────────────────────────────────
383
+ function applyNgTheme(theme) {
384
+ document.documentElement.setAttribute('data-bs-theme', theme);
385
+ // ng-dark class ONLY in dark mode — light mode uses Bootstrap defaults
386
+ if (theme === 'dark') {
387
+ document.body.classList.add('ng-dark');
388
+ } else {
389
+ document.body.classList.remove('ng-dark');
390
+ }
391
+ // Update both icons (original + sidebar)
392
+ var icon = document.getElementById('theme-icon');
393
+ if (icon) icon.className = theme === 'dark' ? 'bi bi-moon-stars-fill' : 'bi bi-sun-fill';
394
+ var ngIcon = document.getElementById('ng-theme-icon');
395
+ if (ngIcon) ngIcon.className = theme === 'dark' ? 'bi bi-moon-stars-fill' : 'bi bi-sun-fill';
396
+
397
+ // Fix inline styles that conflict with dark/light mode (setProperty needed to override inline styles)
398
+ var themedElements = ['graph-container', 'memory-timeline-chart'];
399
+ themedElements.forEach(function(id) {
400
+ var el = document.getElementById(id);
401
+ if (!el) return;
402
+ if (theme === 'dark') {
403
+ el.style.setProperty('background', '#0f1012', 'important');
404
+ el.style.setProperty('background-color', '#0f1012', 'important');
405
+ el.style.setProperty('border-color', 'rgba(255,255,255,0.06)', 'important');
406
+ } else {
407
+ el.style.setProperty('background', '#ffffff', 'important');
408
+ el.style.setProperty('background-color', '#ffffff', 'important');
409
+ el.style.setProperty('border-color', '#e5e7eb', 'important');
410
+ }
411
+ });
412
+
413
+ // Also fix graph label colors for readability
414
+ var graphLabels = document.querySelectorAll('.node-label');
415
+ graphLabels.forEach(function(l) {
416
+ l.style.fill = theme === 'dark' ? '#ccc' : '#333';
417
+ });
418
+ }
419
+
420
+ // ── Initialize ─────────────────────────────────────────────
421
+ function init() {
422
+ // Respect saved theme or auto-detect
423
+ var savedTheme = localStorage.getItem('slm-theme');
424
+ if (!savedTheme) {
425
+ savedTheme = window.matchMedia('(prefers-color-scheme: dark)').matches ? 'dark' : 'light';
426
+ }
427
+ applyNgTheme(savedTheme);
428
+
429
+ // Override toggleDarkMode — syncs Bootstrap theme + ng-dark class
430
+ window.toggleDarkMode = function() {
431
+ var current = document.documentElement.getAttribute('data-bs-theme');
432
+ var next = current === 'dark' ? 'light' : 'dark';
433
+ localStorage.setItem('slm-theme', next);
434
+ applyNgTheme(next);
435
+ };
436
+
437
+ // Restructure DOM
438
+ restructureDOM();
439
+
440
+ // Handle initial hash — run synchronously, no timeout race
441
+ if (window.location.hash) {
442
+ handleHash();
443
+ }
444
+
445
+ // Listen for hash changes
446
+ window.addEventListener('hashchange', handleHash);
447
+
448
+ // toggleDarkMode already overridden above with theme toggle support
449
+
450
+ // Privacy blur toggle for screen recording
451
+ window.togglePrivacyBlur = function() {
452
+ document.body.classList.toggle('ng-privacy-blur');
453
+ var icon = document.getElementById('ng-privacy-icon');
454
+ var isBlurred = document.body.classList.contains('ng-privacy-blur');
455
+ if (icon) icon.className = isBlurred ? 'bi bi-eye' : 'bi bi-eye-slash';
456
+ };
457
+
458
+ // Auto-enable blur if URL has ?blur=1
459
+ if (window.location.search.indexOf('blur=1') >= 0) {
460
+ document.body.classList.add('ng-privacy-blur');
461
+ }
462
+ }
463
+
464
+ // Run when DOM is ready
465
+ if (document.readyState === 'loading') {
466
+ document.addEventListener('DOMContentLoaded', init);
467
+ } else {
468
+ init();
469
+ }
470
+
471
+ })();