clementine-agent 1.18.21 → 1.18.22
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/dist/cli/dashboard.js +1015 -64
- package/package.json +1 -1
package/dist/cli/dashboard.js
CHANGED
|
@@ -288,6 +288,255 @@ async function searchMemory(query, limit = 20, filters = {}) {
|
|
|
288
288
|
db.close();
|
|
289
289
|
}
|
|
290
290
|
}
|
|
291
|
+
function quotedFtsQuery(query) {
|
|
292
|
+
return query
|
|
293
|
+
.split(/\s+/)
|
|
294
|
+
.map((w) => w.replace(/"/g, '').trim())
|
|
295
|
+
.filter((w) => w.length > 0)
|
|
296
|
+
.map((w) => `"${w}"`)
|
|
297
|
+
.join(' OR ');
|
|
298
|
+
}
|
|
299
|
+
function textPreview(raw, query = '', max = 520) {
|
|
300
|
+
const compact = String(raw ?? '').replace(/\s+/g, ' ').trim();
|
|
301
|
+
if (!compact)
|
|
302
|
+
return '';
|
|
303
|
+
const q = query.trim().toLowerCase();
|
|
304
|
+
if (!q)
|
|
305
|
+
return compact.slice(0, max);
|
|
306
|
+
const idx = compact.toLowerCase().indexOf(q);
|
|
307
|
+
if (idx < 0)
|
|
308
|
+
return compact.slice(0, max);
|
|
309
|
+
const start = Math.max(0, idx - 120);
|
|
310
|
+
const end = Math.min(compact.length, start + max);
|
|
311
|
+
return (start > 0 ? '…' : '') + compact.slice(start, end) + (end < compact.length ? '…' : '');
|
|
312
|
+
}
|
|
313
|
+
function classifyVaultOrigin(relPath) {
|
|
314
|
+
if (relPath.startsWith('04-Ingest/'))
|
|
315
|
+
return 'Seeded';
|
|
316
|
+
if (relPath.startsWith('00-System/skills/') || relPath.startsWith('00-System/workflows/') || relPath.startsWith('00-System/agents/')) {
|
|
317
|
+
return 'Agent-created';
|
|
318
|
+
}
|
|
319
|
+
if (relPath.startsWith('01-Daily/'))
|
|
320
|
+
return 'Conversation';
|
|
321
|
+
return 'Vault';
|
|
322
|
+
}
|
|
323
|
+
async function searchBrainLibrary(query, limit = 30, scope = 'all') {
|
|
324
|
+
const trimmed = query.trim();
|
|
325
|
+
const lowered = trimmed.toLowerCase();
|
|
326
|
+
const qWords = lowered.split(/\s+/).filter(Boolean);
|
|
327
|
+
const perTypeLimit = Math.max(8, Math.ceil(limit / 2));
|
|
328
|
+
const results = [];
|
|
329
|
+
const totalByType = { memory: 0, files: 0, artifacts: 0 };
|
|
330
|
+
const include = (kind) => scope === 'all' || scope === kind;
|
|
331
|
+
const dbExists = existsSync(MEMORY_DB_PATH);
|
|
332
|
+
if (include('memory') && dbExists) {
|
|
333
|
+
const Database = (await import('better-sqlite3')).default;
|
|
334
|
+
const db = new Database(MEMORY_DB_PATH, { readonly: true });
|
|
335
|
+
try {
|
|
336
|
+
let rows = [];
|
|
337
|
+
if (trimmed) {
|
|
338
|
+
const ftsQuery = quotedFtsQuery(trimmed);
|
|
339
|
+
if (ftsQuery) {
|
|
340
|
+
rows = db.prepare(`SELECT c.id, c.source_file, c.section, c.content, c.chunk_type,
|
|
341
|
+
c.updated_at, c.source_slug, c.source_type, c.agent_slug, c.pinned,
|
|
342
|
+
bm25(chunks_fts) AS score
|
|
343
|
+
FROM chunks_fts f
|
|
344
|
+
JOIN chunks c ON c.id = f.rowid
|
|
345
|
+
LEFT JOIN chunk_soft_deletes sd ON sd.chunk_id = c.id
|
|
346
|
+
WHERE chunks_fts MATCH ? AND sd.chunk_id IS NULL
|
|
347
|
+
ORDER BY bm25(chunks_fts)
|
|
348
|
+
LIMIT ?`).all(ftsQuery, perTypeLimit);
|
|
349
|
+
}
|
|
350
|
+
}
|
|
351
|
+
else {
|
|
352
|
+
rows = db.prepare(`SELECT c.id, c.source_file, c.section, c.content, c.chunk_type,
|
|
353
|
+
c.updated_at, c.source_slug, c.source_type, c.agent_slug, c.pinned,
|
|
354
|
+
0 AS score
|
|
355
|
+
FROM chunks c
|
|
356
|
+
LEFT JOIN chunk_soft_deletes sd ON sd.chunk_id = c.id
|
|
357
|
+
WHERE sd.chunk_id IS NULL
|
|
358
|
+
ORDER BY c.updated_at DESC, c.id DESC
|
|
359
|
+
LIMIT ?`).all(perTypeLimit);
|
|
360
|
+
}
|
|
361
|
+
totalByType.memory = rows.length;
|
|
362
|
+
for (const row of rows) {
|
|
363
|
+
const sourceFile = String(row.source_file ?? '');
|
|
364
|
+
const sourceSlug = row.source_slug ? String(row.source_slug) : '';
|
|
365
|
+
const section = String(row.section ?? '');
|
|
366
|
+
const score = Number(row.score ?? 0);
|
|
367
|
+
const badges = [
|
|
368
|
+
row.chunk_type ? String(row.chunk_type) : 'chunk',
|
|
369
|
+
sourceSlug ? `source:${sourceSlug}` : classifyVaultOrigin(sourceFile),
|
|
370
|
+
row.pinned ? 'pinned' : '',
|
|
371
|
+
].filter(Boolean);
|
|
372
|
+
results.push({
|
|
373
|
+
id: `memory:${row.id}`,
|
|
374
|
+
kind: 'memory',
|
|
375
|
+
title: section || path.basename(sourceFile) || `Chunk #${row.id}`,
|
|
376
|
+
subtitle: sourceFile,
|
|
377
|
+
preview: textPreview(String(row.content ?? ''), trimmed),
|
|
378
|
+
timestamp: row.updated_at ? String(row.updated_at) : null,
|
|
379
|
+
score: trimmed ? 100 - Math.abs(score) : 0,
|
|
380
|
+
badges,
|
|
381
|
+
chunkId: Number(row.id),
|
|
382
|
+
source: sourceSlug || sourceFile,
|
|
383
|
+
});
|
|
384
|
+
}
|
|
385
|
+
}
|
|
386
|
+
catch {
|
|
387
|
+
// Missing legacy tables should not break dashboard search.
|
|
388
|
+
}
|
|
389
|
+
finally {
|
|
390
|
+
db.close();
|
|
391
|
+
}
|
|
392
|
+
}
|
|
393
|
+
if (include('artifacts') && dbExists) {
|
|
394
|
+
const Database = (await import('better-sqlite3')).default;
|
|
395
|
+
const db = new Database(MEMORY_DB_PATH, { readonly: true });
|
|
396
|
+
try {
|
|
397
|
+
let rows = [];
|
|
398
|
+
if (trimmed) {
|
|
399
|
+
const ftsQuery = quotedFtsQuery(trimmed);
|
|
400
|
+
if (ftsQuery) {
|
|
401
|
+
rows = db.prepare(`SELECT a.id, a.tool_name, a.summary, substr(a.content, 1, 900) AS preview,
|
|
402
|
+
a.tags, a.stored_at, a.session_key, a.agent_slug, bm25(tool_artifacts_fts) AS score
|
|
403
|
+
FROM tool_artifacts_fts f
|
|
404
|
+
JOIN tool_artifacts a ON a.id = f.rowid
|
|
405
|
+
WHERE tool_artifacts_fts MATCH ?
|
|
406
|
+
ORDER BY bm25(tool_artifacts_fts)
|
|
407
|
+
LIMIT ?`).all(ftsQuery, perTypeLimit);
|
|
408
|
+
}
|
|
409
|
+
}
|
|
410
|
+
else {
|
|
411
|
+
rows = db.prepare(`SELECT id, tool_name, summary, substr(content, 1, 900) AS preview,
|
|
412
|
+
tags, stored_at, session_key, agent_slug, 0 AS score
|
|
413
|
+
FROM tool_artifacts
|
|
414
|
+
ORDER BY stored_at DESC, id DESC
|
|
415
|
+
LIMIT ?`).all(perTypeLimit);
|
|
416
|
+
}
|
|
417
|
+
totalByType.artifacts = rows.length;
|
|
418
|
+
for (const row of rows) {
|
|
419
|
+
const tags = String(row.tags ?? '').split(',').map((t) => t.trim()).filter(Boolean).slice(0, 4);
|
|
420
|
+
results.push({
|
|
421
|
+
id: `artifact:${row.id}`,
|
|
422
|
+
kind: 'artifact',
|
|
423
|
+
title: String(row.summary ?? '').trim() || String(row.tool_name ?? 'Tool artifact'),
|
|
424
|
+
subtitle: `Tool output · ${String(row.tool_name ?? 'unknown')}`,
|
|
425
|
+
preview: textPreview(`${row.summary ?? ''}\n${row.preview ?? ''}`, trimmed),
|
|
426
|
+
timestamp: row.stored_at ? String(row.stored_at) : null,
|
|
427
|
+
score: trimmed ? 100 - Math.abs(Number(row.score ?? 0)) : 0,
|
|
428
|
+
badges: ['artifact', ...tags],
|
|
429
|
+
artifactId: Number(row.id),
|
|
430
|
+
source: row.session_key ? String(row.session_key) : undefined,
|
|
431
|
+
});
|
|
432
|
+
}
|
|
433
|
+
}
|
|
434
|
+
catch {
|
|
435
|
+
// Artifact memory may not exist on older DBs.
|
|
436
|
+
}
|
|
437
|
+
finally {
|
|
438
|
+
db.close();
|
|
439
|
+
}
|
|
440
|
+
}
|
|
441
|
+
if (include('files')) {
|
|
442
|
+
const vaultRoot = VAULT_DIR;
|
|
443
|
+
const files = [];
|
|
444
|
+
const maxScan = trimmed ? 5000 : 500;
|
|
445
|
+
let scanned = 0;
|
|
446
|
+
function walk(dir) {
|
|
447
|
+
if (scanned >= maxScan)
|
|
448
|
+
return;
|
|
449
|
+
let entries = [];
|
|
450
|
+
try {
|
|
451
|
+
entries = readdirSync(dir);
|
|
452
|
+
}
|
|
453
|
+
catch {
|
|
454
|
+
return;
|
|
455
|
+
}
|
|
456
|
+
for (const entry of entries) {
|
|
457
|
+
if (scanned >= maxScan)
|
|
458
|
+
break;
|
|
459
|
+
if (entry.startsWith('.'))
|
|
460
|
+
continue;
|
|
461
|
+
const full = path.join(dir, entry);
|
|
462
|
+
let stat;
|
|
463
|
+
try {
|
|
464
|
+
stat = statSync(full);
|
|
465
|
+
}
|
|
466
|
+
catch {
|
|
467
|
+
continue;
|
|
468
|
+
}
|
|
469
|
+
if (stat.isDirectory()) {
|
|
470
|
+
if (entry === 'node_modules' || entry === '.git')
|
|
471
|
+
continue;
|
|
472
|
+
walk(full);
|
|
473
|
+
continue;
|
|
474
|
+
}
|
|
475
|
+
if (!entry.endsWith('.md') || entry.endsWith('.md.bak'))
|
|
476
|
+
continue;
|
|
477
|
+
scanned += 1;
|
|
478
|
+
const relPath = path.relative(vaultRoot, full);
|
|
479
|
+
let raw = '';
|
|
480
|
+
try {
|
|
481
|
+
raw = readFileSync(full, 'utf-8');
|
|
482
|
+
}
|
|
483
|
+
catch {
|
|
484
|
+
continue;
|
|
485
|
+
}
|
|
486
|
+
let title = path.basename(entry, '.md');
|
|
487
|
+
let typeTag = '';
|
|
488
|
+
let content = raw;
|
|
489
|
+
try {
|
|
490
|
+
const parsed = matter(raw.slice(0, 80_000));
|
|
491
|
+
content = parsed.content || raw;
|
|
492
|
+
const data = parsed.data;
|
|
493
|
+
if (typeof data.title === 'string')
|
|
494
|
+
title = data.title;
|
|
495
|
+
else if (typeof data.name === 'string')
|
|
496
|
+
title = data.name;
|
|
497
|
+
else {
|
|
498
|
+
const h1 = content.match(/^#\s+(.+)$/m);
|
|
499
|
+
if (h1)
|
|
500
|
+
title = h1[1].trim();
|
|
501
|
+
}
|
|
502
|
+
if (typeof data.type === 'string')
|
|
503
|
+
typeTag = data.type;
|
|
504
|
+
}
|
|
505
|
+
catch { /* keep filename */ }
|
|
506
|
+
const hay = `${title}\n${relPath}\n${content.slice(0, 80_000)}`.toLowerCase();
|
|
507
|
+
const match = !trimmed || qWords.every((word) => hay.includes(word)) || hay.includes(lowered);
|
|
508
|
+
if (!match)
|
|
509
|
+
continue;
|
|
510
|
+
const titleHit = lowered && title.toLowerCase().includes(lowered) ? 30 : 0;
|
|
511
|
+
const pathHit = lowered && relPath.toLowerCase().includes(lowered) ? 18 : 0;
|
|
512
|
+
const contentHit = lowered && content.toLowerCase().includes(lowered) ? 10 : 0;
|
|
513
|
+
files.push({
|
|
514
|
+
id: `file:${relPath}`,
|
|
515
|
+
kind: 'file',
|
|
516
|
+
title,
|
|
517
|
+
subtitle: relPath,
|
|
518
|
+
preview: textPreview(content, trimmed),
|
|
519
|
+
timestamp: stat.mtime.toISOString(),
|
|
520
|
+
score: trimmed ? titleHit + pathHit + contentHit : stat.mtimeMs / 1_000_000_000,
|
|
521
|
+
badges: [typeTag || 'note', classifyVaultOrigin(relPath)].filter(Boolean),
|
|
522
|
+
relPath,
|
|
523
|
+
source: relPath,
|
|
524
|
+
});
|
|
525
|
+
}
|
|
526
|
+
}
|
|
527
|
+
if (existsSync(vaultRoot))
|
|
528
|
+
walk(vaultRoot);
|
|
529
|
+
files.sort((a, b) => (b.score ?? 0) - (a.score ?? 0));
|
|
530
|
+
totalByType.files = files.length;
|
|
531
|
+
results.push(...files.slice(0, perTypeLimit));
|
|
532
|
+
}
|
|
533
|
+
results.sort((a, b) => {
|
|
534
|
+
if (trimmed)
|
|
535
|
+
return (b.score ?? 0) - (a.score ?? 0);
|
|
536
|
+
return String(b.timestamp ?? '').localeCompare(String(a.timestamp ?? ''));
|
|
537
|
+
});
|
|
538
|
+
return { results: results.slice(0, limit), totalByType, dbExists };
|
|
539
|
+
}
|
|
291
540
|
// ── Remote access config ────────────────────────────────────────────
|
|
292
541
|
const REMOTE_CONFIG_PATH = path.join(BASE_DIR, 'remote-access.json');
|
|
293
542
|
function generateAccessToken() {
|
|
@@ -4335,7 +4584,8 @@ If the tool returns nothing or errors, return an empty array \`[]\`.`,
|
|
|
4335
4584
|
const { getStore } = await import('../tools/shared.js');
|
|
4336
4585
|
const store = await getStore();
|
|
4337
4586
|
const slug = typeof req.query.slug === 'string' ? req.query.slug : undefined;
|
|
4338
|
-
const
|
|
4587
|
+
const limit = Math.min(Math.max(Number(req.query.limit) || 50, 1), 200);
|
|
4588
|
+
const runs = store.listIngestionRuns(slug, limit);
|
|
4339
4589
|
res.json({ runs });
|
|
4340
4590
|
}
|
|
4341
4591
|
catch (err) {
|
|
@@ -6754,6 +7004,30 @@ If the tool returns nothing or errors, return an empty array \`[]\`.`,
|
|
|
6754
7004
|
res.status(500).json({ error: String(err) });
|
|
6755
7005
|
}
|
|
6756
7006
|
});
|
|
7007
|
+
app.get('/api/brain/artifacts/:id', async (req, res) => {
|
|
7008
|
+
try {
|
|
7009
|
+
const id = Number(req.params.id);
|
|
7010
|
+
if (!Number.isFinite(id) || id <= 0) {
|
|
7011
|
+
res.status(400).json({ error: 'invalid artifact id' });
|
|
7012
|
+
return;
|
|
7013
|
+
}
|
|
7014
|
+
const { getStore } = await import('../tools/shared.js');
|
|
7015
|
+
const store = await getStore();
|
|
7016
|
+
if (!store?.getArtifact) {
|
|
7017
|
+
res.status(503).json({ error: 'Artifact memory not available' });
|
|
7018
|
+
return;
|
|
7019
|
+
}
|
|
7020
|
+
const artifact = store.getArtifact(id);
|
|
7021
|
+
if (!artifact) {
|
|
7022
|
+
res.status(404).json({ error: 'artifact not found' });
|
|
7023
|
+
return;
|
|
7024
|
+
}
|
|
7025
|
+
res.json({ ok: true, artifact });
|
|
7026
|
+
}
|
|
7027
|
+
catch (err) {
|
|
7028
|
+
res.status(500).json({ error: String(err) });
|
|
7029
|
+
}
|
|
7030
|
+
});
|
|
6757
7031
|
// ── Cron training chat endpoint ─────────────────────────────────────
|
|
6758
7032
|
app.post('/api/cron/train', async (req, res) => {
|
|
6759
7033
|
const { message, jobName, prompt, context, agentSlug } = req.body;
|
|
@@ -7120,6 +7394,19 @@ If the tool returns nothing or errors, return an empty array \`[]\`.`,
|
|
|
7120
7394
|
res.status(500).json({ results: [], error: String(err) });
|
|
7121
7395
|
}
|
|
7122
7396
|
});
|
|
7397
|
+
app.get('/api/brain/library/search', async (req, res) => {
|
|
7398
|
+
try {
|
|
7399
|
+
const q = String(req.query.q ?? '');
|
|
7400
|
+
const rawScope = String(req.query.scope ?? 'all');
|
|
7401
|
+
const scope = ['all', 'memory', 'files', 'artifacts'].includes(rawScope) ? rawScope : 'all';
|
|
7402
|
+
const limit = Math.min(Math.max(Number(req.query.limit) || 30, 1), 80);
|
|
7403
|
+
const data = await searchBrainLibrary(q, limit, scope);
|
|
7404
|
+
res.json({ ok: true, query: q, scope, ...data });
|
|
7405
|
+
}
|
|
7406
|
+
catch (err) {
|
|
7407
|
+
res.status(500).json({ ok: false, error: String(err), results: [] });
|
|
7408
|
+
}
|
|
7409
|
+
});
|
|
7123
7410
|
// ── Metrics route ─────────────────────────────────────────────────
|
|
7124
7411
|
app.get('/api/metrics', (_req, res) => {
|
|
7125
7412
|
res.json(computeMetrics());
|
|
@@ -11312,6 +11599,201 @@ if('serviceWorker' in navigator){navigator.serviceWorker.getRegistrations().then
|
|
|
11312
11599
|
overflow-y: auto;
|
|
11313
11600
|
}
|
|
11314
11601
|
|
|
11602
|
+
/* ── Brain command center ──────────────── */
|
|
11603
|
+
.brain-command-shell {
|
|
11604
|
+
display: flex;
|
|
11605
|
+
flex-direction: column;
|
|
11606
|
+
gap: 16px;
|
|
11607
|
+
}
|
|
11608
|
+
.brain-hero-panel {
|
|
11609
|
+
border: 1px solid var(--border);
|
|
11610
|
+
border-radius: var(--radius);
|
|
11611
|
+
background: var(--bg-card);
|
|
11612
|
+
padding: 18px;
|
|
11613
|
+
}
|
|
11614
|
+
.brain-pillar-grid {
|
|
11615
|
+
display: grid;
|
|
11616
|
+
grid-template-columns: repeat(auto-fit, minmax(220px, 1fr));
|
|
11617
|
+
gap: 12px;
|
|
11618
|
+
}
|
|
11619
|
+
.brain-pillar-card {
|
|
11620
|
+
border: 1px solid var(--border);
|
|
11621
|
+
border-radius: var(--radius);
|
|
11622
|
+
background: var(--bg-card);
|
|
11623
|
+
padding: 16px;
|
|
11624
|
+
display: flex;
|
|
11625
|
+
flex-direction: column;
|
|
11626
|
+
gap: 10px;
|
|
11627
|
+
min-height: 178px;
|
|
11628
|
+
}
|
|
11629
|
+
.brain-pillar-card strong {
|
|
11630
|
+
font-size: 14px;
|
|
11631
|
+
color: var(--text-primary);
|
|
11632
|
+
}
|
|
11633
|
+
.brain-pillar-card p {
|
|
11634
|
+
margin: 0;
|
|
11635
|
+
color: var(--text-secondary);
|
|
11636
|
+
font-size: 12px;
|
|
11637
|
+
line-height: 1.5;
|
|
11638
|
+
}
|
|
11639
|
+
.brain-pillar-actions {
|
|
11640
|
+
display: flex;
|
|
11641
|
+
gap: 8px;
|
|
11642
|
+
flex-wrap: wrap;
|
|
11643
|
+
margin-top: auto;
|
|
11644
|
+
}
|
|
11645
|
+
.brain-kpi-row {
|
|
11646
|
+
display: grid;
|
|
11647
|
+
grid-template-columns: repeat(auto-fit, minmax(150px, 1fr));
|
|
11648
|
+
gap: 10px;
|
|
11649
|
+
}
|
|
11650
|
+
.brain-kpi {
|
|
11651
|
+
border: 1px solid var(--border);
|
|
11652
|
+
border-radius: var(--radius-sm);
|
|
11653
|
+
background: var(--bg-secondary);
|
|
11654
|
+
padding: 12px;
|
|
11655
|
+
}
|
|
11656
|
+
.brain-kpi .value {
|
|
11657
|
+
font-size: 22px;
|
|
11658
|
+
font-weight: 700;
|
|
11659
|
+
color: var(--text-primary);
|
|
11660
|
+
line-height: 1.1;
|
|
11661
|
+
}
|
|
11662
|
+
.brain-kpi .label {
|
|
11663
|
+
font-size: 11px;
|
|
11664
|
+
color: var(--text-muted);
|
|
11665
|
+
text-transform: uppercase;
|
|
11666
|
+
letter-spacing: 0.04em;
|
|
11667
|
+
margin-top: 4px;
|
|
11668
|
+
}
|
|
11669
|
+
.brain-library-toolbar {
|
|
11670
|
+
display: flex;
|
|
11671
|
+
align-items: center;
|
|
11672
|
+
gap: 8px;
|
|
11673
|
+
flex-wrap: wrap;
|
|
11674
|
+
}
|
|
11675
|
+
.brain-library-toolbar input {
|
|
11676
|
+
flex: 1;
|
|
11677
|
+
min-width: 220px;
|
|
11678
|
+
}
|
|
11679
|
+
.brain-result-row {
|
|
11680
|
+
border: 1px solid var(--border);
|
|
11681
|
+
border-radius: var(--radius);
|
|
11682
|
+
background: var(--bg-card);
|
|
11683
|
+
padding: 13px 15px;
|
|
11684
|
+
margin-bottom: 10px;
|
|
11685
|
+
}
|
|
11686
|
+
.brain-result-row:hover { border-color: var(--accent); }
|
|
11687
|
+
.brain-result-top {
|
|
11688
|
+
display: flex;
|
|
11689
|
+
justify-content: space-between;
|
|
11690
|
+
align-items: flex-start;
|
|
11691
|
+
gap: 12px;
|
|
11692
|
+
}
|
|
11693
|
+
.brain-result-title {
|
|
11694
|
+
font-weight: 600;
|
|
11695
|
+
font-size: 13px;
|
|
11696
|
+
color: var(--text-primary);
|
|
11697
|
+
overflow-wrap: anywhere;
|
|
11698
|
+
}
|
|
11699
|
+
.brain-result-subtitle {
|
|
11700
|
+
font-family: 'JetBrains Mono', monospace;
|
|
11701
|
+
font-size: 11px;
|
|
11702
|
+
color: var(--text-muted);
|
|
11703
|
+
margin-top: 3px;
|
|
11704
|
+
overflow-wrap: anywhere;
|
|
11705
|
+
}
|
|
11706
|
+
.brain-result-preview {
|
|
11707
|
+
font-size: 12px;
|
|
11708
|
+
color: var(--text-secondary);
|
|
11709
|
+
line-height: 1.55;
|
|
11710
|
+
margin-top: 8px;
|
|
11711
|
+
white-space: pre-wrap;
|
|
11712
|
+
max-height: 112px;
|
|
11713
|
+
overflow: hidden;
|
|
11714
|
+
}
|
|
11715
|
+
.brain-badge {
|
|
11716
|
+
display: inline-flex;
|
|
11717
|
+
align-items: center;
|
|
11718
|
+
border: 1px solid var(--border);
|
|
11719
|
+
border-radius: 999px;
|
|
11720
|
+
padding: 2px 7px;
|
|
11721
|
+
font-size: 10px;
|
|
11722
|
+
color: var(--text-secondary);
|
|
11723
|
+
background: var(--bg-secondary);
|
|
11724
|
+
white-space: nowrap;
|
|
11725
|
+
}
|
|
11726
|
+
.brain-flow-list {
|
|
11727
|
+
border: 1px solid var(--border);
|
|
11728
|
+
border-radius: var(--radius);
|
|
11729
|
+
background: var(--bg-card);
|
|
11730
|
+
overflow: hidden;
|
|
11731
|
+
}
|
|
11732
|
+
.brain-flow-row {
|
|
11733
|
+
display: grid;
|
|
11734
|
+
grid-template-columns: minmax(140px, 1.2fr) 100px repeat(4, 72px) minmax(120px, 1fr);
|
|
11735
|
+
gap: 8px;
|
|
11736
|
+
align-items: center;
|
|
11737
|
+
padding: 10px 12px;
|
|
11738
|
+
border-bottom: 1px solid var(--border-light);
|
|
11739
|
+
font-size: 12px;
|
|
11740
|
+
}
|
|
11741
|
+
.brain-flow-row:last-child { border-bottom: none; }
|
|
11742
|
+
.brain-source-card {
|
|
11743
|
+
border: 1px solid var(--border);
|
|
11744
|
+
border-radius: var(--radius);
|
|
11745
|
+
background: var(--bg-card);
|
|
11746
|
+
padding: 12px;
|
|
11747
|
+
display: flex;
|
|
11748
|
+
align-items: flex-start;
|
|
11749
|
+
justify-content: space-between;
|
|
11750
|
+
gap: 12px;
|
|
11751
|
+
margin-bottom: 10px;
|
|
11752
|
+
}
|
|
11753
|
+
.brain-drop-zone {
|
|
11754
|
+
border: 1px dashed var(--border-light);
|
|
11755
|
+
border-radius: var(--radius);
|
|
11756
|
+
background: var(--bg-secondary);
|
|
11757
|
+
padding: 18px;
|
|
11758
|
+
display: flex;
|
|
11759
|
+
align-items: center;
|
|
11760
|
+
justify-content: space-between;
|
|
11761
|
+
gap: 14px;
|
|
11762
|
+
flex-wrap: wrap;
|
|
11763
|
+
}
|
|
11764
|
+
.brain-drop-zone.dragover {
|
|
11765
|
+
border-color: var(--accent);
|
|
11766
|
+
background: var(--accent-glow);
|
|
11767
|
+
}
|
|
11768
|
+
.brain-kv-builder {
|
|
11769
|
+
display: flex;
|
|
11770
|
+
flex-direction: column;
|
|
11771
|
+
gap: 6px;
|
|
11772
|
+
min-width: 0;
|
|
11773
|
+
}
|
|
11774
|
+
.brain-kv-row {
|
|
11775
|
+
display: grid;
|
|
11776
|
+
grid-template-columns: minmax(120px, 1fr) minmax(160px, 1.5fr) 30px;
|
|
11777
|
+
gap: 6px;
|
|
11778
|
+
align-items: center;
|
|
11779
|
+
}
|
|
11780
|
+
.brain-kv-row input {
|
|
11781
|
+
padding: 7px 9px;
|
|
11782
|
+
font-size: 12px;
|
|
11783
|
+
}
|
|
11784
|
+
@media (max-width: 760px) {
|
|
11785
|
+
.brain-flow-row {
|
|
11786
|
+
grid-template-columns: 1fr;
|
|
11787
|
+
gap: 3px;
|
|
11788
|
+
}
|
|
11789
|
+
.brain-result-top {
|
|
11790
|
+
flex-direction: column;
|
|
11791
|
+
}
|
|
11792
|
+
.brain-kv-row {
|
|
11793
|
+
grid-template-columns: 1fr;
|
|
11794
|
+
}
|
|
11795
|
+
}
|
|
11796
|
+
|
|
11315
11797
|
/* ── Toast ──────────────────────────────── */
|
|
11316
11798
|
.toast-container {
|
|
11317
11799
|
position: fixed;
|
|
@@ -13744,9 +14226,10 @@ if('serviceWorker' in navigator){navigator.serviceWorker.getRegistrations().then
|
|
|
13744
14226
|
<h1>Brain</h1>
|
|
13745
14227
|
<p class="desc">Query what you know, feed new knowledge in, and watch the system learn.</p>
|
|
13746
14228
|
</div>
|
|
13747
|
-
<div class="actions" style="flex:1;max-width:
|
|
13748
|
-
<input type="text" id="memory-search-input" placeholder="
|
|
13749
|
-
<button class="btn-primary btn-sm" onclick="
|
|
14229
|
+
<div class="actions" style="flex:1;max-width:640px;display:flex;gap:8px">
|
|
14230
|
+
<input type="text" id="memory-search-input" placeholder="Find seeded files, memories, notes, and artifacts..." style="flex:1;padding:6px 10px;border:1px solid var(--border);border-radius:6px;background:var(--bg-input);color:var(--text-primary);font-size:13px" onkeydown="if(event.key==='Enter')brainUnifiedSearchFromHeader()">
|
|
14231
|
+
<button class="btn-primary btn-sm" onclick="brainUnifiedSearchFromHeader()" title="Search the whole brain"><span class="icon-slot" data-icon="search"></span> Find</button>
|
|
14232
|
+
<button class="btn-sm" onclick="switchTab('intelligence','seed')" title="Upload a local file or folder"><span class="icon-slot" data-icon="upload"></span> Seed</button>
|
|
13750
14233
|
<button class="btn-sm" onclick="openQuickAddMemory()" title="Append a quick note to today's daily log">+ Add memory</button>
|
|
13751
14234
|
</div>
|
|
13752
14235
|
</div>
|
|
@@ -13779,18 +14262,109 @@ if('serviceWorker' in navigator){navigator.serviceWorker.getRegistrations().then
|
|
|
13779
14262
|
</div>
|
|
13780
14263
|
</div>
|
|
13781
14264
|
<div class="tab-bar" id="intelligence-tabs" style="margin:0 0 0 18px">
|
|
13782
|
-
<button class="active" data-icon="
|
|
14265
|
+
<button class="active" data-icon="layoutDashboard" onclick="switchTab('intelligence','overview')"><span class="icon-slot"></span> Overview</button>
|
|
14266
|
+
<button data-icon="database" onclick="switchTab('intelligence','search')"><span class="icon-slot"></span> Memory</button>
|
|
14267
|
+
<button data-icon="upload" onclick="switchTab('intelligence','seed')"><span class="icon-slot"></span> Seed</button>
|
|
14268
|
+
<button data-icon="repeat" onclick="switchTab('intelligence','sources')"><span class="icon-slot"></span> Automate</button>
|
|
14269
|
+
<button data-icon="listChecks" onclick="switchTab('intelligence','runs')"><span class="icon-slot"></span> Runs</button>
|
|
13783
14270
|
<button data-icon="sparkles" onclick="switchTab('intelligence','graph')"><span class="icon-slot"></span> Knowledge</button>
|
|
13784
14271
|
<button data-icon="fileText" onclick="switchTab('intelligence','files')"><span class="icon-slot"></span> Files</button>
|
|
13785
|
-
<button data-icon="folder" onclick="switchTab('intelligence','sources')"><span class="icon-slot"></span> Ingestion</button>
|
|
13786
14272
|
<button data-icon="zap" onclick="switchTab('intelligence','health')"><span class="icon-slot"></span> Health <span class="tab-badge" id="brain-health-badge" style="display:none;background:#ef4444;color:#fff">0</span></button>
|
|
13787
14273
|
<button data-icon="users" onclick="switchTab('intelligence','user-model')"><span class="icon-slot"></span> User Model</button>
|
|
13788
14274
|
<button data-icon="brain" onclick="switchTab('intelligence','learning')"><span class="icon-slot"></span> Learning <span class="tab-badge" id="brain-learning-badge" style="display:none;background:#f59e0b;color:#000">0</span></button>
|
|
13789
|
-
<button onclick="switchTab('intelligence','seed')">Seed</button>
|
|
13790
|
-
<button onclick="switchTab('intelligence','runs')">Runs</button>
|
|
13791
14275
|
</div>
|
|
13792
14276
|
<div id="intelligence-tab-content">
|
|
13793
|
-
<div class="tab-pane active" id="tab-intelligence-
|
|
14277
|
+
<div class="tab-pane active" id="tab-intelligence-overview">
|
|
14278
|
+
<div class="brain-command-shell">
|
|
14279
|
+
<div class="brain-hero-panel">
|
|
14280
|
+
<div style="display:flex;justify-content:space-between;gap:16px;align-items:flex-start;flex-wrap:wrap;margin-bottom:14px">
|
|
14281
|
+
<div style="max-width:720px">
|
|
14282
|
+
<div style="font-size:11px;color:var(--text-muted);text-transform:uppercase;letter-spacing:0.06em;margin-bottom:5px">Brain command center</div>
|
|
14283
|
+
<div style="font-size:18px;font-weight:700;margin-bottom:5px">Find anything Clementine knows, then prove how it got there.</div>
|
|
14284
|
+
<div style="font-size:13px;color:var(--text-secondary);line-height:1.5">Use this page to seed local data, keep connected sources refreshed on a schedule, search agent-created artifacts, and verify retrieval coverage.</div>
|
|
14285
|
+
</div>
|
|
14286
|
+
<div style="display:flex;gap:8px;flex-wrap:wrap">
|
|
14287
|
+
<button class="btn-primary btn-sm" onclick="switchTab('intelligence','seed')"><span class="icon-slot" data-icon="upload"></span> Seed local data</button>
|
|
14288
|
+
<button class="btn-sm" onclick="switchTab('intelligence','sources')"><span class="icon-slot" data-icon="repeat"></span> Add scheduled feed</button>
|
|
14289
|
+
<button class="btn-sm" onclick="switchTab('intelligence','health')"><span class="icon-slot" data-icon="activity"></span> Verify health</button>
|
|
14290
|
+
</div>
|
|
14291
|
+
</div>
|
|
14292
|
+
<div id="brain-command-kpis" class="brain-kpi-row">
|
|
14293
|
+
<div class="skel-block"><div class="skel-row med"></div><div class="skel-row short"></div></div>
|
|
14294
|
+
<div class="skel-block"><div class="skel-row med"></div><div class="skel-row short"></div></div>
|
|
14295
|
+
<div class="skel-block"><div class="skel-row med"></div><div class="skel-row short"></div></div>
|
|
14296
|
+
</div>
|
|
14297
|
+
</div>
|
|
14298
|
+
|
|
14299
|
+
<div class="brain-pillar-grid">
|
|
14300
|
+
<div class="brain-pillar-card">
|
|
14301
|
+
<strong>Find</strong>
|
|
14302
|
+
<p>Search memories, seeded records, vault files, and saved tool artifacts in one place.</p>
|
|
14303
|
+
<div class="brain-pillar-actions">
|
|
14304
|
+
<button class="btn-sm btn-primary" onclick="focusBrainLibrarySearch()">Search library</button>
|
|
14305
|
+
<button class="btn-sm" onclick="switchTab('intelligence','files')">Browse files</button>
|
|
14306
|
+
</div>
|
|
14307
|
+
</div>
|
|
14308
|
+
<div class="brain-pillar-card">
|
|
14309
|
+
<strong>Seed</strong>
|
|
14310
|
+
<p>Choose files or folders from the local machine, preview what will be written, then commit to memory.</p>
|
|
14311
|
+
<div class="brain-pillar-actions">
|
|
14312
|
+
<button class="btn-sm btn-primary" onclick="switchTab('intelligence','seed')">Upload data</button>
|
|
14313
|
+
<button class="btn-sm" onclick="document.getElementById('brain-file-input')?.click()">Choose files</button>
|
|
14314
|
+
</div>
|
|
14315
|
+
</div>
|
|
14316
|
+
<div class="brain-pillar-card">
|
|
14317
|
+
<strong>Automate</strong>
|
|
14318
|
+
<p>Create scheduled feeds for REST endpoints or connected apps so the brain keeps learning without manual uploads.</p>
|
|
14319
|
+
<div class="brain-pillar-actions">
|
|
14320
|
+
<button class="btn-sm btn-primary" onclick="switchTab('intelligence','sources');setTimeout(brainShowPollForm,80)">REST feed</button>
|
|
14321
|
+
<button class="btn-sm" onclick="switchTab('intelligence','sources');setTimeout(brainOpenFeedWizard,80)">Connected app</button>
|
|
14322
|
+
</div>
|
|
14323
|
+
</div>
|
|
14324
|
+
<div class="brain-pillar-card">
|
|
14325
|
+
<strong>Verify</strong>
|
|
14326
|
+
<p>Check dense model readiness, retrieval coverage, ingestion runs, and whether new data is actually searchable.</p>
|
|
14327
|
+
<div class="brain-pillar-actions">
|
|
14328
|
+
<button class="btn-sm btn-primary" onclick="switchTab('intelligence','health')">Open health</button>
|
|
14329
|
+
<button class="btn-sm" onclick="switchTab('intelligence','runs')">View runs</button>
|
|
14330
|
+
</div>
|
|
14331
|
+
</div>
|
|
14332
|
+
</div>
|
|
14333
|
+
|
|
14334
|
+
<div class="brain-hero-panel">
|
|
14335
|
+
<div class="brain-library-toolbar" style="margin-bottom:12px">
|
|
14336
|
+
<input id="brain-library-search-input" type="text" placeholder="Search the whole brain..." onkeydown="if(event.key==='Enter')runBrainLibrarySearch()">
|
|
14337
|
+
<select id="brain-library-scope" onchange="runBrainLibrarySearch()" style="width:150px">
|
|
14338
|
+
<option value="all">Everything</option>
|
|
14339
|
+
<option value="memory">Memory</option>
|
|
14340
|
+
<option value="files">Files</option>
|
|
14341
|
+
<option value="artifacts">Artifacts</option>
|
|
14342
|
+
</select>
|
|
14343
|
+
<button class="btn-primary btn-sm" onclick="runBrainLibrarySearch()">Search</button>
|
|
14344
|
+
<button class="btn-sm" onclick="runBrainLibrarySearch('')">Recent</button>
|
|
14345
|
+
</div>
|
|
14346
|
+
<div id="brain-library-summary" style="font-size:12px;color:var(--text-muted);margin-bottom:10px"></div>
|
|
14347
|
+
<div id="brain-library-results">
|
|
14348
|
+
<div class="empty-state" style="padding:20px">Search the whole brain, or click Recent to inspect the latest remembered items.</div>
|
|
14349
|
+
</div>
|
|
14350
|
+
</div>
|
|
14351
|
+
|
|
14352
|
+
<div>
|
|
14353
|
+
<div style="display:flex;justify-content:space-between;align-items:center;margin-bottom:10px">
|
|
14354
|
+
<div style="font-weight:600">Recent knowledge flow</div>
|
|
14355
|
+
<button class="btn-sm" onclick="refreshBrainOverview()">Refresh</button>
|
|
14356
|
+
</div>
|
|
14357
|
+
<div id="brain-overview-flow"><div class="skel-block"><div class="skel-row med"></div><div class="skel-row"></div></div></div>
|
|
14358
|
+
</div>
|
|
14359
|
+
</div>
|
|
14360
|
+
</div>
|
|
14361
|
+
<div class="tab-pane" id="tab-intelligence-search">
|
|
14362
|
+
<div style="display:flex;gap:8px;align-items:center;margin-bottom:12px;flex-wrap:wrap">
|
|
14363
|
+
<input type="text" id="memory-detail-search-input" placeholder="Search editable memory chunks..." style="flex:1;min-width:220px" onkeydown="if(event.key==='Enter')runMemoryDetailSearch()">
|
|
14364
|
+
<button class="btn-primary btn-sm" onclick="runMemoryDetailSearch()">Search chunks</button>
|
|
14365
|
+
<button class="btn-sm" onclick="document.getElementById('memory-detail-search-input').value='pinned:true';runMemoryDetailSearch()">Pinned</button>
|
|
14366
|
+
<button class="btn-sm" onclick="document.getElementById('memory-detail-search-input').value='since:7d';runMemoryDetailSearch()">Last 7d</button>
|
|
14367
|
+
</div>
|
|
13794
14368
|
<div id="memory-coverage-strip" style="margin-bottom:14px"></div>
|
|
13795
14369
|
<div id="memory-search-results"></div>
|
|
13796
14370
|
<div id="memory-overview" style="margin-top:18px">
|
|
@@ -13861,18 +14435,24 @@ if('serviceWorker' in navigator){navigator.serviceWorker.getRegistrations().then
|
|
|
13861
14435
|
<div class="card" style="padding:20px;margin-bottom:16px">
|
|
13862
14436
|
<div style="font-weight:600;margin-bottom:8px">Drop a file or folder into the brain</div>
|
|
13863
14437
|
<div style="color:var(--muted);margin-bottom:12px;font-size:13px">
|
|
13864
|
-
Supports CSV, JSON, JSONL, Markdown, PDF, email (.eml / .mbox), DOCX. Preview runs the first 10 records through the full pipeline without writing anything;
|
|
14438
|
+
Supports CSV, JSON, JSONL, Markdown, PDF, email (.eml / .mbox), DOCX. Preview runs the first 10 records through the full pipeline without writing anything; Save to brain writes the full source.
|
|
13865
14439
|
</div>
|
|
13866
14440
|
|
|
13867
14441
|
<!-- Primary: native file/folder pickers -->
|
|
13868
|
-
<div
|
|
13869
|
-
<
|
|
13870
|
-
|
|
13871
|
-
|
|
14442
|
+
<div id="brain-drop-zone" class="brain-drop-zone" ondragover="brainHandleDrag(event, true)" ondragleave="brainHandleDrag(event, false)" ondrop="brainHandleDrop(event)">
|
|
14443
|
+
<div style="min-width:220px;flex:1">
|
|
14444
|
+
<div style="font-weight:600;margin-bottom:4px">Upload local knowledge</div>
|
|
14445
|
+
<div style="font-size:12px;color:var(--text-muted);line-height:1.5">Drop files here, choose a folder, or use the advanced path option for data already on this machine. Clementine previews records before writing anything.</div>
|
|
14446
|
+
</div>
|
|
14447
|
+
<div style="display:flex;gap:8px;flex-wrap:wrap;align-items:center">
|
|
14448
|
+
<button class="btn-primary" onclick="document.getElementById('brain-file-input').click()" type="button"><span class="icon-slot" data-icon="fileText"></span> Choose files</button>
|
|
14449
|
+
<button class="btn-primary" onclick="document.getElementById('brain-folder-input').click()" type="button"><span class="icon-slot" data-icon="folder"></span> Choose folder</button>
|
|
14450
|
+
<input type="text" id="brain-seed-slug" placeholder="source name (optional)" style="width:220px">
|
|
14451
|
+
</div>
|
|
13872
14452
|
</div>
|
|
13873
14453
|
<input type="file" id="brain-file-input" multiple style="display:none" onchange="brainHandleFilesChosen(this.files, false)">
|
|
13874
14454
|
<input type="file" id="brain-folder-input" webkitdirectory directory multiple style="display:none" onchange="brainHandleFilesChosen(this.files, true)">
|
|
13875
|
-
<div id="brain-upload-status" style="margin
|
|
14455
|
+
<div id="brain-upload-status" style="margin:8px 0;color:var(--muted);font-size:13px"></div>
|
|
13876
14456
|
|
|
13877
14457
|
<!-- Secondary: for power users who want to point at an existing on-disk path -->
|
|
13878
14458
|
<details style="margin-bottom:8px">
|
|
@@ -13883,8 +14463,8 @@ if('serviceWorker' in navigator){navigator.serviceWorker.getRegistrations().then
|
|
|
13883
14463
|
</details>
|
|
13884
14464
|
|
|
13885
14465
|
<div style="display:flex;gap:8px;margin-top:8px">
|
|
13886
|
-
<button class="btn-primary" onclick="brainPreviewSeed()">Preview</button>
|
|
13887
|
-
<button class="btn-primary" id="brain-commit-btn" onclick="brainCommitSeed()" style="display:none">
|
|
14466
|
+
<button class="btn-primary" onclick="brainPreviewSeed()">Preview records</button>
|
|
14467
|
+
<button class="btn-primary" id="brain-commit-btn" onclick="brainCommitSeed()" style="display:none">Save to brain</button>
|
|
13888
14468
|
</div>
|
|
13889
14469
|
|
|
13890
14470
|
<div id="brain-seed-manifest" style="margin-top:16px"></div>
|
|
@@ -13962,14 +14542,25 @@ if('serviceWorker' in navigator){navigator.serviceWorker.getRegistrations().then
|
|
|
13962
14542
|
<input type="text" id="brain-poll-url" placeholder="https://api.example.com/v1/items">
|
|
13963
14543
|
<label>Method</label>
|
|
13964
14544
|
<select id="brain-poll-method"><option>GET</option><option>POST</option></select>
|
|
13965
|
-
<label>Headers
|
|
13966
|
-
<
|
|
13967
|
-
|
|
13968
|
-
|
|
13969
|
-
|
|
13970
|
-
|
|
14545
|
+
<label>Headers</label>
|
|
14546
|
+
<div class="brain-kv-builder">
|
|
14547
|
+
<div id="brain-poll-headers-rows"></div>
|
|
14548
|
+
<button class="btn-sm" type="button" onclick="brainAddKvRow('headers')">Add header</button>
|
|
14549
|
+
<input type="hidden" id="brain-poll-headers">
|
|
14550
|
+
</div>
|
|
14551
|
+
<label>Query params</label>
|
|
14552
|
+
<div class="brain-kv-builder">
|
|
14553
|
+
<div id="brain-poll-params-rows"></div>
|
|
14554
|
+
<button class="btn-sm" type="button" onclick="brainAddKvRow('params')">Add param</button>
|
|
14555
|
+
<input type="hidden" id="brain-poll-params">
|
|
14556
|
+
</div>
|
|
14557
|
+
<label>Record list field</label>
|
|
14558
|
+
<input type="text" id="brain-poll-recordspath" placeholder="data, items, results">
|
|
13971
14559
|
<label>Cron schedule</label>
|
|
13972
|
-
<
|
|
14560
|
+
<div>
|
|
14561
|
+
<input type="text" id="brain-poll-cron" placeholder="0 * * * * (hourly)">
|
|
14562
|
+
<div id="brain-poll-schedule-chips" style="display:flex;gap:6px;flex-wrap:wrap;margin-top:6px"></div>
|
|
14563
|
+
</div>
|
|
13973
14564
|
<label>Target folder</label>
|
|
13974
14565
|
<input type="text" id="brain-poll-folder" placeholder="04-Ingest/stripe">
|
|
13975
14566
|
<label>Project (optional)</label>
|
|
@@ -14181,6 +14772,269 @@ if('serviceWorker' in navigator){navigator.serviceWorker.getRegistrations().then
|
|
|
14181
14772
|
error: 'Error',
|
|
14182
14773
|
};
|
|
14183
14774
|
|
|
14775
|
+
function brainTypeLabel(kind) {
|
|
14776
|
+
if (kind === 'memory') return 'Memory';
|
|
14777
|
+
if (kind === 'file') return 'File';
|
|
14778
|
+
if (kind === 'artifact') return 'Artifact';
|
|
14779
|
+
return kind || 'Item';
|
|
14780
|
+
}
|
|
14781
|
+
|
|
14782
|
+
function brainStatusBadge(status) {
|
|
14783
|
+
var s = String(status || 'unknown');
|
|
14784
|
+
var color = s === 'ok' ? 'var(--green)' : (s === 'partial' ? 'var(--yellow)' : (s === 'error' ? 'var(--red)' : 'var(--text-muted)'));
|
|
14785
|
+
return '<span class="brain-badge" style="color:' + color + ';border-color:' + color + '33">' + escapeHtml(s) + '</span>';
|
|
14786
|
+
}
|
|
14787
|
+
|
|
14788
|
+
function brainUnifiedSearchFromHeader() {
|
|
14789
|
+
var header = document.getElementById('memory-search-input');
|
|
14790
|
+
var q = header ? header.value.trim() : '';
|
|
14791
|
+
switchTab('intelligence', 'overview');
|
|
14792
|
+
setTimeout(function() {
|
|
14793
|
+
var input = document.getElementById('brain-library-search-input');
|
|
14794
|
+
if (input) input.value = q;
|
|
14795
|
+
runBrainLibrarySearch(q);
|
|
14796
|
+
}, 60);
|
|
14797
|
+
}
|
|
14798
|
+
|
|
14799
|
+
function focusBrainLibrarySearch() {
|
|
14800
|
+
switchTab('intelligence', 'overview');
|
|
14801
|
+
setTimeout(function() {
|
|
14802
|
+
var input = document.getElementById('brain-library-search-input');
|
|
14803
|
+
if (input) input.focus();
|
|
14804
|
+
}, 80);
|
|
14805
|
+
}
|
|
14806
|
+
|
|
14807
|
+
async function runBrainLibrarySearch(forceQuery) {
|
|
14808
|
+
var input = document.getElementById('brain-library-search-input');
|
|
14809
|
+
var header = document.getElementById('memory-search-input');
|
|
14810
|
+
var scopeEl = document.getElementById('brain-library-scope');
|
|
14811
|
+
var q = forceQuery !== undefined ? String(forceQuery || '') : (input ? input.value.trim() : '');
|
|
14812
|
+
if (input && forceQuery !== undefined) input.value = q;
|
|
14813
|
+
if (header && q) header.value = q;
|
|
14814
|
+
var scope = scopeEl ? scopeEl.value : 'all';
|
|
14815
|
+
var resultsEl = document.getElementById('brain-library-results');
|
|
14816
|
+
var summaryEl = document.getElementById('brain-library-summary');
|
|
14817
|
+
if (!resultsEl) return;
|
|
14818
|
+
resultsEl.innerHTML = '<div class="empty-state" style="padding:20px">Searching…</div>';
|
|
14819
|
+
if (summaryEl) summaryEl.textContent = '';
|
|
14820
|
+
try {
|
|
14821
|
+
var r = await apiFetch('/api/brain/library/search?q=' + encodeURIComponent(q) + '&scope=' + encodeURIComponent(scope) + '&limit=36');
|
|
14822
|
+
var d = await r.json();
|
|
14823
|
+
if (!r.ok || !d.ok) {
|
|
14824
|
+
resultsEl.innerHTML = '<div class="empty-state" style="color:var(--red)">Search failed: ' + escapeHtml(d.error || r.status) + '</div>';
|
|
14825
|
+
return;
|
|
14826
|
+
}
|
|
14827
|
+
var counts = d.totalByType || {};
|
|
14828
|
+
if (summaryEl) {
|
|
14829
|
+
var label = q ? ('Results for "' + q + '"') : 'Recent brain items';
|
|
14830
|
+
summaryEl.innerHTML = escapeHtml(label) + ' · '
|
|
14831
|
+
+ (counts.memory || 0) + ' memory · '
|
|
14832
|
+
+ (counts.files || 0) + ' files · '
|
|
14833
|
+
+ (counts.artifacts || 0) + ' artifacts';
|
|
14834
|
+
}
|
|
14835
|
+
if (!d.results || d.results.length === 0) {
|
|
14836
|
+
resultsEl.innerHTML = '<div class="empty-state" style="padding:20px">No matches. Try fewer words or switch the scope to Everything.</div>';
|
|
14837
|
+
return;
|
|
14838
|
+
}
|
|
14839
|
+
var html = '';
|
|
14840
|
+
for (var i = 0; i < d.results.length; i++) {
|
|
14841
|
+
var item = d.results[i];
|
|
14842
|
+
var badges = ['<span class="brain-badge">' + escapeHtml(brainTypeLabel(item.kind)) + '</span>'];
|
|
14843
|
+
(item.badges || []).slice(0, 4).forEach(function(b) {
|
|
14844
|
+
badges.push('<span class="brain-badge">' + escapeHtml(b) + '</span>');
|
|
14845
|
+
});
|
|
14846
|
+
var actions = '';
|
|
14847
|
+
if (item.kind === 'file' && item.relPath) {
|
|
14848
|
+
actions = '<button class="btn-sm" onclick="openVaultFile(\\'' + escapeHtml(item.relPath) + '\\')">Open</button>';
|
|
14849
|
+
} else if (item.kind === 'memory' && item.chunkId) {
|
|
14850
|
+
actions = '<button class="btn-sm" onclick="editChunk(' + item.chunkId + ')">Edit</button>'
|
|
14851
|
+
+ '<button class="btn-sm" onclick="openBrainChunkTrace(' + item.chunkId + ')">Trace</button>';
|
|
14852
|
+
} else if (item.kind === 'artifact' && item.artifactId) {
|
|
14853
|
+
actions = '<button class="btn-sm" onclick="openBrainArtifact(' + item.artifactId + ')">Open</button>';
|
|
14854
|
+
}
|
|
14855
|
+
html += '<div class="brain-result-row">'
|
|
14856
|
+
+ '<div class="brain-result-top">'
|
|
14857
|
+
+ '<div style="min-width:0;flex:1">'
|
|
14858
|
+
+ '<div class="brain-result-title">' + escapeHtml(item.title || '(untitled)') + '</div>'
|
|
14859
|
+
+ '<div class="brain-result-subtitle">' + escapeHtml(item.subtitle || item.source || '') + '</div>'
|
|
14860
|
+
+ '</div>'
|
|
14861
|
+
+ '<div style="display:flex;gap:6px;flex-wrap:wrap;justify-content:flex-end;align-items:center">' + badges.join('') + actions + '</div>'
|
|
14862
|
+
+ '</div>'
|
|
14863
|
+
+ (item.preview ? '<div class="brain-result-preview">' + escapeHtml(item.preview) + '</div>' : '')
|
|
14864
|
+
+ (item.timestamp ? '<div style="font-size:11px;color:var(--text-muted);margin-top:8px">' + escapeHtml(timeAgo(item.timestamp)) + '</div>' : '')
|
|
14865
|
+
+ '</div>';
|
|
14866
|
+
}
|
|
14867
|
+
resultsEl.innerHTML = html;
|
|
14868
|
+
} catch (err) {
|
|
14869
|
+
resultsEl.innerHTML = '<div class="empty-state" style="color:var(--red)">Search error: ' + escapeHtml(String(err)) + '</div>';
|
|
14870
|
+
}
|
|
14871
|
+
}
|
|
14872
|
+
|
|
14873
|
+
async function openBrainChunkTrace(id) {
|
|
14874
|
+
var drawer = document.getElementById('brain-chunk-trace-drawer');
|
|
14875
|
+
if (!drawer) {
|
|
14876
|
+
drawer = document.createElement('div');
|
|
14877
|
+
drawer.id = 'brain-chunk-trace-drawer';
|
|
14878
|
+
drawer.style.cssText = 'position:fixed;right:0;top:0;bottom:0;width:620px;max-width:94vw;background:var(--bg-secondary);border-left:1px solid var(--border);box-shadow:-8px 0 32px rgba(0,0,0,0.18);z-index:221;display:flex;flex-direction:column;transform:translateX(100%);transition:transform 200ms ease';
|
|
14879
|
+
drawer.innerHTML =
|
|
14880
|
+
'<div style="display:flex;align-items:center;gap:10px;padding:14px 18px;border-bottom:1px solid var(--border);flex-shrink:0">'
|
|
14881
|
+
+ '<div style="flex:1;min-width:0">'
|
|
14882
|
+
+ '<div id="brain-chunk-title" style="font-weight:600;font-size:15px"></div>'
|
|
14883
|
+
+ '<div id="brain-chunk-subtitle" style="font-size:11px;color:var(--text-muted);font-family:\\x27JetBrains Mono\\x27,monospace;margin-top:2px"></div>'
|
|
14884
|
+
+ '</div>'
|
|
14885
|
+
+ '<button class="btn-icon btn-sm" onclick="closeBrainChunkTrace()" title="Close">' + lucide('x', 'icn-sm') + '</button>'
|
|
14886
|
+
+ '</div>'
|
|
14887
|
+
+ '<div id="brain-chunk-body" style="flex:1;overflow:auto;padding:18px 22px;font-size:12px;line-height:1.55"></div>';
|
|
14888
|
+
document.body.appendChild(drawer);
|
|
14889
|
+
}
|
|
14890
|
+
drawer.style.transform = 'translateX(0)';
|
|
14891
|
+
document.getElementById('brain-chunk-title').textContent = 'Memory chunk #' + id;
|
|
14892
|
+
document.getElementById('brain-chunk-subtitle').textContent = 'Loading…';
|
|
14893
|
+
document.getElementById('brain-chunk-body').innerHTML = '<div class="skel-block"><div class="skel-row med"></div><div class="skel-row"></div></div>';
|
|
14894
|
+
try {
|
|
14895
|
+
var chunkResp = await apiFetch('/api/memory/chunks/' + id);
|
|
14896
|
+
var chunkData = await chunkResp.json();
|
|
14897
|
+
var historyResp = await apiFetch('/api/memory/chunks/' + id + '/history');
|
|
14898
|
+
var historyData = await historyResp.json();
|
|
14899
|
+
if (!chunkResp.ok || !chunkData.ok || !chunkData.chunk) throw new Error(chunkData.error || 'chunk not found');
|
|
14900
|
+
var c = chunkData.chunk;
|
|
14901
|
+
document.getElementById('brain-chunk-title').textContent = c.section || ('Memory chunk #' + id);
|
|
14902
|
+
document.getElementById('brain-chunk-subtitle').textContent = c.sourceFile || c.source_file || '';
|
|
14903
|
+
var metaRows = [
|
|
14904
|
+
['ID', c.id || id],
|
|
14905
|
+
['Type', c.chunkType || c.chunk_type || '—'],
|
|
14906
|
+
['Source', c.sourceFile || c.source_file || '—'],
|
|
14907
|
+
['Agent', c.agentSlug || c.agent_slug || 'global'],
|
|
14908
|
+
['Salience', c.salience != null ? Number(c.salience).toFixed(2) : '—'],
|
|
14909
|
+
['Confidence', c.confidence != null ? Number(c.confidence).toFixed(2) : '—'],
|
|
14910
|
+
['Updated', c.lastUpdated || c.updated_at || '—'],
|
|
14911
|
+
];
|
|
14912
|
+
var html = '<div style="display:grid;grid-template-columns:110px 1fr;gap:6px 12px;margin-bottom:14px">';
|
|
14913
|
+
metaRows.forEach(function(row) {
|
|
14914
|
+
html += '<div style="color:var(--text-muted)">' + escapeHtml(row[0]) + '</div><div style="overflow-wrap:anywhere">' + escapeHtml(row[1]) + '</div>';
|
|
14915
|
+
});
|
|
14916
|
+
html += '</div>';
|
|
14917
|
+
html += '<div style="font-size:11px;color:var(--text-muted);text-transform:uppercase;letter-spacing:0.05em;margin-bottom:6px">Stored content</div>';
|
|
14918
|
+
html += '<div style="white-space:pre-wrap;background:var(--bg-input);border:1px solid var(--border);border-radius:var(--radius-sm);padding:12px;margin-bottom:14px">' + escapeHtml(c.content || '') + '</div>';
|
|
14919
|
+
var history = historyData.ok && Array.isArray(historyData.history) ? historyData.history : [];
|
|
14920
|
+
html += '<div style="font-size:11px;color:var(--text-muted);text-transform:uppercase;letter-spacing:0.05em;margin-bottom:6px">Change history</div>';
|
|
14921
|
+
if (!history.length) {
|
|
14922
|
+
html += '<div class="empty-state" style="padding:16px;text-align:left">No edits or supersedes recorded.</div>';
|
|
14923
|
+
} else {
|
|
14924
|
+
html += '<div style="border:1px solid var(--border);border-radius:var(--radius-sm);overflow:hidden">';
|
|
14925
|
+
history.forEach(function(h) {
|
|
14926
|
+
html += '<div style="padding:8px 10px;border-bottom:1px solid var(--border-light);font-size:12px">'
|
|
14927
|
+
+ '<div style="color:var(--text-muted);font-size:11px">' + escapeHtml(h.timestamp || h.at || '') + '</div>'
|
|
14928
|
+
+ '<div>' + escapeHtml(h.kind || h.action || 'edit') + (h.reason ? ' · ' + escapeHtml(h.reason) : '') + '</div>'
|
|
14929
|
+
+ '</div>';
|
|
14930
|
+
});
|
|
14931
|
+
html += '</div>';
|
|
14932
|
+
}
|
|
14933
|
+
document.getElementById('brain-chunk-body').innerHTML = html;
|
|
14934
|
+
} catch (err) {
|
|
14935
|
+
document.getElementById('brain-chunk-subtitle').textContent = 'Failed';
|
|
14936
|
+
document.getElementById('brain-chunk-body').innerHTML = '<div style="color:var(--red)">' + escapeHtml(String(err)) + '</div>';
|
|
14937
|
+
}
|
|
14938
|
+
}
|
|
14939
|
+
|
|
14940
|
+
function closeBrainChunkTrace() {
|
|
14941
|
+
var drawer = document.getElementById('brain-chunk-trace-drawer');
|
|
14942
|
+
if (drawer) drawer.style.transform = 'translateX(100%)';
|
|
14943
|
+
}
|
|
14944
|
+
|
|
14945
|
+
async function openBrainArtifact(id) {
|
|
14946
|
+
var drawer = document.getElementById('brain-artifact-drawer');
|
|
14947
|
+
if (!drawer) {
|
|
14948
|
+
drawer = document.createElement('div');
|
|
14949
|
+
drawer.id = 'brain-artifact-drawer';
|
|
14950
|
+
drawer.style.cssText = 'position:fixed;right:0;top:0;bottom:0;width:620px;max-width:94vw;background:var(--bg-secondary);border-left:1px solid var(--border);box-shadow:-8px 0 32px rgba(0,0,0,0.18);z-index:220;display:flex;flex-direction:column;transform:translateX(100%);transition:transform 200ms ease';
|
|
14951
|
+
drawer.innerHTML =
|
|
14952
|
+
'<div style="display:flex;align-items:center;gap:10px;padding:14px 18px;border-bottom:1px solid var(--border);flex-shrink:0">'
|
|
14953
|
+
+ '<div style="flex:1;min-width:0">'
|
|
14954
|
+
+ '<div id="brain-artifact-title" style="font-weight:600;font-size:15px"></div>'
|
|
14955
|
+
+ '<div id="brain-artifact-subtitle" style="font-size:11px;color:var(--text-muted);font-family:\\x27JetBrains Mono\\x27,monospace;margin-top:2px"></div>'
|
|
14956
|
+
+ '</div>'
|
|
14957
|
+
+ '<button class="btn-icon btn-sm" onclick="closeBrainArtifact()" title="Close">' + lucide('x', 'icn-sm') + '</button>'
|
|
14958
|
+
+ '</div>'
|
|
14959
|
+
+ '<div id="brain-artifact-body" style="flex:1;overflow:auto;padding:18px 22px;font-size:12px;line-height:1.55;white-space:pre-wrap;font-family:\\x27JetBrains Mono\\x27,monospace"></div>';
|
|
14960
|
+
document.body.appendChild(drawer);
|
|
14961
|
+
}
|
|
14962
|
+
drawer.style.transform = 'translateX(0)';
|
|
14963
|
+
document.getElementById('brain-artifact-title').textContent = 'Artifact #' + id;
|
|
14964
|
+
document.getElementById('brain-artifact-subtitle').textContent = 'Loading…';
|
|
14965
|
+
document.getElementById('brain-artifact-body').textContent = '';
|
|
14966
|
+
try {
|
|
14967
|
+
var r = await apiFetch('/api/brain/artifacts/' + encodeURIComponent(id));
|
|
14968
|
+
var d = await r.json();
|
|
14969
|
+
if (!r.ok || !d.ok || !d.artifact) throw new Error(d.error || 'not found');
|
|
14970
|
+
var a = d.artifact;
|
|
14971
|
+
document.getElementById('brain-artifact-title').textContent = a.summary || ('Artifact #' + id);
|
|
14972
|
+
document.getElementById('brain-artifact-subtitle').textContent = (a.toolName || 'tool') + ' · ' + (a.storedAt || '');
|
|
14973
|
+
document.getElementById('brain-artifact-body').textContent = a.content || a.summary || '';
|
|
14974
|
+
} catch (err) {
|
|
14975
|
+
document.getElementById('brain-artifact-subtitle').textContent = 'Failed';
|
|
14976
|
+
document.getElementById('brain-artifact-body').textContent = String(err);
|
|
14977
|
+
}
|
|
14978
|
+
}
|
|
14979
|
+
|
|
14980
|
+
function closeBrainArtifact() {
|
|
14981
|
+
var drawer = document.getElementById('brain-artifact-drawer');
|
|
14982
|
+
if (drawer) drawer.style.transform = 'translateX(100%)';
|
|
14983
|
+
}
|
|
14984
|
+
|
|
14985
|
+
async function refreshBrainOverview() {
|
|
14986
|
+
var kpis = document.getElementById('brain-command-kpis');
|
|
14987
|
+
var flow = document.getElementById('brain-overview-flow');
|
|
14988
|
+
try {
|
|
14989
|
+
var all = await Promise.all([
|
|
14990
|
+
apiFetch('/api/memory/health').then(function(r) { return r.json(); }).catch(function() { return {}; }),
|
|
14991
|
+
apiFetch('/api/brain/sources').then(function(r) { return r.json(); }).catch(function() { return {}; }),
|
|
14992
|
+
apiFetch('/api/brain/runs?limit=8').then(function(r) { return r.json(); }).catch(function() { return {}; }),
|
|
14993
|
+
]);
|
|
14994
|
+
var h = (all[0] && all[0].health) || {};
|
|
14995
|
+
var sources = (all[1] && all[1].sources) || [];
|
|
14996
|
+
var runs = (all[2] && all[2].runs) || [];
|
|
14997
|
+
var dense = h.denseEmbeddings || {};
|
|
14998
|
+
var densePct = dense.total > 0 ? Math.round((dense.withDense / Math.max(1, dense.total)) * 100) + '%' : '0%';
|
|
14999
|
+
var activeSources = sources.filter(function(s) { return s.enabled; }).length;
|
|
15000
|
+
var scheduledSources = sources.filter(function(s) { return s.enabled && s.scheduleCron; }).length;
|
|
15001
|
+
var lastRun = runs.length ? runs[0] : null;
|
|
15002
|
+
if (kpis) {
|
|
15003
|
+
kpis.innerHTML =
|
|
15004
|
+
'<div class="brain-kpi"><div class="value">' + ((h.chunks && h.chunks.total) || 0).toLocaleString() + '</div><div class="label">Searchable chunks</div></div>'
|
|
15005
|
+
+ '<div class="brain-kpi"><div class="value">' + densePct + '</div><div class="label">Semantic coverage</div></div>'
|
|
15006
|
+
+ '<div class="brain-kpi"><div class="value">' + activeSources + '</div><div class="label">Active sources</div></div>'
|
|
15007
|
+
+ '<div class="brain-kpi"><div class="value">' + scheduledSources + '</div><div class="label">Scheduled feeds</div></div>'
|
|
15008
|
+
+ '<div class="brain-kpi"><div class="value">' + (lastRun ? escapeHtml(lastRun.status) : 'none') + '</div><div class="label">Latest ingestion</div></div>';
|
|
15009
|
+
}
|
|
15010
|
+
if (flow) {
|
|
15011
|
+
if (!runs.length) {
|
|
15012
|
+
flow.innerHTML = '<div class="empty-cta"><div class="label">No ingestion runs yet</div><div class="hint">Seed local data or add a scheduled feed to start the knowledge flow.</div></div>';
|
|
15013
|
+
} else {
|
|
15014
|
+
var html = '<div class="brain-flow-list">';
|
|
15015
|
+
html += '<div class="brain-flow-row" style="color:var(--text-muted);font-size:11px;text-transform:uppercase;letter-spacing:0.04em"><div>Source</div><div>Status</div><div>In</div><div>Written</div><div>Same</div><div>Failed</div><div>When</div></div>';
|
|
15016
|
+
for (var i = 0; i < runs.length; i++) {
|
|
15017
|
+
var run = runs[i];
|
|
15018
|
+
html += '<div class="brain-flow-row">'
|
|
15019
|
+
+ '<div style="font-weight:600;min-width:0;overflow:hidden;text-overflow:ellipsis">' + escapeHtml(run.sourceSlug || '—') + '</div>'
|
|
15020
|
+
+ '<div>' + brainStatusBadge(run.status) + '</div>'
|
|
15021
|
+
+ '<div>' + (run.recordsIn || 0) + '</div>'
|
|
15022
|
+
+ '<div>' + (run.recordsWritten || 0) + '</div>'
|
|
15023
|
+
+ '<div>' + (run.recordsUnchanged || 0) + '</div>'
|
|
15024
|
+
+ '<div>' + (run.recordsFailed || 0) + '</div>'
|
|
15025
|
+
+ '<div style="color:var(--text-muted)">' + escapeHtml(timeAgo(run.finishedAt || run.startedAt)) + '</div>'
|
|
15026
|
+
+ '</div>';
|
|
15027
|
+
}
|
|
15028
|
+
html += '</div>';
|
|
15029
|
+
flow.innerHTML = html;
|
|
15030
|
+
}
|
|
15031
|
+
}
|
|
15032
|
+
if (document.getElementById('brain-library-results')) runBrainLibrarySearch('');
|
|
15033
|
+
} catch (err) {
|
|
15034
|
+
if (kpis) kpis.innerHTML = '<div class="empty-state" style="color:var(--red)">Failed to load overview: ' + escapeHtml(String(err)) + '</div>';
|
|
15035
|
+
}
|
|
15036
|
+
}
|
|
15037
|
+
|
|
14184
15038
|
function brainRenderProgress(el, opts) {
|
|
14185
15039
|
const label = BRAIN_STAGE_LABELS[opts.stage] || opts.stage || 'Working';
|
|
14186
15040
|
const elapsed = Math.floor((Date.now() - opts.startedAt) / 1000);
|
|
@@ -14368,6 +15222,55 @@ if('serviceWorker' in navigator){navigator.serviceWorker.getRegistrations().then
|
|
|
14368
15222
|
];
|
|
14369
15223
|
}
|
|
14370
15224
|
|
|
15225
|
+
function brainSetPollCron(expr) {
|
|
15226
|
+
var input = document.getElementById('brain-poll-cron');
|
|
15227
|
+
if (input) input.value = expr;
|
|
15228
|
+
}
|
|
15229
|
+
|
|
15230
|
+
function brainRenderPollScheduleChips() {
|
|
15231
|
+
var el = document.getElementById('brain-poll-schedule-chips');
|
|
15232
|
+
if (!el) return;
|
|
15233
|
+
el.innerHTML = brainScheduleChips().map(function(c) {
|
|
15234
|
+
return '<button class="btn-sm" type="button" onclick="brainSetPollCron(\\'' + escapeHtml(c.cron) + '\\')">' + escapeHtml(c.label) + '</button>';
|
|
15235
|
+
}).join('');
|
|
15236
|
+
}
|
|
15237
|
+
|
|
15238
|
+
function brainAddKvRow(kind, key, value) {
|
|
15239
|
+
var el = document.getElementById(kind === 'params' ? 'brain-poll-params-rows' : 'brain-poll-headers-rows');
|
|
15240
|
+
if (!el) return;
|
|
15241
|
+
var row = document.createElement('div');
|
|
15242
|
+
row.className = 'brain-kv-row';
|
|
15243
|
+
row.innerHTML =
|
|
15244
|
+
'<input type="text" class="brain-kv-key" placeholder="' + (kind === 'params' ? 'limit' : 'Authorization') + '">'
|
|
15245
|
+
+ '<input type="text" class="brain-kv-value" placeholder="' + (kind === 'params' ? '100' : 'Bearer ${"${"}ref}') + '">'
|
|
15246
|
+
+ '<button class="btn-icon btn-sm" type="button" onclick="this.closest(\\'.brain-kv-row\\').remove()" title="Remove">' + lucide('x', 'icn-sm') + '</button>';
|
|
15247
|
+
el.appendChild(row);
|
|
15248
|
+
row.querySelector('.brain-kv-key').value = key || '';
|
|
15249
|
+
row.querySelector('.brain-kv-value').value = value || '';
|
|
15250
|
+
}
|
|
15251
|
+
|
|
15252
|
+
function brainEnsureKvRows() {
|
|
15253
|
+
var h = document.getElementById('brain-poll-headers-rows');
|
|
15254
|
+
var p = document.getElementById('brain-poll-params-rows');
|
|
15255
|
+
if (h && h.children.length === 0) brainAddKvRow('headers', 'Authorization', 'Bearer ${"${"}api_key}');
|
|
15256
|
+
if (p && p.children.length === 0) brainAddKvRow('params', 'limit', '100');
|
|
15257
|
+
brainRenderPollScheduleChips();
|
|
15258
|
+
}
|
|
15259
|
+
|
|
15260
|
+
function brainCollectKv(kind) {
|
|
15261
|
+
var el = document.getElementById(kind === 'params' ? 'brain-poll-params-rows' : 'brain-poll-headers-rows');
|
|
15262
|
+
var out = {};
|
|
15263
|
+
if (!el) return out;
|
|
15264
|
+
el.querySelectorAll('.brain-kv-row').forEach(function(row) {
|
|
15265
|
+
var keyEl = row.querySelector('.brain-kv-key');
|
|
15266
|
+
var valEl = row.querySelector('.brain-kv-value');
|
|
15267
|
+
var key = keyEl ? keyEl.value.trim() : '';
|
|
15268
|
+
var val = valEl ? valEl.value.trim() : '';
|
|
15269
|
+
if (key) out[key] = val;
|
|
15270
|
+
});
|
|
15271
|
+
return out;
|
|
15272
|
+
}
|
|
15273
|
+
|
|
14371
15274
|
async function brainLoadFeedConnectors() {
|
|
14372
15275
|
try {
|
|
14373
15276
|
const resp = await apiFetch('/api/brain/connectors');
|
|
@@ -14417,7 +15320,7 @@ if('serviceWorker' in navigator){navigator.serviceWorker.getRegistrations().then
|
|
|
14417
15320
|
'<div style="font-size:12px;margin-top:4px">' + fieldsLine + '</div>' +
|
|
14418
15321
|
'</div>' +
|
|
14419
15322
|
'<button class="btn-primary" onclick="brainRunFeed(\\'' + f.name.replace(/"/g, '') + '\\')">Run now</button> ' +
|
|
14420
|
-
'<button class="btn" onclick="brainDeleteFeed(\\'' + f.name.replace(/"/g, '') + '\\')"
|
|
15323
|
+
'<button class="btn" onclick="brainDeleteFeed(\\'' + f.name.replace(/"/g, '') + '\\')">Delete</button>' +
|
|
14421
15324
|
'</div>';
|
|
14422
15325
|
}).join('');
|
|
14423
15326
|
} catch (err) {
|
|
@@ -14846,25 +15749,44 @@ if('serviceWorker' in navigator){navigator.serviceWorker.getRegistrations().then
|
|
|
14846
15749
|
const data = await resp.json();
|
|
14847
15750
|
const el = document.getElementById('brain-sources-list');
|
|
14848
15751
|
if (!data.sources || !data.sources.length) {
|
|
14849
|
-
el.innerHTML = '<div
|
|
15752
|
+
el.innerHTML = '<div class="empty-cta"><div class="label">No ingestion sources yet</div><div class="hint">Seed a local file/folder or create a scheduled feed. Ingested data remains searchable even if a source is later disabled.</div></div>';
|
|
14850
15753
|
return;
|
|
14851
15754
|
}
|
|
14852
|
-
|
|
14853
|
-
|
|
14854
|
-
|
|
14855
|
-
|
|
14856
|
-
|
|
14857
|
-
|
|
14858
|
-
|
|
14859
|
-
|
|
14860
|
-
|
|
14861
|
-
|
|
14862
|
-
|
|
14863
|
-
|
|
14864
|
-
|
|
14865
|
-
|
|
14866
|
-
|
|
14867
|
-
|
|
15755
|
+
var html = '<div style="display:flex;flex-direction:column;gap:10px">';
|
|
15756
|
+
data.sources.forEach(function(s) {
|
|
15757
|
+
const projTag = s.project
|
|
15758
|
+
? '<span class="brain-badge">' + escapeHtml(s.project.split('/').filter(Boolean).pop() || s.project) + '</span>'
|
|
15759
|
+
: '';
|
|
15760
|
+
const schedule = s.scheduleCron ? '<span class="brain-badge">' + escapeHtml(s.scheduleCron) + '</span>' : '<span class="brain-badge">manual</span>';
|
|
15761
|
+
const enabled = s.enabled ? '<span class="brain-badge" style="color:var(--green);border-color:var(--green)33">enabled</span>' : '<span class="brain-badge">disabled</span>';
|
|
15762
|
+
html += '<div class="brain-source-card">'
|
|
15763
|
+
+ '<div style="min-width:0;flex:1">'
|
|
15764
|
+
+ '<div style="display:flex;gap:8px;align-items:center;flex-wrap:wrap;margin-bottom:5px">'
|
|
15765
|
+
+ '<strong style="font-size:14px">' + escapeHtml(s.slug) + '</strong>'
|
|
15766
|
+
+ '<span class="brain-badge">' + escapeHtml(s.kind) + '</span>'
|
|
15767
|
+
+ '<span class="brain-badge">' + escapeHtml(s.adapter) + '</span>'
|
|
15768
|
+
+ schedule + enabled + projTag
|
|
15769
|
+
+ '</div>'
|
|
15770
|
+
+ '<div style="font-size:12px;color:var(--text-secondary);line-height:1.5">'
|
|
15771
|
+
+ 'Target: <code>' + escapeHtml(s.targetFolder || 'default') + '</code>'
|
|
15772
|
+
+ ' · Last run: ' + escapeHtml(s.lastRunAt ? timeAgo(s.lastRunAt) : 'never')
|
|
15773
|
+
+ ' · Status: ' + escapeHtml(s.lastStatus || 'none')
|
|
15774
|
+
+ '</div>'
|
|
15775
|
+
+ '</div>'
|
|
15776
|
+
+ '<div style="display:flex;gap:6px;flex-wrap:wrap;justify-content:flex-end">'
|
|
15777
|
+
+ '<button class="btn-sm brain-run-source" data-slug="' + escapeHtml(s.slug) + '">Run now</button>'
|
|
15778
|
+
+ '<button class="btn-sm brain-delete-source" data-slug="' + escapeHtml(s.slug) + '" title="Delete source">Delete</button>'
|
|
15779
|
+
+ '</div>'
|
|
15780
|
+
+ '</div>';
|
|
15781
|
+
});
|
|
15782
|
+
html += '</div>';
|
|
15783
|
+
el.innerHTML = html;
|
|
15784
|
+
el.querySelectorAll('.brain-run-source').forEach(function(btn) {
|
|
15785
|
+
btn.onclick = function() { brainRunSource(btn.getAttribute('data-slug') || ''); };
|
|
15786
|
+
});
|
|
15787
|
+
el.querySelectorAll('.brain-delete-source').forEach(function(btn) {
|
|
15788
|
+
btn.onclick = function() { brainDeleteSource(btn.getAttribute('data-slug') || ''); };
|
|
15789
|
+
});
|
|
14868
15790
|
}
|
|
14869
15791
|
|
|
14870
15792
|
async function brainRunSource(slug) {
|
|
@@ -14902,6 +15824,7 @@ if('serviceWorker' in navigator){navigator.serviceWorker.getRegistrations().then
|
|
|
14902
15824
|
document.getElementById('brain-creds-form').style.display = 'none';
|
|
14903
15825
|
const wf = document.getElementById('brain-webhook-form'); if (wf) wf.style.display = 'none';
|
|
14904
15826
|
brainLoadProjects(['brain-poll-project']);
|
|
15827
|
+
brainEnsureKvRows();
|
|
14905
15828
|
}
|
|
14906
15829
|
|
|
14907
15830
|
function brainShowWebhookForm() {
|
|
@@ -14972,17 +15895,14 @@ if('serviceWorker' in navigator){navigator.serviceWorker.getRegistrations().then
|
|
|
14972
15895
|
const slug = document.getElementById('brain-poll-slug').value.trim();
|
|
14973
15896
|
const url = document.getElementById('brain-poll-url').value.trim();
|
|
14974
15897
|
const method = document.getElementById('brain-poll-method').value;
|
|
14975
|
-
const headersText = document.getElementById('brain-poll-headers').value.trim();
|
|
14976
|
-
const paramsText = document.getElementById('brain-poll-params').value.trim();
|
|
14977
15898
|
const recordsPath = document.getElementById('brain-poll-recordspath').value.trim();
|
|
14978
15899
|
const cronExpr = document.getElementById('brain-poll-cron').value.trim();
|
|
14979
15900
|
const folder = document.getElementById('brain-poll-folder').value.trim();
|
|
14980
15901
|
const statusEl = document.getElementById('brain-poll-status');
|
|
14981
15902
|
if (!slug || !url) { statusEl.innerHTML = '<span style="color:#e66">slug and URL required</span>'; return; }
|
|
14982
15903
|
|
|
14983
|
-
|
|
14984
|
-
|
|
14985
|
-
try { if (paramsText) params = JSON.parse(paramsText); } catch (e) { statusEl.innerHTML = '<span style="color:#e66">Invalid params JSON</span>'; return; }
|
|
15904
|
+
const headers = brainCollectKv('headers');
|
|
15905
|
+
const params = brainCollectKv('params');
|
|
14986
15906
|
|
|
14987
15907
|
const cfg = { url, method, headers, params };
|
|
14988
15908
|
if (recordsPath) cfg.recordsJsonPath = recordsPath;
|
|
@@ -15018,6 +15938,7 @@ if('serviceWorker' in navigator){navigator.serviceWorker.getRegistrations().then
|
|
|
15018
15938
|
async function brainShowCredsForm() {
|
|
15019
15939
|
document.getElementById('brain-creds-form').style.display = '';
|
|
15020
15940
|
document.getElementById('brain-poll-form').style.display = 'none';
|
|
15941
|
+
const wf = document.getElementById('brain-webhook-form'); if (wf) wf.style.display = 'none';
|
|
15021
15942
|
const resp = await apiFetch('/api/brain/credentials');
|
|
15022
15943
|
const data = await resp.json();
|
|
15023
15944
|
const refs = data.refs || [];
|
|
@@ -15045,20 +15966,21 @@ if('serviceWorker' in navigator){navigator.serviceWorker.getRegistrations().then
|
|
|
15045
15966
|
}
|
|
15046
15967
|
|
|
15047
15968
|
async function brainLoadRuns() {
|
|
15048
|
-
const resp = await apiFetch('/api/brain/runs');
|
|
15969
|
+
const resp = await apiFetch('/api/brain/runs?limit=80');
|
|
15049
15970
|
const data = await resp.json();
|
|
15050
15971
|
const el = document.getElementById('brain-runs-list');
|
|
15051
15972
|
if (!data.runs || !data.runs.length) {
|
|
15052
|
-
el.innerHTML = '<div
|
|
15973
|
+
el.innerHTML = '<div class="empty-cta"><div class="label">No ingestion runs yet</div><div class="hint">Preview and commit a seed, or run a scheduled source.</div></div>';
|
|
15053
15974
|
return;
|
|
15054
15975
|
}
|
|
15055
|
-
el.innerHTML = '<table class="data-table"><thead><tr><th>#</th><th>Source</th><th>Started</th><th>Status</th><th>In</th><th>Written</th><th>Skipped</th><th>Failed</th><th>Overview</th></tr></thead><tbody>' +
|
|
15976
|
+
el.innerHTML = '<table class="data-table"><thead><tr><th>#</th><th>Source</th><th>Started</th><th>Status</th><th>In</th><th>Written</th><th>Same</th><th>Skipped</th><th>Failed</th><th>Recall check</th><th>Overview</th></tr></thead><tbody>' +
|
|
15056
15977
|
data.runs.map((r) =>
|
|
15057
15978
|
'<tr><td>' + r.id + '</td><td>' + escapeHtml(r.sourceSlug) + '</td>' +
|
|
15058
15979
|
'<td>' + escapeHtml(r.startedAt) + '</td>' +
|
|
15059
|
-
'<td>' +
|
|
15980
|
+
'<td>' + brainStatusBadge(r.status) + '</td>' +
|
|
15060
15981
|
'<td>' + r.recordsIn + '</td><td>' + r.recordsWritten + '</td>' +
|
|
15061
|
-
'<td>' + r.recordsSkipped + '</td><td>' + r.recordsFailed + '</td>' +
|
|
15982
|
+
'<td>' + (r.recordsUnchanged || 0) + '</td><td>' + r.recordsSkipped + '</td><td>' + r.recordsFailed + '</td>' +
|
|
15983
|
+
'<td>' + escapeHtml(r.recallCheckStatus || '—') + '</td>' +
|
|
15062
15984
|
'<td>' + (r.overviewNotePath ? '<code style="font-size:12px">' + escapeHtml(r.overviewNotePath) + '</code>' : '—') + '</td></tr>').join('') +
|
|
15063
15985
|
'</tbody></table>';
|
|
15064
15986
|
}
|
|
@@ -15078,6 +16000,23 @@ if('serviceWorker' in navigator){navigator.serviceWorker.getRegistrations().then
|
|
|
15078
16000
|
// the on-disk path, which we feed into the existing preview/
|
|
15079
16001
|
// commit pipeline.
|
|
15080
16002
|
|
|
16003
|
+
function brainHandleDrag(event, over) {
|
|
16004
|
+
event.preventDefault();
|
|
16005
|
+
var zone = document.getElementById('brain-drop-zone');
|
|
16006
|
+
if (!zone) return;
|
|
16007
|
+
if (over) zone.classList.add('dragover');
|
|
16008
|
+
else zone.classList.remove('dragover');
|
|
16009
|
+
}
|
|
16010
|
+
|
|
16011
|
+
async function brainHandleDrop(event) {
|
|
16012
|
+
event.preventDefault();
|
|
16013
|
+
var zone = document.getElementById('brain-drop-zone');
|
|
16014
|
+
if (zone) zone.classList.remove('dragover');
|
|
16015
|
+
var files = event.dataTransfer && event.dataTransfer.files ? event.dataTransfer.files : null;
|
|
16016
|
+
if (!files || files.length === 0) return;
|
|
16017
|
+
await brainHandleFilesChosen(files, false);
|
|
16018
|
+
}
|
|
16019
|
+
|
|
15081
16020
|
async function brainHandleFilesChosen(fileList, isFolder) {
|
|
15082
16021
|
const statusEl = document.getElementById('brain-upload-status');
|
|
15083
16022
|
if (!fileList || fileList.length === 0) return;
|
|
@@ -15126,15 +16065,7 @@ if('serviceWorker' in navigator){navigator.serviceWorker.getRegistrations().then
|
|
|
15126
16065
|
}
|
|
15127
16066
|
}
|
|
15128
16067
|
|
|
15129
|
-
//
|
|
15130
|
-
(function() {
|
|
15131
|
-
const origSwitch = window.switchTab;
|
|
15132
|
-
window.switchTab = function(page, tab) {
|
|
15133
|
-
origSwitch(page, tab);
|
|
15134
|
-
if (page === 'intelligence' && tab === 'sources') { brainLoadSources(); brainLoadFeedConnectors(); brainLoadFeeds(); }
|
|
15135
|
-
if (page === 'intelligence' && tab === 'runs') brainLoadRuns();
|
|
15136
|
-
};
|
|
15137
|
-
})();
|
|
16068
|
+
// Brain tab refresh is handled by the global switchTab dispatcher.
|
|
15138
16069
|
</script>
|
|
15139
16070
|
</div>
|
|
15140
16071
|
|
|
@@ -16343,6 +17274,11 @@ var LUCIDE = {
|
|
|
16343
17274
|
send: '<path d="m22 2-7 20-4-9-9-4Z"/><path d="M22 2 11 13"/>',
|
|
16344
17275
|
arrowRight: '<path d="M5 12h14"/><path d="m12 5 7 7-7 7"/>',
|
|
16345
17276
|
tool: '<path d="M14.7 6.3a1 1 0 0 0 0 1.4l1.6 1.6a1 1 0 0 0 1.4 0l3.77-3.77a6 6 0 0 1-7.94 7.94l-6.91 6.91a2.12 2.12 0 0 1-3-3l6.91-6.91a6 6 0 0 1 7.94-7.94l-3.76 3.76z"/>',
|
|
17277
|
+
upload: '<path d="M21 15v4a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2v-4"/><polyline points="17 8 12 3 7 8"/><line x1="12" x2="12" y1="3" y2="15"/>',
|
|
17278
|
+
repeat: '<path d="m17 2 4 4-4 4"/><path d="M3 11v-1a4 4 0 0 1 4-4h14"/><path d="m7 22-4-4 4-4"/><path d="M21 13v1a4 4 0 0 1-4 4H3"/>',
|
|
17279
|
+
listChecks: '<path d="m3 17 2 2 4-4"/><path d="m3 7 2 2 4-4"/><path d="M13 6h8"/><path d="M13 12h8"/><path d="M13 18h8"/>',
|
|
17280
|
+
activity: '<path d="M22 12h-4l-3 9L9 3l-3 9H2"/>',
|
|
17281
|
+
layoutDashboard:'<rect width="7" height="9" x="3" y="3" rx="1"/><rect width="7" height="5" x="14" y="3" rx="1"/><rect width="7" height="9" x="14" y="12" rx="1"/><rect width="7" height="5" x="3" y="16" rx="1"/>',
|
|
16346
17282
|
database: '<ellipse cx="12" cy="5" rx="9" ry="3"/><path d="M3 5v14a9 3 0 0 0 18 0V5"/><path d="M3 12a9 3 0 0 0 18 0"/>',
|
|
16347
17283
|
};
|
|
16348
17284
|
function lucide(name, cls) {
|
|
@@ -16366,7 +17302,7 @@ var ROUTE_REDIRECTS = {
|
|
|
16366
17302
|
'heartbeats': { page: 'heartbeat' },
|
|
16367
17303
|
'team-status': { page: 'team', tab: 'activity' },
|
|
16368
17304
|
'agent-detail': { page: 'team', tab: 'roster' },
|
|
16369
|
-
'intelligence': { page: 'brain', tab: '
|
|
17305
|
+
'intelligence': { page: 'brain', tab: 'overview' },
|
|
16370
17306
|
'memory-health': { page: 'brain', tab: 'health' },
|
|
16371
17307
|
'claims': { page: 'brain', tab: 'health' },
|
|
16372
17308
|
'metrics': { page: 'team', tab: 'activity' },
|
|
@@ -16439,9 +17375,10 @@ function navigateTo(page, opts) {
|
|
|
16439
17375
|
}
|
|
16440
17376
|
break;
|
|
16441
17377
|
case 'brain':
|
|
16442
|
-
var bt = opts.tab || '
|
|
17378
|
+
var bt = opts.tab || 'overview';
|
|
16443
17379
|
// Spec tab names → internal intelligence-tab ids
|
|
16444
|
-
var intelTab = bt === '
|
|
17380
|
+
var intelTab = bt === 'overview' ? 'overview'
|
|
17381
|
+
: bt === 'memory' ? 'search'
|
|
16445
17382
|
: bt === 'knowledge' ? 'graph'
|
|
16446
17383
|
: bt === 'ingestion' ? 'sources'
|
|
16447
17384
|
: bt === 'health' ? 'health'
|
|
@@ -16846,6 +17783,7 @@ function switchTab(group, tab) {
|
|
|
16846
17783
|
}
|
|
16847
17784
|
// Tab-specific refresh
|
|
16848
17785
|
if (group === 'intelligence') {
|
|
17786
|
+
if (tab === 'overview' && typeof refreshBrainOverview === 'function') refreshBrainOverview();
|
|
16849
17787
|
if (tab === 'graph') refreshGraph();
|
|
16850
17788
|
if (tab === 'search') {
|
|
16851
17789
|
// Consolidated Memory tab: search results + stats + MEMORY.md + recent writes + supersedes + coverage strip.
|
|
@@ -16855,6 +17793,12 @@ function switchTab(group, tab) {
|
|
|
16855
17793
|
if (typeof refreshCoverageStrip === 'function') refreshCoverageStrip();
|
|
16856
17794
|
}
|
|
16857
17795
|
if (tab === 'files' && typeof refreshVaultFiles === 'function') refreshVaultFiles();
|
|
17796
|
+
if (tab === 'sources') {
|
|
17797
|
+
if (typeof brainLoadSources === 'function') brainLoadSources();
|
|
17798
|
+
if (typeof brainLoadFeedConnectors === 'function') brainLoadFeedConnectors();
|
|
17799
|
+
if (typeof brainLoadFeeds === 'function') brainLoadFeeds();
|
|
17800
|
+
}
|
|
17801
|
+
if (tab === 'runs' && typeof brainLoadRuns === 'function') brainLoadRuns();
|
|
16858
17802
|
if (tab === 'health') {
|
|
16859
17803
|
if (typeof refreshMemoryHealth === 'function') refreshMemoryHealth();
|
|
16860
17804
|
if (typeof refreshGraphStats === 'function') refreshGraphStats();
|
|
@@ -22513,6 +23457,13 @@ function parseSearchFilters(raw) {
|
|
|
22513
23457
|
return { q: cleaned, filters: filters };
|
|
22514
23458
|
}
|
|
22515
23459
|
|
|
23460
|
+
function runMemoryDetailSearch() {
|
|
23461
|
+
var detail = document.getElementById('memory-detail-search-input');
|
|
23462
|
+
var header = document.getElementById('memory-search-input');
|
|
23463
|
+
if (header && detail) header.value = detail.value;
|
|
23464
|
+
runMemorySearch();
|
|
23465
|
+
}
|
|
23466
|
+
|
|
22516
23467
|
async function runMemorySearch() {
|
|
22517
23468
|
const input = document.getElementById('memory-search-input');
|
|
22518
23469
|
const raw = input.value.trim();
|