superlocalmemory 3.4.18 → 3.4.21
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 +35 -0
- package/README.md +42 -34
- package/bin/slm +11 -0
- package/bin/slm.bat +12 -0
- package/package.json +4 -3
- package/pyproject.toml +3 -2
- package/scripts/build-slm-hook.ps1 +40 -0
- package/scripts/build-slm-hook.sh +45 -0
- package/scripts/build_entry.py +452 -0
- package/scripts/ci/stage5b_gate.sh +50 -0
- package/scripts/postinstall/validation.js +187 -0
- package/scripts/postinstall-interactive.js +756 -0
- package/scripts/postinstall_binary.js +287 -0
- package/scripts/release_manifest.py +273 -0
- package/scripts/slm-hook.spec +56 -0
- package/skills/slm-build-graph/SKILL.md +423 -0
- package/skills/slm-list-recent/SKILL.md +348 -0
- package/skills/slm-recall/SKILL.md +343 -0
- package/skills/slm-remember/SKILL.md +194 -0
- package/skills/slm-show-patterns/SKILL.md +224 -0
- package/skills/slm-status/SKILL.md +363 -0
- package/skills/slm-switch-profile/SKILL.md +442 -0
- package/src/superlocalmemory/cli/commands.py +219 -79
- package/src/superlocalmemory/cli/context_commands.py +192 -0
- package/src/superlocalmemory/cli/daemon.py +15 -1
- package/src/superlocalmemory/cli/db_migrate.py +80 -0
- package/src/superlocalmemory/cli/escape_hatch.py +220 -0
- package/src/superlocalmemory/cli/main.py +72 -1
- package/src/superlocalmemory/core/context_cache.py +397 -0
- package/src/superlocalmemory/core/embeddings.py +8 -2
- package/src/superlocalmemory/core/engine.py +38 -2
- package/src/superlocalmemory/core/engine_wiring.py +1 -1
- package/src/superlocalmemory/core/ram_lock.py +111 -0
- package/src/superlocalmemory/core/recall_pipeline.py +433 -3
- package/src/superlocalmemory/core/recall_worker.py +8 -3
- package/src/superlocalmemory/core/security_primitives.py +635 -0
- package/src/superlocalmemory/core/shadow_router.py +319 -0
- package/src/superlocalmemory/core/slm_disabled.py +87 -0
- package/src/superlocalmemory/core/slmignore.py +125 -0
- package/src/superlocalmemory/core/topic_signature.py +143 -0
- package/src/superlocalmemory/core/worker_pool.py +14 -3
- package/src/superlocalmemory/encoding/cognitive_consolidator.py +2 -2
- package/src/superlocalmemory/evolution/budget.py +321 -0
- package/src/superlocalmemory/evolution/llm_dispatch.py +508 -0
- package/src/superlocalmemory/evolution/skill_evolver.py +144 -94
- package/src/superlocalmemory/hooks/_outcome_common.py +506 -0
- package/src/superlocalmemory/hooks/adapter_base.py +317 -0
- package/src/superlocalmemory/hooks/antigravity_adapter.py +192 -0
- package/src/superlocalmemory/hooks/claude_code_hooks.py +33 -1
- package/src/superlocalmemory/hooks/context_payload.py +312 -0
- package/src/superlocalmemory/hooks/copilot_adapter.py +154 -0
- package/src/superlocalmemory/hooks/cross_platform_connector.py +90 -0
- package/src/superlocalmemory/hooks/cursor_adapter.py +195 -0
- package/src/superlocalmemory/hooks/hook_handlers.py +109 -8
- package/src/superlocalmemory/hooks/ide_connector.py +25 -2
- package/src/superlocalmemory/hooks/post_tool_async_hook.py +165 -0
- package/src/superlocalmemory/hooks/post_tool_outcome_hook.py +223 -0
- package/src/superlocalmemory/hooks/prewarm_auth.py +170 -0
- package/src/superlocalmemory/hooks/session_registry.py +186 -0
- package/src/superlocalmemory/hooks/stop_outcome_hook.py +134 -0
- package/src/superlocalmemory/hooks/sync_loop.py +114 -0
- package/src/superlocalmemory/hooks/user_prompt_hook.py +128 -0
- package/src/superlocalmemory/hooks/user_prompt_rehash_hook.py +202 -0
- package/src/superlocalmemory/infra/backup.py +3 -3
- package/src/superlocalmemory/infra/cloud_backup.py +2 -2
- package/src/superlocalmemory/infra/event_bus.py +2 -2
- package/src/superlocalmemory/infra/webhook_dispatcher.py +3 -3
- package/src/superlocalmemory/learning/arm_catalog.py +99 -0
- package/src/superlocalmemory/learning/bandit.py +526 -0
- package/src/superlocalmemory/learning/bandit_cache.py +133 -0
- package/src/superlocalmemory/learning/behavioral.py +53 -1
- package/src/superlocalmemory/learning/consolidation_cycle.py +381 -0
- package/src/superlocalmemory/learning/consolidation_worker.py +188 -520
- package/src/superlocalmemory/learning/database.py +256 -0
- package/src/superlocalmemory/learning/dedup_hnsw.py +413 -0
- package/src/superlocalmemory/learning/ensemble.py +300 -0
- package/src/superlocalmemory/learning/fact_outcome_joins.py +207 -0
- package/src/superlocalmemory/learning/forgetting_scheduler.py +55 -0
- package/src/superlocalmemory/learning/hnsw_dedup.py +69 -0
- package/src/superlocalmemory/learning/labeler.py +87 -0
- package/src/superlocalmemory/learning/legacy_migration.py +277 -0
- package/src/superlocalmemory/learning/memory_merge.py +160 -0
- package/src/superlocalmemory/learning/model_cache.py +269 -0
- package/src/superlocalmemory/learning/model_rollback.py +278 -0
- package/src/superlocalmemory/learning/outcome_queue.py +284 -0
- package/src/superlocalmemory/learning/pattern_miner.py +415 -0
- package/src/superlocalmemory/learning/pattern_miner_constants.py +47 -0
- package/src/superlocalmemory/learning/ranker.py +225 -81
- package/src/superlocalmemory/learning/ranker_common.py +163 -0
- package/src/superlocalmemory/learning/ranker_retrain_legacy.py +202 -0
- package/src/superlocalmemory/learning/ranker_retrain_online.py +411 -0
- package/src/superlocalmemory/learning/reward.py +777 -0
- package/src/superlocalmemory/learning/reward_archive.py +210 -0
- package/src/superlocalmemory/learning/reward_boost.py +201 -0
- package/src/superlocalmemory/learning/reward_proxy.py +326 -0
- package/src/superlocalmemory/learning/shadow_test.py +524 -0
- package/src/superlocalmemory/learning/signal_worker.py +270 -0
- package/src/superlocalmemory/learning/signals.py +314 -0
- package/src/superlocalmemory/learning/trigram_index.py +547 -0
- package/src/superlocalmemory/mcp/server.py +5 -5
- package/src/superlocalmemory/mcp/tools_context.py +183 -0
- package/src/superlocalmemory/mcp/tools_core.py +92 -27
- package/src/superlocalmemory/parameterization/soft_prompt_generator.py +13 -0
- package/src/superlocalmemory/retrieval/engine.py +52 -0
- package/src/superlocalmemory/retrieval/reranker.py +4 -2
- package/src/superlocalmemory/server/api.py +2 -2
- package/src/superlocalmemory/server/bandit_loops.py +140 -0
- package/src/superlocalmemory/server/middleware/__init__.py +11 -0
- package/src/superlocalmemory/server/middleware/security_headers.py +144 -0
- package/src/superlocalmemory/server/routes/backup.py +36 -13
- package/src/superlocalmemory/server/routes/behavioral.py +50 -19
- package/src/superlocalmemory/server/routes/brain.py +1234 -0
- package/src/superlocalmemory/server/routes/data_io.py +4 -4
- package/src/superlocalmemory/server/routes/events.py +2 -2
- package/src/superlocalmemory/server/routes/helpers.py +1 -1
- package/src/superlocalmemory/server/routes/learning.py +192 -7
- package/src/superlocalmemory/server/routes/memories.py +189 -1
- package/src/superlocalmemory/server/routes/prewarm.py +171 -0
- package/src/superlocalmemory/server/routes/profiles.py +3 -3
- package/src/superlocalmemory/server/routes/token.py +88 -0
- package/src/superlocalmemory/server/routes/ws.py +5 -5
- package/src/superlocalmemory/server/security_middleware.py +13 -7
- package/src/superlocalmemory/server/ui.py +2 -2
- package/src/superlocalmemory/server/unified_daemon.py +335 -3
- package/src/superlocalmemory/storage/migration_runner.py +545 -0
- package/src/superlocalmemory/storage/migrations/M001_add_signal_features_columns.py +67 -0
- package/src/superlocalmemory/storage/migrations/M002_model_state_history.py +132 -0
- package/src/superlocalmemory/storage/migrations/M003_migration_log.py +38 -0
- package/src/superlocalmemory/storage/migrations/M004_cross_platform_sync_log.py +46 -0
- package/src/superlocalmemory/storage/migrations/M005_bandit_tables.py +75 -0
- package/src/superlocalmemory/storage/migrations/M006_action_outcomes_reward.py +75 -0
- package/src/superlocalmemory/storage/migrations/M007_pending_outcomes.py +63 -0
- package/src/superlocalmemory/storage/migrations/M009_model_lineage.py +54 -0
- package/src/superlocalmemory/storage/migrations/M010_evolution_config.py +75 -0
- package/src/superlocalmemory/storage/migrations/M011_archive_and_merge.py +87 -0
- package/src/superlocalmemory/storage/migrations/M012_shadow_observations.py +72 -0
- package/src/superlocalmemory/storage/migrations/M013_bi_temporal_columns.py +55 -0
- package/src/superlocalmemory/storage/migrations/__init__.py +81 -0
- package/src/superlocalmemory/storage/models.py +4 -0
- package/src/superlocalmemory/ui/css/brain.css +409 -0
- package/src/superlocalmemory/ui/css/legacy-dashboard.css +645 -0
- package/src/superlocalmemory/ui/index.html +459 -1345
- package/src/superlocalmemory/ui/js/brain.js +1321 -0
- package/src/superlocalmemory/ui/js/clusters.js +123 -4
- package/src/superlocalmemory/ui/js/init.js +48 -39
- package/src/superlocalmemory/ui/js/memories.js +88 -2
- package/src/superlocalmemory/ui/js/modal.js +71 -1
- package/src/superlocalmemory/ui/js/ng-shell.js +101 -88
- package/src/superlocalmemory/ui/js/trust-dashboard.js +168 -25
- package/src/superlocalmemory/ui/vendor/bootstrap-icons/bootstrap-icons.css +2018 -0
- package/src/superlocalmemory/ui/vendor/bootstrap-icons/fonts/bootstrap-icons.woff +0 -0
- package/src/superlocalmemory/ui/vendor/bootstrap-icons/fonts/bootstrap-icons.woff2 +0 -0
- package/src/superlocalmemory/ui/vendor/bootstrap.bundle.min.js +7 -0
- package/src/superlocalmemory/ui/vendor/bootstrap.min.css +6 -0
- package/src/superlocalmemory/ui/vendor/d3.v7.min.js +2 -0
- package/src/superlocalmemory/ui/vendor/graphology-library.min.js +2 -0
- package/src/superlocalmemory/ui/vendor/graphology.umd.min.js +2 -0
- package/src/superlocalmemory/ui/vendor/inter-ui/inter-variable.min.css +8 -0
- package/src/superlocalmemory/ui/vendor/inter-ui/variable/InterVariable-Italic.woff2 +0 -0
- package/src/superlocalmemory/ui/vendor/inter-ui/variable/InterVariable.woff2 +0 -0
- package/src/superlocalmemory/ui/vendor/sigma.min.js +1 -0
- package/src/superlocalmemory/ui/js/behavioral.js +0 -447
- package/src/superlocalmemory/ui/js/graph-core.js +0 -447
- package/src/superlocalmemory/ui/js/graph-interactions.js +0 -351
- package/src/superlocalmemory/ui/js/learning.js +0 -435
- package/src/superlocalmemory/ui/js/patterns.js +0 -93
- package/src/superlocalmemory.egg-info/PKG-INFO +0 -647
- package/src/superlocalmemory.egg-info/SOURCES.txt +0 -335
- package/src/superlocalmemory.egg-info/dependency_links.txt +0 -1
- package/src/superlocalmemory.egg-info/entry_points.txt +0 -2
- package/src/superlocalmemory.egg-info/requires.txt +0 -58
- package/src/superlocalmemory.egg-info/top_level.txt +0 -1
|
@@ -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
|
-
}
|