@enigmax/dashboard 0.1.60 → 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 +89 -14
  2. package/package.json +1 -1
package/assets/index.html CHANGED
@@ -710,10 +710,13 @@
710
710
  <div id="recallBody"><div class="empty" style="padding:12px 0">Loading...</div></div>
711
711
  <div id="recallProviderPanel" class="panel" style="display:none">
712
712
  <div class="set-cat-h">Memory provider</div>
713
- <div class="set-hint" style="padding:4px 0">Which LLM curates your memory (keeps only what's worth remembering). <code>claude-local</code> uses your Claude Code login - no key. For another provider, pick it and set the model/base/key; the key can also come from <code>ENIGMA_RECALL_API_KEY</code>.</div>
714
- <div class="set-row"><div class="set-meta"><div class="set-name">LLM curation</div><div class="set-hint">off = deterministic heuristic, no network</div></div><div class="set-ctl"><button id="recallLlmBtn" type="button" class="toggle">...</button></div></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
715
  <div class="ladd" style="gap:8px;flex-wrap:wrap;margin-top:8px">
716
- <select id="recallProv" class="linput" style="max-width:160px"></select>
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>
717
720
  <input id="recallModel" class="linput" placeholder="model (provider default)" style="flex:1;min-width:160px">
718
721
  </div>
719
722
  <div id="recallKeyRow" class="ladd" style="gap:8px;flex-wrap:wrap;margin-top:8px">
@@ -730,6 +733,24 @@
730
733
  <select id="recallProject" class="linput" style="max-width:200px"></select>
731
734
  </div>
732
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>
733
754
  <div id="recallList"></div>
734
755
  </section>
735
756
 
@@ -3361,14 +3382,14 @@ export {
3361
3382
  v = v || {};
3362
3383
  if (!v.available) {
3363
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>';
3364
- $("recallSearch").style.display = "none"; $("recallProviderPanel").style.display = "none"; $("recallList").innerHTML = ""; return;
3385
+ $("recallSearch").style.display = "none"; $("recallProviderPanel").style.display = "none"; $("recallAddPanel").style.display = "none"; $("recallList").innerHTML = ""; return;
3365
3386
  }
3366
3387
  if (!v.enabled) {
3367
3388
  $("recallBody").innerHTML = '<div class="panel"><div class="set-cat-h">Session memory is off</div>'
3368
3389
  + '<div class="set-hint" style="padding:8px 0">Build a searchable memory of your coding sessions from your own transcripts.</div>'
3369
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>'
3370
3391
  + '<span class="set-hint">or run <code>enigma config recall on</code></span></div></div>';
3371
- $("recallSearch").style.display = "none"; $("recallProviderPanel").style.display = "none"; $("recallList").innerHTML = ""; return;
3392
+ $("recallSearch").style.display = "none"; $("recallProviderPanel").style.display = "none"; $("recallAddPanel").style.display = "none"; $("recallList").innerHTML = ""; return;
3372
3393
  }
3373
3394
  const s = v.stats || {};
3374
3395
  const types = Object.entries(s.byType || {}).map(([t, n]) => '<span class="tag">' + esc(t) + " " + n + "</span>").join(" ");
@@ -3379,7 +3400,9 @@ export {
3379
3400
  + (s.projects || 0) + " projects · " + Math.round((s.dbBytes || 0) / 1024) + " KB · " + esc(last) + "</div>"
3380
3401
  + (types ? '<div style="padding-top:6px">' + types + "</div>" : "") + "</div>";
3381
3402
  $("recallSearch").style.display = "";
3403
+ $("recallAddPanel").style.display = "";
3382
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("");
3383
3406
  const sig = (v.projects || []).join("|");
3384
3407
  if (sig !== recallProjSig) {
3385
3408
  const keep = $("recallProject").value;
@@ -3390,13 +3413,30 @@ export {
3390
3413
  renderRecallProvider(v.provider);
3391
3414
  renderRecallList(v.items);
3392
3415
  }
3393
- let recallProvSig = "";
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";
3394
3425
  function renderRecallProvider(p) {
3395
3426
  if (!p) { $("recallProviderPanel").style.display = "none"; return; }
3396
3427
  $("recallProviderPanel").style.display = "";
3397
- const sig = (p.providers || []).join("|");
3398
- if (sig !== recallProvSig) { $("recallProv").innerHTML = (p.providers || []).map((x) => '<option value="' + x + '">' + x + "</option>").join(""); recallProvSig = sig; }
3399
- $("recallProv").value = p.provider;
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();
3400
3440
  $("recallModel").value = p.model || "";
3401
3441
  $("recallBase").value = p.base || "";
3402
3442
  $("recallKey").placeholder = p.keyFromEnv ? "set via ENIGMA_RECALL_API_KEY" : (p.hasKey ? "set - leave blank to keep" : "API key");
@@ -3407,6 +3447,7 @@ export {
3407
3447
  llmBtn.textContent = p.llm ? "on" : "off";
3408
3448
  llmBtn.classList.toggle("on", !!p.llm);
3409
3449
  llmBtn.dataset.on = p.llm ? "1" : "0";
3450
+ llmBtn.setAttribute("aria-checked", p.llm ? "true" : "false");
3410
3451
  }
3411
3452
  let recallItemsById = {};
3412
3453
  function recallRowHtml(it, anchorId) {
@@ -3425,7 +3466,8 @@ export {
3425
3466
  + facts
3426
3467
  + (it.concepts && it.concepts.length ? '<div style="margin-top:6px">' + tags(it.concepts) + "</div>" : "")
3427
3468
  + list("Modified", it.files) + list("Read", it.filesRead)
3428
- + '<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>"
3429
3471
  + '<div class="rtl" id="rtl-' + it.id + '"></div></div>';
3430
3472
  }
3431
3473
  function renderRecallList(items) {
@@ -3482,6 +3524,16 @@ export {
3482
3524
  setNote("recallNote", out.ok ? "Saved." : (out.error || "Failed."), out.ok ? "ok" : "err");
3483
3525
  } catch { setNote("recallNote", "Could not reach the server.", "err"); }
3484
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
+ }
3485
3537
  function wireRecall() {
3486
3538
  $("recallSync").addEventListener("click", () => recallAction("sync"));
3487
3539
  $("recallClear").addEventListener("click", () => recallAction("clear", "Delete all stored session memory? This cannot be undone."));
@@ -3489,20 +3541,43 @@ export {
3489
3541
  const t = e.target.closest("[data-recall-on]");
3490
3542
  if (t) setRecallEnabled(t.dataset.recallOn === "1");
3491
3543
  });
3492
- // Provider panel: toggle which select fields apply; save provider; flip LLM curation.
3493
- $("recallProv").addEventListener("change", () => { $("recallKeyRow").style.display = $("recallProv").value === "claude-local" ? "none" : ""; });
3544
+ // Provider panel: save provider; flip LLM curation. The provider picker is the
3545
+ // custom icon dropdown (recallProvVal), wired in renderRecallProvider.
3494
3546
  $("recallProvSave").addEventListener("click", () => {
3495
- const e = { provider: $("recallProv").value, model: $("recallModel").value.trim(), base: $("recallBase").value.trim() };
3547
+ const e = { provider: recallProvVal, model: $("recallModel").value.trim(), base: $("recallBase").value.trim() };
3496
3548
  if (!$("recallKey").disabled && $("recallKey").value) e.key = $("recallKey").value; // only send a key the user typed
3497
3549
  recallProviderPost(e, "Saving provider...");
3498
3550
  $("recallKey").value = "";
3499
3551
  });
3500
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
+ });
3501
3570
  let t; const deb = () => { clearTimeout(t); t = setTimeout(loadRecall, 250); };
3502
3571
  $("recallQ").addEventListener("input", deb);
3503
3572
  $("recallType").addEventListener("change", loadRecall);
3504
3573
  $("recallProject").addEventListener("change", loadRecall);
3505
- $("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
+ }
3506
3581
  const tl = e.target.closest("[data-tl]");
3507
3582
  if (tl) { loadRecallTimeline(tl.dataset.tl); return; }
3508
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.60",
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": [