@enigmax/dashboard 0.1.107 → 0.1.109
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 +84 -24
- package/package.json +1 -1
package/assets/index.html
CHANGED
|
@@ -1339,7 +1339,7 @@
|
|
|
1339
1339
|
<div class="page-head" style="display:flex;align-items:flex-start;gap:16px">
|
|
1340
1340
|
<div style="flex:1">
|
|
1341
1341
|
<h1>Code graph</h1>
|
|
1342
|
-
<p>Structural memory of your codebase - a native knowledge graph of the symbols each file defines,
|
|
1342
|
+
<p>Structural memory of your codebase - a native knowledge graph of the symbols each file defines, what imports what, and which symbol calls which. Your agents query it over MCP to find code, trace a change's blast radius and read a file's API surface without opening it. Complements <a href="#/recall">Recall</a> (session memory).</p>
|
|
1343
1343
|
</div>
|
|
1344
1344
|
<button id="cgToggle" type="button" class="toggle" data-kind="bool" role="switch" aria-checked="false" aria-label="Code graph">Off</button>
|
|
1345
1345
|
<button id="cgRefresh" type="button" class="iconbtn" title="Refresh" aria-label="Refresh"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M23 4v6h-6"/><path d="M1 20v-6h6"/><path d="M3.51 9a9 9 0 0 1 14.85-3.36L23 10M1 14l4.64 4.36A9 9 0 0 0 20.49 15"/></svg></button>
|
|
@@ -1350,6 +1350,12 @@
|
|
|
1350
1350
|
<div class="set-hint" style="padding:4px 0">Build the graph for a codebase. Leave the path empty to index enigma's own working directory.</div>
|
|
1351
1351
|
<div class="ladd" style="gap:8px;flex-wrap:wrap"><input id="cgRoot" class="linput" placeholder="absolute project path (optional)" style="flex:1;min-width:240px"><button id="cgIndex" type="button" class="toggle on">Index</button></div>
|
|
1352
1352
|
</div>
|
|
1353
|
+
<div class="panel">
|
|
1354
|
+
<div class="set-cat-h">Ask the graph</div>
|
|
1355
|
+
<div class="set-hint" style="padding:4px 0">Describe a task and get the symbols and files it concerns, ranked, with exact line numbers. The same answer your agents get from the MCP tools.</div>
|
|
1356
|
+
<div class="ladd" style="gap:8px;flex-wrap:wrap"><input id="cgQuery" class="linput" placeholder="where is the session token refreshed?" style="flex:1;min-width:240px"><button id="cgAsk" type="button" class="toggle on">Ask</button></div>
|
|
1357
|
+
<div id="cgAskOut"></div>
|
|
1358
|
+
</div>
|
|
1353
1359
|
<div id="cgBody"><div class="empty" style="padding:12px 0">Loading...</div></div>
|
|
1354
1360
|
</section>
|
|
1355
1361
|
|
|
@@ -3574,7 +3580,8 @@ export {
|
|
|
3574
3580
|
+ s.choices.map((c) => '<option value="' + esc(c) + '"' + (c === s.choice ? " selected" : "") + ">" + esc(prettyChoice(c)) + "</option>").join("")
|
|
3575
3581
|
+ "</select>"
|
|
3576
3582
|
: '<button type="button" class="toggle' + (s.value ? " on" : "") + '" data-skey="' + esc(s.key)
|
|
3577
|
-
+ '" data-kind="bool"
|
|
3583
|
+
+ '" data-kind="bool" role="switch" aria-checked="' + (s.value ? "true" : "false")
|
|
3584
|
+
+ '" data-on="' + (s.value ? 1 : 0) + '">' + (s.value ? "On" : "Off") + "</button>";
|
|
3578
3585
|
return '<div class="set-row"><div class="set-meta"><div class="set-name">' + settingIcon(s.key) + labelWithBadges(s.label) + "</div>"
|
|
3579
3586
|
+ '<div class="set-hint">' + linkify(s.hint) + (s.globalOnly ? " · global" : "") + "</div></div>"
|
|
3580
3587
|
+ '<div class="set-ctl">' + ctl + "</div></div>";
|
|
@@ -3592,7 +3599,19 @@ export {
|
|
|
3592
3599
|
+ '<div class="set-grid">' + c.settings.map(settingRow).join("") + "</div></div>").join("");
|
|
3593
3600
|
}
|
|
3594
3601
|
|
|
3595
|
-
|
|
3602
|
+
// The one painter for every switch on this page: the class the CSS reads, the ARIA state a
|
|
3603
|
+
// screen reader reads, the data-on the click handlers read, and the label when the switch
|
|
3604
|
+
// carries one. Painted before the write lands and rolled back if the server refuses - every
|
|
3605
|
+
// switch here writes through something slow (re-rendering skills, rewriting each agent's
|
|
3606
|
+
// settings and hooks), so one that only moves once the write returns reads as dead.
|
|
3607
|
+
function paintSwitch(btn, on, onLabel, offLabel) {
|
|
3608
|
+
if (!btn) return;
|
|
3609
|
+
btn.classList.toggle("on", !!on);
|
|
3610
|
+
btn.setAttribute("aria-checked", on ? "true" : "false");
|
|
3611
|
+
btn.dataset.on = on ? "1" : "0";
|
|
3612
|
+
if (onLabel != null) btn.textContent = on ? onLabel : offLabel;
|
|
3613
|
+
}
|
|
3614
|
+
function applyBool(ctl, on) { paintSwitch(ctl, on, "On", "Off"); }
|
|
3596
3615
|
|
|
3597
3616
|
// One place that turns an /api/* call into parsed JSON, or an Error naming the real
|
|
3598
3617
|
// cause. res.json() throws on any reply that is not JSON - an empty 405, or an HTML
|
|
@@ -4555,12 +4574,7 @@ export {
|
|
|
4555
4574
|
}
|
|
4556
4575
|
// The two on/off switches at the top of the page. Both are optimistic: the switch flips
|
|
4557
4576
|
// first and rolls back if the server refuses, like every other control here.
|
|
4558
|
-
function gateBool(btn, on) {
|
|
4559
|
-
btn.textContent = on ? "on" : "off";
|
|
4560
|
-
btn.classList.toggle("on", !!on);
|
|
4561
|
-
btn.dataset.on = on ? "1" : "0";
|
|
4562
|
-
btn.setAttribute("aria-checked", on ? "true" : "false");
|
|
4563
|
-
}
|
|
4577
|
+
function gateBool(btn, on) { paintSwitch(btn, on, "on", "off"); }
|
|
4564
4578
|
// Defaults for every gate setting, straight from the gate's own config loader (the
|
|
4565
4579
|
// server resolves an empty config rather than keeping a second copy of the numbers).
|
|
4566
4580
|
// They drive the "default N" hint and the per-row reset below.
|
|
@@ -5567,8 +5581,7 @@ export {
|
|
|
5567
5581
|
function renderCodeGraph(view) {
|
|
5568
5582
|
view = view || {};
|
|
5569
5583
|
const on = !!view.enabled;
|
|
5570
|
-
|
|
5571
|
-
t.classList.toggle("on", on); t.setAttribute("aria-checked", on ? "true" : "false"); t.textContent = on ? "On" : "Off";
|
|
5584
|
+
paintSwitch($("cgToggle"), on, "On", "Off");
|
|
5572
5585
|
const projects = view.projects || [];
|
|
5573
5586
|
const projHtml = projects.length
|
|
5574
5587
|
? projects.map((p) => {
|
|
@@ -5580,9 +5593,25 @@ export {
|
|
|
5580
5593
|
}).join("")
|
|
5581
5594
|
: '<div class="empty" style="padding:12px 0">No projects indexed yet. Use "Index a project" above.</div>';
|
|
5582
5595
|
let html = '<div class="panel"><div class="set-cat-h">Indexed projects <small>' + projects.length + "</small></div>" + projHtml + "</div>";
|
|
5596
|
+
// Drift is stated, not hidden: a graph that has fallen behind the code answers
|
|
5597
|
+
// confidently and wrongly, and the only cue would otherwise be a stale line number.
|
|
5598
|
+
const fresh = view.freshness;
|
|
5599
|
+
if (fresh) {
|
|
5600
|
+
const stale = fresh.stale || 0;
|
|
5601
|
+
html += '<div class="panel"><div class="set-cat-h">Freshness</div><div class="set-row"><div class="set-meta"><div class="set-name">'
|
|
5602
|
+
+ (stale === 0 ? "Up to date" : stale + " file" + (stale === 1 ? "" : "s") + " changed since indexing")
|
|
5603
|
+
+ '</div><div class="set-hint">' + esc("indexed " + new Date(fresh.indexedAt).toLocaleString()
|
|
5604
|
+
+ (fresh.truncated ? " · incomplete: the scan hit a limit, so some files were never indexed" : "")) + "</div></div>"
|
|
5605
|
+
+ (stale === 0 ? "" : '<div class="set-ctl"><button type="button" class="toggle on" data-cg-reindex="' + esc(fresh.root) + '">Re-index</button></div>')
|
|
5606
|
+
+ "</div></div>";
|
|
5607
|
+
}
|
|
5583
5608
|
if (view.architecture) html += cgSection("Architecture" + (view.selected ? " - " + view.selected : ""), view.architecture);
|
|
5584
5609
|
if (view.schema) html += cgSection("Graph schema", view.schema);
|
|
5585
5610
|
$("cgBody").innerHTML = html;
|
|
5611
|
+
const ask = view.ask;
|
|
5612
|
+
$("cgAskOut").innerHTML = ask
|
|
5613
|
+
? '<pre style="white-space:pre-wrap;overflow-x:auto;margin:10px 0 0;font-size:12px">' + esc(ask.report) + "</pre>"
|
|
5614
|
+
: "";
|
|
5586
5615
|
}
|
|
5587
5616
|
async function loadCodeGraph() {
|
|
5588
5617
|
$("cgNote").textContent = "";
|
|
@@ -5600,20 +5629,44 @@ export {
|
|
|
5600
5629
|
} catch { setNote("cgNote", "Could not reach the server.", "err"); return null; }
|
|
5601
5630
|
}
|
|
5602
5631
|
async function cgToggleAction() {
|
|
5603
|
-
const
|
|
5604
|
-
const
|
|
5605
|
-
|
|
5632
|
+
const btn = $("cgToggle");
|
|
5633
|
+
const was = btn.getAttribute("aria-checked") === "true";
|
|
5634
|
+
paintSwitch(btn, !was, "On", "Off");
|
|
5635
|
+
const out = await cgPost({ op: "toggle", on: !was }, was ? "Disabling..." : "Enabling...");
|
|
5636
|
+
if (!out || !out.ok) {
|
|
5637
|
+
paintSwitch(btn, was, "On", "Off");
|
|
5638
|
+
if (out) setNote("cgNote", out.error || "Action failed.", "err"); // cgPost already reported an unreachable server
|
|
5639
|
+
return;
|
|
5640
|
+
}
|
|
5641
|
+
paintSwitch(btn, !!out.enabled, "On", "Off"); // the server's answer wins over the optimistic paint
|
|
5642
|
+
setNote("cgNote", out.enabled
|
|
5643
|
+
? "Code graph enabled - tools and session hooks wired; agents pick it up on their next session."
|
|
5644
|
+
: "Code graph disabled - tools and session hooks removed.", "ok");
|
|
5606
5645
|
}
|
|
5607
5646
|
async function cgIndexAction() {
|
|
5608
5647
|
const root = $("cgRoot").value.trim();
|
|
5609
5648
|
const out = await cgPost({ op: "index", root: root || undefined }, "Indexing...");
|
|
5610
5649
|
if (out) { if (out.view && out.view.selected) cgProject = out.view.selected; setNote("cgNote", out.ok ? "Indexed." : (out.error || "Indexing failed."), out.ok ? "ok" : "err"); }
|
|
5611
5650
|
}
|
|
5651
|
+
async function cgAskAction() {
|
|
5652
|
+
const query = $("cgQuery").value.trim();
|
|
5653
|
+
if (!query) { setNote("cgNote", "Type a question first.", "err"); return; }
|
|
5654
|
+
const out = await cgPost({ op: "ask", query, project: cgProject || undefined }, "Searching...");
|
|
5655
|
+
if (out) setNote("cgNote", out.ok ? "" : (out.error || "Query failed."), out.ok ? "ok" : "err");
|
|
5656
|
+
}
|
|
5612
5657
|
function wireCodeGraph() {
|
|
5613
5658
|
$("cgRefresh").addEventListener("click", loadCodeGraph);
|
|
5614
5659
|
$("cgToggle").addEventListener("click", cgToggleAction);
|
|
5615
5660
|
$("cgIndex").addEventListener("click", cgIndexAction);
|
|
5661
|
+
$("cgAsk").addEventListener("click", cgAskAction);
|
|
5662
|
+
$("cgQuery").addEventListener("keydown", (e) => { if (e.key === "Enter") cgAskAction(); });
|
|
5616
5663
|
$("view-codegraph").addEventListener("click", (e) => {
|
|
5664
|
+
const reindex = e.target.closest("button[data-cg-reindex]");
|
|
5665
|
+
if (reindex) {
|
|
5666
|
+
cgPost({ op: "index", root: reindex.dataset.cgReindex }, "Re-indexing...")
|
|
5667
|
+
.then((out) => { if (out) setNote("cgNote", out.ok ? "Re-indexed." : (out.error || "Indexing failed."), out.ok ? "ok" : "err"); });
|
|
5668
|
+
return;
|
|
5669
|
+
}
|
|
5617
5670
|
const b = e.target.closest("button[data-cg-project]");
|
|
5618
5671
|
if (!b) return;
|
|
5619
5672
|
cgProject = b.dataset.cgProject;
|
|
@@ -5730,7 +5783,7 @@ export {
|
|
|
5730
5783
|
$("pgSend").addEventListener("click", pgSend);
|
|
5731
5784
|
$("pgSaveDefault").addEventListener("click", pgSaveDefault);
|
|
5732
5785
|
const tog = $("pgTools");
|
|
5733
|
-
tog.addEventListener("click", () =>
|
|
5786
|
+
tog.addEventListener("click", () => paintSwitch(tog, tog.getAttribute("aria-checked") !== "true", "On", "Off"));
|
|
5734
5787
|
$("pgMode").addEventListener("change", pgSyncMode);
|
|
5735
5788
|
const copyBtn = $("pgCopyCurl");
|
|
5736
5789
|
// The button is an icon, so the toast is not the only confirmation: swap the icon for a
|
|
@@ -5823,11 +5876,7 @@ export {
|
|
|
5823
5876
|
$("recallKey").disabled = !!p.keyFromEnv;
|
|
5824
5877
|
// claude-local needs no base/key; hide that row for it.
|
|
5825
5878
|
$("recallKeyRow").style.display = p.provider === "claude-local" ? "none" : "";
|
|
5826
|
-
|
|
5827
|
-
llmBtn.textContent = p.llm ? "on" : "off";
|
|
5828
|
-
llmBtn.classList.toggle("on", !!p.llm);
|
|
5829
|
-
llmBtn.dataset.on = p.llm ? "1" : "0";
|
|
5830
|
-
llmBtn.setAttribute("aria-checked", p.llm ? "true" : "false");
|
|
5879
|
+
paintSwitch($("recallLlmBtn"), p.llm, "on", "off");
|
|
5831
5880
|
}
|
|
5832
5881
|
let recallItemsById = {};
|
|
5833
5882
|
// Multi-select state for the Recall list: a Set of selected ids, the ordered ids as
|
|
@@ -5922,14 +5971,25 @@ export {
|
|
|
5922
5971
|
setNote("recallNote", out.ok ? (op === "sync" ? "Synced." : "Cleared.") : (out.error || "Action failed."), out.ok ? "ok" : "err");
|
|
5923
5972
|
} catch { setNote("recallNote", "Could not reach the server.", "err"); }
|
|
5924
5973
|
}
|
|
5925
|
-
|
|
5974
|
+
// The recall switch carries its state in its own label ("Enable recall" when off, "Disable"
|
|
5975
|
+
// when on), in its title and in what the next click asks for - so an optimistic paint has to
|
|
5976
|
+
// move all three. Painting the switch alone left a button reading "Enable recall" while
|
|
5977
|
+
// already on, offering to turn on what it had just turned on, until loadRecall() re-rendered.
|
|
5978
|
+
function paintRecallSwitch(btn, on) {
|
|
5979
|
+
paintSwitch(btn, on, "Disable", "Enable recall");
|
|
5980
|
+
if (!btn) return;
|
|
5981
|
+
btn.dataset.recallOn = on ? "0" : "1";
|
|
5982
|
+
btn.title = on ? "Turn session memory off" : "Turn session memory on";
|
|
5983
|
+
}
|
|
5984
|
+
async function setRecallEnabled(on, btn) {
|
|
5985
|
+
paintRecallSwitch(btn, on);
|
|
5926
5986
|
setNote("recallNote", on ? "Enabling recall..." : "Disabling recall...");
|
|
5927
5987
|
try {
|
|
5928
5988
|
const out = await (await fetch("/api/settings", { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify({ key: "recall", value: on }) })).json();
|
|
5929
|
-
if (!out.ok) { setNote("recallNote", "Could not change recall: " + (out.error || "error"), "err"); return; }
|
|
5989
|
+
if (!out.ok) { paintRecallSwitch(btn, !on); setNote("recallNote", "Could not change recall: " + (out.error || "error"), "err"); return; }
|
|
5930
5990
|
setNote("recallNote", out.restartNote || (on ? "Recall enabled." : "Recall disabled."), out.restartNote ? "warn" : "ok");
|
|
5931
5991
|
loadRecall();
|
|
5932
|
-
} catch { setNote("recallNote", "Could not reach the server.", "err"); }
|
|
5992
|
+
} catch { paintRecallSwitch(btn, !on); setNote("recallNote", "Could not reach the server.", "err"); }
|
|
5933
5993
|
}
|
|
5934
5994
|
async function recallProviderPost(extra, msg) {
|
|
5935
5995
|
setNote("recallNote", msg || "Saving provider...");
|
|
@@ -5954,7 +6014,7 @@ export {
|
|
|
5954
6014
|
$("recallClear").addEventListener("click", () => recallAction("clear", "Delete all stored session memory? This cannot be undone."));
|
|
5955
6015
|
$("recallBody").addEventListener("click", (e) => {
|
|
5956
6016
|
const t = e.target.closest("[data-recall-on]");
|
|
5957
|
-
if (t) setRecallEnabled(t.dataset.recallOn === "1");
|
|
6017
|
+
if (t) setRecallEnabled(t.dataset.recallOn === "1", t);
|
|
5958
6018
|
});
|
|
5959
6019
|
// Provider panel: save provider; flip LLM curation. The provider picker is the
|
|
5960
6020
|
// custom icon dropdown (recallProvVal), wired in renderRecallProvider.
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@enigmax/dashboard",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.109",
|
|
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": [
|