@modusensus/dsh-mneme 0.7.8 → 0.7.10
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/CHANGELOG.md +1 -1
- package/CONTRIBUTING.md +24 -8
- package/README.md +7 -5
- package/SECURITY.md +48 -4
- package/dsh-mneme/CHANGELOG.md +24 -0
- package/dsh-mneme/README.md +9 -6
- package/dsh-mneme/lib/api.js +20 -1
- package/dsh-mneme/lib/client.js +324 -132
- package/dsh-mneme/lib/inject.js +2 -2
- package/dsh-mneme/lib/summarize.js +3 -1
- package/dsh-mneme/package-lock.json +2 -2
- package/dsh-mneme/package.json +1 -1
- package/dsh-mneme/scripts/check-sync.js +42 -0
- package/dsh-mneme/scripts/e2e-dsh.js +4 -3
- package/dsh-mneme/src/api.js +20 -1
- package/dsh-mneme/src/client.js +324 -132
- package/dsh-mneme/test/client.test.js +12 -1
- package/dsh-mneme/test/lib-smoke.test.js +109 -0
- package/package.json +2 -1
package/dsh-mneme/lib/client.js
CHANGED
|
@@ -69,11 +69,11 @@ window.__ModuleLoader__.load({
|
|
|
69
69
|
"memory.tab.fact": "事实",
|
|
70
70
|
"memory.settings.title": "记忆库设置",
|
|
71
71
|
"memory.settings.profile": "用户画像",
|
|
72
|
-
"memory.settings.profileHint": "
|
|
72
|
+
"memory.settings.profileHint": "写一段自我介绍(角色、背景、偏好),Agent 每轮对话都会自动带上。",
|
|
73
73
|
"memory.settings.profileSave": "保存画像",
|
|
74
74
|
"memory.settings.profileSaved": "画像已保存",
|
|
75
75
|
"memory.settings.rules": "规则",
|
|
76
|
-
"memory.settings.rulesHint": "Agent
|
|
76
|
+
"memory.settings.rulesHint": "给 Agent 立下必须遵守的规矩,随时增删,下一轮即生效。",
|
|
77
77
|
"memory.settings.autoTagTitle": "自动打标签",
|
|
78
78
|
"memory.settings.autoTagHint": "开启后,新记忆由轻量模型自动打标签,目录页按标签自动分类(opt-in,默认关)",
|
|
79
79
|
"memory.settings.autoTagEnabled": "自动打标签",
|
|
@@ -87,7 +87,7 @@ window.__ModuleLoader__.load({
|
|
|
87
87
|
"memory.settings.ruleAdd": "添加规则",
|
|
88
88
|
"memory.settings.rulePlaceholder": "例如:回答时总是先给结论",
|
|
89
89
|
"memory.settings.commands": "自定义指令",
|
|
90
|
-
"memory.settings.commandsHint": "
|
|
90
|
+
"memory.settings.commandsHint": "把常用提示词存成 / 命令,聊天时直接键入调用。",
|
|
91
91
|
"memory.settings.cmdName": "命令名",
|
|
92
92
|
"memory.settings.cmdDesc": "描述",
|
|
93
93
|
"memory.settings.cmdInstruction": "指令内容",
|
|
@@ -156,12 +156,13 @@ window.__ModuleLoader__.load({
|
|
|
156
156
|
"memory.graph.related": "关联记忆",
|
|
157
157
|
"memory.graph.relation": "关系",
|
|
158
158
|
"memory.graph.sourceMemory": "查看来源记忆",
|
|
159
|
-
"memory.graph.hint": "
|
|
159
|
+
"memory.graph.hint": "拖拽节点调整布局 · 空白处拖动平移 · 滚轮缩放",
|
|
160
|
+
"memory.graph.resetView": "重置视图",
|
|
160
161
|
"memory.graph.viewInGraph": "在图谱中查看",
|
|
161
162
|
"memory.graph.loading": "加载中…",
|
|
162
163
|
"memory.graph.distance": "距中心 {n} 跳",
|
|
163
164
|
"memory.settings.vectorTitle": "向量搜索",
|
|
164
|
-
"memory.settings.vectorHint": "
|
|
165
|
+
"memory.settings.vectorHint": "搜索不再只认字面:接入任意 OpenAI 兼容的 embedding 接口后,意思相近的记忆也能被召回。",
|
|
165
166
|
"memory.settings.vectorEnabled": "启用向量搜索",
|
|
166
167
|
"memory.settings.vectorBaseUrl": "API 地址 (Base URL)",
|
|
167
168
|
"memory.settings.vectorApiKey": "API Key",
|
|
@@ -172,7 +173,7 @@ window.__ModuleLoader__.load({
|
|
|
172
173
|
"memory.settings.vectorReindexing": "索引中…",
|
|
173
174
|
"memory.settings.vectorReindexDone": "已索引 {n} 条",
|
|
174
175
|
"memory.settings.apiTokenTitle": "API Token(可选)",
|
|
175
|
-
"memory.settings.apiTokenHint": "
|
|
176
|
+
"memory.settings.apiTokenHint": "给面板的写操作加一道锁:设置后,修改记忆或配置需要携带 Token,纯浏览不受影响。清空并保存可关闭。",
|
|
176
177
|
"memory.settings.apiTokenPlaceholder": "留空 = 不鉴权(默认)",
|
|
177
178
|
"memory.settings.apiTokenSave": "保存 Token",
|
|
178
179
|
"memory.settings.apiTokenSaved": "Token 已保存",
|
|
@@ -194,11 +195,11 @@ window.__ModuleLoader__.load({
|
|
|
194
195
|
"memory.tab.fact": "Facts",
|
|
195
196
|
"memory.settings.title": "Memory Settings",
|
|
196
197
|
"memory.settings.profile": "User Profile",
|
|
197
|
-
"memory.settings.profileHint": "Describe yourself — the agent
|
|
198
|
+
"memory.settings.profileHint": "Describe yourself once — the agent reads it every turn.",
|
|
198
199
|
"memory.settings.profileSave": "Save Profile",
|
|
199
200
|
"memory.settings.profileSaved": "Profile saved",
|
|
200
201
|
"memory.settings.rules": "Rules",
|
|
201
|
-
"memory.settings.rulesHint": "
|
|
202
|
+
"memory.settings.rulesHint": "Rules the agent must follow — add or remove anytime, effective next turn.",
|
|
202
203
|
"memory.settings.autoTagTitle": "Auto-Tag",
|
|
203
204
|
"memory.settings.autoTagHint": "When on, new memories are auto-tagged by a light model and the directory auto-grouped by tag (opt-in, off by default)",
|
|
204
205
|
"memory.settings.autoTagEnabled": "Auto-tagging",
|
|
@@ -212,7 +213,7 @@ window.__ModuleLoader__.load({
|
|
|
212
213
|
"memory.settings.ruleAdd": "Add Rule",
|
|
213
214
|
"memory.settings.rulePlaceholder": "e.g. Always lead with a conclusion",
|
|
214
215
|
"memory.settings.commands": "Custom Commands",
|
|
215
|
-
"memory.settings.commandsHint": "
|
|
216
|
+
"memory.settings.commandsHint": "Save prompts as slash commands and invoke them right from the composer.",
|
|
216
217
|
"memory.settings.cmdName": "Name",
|
|
217
218
|
"memory.settings.cmdDesc": "Description",
|
|
218
219
|
"memory.settings.cmdInstruction": "Instruction",
|
|
@@ -281,12 +282,13 @@ window.__ModuleLoader__.load({
|
|
|
281
282
|
"memory.graph.related": "Related memories",
|
|
282
283
|
"memory.graph.relation": "Relation",
|
|
283
284
|
"memory.graph.sourceMemory": "View source memory",
|
|
284
|
-
"memory.graph.hint": "
|
|
285
|
+
"memory.graph.hint": "Drag nodes to rearrange · drag the background to pan · scroll to zoom",
|
|
286
|
+
"memory.graph.resetView": "Reset view",
|
|
285
287
|
"memory.graph.viewInGraph": "View in graph",
|
|
286
288
|
"memory.graph.loading": "Loading…",
|
|
287
289
|
"memory.graph.distance": "{n} hop(s) from root",
|
|
288
290
|
"memory.settings.vectorTitle": "Vector Search",
|
|
289
|
-
"memory.settings.vectorHint": "
|
|
291
|
+
"memory.settings.vectorHint": "Match by meaning, not just wording — connect any OpenAI-compatible embeddings API.",
|
|
290
292
|
"memory.settings.vectorEnabled": "Enable vector search",
|
|
291
293
|
"memory.settings.vectorBaseUrl": "Base URL",
|
|
292
294
|
"memory.settings.vectorApiKey": "API Key",
|
|
@@ -297,7 +299,7 @@ window.__ModuleLoader__.load({
|
|
|
297
299
|
"memory.settings.vectorReindexing": "Indexing…",
|
|
298
300
|
"memory.settings.vectorReindexDone": "Indexed {n} items",
|
|
299
301
|
"memory.settings.apiTokenTitle": "API Token (optional)",
|
|
300
|
-
"memory.settings.apiTokenHint": "
|
|
302
|
+
"memory.settings.apiTokenHint": "Lock write operations behind a token — browsing stays open. Clear and save to disable.",
|
|
301
303
|
"memory.settings.apiTokenPlaceholder": "Empty = no auth (default)",
|
|
302
304
|
"memory.settings.apiTokenSave": "Save Token",
|
|
303
305
|
"memory.settings.apiTokenSaved": "Token saved",
|
|
@@ -315,6 +317,14 @@ window.__ModuleLoader__.load({
|
|
|
315
317
|
return label && label !== key ? label : String(type);
|
|
316
318
|
}
|
|
317
319
|
|
|
320
|
+
// Entity types live under their own i18n subtree (person/project/...);
|
|
321
|
+
// unknown or "other" falls back to the raw type string.
|
|
322
|
+
function entityTypeLabel(t, type) {
|
|
323
|
+
const key = `memory.entity.${type}`;
|
|
324
|
+
const label = t(key);
|
|
325
|
+
return label && label !== key ? label : String(type);
|
|
326
|
+
}
|
|
327
|
+
|
|
318
328
|
function formatDate(value) {
|
|
319
329
|
if (!value) return "—";
|
|
320
330
|
const date = new Date(value);
|
|
@@ -392,10 +402,10 @@ window.__ModuleLoader__.load({
|
|
|
392
402
|
".mneme-xsearch{width:100%}",
|
|
393
403
|
".mneme-xselect{flex:1;min-width:0}",
|
|
394
404
|
".mneme-xcolhead{flex:none;font-size:12px;font-weight:500;color:var(--dsw-alias-label-tertiary);padding:2px 8px 8px}",
|
|
395
|
-
".mneme-xtype{display:flex;justify-content:
|
|
405
|
+
".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}",
|
|
396
406
|
".mneme-xtype:hover{background:var(--dsw-alias-interactive-bg-hover)}",
|
|
397
407
|
".mneme-xtype.mneme-active{background:var(--dsw-alias-interactive-bg-active);color:var(--dsw-alias-label-primary);font-weight:500}",
|
|
398
|
-
".mneme-xcount2{flex:none;font-size:12px;color:var(--dsw-alias-label-tertiary)}",
|
|
408
|
+
".mneme-xcount2{flex:none;margin-left:auto;font-size:12px;color:var(--dsw-alias-label-tertiary)}",
|
|
399
409
|
".mneme-xmonth{display:flex;align-items:center;gap:4px;width:100%;padding:6px 8px 4px;border:none;border-radius:8px;background:none;color:var(--dsw-alias-label-primary);cursor:pointer;font-family:inherit;font-size:13px;font-weight:500;line-height:18px;text-align:left}",
|
|
400
410
|
".mneme-xmonth:first-child{margin-top:0}",
|
|
401
411
|
".mneme-xmonth:hover{background:var(--dsw-alias-interactive-bg-hover)}",
|
|
@@ -407,7 +417,11 @@ window.__ModuleLoader__.load({
|
|
|
407
417
|
".mneme-xitem.mneme-active{background:var(--dsw-alias-interactive-bg-active);color:var(--dsw-alias-label-primary)}",
|
|
408
418
|
".mneme-xtime{flex:none;font-size:12px;line-height:16px;color:var(--dsw-alias-label-tertiary);font-variant-numeric:tabular-nums}",
|
|
409
419
|
".mneme-xname{min-width:0;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}",
|
|
410
|
-
".mneme-xempty{padding:
|
|
420
|
+
".mneme-xempty{padding:18px 16px;text-align:center;color:var(--dsw-alias-label-tertiary);font-size:13px}",
|
|
421
|
+
// --- memory-type color dots (mirrored across filter / timeline / detail) ---
|
|
422
|
+
".mneme-xdot{flex:none;display:inline-block;width:8px;height:8px;border-radius:50%;background:currentColor}",
|
|
423
|
+
".mneme-xdot--all{background:transparent;border:1.5px solid currentColor;opacity:.55;box-sizing:border-box}",
|
|
424
|
+
".mneme-xitem .mneme-xdot{align-self:center}",
|
|
411
425
|
// --- memory explorer root ---
|
|
412
426
|
".mneme-xmain{display:flex;flex-direction:column}",
|
|
413
427
|
".mneme-xfilter-bar{flex:none;height:48px;display:flex;align-items:center;gap:8px;padding:0 12px;border-bottom:1px solid var(--dsw-alias-border-l2,#ddd)}",
|
|
@@ -419,6 +433,7 @@ window.__ModuleLoader__.load({
|
|
|
419
433
|
".mneme-xdinner{max-width:720px}",
|
|
420
434
|
".mneme-xdtitle{font-size:16px;font-weight:600;line-height:24px;color:var(--dsw-alias-label-primary);margin-bottom:10px;word-break:break-word}",
|
|
421
435
|
".mneme-xdmeta{display:flex;flex-wrap:wrap;gap:4px 14px;margin-bottom:6px;font-size:12px;line-height:18px;color:var(--dsw-alias-label-tertiary)}",
|
|
436
|
+
".mneme-xdsrc{display:inline-block;max-width:200px;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;vertical-align:bottom}",
|
|
422
437
|
".mneme-xdcontent{margin-top:14px;font-size:14px;line-height:1.75;color:var(--dsw-alias-label-primary);white-space:pre-wrap;word-break:break-word}",
|
|
423
438
|
".mneme-xdactions{display:flex;gap:8px;margin-top:18px}",
|
|
424
439
|
// --- wiki-link backlinks panel (detail pane footer) ---
|
|
@@ -486,7 +501,28 @@ window.__ModuleLoader__.load({
|
|
|
486
501
|
".mneme-dir-itime{flex:none;font-size:12px;line-height:16px;color:var(--dsw-alias-label-tertiary);font-variant-numeric:tabular-nums}",
|
|
487
502
|
".mneme-dir-empty{color:var(--dsw-alias-label-tertiary);padding:24px 0;text-align:center;font-size:13px}",
|
|
488
503
|
// --- settings sub-view ---
|
|
489
|
-
".mneme-xsettings{flex:1;min-height:0;overflow-y:auto;padding:
|
|
504
|
+
".mneme-xsettings{flex:1;min-height:0;overflow-y:auto;padding:8px 24px 48px;box-sizing:border-box}",
|
|
505
|
+
".mneme-set-sec{padding:20px 0 24px;border-bottom:1px solid var(--dsw-alias-border-l2)}",
|
|
506
|
+
".mneme-xsettings .mneme-set-sec:last-child{border-bottom:none}",
|
|
507
|
+
".mneme-set-title{font-size:14px;font-weight:600;color:var(--dsw-alias-label-primary);margin-bottom:4px}",
|
|
508
|
+
".mneme-set-desc{font-size:13px;line-height:19px;color:var(--dsw-alias-label-tertiary);margin-bottom:14px}",
|
|
509
|
+
".mneme-set-input{box-sizing:border-box;width:100%;height:34px;padding:0 12px;border-radius:8px;border:1px solid var(--dsw-alias-border-l2);background:var(--dsw-alias-bg-base,transparent);color:var(--dsw-alias-label-primary);font-family:inherit;font-size:13px;outline:none;margin-bottom:10px;transition:border-color .12s,box-shadow .12s}",
|
|
510
|
+
".mneme-set-input:focus{border-color:var(--dsw-alias-state-business-primary);box-shadow:0 0 0 3px color-mix(in srgb,var(--dsw-alias-state-business-primary) 15%,transparent)}",
|
|
511
|
+
".mneme-set-input::placeholder{color:var(--dsw-alias-label-tertiary)}",
|
|
512
|
+
".mneme-set-input--area{height:auto;min-height:76px;padding:8px 12px;resize:vertical;line-height:1.6}",
|
|
513
|
+
".mneme-btn{flex:none;height:30px;padding:0 12px;border-radius:8px;border:1px solid var(--dsw-alias-border-l2);background:none;color:var(--dsw-alias-label-primary);cursor:pointer;font-family:inherit;font-size:12px;line-height:16px;display:inline-flex;align-items:center;gap:6px}",
|
|
514
|
+
".mneme-btn:hover{background:var(--dsw-alias-interactive-bg-hover)}",
|
|
515
|
+
".mneme-btn:disabled{opacity:.5;cursor:default}",
|
|
516
|
+
".mneme-saved{font-size:12px;color:var(--dsw-alias-state-success,#2a7)}",
|
|
517
|
+
".mneme-set-row{display:flex;align-items:flex-start;gap:10px;padding:10px 12px;border:1px solid var(--dsw-alias-border-l2);border-radius:10px;margin-bottom:8px;background:var(--dsw-alias-bg-base,transparent)}",
|
|
518
|
+
".mneme-set-idx{flex:none;width:20px;height:20px;border-radius:50%;border:1px solid var(--dsw-alias-border-l2);color:var(--dsw-alias-label-tertiary);font-size:11px;line-height:18px;display:inline-flex;align-items:center;justify-content:center;font-variant-numeric:tabular-nums}",
|
|
519
|
+
".mneme-set-ruletext{flex:1;min-width:0;font-size:13px;line-height:20px;color:var(--dsw-alias-label-primary);word-break:break-word}",
|
|
520
|
+
".mneme-set-del{flex:none;opacity:0;transition:opacity .12s;border:none;background:none;color:var(--dsw-alias-label-tertiary);cursor:pointer;font-size:14px;line-height:20px;padding:0 4px;border-radius:6px}",
|
|
521
|
+
".mneme-set-row:hover .mneme-set-del,.mneme-set-row:focus-within .mneme-set-del{opacity:1}",
|
|
522
|
+
".mneme-set-del:hover{color:var(--dsw-alias-state-error,#c33);background:var(--dsw-alias-interactive-bg-hover)}",
|
|
523
|
+
".mneme-set-cmd{flex:1;min-width:0}",
|
|
524
|
+
".mneme-set-cmdname{font-size:13px;font-weight:600;color:var(--dsw-alias-state-business-primary)}",
|
|
525
|
+
".mneme-set-cmddesc{font-size:12px;line-height:17px;color:var(--dsw-alias-label-tertiary);margin-top:1px;word-break:break-word}",
|
|
490
526
|
".mneme-xsettings-inner{max-width:640px}",
|
|
491
527
|
// --- overview sub-view (layered stats dashboard, .mneme-ov-* only) ---
|
|
492
528
|
".mneme-ov-wrap{flex:1;min-height:0;overflow-y:auto;display:flex;flex-direction:column;gap:12px;padding:12px 16px 28px}",
|
|
@@ -553,6 +589,31 @@ window.__ModuleLoader__.load({
|
|
|
553
589
|
function typeColor(type) {
|
|
554
590
|
return TYPE_COLORS[type] || "#94a3b8";
|
|
555
591
|
}
|
|
592
|
+
|
|
593
|
+
// Memory-type → dot fill, shared by the type filter, timeline rows and the
|
|
594
|
+
// detail meta so one hue always means one memory type across the panel.
|
|
595
|
+
// Same palette rules as TYPE_COLORS: fixed hues, medium saturation,
|
|
596
|
+
// similar luminance, legible on light and dark themes.
|
|
597
|
+
const MEMORY_TYPE_COLORS = {
|
|
598
|
+
preference: "#f59e0b",
|
|
599
|
+
project: "#22c55e",
|
|
600
|
+
decision: "#3b82f6",
|
|
601
|
+
summary: "#a855f7",
|
|
602
|
+
user: "#f59e0b",
|
|
603
|
+
fact: "#22c55e",
|
|
604
|
+
history: "#94a3b8"
|
|
605
|
+
};
|
|
606
|
+
function memoryTypeColor(type) {
|
|
607
|
+
return MEMORY_TYPE_COLORS[type] || "#94a3b8";
|
|
608
|
+
}
|
|
609
|
+
|
|
610
|
+
// Compact date for the detail meta ("2026/9/7"); the full timestamp
|
|
611
|
+
// stays in the tooltip.
|
|
612
|
+
function formatDateShort(value) {
|
|
613
|
+
if (!value) return "—";
|
|
614
|
+
const date = new Date(value);
|
|
615
|
+
return Number.isNaN(date.getTime()) ? "—" : date.toLocaleDateString();
|
|
616
|
+
}
|
|
556
617
|
function nodeRadius(n) {
|
|
557
618
|
// mention_count → area-ish growth, clamped so hubs stay legible.
|
|
558
619
|
const base = 7 + Math.min(20, Math.max(1, n.mention_count ?? 1)) * 0.55;
|
|
@@ -681,6 +742,7 @@ window.__ModuleLoader__.load({
|
|
|
681
742
|
const svgRef = useRef(null);
|
|
682
743
|
const posRef = useRef([]); // live simulation positions, not React state
|
|
683
744
|
const dragRef = useRef(null); // { id, moved }
|
|
745
|
+
const viewRef = useRef({ x: 0, y: 0, k: 1 }); // pan/zoom viewport, viewBox units
|
|
684
746
|
const [frame, setFrame] = useState(0); // re-render tick driven by the simulation
|
|
685
747
|
|
|
686
748
|
useEffect(() => {
|
|
@@ -786,12 +848,13 @@ window.__ModuleLoader__.load({
|
|
|
786
848
|
// drag handling on the svg surface
|
|
787
849
|
const onNodeMouseDown = (e, node) => {
|
|
788
850
|
e.preventDefault();
|
|
851
|
+
e.stopPropagation(); // node drag must not start a background pan
|
|
789
852
|
dragRef.current = { node, moved: false };
|
|
790
853
|
const startX = e.clientX, startY = e.clientY;
|
|
791
854
|
const origX = node.x, origY = node.y;
|
|
792
855
|
const svg = svgRef.current;
|
|
793
856
|
const rect = svg.getBoundingClientRect();
|
|
794
|
-
const scale = 380 / Math.max(1, rect.width); // viewBox
|
|
857
|
+
const scale = 380 / Math.max(1, rect.width) / viewRef.current.k; // viewBox units per css px, zoom-aware
|
|
795
858
|
const onMove = (ev) => {
|
|
796
859
|
dragRef.current.moved = true;
|
|
797
860
|
node.x = origX + (ev.clientX - startX) * scale;
|
|
@@ -807,6 +870,70 @@ window.__ModuleLoader__.load({
|
|
|
807
870
|
window.addEventListener("mouseup", onUp);
|
|
808
871
|
};
|
|
809
872
|
|
|
873
|
+
// --- canvas pan & zoom ---
|
|
874
|
+
// The viewport (translate + scale) lives in viewRef so the simulation
|
|
875
|
+
// and the handlers share it without re-mounting; every mutation bumps
|
|
876
|
+
// `frame` to re-render. Panning starts on the bare svg surface (node
|
|
877
|
+
// mousedowns stopPropagation above); zooming is a native non-passive
|
|
878
|
+
// wheel listener because React attaches wheel handlers passively and
|
|
879
|
+
// could not preventDefault the page scroll.
|
|
880
|
+
const svgToView = (clientX, clientY) => {
|
|
881
|
+
const svg = svgRef.current;
|
|
882
|
+
const rect = svg.getBoundingClientRect();
|
|
883
|
+
const sx = VIEW_W / Math.max(1, rect.width);
|
|
884
|
+
const sy = VIEW_H / Math.max(1, rect.height);
|
|
885
|
+
return { x: (clientX - rect.left) * sx, y: (clientY - rect.top) * sy };
|
|
886
|
+
};
|
|
887
|
+
|
|
888
|
+
const onSurfaceMouseDown = (e) => {
|
|
889
|
+
if (e.button !== 0) return;
|
|
890
|
+
e.preventDefault();
|
|
891
|
+
const start = { x: e.clientX, y: e.clientY };
|
|
892
|
+
const orig = { ...viewRef.current };
|
|
893
|
+
const onMove = (ev) => {
|
|
894
|
+
const rect = svgRef.current?.getBoundingClientRect();
|
|
895
|
+
if (!rect) return;
|
|
896
|
+
const sx = VIEW_W / Math.max(1, rect.width);
|
|
897
|
+
const sy = VIEW_H / Math.max(1, rect.height);
|
|
898
|
+
const v = viewRef.current;
|
|
899
|
+
v.x = orig.x + (ev.clientX - start.x) * sx;
|
|
900
|
+
v.y = orig.y + (ev.clientY - start.y) * sy;
|
|
901
|
+
setFrame((f) => f + 1);
|
|
902
|
+
};
|
|
903
|
+
const onUp = () => {
|
|
904
|
+
window.removeEventListener("mousemove", onMove);
|
|
905
|
+
window.removeEventListener("mouseup", onUp);
|
|
906
|
+
};
|
|
907
|
+
window.addEventListener("mousemove", onMove);
|
|
908
|
+
window.addEventListener("mouseup", onUp);
|
|
909
|
+
};
|
|
910
|
+
|
|
911
|
+
useEffect(() => {
|
|
912
|
+
const svg = svgRef.current;
|
|
913
|
+
if (!svg) return undefined;
|
|
914
|
+
const onWheel = (e) => {
|
|
915
|
+
e.preventDefault();
|
|
916
|
+
const v = viewRef.current;
|
|
917
|
+
const factor = e.deltaY < 0 ? 1.12 : 1 / 1.12;
|
|
918
|
+
const k = Math.min(3, Math.max(0.5, v.k * factor));
|
|
919
|
+
const s = svgToView(e.clientX, e.clientY);
|
|
920
|
+
// keep the point under the cursor pinned while the scale changes
|
|
921
|
+
const px = (s.x - v.x) / v.k;
|
|
922
|
+
const py = (s.y - v.y) / v.k;
|
|
923
|
+
v.k = k;
|
|
924
|
+
v.x = s.x - px * k;
|
|
925
|
+
v.y = s.y - py * k;
|
|
926
|
+
setFrame((f) => f + 1);
|
|
927
|
+
};
|
|
928
|
+
svg.addEventListener("wheel", onWheel, { passive: false });
|
|
929
|
+
return () => svg.removeEventListener("wheel", onWheel);
|
|
930
|
+
});
|
|
931
|
+
|
|
932
|
+
const resetView = () => {
|
|
933
|
+
viewRef.current = { x: 0, y: 0, k: 1 };
|
|
934
|
+
setFrame((f) => f + 1);
|
|
935
|
+
};
|
|
936
|
+
|
|
810
937
|
const onNodeClick = (node) => {
|
|
811
938
|
if (dragRef.current?.moved) return; // it was a drag, not a click
|
|
812
939
|
setSelected({ kind: "node", node });
|
|
@@ -871,7 +998,12 @@ window.__ModuleLoader__.load({
|
|
|
871
998
|
className: depth === 2 ? "mneme-chip mneme-active" : "mneme-chip",
|
|
872
999
|
title: t("memory.graph.depth"),
|
|
873
1000
|
onClick: () => setDepth(depth === 1 ? 2 : 1)
|
|
874
|
-
}, `${depth} ${t("memory.graph.depth")}`)
|
|
1001
|
+
}, `${depth} ${t("memory.graph.depth")}`),
|
|
1002
|
+
h("button", {
|
|
1003
|
+
className: "mneme-chip",
|
|
1004
|
+
title: t("memory.graph.resetView"),
|
|
1005
|
+
onClick: resetView
|
|
1006
|
+
}, t("memory.graph.resetView"))
|
|
875
1007
|
),
|
|
876
1008
|
status === "idle" && h("div", { className: "mneme-hint" }, t("memory.graph.empty")),
|
|
877
1009
|
status === "loading" && h("div", { className: "mneme-hint" }, t("memory.graph.loading")),
|
|
@@ -883,8 +1015,13 @@ window.__ModuleLoader__.load({
|
|
|
883
1015
|
h("svg", {
|
|
884
1016
|
ref: svgRef,
|
|
885
1017
|
className: "mneme-graphsvg",
|
|
886
|
-
viewBox: `0 0 ${VIEW_W} ${VIEW_H}
|
|
1018
|
+
viewBox: `0 0 ${VIEW_W} ${VIEW_H}`,
|
|
1019
|
+
style: { height: 320 },
|
|
1020
|
+
onMouseDown: onSurfaceMouseDown
|
|
887
1021
|
},
|
|
1022
|
+
h("g", {
|
|
1023
|
+
transform: `translate(${viewRef.current.x},${viewRef.current.y}) scale(${viewRef.current.k})`
|
|
1024
|
+
},
|
|
888
1025
|
data.edges.map((e) => {
|
|
889
1026
|
const a = nodeById.get(e.from), b = nodeById.get(e.to);
|
|
890
1027
|
if (!a || !b) return null;
|
|
@@ -906,6 +1043,7 @@ window.__ModuleLoader__.load({
|
|
|
906
1043
|
h("circle", { r: nodeRadius(n), fill: typeColor(n.type), fillOpacity: nodeOpacity(n) }),
|
|
907
1044
|
h("text", { className: "mneme-glabel", y: nodeRadius(n) + 13 }, n.name)
|
|
908
1045
|
))
|
|
1046
|
+
)
|
|
909
1047
|
),
|
|
910
1048
|
h("div", { className: "mneme-graphhint" }, t("memory.graph.hint"))
|
|
911
1049
|
)),
|
|
@@ -1078,109 +1216,135 @@ window.__ModuleLoader__.load({
|
|
|
1078
1216
|
} catch { /* ignore */ }
|
|
1079
1217
|
}
|
|
1080
1218
|
|
|
1081
|
-
|
|
1082
|
-
|
|
1083
|
-
|
|
1084
|
-
|
|
1085
|
-
|
|
1086
|
-
|
|
1087
|
-
|
|
1088
|
-
|
|
1089
|
-
|
|
1090
|
-
|
|
1091
|
-
h("
|
|
1092
|
-
|
|
1093
|
-
|
|
1094
|
-
apiTokenSaved && h("div", { style: { fontSize: 12, color: "var(--dsw-alias-state-success, #2a7)" } }, t("memory.settings.apiTokenSaved")),
|
|
1095
|
-
// profile
|
|
1096
|
-
h("div", { style: labelStyle }, t("memory.settings.profile")),
|
|
1097
|
-
h("div", { style: hintStyle }, t("memory.settings.profileHint")),
|
|
1098
|
-
h("textarea", {
|
|
1099
|
-
style: { ...inputStyle, minHeight: 72, resize: "vertical", fontFamily: "inherit", padding: "8px 12px", height: "auto" },
|
|
1100
|
-
value: profile,
|
|
1101
|
-
placeholder: t("memory.settings.profile"),
|
|
1102
|
-
onChange: (e) => setProfile(e.target.value)
|
|
1103
|
-
}),
|
|
1104
|
-
h("div", null,
|
|
1105
|
-
h("button", { style: styles.footerButton, onClick: saveProfile }, t("memory.settings.profileSave")),
|
|
1106
|
-
saved && h("span", { style: { fontSize: 12, color: "var(--dsw-alias-state-success, #2a7)" } }, t("memory.settings.profileSaved"))
|
|
1107
|
-
),
|
|
1108
|
-
// rules
|
|
1109
|
-
h("div", { style: labelStyle }, t("memory.settings.rules")),
|
|
1110
|
-
h("div", { style: hintStyle }, t("memory.settings.rulesHint")),
|
|
1111
|
-
rules.length === 0 && h("div", { style: { fontSize: 12, color: "var(--dsw-alias-label-tertiary, #999)", padding: "8px 0" } }, t("memory.settings.empty")),
|
|
1112
|
-
rules.map((rule, i) =>
|
|
1113
|
-
h("div", { key: i, style: rowStyle },
|
|
1114
|
-
h("span", { style: { fontSize: 13, flex: 1 } }, rule),
|
|
1115
|
-
h("button", { style: { ...styles.footerButton, color: "var(--dsw-alias-state-error, #c33)" }, onClick: () => removeRule(i) }, "×")
|
|
1219
|
+
return h("div", null,
|
|
1220
|
+
h("section", { className: "mneme-set-sec" },
|
|
1221
|
+
h("div", { className: "mneme-set-title" }, t("memory.settings.profile")),
|
|
1222
|
+
h("div", { className: "mneme-set-desc" }, t("memory.settings.profileHint")),
|
|
1223
|
+
h("textarea", {
|
|
1224
|
+
className: "mneme-set-input mneme-set-input--area",
|
|
1225
|
+
value: profile,
|
|
1226
|
+
placeholder: t("memory.settings.profile"),
|
|
1227
|
+
onChange: (e) => setProfile(e.target.value)
|
|
1228
|
+
}),
|
|
1229
|
+
h("div", { style: { display: "flex", alignItems: "center", gap: 8 } },
|
|
1230
|
+
h("button", { className: "mneme-btn", onClick: saveProfile }, t("memory.settings.profileSave")),
|
|
1231
|
+
saved && h("span", { className: "mneme-saved" }, t("memory.settings.profileSaved"))
|
|
1116
1232
|
)
|
|
1117
1233
|
),
|
|
1118
|
-
h("
|
|
1119
|
-
h("
|
|
1120
|
-
|
|
1121
|
-
|
|
1122
|
-
|
|
1123
|
-
|
|
1124
|
-
|
|
1125
|
-
|
|
1234
|
+
h("section", { className: "mneme-set-sec" },
|
|
1235
|
+
h("div", { className: "mneme-set-title" }, t("memory.settings.rules")),
|
|
1236
|
+
h("div", { className: "mneme-set-desc" }, t("memory.settings.rulesHint")),
|
|
1237
|
+
rules.length === 0 && h("div", { className: "mneme-xempty" }, t("memory.settings.empty")),
|
|
1238
|
+
rules.map((rule, i) =>
|
|
1239
|
+
h("div", { key: i, className: "mneme-set-row" },
|
|
1240
|
+
h("span", { className: "mneme-set-idx" }, String(i + 1)),
|
|
1241
|
+
h("span", { className: "mneme-set-ruletext" }, rule),
|
|
1242
|
+
h("button", {
|
|
1243
|
+
className: "mneme-set-del",
|
|
1244
|
+
title: t("memory.settings.cmdDelete"),
|
|
1245
|
+
"aria-label": t("memory.settings.cmdDelete"),
|
|
1246
|
+
onClick: () => removeRule(i)
|
|
1247
|
+
}, "×")
|
|
1248
|
+
)
|
|
1249
|
+
),
|
|
1250
|
+
h("div", { style: { display: "flex", gap: 8 } },
|
|
1251
|
+
h("input", {
|
|
1252
|
+
className: "mneme-set-input",
|
|
1253
|
+
style: { marginBottom: 0 },
|
|
1254
|
+
value: newRule,
|
|
1255
|
+
placeholder: t("memory.settings.rulePlaceholder"),
|
|
1256
|
+
onChange: (e) => setNewRule(e.target.value),
|
|
1257
|
+
onKeyDown: (e) => { if (e.key === "Enter") addRule(); }
|
|
1258
|
+
}),
|
|
1259
|
+
h("button", { className: "mneme-btn", onClick: addRule }, t("memory.settings.ruleAdd"))
|
|
1260
|
+
)
|
|
1126
1261
|
),
|
|
1127
|
-
|
|
1128
|
-
|
|
1129
|
-
|
|
1130
|
-
|
|
1131
|
-
|
|
1132
|
-
|
|
1133
|
-
|
|
1134
|
-
|
|
1135
|
-
|
|
1262
|
+
h("section", { className: "mneme-set-sec" },
|
|
1263
|
+
h("div", { className: "mneme-set-title" }, t("memory.settings.commands")),
|
|
1264
|
+
h("div", { className: "mneme-set-desc" }, t("memory.settings.commandsHint")),
|
|
1265
|
+
commands.length === 0 && h("div", { className: "mneme-xempty" }, t("memory.settings.empty")),
|
|
1266
|
+
commands.map((cmd) =>
|
|
1267
|
+
h("div", { key: cmd.id, className: "mneme-set-row" },
|
|
1268
|
+
h("div", { className: "mneme-set-cmd" },
|
|
1269
|
+
h("div", { className: "mneme-set-cmdname" }, `/${cmd.name}`),
|
|
1270
|
+
(cmd.description || cmd.instruction) && h("div", { className: "mneme-set-cmddesc" }, cmd.description || cmd.instruction)
|
|
1271
|
+
),
|
|
1272
|
+
h("button", {
|
|
1273
|
+
className: "mneme-set-del",
|
|
1274
|
+
title: t("memory.settings.cmdDelete"),
|
|
1275
|
+
"aria-label": t("memory.settings.cmdDelete"),
|
|
1276
|
+
onClick: () => removeCommand(cmd.id)
|
|
1277
|
+
}, "×")
|
|
1278
|
+
)
|
|
1279
|
+
),
|
|
1280
|
+
h("div", { style: { display: "grid", gap: 8, marginTop: 10 } },
|
|
1281
|
+
h("div", { style: { display: "flex", gap: 8 } },
|
|
1282
|
+
h("input", { className: "mneme-set-input", style: { marginBottom: 0 }, value: newCmd.name, placeholder: t("memory.settings.cmdName"), onChange: (e) => setNewCmd({ ...newCmd, name: e.target.value }) }),
|
|
1283
|
+
h("input", { className: "mneme-set-input", style: { marginBottom: 0 }, value: newCmd.description, placeholder: t("memory.settings.cmdDesc"), onChange: (e) => setNewCmd({ ...newCmd, description: e.target.value }) })
|
|
1136
1284
|
),
|
|
1137
|
-
h("
|
|
1285
|
+
h("textarea", { className: "mneme-set-input mneme-set-input--area", style: { minHeight: 56 }, value: newCmd.instruction, placeholder: t("memory.settings.cmdInstruction"), onChange: (e) => setNewCmd({ ...newCmd, instruction: e.target.value }) }),
|
|
1286
|
+
h("div", { style: { display: "flex", alignItems: "center", gap: 8 } },
|
|
1287
|
+
h("button", { className: "mneme-btn", onClick: addCommand }, t("memory.settings.cmdAdd")),
|
|
1288
|
+
cmdError && h("span", { style: { fontSize: 12, color: "var(--dsw-alias-state-error, #c33)" } }, cmdError)
|
|
1289
|
+
)
|
|
1138
1290
|
)
|
|
1139
1291
|
),
|
|
1140
|
-
h("
|
|
1141
|
-
h("
|
|
1142
|
-
h("
|
|
1143
|
-
h("
|
|
1144
|
-
|
|
1145
|
-
|
|
1146
|
-
|
|
1147
|
-
|
|
1148
|
-
|
|
1149
|
-
|
|
1150
|
-
|
|
1151
|
-
h("input", { type: "checkbox", checked: autoTag, onChange: (e) => setAutoTag(e.target.checked) }),
|
|
1152
|
-
h("span", null, t("memory.settings.autoTagEnabled"))
|
|
1153
|
-
),
|
|
1154
|
-
h("div", { style: { display: "flex", alignItems: "center", gap: 6 } },
|
|
1155
|
-
h("button", { style: styles.footerButton, onClick: saveAutoTag }, t("memory.settings.autoTagSave")),
|
|
1156
|
-
autoTagSaved && h("span", { style: { fontSize: 12, color: "var(--dsw-alias-state-success, #2a7)" } }, t("memory.settings.autoTagSaved"))
|
|
1157
|
-
),
|
|
1158
|
-
// sidebar trigger switch (issue #38)
|
|
1159
|
-
h("div", { style: { ...labelStyle, marginTop: 20 } }, t("memory.settings.sidebarTriggerTitle")),
|
|
1160
|
-
h("div", { style: hintStyle }, t("memory.settings.sidebarTriggerHint")),
|
|
1161
|
-
h("label", { style: { display: "flex", alignItems: "center", gap: 6, marginBottom: 8, fontSize: 13 } },
|
|
1162
|
-
h("input", { type: "checkbox", checked: showSidebarTrigger, onChange: (e) => setShowSidebarTrigger(e.target.checked) }),
|
|
1163
|
-
h("span", null, t("memory.settings.sidebarTriggerEnabled"))
|
|
1292
|
+
h("section", { className: "mneme-set-sec" },
|
|
1293
|
+
h("div", { className: "mneme-set-title" }, t("memory.settings.autoTagTitle")),
|
|
1294
|
+
h("div", { className: "mneme-set-desc" }, t("memory.settings.autoTagHint")),
|
|
1295
|
+
h("label", { style: { display: "flex", alignItems: "center", gap: 8, marginBottom: 10, fontSize: 13, color: "var(--dsw-alias-label-primary)" } },
|
|
1296
|
+
h("input", { type: "checkbox", checked: autoTag, onChange: (e) => setAutoTag(e.target.checked) }),
|
|
1297
|
+
h("span", null, t("memory.settings.autoTagEnabled"))
|
|
1298
|
+
),
|
|
1299
|
+
h("div", { style: { display: "flex", alignItems: "center", gap: 8 } },
|
|
1300
|
+
h("button", { className: "mneme-btn", onClick: saveAutoTag }, t("memory.settings.autoTagSave")),
|
|
1301
|
+
autoTagSaved && h("span", { className: "mneme-saved" }, t("memory.settings.autoTagSaved"))
|
|
1302
|
+
)
|
|
1164
1303
|
),
|
|
1165
|
-
h("
|
|
1166
|
-
h("
|
|
1167
|
-
|
|
1304
|
+
h("section", { className: "mneme-set-sec" },
|
|
1305
|
+
h("div", { className: "mneme-set-title" }, t("memory.settings.sidebarTriggerTitle")),
|
|
1306
|
+
h("div", { className: "mneme-set-desc" }, t("memory.settings.sidebarTriggerHint")),
|
|
1307
|
+
h("label", { style: { display: "flex", alignItems: "center", gap: 8, marginBottom: 10, fontSize: 13, color: "var(--dsw-alias-label-primary)" } },
|
|
1308
|
+
h("input", { type: "checkbox", checked: showSidebarTrigger, onChange: (e) => setShowSidebarTrigger(e.target.checked) }),
|
|
1309
|
+
h("span", null, t("memory.settings.sidebarTriggerEnabled"))
|
|
1310
|
+
),
|
|
1311
|
+
h("div", { style: { display: "flex", alignItems: "center", gap: 8 } },
|
|
1312
|
+
h("button", { className: "mneme-btn", onClick: saveSidebarTrigger }, t("memory.settings.sidebarTriggerSave")),
|
|
1313
|
+
sidebarTriggerSaved && h("span", { className: "mneme-saved" }, t("memory.settings.sidebarTriggerSaved"))
|
|
1314
|
+
)
|
|
1168
1315
|
),
|
|
1169
|
-
|
|
1170
|
-
|
|
1171
|
-
|
|
1172
|
-
|
|
1173
|
-
|
|
1174
|
-
|
|
1316
|
+
h("section", { className: "mneme-set-sec" },
|
|
1317
|
+
h("div", { className: "mneme-set-title" }, t("memory.settings.vectorTitle")),
|
|
1318
|
+
h("div", { className: "mneme-set-desc" }, t("memory.settings.vectorHint")),
|
|
1319
|
+
h("label", { style: { display: "flex", alignItems: "center", gap: 8, marginBottom: 10, fontSize: 13, color: "var(--dsw-alias-label-primary)" } },
|
|
1320
|
+
h("input", { type: "checkbox", checked: !!vector.enabled, onChange: (e) => setVector({ ...vector, enabled: e.target.checked }) }),
|
|
1321
|
+
h("span", null, t("memory.settings.vectorEnabled"))
|
|
1322
|
+
),
|
|
1323
|
+
h("input", { className: "mneme-set-input", value: vector.baseUrl, placeholder: t("memory.settings.vectorBaseUrl"), onChange: (e) => setVector({ ...vector, baseUrl: e.target.value }) }),
|
|
1324
|
+
h("input", { className: "mneme-set-input", type: "password", value: vector.apiKey, placeholder: t("memory.settings.vectorApiKey"), onChange: (e) => setVector({ ...vector, apiKey: e.target.value }) }),
|
|
1325
|
+
h("input", { className: "mneme-set-input", value: vector.model, placeholder: t("memory.settings.vectorModel"), onChange: (e) => setVector({ ...vector, model: e.target.value }) }),
|
|
1326
|
+
h("div", { style: { display: "flex", alignItems: "center", gap: 8, flexWrap: "wrap" } },
|
|
1327
|
+
h("button", { className: "mneme-btn", onClick: saveVector }, t("memory.settings.vectorSave")),
|
|
1328
|
+
vectorSaved && h("span", { className: "mneme-saved" }, t("memory.settings.vectorSaved")),
|
|
1329
|
+
h("button", { className: "mneme-btn", onClick: reindex, disabled: reindexing }, reindexing ? t("memory.settings.vectorReindexing") : t("memory.settings.vectorReindex")),
|
|
1330
|
+
reindexMsg && h("span", { style: { fontSize: 12, color: "var(--dsw-alias-label-secondary, #666)" } }, reindexMsg)
|
|
1331
|
+
)
|
|
1175
1332
|
),
|
|
1176
|
-
h("
|
|
1177
|
-
|
|
1178
|
-
|
|
1179
|
-
|
|
1180
|
-
|
|
1181
|
-
|
|
1182
|
-
|
|
1183
|
-
|
|
1333
|
+
h("section", { className: "mneme-set-sec" },
|
|
1334
|
+
h("div", { className: "mneme-set-title" }, t("memory.settings.apiTokenTitle")),
|
|
1335
|
+
h("div", { className: "mneme-set-desc" }, t("memory.settings.apiTokenHint")),
|
|
1336
|
+
h("div", { style: { display: "flex", gap: 8 } },
|
|
1337
|
+
h("input", {
|
|
1338
|
+
className: "mneme-set-input",
|
|
1339
|
+
style: { marginBottom: 0 },
|
|
1340
|
+
type: "password",
|
|
1341
|
+
value: apiToken,
|
|
1342
|
+
placeholder: t("memory.settings.apiTokenPlaceholder"),
|
|
1343
|
+
onChange: (e) => setApiToken(e.target.value)
|
|
1344
|
+
}),
|
|
1345
|
+
h("button", { className: "mneme-btn", onClick: saveToken }, t("memory.settings.apiTokenSave"))
|
|
1346
|
+
),
|
|
1347
|
+
apiTokenSaved && h("div", { style: { marginTop: 8 } }, h("span", { className: "mneme-saved" }, t("memory.settings.apiTokenSaved")))
|
|
1184
1348
|
)
|
|
1185
1349
|
);
|
|
1186
1350
|
}
|
|
@@ -1197,28 +1361,49 @@ window.__ModuleLoader__.load({
|
|
|
1197
1361
|
// screen. There the tab simply does not exist, so activateExplorerTab
|
|
1198
1362
|
// resolves false and the sidebar entry falls back to the full-viewport
|
|
1199
1363
|
// overlay surface below.
|
|
1200
|
-
|
|
1201
|
-
|
|
1202
|
-
|
|
1203
|
-
|
|
1364
|
+
// Candidate tabs whose label matches ours exactly. The conversation tab
|
|
1365
|
+
// ring carries no registrant id in the DOM, so the label is all we have —
|
|
1366
|
+
// but other plugins may register views (or settings dialogs may render
|
|
1367
|
+
// panes) with the same text, and hidden panes still sit in the document.
|
|
1368
|
+
// Restrict to rendered tabs (offsetParent filters display:none subtrees)
|
|
1369
|
+
// and verify by rendering, not by label alone (see activateExplorerTab).
|
|
1370
|
+
function findExplorerTabs(label) {
|
|
1371
|
+
const matches = [];
|
|
1372
|
+
for (const tab of document.querySelectorAll('[role="tab"]')) {
|
|
1373
|
+
if ((tab.textContent || "").trim() !== label) continue;
|
|
1374
|
+
if (tab.offsetParent === null && getComputedStyle(tab).position !== "fixed") continue;
|
|
1375
|
+
matches.push(tab);
|
|
1204
1376
|
}
|
|
1205
|
-
return
|
|
1377
|
+
return matches;
|
|
1206
1378
|
}
|
|
1207
1379
|
|
|
1208
1380
|
// Click the memory library tab and wait (bounded, rAF-polled) until the
|
|
1209
1381
|
// host actually marks it selected — a silent React re-render gap must not
|
|
1210
1382
|
// be mistaken for success, or the fallback would never kick in.
|
|
1383
|
+
// Click the memory library tab and wait (bounded, rAF-polled) until the
|
|
1384
|
+
// host marks it selected AND our explorer actually rendered (`.mneme-x`).
|
|
1385
|
+
// aria-selected alone cannot tell our tab from a same-labelled tab of
|
|
1386
|
+
// another plugin — the sidebar entry must not activate theirs. On a
|
|
1387
|
+
// failed candidate, move on to the next one; resolve false when the
|
|
1388
|
+
// fallback overlay should take over.
|
|
1211
1389
|
function activateExplorerTab(label) {
|
|
1212
1390
|
return new Promise((resolve) => {
|
|
1213
|
-
const
|
|
1214
|
-
if (
|
|
1215
|
-
|
|
1216
|
-
const
|
|
1217
|
-
|
|
1218
|
-
|
|
1219
|
-
|
|
1220
|
-
|
|
1221
|
-
|
|
1391
|
+
const candidates = findExplorerTabs(label);
|
|
1392
|
+
if (candidates.length === 0) { resolve(false); return; }
|
|
1393
|
+
let index = 0;
|
|
1394
|
+
const tryNext = () => {
|
|
1395
|
+
if (index >= candidates.length) { resolve(false); return; }
|
|
1396
|
+
const tab = candidates[index++];
|
|
1397
|
+
tab.click();
|
|
1398
|
+
const deadline = Date.now() + 400;
|
|
1399
|
+
(function check() {
|
|
1400
|
+
const rendered = document.querySelector(".mneme-x") !== null;
|
|
1401
|
+
if (tab.getAttribute("aria-selected") === "true" && rendered) { resolve(true); return; }
|
|
1402
|
+
if (Date.now() >= deadline) { setTimeout(tryNext, 0); return; }
|
|
1403
|
+
requestAnimationFrame(check);
|
|
1404
|
+
})();
|
|
1405
|
+
};
|
|
1406
|
+
tryNext();
|
|
1222
1407
|
});
|
|
1223
1408
|
}
|
|
1224
1409
|
|
|
@@ -1802,7 +1987,7 @@ window.__ModuleLoader__.load({
|
|
|
1802
1987
|
className: type === "all" ? "mneme-xtype mneme-active" : "mneme-xtype",
|
|
1803
1988
|
"aria-pressed": type === "all",
|
|
1804
1989
|
onClick: () => setType("all")
|
|
1805
|
-
}, h("span", null, t("memory.tab.all")), h("span", { className: "mneme-xcount2" }, String(items.length))),
|
|
1990
|
+
}, h("span", { className: "mneme-xdot mneme-xdot--all", "aria-hidden": "true" }), h("span", null, t("memory.tab.all")), h("span", { className: "mneme-xcount2" }, String(items.length))),
|
|
1806
1991
|
knownTypes.concat(extraTypes).map((key) =>
|
|
1807
1992
|
h("button", {
|
|
1808
1993
|
key,
|
|
@@ -1810,6 +1995,7 @@ window.__ModuleLoader__.load({
|
|
|
1810
1995
|
"aria-pressed": type === key,
|
|
1811
1996
|
onClick: () => setType(key)
|
|
1812
1997
|
},
|
|
1998
|
+
h("span", { className: "mneme-xdot", style: { color: memoryTypeColor(key) }, title: typeLabel(t, key), "aria-hidden": "true" }),
|
|
1813
1999
|
h("span", null, typeLabel(t, key)),
|
|
1814
2000
|
h("span", { className: "mneme-xcount2" }, String(counts[key]))
|
|
1815
2001
|
))
|
|
@@ -1887,6 +2073,7 @@ window.__ModuleLoader__.load({
|
|
|
1887
2073
|
className: m.id === selectedId ? "mneme-xitem mneme-active" : "mneme-xitem",
|
|
1888
2074
|
onClick: () => setSelectedId(m.id)
|
|
1889
2075
|
},
|
|
2076
|
+
h("span", { className: "mneme-xdot", style: { color: memoryTypeColor(m.type) }, title: typeLabel(t, m.type), "aria-hidden": "true" }),
|
|
1890
2077
|
h("span", { className: "mneme-xtime" }, time),
|
|
1891
2078
|
h("span", { className: "mneme-xname" }, m.title || m.content?.slice(0, 40))
|
|
1892
2079
|
);
|
|
@@ -1902,10 +2089,15 @@ window.__ModuleLoader__.load({
|
|
|
1902
2089
|
h("div", { className: "mneme-xdinner" },
|
|
1903
2090
|
h("div", { className: "mneme-xdtitle" }, selected.title),
|
|
1904
2091
|
h("div", { className: "mneme-xdmeta" },
|
|
1905
|
-
h("span",
|
|
1906
|
-
|
|
1907
|
-
|
|
1908
|
-
|
|
2092
|
+
h("span", { style: { display: "inline-flex", alignItems: "center", gap: "6px" } },
|
|
2093
|
+
h("span", { className: "mneme-xdot", style: { color: memoryTypeColor(selected.type) }, "aria-hidden": "true" }),
|
|
2094
|
+
`${typeLabel(t, selected.type)} · ${t("memory.explorer.importance")} ★${selected.importance}`
|
|
2095
|
+
),
|
|
2096
|
+
selected.source && h("span", { title: selected.source },
|
|
2097
|
+
`${t("memory.explorer.source")}: `,
|
|
2098
|
+
h("span", { className: "mneme-xdsrc" }, selected.source)),
|
|
2099
|
+
h("span", { title: formatDate(selected.created_at) }, `${t("memory.explorer.created")}: ${formatDateShort(selected.created_at)}`),
|
|
2100
|
+
h("span", { title: formatDate(selected.updated_at) }, `${t("memory.explorer.updated")}: ${formatRelativeTime(selected.updated_at, t)}`)
|
|
1909
2101
|
),
|
|
1910
2102
|
h("div", { className: "mneme-xdmeta" },
|
|
1911
2103
|
h("span", null, t("memory.explorer.tags")),
|