@andespindola/brainlink 0.1.0-beta.96 → 0.1.0-beta.97
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 +1 -1
- package/dist/application/frontend/client-js.js +93 -45
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -602,7 +602,7 @@ The graph UI shows:
|
|
|
602
602
|
- WebGL node and edge acceleration when supported, falling back to Canvas 2D without changing graph behavior
|
|
603
603
|
- compact macro-to-micro density progression so reset keeps the graph mass oriented and zoom-in separates local neighborhoods progressively
|
|
604
604
|
- graph camera treats hub-centered navigation as structural only when the hub is dominant; diffuse stress graphs reset and zoom around the full graph mass
|
|
605
|
-
- graph LOD progression: graphs up to 1000 notes render directly; larger graphs use
|
|
605
|
+
- graph LOD progression: graphs up to 1000 notes render directly; larger graphs use one recursive model where each visible level targets up to 999 non-hub nodes, starts from a memory-hub-centered mesh, and each supernode can expand into another same-shape subgraph level (again up to 999 children) with latent fade-in, aggregated real links and local sibling mesh links so org-heavy and stress-50k follow the same structure at different depths
|
|
606
606
|
|
|
607
607
|
The server indexes before starting by default. Use `--no-index` to skip that step:
|
|
608
608
|
|
|
@@ -21,9 +21,8 @@ const viewportPaddingPx = 280
|
|
|
21
21
|
const worldCoordinateLimit = 5_000_000
|
|
22
22
|
const transformCoordinateLimit = 20_000_000
|
|
23
23
|
const hoverHitTestIntervalMs = 64
|
|
24
|
-
const
|
|
24
|
+
const ecosystemLevelNodeCap = 999
|
|
25
25
|
const ecosystemActivationNodeThreshold = 1000
|
|
26
|
-
const ecosystemGroupSizes = [1000, 500, 250, 125, 60, 30, 15, 8]
|
|
27
26
|
const ecosystemClusterEdgeLimit = 520
|
|
28
27
|
const ecosystemHubEdgeLimit = 120
|
|
29
28
|
const ecosystemSiblingEdgeLimit = 180
|
|
@@ -32,15 +31,6 @@ const massiveEcosystemClusterScaleThreshold = 4.2
|
|
|
32
31
|
const ecosystemSubgraphScaleThreshold = 0.18
|
|
33
32
|
const ecosystemMicroScaleThreshold = 0.08
|
|
34
33
|
const ecosystemFocusedParentLimit = 2
|
|
35
|
-
const ecosystemExpansionLevels = [
|
|
36
|
-
{ parentSize: 1000, childSize: 500, start: 0.04, end: 0.62 },
|
|
37
|
-
{ parentSize: 500, childSize: 250, start: 0.16, end: 0.72 },
|
|
38
|
-
{ parentSize: 250, childSize: 125, start: 0.3, end: 0.78 },
|
|
39
|
-
{ parentSize: 125, childSize: 60, start: 0.46, end: 0.86 },
|
|
40
|
-
{ parentSize: 60, childSize: 30, start: 0.64, end: 1.06 },
|
|
41
|
-
{ parentSize: 30, childSize: 15, start: 0.84, end: 1.38 },
|
|
42
|
-
{ parentSize: 15, childSize: 8, start: 1.08, end: 1.82 }
|
|
43
|
-
]
|
|
44
34
|
const zoomRecoveryGuardMs = 4200
|
|
45
35
|
const zoomCapTargetViewportShare = 0.72
|
|
46
36
|
const meshEdgeScaleThreshold = 0.09
|
|
@@ -87,6 +77,9 @@ const state = {
|
|
|
87
77
|
ecosystemClusters: [],
|
|
88
78
|
ecosystemClustersBySize: new Map(),
|
|
89
79
|
ecosystemNodeClusterBySize: new Map(),
|
|
80
|
+
ecosystemLevelSizes: [],
|
|
81
|
+
ecosystemExpansionLevels: [],
|
|
82
|
+
ecosystemBaseSize: ecosystemLevelNodeCap,
|
|
90
83
|
ecosystemHubCluster: null,
|
|
91
84
|
macroCenter: { x: 0, y: 0 },
|
|
92
85
|
macroRepresentative: null,
|
|
@@ -597,10 +590,21 @@ const recomputeVisibility = () => {
|
|
|
597
590
|
: { x: 0, y: 0 }
|
|
598
591
|
const ecosystemGraph = nodes.length > ecosystemActivationNodeThreshold
|
|
599
592
|
? buildEcosystemGraph(nodes, state.macroCenter, primaryHub)
|
|
600
|
-
: {
|
|
593
|
+
: {
|
|
594
|
+
clusters: [],
|
|
595
|
+
clustersBySize: new Map(),
|
|
596
|
+
nodeClusterBySize: new Map(),
|
|
597
|
+
levelSizes: [],
|
|
598
|
+
expansionLevels: [],
|
|
599
|
+
baseSize: ecosystemLevelNodeCap,
|
|
600
|
+
hubCluster: null
|
|
601
|
+
}
|
|
601
602
|
state.ecosystemClusters = ecosystemGraph.clusters
|
|
602
603
|
state.ecosystemClustersBySize = ecosystemGraph.clustersBySize
|
|
603
604
|
state.ecosystemNodeClusterBySize = ecosystemGraph.nodeClusterBySize
|
|
605
|
+
state.ecosystemLevelSizes = ecosystemGraph.levelSizes
|
|
606
|
+
state.ecosystemExpansionLevels = ecosystemGraph.expansionLevels
|
|
607
|
+
state.ecosystemBaseSize = ecosystemGraph.baseSize
|
|
604
608
|
state.ecosystemHubCluster = ecosystemGraph.hubCluster
|
|
605
609
|
state.macroRepresentative = resolveMacroRepresentative(nodes)
|
|
606
610
|
markRenderDirty()
|
|
@@ -747,14 +751,56 @@ const selectEcosystemRepresentative = nodes => {
|
|
|
747
751
|
}
|
|
748
752
|
|
|
749
753
|
const ecosystemLayoutSpacingForSize = size => {
|
|
750
|
-
if (size >=
|
|
751
|
-
if (size >=
|
|
752
|
-
if (size >=
|
|
753
|
-
if (size >=
|
|
754
|
-
if (size >=
|
|
755
|
-
if (size >=
|
|
756
|
-
|
|
757
|
-
|
|
754
|
+
if (size >= ecosystemLevelNodeCap) return 260
|
|
755
|
+
if (size >= 320) return 110
|
|
756
|
+
if (size >= 120) return 64
|
|
757
|
+
if (size >= 48) return 34
|
|
758
|
+
if (size >= 18) return 18
|
|
759
|
+
if (size >= 8) return 11
|
|
760
|
+
return 7
|
|
761
|
+
}
|
|
762
|
+
|
|
763
|
+
const buildEcosystemLevelSizes = nodeCount => {
|
|
764
|
+
if (nodeCount <= 0) return []
|
|
765
|
+
const sizes = []
|
|
766
|
+
let currentSize = Math.max(1, Math.ceil(nodeCount / ecosystemLevelNodeCap))
|
|
767
|
+
while (currentSize >= 1) {
|
|
768
|
+
sizes.push(currentSize)
|
|
769
|
+
if (currentSize === 1) {
|
|
770
|
+
break
|
|
771
|
+
}
|
|
772
|
+
const nextSize = Math.max(1, Math.ceil(currentSize / ecosystemLevelNodeCap))
|
|
773
|
+
if (nextSize === currentSize) {
|
|
774
|
+
break
|
|
775
|
+
}
|
|
776
|
+
currentSize = nextSize
|
|
777
|
+
}
|
|
778
|
+
return sizes
|
|
779
|
+
}
|
|
780
|
+
|
|
781
|
+
const buildEcosystemExpansionLevels = (levelSizes, nodeCount) => {
|
|
782
|
+
if (levelSizes.length <= 1) {
|
|
783
|
+
return []
|
|
784
|
+
}
|
|
785
|
+
const maxScale = nodeCount > massiveGraphNodeThreshold
|
|
786
|
+
? massiveEcosystemClusterScaleThreshold
|
|
787
|
+
: ecosystemClusterScaleThreshold
|
|
788
|
+
const startScale = 0.04
|
|
789
|
+
const transitionCount = levelSizes.length - 1
|
|
790
|
+
const usableScale = Math.max(0.08, maxScale - startScale)
|
|
791
|
+
const step = usableScale / transitionCount
|
|
792
|
+
const levels = []
|
|
793
|
+
for (let index = 0; index < transitionCount; index += 1) {
|
|
794
|
+
const start = startScale + step * index * 0.72
|
|
795
|
+
const end = Math.min(maxScale, start + step * 1.85)
|
|
796
|
+
levels.push({
|
|
797
|
+
parentSize: levelSizes[index],
|
|
798
|
+
childSize: levelSizes[index + 1],
|
|
799
|
+
start,
|
|
800
|
+
end
|
|
801
|
+
})
|
|
802
|
+
}
|
|
803
|
+
return levels
|
|
758
804
|
}
|
|
759
805
|
|
|
760
806
|
const ecosystemCompactPoint = (index, total, center, spacing) => {
|
|
@@ -837,19 +883,30 @@ const buildEcosystemLevel = (sortedNodes, size, parentLookup, center) => {
|
|
|
837
883
|
|
|
838
884
|
const buildEcosystemGraph = (nodes, center, hub) => {
|
|
839
885
|
if (nodes.length === 0) {
|
|
840
|
-
return {
|
|
886
|
+
return {
|
|
887
|
+
clusters: [],
|
|
888
|
+
clustersBySize: new Map(),
|
|
889
|
+
nodeClusterBySize: new Map(),
|
|
890
|
+
levelSizes: [],
|
|
891
|
+
expansionLevels: [],
|
|
892
|
+
baseSize: ecosystemLevelNodeCap,
|
|
893
|
+
hubCluster: null
|
|
894
|
+
}
|
|
841
895
|
}
|
|
842
896
|
|
|
843
897
|
const hubCluster = buildEcosystemHubCluster(hub, center)
|
|
844
898
|
const sortedNodes = nodes
|
|
845
899
|
.filter(node => node.id !== hub?.id)
|
|
846
900
|
.sort(compareNodesForEcosystem)
|
|
901
|
+
const levelSizes = buildEcosystemLevelSizes(sortedNodes.length)
|
|
902
|
+
const expansionLevels = buildEcosystemExpansionLevels(levelSizes, nodes.length)
|
|
903
|
+
const baseSize = levelSizes[0] ?? ecosystemLevelNodeCap
|
|
847
904
|
const clustersBySize = new Map()
|
|
848
905
|
const nodeClusterBySize = new Map()
|
|
849
906
|
let parentLookup = null
|
|
850
907
|
|
|
851
|
-
for (let index = 0; index <
|
|
852
|
-
const size =
|
|
908
|
+
for (let index = 0; index < levelSizes.length; index += 1) {
|
|
909
|
+
const size = levelSizes[index]
|
|
853
910
|
const level = buildEcosystemLevel(sortedNodes, size, parentLookup, center)
|
|
854
911
|
clustersBySize.set(size, level.clusters)
|
|
855
912
|
nodeClusterBySize.set(size, level.clusterByNodeId)
|
|
@@ -857,9 +914,12 @@ const buildEcosystemGraph = (nodes, center, hub) => {
|
|
|
857
914
|
}
|
|
858
915
|
|
|
859
916
|
return {
|
|
860
|
-
clusters: clustersBySize.get(
|
|
917
|
+
clusters: clustersBySize.get(baseSize) ?? [],
|
|
861
918
|
clustersBySize,
|
|
862
919
|
nodeClusterBySize,
|
|
920
|
+
levelSizes,
|
|
921
|
+
expansionLevels,
|
|
922
|
+
baseSize,
|
|
863
923
|
hubCluster
|
|
864
924
|
}
|
|
865
925
|
}
|
|
@@ -903,7 +963,7 @@ const zoomProgress = (scale, start, end) =>
|
|
|
903
963
|
|
|
904
964
|
const semanticZoomSpread = (progress, childSize) => {
|
|
905
965
|
const curve = Math.pow(progress, 4.2)
|
|
906
|
-
if (childSize >=
|
|
966
|
+
if (childSize >= Math.ceil(ecosystemLevelNodeCap / 2)) {
|
|
907
967
|
return 0.12 + curve * 0.88
|
|
908
968
|
}
|
|
909
969
|
return curve
|
|
@@ -911,7 +971,7 @@ const semanticZoomSpread = (progress, childSize) => {
|
|
|
911
971
|
|
|
912
972
|
const opacityForProgress = (progress, childSize) => {
|
|
913
973
|
const eased = Math.pow(progress, 2.1)
|
|
914
|
-
if (childSize >=
|
|
974
|
+
if (childSize >= Math.ceil(ecosystemLevelNodeCap / 2)) {
|
|
915
975
|
return 0.22 + eased * 0.78
|
|
916
976
|
}
|
|
917
977
|
return eased
|
|
@@ -953,13 +1013,13 @@ const spreadChildClusterFromParent = (cluster, childSize, progress, spread) => {
|
|
|
953
1013
|
}
|
|
954
1014
|
|
|
955
1015
|
const selectHierarchicalEcosystemClusters = viewport => {
|
|
956
|
-
const baseClusters = state.ecosystemClustersBySize.get(
|
|
1016
|
+
const baseClusters = state.ecosystemClustersBySize.get(state.ecosystemBaseSize) ?? state.ecosystemClusters
|
|
957
1017
|
const visibleBaseClusters = filterEcosystemClustersByViewport(baseClusters, viewport)
|
|
958
1018
|
const hubClusters = state.ecosystemHubCluster ? [state.ecosystemHubCluster] : []
|
|
959
1019
|
const visibleClusters = [...visibleBaseClusters]
|
|
960
1020
|
|
|
961
|
-
for (let index = 0; index < ecosystemExpansionLevels.length; index += 1) {
|
|
962
|
-
const level = ecosystemExpansionLevels[index]
|
|
1021
|
+
for (let index = 0; index < state.ecosystemExpansionLevels.length; index += 1) {
|
|
1022
|
+
const level = state.ecosystemExpansionLevels[index]
|
|
963
1023
|
const parentClusters = visibleClusters.filter(cluster => cluster.size === level.parentSize)
|
|
964
1024
|
if (parentClusters.length === 0) {
|
|
965
1025
|
continue
|
|
@@ -2550,22 +2610,10 @@ const clusterRadiusPx = cluster => {
|
|
|
2550
2610
|
return 3.8
|
|
2551
2611
|
}
|
|
2552
2612
|
if (String(cluster.id).startsWith('ecosystem-')) {
|
|
2553
|
-
const
|
|
2554
|
-
|
|
2555
|
-
|
|
2556
|
-
|
|
2557
|
-
: cluster.size >= 250
|
|
2558
|
-
? 0.82
|
|
2559
|
-
: cluster.size >= 125
|
|
2560
|
-
? 0.74
|
|
2561
|
-
: cluster.size >= 60
|
|
2562
|
-
? 0.66
|
|
2563
|
-
: cluster.size >= 30
|
|
2564
|
-
? 0.62
|
|
2565
|
-
: cluster.size >= 15
|
|
2566
|
-
? 0.74
|
|
2567
|
-
: 0.9
|
|
2568
|
-
return Math.max(0.62, Math.min(2.2, base + Math.log10(cluster.count + 1) * 0.14))
|
|
2613
|
+
const size = Math.max(1, Math.min(ecosystemLevelNodeCap, cluster.size || cluster.count || 1))
|
|
2614
|
+
const sizeBias = 0.56 + Math.log10(size + 1) * 0.28
|
|
2615
|
+
const densityBias = Math.log10((cluster.count || 1) + 1) * 0.12
|
|
2616
|
+
return Math.max(0.62, Math.min(2.4, sizeBias + densityBias))
|
|
2569
2617
|
}
|
|
2570
2618
|
return Math.max(8, Math.min(28, 8 + Math.log2(cluster.count + 1) * 3))
|
|
2571
2619
|
}
|
package/package.json
CHANGED