@modusensus/dsh-mneme 0.8.4 → 0.8.6
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/README.md +45 -8
- package/bin/dsh-mneme-mcp.mjs +11 -3
- package/lib/api-standalone.js +11 -0
- package/lib/api.js +21 -0
- package/lib/client.js +213 -31
- package/lib/config.js +106 -5
- package/lib/document.js +221 -0
- package/lib/dream/sleep.js +128 -12
- package/lib/dream.js +46 -11
- package/lib/guide.js +44 -0
- package/lib/heat.js +2 -0
- package/lib/index.js +65 -3
- package/lib/inject.js +91 -15
- package/lib/lang.js +6 -0
- package/lib/local-embedder.js +44 -6
- package/lib/organize.js +450 -0
- package/lib/recall-stats.js +23 -1
- package/lib/runtime/layout.js +30 -0
- package/lib/runtime/loader.js +4 -1
- package/lib/runtime/verify.js +19 -6
- package/lib/search/adaptive.js +38 -0
- package/lib/service.js +165 -12
- package/lib/settings.js +35 -4
- package/lib/store.js +126 -8
- package/lib/summarize.js +253 -8
- package/lib/tools.js +136 -8
- package/package.json +6 -6
- package/runtime-manifest.json +104 -174
- package/scripts/build-runtime-manifest.mjs +8 -1
- package/scripts/sync-test-badge.mjs +18 -12
- package/src/api-standalone.js +11 -0
- package/src/api.js +21 -0
- package/src/config.js +106 -5
- package/src/document.js +221 -0
- package/src/dream/sleep.js +128 -12
- package/src/dream.js +46 -11
- package/src/guide.js +44 -0
- package/src/heat.js +2 -0
- package/src/index.js +65 -3
- package/src/inject.js +91 -15
- package/src/lang.js +6 -0
- package/src/local-embedder.js +44 -6
- package/src/organize.js +450 -0
- package/src/recall-stats.js +23 -1
- package/src/runtime/layout.js +30 -0
- package/src/runtime/loader.js +4 -1
- package/src/runtime/verify.js +19 -6
- package/src/search/adaptive.js +38 -0
- package/src/service.js +165 -12
- package/src/settings.js +35 -4
- package/src/store.js +126 -8
- package/src/summarize.js +253 -8
- package/src/tools.js +136 -8
- package/test/adaptive-inject-budget.test.js +85 -0
- package/test/api.test.js +66 -3
- package/test/client.test.js +63 -2
- package/test/document.test.js +523 -0
- package/test/dream.test.js +51 -0
- package/test/inject-content-cap.test.js +5 -3
- package/test/inject-pools.test.js +170 -0
- package/test/inject-recall.test.js +105 -0
- package/test/inject.test.js +35 -1
- package/test/lastrunat-persist.test.js +101 -0
- package/test/llm-audit.test.js +247 -0
- package/test/local-embedder.test.js +45 -2
- package/test/organize.test.js +418 -0
- package/test/recall-stats.test.js +7 -1
- package/test/runtime-loader.test.js +18 -0
- package/test/runtime-manifest.test.js +2 -1
- package/test/runtime-verify.test.js +65 -0
- package/test/sleep.test.js +154 -0
- package/test/store.test.js +13 -0
- package/test/summarize.test.js +407 -3
- package/test/tools-dto-schema-matrix.test.js +325 -0
- package/test/tools.test.js +36 -3
package/lib/client.js
CHANGED
|
@@ -7,6 +7,25 @@ window.__ModuleLoader__.load({
|
|
|
7
7
|
let react = require("react");
|
|
8
8
|
let primitives = require("@deepseek-ai/dsh-client-ui-primitives");
|
|
9
9
|
let { useState, useEffect, useCallback, useRef } = react;
|
|
10
|
+
|
|
11
|
+
// #178 无障碍批次一:aria-live 网络。模块级单例 polite live region +
|
|
12
|
+
// announce():面板所有瞬时反馈(保存成功/失败、队列刷新、裁决完成、
|
|
13
|
+
// 复制成功)经此播报,读屏用户不再依赖纯视觉 span。单例挂 <body>,
|
|
14
|
+
// 与面板组件生命周期解耦;文本先清空下一帧再写入,让连续两次相同
|
|
15
|
+
// 文案也能各播报一次。
|
|
16
|
+
let liveRegion = null;
|
|
17
|
+
function announce(text) {
|
|
18
|
+
if (!text || typeof document === "undefined") return;
|
|
19
|
+
if (!liveRegion || !liveRegion.isConnected) {
|
|
20
|
+
liveRegion = document.createElement("div");
|
|
21
|
+
liveRegion.setAttribute("role", "status");
|
|
22
|
+
liveRegion.setAttribute("aria-live", "polite");
|
|
23
|
+
liveRegion.style.cssText = "position:absolute;width:1px;height:1px;margin:-1px;overflow:hidden;clip:rect(0 0 0 0);white-space:nowrap";
|
|
24
|
+
document.body.appendChild(liveRegion);
|
|
25
|
+
}
|
|
26
|
+
liveRegion.textContent = "";
|
|
27
|
+
window.requestAnimationFrame(() => { if (liveRegion) liveRegion.textContent = text; });
|
|
28
|
+
}
|
|
10
29
|
const IconArchiveOutline20 = primitives.IconArchiveOutline20;
|
|
11
30
|
|
|
12
31
|
// Portal target for the hero fallback surface. The host whitelists
|
|
@@ -245,6 +264,7 @@ window.__ModuleLoader__.load({
|
|
|
245
264
|
"memory.graph.sourceMemory": "查看来源记忆",
|
|
246
265
|
"memory.graph.hint": "拖拽节点调整布局 · 空白处拖动平移 · 滚轮缩放",
|
|
247
266
|
"memory.graph.resetView": "重置视图",
|
|
267
|
+
"memory.graph.summary": "实体关系图:{entity} 及其 {nodes} 个节点、{edges} 条关系",
|
|
248
268
|
"memory.graph.viewInGraph": "在实体视图中打开",
|
|
249
269
|
"memory.graph.loading": "加载中…",
|
|
250
270
|
"memory.graph.distance": "距中心 {n} 跳",
|
|
@@ -292,6 +312,7 @@ window.__ModuleLoader__.load({
|
|
|
292
312
|
"memory.tab.rejected_solution": "被否决方案",
|
|
293
313
|
"memory.tab.pitfall": "踩坑记录",
|
|
294
314
|
"memory.tab.constraint": "工程约束",
|
|
315
|
+
"memory.tab.document": "文档",
|
|
295
316
|
"memory.features.title": "功能开关",
|
|
296
317
|
"memory.features.desc": "按需启停后端能力,改动保存后重启 DSH 生效。",
|
|
297
318
|
"memory.features.group.core": "核心",
|
|
@@ -304,6 +325,8 @@ window.__ModuleLoader__.load({
|
|
|
304
325
|
"memory.features.loadFailed": "加载失败",
|
|
305
326
|
"memory.features.autoInject": "自动注入",
|
|
306
327
|
"memory.features.autoInject.hint": "每轮对话自动携带相关记忆",
|
|
328
|
+
"memory.features.injectGuidanceEnabled": "能力说明",
|
|
329
|
+
"memory.features.injectGuidanceEnabled.hint": "在工具描述与一次性提示段里说明怎么用记忆(何时查、何时写、拿不准就不做)",
|
|
307
330
|
"memory.features.autoSummarize": "自动总结",
|
|
308
331
|
"memory.features.autoSummarize.hint": "对话结束自动提炼记忆条目",
|
|
309
332
|
"memory.features.hotMemoryEnabled": "热记忆",
|
|
@@ -324,6 +347,8 @@ window.__ModuleLoader__.load({
|
|
|
324
347
|
"memory.features.rerankEnabled.hint": "本地重排模型对召回结果精排,更慢更准",
|
|
325
348
|
"memory.features.heatEnabled": "热度衰减",
|
|
326
349
|
"memory.features.heatEnabled.hint": "按遗忘曲线给记忆降温:久未访问热度越低,参与注入排序与睡眠降级判定",
|
|
350
|
+
"memory.features.documentMemoryEnabled": "document 型记忆",
|
|
351
|
+
"memory.features.documentMemoryEnabled.hint": "agent 产长文档入库为指针行:注册校验(文件存在 + evidence 求交)、摘要 + 路径落库、去重与 supersede 记账;全文归 agent 按需读,注入只带摘要行(默认关)",
|
|
327
352
|
"memory.features.resilientModelDownload": "模型下载断点续传",
|
|
328
353
|
"memory.features.resilientModelDownload.hint": "模型文件下载中断后从已下载部分续传并自动重试,失败会记录中断位置;关闭后回到一次性下载",
|
|
329
354
|
"memory.features.searchSemanticDedup": "语义去重",
|
|
@@ -445,6 +470,15 @@ window.__ModuleLoader__.load({
|
|
|
445
470
|
"memory.status.conflictsHint": "冻结的矛盾记忆,等待人工确认",
|
|
446
471
|
"memory.status.injectSuppressed": "极简模式下注入按宿主设计关闭",
|
|
447
472
|
"memory.status.injectSuppressedHint": "当前会话使用 minimal 预设,记忆注入 / 用户画像 / hot memory 不送达模型(宿主设计,非插件缺陷)。解法:切换标准模式,或在 ~/.dsh/settings.yaml 设 agent-presets.default: standard;过渡方案:把画像与规则写入 AGENTS.md",
|
|
473
|
+
"memory.status.injectPreview": "注入预览",
|
|
474
|
+
"memory.status.injectPreviewNone": "暂无预览——尚未发生注入(新会话或注入已关闭)",
|
|
475
|
+
"memory.status.injectPreview.chars": "总体积 {chars} 字符",
|
|
476
|
+
"memory.status.injectPreview.charsUnit": " 字符",
|
|
477
|
+
"memory.status.injectPreview.query": "查询「{query}…」",
|
|
478
|
+
"memory.status.injectPreview.hot": "hot memory",
|
|
479
|
+
"memory.status.injectPreview.adaptiveOn": "自适应条数",
|
|
480
|
+
"memory.status.injectPreview.rotated": "轮换抑制",
|
|
481
|
+
"memory.status.injectPreview.empty": "本次组装未注入任何跨会话记忆(阈值或轮换过滤)",
|
|
448
482
|
"memory.status.conflictQueue.reason": "原因",
|
|
449
483
|
"memory.status.conflictQueue.sideA": "A 方",
|
|
450
484
|
"memory.status.conflictQueue.sideB": "B 方",
|
|
@@ -453,7 +487,10 @@ window.__ModuleLoader__.load({
|
|
|
453
487
|
"memory.status.conflictQueue.markReviewed": "仅标记已处理",
|
|
454
488
|
"memory.status.conflictQueue.applyHint": "确认后:保留方正文追加已否决注记,另一方归档。",
|
|
455
489
|
"memory.status.conflictQueue.missing": "(该记忆已不存在)",
|
|
490
|
+
"memory.status.conflictQueue.goto": "查看待确认冲突队列",
|
|
491
|
+
"memory.status.conflictQueue.resolved": "已裁决:保留{name}",
|
|
456
492
|
"memory.status.conflictQueue.refresh": "刷新队列",
|
|
493
|
+
"memory.status.conflictQueue.refreshed": "冲突队列已刷新",
|
|
457
494
|
"memory.status.workbench": "工作动态",
|
|
458
495
|
"memory.status.dreamConsolidate": "记忆巩固",
|
|
459
496
|
"memory.status.summarize": "总结提炼",
|
|
@@ -470,6 +507,7 @@ window.__ModuleLoader__.load({
|
|
|
470
507
|
"memory.status.heatHint": "热门(≥66%){hot} · 温热(33–66%){warm} · 冷却(<33%){cold},样本为最近 {sample} 条",
|
|
471
508
|
"memory.status.recallStats": "记忆复用",
|
|
472
509
|
"memory.status.recallHint": "复用率 {rate} · 僵尸 {zombie}/{active}(豁免 {exempt})· 30 天回执 {runs} 次 · Top:{top}",
|
|
510
|
+
"memory.status.recallInject": "注入 {runs} 轮 {count} 条,槽位填充 {fill}%",
|
|
473
511
|
"memory.status.viewAll": "查看全部",
|
|
474
512
|
"memory.status.depositedCount": "沉淀的记忆({n})",
|
|
475
513
|
"memory.status.archivedCount": "已归档的记忆({n})",
|
|
@@ -604,6 +642,7 @@ window.__ModuleLoader__.load({
|
|
|
604
642
|
"memory.graph.sourceMemory": "View source memory",
|
|
605
643
|
"memory.graph.hint": "Drag nodes to rearrange · drag the background to pan · scroll to zoom",
|
|
606
644
|
"memory.graph.resetView": "Reset view",
|
|
645
|
+
"memory.graph.summary": "Entity graph: {entity} with {nodes} nodes and {edges} edges",
|
|
607
646
|
"memory.graph.viewInGraph": "Open in entity explorer",
|
|
608
647
|
"memory.graph.loading": "Loading…",
|
|
609
648
|
"memory.graph.distance": "{n} hop(s) from root",
|
|
@@ -651,6 +690,7 @@ window.__ModuleLoader__.load({
|
|
|
651
690
|
"memory.tab.rejected_solution": "Rejected solutions",
|
|
652
691
|
"memory.tab.pitfall": "Pitfalls",
|
|
653
692
|
"memory.tab.constraint": "Constraints",
|
|
693
|
+
"memory.tab.document": "Documents",
|
|
654
694
|
"memory.features.title": "Features",
|
|
655
695
|
"memory.features.desc": "Toggle backend capabilities. Changes take effect after restarting DSH.",
|
|
656
696
|
"memory.features.group.core": "Core",
|
|
@@ -663,6 +703,8 @@ window.__ModuleLoader__.load({
|
|
|
663
703
|
"memory.features.loadFailed": "Failed to load",
|
|
664
704
|
"memory.features.autoInject": "Auto injection",
|
|
665
705
|
"memory.features.autoInject.hint": "Carry relevant memories into every turn",
|
|
706
|
+
"memory.features.injectGuidanceEnabled": "Capability guide",
|
|
707
|
+
"memory.features.injectGuidanceEnabled.hint": "Explain how to use memory (when to search, when to save, when to do nothing) in tool descriptions plus a one-time prompt section",
|
|
666
708
|
"memory.features.autoSummarize": "Auto summarization",
|
|
667
709
|
"memory.features.autoSummarize.hint": "Distill memory entries when a conversation ends",
|
|
668
710
|
"memory.features.hotMemoryEnabled": "Hot memory",
|
|
@@ -683,6 +725,8 @@ window.__ModuleLoader__.load({
|
|
|
683
725
|
"memory.features.rerankEnabled.hint": "Rerank recalled results with a local model — slower, more precise",
|
|
684
726
|
"memory.features.heatEnabled": "Heat decay",
|
|
685
727
|
"memory.features.heatEnabled.hint": "Decay memory heat since last access; feeds injection ranking and sleep demotion",
|
|
728
|
+
"memory.features.documentMemoryEnabled": "Document memory",
|
|
729
|
+
"memory.features.documentMemoryEnabled.hint": "Register agent-authored documents as pointer rows: existence + evidence validation, summary + path stored, dedupe + supersede bookkeeping; the full text stays agent-owned and is read on demand; injection carries the summary row only (off by default)",
|
|
686
730
|
"memory.features.resilientModelDownload": "Resumable model downloads",
|
|
687
731
|
"memory.features.resilientModelDownload.hint": "Resume model file downloads from received bytes and retry on failure; failures log where they stopped",
|
|
688
732
|
"memory.features.searchSemanticDedup": "Semantic dedup",
|
|
@@ -804,6 +848,15 @@ window.__ModuleLoader__.load({
|
|
|
804
848
|
"memory.status.conflictsHint": "Frozen contradictory memories awaiting confirmation",
|
|
805
849
|
"memory.status.injectSuppressed": "Injection disabled by host minimal preset",
|
|
806
850
|
"memory.status.injectSuppressedHint": "This session uses the minimal preset: memory injection / user profile / hot memory never reach the model (by host design, not a plugin defect). Fix: switch to standard mode, or set agent-presets.default: standard in ~/.dsh/settings.yaml. Interim: put profile and rules in AGENTS.md",
|
|
851
|
+
"memory.status.injectPreview": "Injection preview",
|
|
852
|
+
"memory.status.injectPreviewNone": "No preview yet — no injection has happened (new session or injection off)",
|
|
853
|
+
"memory.status.injectPreview.chars": "total {chars} chars",
|
|
854
|
+
"memory.status.injectPreview.charsUnit": " chars",
|
|
855
|
+
"memory.status.injectPreview.query": "query \"{query}…\"",
|
|
856
|
+
"memory.status.injectPreview.hot": "hot memory",
|
|
857
|
+
"memory.status.injectPreview.adaptiveOn": "adaptive budget",
|
|
858
|
+
"memory.status.injectPreview.rotated": "rotation-suppressed",
|
|
859
|
+
"memory.status.injectPreview.empty": "No cross-session memories injected this assembly (threshold or rotation filter)",
|
|
807
860
|
"memory.status.conflictQueue.reason": "Reason",
|
|
808
861
|
"memory.status.conflictQueue.sideA": "Side A",
|
|
809
862
|
"memory.status.conflictQueue.sideB": "Side B",
|
|
@@ -812,7 +865,10 @@ window.__ModuleLoader__.load({
|
|
|
812
865
|
"memory.status.conflictQueue.markReviewed": "Mark reviewed only",
|
|
813
866
|
"memory.status.conflictQueue.applyHint": "On confirm: a veto note is appended to the kept side and the other side is archived.",
|
|
814
867
|
"memory.status.conflictQueue.missing": "(memory no longer exists)",
|
|
868
|
+
"memory.status.conflictQueue.goto": "View pending conflict queue",
|
|
869
|
+
"memory.status.conflictQueue.resolved": "Resolved: kept {name}",
|
|
815
870
|
"memory.status.conflictQueue.refresh": "Refresh queue",
|
|
871
|
+
"memory.status.conflictQueue.refreshed": "Conflict queue refreshed",
|
|
816
872
|
"memory.status.workbench": "Activity",
|
|
817
873
|
"memory.status.dreamConsolidate": "Consolidation",
|
|
818
874
|
"memory.status.summarize": "Summarization",
|
|
@@ -829,6 +885,7 @@ window.__ModuleLoader__.load({
|
|
|
829
885
|
"memory.status.heatHint": "hot (≥66%) {hot} · warm (33–66%) {warm} · cooled (<33%) {cold}, sampled from the latest {sample}",
|
|
830
886
|
"memory.status.recallStats": "Recall reuse",
|
|
831
887
|
"memory.status.recallHint": "reuse {rate} · zombie {zombie}/{active} (exempt {exempt}) · {runs} runs in 30d · top: {top}",
|
|
888
|
+
"memory.status.recallInject": "injected {runs} turns · {count} items ({fill}% slots)",
|
|
832
889
|
"memory.status.viewAll": "View all",
|
|
833
890
|
"memory.status.depositedCount": "Deposited memories ({n})",
|
|
834
891
|
"memory.status.archivedCount": "Archived memories ({n})",
|
|
@@ -964,7 +1021,7 @@ window.__ModuleLoader__.load({
|
|
|
964
1021
|
".mneme-xrow{display:flex;align-items:center;gap:8px;flex-wrap:wrap}",
|
|
965
1022
|
".mneme-xsearch{width:100%}",
|
|
966
1023
|
".mneme-xselect{flex:1;min-width:0}",
|
|
967
|
-
".mneme-xcolhead{flex:none;font-size:12px;font-weight:500;color:var(--dsw-alias-label-tertiary);padding:2px 8px 8px}",
|
|
1024
|
+
".mneme-xcolhead{flex:none;font-size:12px;font-weight:500;margin:0;color:var(--dsw-alias-label-tertiary);padding:2px 8px 8px}",
|
|
968
1025
|
".mneme-xtype{display:flex;justify-content:flex-start;align-items:center;gap:8px;width:100%;padding:5px 8px;border:none;border-radius:8px;background:none;color:var(--dsw-alias-label-secondary);cursor:pointer;font-family:inherit;font-size:13px;line-height:18px;text-align:left}",
|
|
969
1026
|
".mneme-xtype:hover{background:var(--dsw-alias-interactive-bg-hover)}",
|
|
970
1027
|
".mneme-xtype.mneme-active{background:var(--dsw-alias-interactive-bg-active);color:var(--dsw-alias-label-primary);font-weight:500}",
|
|
@@ -1719,6 +1776,11 @@ window.__ModuleLoader__.load({
|
|
|
1719
1776
|
className: "mneme-graphsvg",
|
|
1720
1777
|
viewBox: `0 0 ${VIEW_W} ${VIEW_H}`,
|
|
1721
1778
|
style: { height: 320 },
|
|
1779
|
+
role: "img",
|
|
1780
|
+
"aria-label": t("memory.graph.summary")
|
|
1781
|
+
.replace("{entity}", (data.root && (data.root.label || data.root.name)) || "")
|
|
1782
|
+
.replace("{nodes}", String(data.nodes.length))
|
|
1783
|
+
.replace("{edges}", String(data.edges.length)),
|
|
1722
1784
|
onMouseDown: onSurfaceMouseDown
|
|
1723
1785
|
},
|
|
1724
1786
|
h("g", {
|
|
@@ -1766,8 +1828,8 @@ window.__ModuleLoader__.load({
|
|
|
1766
1828
|
// 不被专业项淹没。429 调速器参数、distillMaxChars、codingBoostFactor
|
|
1767
1829
|
// 属调优噪音,按对齐结论留在配置文件,不上 UI。
|
|
1768
1830
|
const FEATURE_GROUPS = [
|
|
1769
|
-
{ key: "group.core", items: ["autoInject", "autoSummarize", "hotMemoryEnabled", "memoryQualityFilter.enabled", "llmAudit.enabled"] },
|
|
1770
|
-
{ key: "group.enhance", items: ["entityExtractionEnabled", "codingRetrospect", "rerankEnabled", "resilientModelDownload", "searchSemanticDedup", "bm25SearchEnabled", "heatEnabled"] },
|
|
1831
|
+
{ key: "group.core", items: ["autoInject", "injectGuidanceEnabled", "autoSummarize", "hotMemoryEnabled", "memoryQualityFilter.enabled", "llmAudit.enabled"] },
|
|
1832
|
+
{ key: "group.enhance", items: ["entityExtractionEnabled", "codingRetrospect", "rerankEnabled", "resilientModelDownload", "searchSemanticDedup", "bm25SearchEnabled", "heatEnabled", "documentMemoryEnabled"] },
|
|
1771
1833
|
{ key: "group.dream", items: ["autoDream", "sleepModeEnabled"] },
|
|
1772
1834
|
// v0.8.0 A4(issue #17):作用域隔离组——标注总开关 + 严格硬过滤。
|
|
1773
1835
|
{ key: "group.scope", items: ["scopeEnabled", "strictScope"] }
|
|
@@ -1851,6 +1913,7 @@ window.__ModuleLoader__.load({
|
|
|
1851
1913
|
});
|
|
1852
1914
|
if (!res.ok) throw new Error("HTTP " + res.status);
|
|
1853
1915
|
setState(await res.json());
|
|
1916
|
+
announce(t("memory.features.restartHint"));
|
|
1854
1917
|
setSavedTick(true);
|
|
1855
1918
|
setTimeout(() => setSavedTick(false), 1800);
|
|
1856
1919
|
} catch {
|
|
@@ -2194,7 +2257,7 @@ window.__ModuleLoader__.load({
|
|
|
2194
2257
|
// 版本/平台方便归档,正文同款模板;纯链接零后端成本。
|
|
2195
2258
|
const feedbackEnv = `**插件版本**: ${pkgVersion}\n**平台**: ${platformLabel()}\n`;
|
|
2196
2259
|
const feedbackBody = feedbackEnv + "**问题描述**:\n- 期望行为:\n- 实际行为:\n- 复现步骤:\n";
|
|
2197
|
-
const issueHref = "https://github.com/
|
|
2260
|
+
const issueHref = "https://github.com/slow-stack/dsh-mneme/issues/new?title="
|
|
2198
2261
|
+ encodeURIComponent("[dsh-mneme] 问题反馈")
|
|
2199
2262
|
+ "&body=" + encodeURIComponent(feedbackBody);
|
|
2200
2263
|
const mailHref = "mailto:work@modusensus.space?subject="
|
|
@@ -2237,6 +2300,7 @@ window.__ModuleLoader__.load({
|
|
|
2237
2300
|
body: JSON.stringify({ profile })
|
|
2238
2301
|
});
|
|
2239
2302
|
setSaved(true);
|
|
2303
|
+
announce(t("memory.settings.profileSaved"));
|
|
2240
2304
|
setTimeout(() => setSaved(false), 1500);
|
|
2241
2305
|
} catch { /* ignore */ }
|
|
2242
2306
|
}
|
|
@@ -2295,6 +2359,7 @@ window.__ModuleLoader__.load({
|
|
|
2295
2359
|
body: JSON.stringify(vector)
|
|
2296
2360
|
});
|
|
2297
2361
|
setVectorSaved(true);
|
|
2362
|
+
announce(t("memory.settings.vectorSaved"));
|
|
2298
2363
|
setTimeout(() => setVectorSaved(false), 1500);
|
|
2299
2364
|
} catch { /* ignore */ }
|
|
2300
2365
|
}
|
|
@@ -2316,6 +2381,7 @@ window.__ModuleLoader__.load({
|
|
|
2316
2381
|
if (apiToken.trim()) window.localStorage.setItem("dsh-mneme-api-token", apiToken.trim());
|
|
2317
2382
|
else window.localStorage.removeItem("dsh-mneme-api-token");
|
|
2318
2383
|
setApiTokenSaved(true);
|
|
2384
|
+
announce(t("memory.settings.apiTokenSaved"));
|
|
2319
2385
|
setTimeout(() => setApiTokenSaved(false), 1500);
|
|
2320
2386
|
} catch { /* ignore */ }
|
|
2321
2387
|
}
|
|
@@ -2336,6 +2402,7 @@ window.__ModuleLoader__.load({
|
|
|
2336
2402
|
});
|
|
2337
2403
|
if (!res.ok) throw new Error("HTTP " + res.status);
|
|
2338
2404
|
setModeSaved(true);
|
|
2405
|
+
announce(t("memory.settings.mode.savedHint"));
|
|
2339
2406
|
setTimeout(() => setModeSaved(false), 2500);
|
|
2340
2407
|
} catch (err) {
|
|
2341
2408
|
setMode(prev);
|
|
@@ -2368,6 +2435,7 @@ window.__ModuleLoader__.load({
|
|
|
2368
2435
|
setExtapiHost(next.host);
|
|
2369
2436
|
setExtapiPort(next.port ? String(next.port) : "");
|
|
2370
2437
|
setExtapiSaved(true);
|
|
2438
|
+
announce(t("memory.settings.extapi.savedHint"));
|
|
2371
2439
|
setTimeout(() => setExtapiSaved(false), 2500);
|
|
2372
2440
|
} catch (err) {
|
|
2373
2441
|
setExtapiError((err && err.message) || "failed");
|
|
@@ -2394,7 +2462,7 @@ window.__ModuleLoader__.load({
|
|
|
2394
2462
|
function copyExtapiToken() {
|
|
2395
2463
|
const token = extapi ? extapi.token || "" : "";
|
|
2396
2464
|
navigator.clipboard?.writeText(token).then(
|
|
2397
|
-
() => { setExtapiCopied(true); setTimeout(() => setExtapiCopied(false), 1500); },
|
|
2465
|
+
() => { setExtapiCopied(true); announce(t("memory.settings.extapi.copied")); setTimeout(() => setExtapiCopied(false), 1500); },
|
|
2398
2466
|
() => {}
|
|
2399
2467
|
);
|
|
2400
2468
|
}
|
|
@@ -2622,7 +2690,7 @@ window.__ModuleLoader__.load({
|
|
|
2622
2690
|
h("div", { style: { display: "flex", flexDirection: "column", gap: 2 } },
|
|
2623
2691
|
h("a", { className: "mneme-feed-link", href: issueHref, target: "_blank", rel: "noopener noreferrer" }, t("memory.settings.feedback.newIssue")),
|
|
2624
2692
|
h("a", { className: "mneme-feed-link", href: mailHref }, t("memory.settings.feedback.email")),
|
|
2625
|
-
h("a", { className: "mneme-feed-link", href: "https://github.com/
|
|
2693
|
+
h("a", { className: "mneme-feed-link", href: "https://github.com/slow-stack/dsh-mneme/issues", target: "_blank", rel: "noopener noreferrer" }, t("memory.settings.feedback.browse"))
|
|
2626
2694
|
),
|
|
2627
2695
|
h("div", { className: "mneme-featsubhint", style: { marginTop: 8 } }, t("memory.settings.feedback.hint"))
|
|
2628
2696
|
)
|
|
@@ -2662,11 +2730,35 @@ window.__ModuleLoader__.load({
|
|
|
2662
2730
|
// 上下文裁不住它;运行时拒绝 react-dom 时就地渲染(position:fixed 仍然成立)。
|
|
2663
2731
|
function MemoryOverlay({ t }) {
|
|
2664
2732
|
const [open, setOpen] = useOverlayOpen();
|
|
2733
|
+
// #178:弹层焦点管理——打开时把焦点移入面板(关闭按钮为入口),
|
|
2734
|
+
// Tab 循环圈在面板内(Tab 从最后一个元素出去回到关闭按钮),
|
|
2735
|
+
// Esc/关闭时把焦点还给触发元素(侧边栏入口)。
|
|
2736
|
+
const panelRef = useRef(null);
|
|
2737
|
+
const closeBtnRef = useRef(null);
|
|
2665
2738
|
useEffect(() => {
|
|
2666
2739
|
if (!open) return undefined;
|
|
2667
|
-
const onKey = (e) => {
|
|
2740
|
+
const onKey = (e) => {
|
|
2741
|
+
if (e.key === "Escape") { setOpen(false); return; }
|
|
2742
|
+
if (e.key !== "Tab" || !panelRef.current) return;
|
|
2743
|
+
const focusables = panelRef.current.querySelectorAll('button, input, select, textarea, a[href], [tabindex]:not([tabindex="-1"])');
|
|
2744
|
+
if (focusables.length === 0) return;
|
|
2745
|
+
const first = focusables[0];
|
|
2746
|
+
const last = focusables[focusables.length - 1];
|
|
2747
|
+
if (e.shiftKey && (document.activeElement === first || !panelRef.current.contains(document.activeElement))) {
|
|
2748
|
+
e.preventDefault();
|
|
2749
|
+
last.focus();
|
|
2750
|
+
} else if (!e.shiftKey && document.activeElement === last) {
|
|
2751
|
+
e.preventDefault();
|
|
2752
|
+
first.focus();
|
|
2753
|
+
}
|
|
2754
|
+
};
|
|
2668
2755
|
window.addEventListener("keydown", onKey);
|
|
2669
|
-
|
|
2756
|
+
if (closeBtnRef.current) closeBtnRef.current.focus();
|
|
2757
|
+
return () => {
|
|
2758
|
+
window.removeEventListener("keydown", onKey);
|
|
2759
|
+
const opener = document.querySelector('[data-mneme-overlay-opener]');
|
|
2760
|
+
if (opener) opener.focus();
|
|
2761
|
+
};
|
|
2670
2762
|
}, [open]);
|
|
2671
2763
|
if (!open) return null;
|
|
2672
2764
|
// 背板与 sheet 是兄弟节点而不是嵌套:点击背板自身才关闭,sheet 内部
|
|
@@ -2677,7 +2769,7 @@ window.__ModuleLoader__.load({
|
|
|
2677
2769
|
onClick: () => setOpen(false),
|
|
2678
2770
|
"aria-hidden": "true"
|
|
2679
2771
|
}),
|
|
2680
|
-
h("div", { className: "mneme-overlay", role: "region", "aria-label": t("memory.view.label") },
|
|
2772
|
+
h("div", { className: "mneme-overlay", role: "region", "aria-label": t("memory.view.label"), ref: panelRef },
|
|
2681
2773
|
h("div", { className: "mneme-overlaybar" },
|
|
2682
2774
|
h("span", { className: "mneme-overlaytitle" },
|
|
2683
2775
|
h(IconArchiveOutline20, { size: 15 }),
|
|
@@ -2686,6 +2778,7 @@ window.__ModuleLoader__.load({
|
|
|
2686
2778
|
h("button", {
|
|
2687
2779
|
type: "button",
|
|
2688
2780
|
className: "mneme-footbtn",
|
|
2781
|
+
ref: closeBtnRef,
|
|
2689
2782
|
"aria-label": t("memory.overlay.close"),
|
|
2690
2783
|
title: t("memory.overlay.close"),
|
|
2691
2784
|
onClick: () => setOpen(false)
|
|
@@ -2706,7 +2799,7 @@ window.__ModuleLoader__.load({
|
|
|
2706
2799
|
// never blanks or blocks the others. ---
|
|
2707
2800
|
function StatusCard({ t, title, loading, error, num, cap }) {
|
|
2708
2801
|
return h("div", { className: "mneme-statuscard" },
|
|
2709
|
-
h("
|
|
2802
|
+
h("h3", { className: "mneme-xcolhead" }, title),
|
|
2710
2803
|
loading
|
|
2711
2804
|
? h("div", { className: "mneme-statusnum" }, "…")
|
|
2712
2805
|
: error
|
|
@@ -2987,7 +3080,7 @@ window.__ModuleLoader__.load({
|
|
|
2987
3080
|
|
|
2988
3081
|
// 巩固状态卡(×2):最近一次 autoDream 运行 + 待确认冲突计数。数据来自
|
|
2989
3082
|
// /dream-status,一次取回两张卡共用;失败只影响这两张卡自身。
|
|
2990
|
-
function DreamStatusCards({ t }) {
|
|
3083
|
+
function DreamStatusCards({ t, onGotoQueue }) {
|
|
2991
3084
|
const [state, setState] = useState({ loading: true, error: false, lastRun: null, pending: 0 });
|
|
2992
3085
|
useEffect(() => {
|
|
2993
3086
|
let cancelled = false;
|
|
@@ -3010,16 +3103,29 @@ window.__ModuleLoader__.load({
|
|
|
3010
3103
|
const cap = run
|
|
3011
3104
|
? `${run.status || "—"}${run.model ? ` · ${run.model}` : ""}`
|
|
3012
3105
|
: t("memory.settings.mode.offList");
|
|
3106
|
+
// #178:pending>0 时冲突卡可激活(role=button + 可聚焦 + 回车/空格),
|
|
3107
|
+
// 激活直达队列——StatusPanel 把 onGotoQueue 传进来(一步 prop,无线程)。
|
|
3108
|
+
const pending = state.pending > 0;
|
|
3109
|
+
const gotoQueue = () => { if (pending && onGotoQueue) onGotoQueue(); };
|
|
3013
3110
|
return h(react.Fragment, null,
|
|
3014
3111
|
h(StatusCard, { t, title: t("memory.status.dream"), loading: state.loading, error: state.error, num, cap }),
|
|
3015
|
-
h(
|
|
3016
|
-
|
|
3017
|
-
|
|
3018
|
-
|
|
3019
|
-
|
|
3020
|
-
|
|
3021
|
-
|
|
3022
|
-
}
|
|
3112
|
+
h("div", {
|
|
3113
|
+
role: pending ? "button" : undefined,
|
|
3114
|
+
tabIndex: pending ? 0 : undefined,
|
|
3115
|
+
"aria-label": pending ? t("memory.status.conflictQueue.goto") : undefined,
|
|
3116
|
+
style: pending ? { cursor: "pointer" } : undefined,
|
|
3117
|
+
onClick: pending ? gotoQueue : undefined,
|
|
3118
|
+
onKeyDown: pending ? (e) => { if (e.key === "Enter" || e.key === " ") { e.preventDefault(); gotoQueue(); } } : undefined
|
|
3119
|
+
},
|
|
3120
|
+
h(StatusCard, {
|
|
3121
|
+
t,
|
|
3122
|
+
title: t("memory.status.conflicts"),
|
|
3123
|
+
loading: state.loading,
|
|
3124
|
+
error: state.error,
|
|
3125
|
+
num: state.pending.toLocaleString(),
|
|
3126
|
+
cap: t("memory.status.conflictsHint")
|
|
3127
|
+
})
|
|
3128
|
+
)
|
|
3023
3129
|
);
|
|
3024
3130
|
}
|
|
3025
3131
|
|
|
@@ -3043,6 +3149,55 @@ window.__ModuleLoader__.load({
|
|
|
3043
3149
|
);
|
|
3044
3150
|
}
|
|
3045
3151
|
|
|
3152
|
+
// --- 注入预览(issue #179,状态页)---------------------------------------
|
|
3153
|
+
// 展示最近一帧 prompt 组装注入了什么:条目构成(类型/标题/重要性/字符数)、
|
|
3154
|
+
// hot memory 与总体积、当前生效参数(maxItems/threshold/自适应/scope/轮换)。
|
|
3155
|
+
// 数据来自 /inject-preview 的旁路快照——就是上次真实渲染用的同一份候选,
|
|
3156
|
+
// 不二次检索。无快照(autoInject 关/新会话/旧宿主)整卡退化为「暂无预览」。
|
|
3157
|
+
function InjectPreviewCard({ t }) {
|
|
3158
|
+
const [snap, setSnap] = useState(null);
|
|
3159
|
+
const [loading, setLoading] = useState(true);
|
|
3160
|
+
useEffect(() => {
|
|
3161
|
+
let cancelled = false;
|
|
3162
|
+
apiFetch("/api/dsh-mneme/inject-preview")
|
|
3163
|
+
.then((res) => { if (!res.ok) throw new Error("http"); return res.json(); })
|
|
3164
|
+
.then((j) => { if (!cancelled) { setSnap(j && j.snapshot); setLoading(false); } })
|
|
3165
|
+
.catch(() => { if (!cancelled) setLoading(false); });
|
|
3166
|
+
return () => { cancelled = true; };
|
|
3167
|
+
}, []);
|
|
3168
|
+
if (loading) return null;
|
|
3169
|
+
if (!snap) {
|
|
3170
|
+
return h("div", { className: "mneme-statuscard" },
|
|
3171
|
+
h("h3", { className: "mneme-xcolhead" }, t("memory.status.injectPreview")),
|
|
3172
|
+
h("div", { className: "mneme-statuscap" }, t("memory.status.injectPreviewNone"))
|
|
3173
|
+
);
|
|
3174
|
+
}
|
|
3175
|
+
const params = [
|
|
3176
|
+
`maxItems=${snap.maxItems}`,
|
|
3177
|
+
`threshold=${snap.threshold}`,
|
|
3178
|
+
snap.adaptive ? t("memory.status.injectPreview.adaptiveOn") : null,
|
|
3179
|
+
snap.scoped ? `scope=${snap.scoped.agent_scope || snap.scoped.workspace_scope}` : null,
|
|
3180
|
+
snap.rotated > 0 ? `${t("memory.status.injectPreview.rotated")} ${snap.rotated}` : null
|
|
3181
|
+
].filter(Boolean).join(" · ");
|
|
3182
|
+
return h("div", { className: "mneme-statuscard", style: { gridColumn: "1 / -1" } },
|
|
3183
|
+
h("h3", { className: "mneme-xcolhead" }, t("memory.status.injectPreview")),
|
|
3184
|
+
h("div", { className: "mneme-statuscap" },
|
|
3185
|
+
params,
|
|
3186
|
+
` · ${t("memory.status.injectPreview.chars").replace("{chars}", String(snap.totalChars))}`,
|
|
3187
|
+
snap.query ? ` · ${t("memory.status.injectPreview.query").replace("{query}", snap.query.slice(0, 24))}` : ""
|
|
3188
|
+
),
|
|
3189
|
+
snap.hotChars > 0 && h("div", { className: "mneme-statuscap", style: { marginTop: 4 } },
|
|
3190
|
+
`${t("memory.status.injectPreview.hot")} · ${snap.hotChars}${t("memory.status.injectPreview.charsUnit")}`),
|
|
3191
|
+
h("div", { style: { marginTop: 6 } },
|
|
3192
|
+
(snap.entries || []).length === 0
|
|
3193
|
+
? h("div", { className: "mneme-statuscap" }, t("memory.status.injectPreview.empty"))
|
|
3194
|
+
: (snap.entries || []).map((m) => h("div", { key: m.id, style: { display: "flex", gap: 8, alignItems: "baseline", fontSize: 12, padding: "2px 0" } },
|
|
3195
|
+
h("span", { className: "mneme-xcolhead" }, typeLabel(t, m.type)),
|
|
3196
|
+
h("span", { style: { flex: 1, overflow: "hidden", textOverflow: "ellipsis", whiteSpace: "nowrap" } }, m.title || "—"),
|
|
3197
|
+
h("span", { className: "mneme-xcolhead" }, `★${m.importance ?? "—"} · ${m.chars}${t("memory.status.injectPreview.charsUnit")}`))))
|
|
3198
|
+
);
|
|
3199
|
+
}
|
|
3200
|
+
|
|
3046
3201
|
// --- 冲突集中处理队列(v0.8.0,状态页)---------------------------------
|
|
3047
3202
|
// 此前冻结冲突只有计数与散落徽章,resolveConflictPending 无任何调用方——
|
|
3048
3203
|
// 这里是第一处理入口:并排展示双方内容 + reason,人工选保留方后走
|
|
@@ -3054,20 +3209,27 @@ window.__ModuleLoader__.load({
|
|
|
3054
3209
|
const load = () => {
|
|
3055
3210
|
apiFetch("/api/dsh-mneme/conflicts")
|
|
3056
3211
|
.then((res) => { if (!res.ok) throw new Error("http"); return res.json(); })
|
|
3057
|
-
.then((d) => setItems(d.items || []))
|
|
3212
|
+
.then((d) => { setItems(d.items || []); announce(t("memory.status.conflictQueue.refreshed")); })
|
|
3058
3213
|
.catch(() => setItems([]));
|
|
3059
3214
|
};
|
|
3060
3215
|
useEffect(() => { load(); }, []);
|
|
3061
3216
|
if (!items || items.length === 0) return null;
|
|
3062
3217
|
const resolve = (id, winner) => {
|
|
3218
|
+
if (busy) return;
|
|
3063
3219
|
setBusy(true);
|
|
3220
|
+
// #178:裁决结果经 live region 播报(纯视觉刷新读屏不可感知)
|
|
3221
|
+
const it = (items || []).find((x) => x.id === id) || {};
|
|
3064
3222
|
apiFetch("/api/dsh-mneme/conflicts/resolve", {
|
|
3065
3223
|
method: "POST",
|
|
3066
3224
|
headers: { "Content-Type": "application/json" },
|
|
3067
3225
|
body: JSON.stringify({ id, winner, apply: winner !== null })
|
|
3068
3226
|
})
|
|
3069
3227
|
.then((res) => { if (!res.ok) throw new Error("http"); return res.json(); })
|
|
3070
|
-
.then(() =>
|
|
3228
|
+
.then(() => {
|
|
3229
|
+
announce(t("memory.status.conflictQueue.resolved").replace("{name}",
|
|
3230
|
+
(winner === "a" ? (it.memory_a && it.memory_a.title) : winner === "b" ? (it.memory_b && it.memory_b.title) : "") || ""));
|
|
3231
|
+
load();
|
|
3232
|
+
})
|
|
3071
3233
|
.catch(() => {})
|
|
3072
3234
|
.finally(() => setBusy(false));
|
|
3073
3235
|
};
|
|
@@ -3093,8 +3255,8 @@ window.__ModuleLoader__.load({
|
|
|
3093
3255
|
side(it.memory_b, t("memory.status.conflictQueue.sideB"))),
|
|
3094
3256
|
h("div", { className: "mneme-conflict-actions" },
|
|
3095
3257
|
h("span", { className: "mneme-conflict-hint" }, t("memory.status.conflictQueue.applyHint")),
|
|
3096
|
-
h("button", { className: "mneme-footbtn", disabled: busy, onClick: () => resolve(it.id, "a") }, t("memory.status.conflictQueue.keepA")),
|
|
3097
|
-
h("button", { className: "mneme-footbtn", disabled: busy, onClick: () => resolve(it.id, "b") }, t("memory.status.conflictQueue.keepB")),
|
|
3258
|
+
h("button", { className: "mneme-footbtn", disabled: busy, "aria-label": `${t("memory.status.conflictQueue.keepA")}: ${(it.memory_a && it.memory_a.title) || ""}`, onClick: () => resolve(it.id, "a") }, t("memory.status.conflictQueue.keepA")),
|
|
3259
|
+
h("button", { className: "mneme-footbtn", disabled: busy, "aria-label": `${t("memory.status.conflictQueue.keepB")}: ${(it.memory_b && it.memory_b.title) || ""}`, onClick: () => resolve(it.id, "b") }, t("memory.status.conflictQueue.keepB")),
|
|
3098
3260
|
h("button", { className: "mneme-footbtn", disabled: busy, onClick: () => resolve(it.id, null) }, t("memory.status.conflictQueue.markReviewed"))))
|
|
3099
3261
|
));
|
|
3100
3262
|
}
|
|
@@ -3284,7 +3446,7 @@ window.__ModuleLoader__.load({
|
|
|
3284
3446
|
// 口径不可信)或库为空时整卡不渲染,前端不感知;truncated(扫描超上限)
|
|
3285
3447
|
// 只影响 hint 里的回执计数,不挡渲染。
|
|
3286
3448
|
function RecallStatsCard({ t }) {
|
|
3287
|
-
const [state, setState] = useState({ loading: true, off: false, rate: null, zombie: 0, active: 0, exempt: 0, runs: 0, top: "" });
|
|
3449
|
+
const [state, setState] = useState({ loading: true, off: false, rate: null, zombie: 0, active: 0, exempt: 0, runs: 0, top: "", inject: "" });
|
|
3288
3450
|
useEffect(() => {
|
|
3289
3451
|
let cancelled = false;
|
|
3290
3452
|
apiFetch("/api/dsh-mneme/recall-stats?window=30")
|
|
@@ -3301,10 +3463,18 @@ window.__ModuleLoader__.load({
|
|
|
3301
3463
|
const top = (d.topRecalled || []).slice(0, 3)
|
|
3302
3464
|
.map((m) => (m.title || "").slice(0, 12))
|
|
3303
3465
|
.join(" · ");
|
|
3466
|
+
// 注入口径(#217 增量):窗口内无注入行(旧数据/关闭落账)时整段省略
|
|
3467
|
+
const inj = d.injection || {};
|
|
3468
|
+
const inject = (inj.runs ?? 0) > 0
|
|
3469
|
+
? t("memory.status.recallInject")
|
|
3470
|
+
.replace("{runs}", String(inj.runs))
|
|
3471
|
+
.replace("{count}", String(inj.injectedCount ?? 0))
|
|
3472
|
+
.replace("{fill}", inj.slotFillRate == null ? "—" : String(Math.round(inj.slotFillRate * 100)))
|
|
3473
|
+
: "";
|
|
3304
3474
|
setState({
|
|
3305
3475
|
loading: false, off: false, rate,
|
|
3306
3476
|
zombie: z.zombieCount ?? 0, active: z.activeCount ?? 0,
|
|
3307
|
-
exempt: z.exemptCount ?? 0, runs: d.coverage?.runsScanned ?? 0, top
|
|
3477
|
+
exempt: z.exemptCount ?? 0, runs: d.coverage?.runsScanned ?? 0, top, inject
|
|
3308
3478
|
});
|
|
3309
3479
|
})
|
|
3310
3480
|
.catch(() => { if (!cancelled) setState({ loading: false, off: true }); });
|
|
@@ -3323,23 +3493,32 @@ window.__ModuleLoader__.load({
|
|
|
3323
3493
|
.replace("{active}", String(state.active))
|
|
3324
3494
|
.replace("{exempt}", String(state.exempt))
|
|
3325
3495
|
.replace("{runs}", String(state.runs))
|
|
3326
|
-
.replace("{top}", state.top || "—")
|
|
3496
|
+
.replace("{top}", state.top || "—") + (state.inject ? " · " + state.inject : "")
|
|
3327
3497
|
});
|
|
3328
3498
|
}
|
|
3329
3499
|
|
|
3330
3500
|
function StatusPanel({ t, onBrowse }) {
|
|
3501
|
+
// #178:冲突卡键盘直达——激活后把焦点与视口带到队列块。
|
|
3502
|
+
const queueRef = useRef(null);
|
|
3503
|
+
const gotoQueue = () => {
|
|
3504
|
+
if (!queueRef.current) return;
|
|
3505
|
+
queueRef.current.setAttribute("tabindex", "-1");
|
|
3506
|
+
queueRef.current.focus({ preventScroll: true });
|
|
3507
|
+
if (queueRef.current.scrollIntoView) queueRef.current.scrollIntoView({ block: "start", behavior: "smooth" });
|
|
3508
|
+
};
|
|
3331
3509
|
return h("div", { className: "mneme-status" },
|
|
3332
3510
|
h("div", { className: "mneme-statusgrid" },
|
|
3333
3511
|
h(MemoriesStatusCard, { t }),
|
|
3334
3512
|
h(EntitiesStatusCard, { t }),
|
|
3335
3513
|
h(VectorStatusCard, { t }),
|
|
3336
3514
|
h(LlmStatusCard, { t }),
|
|
3337
|
-
h(DreamStatusCards, { t }),
|
|
3515
|
+
h(DreamStatusCards, { t, onGotoQueue: gotoQueue }),
|
|
3338
3516
|
h(HeatStatusCard, { t }),
|
|
3339
3517
|
h(RecallStatsCard, { t }),
|
|
3340
|
-
h(InjectStatusCard, { t })
|
|
3518
|
+
h(InjectStatusCard, { t }),
|
|
3519
|
+
h(InjectPreviewCard, { t })
|
|
3341
3520
|
),
|
|
3342
|
-
h(ConflictsQueue, { t }),
|
|
3521
|
+
h("div", { ref: queueRef }, h(ConflictsQueue, { t })),
|
|
3343
3522
|
h(WorkbenchSection, { t, onBrowse })
|
|
3344
3523
|
);
|
|
3345
3524
|
}
|
|
@@ -3401,6 +3580,7 @@ window.__ModuleLoader__.load({
|
|
|
3401
3580
|
.then((d) => {
|
|
3402
3581
|
onSaved(d.memory, opts);
|
|
3403
3582
|
setEditing(false);
|
|
3583
|
+
announce(t("memory.explorer.detail.saved"));
|
|
3404
3584
|
setSavedTick(true);
|
|
3405
3585
|
setTimeout(() => setSavedTick(false), 2000);
|
|
3406
3586
|
})
|
|
@@ -4301,7 +4481,8 @@ window.__ModuleLoader__.load({
|
|
|
4301
4481
|
className: wide ? "mneme-trigger" : "mneme-trigger mneme-rail",
|
|
4302
4482
|
"aria-label": t("memory.sidebar.aria"),
|
|
4303
4483
|
title: t("memory.panel.open"),
|
|
4304
|
-
onClick: openLibrary
|
|
4484
|
+
onClick: openLibrary,
|
|
4485
|
+
"data-mneme-overlay-opener": "true"
|
|
4305
4486
|
},
|
|
4306
4487
|
h(IconArchiveOutline20, { size: wide ? 16 : 18 }),
|
|
4307
4488
|
wide && h("span", { className: "mneme-trigger-label" }, t("memory.panel.open"))
|
|
@@ -4388,7 +4569,8 @@ window.__ModuleLoader__.load({
|
|
|
4388
4569
|
className: `${nativeCls} mneme-topentry-native`.trim(),
|
|
4389
4570
|
"aria-label": t("memory.sidebar.aria"),
|
|
4390
4571
|
title: t("memory.panel.open"),
|
|
4391
|
-
onClick: openLibrary
|
|
4572
|
+
onClick: openLibrary,
|
|
4573
|
+
"data-mneme-overlay-opener": "true"
|
|
4392
4574
|
},
|
|
4393
4575
|
h(IconArchiveOutline20, { size: wide ? 15 : 18 }),
|
|
4394
4576
|
wide && h("span", { className: "mneme-topentry-label" }, t("memory.panel.open"))
|