@andespindola/brainlink 0.1.0-beta.76 → 0.1.0-beta.78
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
CHANGED
|
@@ -83,6 +83,7 @@ Legacy `.jsonl.gz` packs are upgraded to `.blpk` automatically on first search/c
|
|
|
83
83
|
- Realtime graph UI with agent selector and colored knowledge groups.
|
|
84
84
|
- Graph renderer optimized for large datasets with viewport-driven node culling and edge lookup by visible nodes.
|
|
85
85
|
- Large graph layout API automatically uses compact payload encoding with link-coverage-aware edge selection to reduce initial client load without hiding major relationships.
|
|
86
|
+
- Large-segment layout spacing now grows logarithmically to keep initial visual density consistent between medium and very large vaults (for example, ~1k vs ~50k notes).
|
|
86
87
|
- Zoomed-out graph LOD now clusters dense regions and progressively expands nodes as zoom increases.
|
|
87
88
|
- Graph reset starts in macro "galaxy" overview mode and progressively reveals nearby nodes as zoom increases, including smaller vaults.
|
|
88
89
|
- Graph filtering runs in a dedicated browser worker to keep the UI thread responsive during heavy datasets.
|
|
@@ -579,6 +579,14 @@ const nodeBudgetForScale = (scale) => {
|
|
|
579
579
|
return renderNodeBudget
|
|
580
580
|
}
|
|
581
581
|
|
|
582
|
+
const massiveLowZoomNodeBudgetForScale = (scale) => {
|
|
583
|
+
if (scale < 0.004) return 780
|
|
584
|
+
if (scale < 0.01) return 860
|
|
585
|
+
if (scale < 0.02) return 900
|
|
586
|
+
if (scale < 0.035) return 900
|
|
587
|
+
return renderNodeBudget
|
|
588
|
+
}
|
|
589
|
+
|
|
582
590
|
const layerFocusForScale = (scale) => {
|
|
583
591
|
const normalized = Math.max(0, Math.min(1, (scale - 0.06) / 0.94))
|
|
584
592
|
const shellCenter = Math.max(0.08, 0.96 - normalized * 0.86)
|
|
@@ -1119,6 +1127,7 @@ const includeHubPreviewNeighborhood = (nodes, limit) => {
|
|
|
1119
1127
|
const maxNodes = Math.max(1, Math.min(renderNodeBudget, limit))
|
|
1120
1128
|
const merged = [...nodes]
|
|
1121
1129
|
const ids = new Set(merged.map((node) => node.id))
|
|
1130
|
+
const protectedIds = new Set()
|
|
1122
1131
|
|
|
1123
1132
|
if (!ids.has(hub.id)) {
|
|
1124
1133
|
if (merged.length < maxNodes) {
|
|
@@ -1133,6 +1142,7 @@ const includeHubPreviewNeighborhood = (nodes, limit) => {
|
|
|
1133
1142
|
}
|
|
1134
1143
|
}
|
|
1135
1144
|
}
|
|
1145
|
+
protectedIds.add(hub.id)
|
|
1136
1146
|
|
|
1137
1147
|
const hubEdges = [...(state.visibleEdgeByNode.get(hub.id) ?? [])]
|
|
1138
1148
|
.filter((edge) => edge.target && (edge.source === hub.id || edge.target === hub.id))
|
|
@@ -1161,8 +1171,31 @@ const includeHubPreviewNeighborhood = (nodes, limit) => {
|
|
|
1161
1171
|
continue
|
|
1162
1172
|
}
|
|
1163
1173
|
|
|
1164
|
-
|
|
1165
|
-
|
|
1174
|
+
if (merged.length < maxNodes) {
|
|
1175
|
+
ids.add(otherId)
|
|
1176
|
+
merged.push(otherNode)
|
|
1177
|
+
protectedIds.add(otherId)
|
|
1178
|
+
continue
|
|
1179
|
+
}
|
|
1180
|
+
|
|
1181
|
+
const replaceIndex = (() => {
|
|
1182
|
+
for (let cursor = merged.length - 1; cursor >= 0; cursor -= 1) {
|
|
1183
|
+
const candidateId = merged[cursor]?.id
|
|
1184
|
+
if (candidateId && !protectedIds.has(candidateId)) {
|
|
1185
|
+
return cursor
|
|
1186
|
+
}
|
|
1187
|
+
}
|
|
1188
|
+
return -1
|
|
1189
|
+
})()
|
|
1190
|
+
if (replaceIndex >= 0) {
|
|
1191
|
+
const replacedId = merged[replaceIndex]?.id
|
|
1192
|
+
if (replacedId) {
|
|
1193
|
+
ids.delete(replacedId)
|
|
1194
|
+
}
|
|
1195
|
+
merged[replaceIndex] = otherNode
|
|
1196
|
+
ids.add(otherId)
|
|
1197
|
+
protectedIds.add(otherId)
|
|
1198
|
+
}
|
|
1166
1199
|
}
|
|
1167
1200
|
|
|
1168
1201
|
return merged
|
|
@@ -1283,7 +1316,7 @@ const autoFitScaleRangeByNodeCount = nodeCount => {
|
|
|
1283
1316
|
if (nodeCount <= 600) return { min: 0.12, max: 0.72 }
|
|
1284
1317
|
if (nodeCount <= 2000) return { min: 0.08, max: 0.52 }
|
|
1285
1318
|
if (nodeCount <= 6000) return { min: 0.06, max: 0.32 }
|
|
1286
|
-
return { min: 0.
|
|
1319
|
+
return { min: 0.0012, max: 0.24 }
|
|
1287
1320
|
}
|
|
1288
1321
|
|
|
1289
1322
|
const fitView = (options = { useFiltered: true, macro: false, preferHubCenter: true }) => {
|
|
@@ -1882,16 +1915,26 @@ const computeRenderVisibility = () => {
|
|
|
1882
1915
|
if (state.visibleNodes.length > massiveGraphNodeThreshold) {
|
|
1883
1916
|
const viewportNodes = viewportNodesFromSpatialIndex(viewport)
|
|
1884
1917
|
if (state.transform.scale <= massiveOverviewClusterScaleThreshold) {
|
|
1918
|
+
const overviewLimit = Math.min(renderNodeBudget, massiveLowZoomNodeBudgetForScale(state.transform.scale))
|
|
1885
1919
|
const overviewClusters = filterOverviewClustersByViewport(viewport)
|
|
1886
1920
|
.sort((left, right) => right.count - left.count)
|
|
1887
|
-
.slice(0,
|
|
1921
|
+
.slice(0, overviewLimit)
|
|
1888
1922
|
if (overviewClusters.length > 0) {
|
|
1889
|
-
|
|
1890
|
-
state.renderNodes = representativeNodesFromClusters(
|
|
1923
|
+
const overviewNodes = representativeNodesFromClusters(
|
|
1891
1924
|
overviewClusters,
|
|
1892
|
-
|
|
1925
|
+
overviewLimit
|
|
1926
|
+
)
|
|
1927
|
+
const anchoredNodes = includeHubPreviewNeighborhood(
|
|
1928
|
+
overviewNodes,
|
|
1929
|
+
Math.min(renderNodeBudget, overviewLimit)
|
|
1893
1930
|
)
|
|
1894
|
-
|
|
1931
|
+
const enriched = enrichSampleWithNeighbors(anchoredNodes)
|
|
1932
|
+
const previewNodes = ensureHubNodesInRenderedSet(enriched.nodes)
|
|
1933
|
+
const previewIds = new Set(previewNodes.map((node) => node.id))
|
|
1934
|
+
const previewEdges = collectVisibleEdgesForNodes(previewIds)
|
|
1935
|
+
state.renderClusters = []
|
|
1936
|
+
state.renderNodes = previewNodes
|
|
1937
|
+
state.renderEdges = previewEdges
|
|
1895
1938
|
return
|
|
1896
1939
|
}
|
|
1897
1940
|
}
|
|
@@ -2099,13 +2142,15 @@ const render = now => {
|
|
|
2099
2142
|
state.offscreenFrameCount = 0
|
|
2100
2143
|
}
|
|
2101
2144
|
const minimumEdgeScale =
|
|
2102
|
-
state.
|
|
2103
|
-
? 0
|
|
2104
|
-
: state.renderNodes.length >
|
|
2105
|
-
? 0.
|
|
2106
|
-
: state.renderNodes.length >
|
|
2107
|
-
? 0.
|
|
2108
|
-
:
|
|
2145
|
+
state.nodes.length > massiveGraphNodeThreshold
|
|
2146
|
+
? 0
|
|
2147
|
+
: state.renderNodes.length > 1300
|
|
2148
|
+
? 0.12
|
|
2149
|
+
: state.renderNodes.length > 900
|
|
2150
|
+
? 0.085
|
|
2151
|
+
: state.renderNodes.length > 500
|
|
2152
|
+
? 0.05
|
|
2153
|
+
: 0
|
|
2109
2154
|
const drawEdges =
|
|
2110
2155
|
state.renderClusters.length === 0 &&
|
|
2111
2156
|
state.transform.scale >= minimumEdgeScale
|
|
@@ -2288,8 +2333,28 @@ const selectNodeById = id => {
|
|
|
2288
2333
|
}
|
|
2289
2334
|
|
|
2290
2335
|
const zoomAtPoint = (screenX, screenY, factor, source = 'generic') => {
|
|
2336
|
+
const resolveZoomFactor = () => {
|
|
2337
|
+
if (state.nodes.length <= massiveGraphNodeThreshold) {
|
|
2338
|
+
return factor
|
|
2339
|
+
}
|
|
2340
|
+
|
|
2341
|
+
const scale = state.transform.scale
|
|
2342
|
+
if (factor > 1) {
|
|
2343
|
+
if (scale < 0.006) return Math.max(factor, 1.48)
|
|
2344
|
+
if (scale < 0.02) return Math.max(factor, 1.34)
|
|
2345
|
+
if (scale < 0.08) return Math.max(factor, 1.22)
|
|
2346
|
+
return factor
|
|
2347
|
+
}
|
|
2348
|
+
|
|
2349
|
+
if (scale < 0.006) return Math.min(factor, 0.68)
|
|
2350
|
+
if (scale < 0.02) return Math.min(factor, 0.78)
|
|
2351
|
+
if (scale < 0.08) return Math.min(factor, 0.86)
|
|
2352
|
+
return factor
|
|
2353
|
+
}
|
|
2354
|
+
|
|
2291
2355
|
state.lastManualZoomAt = performance.now()
|
|
2292
|
-
const
|
|
2356
|
+
const effectiveFactor = resolveZoomFactor()
|
|
2357
|
+
const nextScale = clampScale(state.transform.scale * effectiveFactor)
|
|
2293
2358
|
if (nextScale === state.transform.scale) {
|
|
2294
2359
|
return
|
|
2295
2360
|
}
|
|
@@ -2366,11 +2431,11 @@ const bindEvents = () => {
|
|
|
2366
2431
|
})
|
|
2367
2432
|
elements.zoomIn.addEventListener('click', () => {
|
|
2368
2433
|
const rect = canvas.getBoundingClientRect()
|
|
2369
|
-
zoomAtPoint(Math.max(rect.width, 320) / 2, Math.max(rect.height, 320) / 2, 1.14)
|
|
2434
|
+
zoomAtPoint(Math.max(rect.width, 320) / 2, Math.max(rect.height, 320) / 2, 1.14, 'button')
|
|
2370
2435
|
})
|
|
2371
2436
|
elements.zoomOut.addEventListener('click', () => {
|
|
2372
2437
|
const rect = canvas.getBoundingClientRect()
|
|
2373
|
-
zoomAtPoint(Math.max(rect.width, 320) / 2, Math.max(rect.height, 320) / 2, 0.88)
|
|
2438
|
+
zoomAtPoint(Math.max(rect.width, 320) / 2, Math.max(rect.height, 320) / 2, 0.88, 'button')
|
|
2374
2439
|
})
|
|
2375
2440
|
if (elements.fit) {
|
|
2376
2441
|
elements.fit.addEventListener('click', () => {
|
|
@@ -167,13 +167,17 @@ const groupNodesBySegment = (nodes, segments) => {
|
|
|
167
167
|
return new Map(groups);
|
|
168
168
|
};
|
|
169
169
|
const segmentAngle = (segment, index, count) => segmentAngles[segment] ?? (Math.PI * 2 * index) / Math.max(count, 1) - Math.PI / 2;
|
|
170
|
+
const petalSpreadForSegmentSize = (size) => {
|
|
171
|
+
const safeSize = Math.max(size, 1);
|
|
172
|
+
return 180 + Math.log2(safeSize + 1) * 6;
|
|
173
|
+
};
|
|
170
174
|
const createSegmentNodes = (segments, degrees, segmentCount) => ([segment, nodes], segmentIndex) => {
|
|
171
175
|
const sortedNodes = [...nodes].sort(byDegreeThenTitle(degrees));
|
|
172
176
|
const angle = segmentAngle(segment, segmentIndex, segmentCount);
|
|
173
177
|
const baseRadius = segmentCount === 1 ? 0 : 340 + Math.min(sortedNodes.length, 22) * 10;
|
|
174
178
|
const centerX = Math.cos(angle) * baseRadius;
|
|
175
179
|
const centerY = Math.sin(angle) * (baseRadius * 0.78);
|
|
176
|
-
const petalSpread =
|
|
180
|
+
const petalSpread = petalSpreadForSegmentSize(sortedNodes.length);
|
|
177
181
|
return sortedNodes.map((node, index) => {
|
|
178
182
|
const localAngle = index * 2.399963 + jitter(node.title, 0.42);
|
|
179
183
|
const localRadius = Math.sqrt(index + 1) * petalSpread;
|
package/package.json
CHANGED