noteconnection 1.1.2 → 1.3.0
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/README.md +81 -3
- package/dist/src/core/Graph.js +84 -0
- package/dist/src/core/PathBridge.js +49 -0
- package/dist/src/core/PathEngine.js +196 -0
- package/dist/src/core/PathEngine.test.js +86 -0
- package/dist/src/electron/main.js +14 -0
- package/dist/src/frontend/README.md +81 -3
- package/dist/src/frontend/app.js +39 -0
- package/dist/src/frontend/index.html +128 -2
- package/dist/src/frontend/libs/path_core.js +429 -0
- package/dist/src/frontend/locales/en.json +52 -29
- package/dist/src/frontend/locales/zh.json +30 -7
- package/dist/src/frontend/path.html +100 -0
- package/dist/src/frontend/path_app.js +685 -0
- package/dist/src/frontend/path_styles.css +240 -0
- package/dist/src/frontend/path_worker.js +176 -0
- package/dist/src/frontend/styles.css +1 -1
- package/package.json +7 -3
|
@@ -0,0 +1,240 @@
|
|
|
1
|
+
/* Path Mode Styles */
|
|
2
|
+
|
|
3
|
+
.path-container {
|
|
4
|
+
position: absolute;
|
|
5
|
+
top: 0;
|
|
6
|
+
left: 0;
|
|
7
|
+
width: 100vw;
|
|
8
|
+
height: 100vh;
|
|
9
|
+
background-color: #1e1e1e; /* Dark theme default */
|
|
10
|
+
z-index: 2000; /* Above main graph */
|
|
11
|
+
display: none; /* Hidden by default */
|
|
12
|
+
overflow: hidden;
|
|
13
|
+
font-family: "Segoe UI", Tahoma, Geneva, Verdana, sans-serif;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
.path-toolbar {
|
|
17
|
+
position: absolute;
|
|
18
|
+
top: 10px;
|
|
19
|
+
left: 50%;
|
|
20
|
+
transform: translateX(-50%);
|
|
21
|
+
background: rgba(30, 30, 30, 0.9);
|
|
22
|
+
backdrop-filter: blur(10px);
|
|
23
|
+
padding: 8px 16px;
|
|
24
|
+
border-radius: 8px;
|
|
25
|
+
border: 1px solid rgba(255, 255, 255, 0.1);
|
|
26
|
+
display: flex;
|
|
27
|
+
gap: 20px;
|
|
28
|
+
align-items: center;
|
|
29
|
+
box-shadow: 0 4px 6px rgba(0, 0, 0, 0.3);
|
|
30
|
+
z-index: 2005;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
.toolbar-group {
|
|
34
|
+
display: flex;
|
|
35
|
+
align-items: center;
|
|
36
|
+
gap: 8px;
|
|
37
|
+
color: #e0e0e0;
|
|
38
|
+
font-size: 0.9rem;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
.toolbar-group select {
|
|
42
|
+
background: #333;
|
|
43
|
+
color: white;
|
|
44
|
+
border: 1px solid #555;
|
|
45
|
+
padding: 4px 8px;
|
|
46
|
+
border-radius: 4px;
|
|
47
|
+
outline: none;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
.toolbar-group select:hover {
|
|
51
|
+
border-color: #777;
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
.toolbar-group.right {
|
|
55
|
+
margin-left: 20px;
|
|
56
|
+
border-left: 1px solid #555;
|
|
57
|
+
padding-left: 20px;
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
.icon-btn {
|
|
61
|
+
background: none;
|
|
62
|
+
border: none;
|
|
63
|
+
color: #ff6b6b;
|
|
64
|
+
cursor: pointer;
|
|
65
|
+
font-size: 1rem;
|
|
66
|
+
padding: 4px 8px;
|
|
67
|
+
border-radius: 4px;
|
|
68
|
+
transition: background 0.2s;
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
.icon-btn:hover {
|
|
72
|
+
background: rgba(255, 107, 107, 0.1);
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
#path-canvas {
|
|
76
|
+
position: absolute;
|
|
77
|
+
top: 0;
|
|
78
|
+
left: 0;
|
|
79
|
+
width: 100%;
|
|
80
|
+
height: 100%;
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
#path-status {
|
|
84
|
+
position: absolute;
|
|
85
|
+
bottom: 20px;
|
|
86
|
+
left: 20px;
|
|
87
|
+
color: #888;
|
|
88
|
+
font-size: 0.9rem;
|
|
89
|
+
pointer-events: none;
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
/* Node Cards (if we use DOM overlays) */
|
|
93
|
+
.path-node-card {
|
|
94
|
+
position: absolute;
|
|
95
|
+
background: #2d2d2d;
|
|
96
|
+
border: 1px solid #444;
|
|
97
|
+
border-radius: 6px;
|
|
98
|
+
padding: 10px;
|
|
99
|
+
width: 200px;
|
|
100
|
+
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.5);
|
|
101
|
+
pointer-events: auto;
|
|
102
|
+
transition:
|
|
103
|
+
transform 0.2s,
|
|
104
|
+
box-shadow 0.2s;
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
.path-node-card:hover {
|
|
108
|
+
transform: translateY(-2px);
|
|
109
|
+
box-shadow: 0 6px 16px rgba(0, 0, 0, 0.6);
|
|
110
|
+
border-color: #666;
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
.path-node-title {
|
|
114
|
+
color: #fff;
|
|
115
|
+
font-weight: bold;
|
|
116
|
+
margin-bottom: 5px;
|
|
117
|
+
font-size: 1rem;
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
.path-node-meta {
|
|
121
|
+
color: #aaa;
|
|
122
|
+
font-size: 0.8rem;
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
.step-badge {
|
|
126
|
+
display: inline-block;
|
|
127
|
+
background: #4a9eff;
|
|
128
|
+
color: white;
|
|
129
|
+
padding: 2px 6px;
|
|
130
|
+
border-radius: 10px;
|
|
131
|
+
font-size: 0.7rem;
|
|
132
|
+
margin-right: 5px;
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
/* v1.3 Path Mode Extensions */
|
|
136
|
+
|
|
137
|
+
/* Action Buttons */
|
|
138
|
+
#btn-mark-complete {
|
|
139
|
+
background: linear-gradient(135deg, #ffd700 0%, #ffa500 100%);
|
|
140
|
+
color: #000;
|
|
141
|
+
font-weight: bold;
|
|
142
|
+
border: none;
|
|
143
|
+
box-shadow: 0 0 10px rgba(255, 215, 0, 0.4);
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
#btn-mark-complete:hover {
|
|
147
|
+
box-shadow: 0 0 15px rgba(255, 215, 0, 0.6);
|
|
148
|
+
transform: scale(1.05);
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
#btn-toggle-history {
|
|
152
|
+
color: #4a9eff;
|
|
153
|
+
border-color: #4a9eff;
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
/* Sidebar */
|
|
157
|
+
.path-sidebar {
|
|
158
|
+
position: absolute;
|
|
159
|
+
top: 60px; /* Below toolbar */
|
|
160
|
+
right: 0;
|
|
161
|
+
width: 300px;
|
|
162
|
+
height: calc(100% - 60px);
|
|
163
|
+
background: rgba(30, 30, 30, 0.95);
|
|
164
|
+
border-left: 1px solid #444;
|
|
165
|
+
transition: transform 0.3s ease-in-out;
|
|
166
|
+
transform: translateX(
|
|
167
|
+
100%
|
|
168
|
+
); /* Hidden state logic handled by display: none for now, but transform is smoother */
|
|
169
|
+
display: flex; /* Overridden by inline style */
|
|
170
|
+
flex-direction: column;
|
|
171
|
+
z-index: 1000;
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
.sidebar-header {
|
|
175
|
+
padding: 15px;
|
|
176
|
+
background: #252525;
|
|
177
|
+
border-bottom: 1px solid #444;
|
|
178
|
+
display: flex;
|
|
179
|
+
justify-content: space-between;
|
|
180
|
+
align-items: center;
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
.sidebar-header h3 {
|
|
184
|
+
margin: 0;
|
|
185
|
+
font-size: 1rem;
|
|
186
|
+
color: #ffd700;
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
.close-btn {
|
|
190
|
+
background: none;
|
|
191
|
+
border: none;
|
|
192
|
+
color: #aaa;
|
|
193
|
+
font-size: 1.2rem;
|
|
194
|
+
cursor: pointer;
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
.sidebar-content {
|
|
198
|
+
flex: 1;
|
|
199
|
+
overflow-y: auto;
|
|
200
|
+
padding: 10px;
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
.history-item {
|
|
204
|
+
padding: 8px;
|
|
205
|
+
border-bottom: 1px solid #333;
|
|
206
|
+
cursor: pointer;
|
|
207
|
+
display: flex;
|
|
208
|
+
align-items: center;
|
|
209
|
+
gap: 10px;
|
|
210
|
+
transition: background 0.2s;
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
.history-item:hover {
|
|
214
|
+
background: #333;
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
.history-dot {
|
|
218
|
+
width: 10px;
|
|
219
|
+
height: 10px;
|
|
220
|
+
background: #ffd700;
|
|
221
|
+
border-radius: 50%;
|
|
222
|
+
box-shadow: 0 0 5px #ffd700;
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
.history-label {
|
|
226
|
+
color: #ccc;
|
|
227
|
+
font-size: 0.9rem;
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
/* Modal tweaks specific to Path Mode */
|
|
231
|
+
#node-select-list li {
|
|
232
|
+
padding: 8px;
|
|
233
|
+
border-bottom: 1px solid #333;
|
|
234
|
+
cursor: pointer;
|
|
235
|
+
color: #ccc;
|
|
236
|
+
}
|
|
237
|
+
#node-select-list li:hover {
|
|
238
|
+
background: #444;
|
|
239
|
+
color: #fff;
|
|
240
|
+
}
|
|
@@ -0,0 +1,176 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Path Mode Worker
|
|
3
|
+
* Bundles Core Algorithms and Layout Logic
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
importScripts("libs/d3.v7.min.js");
|
|
7
|
+
// We will rely on a bundled version of the core being importable
|
|
8
|
+
// The build script will generate 'libs/path_core.js' which defines 'Graph' and 'PathEngine' classes globally or via a shim
|
|
9
|
+
importScripts("libs/path_core.js");
|
|
10
|
+
|
|
11
|
+
let graph = null;
|
|
12
|
+
let engine = null;
|
|
13
|
+
let rawNodes = [];
|
|
14
|
+
let rawLinks = [];
|
|
15
|
+
|
|
16
|
+
onmessage = function(e) {
|
|
17
|
+
const { type, payload } = e.data;
|
|
18
|
+
|
|
19
|
+
try {
|
|
20
|
+
switch (type) {
|
|
21
|
+
case 'initData':
|
|
22
|
+
initData(payload);
|
|
23
|
+
break;
|
|
24
|
+
case 'computePath':
|
|
25
|
+
computePath(payload);
|
|
26
|
+
break;
|
|
27
|
+
}
|
|
28
|
+
} catch (err) {
|
|
29
|
+
console.error('Worker Error:', err);
|
|
30
|
+
}
|
|
31
|
+
};
|
|
32
|
+
|
|
33
|
+
function initData(data) {
|
|
34
|
+
// Reconstruct Graph object
|
|
35
|
+
// Assuming Graph class is globally available from path_core.js
|
|
36
|
+
graph = new Graph();
|
|
37
|
+
data.nodes.forEach(n => graph.addNode(n));
|
|
38
|
+
data.links.forEach(l => graph.addEdge(l.source, l.target, l.type, l.weight));
|
|
39
|
+
|
|
40
|
+
engine = new PathEngine(graph);
|
|
41
|
+
postMessage({ type: 'log', payload: `Graph Initialized: ${data.nodes.length} nodes` });
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
function computePath(config) {
|
|
45
|
+
if (!engine) return;
|
|
46
|
+
|
|
47
|
+
const { mode, strategy, layout, targetId } = config;
|
|
48
|
+
let result;
|
|
49
|
+
|
|
50
|
+
if (mode === 'domain') {
|
|
51
|
+
result = engine.domainLearning(null, strategy);
|
|
52
|
+
} else {
|
|
53
|
+
// Validation for Diffusion Mode
|
|
54
|
+
if (!targetId || targetId === 'null' || !graph.hasNode(targetId)) {
|
|
55
|
+
console.warn(`[PathWorker] Invalid target '${targetId}' for Diffusion. Fallback to Domain.`);
|
|
56
|
+
result = engine.domainLearning(null, 'foundational'); // Fallback
|
|
57
|
+
} else {
|
|
58
|
+
result = engine.diffusionLearning(targetId, strategy);
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
// Layout Calculation
|
|
63
|
+
// We need to assign x,y coordinates to the result nodes
|
|
64
|
+
// Structure: result.nodes is a linear sequence or a set.
|
|
65
|
+
// For Tree layout, we need to reconstruct the hierarchy passed in result.edges (which are relevant dependencies)
|
|
66
|
+
|
|
67
|
+
const layoutData = runLayout(result.nodes, result.edges, layout);
|
|
68
|
+
|
|
69
|
+
postMessage({
|
|
70
|
+
type: 'pathResult',
|
|
71
|
+
payload: {
|
|
72
|
+
nodes: layoutData.nodes,
|
|
73
|
+
edges: layoutData.edges
|
|
74
|
+
}
|
|
75
|
+
});
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
function runLayout(nodes, edges, type) {
|
|
79
|
+
// Convert to D3 Stratify structure if possible, or use d3-dag
|
|
80
|
+
// Simple approach: Build a hierarchy from stepOrder or dependencies
|
|
81
|
+
|
|
82
|
+
// Create a hierarchy object for D3
|
|
83
|
+
// Root is a virtual node connecting to all step 1 nodes?
|
|
84
|
+
// Or finds roots (in-degree 0) in the subgraph
|
|
85
|
+
|
|
86
|
+
// 1. Map nodes for quick access
|
|
87
|
+
const nodeMap = new Map();
|
|
88
|
+
nodes.forEach(n => {
|
|
89
|
+
// Clone to avoid mutating original logic objects if needed
|
|
90
|
+
nodeMap.set(n.id, { ...n, children: [] });
|
|
91
|
+
});
|
|
92
|
+
|
|
93
|
+
// 2. Build Tree Structure
|
|
94
|
+
// Note: Graph might be DAG, D3 Tree requires strict Tree.
|
|
95
|
+
// We break cycles/multi-parents by just taking the first parent found in this path?
|
|
96
|
+
// Or use graph layout. For MVP, we use a simple Level-based layout based on 'stepOrder'.
|
|
97
|
+
|
|
98
|
+
if (type === 'horizontal' || type === 'vertical') {
|
|
99
|
+
const levelHeight = 100;
|
|
100
|
+
const levelWidth = 80;
|
|
101
|
+
|
|
102
|
+
// Group by stepOrder
|
|
103
|
+
const levels = [];
|
|
104
|
+
nodes.forEach(node => {
|
|
105
|
+
if (!levels[node.stepOrder]) levels[node.stepOrder] = [];
|
|
106
|
+
levels[node.stepOrder].push(nodeMap.get(node.id));
|
|
107
|
+
});
|
|
108
|
+
|
|
109
|
+
// Assign coordinates
|
|
110
|
+
levels.forEach((level, i) => {
|
|
111
|
+
const y = i * levelHeight;
|
|
112
|
+
const xStart = -(level.length * levelWidth) / 2;
|
|
113
|
+
level.forEach((node, j) => {
|
|
114
|
+
if (type === 'vertical') {
|
|
115
|
+
node.x = xStart + j * levelWidth;
|
|
116
|
+
node.y = y;
|
|
117
|
+
} else {
|
|
118
|
+
node.x = y;
|
|
119
|
+
node.y = xStart + j * levelWidth;
|
|
120
|
+
}
|
|
121
|
+
});
|
|
122
|
+
});
|
|
123
|
+
} else if (type === 'radial') {
|
|
124
|
+
// Radial: Angle based on index, Radius based on step
|
|
125
|
+
const radiusStep = 120;
|
|
126
|
+
|
|
127
|
+
// Group by step for better radial distribution
|
|
128
|
+
const levels = [];
|
|
129
|
+
nodes.forEach(node => {
|
|
130
|
+
const step = node.stepOrder || 1;
|
|
131
|
+
if (!levels[step]) levels[step] = [];
|
|
132
|
+
levels[step].push(nodeMap.get(node.id));
|
|
133
|
+
});
|
|
134
|
+
|
|
135
|
+
levels.forEach((level, stepIndex) => {
|
|
136
|
+
if (!level) return;
|
|
137
|
+
const r = stepIndex * radiusStep;
|
|
138
|
+
level.forEach((node, i) => {
|
|
139
|
+
// Distribute nodes in this level around the circle
|
|
140
|
+
const angle = (i / level.length) * 2 * Math.PI;
|
|
141
|
+
// Rotate slightly per level to avoid overlap
|
|
142
|
+
const offset = stepIndex * 0.2;
|
|
143
|
+
|
|
144
|
+
node.x = r * Math.cos(angle + offset);
|
|
145
|
+
node.y = r * Math.sin(angle + offset);
|
|
146
|
+
});
|
|
147
|
+
});
|
|
148
|
+
} else if (type === 'orbital') {
|
|
149
|
+
// Orbital: Central node at 0,0. Neighbors in orbit.
|
|
150
|
+
// We need to identify the "Central" node.
|
|
151
|
+
// Strategy: First node in the list is assumed central/focus for now,
|
|
152
|
+
// OR the app should pass a 'focusId'.
|
|
153
|
+
// For Path Mode, usually the first node (step 1) or the "current" node is central.
|
|
154
|
+
// Let's assume nodes[0] is the center if not specified.
|
|
155
|
+
|
|
156
|
+
const centerNode = nodes[0];
|
|
157
|
+
if (centerNode) {
|
|
158
|
+
nodeMap.get(centerNode.id).x = 0;
|
|
159
|
+
nodeMap.get(centerNode.id).y = 0;
|
|
160
|
+
|
|
161
|
+
const radius = 200;
|
|
162
|
+
const peripherals = nodes.slice(1);
|
|
163
|
+
peripherals.forEach((n, i) => {
|
|
164
|
+
const node = nodeMap.get(n.id);
|
|
165
|
+
const angle = (i / peripherals.length) * 2 * Math.PI;
|
|
166
|
+
node.x = radius * Math.cos(angle);
|
|
167
|
+
node.y = radius * Math.sin(angle);
|
|
168
|
+
});
|
|
169
|
+
}
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
return {
|
|
173
|
+
nodes: Array.from(nodeMap.values()),
|
|
174
|
+
edges: edges
|
|
175
|
+
};
|
|
176
|
+
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "noteconnection",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.3.0",
|
|
4
4
|
"description": "Hierarchical Knowledge Graph Visualization System",
|
|
5
5
|
"main": "dist/src/electron/main.js",
|
|
6
6
|
"bin": {
|
|
@@ -13,8 +13,10 @@
|
|
|
13
13
|
"electron:dev:mini": "npm run build:mini && electron .",
|
|
14
14
|
"electron:build": "npm run build && electron-builder",
|
|
15
15
|
"electron:build:mini": "npm run build:mini && electron-builder",
|
|
16
|
-
"build": "tsc && node scripts/copy-assets.js",
|
|
16
|
+
"build": "tsc && node scripts/copy-assets.js && node scripts/bundle_path_core.js",
|
|
17
17
|
"build:mini": "tsc && node scripts/copy-assets.js --mini",
|
|
18
|
+
"pathmode:dev": "node -r ts-node/register src/server.ts --pathmode",
|
|
19
|
+
"pathmode:test": "jest --testPathPatterns=Path",
|
|
18
20
|
"prepublishOnly": "npm run build",
|
|
19
21
|
"test": "jest"
|
|
20
22
|
},
|
|
@@ -60,8 +62,10 @@
|
|
|
60
62
|
"@capacitor/cli": "^8.0.0",
|
|
61
63
|
"@capacitor/core": "^8.0.0",
|
|
62
64
|
"@types/d3-force": "^3.0.10",
|
|
65
|
+
"@types/ws": "^8.18.1",
|
|
63
66
|
"d3-force": "^3.0.0",
|
|
64
67
|
"electron-squirrel-startup": "^1.0.1",
|
|
65
|
-
"tslib": "^2.8.1"
|
|
68
|
+
"tslib": "^2.8.1",
|
|
69
|
+
"ws": "^8.19.0"
|
|
66
70
|
}
|
|
67
71
|
}
|