@andespindola/brainlink 0.1.0-beta.147 → 0.1.0-beta.149

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.
@@ -258,12 +258,12 @@ li small {
258
258
 
259
259
  .content-dialog {
260
260
  position: fixed;
261
- top: 84px;
262
- right: 12px;
261
+ top: 74px;
262
+ right: 16px;
263
263
  margin: 0;
264
- width: min(440px, calc(100vw - 24px));
265
- height: min(calc(100svh - 124px), 820px);
266
- max-height: calc(100svh - 124px);
264
+ width: min(760px, calc(100vw - 32px));
265
+ height: min(calc(100svh - 96px), 920px);
266
+ max-height: calc(100svh - 96px);
267
267
  padding: 0;
268
268
  border: 1px solid var(--line);
269
269
  border-radius: 8px;
@@ -331,7 +331,7 @@ li small {
331
331
 
332
332
  .content-meta {
333
333
  display: grid;
334
- grid-template-columns: repeat(3, minmax(0, 1fr));
334
+ grid-template-columns: repeat(2, minmax(0, 1fr));
335
335
  gap: 10px;
336
336
  padding: 14px 22px;
337
337
  border-bottom: 1px solid var(--line);
@@ -358,12 +358,16 @@ li small {
358
358
 
359
359
  .content-meta-section ul,
360
360
  .content-meta-section .tags {
361
- max-height: 220px;
361
+ max-height: 280px;
362
362
  overflow: auto;
363
363
  align-content: flex-start;
364
364
  padding-right: 4px;
365
365
  }
366
366
 
367
+ .content-meta-section:last-child {
368
+ grid-column: span 2;
369
+ }
370
+
367
371
  .content-dialog .note-content {
368
372
  max-height: none;
369
373
  min-height: 0;
@@ -421,10 +425,10 @@ li small {
421
425
  top: auto;
422
426
  right: 12px;
423
427
  left: 12px;
424
- bottom: 38px;
428
+ bottom: 28px;
425
429
  width: auto;
426
- height: min(calc(100svh - 170px), 640px);
427
- max-height: calc(100svh - 170px);
430
+ height: min(calc(100svh - 150px), 760px);
431
+ max-height: calc(100svh - 150px);
428
432
  }
429
433
 
430
434
  .metric-chip {
@@ -59,8 +59,10 @@ const state = {
59
59
  searchToken: 0,
60
60
  fetchToken: 0,
61
61
  fetchTimer: null,
62
+ fetchAbortController: null,
62
63
  cameraSyncScheduled: false,
63
64
  lastDragFetchAt: 0,
65
+ lastWheelAt: 0,
64
66
  lastVisibleNodes: 0,
65
67
  lastVisibleEdges: 0,
66
68
  totals: {
@@ -303,7 +305,7 @@ const extractContextLinks = (content) => {
303
305
  const lines = content.split(/\\r?\\n/)
304
306
  let start = -1
305
307
  for (let index = 0; index < lines.length; index += 1) {
306
- if (/^##\\s+context\\s+links\\b/i.test(lines[index].trim())) {
308
+ if (/^#{1,6}\\s+(?:context\\s+links?|links?\\s+de\\s+contexto)\\b/i.test(lines[index].trim())) {
307
309
  start = index + 1
308
310
  break
309
311
  }
@@ -313,6 +315,7 @@ const extractContextLinks = (content) => {
313
315
  }
314
316
 
315
317
  const links = []
318
+ const seenTitles = new Set()
316
319
  for (let index = start; index < lines.length; index += 1) {
317
320
  const line = lines[index].trim()
318
321
  if (!line) {
@@ -321,17 +324,21 @@ const extractContextLinks = (content) => {
321
324
  if (/^#{1,6}\\s+/.test(line)) {
322
325
  break
323
326
  }
324
- const match = line.match(/\\[\\[([^\\]]+)\\]\\]/)
325
- if (!match) {
326
- continue
327
- }
328
- const title = match[1].trim()
329
- if (!title) {
327
+ const matches = Array.from(line.matchAll(/\\[\\[([^\\]]+)\\]\\]/g))
328
+ if (matches.length === 0) {
330
329
  continue
331
330
  }
332
331
  const priorityMatch = line.match(/#(critical|important)\\b|priority:\\s*(high|critical)/i)
333
332
  const priority = priorityMatch ? String(priorityMatch[1] || priorityMatch[2] || 'normal').toLowerCase() : 'normal'
334
- links.push({ title, priority })
333
+
334
+ for (let matchIndex = 0; matchIndex < matches.length; matchIndex += 1) {
335
+ const title = String(matches[matchIndex][1] || '').trim()
336
+ if (!title || seenTitles.has(title.toLowerCase())) {
337
+ continue
338
+ }
339
+ seenTitles.add(title.toLowerCase())
340
+ links.push({ title, priority })
341
+ }
335
342
  }
336
343
  return links
337
344
  }
@@ -477,6 +484,11 @@ const fitFromChunk = () => {
477
484
 
478
485
  const fetchChunk = async ({ fit } = { fit: false }) => {
479
486
  const token = ++state.fetchToken
487
+ if (state.fetchAbortController) {
488
+ state.fetchAbortController.abort()
489
+ }
490
+ const controller = new AbortController()
491
+ state.fetchAbortController = controller
480
492
  const worldTopLeft = screenToWorld(0, 0)
481
493
  const worldBottomRight = screenToWorld(state.viewport.width, state.viewport.height)
482
494
  const x = Math.min(worldTopLeft.x, worldBottomRight.x)
@@ -498,12 +510,15 @@ const fetchChunk = async ({ fit } = { fit: false }) => {
498
510
  params.set('agent', state.agentId)
499
511
  }
500
512
 
501
- const response = await fetch('/api/graph-stream?' + params.toString())
513
+ const response = await fetch('/api/graph-stream?' + params.toString(), { signal: controller.signal })
502
514
  if (!response.ok) {
503
515
  throw new Error('Failed to fetch graph stream chunk')
504
516
  }
505
517
 
506
518
  const chunk = await response.json()
519
+ if (controller.signal.aborted) {
520
+ return
521
+ }
507
522
  if (token !== state.fetchToken) {
508
523
  return
509
524
  }
@@ -539,10 +554,15 @@ const scheduleChunkFetch = ({ fit } = { fit: false }) => {
539
554
  clearTimeout(state.fetchTimer)
540
555
  }
541
556
 
542
- const delay = fit ? 0 : (state.pointer.down ? 80 : 32)
557
+ const now = performance.now()
558
+ const recentlyWheeling = now - state.lastWheelAt < 180
559
+ const delay = fit ? 0 : (state.pointer.down ? 120 : (recentlyWheeling ? 140 : 48))
543
560
  state.fetchTimer = setTimeout(() => {
544
561
  state.fetchTimer = null
545
562
  fetchChunk({ fit }).catch((error) => {
563
+ if (error && error.name === 'AbortError') {
564
+ return
565
+ }
546
566
  console.error(error)
547
567
  })
548
568
  }, delay)
@@ -630,6 +650,7 @@ const setupInput = () => {
630
650
 
631
651
  canvas.addEventListener('wheel', (event) => {
632
652
  event.preventDefault()
653
+ state.lastWheelAt = performance.now()
633
654
  const pointer = resolvePointer(event)
634
655
  const exponent = Math.max(-0.05, Math.min(0.05, -event.deltaY * 0.001))
635
656
  zoomAtPoint(pointer.x, pointer.y, Math.exp(exponent))
@@ -680,13 +701,6 @@ const setupInput = () => {
680
701
  return
681
702
  }
682
703
 
683
- if (state.renderWorker && state.workerReady) {
684
- state.renderWorker.postMessage({
685
- type: 'pointer',
686
- x: pointer.x,
687
- y: pointer.y
688
- })
689
- }
690
704
  })
691
705
 
692
706
  canvas.addEventListener('pointerup', (event) => {
@@ -55,6 +55,7 @@ const createLayoutCache = (signature, nodes, edges, groups) => {
55
55
  const groupById = new Map(groups.map((group) => [group.id, group]));
56
56
  const degrees = new Map();
57
57
  const adjacencyByNodeId = new Map();
58
+ const rankedAdjacencyByNodeId = new Map();
58
59
  for (let index = 0; index < edges.length; index += 1) {
59
60
  const edge = edges[index];
60
61
  degrees.set(edge.source, (degrees.get(edge.source) ?? 0) + edge.weight);
@@ -69,12 +70,16 @@ const createLayoutCache = (signature, nodes, edges, groups) => {
69
70
  targetAdjacency.push(index);
70
71
  adjacencyByNodeId.set(edge.target, targetAdjacency);
71
72
  }
73
+ for (const [nodeId, adjacency] of adjacencyByNodeId.entries()) {
74
+ rankedAdjacencyByNodeId.set(nodeId, [...adjacency].sort((leftIndex, rightIndex) => edgeRank(edges[rightIndex]) - edgeRank(edges[leftIndex])));
75
+ }
72
76
  return {
73
77
  signature,
74
78
  degrees,
75
79
  nodeById,
76
80
  groupById,
77
- adjacencyByNodeId
81
+ adjacencyByNodeId,
82
+ rankedAdjacencyByNodeId
78
83
  };
79
84
  };
80
85
  const getOrCreateLayoutCache = (signature, nodes, edges, groups) => {
@@ -198,17 +203,16 @@ const collectEdgesForNodes = (allEdges, cache, nodeRows, edgeBudget, maxEdgesPer
198
203
  const nodeIds = new Set(nodeRows.map((row) => row[0]));
199
204
  const collected = new Map();
200
205
  for (const nodeId of nodeIds) {
201
- const adjacency = cache.adjacencyByNodeId.get(nodeId);
206
+ const adjacency = cache.rankedAdjacencyByNodeId.get(nodeId);
202
207
  if (!adjacency || adjacency.length === 0) {
203
208
  continue;
204
209
  }
205
210
  let perNodeCount = 0;
206
- const rankedAdjacency = [...adjacency].sort((leftIndex, rightIndex) => edgeRank(allEdges[rightIndex]) - edgeRank(allEdges[leftIndex]));
207
- for (let index = 0; index < rankedAdjacency.length; index += 1) {
211
+ for (let index = 0; index < adjacency.length; index += 1) {
208
212
  if (perNodeCount >= maxEdgesPerNode) {
209
213
  break;
210
214
  }
211
- const edge = allEdges[rankedAdjacency[index]];
215
+ const edge = allEdges[adjacency[index]];
212
216
  if (!edge.target || !nodeIds.has(edge.source) || !nodeIds.has(edge.target)) {
213
217
  continue;
214
218
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@andespindola/brainlink",
3
- "version": "0.1.0-beta.147",
3
+ "version": "0.1.0-beta.149",
4
4
  "description": "Local-first knowledge memory for agents with Markdown, backlinks, indexing and context retrieval.",
5
5
  "type": "module",
6
6
  "license": "MIT",