@andespindola/brainlink 0.1.0-beta.2 → 0.1.0-beta.20
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/AGENTS.md +5 -5
- package/CHANGELOG.md +50 -2
- package/CONTRIBUTING.md +2 -2
- package/README.md +157 -20
- package/SECURITY.md +1 -1
- package/dist/application/add-note.js +62 -13
- package/dist/application/analyze-vault.js +95 -8
- package/dist/application/frontend/client-css.js +190 -99
- package/dist/application/frontend/client-html.js +57 -45
- package/dist/application/frontend/client-js.js +416 -85
- package/dist/application/get-graph-layout.js +22 -7
- package/dist/application/get-graph-node.js +12 -0
- package/dist/application/get-graph-summary.js +12 -0
- package/dist/application/get-graph.js +3 -3
- package/dist/application/index-vault.js +11 -4
- package/dist/application/list-agents.js +3 -3
- package/dist/application/list-links.js +5 -5
- package/dist/application/migrate-vault.js +91 -0
- package/dist/application/search-graph-node-ids.js +12 -0
- package/dist/application/search-knowledge.js +75 -5
- package/dist/application/server/routes.js +27 -1
- package/dist/benchmarks/large-vault.js +1 -1
- package/dist/cli/commands/agent-commands.js +412 -0
- package/dist/cli/commands/config-commands.js +167 -0
- package/dist/cli/commands/read-commands.js +25 -8
- package/dist/cli/commands/write-commands.js +173 -4
- package/dist/cli/main.js +4 -0
- package/dist/cli/runtime.js +5 -2
- package/dist/domain/context.js +53 -11
- package/dist/domain/embeddings.js +2 -1
- package/dist/domain/graph-layout.js +20 -14
- package/dist/domain/markdown.js +36 -4
- package/dist/domain/middle-out.js +18 -0
- package/dist/infrastructure/config.js +94 -8
- package/dist/infrastructure/file-index.js +294 -0
- package/dist/infrastructure/file-system-vault.js +15 -0
- package/dist/infrastructure/paths.js +9 -1
- package/dist/infrastructure/private-pack-codec.js +73 -0
- package/dist/infrastructure/search-packs.js +348 -0
- package/dist/infrastructure/session-state.js +172 -0
- package/dist/mcp/main.js +11 -3
- package/dist/mcp/server.js +17 -2
- package/dist/mcp/startup.js +35 -0
- package/dist/mcp/tools.js +571 -19
- package/docs/AGENT_USAGE.md +99 -15
- package/docs/ARCHITECTURE.md +37 -26
- package/docs/QUICKSTART.md +104 -0
- package/docs/RELEASE.md +3 -3
- package/package.json +1 -3
- package/dist/infrastructure/sqlite/document-writer.js +0 -51
- package/dist/infrastructure/sqlite/graph-reader.js +0 -120
- package/dist/infrastructure/sqlite/schema.js +0 -111
- package/dist/infrastructure/sqlite/search-reader.js +0 -156
- package/dist/infrastructure/sqlite/types.js +0 -1
- package/dist/infrastructure/sqlite-index.js +0 -25
|
@@ -1,10 +1,89 @@
|
|
|
1
|
+
import { stat } from 'node:fs/promises';
|
|
2
|
+
import { performance } from 'node:perf_hooks';
|
|
1
3
|
import { validateGraph, getBrokenLinks, getOrphanNodes, getVaultStats } from '../domain/graph-analysis.js';
|
|
2
|
-
import { ensureVault, readMarkdownFiles } from '../infrastructure/file-system-vault.js';
|
|
3
|
-
import {
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
4
|
+
import { ensureVault, listVaultFiles, readMarkdownFiles } from '../infrastructure/file-system-vault.js';
|
|
5
|
+
import { resolveAgentRuntimeDefaults } from '../infrastructure/config.js';
|
|
6
|
+
import { getGraphSummary } from './get-graph-summary.js';
|
|
7
|
+
import { buildContextPackage } from './build-context.js';
|
|
8
|
+
import { indexVault } from './index-vault.js';
|
|
9
|
+
import { searchKnowledge } from './search-knowledge.js';
|
|
10
|
+
import { loadBrainlinkConfig } from '../infrastructure/config.js';
|
|
11
|
+
export const getStats = async (vaultPath, agentId) => getVaultStats(await getGraphSummary(vaultPath, agentId));
|
|
12
|
+
export const getBrokenLinksReport = async (vaultPath, agentId) => getBrokenLinks(await getGraphSummary(vaultPath, agentId));
|
|
13
|
+
export const getOrphansReport = async (vaultPath, agentId) => getOrphanNodes(await getGraphSummary(vaultPath, agentId));
|
|
14
|
+
export const validateVault = async (vaultPath, agentId) => validateGraph(await getGraphSummary(vaultPath, agentId));
|
|
15
|
+
const toRatio = (part, total) => total === 0 ? 0 : Number((part / total).toFixed(4));
|
|
16
|
+
export const getExtendedStats = async (vaultPath, agentId) => {
|
|
17
|
+
const absoluteVaultPath = await ensureVault(vaultPath);
|
|
18
|
+
const graph = await getGraphSummary(absoluteVaultPath, agentId);
|
|
19
|
+
const stats = getVaultStats(graph);
|
|
20
|
+
const markdownFiles = await readMarkdownFiles(absoluteVaultPath);
|
|
21
|
+
const allFiles = await listVaultFiles(absoluteVaultPath);
|
|
22
|
+
const totalBytes = (await Promise.all(allFiles.map(async (filePath) => {
|
|
23
|
+
try {
|
|
24
|
+
return (await stat(filePath)).size;
|
|
25
|
+
}
|
|
26
|
+
catch {
|
|
27
|
+
return 0;
|
|
28
|
+
}
|
|
29
|
+
}))).reduce((sum, value) => sum + value, 0);
|
|
30
|
+
const updatedAt = markdownFiles
|
|
31
|
+
.map((file) => file.updatedAt.getTime())
|
|
32
|
+
.filter((time) => Number.isFinite(time))
|
|
33
|
+
.sort((left, right) => left - right);
|
|
34
|
+
const priorities = graph.edges.reduce((state, edge) => ({
|
|
35
|
+
...state,
|
|
36
|
+
[edge.priority]: state[edge.priority] + 1
|
|
37
|
+
}), {
|
|
38
|
+
low: 0,
|
|
39
|
+
normal: 0,
|
|
40
|
+
high: 0,
|
|
41
|
+
critical: 0
|
|
42
|
+
});
|
|
43
|
+
const config = await loadBrainlinkConfig();
|
|
44
|
+
const defaults = resolveAgentRuntimeDefaults(config, agentId);
|
|
45
|
+
const probeQuery = graph.nodes[0]?.title ?? 'architecture';
|
|
46
|
+
const indexStart = performance.now();
|
|
47
|
+
await indexVault(absoluteVaultPath);
|
|
48
|
+
const indexLatency = performance.now() - indexStart;
|
|
49
|
+
const searchStart = performance.now();
|
|
50
|
+
await searchKnowledge(absoluteVaultPath, probeQuery, Math.min(defaults.defaultSearchLimit, 8), agentId, 'hybrid');
|
|
51
|
+
const searchLatency = performance.now() - searchStart;
|
|
52
|
+
const contextStart = performance.now();
|
|
53
|
+
await buildContextPackage(absoluteVaultPath, probeQuery, Math.min(defaults.defaultSearchLimit, 8), defaults.defaultContextTokens, agentId, 'hybrid');
|
|
54
|
+
const contextLatency = performance.now() - contextStart;
|
|
55
|
+
return {
|
|
56
|
+
stats,
|
|
57
|
+
storage: {
|
|
58
|
+
markdownFileCount: markdownFiles.length,
|
|
59
|
+
totalFileCount: allFiles.length,
|
|
60
|
+
totalBytes,
|
|
61
|
+
averageMarkdownBytes: markdownFiles.length === 0
|
|
62
|
+
? 0
|
|
63
|
+
: Math.round(markdownFiles.reduce((sum, file) => sum + Buffer.byteLength(file.content, 'utf8'), 0) / markdownFiles.length),
|
|
64
|
+
...(updatedAt.length > 0
|
|
65
|
+
? {
|
|
66
|
+
oldestNoteUpdatedAt: new Date(updatedAt[0]).toISOString(),
|
|
67
|
+
newestNoteUpdatedAt: new Date(updatedAt[updatedAt.length - 1]).toISOString()
|
|
68
|
+
}
|
|
69
|
+
: {})
|
|
70
|
+
},
|
|
71
|
+
quality: {
|
|
72
|
+
resolvedLinkRatio: toRatio(stats.resolvedLinkCount, stats.linkCount),
|
|
73
|
+
brokenLinkRatio: toRatio(stats.brokenLinkCount, stats.linkCount),
|
|
74
|
+
orphanRatio: toRatio(stats.orphanCount, Math.max(stats.documentCount, 1)),
|
|
75
|
+
priorityDistribution: priorities
|
|
76
|
+
},
|
|
77
|
+
observability: {
|
|
78
|
+
probeQuery,
|
|
79
|
+
latenciesMs: {
|
|
80
|
+
index: Number(indexLatency.toFixed(2)),
|
|
81
|
+
search: Number(searchLatency.toFixed(2)),
|
|
82
|
+
context: Number(contextLatency.toFixed(2))
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
};
|
|
86
|
+
};
|
|
8
87
|
const createCheck = (name, ok, message) => ({
|
|
9
88
|
name,
|
|
10
89
|
ok,
|
|
@@ -13,7 +92,7 @@ const createCheck = (name, ok, message) => ({
|
|
|
13
92
|
export const doctorVault = async (vaultPath) => {
|
|
14
93
|
const absoluteVaultPath = await ensureVault(vaultPath);
|
|
15
94
|
const files = await readMarkdownFiles(absoluteVaultPath);
|
|
16
|
-
const graph = await
|
|
95
|
+
const graph = await getGraphSummary(absoluteVaultPath);
|
|
17
96
|
const validation = validateGraph(graph);
|
|
18
97
|
const checks = [
|
|
19
98
|
createCheck('vault', true, `Vault ready at ${absoluteVaultPath}`),
|
|
@@ -21,8 +100,16 @@ export const doctorVault = async (vaultPath) => {
|
|
|
21
100
|
createCheck('index', graph.nodes.length > 0, `${graph.nodes.length} indexed documents found`),
|
|
22
101
|
createCheck('broken-links', validation.brokenLinks.length === 0, `${validation.brokenLinks.length} broken links found`)
|
|
23
102
|
];
|
|
103
|
+
const recommendations = files.length === 0 && graph.nodes.length === 0
|
|
104
|
+
? [
|
|
105
|
+
`Vault is empty. Add your first note: blink add "Architecture" --vault "${absoluteVaultPath}" --content "Markdown source of truth. #architecture"`,
|
|
106
|
+
`If this path is not the expected vault, inspect active config: blink config where`,
|
|
107
|
+
`If you changed vault recently, migrate existing memory: blink migrate-vault --from ~/.brainlink/vault --to "${absoluteVaultPath}"`
|
|
108
|
+
]
|
|
109
|
+
: [];
|
|
24
110
|
return {
|
|
25
111
|
ok: checks.every((check) => check.ok),
|
|
26
|
-
checks
|
|
112
|
+
checks,
|
|
113
|
+
...(recommendations.length > 0 ? { recommendations } : {})
|
|
27
114
|
};
|
|
28
115
|
};
|
|
@@ -32,19 +32,43 @@ select {
|
|
|
32
32
|
}
|
|
33
33
|
|
|
34
34
|
.shell {
|
|
35
|
-
display: grid;
|
|
36
|
-
grid-template-columns: minmax(0, 1fr) 360px;
|
|
37
35
|
width: 100%;
|
|
38
36
|
height: 100svh;
|
|
39
37
|
overflow: hidden;
|
|
40
38
|
}
|
|
41
39
|
|
|
42
40
|
.workspace {
|
|
41
|
+
display: grid;
|
|
42
|
+
grid-template-rows: auto minmax(0, 1fr);
|
|
43
43
|
position: relative;
|
|
44
|
+
width: 100%;
|
|
45
|
+
height: 100%;
|
|
44
46
|
min-width: 0;
|
|
45
47
|
min-height: 0;
|
|
46
48
|
}
|
|
47
49
|
|
|
50
|
+
.graph-header {
|
|
51
|
+
z-index: 5;
|
|
52
|
+
display: flex;
|
|
53
|
+
align-items: center;
|
|
54
|
+
gap: 12px;
|
|
55
|
+
min-height: 72px;
|
|
56
|
+
padding: 10px 16px;
|
|
57
|
+
border-bottom: 1px solid var(--line);
|
|
58
|
+
background: linear-gradient(180deg, rgba(17, 21, 27, 0.96) 0%, rgba(17, 21, 27, 0.86) 100%);
|
|
59
|
+
backdrop-filter: blur(8px);
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
.brand-block {
|
|
63
|
+
display: grid;
|
|
64
|
+
gap: 2px;
|
|
65
|
+
min-width: max-content;
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
.brand-block strong {
|
|
69
|
+
font-size: 18px;
|
|
70
|
+
}
|
|
71
|
+
|
|
48
72
|
#graph {
|
|
49
73
|
display: block;
|
|
50
74
|
width: 100%;
|
|
@@ -59,43 +83,25 @@ select {
|
|
|
59
83
|
cursor: grabbing;
|
|
60
84
|
}
|
|
61
85
|
|
|
62
|
-
.
|
|
63
|
-
position: absolute;
|
|
64
|
-
top: 18px;
|
|
65
|
-
left: 18px;
|
|
66
|
-
right: 18px;
|
|
67
|
-
display: flex;
|
|
68
|
-
align-items: center;
|
|
69
|
-
justify-content: space-between;
|
|
70
|
-
gap: 18px;
|
|
71
|
-
pointer-events: none;
|
|
72
|
-
}
|
|
73
|
-
|
|
74
|
-
.topbar > div {
|
|
75
|
-
display: flex;
|
|
76
|
-
align-items: baseline;
|
|
77
|
-
gap: 12px;
|
|
78
|
-
}
|
|
79
|
-
|
|
80
|
-
.topbar strong {
|
|
81
|
-
font-size: 18px;
|
|
82
|
-
}
|
|
83
|
-
|
|
84
|
-
.topbar span,
|
|
85
|
-
.eyebrow,
|
|
86
|
-
.inspector small {
|
|
86
|
+
.eyebrow {
|
|
87
87
|
color: var(--muted);
|
|
88
88
|
font-size: 12px;
|
|
89
89
|
}
|
|
90
90
|
|
|
91
91
|
.search {
|
|
92
|
-
|
|
93
|
-
|
|
92
|
+
flex: 1 1 320px;
|
|
93
|
+
min-width: 220px;
|
|
94
94
|
}
|
|
95
95
|
|
|
96
96
|
.agent-filter {
|
|
97
97
|
width: min(220px, 28vw);
|
|
98
|
-
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
.header-actions {
|
|
101
|
+
display: flex;
|
|
102
|
+
align-items: center;
|
|
103
|
+
gap: 10px;
|
|
104
|
+
margin-left: auto;
|
|
99
105
|
}
|
|
100
106
|
|
|
101
107
|
.search input,
|
|
@@ -116,9 +122,6 @@ select {
|
|
|
116
122
|
}
|
|
117
123
|
|
|
118
124
|
.toolbar {
|
|
119
|
-
position: absolute;
|
|
120
|
-
left: 18px;
|
|
121
|
-
bottom: 18px;
|
|
122
125
|
display: flex;
|
|
123
126
|
gap: 8px;
|
|
124
127
|
}
|
|
@@ -138,70 +141,34 @@ select {
|
|
|
138
141
|
color: var(--accent);
|
|
139
142
|
}
|
|
140
143
|
|
|
141
|
-
.
|
|
142
|
-
display:
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
min-width: 0;
|
|
146
|
-
height: 100%;
|
|
147
|
-
padding: 24px;
|
|
148
|
-
border-left: 1px solid var(--line);
|
|
149
|
-
background: var(--panel);
|
|
150
|
-
overflow: auto;
|
|
144
|
+
.floating-metrics {
|
|
145
|
+
display: flex;
|
|
146
|
+
gap: 10px;
|
|
147
|
+
flex-wrap: wrap;
|
|
151
148
|
}
|
|
152
149
|
|
|
153
|
-
.
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
150
|
+
.metric-chip {
|
|
151
|
+
min-width: 94px;
|
|
152
|
+
padding: 10px 12px;
|
|
153
|
+
border: 1px solid var(--line);
|
|
154
|
+
border-radius: 10px;
|
|
155
|
+
background: rgba(21, 25, 31, 0.88);
|
|
156
|
+
display: grid;
|
|
157
|
+
gap: 3px;
|
|
157
158
|
}
|
|
158
159
|
|
|
159
|
-
.
|
|
160
|
-
margin-top: 6px;
|
|
160
|
+
.metric-chip strong {
|
|
161
161
|
font-size: 26px;
|
|
162
|
-
line-height: 1
|
|
163
|
-
overflow-wrap: anywhere;
|
|
162
|
+
line-height: 1;
|
|
164
163
|
}
|
|
165
164
|
|
|
166
|
-
.
|
|
167
|
-
margin-bottom: 10px;
|
|
165
|
+
.metric-chip small {
|
|
168
166
|
color: var(--muted);
|
|
169
|
-
font-size:
|
|
170
|
-
|
|
167
|
+
font-size: 11px;
|
|
168
|
+
letter-spacing: 0.03em;
|
|
171
169
|
text-transform: uppercase;
|
|
172
170
|
}
|
|
173
171
|
|
|
174
|
-
#path {
|
|
175
|
-
margin-top: 10px;
|
|
176
|
-
color: var(--muted);
|
|
177
|
-
line-height: 1.45;
|
|
178
|
-
overflow-wrap: anywhere;
|
|
179
|
-
}
|
|
180
|
-
|
|
181
|
-
.metrics {
|
|
182
|
-
display: grid;
|
|
183
|
-
grid-template-columns: repeat(3, 1fr);
|
|
184
|
-
border: 1px solid var(--line);
|
|
185
|
-
border-radius: 8px;
|
|
186
|
-
overflow: hidden;
|
|
187
|
-
}
|
|
188
|
-
|
|
189
|
-
.metrics div {
|
|
190
|
-
display: grid;
|
|
191
|
-
gap: 4px;
|
|
192
|
-
padding: 14px;
|
|
193
|
-
background: var(--panel-strong);
|
|
194
|
-
}
|
|
195
|
-
|
|
196
|
-
.metrics div + div {
|
|
197
|
-
border-left: 1px solid var(--line);
|
|
198
|
-
}
|
|
199
|
-
|
|
200
|
-
.metrics span {
|
|
201
|
-
font-size: 22px;
|
|
202
|
-
font-weight: 700;
|
|
203
|
-
}
|
|
204
|
-
|
|
205
172
|
.tags {
|
|
206
173
|
display: flex;
|
|
207
174
|
flex-wrap: wrap;
|
|
@@ -215,6 +182,7 @@ select {
|
|
|
215
182
|
background: var(--accent-weak);
|
|
216
183
|
color: var(--accent);
|
|
217
184
|
font-size: 12px;
|
|
185
|
+
word-break: break-word;
|
|
218
186
|
overflow-wrap: anywhere;
|
|
219
187
|
}
|
|
220
188
|
|
|
@@ -230,6 +198,7 @@ li {
|
|
|
230
198
|
padding: 10px 0;
|
|
231
199
|
border-bottom: 1px solid var(--line);
|
|
232
200
|
color: var(--text);
|
|
201
|
+
word-break: break-word;
|
|
233
202
|
overflow-wrap: anywhere;
|
|
234
203
|
}
|
|
235
204
|
|
|
@@ -253,7 +222,7 @@ li small {
|
|
|
253
222
|
}
|
|
254
223
|
|
|
255
224
|
.note-content {
|
|
256
|
-
max-height:
|
|
225
|
+
max-height: min(68svh, 760px);
|
|
257
226
|
margin: 0;
|
|
258
227
|
padding: 12px;
|
|
259
228
|
border: 1px solid var(--line);
|
|
@@ -267,28 +236,150 @@ li small {
|
|
|
267
236
|
line-height: 1.5;
|
|
268
237
|
}
|
|
269
238
|
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
239
|
+
.content-dialog {
|
|
240
|
+
width: min(920px, calc(100vw - 32px));
|
|
241
|
+
max-height: calc(100svh - 32px);
|
|
242
|
+
padding: 0;
|
|
243
|
+
border: 1px solid var(--line);
|
|
244
|
+
border-radius: 8px;
|
|
245
|
+
background: var(--panel);
|
|
246
|
+
color: var(--text);
|
|
247
|
+
box-shadow: 0 24px 80px rgba(0, 0, 0, 0.48);
|
|
248
|
+
}
|
|
275
249
|
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
250
|
+
.content-dialog::backdrop {
|
|
251
|
+
background: rgba(4, 7, 10, 0.72);
|
|
252
|
+
backdrop-filter: blur(4px);
|
|
253
|
+
}
|
|
254
|
+
|
|
255
|
+
.content-dialog article {
|
|
256
|
+
display: grid;
|
|
257
|
+
grid-template-rows: auto auto minmax(0, 1fr);
|
|
258
|
+
max-height: calc(100svh - 34px);
|
|
259
|
+
}
|
|
260
|
+
|
|
261
|
+
.content-dialog header {
|
|
262
|
+
display: flex;
|
|
263
|
+
align-items: flex-start;
|
|
264
|
+
justify-content: space-between;
|
|
265
|
+
gap: 18px;
|
|
266
|
+
padding: 22px;
|
|
267
|
+
border-bottom: 1px solid var(--line);
|
|
268
|
+
}
|
|
269
|
+
|
|
270
|
+
.content-dialog h2,
|
|
271
|
+
.content-dialog p {
|
|
272
|
+
margin: 0;
|
|
273
|
+
}
|
|
274
|
+
|
|
275
|
+
.content-dialog h2 {
|
|
276
|
+
margin-top: 6px;
|
|
277
|
+
font-size: 24px;
|
|
278
|
+
line-height: 1.15;
|
|
279
|
+
overflow-wrap: anywhere;
|
|
280
|
+
}
|
|
281
281
|
|
|
282
|
-
|
|
282
|
+
.content-dialog p {
|
|
283
|
+
margin-top: 8px;
|
|
284
|
+
color: var(--muted);
|
|
285
|
+
overflow-wrap: anywhere;
|
|
286
|
+
}
|
|
287
|
+
|
|
288
|
+
.content-dialog button {
|
|
289
|
+
flex: 0 0 auto;
|
|
290
|
+
height: 38px;
|
|
291
|
+
padding: 0 14px;
|
|
292
|
+
border: 1px solid var(--line);
|
|
293
|
+
border-radius: 8px;
|
|
294
|
+
background: var(--panel-strong);
|
|
295
|
+
color: var(--text);
|
|
296
|
+
cursor: pointer;
|
|
297
|
+
}
|
|
298
|
+
|
|
299
|
+
.content-dialog button:hover,
|
|
300
|
+
.content-dialog button:focus {
|
|
301
|
+
border-color: var(--accent);
|
|
302
|
+
color: var(--accent);
|
|
303
|
+
}
|
|
304
|
+
|
|
305
|
+
.content-meta {
|
|
306
|
+
display: grid;
|
|
307
|
+
grid-template-columns: repeat(3, minmax(0, 1fr));
|
|
308
|
+
gap: 10px;
|
|
309
|
+
padding: 14px 22px;
|
|
310
|
+
border-bottom: 1px solid var(--line);
|
|
311
|
+
}
|
|
312
|
+
|
|
313
|
+
.content-meta-section {
|
|
314
|
+
min-height: 0;
|
|
315
|
+
padding: 10px;
|
|
316
|
+
border: 1px solid var(--line);
|
|
317
|
+
border-radius: 8px;
|
|
318
|
+
background: var(--panel-strong);
|
|
319
|
+
display: grid;
|
|
320
|
+
grid-template-rows: auto minmax(0, 1fr);
|
|
321
|
+
gap: 8px;
|
|
322
|
+
}
|
|
323
|
+
|
|
324
|
+
.content-meta-section h3 {
|
|
325
|
+
margin: 0;
|
|
326
|
+
color: var(--muted);
|
|
327
|
+
font-size: 11px;
|
|
328
|
+
font-weight: 700;
|
|
329
|
+
text-transform: uppercase;
|
|
330
|
+
}
|
|
331
|
+
|
|
332
|
+
.content-meta-section ul,
|
|
333
|
+
.content-meta-section .tags {
|
|
334
|
+
max-height: 140px;
|
|
335
|
+
overflow: auto;
|
|
336
|
+
align-content: flex-start;
|
|
337
|
+
padding-right: 4px;
|
|
338
|
+
}
|
|
339
|
+
|
|
340
|
+
.content-dialog .note-content {
|
|
341
|
+
max-height: none;
|
|
342
|
+
min-height: 0;
|
|
343
|
+
border: 0;
|
|
344
|
+
border-radius: 0;
|
|
345
|
+
padding: 22px;
|
|
346
|
+
}
|
|
347
|
+
|
|
348
|
+
@media (max-width: 860px) {
|
|
349
|
+
.graph-header {
|
|
283
350
|
align-items: stretch;
|
|
284
|
-
flex-
|
|
351
|
+
flex-wrap: wrap;
|
|
352
|
+
padding: 10px 12px;
|
|
353
|
+
min-height: 0;
|
|
285
354
|
}
|
|
286
355
|
|
|
287
356
|
.search {
|
|
288
357
|
width: 100%;
|
|
358
|
+
flex-basis: 100%;
|
|
359
|
+
order: 3;
|
|
289
360
|
}
|
|
290
361
|
|
|
291
362
|
.agent-filter {
|
|
292
363
|
width: 100%;
|
|
293
364
|
}
|
|
365
|
+
|
|
366
|
+
.header-actions {
|
|
367
|
+
width: 100%;
|
|
368
|
+
margin-left: 0;
|
|
369
|
+
justify-content: space-between;
|
|
370
|
+
order: 4;
|
|
371
|
+
}
|
|
372
|
+
|
|
373
|
+
.content-dialog header {
|
|
374
|
+
align-items: stretch;
|
|
375
|
+
flex-direction: column;
|
|
376
|
+
}
|
|
377
|
+
|
|
378
|
+
.metric-chip {
|
|
379
|
+
min-width: 82px;
|
|
380
|
+
}
|
|
381
|
+
|
|
382
|
+
.content-meta {
|
|
383
|
+
grid-template-columns: 1fr;
|
|
384
|
+
}
|
|
294
385
|
}`;
|
|
@@ -9,58 +9,70 @@ export const createClientHtml = () => `<!doctype html>
|
|
|
9
9
|
<body>
|
|
10
10
|
<main class="shell">
|
|
11
11
|
<section class="workspace" aria-label="Knowledge graph">
|
|
12
|
-
<
|
|
13
|
-
|
|
14
|
-
<div>
|
|
12
|
+
<header class="graph-header" aria-label="Graph actions">
|
|
13
|
+
<div class="brand-block">
|
|
15
14
|
<strong>Brainlink</strong>
|
|
16
|
-
<span
|
|
15
|
+
<span class="eyebrow">Knowledge Graph</span>
|
|
16
|
+
</div>
|
|
17
|
+
<div class="floating-metrics" aria-label="Graph totals">
|
|
18
|
+
<div class="metric-chip">
|
|
19
|
+
<strong id="nodeCount">0</strong>
|
|
20
|
+
<small>Notes</small>
|
|
21
|
+
</div>
|
|
22
|
+
<div class="metric-chip">
|
|
23
|
+
<strong id="edgeCount">0</strong>
|
|
24
|
+
<small>Links</small>
|
|
25
|
+
</div>
|
|
26
|
+
<div class="metric-chip">
|
|
27
|
+
<strong id="tagCount">0</strong>
|
|
28
|
+
<small>Tags</small>
|
|
29
|
+
</div>
|
|
17
30
|
</div>
|
|
18
31
|
<label class="search">
|
|
19
32
|
<input id="search" type="search" placeholder="Filter notes, tags or paths" autocomplete="off" />
|
|
20
33
|
</label>
|
|
21
|
-
<
|
|
22
|
-
<
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
34
|
+
<div class="header-actions">
|
|
35
|
+
<label class="agent-filter">
|
|
36
|
+
<select id="agent"></select>
|
|
37
|
+
</label>
|
|
38
|
+
<div class="toolbar" aria-label="Graph controls">
|
|
39
|
+
<button id="zoomIn" type="button" title="Zoom in">+</button>
|
|
40
|
+
<button id="zoomOut" type="button" title="Zoom out">-</button>
|
|
41
|
+
<button id="fit" type="button" title="Fit visible nodes">◎</button>
|
|
42
|
+
<button id="reset" type="button" title="Reset view">⌂</button>
|
|
43
|
+
</div>
|
|
44
|
+
</div>
|
|
45
|
+
</header>
|
|
46
|
+
<canvas id="graph" aria-label="Brainlink knowledge graph"></canvas>
|
|
30
47
|
</section>
|
|
31
|
-
<aside class="inspector" aria-label="Selected note">
|
|
32
|
-
<div>
|
|
33
|
-
<span class="eyebrow">Selected note</span>
|
|
34
|
-
<h1 id="title">Graph Overview</h1>
|
|
35
|
-
<p id="path">Select a node to inspect links and backlinks.</p>
|
|
36
|
-
</div>
|
|
37
|
-
<div class="metrics">
|
|
38
|
-
<div><span id="nodeCount">0</span><small>Notes</small></div>
|
|
39
|
-
<div><span id="edgeCount">0</span><small>Links</small></div>
|
|
40
|
-
<div><span id="tagCount">0</span><small>Tags</small></div>
|
|
41
|
-
</div>
|
|
42
|
-
<section>
|
|
43
|
-
<h2>Tags</h2>
|
|
44
|
-
<div id="tags" class="tags"></div>
|
|
45
|
-
</section>
|
|
46
|
-
<section>
|
|
47
|
-
<h2>Notes</h2>
|
|
48
|
-
<ul id="notes"></ul>
|
|
49
|
-
</section>
|
|
50
|
-
<section>
|
|
51
|
-
<h2>Content</h2>
|
|
52
|
-
<pre id="content" class="note-content"></pre>
|
|
53
|
-
</section>
|
|
54
|
-
<section>
|
|
55
|
-
<h2>Outgoing</h2>
|
|
56
|
-
<ul id="outgoing"></ul>
|
|
57
|
-
</section>
|
|
58
|
-
<section>
|
|
59
|
-
<h2>Backlinks</h2>
|
|
60
|
-
<ul id="incoming"></ul>
|
|
61
|
-
</section>
|
|
62
|
-
</aside>
|
|
63
48
|
</main>
|
|
49
|
+
<dialog id="contentDialog" class="content-dialog" aria-labelledby="contentTitle">
|
|
50
|
+
<article>
|
|
51
|
+
<header>
|
|
52
|
+
<div>
|
|
53
|
+
<span class="eyebrow">Markdown content</span>
|
|
54
|
+
<h2 id="contentTitle">Selected note</h2>
|
|
55
|
+
<p id="contentPath"></p>
|
|
56
|
+
</div>
|
|
57
|
+
<button id="contentClose" type="button">Close</button>
|
|
58
|
+
</header>
|
|
59
|
+
<div class="content-meta">
|
|
60
|
+
<section class="content-meta-section">
|
|
61
|
+
<h3>Tags</h3>
|
|
62
|
+
<div id="contentTags" class="tags"></div>
|
|
63
|
+
</section>
|
|
64
|
+
<section class="content-meta-section">
|
|
65
|
+
<h3>Outgoing</h3>
|
|
66
|
+
<ul id="contentOutgoing"></ul>
|
|
67
|
+
</section>
|
|
68
|
+
<section class="content-meta-section">
|
|
69
|
+
<h3>Backlinks</h3>
|
|
70
|
+
<ul id="contentIncoming"></ul>
|
|
71
|
+
</section>
|
|
72
|
+
</div>
|
|
73
|
+
<pre id="contentBody" class="note-content"></pre>
|
|
74
|
+
</article>
|
|
75
|
+
</dialog>
|
|
64
76
|
<script src="/app.js"></script>
|
|
65
77
|
</body>
|
|
66
78
|
</html>`;
|