@andespindola/brainlink 0.1.0-beta.5 → 0.1.0-beta.7

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.
@@ -140,7 +140,7 @@ select {
140
140
 
141
141
  .inspector {
142
142
  display: grid;
143
- grid-template-rows: auto auto auto auto auto 1fr 1fr;
143
+ grid-template-rows: auto auto auto minmax(0, 1fr) minmax(0, 1fr) minmax(0, 1fr);
144
144
  gap: 22px;
145
145
  min-width: 0;
146
146
  height: 100%;
@@ -253,7 +253,7 @@ li small {
253
253
  }
254
254
 
255
255
  .note-content {
256
- max-height: 32svh;
256
+ max-height: min(68svh, 760px);
257
257
  margin: 0;
258
258
  padding: 12px;
259
259
  border: 1px solid var(--line);
@@ -267,6 +267,80 @@ li small {
267
267
  line-height: 1.5;
268
268
  }
269
269
 
270
+ .content-dialog {
271
+ width: min(920px, calc(100vw - 32px));
272
+ max-height: calc(100svh - 32px);
273
+ padding: 0;
274
+ border: 1px solid var(--line);
275
+ border-radius: 8px;
276
+ background: var(--panel);
277
+ color: var(--text);
278
+ box-shadow: 0 24px 80px rgba(0, 0, 0, 0.48);
279
+ }
280
+
281
+ .content-dialog::backdrop {
282
+ background: rgba(4, 7, 10, 0.72);
283
+ backdrop-filter: blur(4px);
284
+ }
285
+
286
+ .content-dialog article {
287
+ display: grid;
288
+ grid-template-rows: auto minmax(0, 1fr);
289
+ max-height: calc(100svh - 34px);
290
+ }
291
+
292
+ .content-dialog header {
293
+ display: flex;
294
+ align-items: flex-start;
295
+ justify-content: space-between;
296
+ gap: 18px;
297
+ padding: 22px;
298
+ border-bottom: 1px solid var(--line);
299
+ }
300
+
301
+ .content-dialog h2,
302
+ .content-dialog p {
303
+ margin: 0;
304
+ }
305
+
306
+ .content-dialog h2 {
307
+ margin-top: 6px;
308
+ font-size: 24px;
309
+ line-height: 1.15;
310
+ overflow-wrap: anywhere;
311
+ }
312
+
313
+ .content-dialog p {
314
+ margin-top: 8px;
315
+ color: var(--muted);
316
+ overflow-wrap: anywhere;
317
+ }
318
+
319
+ .content-dialog button {
320
+ flex: 0 0 auto;
321
+ height: 38px;
322
+ padding: 0 14px;
323
+ border: 1px solid var(--line);
324
+ border-radius: 8px;
325
+ background: var(--panel-strong);
326
+ color: var(--text);
327
+ cursor: pointer;
328
+ }
329
+
330
+ .content-dialog button:hover,
331
+ .content-dialog button:focus {
332
+ border-color: var(--accent);
333
+ color: var(--accent);
334
+ }
335
+
336
+ .content-dialog .note-content {
337
+ max-height: none;
338
+ min-height: 0;
339
+ border: 0;
340
+ border-radius: 0;
341
+ padding: 22px;
342
+ }
343
+
270
344
  @media (max-width: 860px) {
271
345
  .shell {
272
346
  grid-template-columns: 1fr;
@@ -291,4 +365,9 @@ li small {
291
365
  .agent-filter {
292
366
  width: 100%;
293
367
  }
368
+
369
+ .content-dialog header {
370
+ align-items: stretch;
371
+ flex-direction: column;
372
+ }
294
373
  }`;
@@ -47,10 +47,6 @@ export const createClientHtml = () => `<!doctype html>
47
47
  <h2>Notes</h2>
48
48
  <ul id="notes"></ul>
49
49
  </section>
50
- <section>
51
- <h2>Content</h2>
52
- <pre id="content" class="note-content"></pre>
53
- </section>
54
50
  <section>
55
51
  <h2>Outgoing</h2>
56
52
  <ul id="outgoing"></ul>
@@ -61,6 +57,19 @@ export const createClientHtml = () => `<!doctype html>
61
57
  </section>
62
58
  </aside>
63
59
  </main>
60
+ <dialog id="contentDialog" class="content-dialog" aria-labelledby="contentTitle">
61
+ <article>
62
+ <header>
63
+ <div>
64
+ <span class="eyebrow">Markdown content</span>
65
+ <h2 id="contentTitle">Selected note</h2>
66
+ <p id="contentPath"></p>
67
+ </div>
68
+ <button id="contentClose" type="button">Close</button>
69
+ </header>
70
+ <pre id="contentBody" class="note-content"></pre>
71
+ </article>
72
+ </dialog>
64
73
  <script src="/app.js"></script>
65
74
  </body>
66
75
  </html>`;
@@ -12,6 +12,7 @@ const state = {
12
12
  transform: { x: 0, y: 0, scale: 1 },
13
13
  pointer: { x: 0, y: 0, down: false, dragNode: null, moved: false },
14
14
  graphSignature: '',
15
+ graphStatus: '',
15
16
  last: performance.now()
16
17
  }
17
18
 
@@ -30,7 +31,6 @@ const elements = {
30
31
  path: byId('path'),
31
32
  tags: byId('tags'),
32
33
  notes: byId('notes'),
33
- content: byId('content'),
34
34
  outgoing: byId('outgoing'),
35
35
  incoming: byId('incoming'),
36
36
  nodeCount: byId('nodeCount'),
@@ -38,11 +38,32 @@ const elements = {
38
38
  tagCount: byId('tagCount'),
39
39
  zoomIn: byId('zoomIn'),
40
40
  zoomOut: byId('zoomOut'),
41
- reset: byId('reset')
41
+ reset: byId('reset'),
42
+ contentDialog: byId('contentDialog'),
43
+ contentTitle: byId('contentTitle'),
44
+ contentPath: byId('contentPath'),
45
+ contentBody: byId('contentBody'),
46
+ contentClose: byId('contentClose')
42
47
  }
43
48
 
44
49
  const agentQuery = () => state.agentId ? '?agent=' + encodeURIComponent(state.agentId) : ''
45
50
 
51
+ const setGraphStatus = text => {
52
+ state.graphStatus = text
53
+ elements.stats.textContent = text
54
+ }
55
+
56
+ const handleGraphRefreshError = error => {
57
+ if (state.graphSignature) {
58
+ elements.stats.textContent = state.graphStatus
59
+ console.error(error)
60
+ return
61
+ }
62
+
63
+ elements.stats.textContent = 'Failed to load graph'
64
+ console.error(error)
65
+ }
66
+
46
67
  const graphTheme = {
47
68
  node: '#aeb8c5',
48
69
  nodeSelected: '#f3f7fb',
@@ -111,7 +132,7 @@ const encodeEntityTag = (value) => {
111
132
  binary += String.fromCharCode(utf8[index])
112
133
  }
113
134
 
114
- return btoa(binary).replace(/\+/g, '-').replace(/\//g, '_').replace(/=+$/g, '')
135
+ return btoa(binary).replaceAll('+', '-').replaceAll('/', '_').replace(/=+$/g, '')
115
136
  }
116
137
 
117
138
  const graphSignature = graph => JSON.stringify({
@@ -261,14 +282,23 @@ const allNotesList = () => state.nodes.length
261
282
  ? state.nodes.map(node => '<li><button type="button" data-node-id="' + escapeHtml(node.id) + '">' + escapeHtml(node.title) + '</button><small>' + escapeHtml(node.path) + '</small></li>').join('')
262
283
  : '<li><small>No notes indexed.</small></li>'
263
284
 
264
- const selectNode = node => {
285
+ const openContentDialog = node => {
286
+ if (!node) return
287
+ elements.contentTitle.textContent = node.title
288
+ elements.contentPath.textContent = node.path
289
+ elements.contentBody.textContent = node.content
290
+ if (!elements.contentDialog.open) {
291
+ elements.contentDialog.showModal()
292
+ }
293
+ }
294
+
295
+ const selectNode = (node, options = { openContent: false }) => {
265
296
  state.selected = node
266
297
  if (!node) {
267
298
  elements.title.textContent = 'Graph Overview'
268
299
  elements.path.textContent = state.nodes.length + ' notes and ' + state.graph.edges.length + ' links indexed.'
269
300
  elements.tags.innerHTML = ''
270
301
  elements.notes.innerHTML = allNotesList()
271
- elements.content.textContent = 'Selecione uma nota no grafo ou na lista para ver o Markdown completo, backlinks e links de saida.'
272
302
  elements.outgoing.innerHTML = '<li><small>Select a note to inspect outgoing links.</small></li>'
273
303
  elements.incoming.innerHTML = '<li><small>Select a note to inspect backlinks.</small></li>'
274
304
  return
@@ -294,14 +324,14 @@ const selectNode = node => {
294
324
  ? node.tags.map(tag => '<span>#' + escapeHtml(tag) + '</span>').join('')
295
325
  : '<span>No tags</span>'
296
326
  elements.notes.innerHTML = allNotesList()
297
- elements.content.textContent = node.content
298
327
  elements.outgoing.innerHTML = list(outgoing)
299
328
  elements.incoming.innerHTML = list(incoming)
329
+ if (options.openContent) openContentDialog(node)
300
330
  }
301
331
 
302
332
  const selectNodeById = id => {
303
333
  const node = state.nodes.find(item => item.id === id)
304
- if (node) selectNode(node)
334
+ if (node) selectNode(node, { openContent: true })
305
335
  }
306
336
 
307
337
  const zoom = factor => {
@@ -327,6 +357,10 @@ const bindEvents = () => {
327
357
  elements.zoomIn.addEventListener('click', () => zoom(1.18))
328
358
  elements.zoomOut.addEventListener('click', () => zoom(0.84))
329
359
  elements.reset.addEventListener('click', resetView)
360
+ elements.contentClose.addEventListener('click', () => elements.contentDialog.close())
361
+ elements.contentDialog.addEventListener('click', event => {
362
+ if (event.target === elements.contentDialog) elements.contentDialog.close()
363
+ })
330
364
  ;[elements.notes, elements.outgoing, elements.incoming].forEach(element => {
331
365
  element.addEventListener('click', event => {
332
366
  const target = event.target
@@ -367,8 +401,8 @@ const bindEvents = () => {
367
401
  state.transform.y += dy
368
402
  })
369
403
  canvas.addEventListener('pointerup', event => {
370
- if (state.pointer.dragNode && !state.pointer.moved) selectNode(state.pointer.dragNode)
371
- if (!state.pointer.dragNode && !state.pointer.moved) selectNode(state.hovered)
404
+ if (state.pointer.dragNode && !state.pointer.moved) selectNode(state.pointer.dragNode, { openContent: true })
405
+ if (!state.pointer.dragNode && !state.pointer.moved) selectNode(state.hovered, { openContent: true })
372
406
  state.pointer = { x: 0, y: 0, down: false, dragNode: null, moved: false }
373
407
  canvas.releasePointerCapture(event.pointerId)
374
408
  })
@@ -418,7 +452,7 @@ const loadGraph = async (options = { reset: false }) => {
418
452
  state.nodes = layout.nodes
419
453
  state.edges = layout.edges
420
454
  const tags = new Set(graph.nodes.flatMap(node => node.tags))
421
- elements.stats.textContent = state.agentId + ' · ' + graph.nodes.length + ' notes · ' + graph.edges.length + ' links · live'
455
+ setGraphStatus(state.agentId + ' · ' + graph.nodes.length + ' notes · ' + graph.edges.length + ' links · live')
422
456
  elements.nodeCount.textContent = graph.nodes.length
423
457
  elements.edgeCount.textContent = graph.edges.length
424
458
  elements.tagCount.textContent = tags.size
@@ -441,10 +475,7 @@ const refreshGraphLoop = () => {
441
475
  return
442
476
  }
443
477
 
444
- loadGraph().catch((error) => {
445
- elements.stats.textContent = 'Failed to refresh graph'
446
- console.error(error)
447
- })
478
+ loadGraph().catch(handleGraphRefreshError)
448
479
 
449
480
  tickCounter += 1
450
481
  if (tickCounter % 3 === 0) {
@@ -470,9 +501,6 @@ document.addEventListener('visibilitychange', () => {
470
501
  return
471
502
  }
472
503
 
473
- loadGraph({ reset: true }).catch(error => {
474
- elements.stats.textContent = 'Failed to refresh graph'
475
- console.error(error)
476
- })
504
+ loadGraph({ reset: true }).catch(handleGraphRefreshError)
477
505
  })
478
506
  `;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@andespindola/brainlink",
3
- "version": "0.1.0-beta.5",
3
+ "version": "0.1.0-beta.7",
4
4
  "description": "Local-first knowledge memory for agents with Markdown, backlinks, indexing and context retrieval.",
5
5
  "type": "module",
6
6
  "license": "MIT",