@enigmax/dashboard 0.1.59 → 0.1.61

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.
Files changed (2) hide show
  1. package/assets/index.html +129 -4
  2. package/package.json +1 -1
package/assets/index.html CHANGED
@@ -708,6 +708,23 @@
708
708
  </div>
709
709
  <div id="recallNote" class="set-note"></div>
710
710
  <div id="recallBody"><div class="empty" style="padding:12px 0">Loading...</div></div>
711
+ <div id="recallProviderPanel" class="panel" style="display:none">
712
+ <div class="set-cat-h">Memory provider</div>
713
+ <div class="set-hint" style="padding:4px 0">Which model curates your memory - it keeps only what's worth remembering. The default reuses your local Claude Code login (no key). Or pick Anthropic or an OpenAI-compatible API and add its key. The key is stored encrypted, and <code>ENIGMA_RECALL_API_KEY</code> overrides it.</div>
714
+ <div class="set-row"><div class="set-meta"><div class="set-name">LLM curation</div><div class="set-hint">Off falls back to a local heuristic - no network calls</div></div><div class="set-ctl"><button id="recallLlmBtn" type="button" class="toggle" data-kind="bool" role="switch" aria-checked="false" aria-label="LLM curation">off</button></div></div>
715
+ <div class="ladd" style="gap:8px;flex-wrap:wrap;margin-top:8px">
716
+ <div class="dd" id="recallProvDD">
717
+ <button type="button" class="dd-btn" aria-haspopup="listbox" aria-expanded="false"><span class="dd-ic"></span><span class="dd-label"></span><svg class="dd-caret" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><polyline points="6 9 12 15 18 9"></polyline></svg></button>
718
+ <ul class="dd-menu" role="listbox" aria-label="Memory provider" hidden></ul>
719
+ </div>
720
+ <input id="recallModel" class="linput" placeholder="model (provider default)" style="flex:1;min-width:160px">
721
+ </div>
722
+ <div id="recallKeyRow" class="ladd" style="gap:8px;flex-wrap:wrap;margin-top:8px">
723
+ <input id="recallBase" class="linput" placeholder="API base URL (provider default)" style="flex:1;min-width:200px">
724
+ <input id="recallKey" class="linput" type="password" placeholder="API key" style="flex:1;min-width:160px">
725
+ </div>
726
+ <div style="margin-top:8px"><button id="recallProvSave" type="button" class="toggle on">Save provider</button></div>
727
+ </div>
711
728
  <div id="recallSearch" class="panel" style="display:none">
712
729
  <div class="set-cat-h">Search</div>
713
730
  <div class="ladd" style="gap:8px;flex-wrap:wrap">
@@ -716,6 +733,24 @@
716
733
  <select id="recallProject" class="linput" style="max-width:200px"></select>
717
734
  </div>
718
735
  </div>
736
+ <div id="recallAddPanel" class="panel" style="display:none">
737
+ <div class="set-cat-h">Add a memory</div>
738
+ <div class="set-hint" style="padding:4px 0">Write one yourself, or generate it from a short note with the configured model.</div>
739
+ <div class="ladd" style="gap:8px;flex-wrap:wrap">
740
+ <select id="recallNewType" class="linput" style="max-width:150px"></select>
741
+ <input id="recallNewTitle" class="linput" placeholder="Title" style="flex:1;min-width:200px">
742
+ <input id="recallNewProject" class="linput" placeholder="Project (optional)" style="flex:1;min-width:140px">
743
+ </div>
744
+ <div class="ladd" style="gap:8px;flex-wrap:wrap;margin-top:8px">
745
+ <input id="recallNewNarrative" class="linput" placeholder="What's worth remembering (optional)" style="flex:1;min-width:240px">
746
+ <input id="recallNewFacts" class="linput" placeholder="Facts, comma-separated (optional)" style="flex:1;min-width:200px">
747
+ </div>
748
+ <div style="margin-top:8px"><button id="recallCreate" type="button" class="toggle on">Save memory</button></div>
749
+ <div class="ladd" style="gap:8px;flex-wrap:wrap;margin-top:12px;border-top:1px solid rgba(128,128,128,.2);padding-top:12px">
750
+ <input id="recallGenNote" class="linput" placeholder="Note to generate a memory from..." style="flex:1;min-width:260px">
751
+ <button id="recallGenerate" type="button" class="toggle" title="Generate a structured memory from your note">Generate with LLM</button>
752
+ </div>
753
+ </div>
719
754
  <div id="recallList"></div>
720
755
  </section>
721
756
 
@@ -3347,14 +3382,14 @@ export {
3347
3382
  v = v || {};
3348
3383
  if (!v.available) {
3349
3384
  $("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;
3385
+ $("recallSearch").style.display = "none"; $("recallProviderPanel").style.display = "none"; $("recallAddPanel").style.display = "none"; $("recallList").innerHTML = ""; return;
3351
3386
  }
3352
3387
  if (!v.enabled) {
3353
3388
  $("recallBody").innerHTML = '<div class="panel"><div class="set-cat-h">Session memory is off</div>'
3354
3389
  + '<div class="set-hint" style="padding:8px 0">Build a searchable memory of your coding sessions from your own transcripts.</div>'
3355
3390
  + '<div style="display:flex;align-items:center;gap:10px;flex-wrap:wrap"><button type="button" class="toggle" data-recall-on="1">Enable recall</button>'
3356
3391
  + '<span class="set-hint">or run <code>enigma config recall on</code></span></div></div>';
3357
- $("recallSearch").style.display = "none"; $("recallList").innerHTML = ""; return;
3392
+ $("recallSearch").style.display = "none"; $("recallProviderPanel").style.display = "none"; $("recallAddPanel").style.display = "none"; $("recallList").innerHTML = ""; return;
3358
3393
  }
3359
3394
  const s = v.stats || {};
3360
3395
  const types = Object.entries(s.byType || {}).map(([t, n]) => '<span class="tag">' + esc(t) + " " + n + "</span>").join(" ");
@@ -3365,7 +3400,9 @@ export {
3365
3400
  + (s.projects || 0) + " projects · " + Math.round((s.dbBytes || 0) / 1024) + " KB · " + esc(last) + "</div>"
3366
3401
  + (types ? '<div style="padding-top:6px">' + types + "</div>" : "") + "</div>";
3367
3402
  $("recallSearch").style.display = "";
3403
+ $("recallAddPanel").style.display = "";
3368
3404
  if (!$("recallType").options.length) $("recallType").innerHTML = '<option value="">All types</option>' + RECALL_TYPES.map((t) => '<option value="' + t + '">' + t + "</option>").join("");
3405
+ if (!$("recallNewType").options.length) $("recallNewType").innerHTML = RECALL_TYPES.map((t) => '<option value="' + t + '">' + t + "</option>").join("");
3369
3406
  const sig = (v.projects || []).join("|");
3370
3407
  if (sig !== recallProjSig) {
3371
3408
  const keep = $("recallProject").value;
@@ -3373,8 +3410,45 @@ export {
3373
3410
  $("recallProject").value = keep;
3374
3411
  recallProjSig = sig;
3375
3412
  }
3413
+ renderRecallProvider(v.provider);
3376
3414
  renderRecallList(v.items);
3377
3415
  }
3416
+ // Provider picker shows the right logo + a human label (the stored value stays the
3417
+ // internal id). claude-local and anthropic are Anthropic models (Claude logo); openai
3418
+ // is any OpenAI-compatible endpoint (OpenAI/Codex logo).
3419
+ const RECALL_PROV_META = {
3420
+ "claude-local": { label: "Claude Code (local login)", icon: "claude" },
3421
+ "anthropic": { label: "Anthropic API", icon: "claude" },
3422
+ "openai": { label: "OpenAI-compatible", icon: "codex" },
3423
+ };
3424
+ let recallProvDD = null, recallProvVal = "claude-local";
3425
+ function renderRecallProvider(p) {
3426
+ if (!p) { $("recallProviderPanel").style.display = "none"; return; }
3427
+ $("recallProviderPanel").style.display = "";
3428
+ recallProvVal = p.provider;
3429
+ if (!recallProvDD) {
3430
+ const entries = (p.providers || []).map((v) => {
3431
+ const m = RECALL_PROV_META[v] || { label: v, icon: "" };
3432
+ return { value: v, label: m.label, icon: agentSvg(m.icon) };
3433
+ });
3434
+ recallProvDD = iconDropdown("recallProvDD", entries, () => recallProvVal, (val) => {
3435
+ recallProvVal = val;
3436
+ $("recallKeyRow").style.display = val === "claude-local" ? "none" : "";
3437
+ });
3438
+ }
3439
+ recallProvDD.render();
3440
+ $("recallModel").value = p.model || "";
3441
+ $("recallBase").value = p.base || "";
3442
+ $("recallKey").placeholder = p.keyFromEnv ? "set via ENIGMA_RECALL_API_KEY" : (p.hasKey ? "set - leave blank to keep" : "API key");
3443
+ $("recallKey").disabled = !!p.keyFromEnv;
3444
+ // claude-local needs no base/key; hide that row for it.
3445
+ $("recallKeyRow").style.display = p.provider === "claude-local" ? "none" : "";
3446
+ const llmBtn = $("recallLlmBtn");
3447
+ llmBtn.textContent = p.llm ? "on" : "off";
3448
+ llmBtn.classList.toggle("on", !!p.llm);
3449
+ llmBtn.dataset.on = p.llm ? "1" : "0";
3450
+ llmBtn.setAttribute("aria-checked", p.llm ? "true" : "false");
3451
+ }
3378
3452
  let recallItemsById = {};
3379
3453
  function recallRowHtml(it, anchorId) {
3380
3454
  const ic = '<span class="aicon" title="' + esc(it.source) + '" aria-label="' + esc(it.source) + '">' + (agentSvg(it.source) || esc(it.source)) + "</span>";
@@ -3392,7 +3466,8 @@ export {
3392
3466
  + facts
3393
3467
  + (it.concepts && it.concepts.length ? '<div style="margin-top:6px">' + tags(it.concepts) + "</div>" : "")
3394
3468
  + list("Modified", it.files) + list("Read", it.filesRead)
3395
- + '<div style="margin-top:8px"><button type="button" class="toggle" data-tl="' + it.id + '">Show timeline</button></div>'
3469
+ + '<div style="margin-top:8px;display:flex;gap:8px;flex-wrap:wrap"><button type="button" class="toggle" data-tl="' + it.id + '">Show timeline</button>'
3470
+ + (it.id ? '<button type="button" class="btn-danger" data-del="' + it.id + '" title="Delete this memory">Delete</button>' : "") + "</div>"
3396
3471
  + '<div class="rtl" id="rtl-' + it.id + '"></div></div>';
3397
3472
  }
3398
3473
  function renderRecallList(items) {
@@ -3441,6 +3516,24 @@ export {
3441
3516
  loadRecall();
3442
3517
  } catch { setNote("recallNote", "Could not reach the server.", "err"); }
3443
3518
  }
3519
+ async function recallProviderPost(extra, msg) {
3520
+ setNote("recallNote", msg || "Saving provider...");
3521
+ try {
3522
+ const out = await (await fetch("/api/recall", { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify(Object.assign({ op: "set-provider" }, extra)) })).json();
3523
+ if (out.view) renderRecall(out.view);
3524
+ setNote("recallNote", out.ok ? "Saved." : (out.error || "Failed."), out.ok ? "ok" : "err");
3525
+ } catch { setNote("recallNote", "Could not reach the server.", "err"); }
3526
+ }
3527
+ // Generic recall action post (delete/create/generate); refreshes the view and returns ok.
3528
+ async function recallOpPost(body, msg, okMsg) {
3529
+ setNote("recallNote", msg || "Working...");
3530
+ try {
3531
+ const out = await (await fetch("/api/recall", { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify(body) })).json();
3532
+ if (out.view) renderRecall(out.view);
3533
+ setNote("recallNote", out.ok ? (okMsg || "Done.") : (out.error || "Failed."), out.ok ? "ok" : "err");
3534
+ return out.ok;
3535
+ } catch { setNote("recallNote", "Could not reach the server.", "err"); return false; }
3536
+ }
3444
3537
  function wireRecall() {
3445
3538
  $("recallSync").addEventListener("click", () => recallAction("sync"));
3446
3539
  $("recallClear").addEventListener("click", () => recallAction("clear", "Delete all stored session memory? This cannot be undone."));
@@ -3448,11 +3541,43 @@ export {
3448
3541
  const t = e.target.closest("[data-recall-on]");
3449
3542
  if (t) setRecallEnabled(t.dataset.recallOn === "1");
3450
3543
  });
3544
+ // Provider panel: save provider; flip LLM curation. The provider picker is the
3545
+ // custom icon dropdown (recallProvVal), wired in renderRecallProvider.
3546
+ $("recallProvSave").addEventListener("click", () => {
3547
+ const e = { provider: recallProvVal, model: $("recallModel").value.trim(), base: $("recallBase").value.trim() };
3548
+ if (!$("recallKey").disabled && $("recallKey").value) e.key = $("recallKey").value; // only send a key the user typed
3549
+ recallProviderPost(e, "Saving provider...");
3550
+ $("recallKey").value = "";
3551
+ });
3552
+ $("recallLlmBtn").addEventListener("click", () => recallProviderPost({ llm: $("recallLlmBtn").dataset.on !== "1" }, "Updating curation..."));
3553
+ // Add a memory: manual form, or generate one from a note via the configured model.
3554
+ $("recallCreate").addEventListener("click", async () => {
3555
+ const title = $("recallNewTitle").value.trim();
3556
+ if (!title) { setNote("recallNote", "A title is required.", "err"); return; }
3557
+ const facts = $("recallNewFacts").value.split(",").map((s) => s.trim()).filter(Boolean);
3558
+ const ok = await recallOpPost({
3559
+ op: "create", type: $("recallNewType").value, title, project: $("recallNewProject").value.trim(),
3560
+ narrative: $("recallNewNarrative").value.trim(), facts,
3561
+ }, "Saving memory...", "Memory saved.");
3562
+ if (ok) { $("recallNewTitle").value = ""; $("recallNewNarrative").value = ""; $("recallNewFacts").value = ""; }
3563
+ });
3564
+ $("recallGenerate").addEventListener("click", async () => {
3565
+ const prompt = $("recallGenNote").value.trim();
3566
+ if (!prompt) { setNote("recallNote", "Write a note to generate from.", "err"); return; }
3567
+ const ok = await recallOpPost({ op: "generate", prompt, project: $("recallNewProject").value.trim() }, "Generating memory...", "Memory generated.");
3568
+ if (ok) $("recallGenNote").value = "";
3569
+ });
3451
3570
  let t; const deb = () => { clearTimeout(t); t = setTimeout(loadRecall, 250); };
3452
3571
  $("recallQ").addEventListener("input", deb);
3453
3572
  $("recallType").addEventListener("change", loadRecall);
3454
3573
  $("recallProject").addEventListener("change", loadRecall);
3455
- $("recallList").addEventListener("click", (e) => {
3574
+ $("recallList").addEventListener("click", async (e) => {
3575
+ const del = e.target.closest("[data-del]");
3576
+ if (del) {
3577
+ if (!(await askConfirm("Delete memory", "Delete this memory? This cannot be undone.", true))) return;
3578
+ recallOpPost({ op: "delete", id: Number(del.dataset.del) }, "Deleting...", "Memory deleted.");
3579
+ return;
3580
+ }
3456
3581
  const tl = e.target.closest("[data-tl]");
3457
3582
  if (tl) { loadRecallTimeline(tl.dataset.tl); return; }
3458
3583
  const row = e.target.closest("[data-rid]");
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@enigmax/dashboard",
3
- "version": "0.1.59",
3
+ "version": "0.1.61",
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": [