@enigmax/dashboard 0.1.56 → 0.1.58
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/assets/index.html +135 -1
- package/package.json +1 -1
package/assets/index.html
CHANGED
|
@@ -426,6 +426,7 @@
|
|
|
426
426
|
<a href="#/skills" data-view="skills" class="tab">Skills</a>
|
|
427
427
|
<a href="#/settings" data-view="settings" class="tab">Settings</a>
|
|
428
428
|
<a href="#/resources" data-view="resources" class="tab">Resources</a>
|
|
429
|
+
<a href="#/recall" data-view="recall" class="tab">Recall</a>
|
|
429
430
|
</nav>
|
|
430
431
|
<div class="nav">
|
|
431
432
|
<label class="scope-wrap" title="Scope: global config or a specific project"><span class="scope-lbl">Scope</span><select id="scopeSel" class="scope-sel"><option value="global">Global</option></select></label>
|
|
@@ -696,6 +697,28 @@
|
|
|
696
697
|
<div class="panel"><div class="set-cat-h">Processes <small>by memory</small></div><div id="resProcs"><div class="empty" style="padding:12px 0">Loading...</div></div></div>
|
|
697
698
|
</section>
|
|
698
699
|
|
|
700
|
+
<section id="view-recall" style="display:none">
|
|
701
|
+
<div class="page-head" style="display:flex;align-items:flex-start;gap:16px">
|
|
702
|
+
<div style="flex:1">
|
|
703
|
+
<h1>Recall</h1>
|
|
704
|
+
<p>Searchable memory of your past coding sessions, built locally from your own transcripts. Agents can query it over MCP (<code>enigma_recall</code>); it never leaves this machine.</p>
|
|
705
|
+
</div>
|
|
706
|
+
<button id="recallSync" type="button" class="toggle on" title="Read new transcripts into the store">Sync now</button>
|
|
707
|
+
<button id="recallClear" type="button" class="btn-danger" title="Delete all stored memory">Clear</button>
|
|
708
|
+
</div>
|
|
709
|
+
<div id="recallNote" class="set-note"></div>
|
|
710
|
+
<div id="recallBody"><div class="empty" style="padding:12px 0">Loading...</div></div>
|
|
711
|
+
<div id="recallSearch" class="panel" style="display:none">
|
|
712
|
+
<div class="set-cat-h">Search</div>
|
|
713
|
+
<div class="ladd" style="gap:8px;flex-wrap:wrap">
|
|
714
|
+
<input id="recallQ" class="linput" type="search" placeholder="Search your session memory..." style="flex:1;min-width:220px">
|
|
715
|
+
<select id="recallType" class="linput" style="max-width:160px"></select>
|
|
716
|
+
<select id="recallProject" class="linput" style="max-width:200px"></select>
|
|
717
|
+
</div>
|
|
718
|
+
</div>
|
|
719
|
+
<div id="recallList"></div>
|
|
720
|
+
</section>
|
|
721
|
+
|
|
699
722
|
<footer>
|
|
700
723
|
Polling pauses automatically when this tab is hidden, and the server caches each snapshot - so an open dashboard costs almost nothing.
|
|
701
724
|
Data source: <code>~/.enigma/ccr/stats.json</code> + <code>history.jsonl</code> - reset it with <code>enigma compress --clear</code>. Enigma <span id="ver"></span>
|
|
@@ -3237,7 +3260,7 @@ export {
|
|
|
3237
3260
|
|
|
3238
3261
|
// --- hash routing between the subpages ---
|
|
3239
3262
|
// Single source of truth for the subpage ids (savings is the default/fallback).
|
|
3240
|
-
const VIEWS = ["savings", "usage", "accounts", "skills", "settings", "resources"];
|
|
3263
|
+
const VIEWS = ["savings", "usage", "accounts", "skills", "settings", "resources", "recall"];
|
|
3241
3264
|
// Views that apply to a single project; the rest are global/machine-level only.
|
|
3242
3265
|
const PROJECT_VIEWS = ["settings", "skills"];
|
|
3243
3266
|
let activeView = "savings";
|
|
@@ -3257,6 +3280,7 @@ export {
|
|
|
3257
3280
|
else if (v === "accounts") { if (!accountsLoaded) { accountsLoaded = true; loadAccounts(); } }
|
|
3258
3281
|
else if (v === "usage") { loadProviderStatus(); if (!usageAccountsLoaded) { usageAccountsLoaded = true; loadUsageAccounts(); } }
|
|
3259
3282
|
else if (v === "resources") loadResources();
|
|
3283
|
+
else if (v === "recall") loadRecall();
|
|
3260
3284
|
// The chart was sized while its view may have been hidden; nudge it on return.
|
|
3261
3285
|
else if (v === "savings") { loadSystems(true); if (chart) { try { applyRange(); } catch { /* not ready */ } } }
|
|
3262
3286
|
}
|
|
@@ -3316,6 +3340,115 @@ export {
|
|
|
3316
3340
|
});
|
|
3317
3341
|
}
|
|
3318
3342
|
|
|
3343
|
+
// --- recall subpage (local session memory; reads your own transcripts) ---
|
|
3344
|
+
const RECALL_TYPES = ["bugfix", "feature", "refactor", "change", "discovery", "decision", "security"];
|
|
3345
|
+
let recallProjSig = "";
|
|
3346
|
+
function renderRecall(v) {
|
|
3347
|
+
v = v || {};
|
|
3348
|
+
if (!v.available) {
|
|
3349
|
+
$("recallBody").innerHTML = '<div class="panel"><div class="empty" style="padding:16px 0">Recall needs the enigma binary - open the dashboard via the enigma app.</div></div>';
|
|
3350
|
+
$("recallSearch").style.display = "none"; $("recallList").innerHTML = ""; return;
|
|
3351
|
+
}
|
|
3352
|
+
if (!v.enabled) {
|
|
3353
|
+
$("recallBody").innerHTML = '<div class="panel"><div class="set-cat-h">Session memory is off</div>'
|
|
3354
|
+
+ '<div class="set-hint" style="padding:8px 0">Build a searchable memory of your coding sessions from your own transcripts. Turn it on with:</div>'
|
|
3355
|
+
+ '<code>enigma config recall on</code></div>';
|
|
3356
|
+
$("recallSearch").style.display = "none"; $("recallList").innerHTML = ""; return;
|
|
3357
|
+
}
|
|
3358
|
+
const s = v.stats || {};
|
|
3359
|
+
const types = Object.entries(s.byType || {}).map(([t, n]) => '<span class="tag">' + esc(t) + " " + n + "</span>").join(" ");
|
|
3360
|
+
const last = v.lastSync ? "last sync " + new Date(v.lastSync).toLocaleString() : "not synced yet";
|
|
3361
|
+
$("recallBody").innerHTML = '<div class="panel"><div class="set-cat-h">Stored memory</div>'
|
|
3362
|
+
+ '<div class="set-hint" style="padding:6px 0">' + (s.observations || 0) + " observations · " + (s.sessions || 0) + " sessions · "
|
|
3363
|
+
+ (s.projects || 0) + " projects · " + Math.round((s.dbBytes || 0) / 1024) + " KB · " + esc(last) + "</div>"
|
|
3364
|
+
+ (types ? '<div style="padding-top:6px">' + types + "</div>" : "") + "</div>";
|
|
3365
|
+
$("recallSearch").style.display = "";
|
|
3366
|
+
if (!$("recallType").options.length) $("recallType").innerHTML = '<option value="">All types</option>' + RECALL_TYPES.map((t) => '<option value="' + t + '">' + t + "</option>").join("");
|
|
3367
|
+
const sig = (v.projects || []).join("|");
|
|
3368
|
+
if (sig !== recallProjSig) {
|
|
3369
|
+
const keep = $("recallProject").value;
|
|
3370
|
+
$("recallProject").innerHTML = '<option value="">All projects</option>' + (v.projects || []).map((p) => '<option value="' + esc(p) + '">' + esc(p) + "</option>").join("");
|
|
3371
|
+
$("recallProject").value = keep;
|
|
3372
|
+
recallProjSig = sig;
|
|
3373
|
+
}
|
|
3374
|
+
renderRecallList(v.items);
|
|
3375
|
+
}
|
|
3376
|
+
let recallItemsById = {};
|
|
3377
|
+
function recallRowHtml(it, anchorId) {
|
|
3378
|
+
const ic = '<span class="aicon" title="' + esc(it.source) + '" aria-label="' + esc(it.source) + '">' + (agentSvg(it.source) || esc(it.source)) + "</span>";
|
|
3379
|
+
const date = it.createdAt ? new Date(it.createdAt).toISOString().slice(0, 10) : "";
|
|
3380
|
+
const mark = (anchorId && it.id === anchorId) ? ' style="border-left:2px solid var(--accent,#888);padding-left:8px"' : "";
|
|
3381
|
+
return '<div class="set-meta"' + mark + '><div class="set-name">' + ic + ' <span class="tag">' + esc(it.type) + "</span> " + esc(it.title) + "</div>"
|
|
3382
|
+
+ '<div class="set-hint" style="opacity:.6">' + esc(it.project) + " · " + date + (it.id ? " · #" + it.id : "") + "</div></div>";
|
|
3383
|
+
}
|
|
3384
|
+
function recallDetailHtml(it) {
|
|
3385
|
+
const tags = (a) => (a && a.length) ? a.map((x) => '<span class="tag">' + esc(x) + "</span>").join(" ") : "";
|
|
3386
|
+
const list = (label, a) => (a && a.length) ? '<div class="set-hint"><strong>' + label + ":</strong> " + esc(a.join(", ")) + "</div>" : "";
|
|
3387
|
+
const facts = (it.facts && it.facts.length) ? "<ul style='margin:4px 0 0 16px'>" + it.facts.map((f) => "<li>" + esc(f) + "</li>").join("") + "</ul>" : "";
|
|
3388
|
+
return '<div style="padding:8px 0 4px 28px">'
|
|
3389
|
+
+ (it.narrative ? '<div style="margin-bottom:6px">' + esc(it.narrative) + "</div>" : "")
|
|
3390
|
+
+ facts
|
|
3391
|
+
+ (it.concepts && it.concepts.length ? '<div style="margin-top:6px">' + tags(it.concepts) + "</div>" : "")
|
|
3392
|
+
+ list("Modified", it.files) + list("Read", it.filesRead)
|
|
3393
|
+
+ '<div style="margin-top:8px"><button type="button" class="toggle" data-tl="' + it.id + '">Show timeline</button></div>'
|
|
3394
|
+
+ '<div class="rtl" id="rtl-' + it.id + '"></div></div>';
|
|
3395
|
+
}
|
|
3396
|
+
function renderRecallList(items) {
|
|
3397
|
+
recallItemsById = {};
|
|
3398
|
+
if (!items || !items.length) { $("recallList").innerHTML = '<div class="empty" style="padding:16px 0">No memories yet - run a sync, or adjust your search.</div>'; return; }
|
|
3399
|
+
$("recallList").innerHTML = '<div class="panel">' + items.map((it) => {
|
|
3400
|
+
if (it.id) recallItemsById[it.id] = it;
|
|
3401
|
+
return '<div class="set-row" style="cursor:pointer" data-rid="' + (it.id || "") + '">' + recallRowHtml(it) + "</div>"
|
|
3402
|
+
+ '<div id="rd-' + (it.id || "") + '" style="display:none"></div>';
|
|
3403
|
+
}).join("") + "</div>";
|
|
3404
|
+
}
|
|
3405
|
+
async function loadRecallTimeline(id) {
|
|
3406
|
+
const el = $("rtl-" + id);
|
|
3407
|
+
if (!el) return;
|
|
3408
|
+
el.innerHTML = '<div class="set-hint" style="padding:6px 0">Loading timeline...</div>';
|
|
3409
|
+
try {
|
|
3410
|
+
const out = await (await fetch("/api/recall?timeline=" + id, { cache: "no-store" })).json();
|
|
3411
|
+
const rows = out.items || [];
|
|
3412
|
+
el.innerHTML = rows.length
|
|
3413
|
+
? '<div style="margin-top:6px;border-top:1px solid rgba(128,128,128,.2)">' + rows.map((r) => '<div class="set-row">' + recallRowHtml(r, Number(id)) + "</div>").join("") + "</div>"
|
|
3414
|
+
: '<div class="set-hint">No surrounding context.</div>';
|
|
3415
|
+
} catch { el.innerHTML = '<div class="set-hint err">Could not load timeline.</div>'; }
|
|
3416
|
+
}
|
|
3417
|
+
async function loadRecall() {
|
|
3418
|
+
const q = encodeURIComponent($("recallQ").value || "");
|
|
3419
|
+
const type = encodeURIComponent($("recallType").value || "");
|
|
3420
|
+
const proj = encodeURIComponent($("recallProject").value || "");
|
|
3421
|
+
try { renderRecall(await (await fetch("/api/recall?q=" + q + "&type=" + type + "&project=" + proj, { cache: "no-store" })).json()); }
|
|
3422
|
+
catch { $("recallBody").innerHTML = '<div class="empty" style="padding:12px 0">Recall unavailable.</div>'; }
|
|
3423
|
+
}
|
|
3424
|
+
async function recallAction(op, confirmMsg) {
|
|
3425
|
+
if (confirmMsg && !(await askConfirm("Confirm", confirmMsg, true))) return;
|
|
3426
|
+
setNote("recallNote", op === "sync" ? "Reading transcripts..." : "Working...");
|
|
3427
|
+
try {
|
|
3428
|
+
const out = await (await fetch("/api/recall", { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify({ op }) })).json();
|
|
3429
|
+
if (out.view) renderRecall(out.view);
|
|
3430
|
+
setNote("recallNote", out.ok ? (op === "sync" ? "Synced." : "Cleared.") : (out.error || "Action failed."), out.ok ? "ok" : "err");
|
|
3431
|
+
} catch { setNote("recallNote", "Could not reach the server.", "err"); }
|
|
3432
|
+
}
|
|
3433
|
+
function wireRecall() {
|
|
3434
|
+
$("recallSync").addEventListener("click", () => recallAction("sync"));
|
|
3435
|
+
$("recallClear").addEventListener("click", () => recallAction("clear", "Delete all stored session memory? This cannot be undone."));
|
|
3436
|
+
let t; const deb = () => { clearTimeout(t); t = setTimeout(loadRecall, 250); };
|
|
3437
|
+
$("recallQ").addEventListener("input", deb);
|
|
3438
|
+
$("recallType").addEventListener("change", loadRecall);
|
|
3439
|
+
$("recallProject").addEventListener("change", loadRecall);
|
|
3440
|
+
$("recallList").addEventListener("click", (e) => {
|
|
3441
|
+
const tl = e.target.closest("[data-tl]");
|
|
3442
|
+
if (tl) { loadRecallTimeline(tl.dataset.tl); return; }
|
|
3443
|
+
const row = e.target.closest("[data-rid]");
|
|
3444
|
+
if (!row || !row.dataset.rid) return;
|
|
3445
|
+
const detail = $("rd-" + row.dataset.rid);
|
|
3446
|
+
if (!detail) return;
|
|
3447
|
+
if (detail.innerHTML === "") detail.innerHTML = recallDetailHtml(recallItemsById[row.dataset.rid]);
|
|
3448
|
+
detail.style.display = detail.style.display === "none" ? "" : "none";
|
|
3449
|
+
});
|
|
3450
|
+
}
|
|
3451
|
+
|
|
3319
3452
|
// --- scope (global vs a project) ---------------------------------------------
|
|
3320
3453
|
// A header selector switches the whole dashboard between Global and a specific
|
|
3321
3454
|
// project. Project scope is loopback-only and only ever touches a registered
|
|
@@ -3607,6 +3740,7 @@ export {
|
|
|
3607
3740
|
wireAccounts();
|
|
3608
3741
|
wireUsage();
|
|
3609
3742
|
wireResources();
|
|
3743
|
+
wireRecall();
|
|
3610
3744
|
wireScope();
|
|
3611
3745
|
$("cfgExport").addEventListener("click", exportConfig);
|
|
3612
3746
|
$("cfgImport").addEventListener("click", importConfig);
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@enigmax/dashboard",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.58",
|
|
4
4
|
"description": "Local browser dashboard UI for enigma: the static page and chart assets enigma serves on its loopback dashboard (savings, real tool usage, in-browser settings). Installed on demand by enigma-cli; not a runtime dependency.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"files": [
|