@andespindola/brainlink 1.0.5 → 1.0.6
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 +8 -0
- package/dist/application/add-note.js +2 -2
- package/dist/application/build-context.js +16 -10
- package/dist/application/canonical-context-links.js +44 -5
- package/dist/application/check-package-update.js +105 -0
- package/dist/application/frontend/client/chunk-fetch.js +236 -0
- package/dist/application/frontend/client/controls.js +178 -0
- package/dist/application/frontend/client/elements.js +122 -0
- package/dist/application/frontend/client/input.js +202 -0
- package/dist/application/frontend/client/node-details.js +191 -0
- package/dist/application/frontend/client/rendering.js +296 -0
- package/dist/application/frontend/client/scope-theme.js +114 -0
- package/dist/application/frontend/client/spatial.js +98 -0
- package/dist/application/frontend/client/storage.js +215 -0
- package/dist/application/frontend/client/upload.js +90 -0
- package/dist/application/frontend/client/worker-bootstrap.js +147 -0
- package/dist/application/frontend/client-js.js +24 -1837
- package/dist/application/frontend/client-render-worker-js.js +1 -1
- package/dist/application/index-vault-phases.js +189 -0
- package/dist/application/index-vault.js +44 -165
- package/dist/cli/commands/write/dedupe-commands.js +59 -0
- package/dist/cli/commands/write/index-commands.js +205 -0
- package/dist/cli/commands/write/link-commands.js +68 -0
- package/dist/cli/commands/write/note-commands.js +146 -0
- package/dist/cli/commands/write/server-commands.js +553 -0
- package/dist/cli/commands/write/shared.js +35 -0
- package/dist/cli/commands/write/vault-lifecycle-commands.js +270 -0
- package/dist/cli/commands/write-commands.js +12 -1303
- package/dist/cli/main.js +39 -3
- package/dist/domain/context.js +39 -3
- package/dist/domain/embeddings.js +31 -5
- package/dist/domain/graph-contexts.js +62 -57
- package/dist/domain/graph-layout/cauliflower-layout.js +116 -0
- package/dist/domain/graph-layout/collisions.js +100 -0
- package/dist/domain/graph-layout/hierarchy.js +135 -0
- package/dist/domain/graph-layout/metrics.js +111 -0
- package/dist/domain/graph-layout/segments.js +76 -0
- package/dist/domain/graph-layout/star-layout.js +110 -0
- package/dist/domain/graph-layout.js +4 -625
- package/dist/infrastructure/config.js +6 -0
- package/dist/infrastructure/file-index.js +13 -4
- package/dist/infrastructure/semantic-prefilter.js +24 -0
- package/dist/mcp/server.js +7 -0
- package/dist/mcp/tool-guard.js +29 -0
- package/dist/mcp/tools/maintenance-tools.js +409 -0
- package/dist/mcp/tools/read-tools.js +504 -0
- package/dist/mcp/tools/shared.js +216 -0
- package/dist/mcp/tools/write-tools.js +247 -0
- package/dist/mcp/tools.js +3 -1357
- package/docs/QUICKSTART.md +4 -0
- package/package.json +2 -2
|
@@ -0,0 +1,215 @@
|
|
|
1
|
+
export const createStorageJs = () => `
|
|
2
|
+
const escapeHtml = (value) => String(value)
|
|
3
|
+
.replaceAll('&', '&')
|
|
4
|
+
.replaceAll('<', '<')
|
|
5
|
+
.replaceAll('>', '>')
|
|
6
|
+
.replaceAll('"', '"')
|
|
7
|
+
.replaceAll("'", ''')
|
|
8
|
+
|
|
9
|
+
const readStoredAgent = () => {
|
|
10
|
+
try {
|
|
11
|
+
const value = window.localStorage.getItem(selectedAgentStorageKey)?.trim() ?? ''
|
|
12
|
+
return value.length > 0 ? value : ''
|
|
13
|
+
} catch {
|
|
14
|
+
return ''
|
|
15
|
+
}
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
const writeStoredAgent = (agentId) => {
|
|
19
|
+
try {
|
|
20
|
+
if (!agentId) {
|
|
21
|
+
window.localStorage.removeItem(selectedAgentStorageKey)
|
|
22
|
+
return
|
|
23
|
+
}
|
|
24
|
+
window.localStorage.setItem(selectedAgentStorageKey, agentId)
|
|
25
|
+
} catch {}
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
const readStoredContext = () => {
|
|
29
|
+
try {
|
|
30
|
+
const value = window.localStorage.getItem(selectedContextStorageKey)?.trim() ?? ''
|
|
31
|
+
return value.length > 0 ? value : ''
|
|
32
|
+
} catch {
|
|
33
|
+
return ''
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
const writeStoredContext = (contextId) => {
|
|
38
|
+
try {
|
|
39
|
+
if (!contextId) {
|
|
40
|
+
window.localStorage.removeItem(selectedContextStorageKey)
|
|
41
|
+
return
|
|
42
|
+
}
|
|
43
|
+
window.localStorage.setItem(selectedContextStorageKey, contextId)
|
|
44
|
+
} catch {}
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
const nodePositionsStorageKey = () => [
|
|
48
|
+
nodePositionsStoragePrefix,
|
|
49
|
+
state.graphSignature || 'unknown',
|
|
50
|
+
state.agentId || 'all-agents',
|
|
51
|
+
state.contextId || 'all-contexts'
|
|
52
|
+
].join(':')
|
|
53
|
+
|
|
54
|
+
const readStoredNodePositions = () => {
|
|
55
|
+
try {
|
|
56
|
+
const raw = window.localStorage.getItem(nodePositionsStorageKey())
|
|
57
|
+
const parsed = raw ? JSON.parse(raw) : []
|
|
58
|
+
if (!Array.isArray(parsed)) {
|
|
59
|
+
return new Map()
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
return new Map(parsed.flatMap((entry) => {
|
|
63
|
+
const id = typeof entry?.[0] === 'string' ? entry[0] : ''
|
|
64
|
+
const x = Number(entry?.[1])
|
|
65
|
+
const y = Number(entry?.[2])
|
|
66
|
+
return id && Number.isFinite(x) && Number.isFinite(y) ? [[id, { x, y }]] : []
|
|
67
|
+
}))
|
|
68
|
+
} catch {
|
|
69
|
+
return new Map()
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
const ensureNodePositionsLoaded = () => {
|
|
74
|
+
const storageKey = nodePositionsStorageKey()
|
|
75
|
+
if (!state.graphSignature || (state.nodePositionsSignature === state.graphSignature && state.nodePositionsScope === storageKey)) {
|
|
76
|
+
return
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
state.nodePositions = readStoredNodePositions()
|
|
80
|
+
state.nodePositionsSignature = state.graphSignature
|
|
81
|
+
state.nodePositionsScope = storageKey
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
const writeStoredNodePositions = () => {
|
|
85
|
+
try {
|
|
86
|
+
if (!state.graphSignature) {
|
|
87
|
+
return
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
const entries = Array.from(state.nodePositions.entries())
|
|
91
|
+
.filter((entry) => Number.isFinite(entry[1]?.x) && Number.isFinite(entry[1]?.y))
|
|
92
|
+
.map((entry) => [entry[0], entry[1].x, entry[1].y])
|
|
93
|
+
|
|
94
|
+
if (entries.length === 0) {
|
|
95
|
+
window.localStorage.removeItem(nodePositionsStorageKey())
|
|
96
|
+
return
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
window.localStorage.setItem(nodePositionsStorageKey(), JSON.stringify(entries))
|
|
100
|
+
} catch {}
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
const clearStoredNodePositions = () => {
|
|
104
|
+
try {
|
|
105
|
+
if (state.graphSignature) {
|
|
106
|
+
window.localStorage.removeItem(nodePositionsStorageKey())
|
|
107
|
+
}
|
|
108
|
+
} catch {}
|
|
109
|
+
state.nodePositions = new Map()
|
|
110
|
+
state.nodePositionsSignature = state.graphSignature
|
|
111
|
+
state.nodePositionsScope = nodePositionsStorageKey()
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
const graphViewStateQuery = () => {
|
|
115
|
+
const params = new URLSearchParams({ signature: state.graphSignature })
|
|
116
|
+
if (state.agentId) {
|
|
117
|
+
params.set('agent', state.agentId)
|
|
118
|
+
}
|
|
119
|
+
if (state.contextId) {
|
|
120
|
+
params.set('context', state.contextId)
|
|
121
|
+
}
|
|
122
|
+
return params.toString()
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
const syncNodePositionsFromServer = async () => {
|
|
126
|
+
if (!state.graphSignature) {
|
|
127
|
+
return
|
|
128
|
+
}
|
|
129
|
+
const scope = nodePositionsStorageKey()
|
|
130
|
+
if (state.serverNodePositionsScope === scope) {
|
|
131
|
+
return
|
|
132
|
+
}
|
|
133
|
+
state.serverNodePositionsScope = scope
|
|
134
|
+
|
|
135
|
+
try {
|
|
136
|
+
const response = await fetch('/api/graph-view-state?' + graphViewStateQuery())
|
|
137
|
+
if (!response.ok) {
|
|
138
|
+
return
|
|
139
|
+
}
|
|
140
|
+
const payload = await response.json()
|
|
141
|
+
const positions = Array.isArray(payload?.positions) ? payload.positions : []
|
|
142
|
+
if (positions.length === 0) {
|
|
143
|
+
return
|
|
144
|
+
}
|
|
145
|
+
state.nodePositions = new Map(positions.flatMap((position) => {
|
|
146
|
+
const id = typeof position?.id === 'string' ? position.id : ''
|
|
147
|
+
const x = Number(position?.x)
|
|
148
|
+
const y = Number(position?.y)
|
|
149
|
+
return id && Number.isFinite(x) && Number.isFinite(y) ? [[id, { x, y }]] : []
|
|
150
|
+
}))
|
|
151
|
+
writeStoredNodePositions()
|
|
152
|
+
} catch {}
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
const persistNodePositionsToServer = () => {
|
|
156
|
+
if (!state.graphSignature) {
|
|
157
|
+
return
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
const positions = Array.from(state.nodePositions.entries()).map(([id, position]) => ({
|
|
161
|
+
id,
|
|
162
|
+
x: position.x,
|
|
163
|
+
y: position.y
|
|
164
|
+
}))
|
|
165
|
+
|
|
166
|
+
fetch('/api/graph-view-state?' + graphViewStateQuery(), {
|
|
167
|
+
method: 'POST',
|
|
168
|
+
headers: { 'content-type': 'application/json' },
|
|
169
|
+
body: JSON.stringify({ positions })
|
|
170
|
+
}).catch(() => {})
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
const clearNodePositionsOnServer = () => {
|
|
174
|
+
if (!state.graphSignature) {
|
|
175
|
+
return
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
fetch('/api/graph-view-state?' + graphViewStateQuery(), { method: 'DELETE' }).catch(() => {})
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
const releaseSelectedNodePosition = () => {
|
|
182
|
+
if (!state.selectedNodeId || !state.nodePositions.has(state.selectedNodeId)) {
|
|
183
|
+
return
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
state.nodePositions.delete(state.selectedNodeId)
|
|
187
|
+
writeStoredNodePositions()
|
|
188
|
+
persistNodePositionsToServer()
|
|
189
|
+
scheduleChunkFetch({ fit: false })
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
const syncAgentInUrl = (agentId) => {
|
|
193
|
+
try {
|
|
194
|
+
const url = new URL(window.location.href)
|
|
195
|
+
if (agentId && agentId.trim().length > 0) {
|
|
196
|
+
url.searchParams.set('agent', agentId)
|
|
197
|
+
} else {
|
|
198
|
+
url.searchParams.delete('agent')
|
|
199
|
+
}
|
|
200
|
+
window.history.replaceState({}, '', url.toString())
|
|
201
|
+
} catch {}
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
const syncContextInUrl = (contextId) => {
|
|
205
|
+
try {
|
|
206
|
+
const url = new URL(window.location.href)
|
|
207
|
+
if (contextId && contextId.trim().length > 0) {
|
|
208
|
+
url.searchParams.set('context', contextId)
|
|
209
|
+
} else {
|
|
210
|
+
url.searchParams.delete('context')
|
|
211
|
+
}
|
|
212
|
+
window.history.replaceState({}, '', url.toString())
|
|
213
|
+
} catch {}
|
|
214
|
+
}
|
|
215
|
+
`;
|
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
export const createUploadJs = () => `
|
|
2
|
+
const openUploadDialog = () => {
|
|
3
|
+
elements.uploadDialog.hidden = false
|
|
4
|
+
elements.uploadStatus.textContent = ''
|
|
5
|
+
elements.uploadTitleInput.value = ''
|
|
6
|
+
elements.uploadFile.value = ''
|
|
7
|
+
elements.uploadAllowSensitive.checked = false
|
|
8
|
+
window.setTimeout(() => elements.uploadFile.focus(), 0)
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
const closeUploadDialog = () => {
|
|
12
|
+
elements.uploadDialog.hidden = true
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
const setUploadBusy = (busy) => {
|
|
16
|
+
elements.uploadSubmit.disabled = busy
|
|
17
|
+
elements.uploadFile.disabled = busy
|
|
18
|
+
elements.uploadTitleInput.disabled = busy
|
|
19
|
+
elements.uploadAllowSensitive.disabled = busy
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
const uploadImportUrl = () => {
|
|
23
|
+
const params = new URLSearchParams()
|
|
24
|
+
if (state.agentId) {
|
|
25
|
+
params.set('agent', state.agentId)
|
|
26
|
+
}
|
|
27
|
+
const query = params.toString()
|
|
28
|
+
|
|
29
|
+
return '/api/import-file' + (query ? '?' + query : '')
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
const submitUpload = async (event) => {
|
|
33
|
+
event.preventDefault()
|
|
34
|
+
const file = elements.uploadFile.files?.[0]
|
|
35
|
+
if (!file) {
|
|
36
|
+
elements.uploadStatus.textContent = 'Choose a file to import.'
|
|
37
|
+
return
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
const form = new FormData()
|
|
41
|
+
form.append('file', file)
|
|
42
|
+
const title = elements.uploadTitleInput.value.trim()
|
|
43
|
+
if (title) {
|
|
44
|
+
form.append('title', title)
|
|
45
|
+
}
|
|
46
|
+
if (elements.uploadAllowSensitive.checked) {
|
|
47
|
+
form.append('allowSensitive', 'true')
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
setUploadBusy(true)
|
|
51
|
+
elements.uploadStatus.textContent = 'Importing...'
|
|
52
|
+
|
|
53
|
+
try {
|
|
54
|
+
const response = await fetch(uploadImportUrl(), {
|
|
55
|
+
method: 'POST',
|
|
56
|
+
body: form
|
|
57
|
+
})
|
|
58
|
+
const payload = await response.json().catch(() => ({}))
|
|
59
|
+
if (!response.ok) {
|
|
60
|
+
throw new Error(payload?.error || 'Import failed')
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
elements.uploadStatus.textContent = 'Imported "' + String(payload?.title || file.name) + '".'
|
|
64
|
+
await loadAgents()
|
|
65
|
+
await loadContexts()
|
|
66
|
+
scheduleChunkFetch({ fit: true })
|
|
67
|
+
window.setTimeout(closeUploadDialog, 700)
|
|
68
|
+
} catch (error) {
|
|
69
|
+
elements.uploadStatus.textContent = error instanceof Error ? error.message : String(error)
|
|
70
|
+
} finally {
|
|
71
|
+
setUploadBusy(false)
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
const setupUploadDialog = () => {
|
|
76
|
+
elements.uploadOpen.addEventListener('click', openUploadDialog)
|
|
77
|
+
elements.uploadClose.addEventListener('click', closeUploadDialog)
|
|
78
|
+
elements.uploadForm.addEventListener('submit', (event) => {
|
|
79
|
+
submitUpload(event).catch((error) => {
|
|
80
|
+
elements.uploadStatus.textContent = error instanceof Error ? error.message : String(error)
|
|
81
|
+
setUploadBusy(false)
|
|
82
|
+
})
|
|
83
|
+
})
|
|
84
|
+
elements.uploadDialog.addEventListener('click', (event) => {
|
|
85
|
+
if (event.target === elements.uploadDialog) {
|
|
86
|
+
closeUploadDialog()
|
|
87
|
+
}
|
|
88
|
+
})
|
|
89
|
+
}
|
|
90
|
+
`;
|
|
@@ -0,0 +1,147 @@
|
|
|
1
|
+
export const createWorkerBootstrapJs = () => `
|
|
2
|
+
const mainThreadWebGlAvailable = () => {
|
|
3
|
+
try {
|
|
4
|
+
const probe = document.createElement('canvas')
|
|
5
|
+
const gl = probe.getContext('webgl2') || probe.getContext('webgl') || probe.getContext('experimental-webgl')
|
|
6
|
+
return Boolean(gl)
|
|
7
|
+
} catch (error) {
|
|
8
|
+
return false
|
|
9
|
+
}
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
// Uma vez que o controle do canvas é transferido para o worker (OffscreenCanvas),
|
|
13
|
+
// o elemento original não fornece mais nenhum contexto (2D ou WebGL). Quando o
|
|
14
|
+
// worker falha ao iniciar o WebGL, trocamos por um canvas novo para que o
|
|
15
|
+
// fallback 2D consiga desenhar o grafo.
|
|
16
|
+
const replaceGraphCanvas = () => {
|
|
17
|
+
const replacement = canvas.cloneNode(false)
|
|
18
|
+
if (canvas.parentNode) {
|
|
19
|
+
canvas.parentNode.replaceChild(replacement, canvas)
|
|
20
|
+
}
|
|
21
|
+
canvas = replacement
|
|
22
|
+
ctx2dFallback = null
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
const enterFallbackMode = (rebindInput) => {
|
|
26
|
+
state.rendererMode = 'fallback'
|
|
27
|
+
if (rebindInput) {
|
|
28
|
+
setupInput()
|
|
29
|
+
}
|
|
30
|
+
setViewportFromCanvas()
|
|
31
|
+
drawFallback()
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
const setupRenderWorker = () => {
|
|
35
|
+
const hasWorker = typeof Worker !== 'undefined'
|
|
36
|
+
const canTransfer = typeof canvas.transferControlToOffscreen === 'function'
|
|
37
|
+
|
|
38
|
+
if (!hasWorker || !canTransfer || !mainThreadWebGlAvailable()) {
|
|
39
|
+
enterFallbackMode(false)
|
|
40
|
+
return
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
try {
|
|
44
|
+
const offscreen = canvas.transferControlToOffscreen()
|
|
45
|
+
const worker = new Worker('/render-worker.js')
|
|
46
|
+
state.renderWorker = worker
|
|
47
|
+
|
|
48
|
+
worker.onmessage = (event) => {
|
|
49
|
+
const payload = event.data
|
|
50
|
+
if (!payload || typeof payload !== 'object') {
|
|
51
|
+
return
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
if (payload.type === 'ready') {
|
|
55
|
+
state.workerReady = true
|
|
56
|
+
scheduleChunkFetch({ fit: true })
|
|
57
|
+
return
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
if (payload.type === 'pick-result') {
|
|
61
|
+
if (payload.node && typeof payload.node.id === 'string' && payload.node.id.length > 0) {
|
|
62
|
+
handlePickedNode(payload.node)
|
|
63
|
+
}
|
|
64
|
+
return
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
if (payload.type === 'frame-stats') {
|
|
68
|
+
state.lastVisibleNodes = Number.isFinite(payload.visibleNodes) ? payload.visibleNodes : state.lastVisibleNodes
|
|
69
|
+
state.lastVisibleEdges = Number.isFinite(payload.visibleEdges) ? payload.visibleEdges : state.lastVisibleEdges
|
|
70
|
+
return
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
if (payload.type === 'fatal') {
|
|
74
|
+
console.error(payload.message)
|
|
75
|
+
state.workerReady = false
|
|
76
|
+
if (state.renderWorker) {
|
|
77
|
+
state.renderWorker.terminate()
|
|
78
|
+
}
|
|
79
|
+
state.renderWorker = null
|
|
80
|
+
// O canvas já foi transferido para o worker: troca por um novo e
|
|
81
|
+
// religa os eventos de interação antes de cair no fallback 2D.
|
|
82
|
+
replaceGraphCanvas()
|
|
83
|
+
enterFallbackMode(true)
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
worker.postMessage({
|
|
88
|
+
type: 'init',
|
|
89
|
+
canvas: offscreen,
|
|
90
|
+
width: state.viewport.width,
|
|
91
|
+
height: state.viewport.height,
|
|
92
|
+
devicePixelRatio: state.viewport.ratio,
|
|
93
|
+
camera: state.camera,
|
|
94
|
+
theme: graphTheme
|
|
95
|
+
}, [offscreen])
|
|
96
|
+
} catch (error) {
|
|
97
|
+
console.error(error)
|
|
98
|
+
replaceGraphCanvas()
|
|
99
|
+
enterFallbackMode(false)
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
const wireNodeLinkClicks = () => {
|
|
104
|
+
const dialog = elements.contentDialog
|
|
105
|
+
dialog.addEventListener('click', (event) => {
|
|
106
|
+
const target = event.target
|
|
107
|
+
if (!(target instanceof HTMLElement)) {
|
|
108
|
+
return
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
const button = target.closest('button[data-node-id]')
|
|
112
|
+
if (!button) {
|
|
113
|
+
return
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
const id = button.getAttribute('data-node-id') || ''
|
|
117
|
+
if (id) {
|
|
118
|
+
loadNodeDetails(id).catch((error) => console.error(error))
|
|
119
|
+
}
|
|
120
|
+
})
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
const bootstrap = async () => {
|
|
124
|
+
setViewportFromCanvas()
|
|
125
|
+
setupRenderWorker()
|
|
126
|
+
setupInput()
|
|
127
|
+
setupControls()
|
|
128
|
+
setupUploadDialog()
|
|
129
|
+
setupContextControl()
|
|
130
|
+
wireNodeLinkClicks()
|
|
131
|
+
|
|
132
|
+
window.addEventListener('resize', () => {
|
|
133
|
+
setViewportFromCanvas()
|
|
134
|
+
scheduleChunkFetch()
|
|
135
|
+
})
|
|
136
|
+
|
|
137
|
+
await loadAgents()
|
|
138
|
+
await loadContexts()
|
|
139
|
+
updateTotals()
|
|
140
|
+
|
|
141
|
+
scheduleChunkFetch({ fit: true })
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
bootstrap().catch((error) => {
|
|
145
|
+
console.error(error)
|
|
146
|
+
})
|
|
147
|
+
`;
|