@andespindola/brainlink 0.1.0-beta.52 → 0.1.0-beta.54
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.
|
@@ -52,6 +52,7 @@ const state = {
|
|
|
52
52
|
overviewClusters: [],
|
|
53
53
|
macroCenter: { x: 0, y: 0 },
|
|
54
54
|
macroRepresentative: null,
|
|
55
|
+
primaryHub: null,
|
|
55
56
|
filterWorker: null,
|
|
56
57
|
filterReady: false,
|
|
57
58
|
lastHoverHitAt: 0,
|
|
@@ -100,6 +101,39 @@ const initialAgentFromUrl = (() => {
|
|
|
100
101
|
}
|
|
101
102
|
})()
|
|
102
103
|
|
|
104
|
+
const selectedAgentStorageKey = 'brainlink:selected-agent'
|
|
105
|
+
|
|
106
|
+
const readStoredAgent = () => {
|
|
107
|
+
try {
|
|
108
|
+
const value = window.localStorage.getItem(selectedAgentStorageKey)?.trim() ?? ''
|
|
109
|
+
return value.length > 0 ? value : ''
|
|
110
|
+
} catch {
|
|
111
|
+
return ''
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
const writeStoredAgent = (agentId) => {
|
|
116
|
+
try {
|
|
117
|
+
if (!agentId) {
|
|
118
|
+
window.localStorage.removeItem(selectedAgentStorageKey)
|
|
119
|
+
return
|
|
120
|
+
}
|
|
121
|
+
window.localStorage.setItem(selectedAgentStorageKey, agentId)
|
|
122
|
+
} catch {}
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
const syncAgentInUrl = (agentId) => {
|
|
126
|
+
try {
|
|
127
|
+
const url = new URL(window.location.href)
|
|
128
|
+
if (agentId && agentId.trim().length > 0) {
|
|
129
|
+
url.searchParams.set('agent', agentId)
|
|
130
|
+
} else {
|
|
131
|
+
url.searchParams.delete('agent')
|
|
132
|
+
}
|
|
133
|
+
window.history.replaceState({}, '', url.toString())
|
|
134
|
+
} catch {}
|
|
135
|
+
}
|
|
136
|
+
|
|
103
137
|
const agentQuery = (separator = '?') => state.agentId ? separator + 'agent=' + encodeURIComponent(state.agentId) : ''
|
|
104
138
|
|
|
105
139
|
const setGraphStatus = text => {
|
|
@@ -296,6 +330,7 @@ const recomputeVisibility = () => {
|
|
|
296
330
|
}
|
|
297
331
|
: { x: 0, y: 0 }
|
|
298
332
|
state.macroRepresentative = resolveMacroRepresentative(nodes)
|
|
333
|
+
state.primaryHub = rankedHubNodes()[0] ?? null
|
|
299
334
|
markRenderDirty()
|
|
300
335
|
}
|
|
301
336
|
|
|
@@ -679,7 +714,7 @@ const autoFitScaleRangeByNodeCount = nodeCount => {
|
|
|
679
714
|
return { min: 0.0008, max: 0.24 }
|
|
680
715
|
}
|
|
681
716
|
|
|
682
|
-
const fitView = (options = { useFiltered: true, macro: false }) => {
|
|
717
|
+
const fitView = (options = { useFiltered: true, macro: false, preferHubCenter: true }) => {
|
|
683
718
|
const rect = canvas.getBoundingClientRect()
|
|
684
719
|
const width = Math.max(rect.width, 320)
|
|
685
720
|
const height = Math.max(rect.height, 320)
|
|
@@ -716,8 +751,12 @@ const fitView = (options = { useFiltered: true, macro: false }) => {
|
|
|
716
751
|
: nodes.length > massiveGraphNodeThreshold
|
|
717
752
|
? clampScale(Math.min(baselineScale, massiveAutoFitMacroScale))
|
|
718
753
|
: baselineScale
|
|
719
|
-
const
|
|
720
|
-
|
|
754
|
+
const hubCenter =
|
|
755
|
+
options.preferHubCenter && state.primaryHub && nodes.some((node) => node.id === state.primaryHub.id)
|
|
756
|
+
? state.primaryHub
|
|
757
|
+
: null
|
|
758
|
+
const centerX = hubCenter ? hubCenter.x : (bounds.minX + bounds.maxX) / 2
|
|
759
|
+
const centerY = hubCenter ? hubCenter.y : (bounds.minY + bounds.maxY) / 2
|
|
721
760
|
|
|
722
761
|
state.transform = {
|
|
723
762
|
x: clampTransformCoordinate(width / 2 - centerX * scale),
|
|
@@ -729,7 +768,7 @@ const fitView = (options = { useFiltered: true, macro: false }) => {
|
|
|
729
768
|
markRenderDirty()
|
|
730
769
|
}
|
|
731
770
|
|
|
732
|
-
const resetView = () => fitView({ useFiltered: false, macro: true })
|
|
771
|
+
const resetView = () => fitView({ useFiltered: false, macro: true, preferHubCenter: true })
|
|
733
772
|
|
|
734
773
|
const createLayout = graph => {
|
|
735
774
|
const nodeRows = Array.isArray(graph.nodes) ? graph.nodes : []
|
|
@@ -1539,6 +1578,8 @@ const bindEvents = () => {
|
|
|
1539
1578
|
})
|
|
1540
1579
|
elements.agent.addEventListener('change', event => {
|
|
1541
1580
|
state.agentId = event.target.value
|
|
1581
|
+
writeStoredAgent(state.agentId)
|
|
1582
|
+
syncAgentInUrl(state.agentId)
|
|
1542
1583
|
state.selected = null
|
|
1543
1584
|
state.nodeDetails = new Map()
|
|
1544
1585
|
resetContentFilter()
|
|
@@ -1665,7 +1706,7 @@ const loadAgents = async () => {
|
|
|
1665
1706
|
const response = await fetch('/api/agents')
|
|
1666
1707
|
const payload = await response.json()
|
|
1667
1708
|
const agents = Array.isArray(payload.agents) ? payload.agents : []
|
|
1668
|
-
const preferredAgent = state.agentId || initialAgentFromUrl
|
|
1709
|
+
const preferredAgent = state.agentId || initialAgentFromUrl || readStoredAgent()
|
|
1669
1710
|
const currentExists = agents.some(agent => agent.id === preferredAgent)
|
|
1670
1711
|
const selected = currentExists
|
|
1671
1712
|
? preferredAgent
|
|
@@ -1673,6 +1714,8 @@ const loadAgents = async () => {
|
|
|
1673
1714
|
const signature = JSON.stringify(agents.map(agent => [agent.id, agent.documentCount]))
|
|
1674
1715
|
|
|
1675
1716
|
state.agentId = selected
|
|
1717
|
+
writeStoredAgent(selected)
|
|
1718
|
+
syncAgentInUrl(selected)
|
|
1676
1719
|
if (signature !== state.agentsSignature) {
|
|
1677
1720
|
const formatAgentLabel = (agent) => agent.id
|
|
1678
1721
|
elements.agent.innerHTML = agents.length
|
|
@@ -20,6 +20,7 @@ const segmentAngles = {
|
|
|
20
20
|
Evaluation: 2.08,
|
|
21
21
|
Security: 2.82
|
|
22
22
|
};
|
|
23
|
+
const hubTitlePattern = /\b(memory\s*hub|knowledge\s*root|moc|map)\b/i;
|
|
23
24
|
const hashText = (value) => Array.from(value).reduce((hash, char) => ((hash << 5) - hash + char.charCodeAt(0)) | 0, 0);
|
|
24
25
|
const jitter = (value, range) => {
|
|
25
26
|
const normalized = Math.abs(hashText(value) % 1000) / 1000;
|
|
@@ -62,6 +63,44 @@ const byDegreeThenTitle = (degrees) => (left, right) => {
|
|
|
62
63
|
const degreeDelta = (degrees.get(right.id) ?? 0) - (degrees.get(left.id) ?? 0);
|
|
63
64
|
return degreeDelta === 0 ? byTitle(left, right) : degreeDelta;
|
|
64
65
|
};
|
|
66
|
+
const hubScore = (node) => {
|
|
67
|
+
const title = node.title.trim().toLowerCase();
|
|
68
|
+
if (title === 'memory hub')
|
|
69
|
+
return 5;
|
|
70
|
+
if (title === 'knowledge root')
|
|
71
|
+
return 4;
|
|
72
|
+
if (/\bmoc\b/i.test(node.title))
|
|
73
|
+
return 3;
|
|
74
|
+
return hubTitlePattern.test(node.title) ? 2 : 0;
|
|
75
|
+
};
|
|
76
|
+
const selectPrimaryHubId = (nodes, degrees) => {
|
|
77
|
+
const ranked = [...nodes]
|
|
78
|
+
.filter((node) => hubScore(node) > 0)
|
|
79
|
+
.sort((left, right) => {
|
|
80
|
+
const scoreDelta = hubScore(right) - hubScore(left);
|
|
81
|
+
if (scoreDelta !== 0)
|
|
82
|
+
return scoreDelta;
|
|
83
|
+
const degreeDelta = (degrees.get(right.id) ?? 0) - (degrees.get(left.id) ?? 0);
|
|
84
|
+
if (degreeDelta !== 0)
|
|
85
|
+
return degreeDelta;
|
|
86
|
+
return left.title.localeCompare(right.title);
|
|
87
|
+
});
|
|
88
|
+
return ranked[0]?.id ?? null;
|
|
89
|
+
};
|
|
90
|
+
const centerLayoutByNode = (nodes, nodeId) => {
|
|
91
|
+
if (!nodeId) {
|
|
92
|
+
return nodes;
|
|
93
|
+
}
|
|
94
|
+
const anchor = nodes.find((node) => node.id === nodeId);
|
|
95
|
+
if (!anchor) {
|
|
96
|
+
return nodes;
|
|
97
|
+
}
|
|
98
|
+
return nodes.map((node) => ({
|
|
99
|
+
...node,
|
|
100
|
+
x: node.x - anchor.x,
|
|
101
|
+
y: node.y - anchor.y
|
|
102
|
+
}));
|
|
103
|
+
};
|
|
65
104
|
const naturalSegmentSeed = (node) => groupKey(node) === '00-maps' || /\b(moc|map)\b/i.test(node.title);
|
|
66
105
|
const segmentName = (node) => node.title.replace(/^MOC\s+/i, '').replace(/\s+Memory Map$/i, '').trim() || node.title;
|
|
67
106
|
const collectComponent = (adjacency, startId, visited) => {
|
|
@@ -246,8 +285,10 @@ export const createCauliflowerGraphLayout = (graph) => {
|
|
|
246
285
|
const segmentGroups = Array.from(groupNodesBySegment(graph.nodes, segments).entries())
|
|
247
286
|
.sort(([left], [right]) => left.localeCompare(right));
|
|
248
287
|
const nodes = relaxCollisions(segmentGroups.flatMap(createSegmentNodes(segments, degrees, segmentGroups.length)));
|
|
288
|
+
const primaryHubId = selectPrimaryHubId(graph.nodes, degrees);
|
|
289
|
+
const centeredNodes = centerLayoutByNode(nodes, primaryHubId);
|
|
249
290
|
return {
|
|
250
|
-
nodes,
|
|
291
|
+
nodes: centeredNodes,
|
|
251
292
|
edges: graph.edges
|
|
252
293
|
};
|
|
253
294
|
};
|
package/package.json
CHANGED