@andespindola/brainlink 0.1.0-beta.143 → 0.1.0-beta.144
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.
|
@@ -62,6 +62,14 @@ export const createClientHtml = () => `<!doctype html>
|
|
|
62
62
|
<button id="contentClose" type="button">Close</button>
|
|
63
63
|
</header>
|
|
64
64
|
<div class="content-meta">
|
|
65
|
+
<section class="content-meta-section">
|
|
66
|
+
<h3>Facts</h3>
|
|
67
|
+
<ul id="contentFacts"></ul>
|
|
68
|
+
</section>
|
|
69
|
+
<section class="content-meta-section">
|
|
70
|
+
<h3>Context Links</h3>
|
|
71
|
+
<ul id="contentContextLinks"></ul>
|
|
72
|
+
</section>
|
|
65
73
|
<section class="content-meta-section">
|
|
66
74
|
<h3>Tags</h3>
|
|
67
75
|
<div id="contentTags" class="tags"></div>
|
|
@@ -14,6 +14,8 @@ const elements = {
|
|
|
14
14
|
contentDialog: byId('contentDialog'),
|
|
15
15
|
contentTitle: byId('contentTitle'),
|
|
16
16
|
contentPath: byId('contentPath'),
|
|
17
|
+
contentFacts: byId('contentFacts'),
|
|
18
|
+
contentContextLinks: byId('contentContextLinks'),
|
|
17
19
|
contentTags: byId('contentTags'),
|
|
18
20
|
contentOutgoing: byId('contentOutgoing'),
|
|
19
21
|
contentIncoming: byId('contentIncoming'),
|
|
@@ -279,6 +281,71 @@ const list = (items) => {
|
|
|
279
281
|
.join('')
|
|
280
282
|
}
|
|
281
283
|
|
|
284
|
+
const extractContextLinks = (content) => {
|
|
285
|
+
if (typeof content !== 'string' || content.length === 0) {
|
|
286
|
+
return []
|
|
287
|
+
}
|
|
288
|
+
const lines = content.split(/\\r?\\n/)
|
|
289
|
+
let start = -1
|
|
290
|
+
for (let index = 0; index < lines.length; index += 1) {
|
|
291
|
+
if (/^##\\s+context\\s+links\\b/i.test(lines[index].trim())) {
|
|
292
|
+
start = index + 1
|
|
293
|
+
break
|
|
294
|
+
}
|
|
295
|
+
}
|
|
296
|
+
if (start < 0) {
|
|
297
|
+
return []
|
|
298
|
+
}
|
|
299
|
+
|
|
300
|
+
const links = []
|
|
301
|
+
for (let index = start; index < lines.length; index += 1) {
|
|
302
|
+
const line = lines[index].trim()
|
|
303
|
+
if (!line) {
|
|
304
|
+
continue
|
|
305
|
+
}
|
|
306
|
+
if (/^#{1,6}\\s+/.test(line)) {
|
|
307
|
+
break
|
|
308
|
+
}
|
|
309
|
+
const match = line.match(/\\[\\[([^\\]]+)\\]\\]/)
|
|
310
|
+
if (!match) {
|
|
311
|
+
continue
|
|
312
|
+
}
|
|
313
|
+
const title = match[1].trim()
|
|
314
|
+
if (!title) {
|
|
315
|
+
continue
|
|
316
|
+
}
|
|
317
|
+
const priorityMatch = line.match(/#(critical|important)\\b|priority:\\s*(high|critical)/i)
|
|
318
|
+
const priority = priorityMatch ? String(priorityMatch[1] || priorityMatch[2] || 'normal').toLowerCase() : 'normal'
|
|
319
|
+
links.push({ title, priority })
|
|
320
|
+
}
|
|
321
|
+
return links
|
|
322
|
+
}
|
|
323
|
+
|
|
324
|
+
const buildFacts = (node, outgoingCount, incomingCount) => {
|
|
325
|
+
const content = typeof node?.content === 'string' ? node.content : ''
|
|
326
|
+
const words = content.trim().length > 0 ? content.trim().split(/\\s+/).length : 0
|
|
327
|
+
return [
|
|
328
|
+
{ label: 'Agent', value: typeof node?.agentId === 'string' && node.agentId ? node.agentId : 'shared' },
|
|
329
|
+
{ label: 'Words', value: String(words) },
|
|
330
|
+
{ label: 'Chars', value: String(content.length) },
|
|
331
|
+
{ label: 'Outgoing', value: String(outgoingCount) },
|
|
332
|
+
{ label: 'Backlinks', value: String(incomingCount) }
|
|
333
|
+
]
|
|
334
|
+
}
|
|
335
|
+
|
|
336
|
+
const listFacts = (facts) => facts
|
|
337
|
+
.map((fact) => '<li><strong>' + escapeHtml(fact.label) + ':</strong> <small>' + escapeHtml(fact.value) + '</small></li>')
|
|
338
|
+
.join('')
|
|
339
|
+
|
|
340
|
+
const listContextLinks = (links) => {
|
|
341
|
+
if (!Array.isArray(links) || links.length === 0) {
|
|
342
|
+
return '<li><small>No context links found.</small></li>'
|
|
343
|
+
}
|
|
344
|
+
return links
|
|
345
|
+
.map((link) => '<li><span>' + escapeHtml(link.title) + '</span><small>' + escapeHtml(link.priority || 'normal') + '</small></li>')
|
|
346
|
+
.join('')
|
|
347
|
+
}
|
|
348
|
+
|
|
282
349
|
const linkedNodes = (node) => {
|
|
283
350
|
const nodeById = new Map((state.chunk.nodes || []).map((item) => [item[0], item]))
|
|
284
351
|
const edges = normalizeList(state.chunk.edges)
|
|
@@ -342,6 +409,10 @@ const loadNodeDetails = async (nodeId) => {
|
|
|
342
409
|
: '<span>No tags</span>'
|
|
343
410
|
|
|
344
411
|
const related = linkedNodes(node)
|
|
412
|
+
const contextLinks = extractContextLinks(node.content)
|
|
413
|
+
const facts = buildFacts(node, related.outgoing.length, related.incoming.length)
|
|
414
|
+
elements.contentFacts.innerHTML = listFacts(facts)
|
|
415
|
+
elements.contentContextLinks.innerHTML = listContextLinks(contextLinks)
|
|
345
416
|
elements.contentOutgoing.innerHTML = list(related.outgoing)
|
|
346
417
|
elements.contentIncoming.innerHTML = list(related.incoming)
|
|
347
418
|
elements.contentBody.textContent = typeof node.content === 'string' ? node.content : ''
|
|
@@ -25,10 +25,14 @@ const nodeIndexById = new Map()
|
|
|
25
25
|
const highlightedIds = new Set()
|
|
26
26
|
let selectedNodeId = null
|
|
27
27
|
let dirty = true
|
|
28
|
-
let
|
|
28
|
+
let renderScheduled = false
|
|
29
29
|
let hoverX = null
|
|
30
30
|
let hoverY = null
|
|
31
31
|
let lastFrameAt = 0
|
|
32
|
+
let lastVisibleEdges = 0
|
|
33
|
+
let edgePositionsBuffer = new Float32Array(0)
|
|
34
|
+
let pointPositionsBuffer = new Float32Array(0)
|
|
35
|
+
let pointSizesBuffer = new Float32Array(0)
|
|
32
36
|
|
|
33
37
|
const defaultTheme = {
|
|
34
38
|
node: [0.68, 0.72, 0.78, 1],
|
|
@@ -133,6 +137,14 @@ const initWebGl = () => {
|
|
|
133
137
|
return true
|
|
134
138
|
}
|
|
135
139
|
|
|
140
|
+
const ensureFloat32Capacity = (buffer, neededLength) => {
|
|
141
|
+
if (buffer.length >= neededLength) {
|
|
142
|
+
return buffer
|
|
143
|
+
}
|
|
144
|
+
const next = Math.max(neededLength, Math.ceil(buffer.length * 1.6), 1024)
|
|
145
|
+
return new Float32Array(next)
|
|
146
|
+
}
|
|
147
|
+
|
|
136
148
|
const resizeCanvas = (width, height, ratio) => {
|
|
137
149
|
viewportWidth = Math.max(320, Number.isFinite(width) ? width : viewportWidth)
|
|
138
150
|
viewportHeight = Math.max(320, Number.isFinite(height) ? height : viewportHeight)
|
|
@@ -146,6 +158,7 @@ const resizeCanvas = (width, height, ratio) => {
|
|
|
146
158
|
gl.viewport(0, 0, canvas.width, canvas.height)
|
|
147
159
|
}
|
|
148
160
|
dirty = true
|
|
161
|
+
requestRender()
|
|
149
162
|
}
|
|
150
163
|
|
|
151
164
|
const toScreenPoint = (x, y) => {
|
|
@@ -262,7 +275,9 @@ const cullVisibleNodes = () => {
|
|
|
262
275
|
const drawEdges = () => {
|
|
263
276
|
if (!gl || state.edgeCount === 0) return
|
|
264
277
|
|
|
265
|
-
|
|
278
|
+
edgePositionsBuffer = ensureFloat32Capacity(edgePositionsBuffer, state.edgeCount * 4)
|
|
279
|
+
let cursor = 0
|
|
280
|
+
let visibleEdges = 0
|
|
266
281
|
for (let index = 0; index < state.edgeCount; index += 1) {
|
|
267
282
|
const source = state.edgeSource[index]
|
|
268
283
|
const target = state.edgeTarget[index]
|
|
@@ -271,50 +286,61 @@ const drawEdges = () => {
|
|
|
271
286
|
}
|
|
272
287
|
const [sx, sy] = toScreenPoint(state.x[source], state.y[source])
|
|
273
288
|
const [tx, ty] = toScreenPoint(state.x[target], state.y[target])
|
|
274
|
-
|
|
289
|
+
edgePositionsBuffer[cursor] = sx
|
|
290
|
+
edgePositionsBuffer[cursor + 1] = sy
|
|
291
|
+
edgePositionsBuffer[cursor + 2] = tx
|
|
292
|
+
edgePositionsBuffer[cursor + 3] = ty
|
|
293
|
+
cursor += 4
|
|
294
|
+
visibleEdges += 1
|
|
275
295
|
}
|
|
276
296
|
|
|
277
|
-
|
|
297
|
+
lastVisibleEdges = visibleEdges
|
|
298
|
+
if (cursor === 0) return
|
|
278
299
|
|
|
279
300
|
gl.useProgram(lineProgram)
|
|
280
301
|
gl.bindBuffer(gl.ARRAY_BUFFER, lineBuffer)
|
|
281
|
-
gl.bufferData(gl.ARRAY_BUFFER,
|
|
302
|
+
gl.bufferData(gl.ARRAY_BUFFER, edgePositionsBuffer.subarray(0, cursor), gl.STREAM_DRAW)
|
|
282
303
|
gl.enableVertexAttribArray(linePositionLocation)
|
|
283
304
|
gl.vertexAttribPointer(linePositionLocation, 2, gl.FLOAT, false, 0, 0)
|
|
284
305
|
gl.uniform2f(lineResolutionLocation, canvas.width, canvas.height)
|
|
285
306
|
gl.uniform4fv(lineColorLocation, state.nodeCount > 4000 ? theme.edge : theme.edgeHeavy)
|
|
286
307
|
gl.lineWidth(1)
|
|
287
|
-
gl.drawArrays(gl.LINES, 0,
|
|
308
|
+
gl.drawArrays(gl.LINES, 0, cursor / 2)
|
|
288
309
|
}
|
|
289
310
|
|
|
290
311
|
const drawNodeLayer = (predicate, color, radiusBoost = 1) => {
|
|
291
312
|
if (!gl || state.nodeCount === 0) return
|
|
292
313
|
|
|
293
|
-
|
|
294
|
-
|
|
314
|
+
pointPositionsBuffer = ensureFloat32Capacity(pointPositionsBuffer, state.nodeCount * 2)
|
|
315
|
+
pointSizesBuffer = ensureFloat32Capacity(pointSizesBuffer, state.nodeCount)
|
|
316
|
+
let positionCursor = 0
|
|
317
|
+
let sizeCursor = 0
|
|
295
318
|
for (let index = 0; index < state.nodeCount; index += 1) {
|
|
296
319
|
if (!predicate(index)) continue
|
|
297
320
|
const [sx, sy] = toScreenPoint(state.x[index], state.y[index])
|
|
298
|
-
|
|
299
|
-
|
|
321
|
+
pointPositionsBuffer[positionCursor] = sx
|
|
322
|
+
pointPositionsBuffer[positionCursor + 1] = sy
|
|
323
|
+
pointSizesBuffer[sizeCursor] = Math.max(1.2, state.radius[index] * camera.scale * devicePixelRatio * radiusBoost)
|
|
324
|
+
positionCursor += 2
|
|
325
|
+
sizeCursor += 1
|
|
300
326
|
}
|
|
301
327
|
|
|
302
|
-
if (
|
|
328
|
+
if (positionCursor === 0) return
|
|
303
329
|
|
|
304
330
|
gl.useProgram(pointProgram)
|
|
305
331
|
gl.bindBuffer(gl.ARRAY_BUFFER, pointPositionBuffer)
|
|
306
|
-
gl.bufferData(gl.ARRAY_BUFFER,
|
|
332
|
+
gl.bufferData(gl.ARRAY_BUFFER, pointPositionsBuffer.subarray(0, positionCursor), gl.STREAM_DRAW)
|
|
307
333
|
gl.enableVertexAttribArray(pointPositionLocation)
|
|
308
334
|
gl.vertexAttribPointer(pointPositionLocation, 2, gl.FLOAT, false, 0, 0)
|
|
309
335
|
|
|
310
336
|
gl.bindBuffer(gl.ARRAY_BUFFER, pointSizeBuffer)
|
|
311
|
-
gl.bufferData(gl.ARRAY_BUFFER,
|
|
337
|
+
gl.bufferData(gl.ARRAY_BUFFER, pointSizesBuffer.subarray(0, sizeCursor), gl.STREAM_DRAW)
|
|
312
338
|
gl.enableVertexAttribArray(pointSizeLocation)
|
|
313
339
|
gl.vertexAttribPointer(pointSizeLocation, 1, gl.FLOAT, false, 0, 0)
|
|
314
340
|
|
|
315
341
|
gl.uniform2f(pointResolutionLocation, canvas.width, canvas.height)
|
|
316
342
|
gl.uniform4fv(pointColorLocation, color)
|
|
317
|
-
gl.drawArrays(gl.POINTS, 0,
|
|
343
|
+
gl.drawArrays(gl.POINTS, 0, positionCursor / 2)
|
|
318
344
|
}
|
|
319
345
|
|
|
320
346
|
const clear = () => {
|
|
@@ -325,16 +351,20 @@ const clear = () => {
|
|
|
325
351
|
}
|
|
326
352
|
|
|
327
353
|
const renderFrame = (now) => {
|
|
354
|
+
renderScheduled = false
|
|
355
|
+
if (!dirty) {
|
|
356
|
+
return
|
|
357
|
+
}
|
|
358
|
+
|
|
328
359
|
const delta = now - lastFrameAt
|
|
329
360
|
const minInterval = state.nodeCount > 20000 ? 22 : 16
|
|
330
|
-
if (delta < minInterval
|
|
331
|
-
|
|
361
|
+
if (delta < minInterval) {
|
|
362
|
+
requestRender()
|
|
332
363
|
return
|
|
333
364
|
}
|
|
334
365
|
lastFrameAt = now
|
|
335
366
|
|
|
336
367
|
if (!gl) {
|
|
337
|
-
scheduleNextFrame()
|
|
338
368
|
return
|
|
339
369
|
}
|
|
340
370
|
|
|
@@ -376,15 +406,17 @@ const renderFrame = (now) => {
|
|
|
376
406
|
}
|
|
377
407
|
return count
|
|
378
408
|
})(),
|
|
379
|
-
visibleEdges:
|
|
409
|
+
visibleEdges: lastVisibleEdges
|
|
380
410
|
})
|
|
381
411
|
dirty = false
|
|
382
412
|
}
|
|
383
|
-
|
|
384
|
-
scheduleNextFrame()
|
|
385
413
|
}
|
|
386
414
|
|
|
387
|
-
const
|
|
415
|
+
const requestRender = () => {
|
|
416
|
+
if (renderScheduled) {
|
|
417
|
+
return
|
|
418
|
+
}
|
|
419
|
+
renderScheduled = true
|
|
388
420
|
const raf = self.requestAnimationFrame
|
|
389
421
|
if (typeof raf === 'function') {
|
|
390
422
|
raf.call(self, renderFrame)
|
|
@@ -401,6 +433,7 @@ const setCamera = (nextCamera) => {
|
|
|
401
433
|
camera.y = Number.isFinite(nextCamera.y) ? Number(nextCamera.y) : camera.y
|
|
402
434
|
camera.scale = Number.isFinite(nextCamera.scale) ? Math.max(0.0002, Math.min(8, Number(nextCamera.scale))) : camera.scale
|
|
403
435
|
dirty = true
|
|
436
|
+
requestRender()
|
|
404
437
|
}
|
|
405
438
|
|
|
406
439
|
const worldAtScreen = (screenX, screenY) => {
|
|
@@ -453,6 +486,7 @@ const setHighlights = (ids) => {
|
|
|
453
486
|
state.highlighted[index] = highlightedIds.has(state.ids[index]) ? 1 : 0
|
|
454
487
|
}
|
|
455
488
|
dirty = true
|
|
489
|
+
requestRender()
|
|
456
490
|
}
|
|
457
491
|
|
|
458
492
|
const setSelected = (id) => {
|
|
@@ -461,6 +495,7 @@ const setSelected = (id) => {
|
|
|
461
495
|
state.selected[index] = selectedNodeId === state.ids[index] ? 1 : 0
|
|
462
496
|
}
|
|
463
497
|
dirty = true
|
|
498
|
+
requestRender()
|
|
464
499
|
}
|
|
465
500
|
|
|
466
501
|
self.onmessage = (event) => {
|
|
@@ -481,8 +516,7 @@ self.onmessage = (event) => {
|
|
|
481
516
|
}
|
|
482
517
|
resizeCanvas(payload.width, payload.height, payload.devicePixelRatio)
|
|
483
518
|
setCamera(payload.camera)
|
|
484
|
-
|
|
485
|
-
scheduleNextFrame()
|
|
519
|
+
requestRender()
|
|
486
520
|
postMessage({ type: 'ready' })
|
|
487
521
|
return
|
|
488
522
|
}
|
|
@@ -499,6 +533,7 @@ self.onmessage = (event) => {
|
|
|
499
533
|
|
|
500
534
|
if (payload.type === 'chunk') {
|
|
501
535
|
loadChunk(payload.chunk)
|
|
536
|
+
requestRender()
|
|
502
537
|
return
|
|
503
538
|
}
|
|
504
539
|
|
package/package.json
CHANGED