dsh-memory-engine 0.8.0 → 0.8.2
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 +20 -6
- package/lib/client.js +121 -29
- package/lib/client.src.js +10 -8
- package/lib/client.src.js.bak-before-closefix +878 -0
- package/lib/config-write.js +5 -1
- package/lib/config.js +3 -1
- package/lib/render/canvas.js +10 -4
- package/lib/render/layout.mjs +101 -17
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
# DSH 记忆引擎
|
|
2
2
|
|
|
3
|
-
[](https://www.npmjs.com/package/dsh-memory-engine)
|
|
4
4
|
[](LICENSE)
|
|
5
|
-
[](test)
|
|
6
6
|
|
|
7
7
|
DSH 插件的本地记忆引擎,带神经网络可视化。它让 agent 保存、召回和管理跨会话记忆,也能把你已有的记忆文件导进来。
|
|
8
8
|
|
|
@@ -11,18 +11,32 @@ DSH 插件的本地记忆引擎,带神经网络可视化。它让 agent 保存
|
|
|
11
11
|
|
|
12
12
|

|
|
13
13
|
|
|
14
|
-
当前版本:0.8.
|
|
14
|
+
当前版本:0.8.1。Node ≥22,零新增依赖。四套配色与球体、星系、大脑神经网络三种布局自由组合,默认保留原来的经典星空与球体。自动记忆默认关闭,由用户在图谱头部或设置页手动打开;开启后每轮对话会额外调用一次模型,可能产生费用。自动导入默认只收聊天会话的用户消息,支持来源白名单、工作指令过滤、流程话过滤、规则分类、错误原因、一次性重扫和账本清理。
|
|
15
15
|
|
|
16
|
-
##
|
|
16
|
+
## 安装(重要)
|
|
17
|
+
|
|
18
|
+
**请指定版本号安装。** pnpm 可能因为本地缓存的包元数据解析到旧版本(实测 `pnpm add dsh-memory-engine` 会装到 0.7.5):
|
|
19
|
+
|
|
20
|
+
```bash
|
|
21
|
+
pnpm add dsh-memory-engine@0.8.1
|
|
22
|
+
```
|
|
23
|
+
|
|
24
|
+
装完重启 DSH,标题栏出现「🧠 记忆图谱」。
|
|
25
|
+
|
|
26
|
+
0.7.5 及更早版本存在 `memory_map` 参数名撞 JSON Schema 保留字的问题,调用时整轮返回 400,请勿使用。
|
|
27
|
+
|
|
28
|
+
## 0.8.1 图谱外观
|
|
17
29
|
|
|
18
30
|
图谱头部点击「外观」,可组合选择四套配色与三种布局;「设置」页也有同一组选项。点选立即应用并保存,刷新后保留;保存失败会回滚并显示原因。「恢复默认」同时恢复经典星空和球体。
|
|
19
31
|
|
|
20
32
|
| 维度 | 选项 |
|
|
21
33
|
| --- | --- |
|
|
22
34
|
| 配色 | 经典星空 `classic` / 赛博霓虹 `neon` / 铅绘素描 `paper` / 极简单色 `mono` |
|
|
23
|
-
| 布局 | 球体 `sphere` /
|
|
35
|
+
| 布局 | 球体 `sphere` / 星系 `galaxy` / 大脑神经网络 `brain` |
|
|
36
|
+
|
|
37
|
+
铅绘素描采用素纸底色、石墨灰节点和极淡光晕。星系有密集核心、2~4 条带抖动的旋臂和 30% 星尘;大脑神经网络有不对称的左右半球、每半球 12~14 个簇心、5% 游离节点和表面起伏。固定种子与节点 id 哈希保证相同数据的位置完全一致。三种布局都保留中心区域角色卡、旋转、缩放和点击详情;大脑角色卡位于纵裂中间偏下。切换时保留已选节点、相机及动画状态,不重新请求记忆数据,不改变记忆的重要度、留存或节点尺寸算法。星系仅将选中关联线的透明度和宽度降到球体的 80%,避免密集连线发白。
|
|
24
38
|
|
|
25
|
-
|
|
39
|
+
0.8.0 的旧配置自动兼容:`flat` 按 `galaxy` 读取,`spiral` 按 `brain` 读取,不需要手动改文件,也不在读取时写盘。旧客户端提交这两个旧值也会被接受,并通过原有备份和原子写保存为新值。界面只显示球体、星系、大脑神经网络三种选项。
|
|
26
40
|
|
|
27
41
|
在现有配置中可合并 `"graphTheme": "classic"` 和 `"graphLayout": "sphere"`。这两个字段通过现有配置接口和备份、原子写流程保存,非法值返回 400。未配置时外观与升级前相同;没有角色卡的库仍保留原来的默认脑状布局。CLI 生成的离线图谱也读取同一配置。
|
|
28
42
|
|
package/lib/client.js
CHANGED
|
@@ -54,7 +54,9 @@ window.__ModuleLoader__.load({
|
|
|
54
54
|
if (!Object.hasOwn(THEMES, themeName)) throw new Error('未知图谱配色');
|
|
55
55
|
let theme = THEMES[themeName];
|
|
56
56
|
let layoutName = options.graphLayout ?? data.graphLayout ?? 'sphere';
|
|
57
|
-
if (
|
|
57
|
+
if (layoutName === 'flat') layoutName = 'galaxy';
|
|
58
|
+
if (layoutName === 'spiral') layoutName = 'brain';
|
|
59
|
+
if (!['sphere', 'galaxy', 'brain'].includes(layoutName)) throw new Error('未知图谱布局');
|
|
58
60
|
let sphereNodes = [];
|
|
59
61
|
const layouts = new Map();
|
|
60
62
|
let nodes = [], links = [], hubs = [], linkTwinkle = [], visibleLinks = [];
|
|
@@ -102,7 +104,9 @@ function glowSprite(rgb) {
|
|
|
102
104
|
|
|
103
105
|
function setLayout(nextLayout) {
|
|
104
106
|
if (destroyed) return;
|
|
105
|
-
if (
|
|
107
|
+
if (nextLayout === 'flat') nextLayout = 'galaxy';
|
|
108
|
+
if (nextLayout === 'spiral') nextLayout = 'brain';
|
|
109
|
+
if (!['sphere', 'galaxy', 'brain'].includes(nextLayout)) throw new Error('未知图谱布局');
|
|
106
110
|
if (layoutName === nextLayout && layouts.has(nextLayout)) return;
|
|
107
111
|
if (!layouts.has(nextLayout)) {
|
|
108
112
|
if (typeof options.layoutNodes !== 'function') throw new Error('缺少图谱布局函数');
|
|
@@ -327,20 +331,22 @@ function frame(now) {
|
|
|
327
331
|
}
|
|
328
332
|
}
|
|
329
333
|
if (linkPaths.length) {
|
|
334
|
+
if (layoutName === 'galaxy') ctx.globalAlpha=0.8;
|
|
330
335
|
if (Array.isArray(theme.link)) {
|
|
331
336
|
const gradient = ctx.createLinearGradient(0,0,width,height);
|
|
332
337
|
gradient.addColorStop(0,theme.link[0]);
|
|
333
338
|
gradient.addColorStop(1,theme.link[1]);
|
|
334
339
|
ctx.strokeStyle=gradient;
|
|
335
340
|
} else ctx.strokeStyle=theme.link;
|
|
336
|
-
ctx.lineWidth=1;
|
|
341
|
+
ctx.lineWidth=layoutName === 'galaxy' ? 0.8 : 1;
|
|
337
342
|
ctx.beginPath();
|
|
338
343
|
for (const p of linkPaths) { ctx.moveTo(p[0],p[1]); ctx.quadraticCurveTo(p[2],p[3],p[4],p[5]); }
|
|
339
344
|
ctx.stroke();
|
|
340
345
|
// 更亮的一层,让关联线看得清
|
|
341
346
|
ctx.strokeStyle=theme.linkHighlight;
|
|
342
|
-
ctx.lineWidth=2;
|
|
347
|
+
ctx.lineWidth=layoutName === 'galaxy' ? 1.6 : 2;
|
|
343
348
|
ctx.stroke();
|
|
349
|
+
if (layoutName === 'galaxy') ctx.globalAlpha=1;
|
|
344
350
|
}
|
|
345
351
|
for (let index=pulses.length-1;index>=0;index--) {
|
|
346
352
|
const pulse=pulses[index],fraction=(now-pulse.born)/pulse.life;
|
|
@@ -473,7 +479,9 @@ function frame(now) {
|
|
|
473
479
|
|
|
474
480
|
|
|
475
481
|
function layoutPositions(nodes, layout = 'sphere') {
|
|
476
|
-
if (
|
|
482
|
+
if (layout === 'flat') layout = 'galaxy';
|
|
483
|
+
if (layout === 'spiral') layout = 'brain';
|
|
484
|
+
if (!['sphere', 'galaxy', 'brain'].includes(layout)) throw new Error('未知图谱布局');
|
|
477
485
|
if (layout === 'sphere') return nodes.map(node => ({ ...node, pos: { ...node.pos } }));
|
|
478
486
|
const memories = nodes.filter(node => !node.isCore).sort((left, right) =>
|
|
479
487
|
(right.degree || 0) - (left.degree || 0) || (String(left.id) < String(right.id) ? -1 : String(left.id) > String(right.id) ? 1 : 0));
|
|
@@ -484,34 +492,117 @@ function frame(now) {
|
|
|
484
492
|
hash = Math.imul(hash ^ (hash >>> 16), 0x45d9f3b);
|
|
485
493
|
return ((hash ^ (hash >>> 16)) >>> 0) / 4294967295 * 2 - 1;
|
|
486
494
|
}
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
|
|
494
|
-
|
|
495
|
-
|
|
496
|
-
|
|
497
|
-
|
|
495
|
+
let datasetSeed = 20261030;
|
|
496
|
+
for (const id of memories.map(node => String(node.id)).sort()) {
|
|
497
|
+
for (const character of id + '\u0000') datasetSeed = Math.imul(datasetSeed ^ character.codePointAt(0), 16777619);
|
|
498
|
+
}
|
|
499
|
+
const uniform = (id, axis) => Math.max(1e-9, Math.min(1 - 1e-9, (jitter(datasetSeed + ':' + id, axis) + 1) / 2));
|
|
500
|
+
const gaussian = (id, axis) => Math.sqrt(-2 * Math.log(uniform(id, axis + ':radius'))) * Math.cos(2 * Math.PI * uniform(id, axis + ':angle'));
|
|
501
|
+
if (layout === 'galaxy') {
|
|
502
|
+
const armCount = 2 + Math.floor(uniform('galaxy', 'arms') * 3);
|
|
503
|
+
const rotation = uniform('galaxy', 'rotation') * Math.PI * 2;
|
|
504
|
+
const coreCount = Math.ceil(memories.length * 0.15);
|
|
505
|
+
const haloCount = Math.floor(memories.length * 0.05);
|
|
506
|
+
const diskEnd = memories.length - haloCount;
|
|
507
|
+
const dustCount = Math.round(memories.length * 0.30);
|
|
508
|
+
const dust = new Set(memories.slice(coreCount, diskEnd)
|
|
509
|
+
.sort((left, right) => uniform(left.id, 'dust') - uniform(right.id, 'dust') || (String(left.id) < String(right.id) ? -1 : String(left.id) > String(right.id) ? 1 : 0))
|
|
510
|
+
.slice(0, Math.max(0, dustCount - haloCount)).map(node => node.id));
|
|
498
511
|
memories.forEach((node, index) => {
|
|
499
|
-
|
|
500
|
-
|
|
501
|
-
|
|
502
|
-
|
|
512
|
+
let radius, angle;
|
|
513
|
+
if (index < coreCount) {
|
|
514
|
+
radius = 6 + Math.sqrt(uniform(node.id, 'core-radius')) * 16;
|
|
515
|
+
angle = uniform(node.id, 'core-angle') * Math.PI * 2;
|
|
516
|
+
} else if (index >= diskEnd) {
|
|
517
|
+
radius = 95 + Math.sqrt(uniform(node.id, 'halo-radius')) * 35;
|
|
518
|
+
angle = uniform(node.id, 'halo-angle') * Math.PI * 2;
|
|
519
|
+
} else if (dust.has(node.id)) {
|
|
520
|
+
radius = 22 + Math.sqrt(uniform(node.id, 'dust-radius')) * 73;
|
|
521
|
+
angle = uniform(node.id, 'dust-angle') * Math.PI * 2;
|
|
522
|
+
} else {
|
|
523
|
+
const fraction = (index - coreCount + 0.5) / Math.max(1, diskEnd - coreCount);
|
|
524
|
+
const offset = fraction * 73 + gaussian(node.id, 'radius') * 9;
|
|
525
|
+
const folded = ((offset % 146) + 146) % 146;
|
|
526
|
+
radius = 22 + (folded <= 73 ? folded : 146 - folded);
|
|
527
|
+
const arm = Math.floor(uniform(node.id, 'arm') * armCount);
|
|
528
|
+
const base = rotation + arm * Math.PI * 2 / armCount + (uniform(arm, 'arm-offset') - 0.5) * 0.35;
|
|
529
|
+
const twist = (radius - 18) / 95 * 2.2 * Math.PI;
|
|
530
|
+
const ripple = Math.sin(radius * 0.055 + uniform(arm, 'phase') * Math.PI * 2) * 0.10;
|
|
531
|
+
angle = base + twist + ripple + gaussian(node.id, 'angle') * 0.30;
|
|
532
|
+
}
|
|
533
|
+
const thickness = 8 / (1 + radius / 40);
|
|
534
|
+
const height = Math.max(-1, Math.min(1, gaussian(node.id, 'height') * 0.5)) * thickness;
|
|
535
|
+
positions.set(node.id, { x: Math.cos(angle) * radius, y: Math.sin(angle) * radius, z: height });
|
|
536
|
+
});
|
|
537
|
+
} else {
|
|
538
|
+
function brainPoint(id, axis, extent = 1) {
|
|
539
|
+
const horizontal = gaussian(id, axis + ':x');
|
|
540
|
+
const vertical = gaussian(id, axis + ':y');
|
|
541
|
+
const depth = gaussian(id, axis + ':z');
|
|
542
|
+
const scale = Math.cbrt(uniform(id, axis + ':distance')) * extent / Math.hypot(horizontal, vertical, depth);
|
|
543
|
+
return { x: 42 + horizontal * scale * 40, y: vertical * scale * 106, z: depth * scale * 52 };
|
|
544
|
+
}
|
|
545
|
+
const centers = [0, 1].map(side => {
|
|
546
|
+
const count = 12 + Math.floor(uniform('brain:' + side, 'clusters') * 3);
|
|
547
|
+
const candidates = Array.from({ length: 96 }, (_, index) => {
|
|
548
|
+
const id = 'brain:' + side + ':' + index;
|
|
549
|
+
const point = brainPoint(id, 'center', 0.82 + uniform(id, 'surface') * 0.12);
|
|
550
|
+
return { ...point, spread: 6 + uniform(id, 'spread') * 8, phase: uniform(id, 'phase') * Math.PI * 2 };
|
|
551
|
+
});
|
|
552
|
+
const interior = candidates.reduce((best, point, index) => Math.hypot(point.x - 42, point.y, point.z)
|
|
553
|
+
< Math.hypot(candidates[best].x - 42, candidates[best].y, candidates[best].z) ? index : best, 0);
|
|
554
|
+
const selected = [candidates.splice(interior, 1)[0]];
|
|
555
|
+
while (selected.length < count) {
|
|
556
|
+
let best = 0, greatest = -Infinity;
|
|
557
|
+
candidates.forEach((point, index) => {
|
|
558
|
+
const distance = Math.min(...selected.map(center => Math.hypot(point.x - center.x, point.y - center.y, (point.z - center.z) * 0.7)));
|
|
559
|
+
if (distance > greatest) { greatest = distance; best = index; }
|
|
560
|
+
});
|
|
561
|
+
selected.push(candidates.splice(best, 1)[0]);
|
|
562
|
+
}
|
|
563
|
+
return selected;
|
|
564
|
+
});
|
|
565
|
+
const roamers = new Set([...memories].sort((left, right) => uniform(left.id, 'roaming') - uniform(right.id, 'roaming')
|
|
566
|
+
|| (String(left.id) < String(right.id) ? -1 : String(left.id) > String(right.id) ? 1 : 0))
|
|
567
|
+
.slice(0, Math.round(memories.length * 0.05)).map(node => node.id));
|
|
568
|
+
memories.forEach(node => {
|
|
569
|
+
const side = uniform(node.id, 'hemisphere') < 0.5 ? 0 : 1;
|
|
570
|
+
const target = brainPoint(node.id, 'target');
|
|
571
|
+
let center, nearest = Infinity;
|
|
572
|
+
centers[side].forEach((candidate, index) => {
|
|
573
|
+
const distance = Math.hypot(target.x - candidate.x, target.y - candidate.y, target.z - candidate.z)
|
|
574
|
+
* (0.8 + uniform(node.id, 'weight:' + index) * 0.4);
|
|
575
|
+
if (distance < nearest) { nearest = distance; center = candidate; }
|
|
576
|
+
});
|
|
577
|
+
const point = roamers.has(node.id) ? target : {
|
|
578
|
+
x: center.x + gaussian(node.id, 'scatter:x') * center.spread,
|
|
579
|
+
y: center.y + gaussian(node.id, 'scatter:y') * center.spread,
|
|
580
|
+
z: center.z + gaussian(node.id, 'scatter:z') * center.spread,
|
|
581
|
+
};
|
|
582
|
+
point.x += Math.sin(point.y * 0.08 + center.phase) * 6;
|
|
583
|
+
point.z += Math.sin(point.y * 0.09 + center.phase) * 4;
|
|
584
|
+
point.y += Math.sin(point.z * 0.12 + center.phase) * 3;
|
|
585
|
+
const envelope = 0.96 + 0.06 * Math.sin(point.y * 0.08) + 0.035 * Math.cos(point.z * 0.11 + side);
|
|
586
|
+
const distance = Math.hypot((point.x - 42) / 40, point.y / 106, point.z / 52);
|
|
587
|
+
if (distance > envelope) {
|
|
588
|
+
const scale = envelope * (0.93 + uniform(node.id, 'edge') * 0.06) / distance;
|
|
589
|
+
point.x = 42 + (point.x - 42) * scale; point.y *= scale; point.z *= scale;
|
|
590
|
+
}
|
|
591
|
+
positions.set(node.id, { x: (side ? 1 : -1) * (4 + Math.abs(point.x)) + (side ? 2 : 0),
|
|
592
|
+
y: point.y - (side ? 1 : 0), z: point.z });
|
|
503
593
|
});
|
|
504
594
|
}
|
|
505
|
-
return nodes.map(node => ({ ...node, pos: node.isCore ? { x: 0, y: 0, z: 0 } : positions.get(node.id) }));
|
|
595
|
+
return nodes.map(node => ({ ...node, pos: node.isCore ? { x: 0, y: layout === 'brain' ? 14 : 0, z: 0 } : positions.get(node.id) }));
|
|
506
596
|
}
|
|
507
597
|
|
|
508
598
|
var defaultSettings = { hideButton: false, showGuideOnEmpty: true, graphTheme: "classic", graphLayout: "sphere", autoMemory: { enabled: false, maxPerTurn: 2, dailyCap: 40 }, showAutoMemoryCostHint: true };
|
|
509
599
|
var actionStyle = { padding: "7px 12px", borderRadius: "8px", border: "1px solid var(--memory-border,#6999b8)", background: "var(--memory-button,#102236)", color: "var(--memory-text,#e2f4ff)", font: "inherit", cursor: "pointer" };
|
|
510
|
-
var
|
|
600
|
+
var closeStyle = Object.assign({}, actionStyle, { padding: "11px 20px", minHeight: "42px", fontWeight: 700, letterSpacing: "0.02em" });
|
|
601
|
+
var fieldStyle = { boxSizing: "border-box", padding: "10px 12px", borderRadius: "8px", border: "1px solid #35536b", background: "#091422", color: "#e2f4ff", font: "inherit", minWidth: 0 };
|
|
511
602
|
var kinds = ["知识", "印象", "对话", "事件", "日记"];
|
|
512
603
|
var appearanceOptions = [
|
|
513
604
|
{ key: "graphTheme", label: "配色", choices: [["classic", "经典星空"], ["neon", "赛博霓虹"], ["paper", "铅绘素描"], ["mono", "极简单色"]] },
|
|
514
|
-
{ key: "graphLayout", label: "布局", choices: [["sphere", "球体"], ["
|
|
605
|
+
{ key: "graphLayout", label: "布局", choices: [["sphere", "球体"], ["galaxy", "星系"], ["brain", "大脑神经网络"]] },
|
|
515
606
|
];
|
|
516
607
|
var appearanceStyles = {
|
|
517
608
|
classic: {},
|
|
@@ -676,7 +767,7 @@ function frame(now) {
|
|
|
676
767
|
style: { position: "absolute", top: "72px", right: "18px", zIndex: 1, width: "min(350px,calc(100% - 36px))", maxHeight: "calc(100% - 96px)", boxSizing: "border-box", display: "flex", flexDirection: "column", overflow: "hidden", padding: "18px", border: "1px solid #3976a0", borderRadius: "14px", background: "#07111fee", color: "#dbeaf8", font: "14px/1.6 system-ui,sans-serif", boxShadow: "0 0 40px #1763a033", backdropFilter: "blur(8px)", animation: "dsh-memory-detail-in 180ms ease-out" },
|
|
677
768
|
}, React.createElement("div", { style: { display: "flex", alignItems: "center", justifyContent: "space-between", gap: "12px", flexShrink: 0 } },
|
|
678
769
|
React.createElement("span", { "data-memory-engine-detail-kind": "true", style: { color: "#a4e6ff", background: "#15344b", borderRadius: "6px", padding: "2px 8px", overflowWrap: "anywhere" } }, node.kind || "未分类"),
|
|
679
|
-
React.createElement("button", { type: "button", "aria-label": "关闭记忆详情", onClick: props.onClose, style: { border: 0, padding: "2px 6px", background: "transparent", color: "#a4bad0", fontSize: "22px", cursor: "pointer" } }, "×")),
|
|
770
|
+
React.createElement("button", { type: "button", "aria-label": "关闭记忆详情", onClick: props.onClose, style: Object.assign({ minWidth: "40px", minHeight: "40px", fontSize: "18px", lineHeight: 1, cursor: "pointer" }, { border: 0, padding: "2px 6px", background: "transparent", color: "#a4bad0", fontSize: "22px", cursor: "pointer" }) }, "×")),
|
|
680
771
|
React.createElement("h2", { "data-memory-engine-detail-title": "true", style: { margin: "10px 0", fontSize: "18px", color: "#b7e9ff", overflowWrap: "anywhere", whiteSpace: "normal", maxHeight: "18vh", overflowY: "auto", flexShrink: 0 } }, title),
|
|
681
772
|
node.isCore ? null : React.createElement("dl", { style: { display: "grid", gridTemplateColumns: "64px minmax(0,1fr)", gap: "5px 10px", margin: "0 0 12px", fontSize: "12px", color: "#9fb7ce", maxHeight: "25vh", overflowY: "auto", flexShrink: 0 } },
|
|
682
773
|
rows.map(function (row) { return React.createElement(React.Fragment, { key: row[0] },
|
|
@@ -1127,7 +1218,7 @@ function frame(now) {
|
|
|
1127
1218
|
return function () { if (configRequest.current) configRequest.current.abort(); };
|
|
1128
1219
|
}, []);
|
|
1129
1220
|
React.useEffect(function () {
|
|
1130
|
-
if (!open) return undefined;
|
|
1221
|
+
if (!open || typeof window === "undefined" || typeof window.addEventListener !== "function") return undefined;
|
|
1131
1222
|
var controller = new AbortController();
|
|
1132
1223
|
setConfigBusy(true);
|
|
1133
1224
|
readApi("/memory-engine/api/config", controller.signal).then(function (data) {
|
|
@@ -1219,7 +1310,7 @@ function frame(now) {
|
|
|
1219
1310
|
if (gesture && gesture.moved < 6 && gesture.version === selectionVersion.current) setSelected(null);
|
|
1220
1311
|
}
|
|
1221
1312
|
React.useEffect(function () {
|
|
1222
|
-
if (!open) return undefined;
|
|
1313
|
+
if (!open || typeof window === "undefined" || typeof window.addEventListener !== "function") return undefined;
|
|
1223
1314
|
var controller = new AbortController();
|
|
1224
1315
|
var active = true;
|
|
1225
1316
|
setGuideData(null);
|
|
@@ -1237,7 +1328,7 @@ function frame(now) {
|
|
|
1237
1328
|
return function () { active = false; controller.abort(); };
|
|
1238
1329
|
}, [open, retry, settings.showGuideOnEmpty]);
|
|
1239
1330
|
React.useEffect(function () {
|
|
1240
|
-
if (!open) return undefined;
|
|
1331
|
+
if (!open || typeof window === "undefined" || typeof window.addEventListener !== "function") return undefined;
|
|
1241
1332
|
function onEscape() {
|
|
1242
1333
|
if (guideOpen) closeGuide();
|
|
1243
1334
|
else if (appearanceOpen) { setAppearanceOpen(false); if (appearanceButton.current) appearanceButton.current.focus(); }
|
|
@@ -1296,12 +1387,13 @@ function frame(now) {
|
|
|
1296
1387
|
type: "button",
|
|
1297
1388
|
onClick: closeMap,
|
|
1298
1389
|
"aria-label": "关闭记忆图谱",
|
|
1299
|
-
|
|
1390
|
+
title: "关闭(Esc)",
|
|
1391
|
+
style: closeStyle,
|
|
1300
1392
|
}, "关闭 ×")), appearanceOpen ? React.createElement("section", { ref: appearancePanel, role: "dialog", "aria-label": "外观", "data-memory-engine-appearance-panel": "true",
|
|
1301
1393
|
style: { position: "absolute", top: "62px", right: "18px", zIndex: 3, boxSizing: "border-box", width: "min(380px,calc(100vw - 36px))", maxHeight: "calc(100vh - 90px)", overflowY: "auto",
|
|
1302
1394
|
padding: "18px", border: "1px solid var(--memory-border,#3976a0)", borderRadius: "12px", background: "var(--memory-panel,#07111f)", boxShadow: "0 12px 35px #0004" } },
|
|
1303
1395
|
React.createElement("div", { style: { display: "flex", justifyContent: "space-between", alignItems: "center", marginBottom: "14px", color: "var(--memory-text,#dbeaf8)" } },
|
|
1304
|
-
React.createElement("strong", null, "外观"), React.createElement("button", { type: "button", "aria-label": "关闭外观", style:
|
|
1396
|
+
React.createElement("strong", null, "外观"), React.createElement("button", { type: "button", "aria-label": "关闭外观", style: closeStyle,
|
|
1305
1397
|
onClick: function () { setAppearanceOpen(false); if (appearanceButton.current) appearanceButton.current.focus(); } }, "×")),
|
|
1306
1398
|
React.createElement(AppearanceControls, { settings: settings, busy: configBusy, onChange: saveAppearance, location: "header" })) : null,
|
|
1307
1399
|
configError ? React.createElement("p", { role: "alert", "data-memory-engine-config-error": "true",
|
package/lib/client.src.js
CHANGED
|
@@ -18,11 +18,12 @@ window.__ModuleLoader__.load({
|
|
|
18
18
|
|
|
19
19
|
var defaultSettings = { hideButton: false, showGuideOnEmpty: true, graphTheme: "classic", graphLayout: "sphere", autoMemory: { enabled: false, maxPerTurn: 2, dailyCap: 40 }, showAutoMemoryCostHint: true };
|
|
20
20
|
var actionStyle = { padding: "7px 12px", borderRadius: "8px", border: "1px solid var(--memory-border,#6999b8)", background: "var(--memory-button,#102236)", color: "var(--memory-text,#e2f4ff)", font: "inherit", cursor: "pointer" };
|
|
21
|
-
var
|
|
21
|
+
var closeStyle = Object.assign({}, actionStyle, { padding: "11px 20px", minHeight: "42px", fontWeight: 700, letterSpacing: "0.02em" });
|
|
22
|
+
var fieldStyle = { boxSizing: "border-box", padding: "10px 12px", borderRadius: "8px", border: "1px solid #35536b", background: "#091422", color: "#e2f4ff", font: "inherit", minWidth: 0 };
|
|
22
23
|
var kinds = ["知识", "印象", "对话", "事件", "日记"];
|
|
23
24
|
var appearanceOptions = [
|
|
24
25
|
{ key: "graphTheme", label: "配色", choices: [["classic", "经典星空"], ["neon", "赛博霓虹"], ["paper", "铅绘素描"], ["mono", "极简单色"]] },
|
|
25
|
-
{ key: "graphLayout", label: "布局", choices: [["sphere", "球体"], ["
|
|
26
|
+
{ key: "graphLayout", label: "布局", choices: [["sphere", "球体"], ["galaxy", "星系"], ["brain", "大脑神经网络"]] },
|
|
26
27
|
];
|
|
27
28
|
var appearanceStyles = {
|
|
28
29
|
classic: {},
|
|
@@ -187,7 +188,7 @@ window.__ModuleLoader__.load({
|
|
|
187
188
|
style: { position: "absolute", top: "72px", right: "18px", zIndex: 1, width: "min(350px,calc(100% - 36px))", maxHeight: "calc(100% - 96px)", boxSizing: "border-box", display: "flex", flexDirection: "column", overflow: "hidden", padding: "18px", border: "1px solid #3976a0", borderRadius: "14px", background: "#07111fee", color: "#dbeaf8", font: "14px/1.6 system-ui,sans-serif", boxShadow: "0 0 40px #1763a033", backdropFilter: "blur(8px)", animation: "dsh-memory-detail-in 180ms ease-out" },
|
|
188
189
|
}, React.createElement("div", { style: { display: "flex", alignItems: "center", justifyContent: "space-between", gap: "12px", flexShrink: 0 } },
|
|
189
190
|
React.createElement("span", { "data-memory-engine-detail-kind": "true", style: { color: "#a4e6ff", background: "#15344b", borderRadius: "6px", padding: "2px 8px", overflowWrap: "anywhere" } }, node.kind || "未分类"),
|
|
190
|
-
React.createElement("button", { type: "button", "aria-label": "关闭记忆详情", onClick: props.onClose, style: { border: 0, padding: "2px 6px", background: "transparent", color: "#a4bad0", fontSize: "22px", cursor: "pointer" } }, "×")),
|
|
191
|
+
React.createElement("button", { type: "button", "aria-label": "关闭记忆详情", onClick: props.onClose, style: Object.assign({ minWidth: "40px", minHeight: "40px", fontSize: "18px", lineHeight: 1, cursor: "pointer" }, { border: 0, padding: "2px 6px", background: "transparent", color: "#a4bad0", fontSize: "22px", cursor: "pointer" }) }, "×")),
|
|
191
192
|
React.createElement("h2", { "data-memory-engine-detail-title": "true", style: { margin: "10px 0", fontSize: "18px", color: "#b7e9ff", overflowWrap: "anywhere", whiteSpace: "normal", maxHeight: "18vh", overflowY: "auto", flexShrink: 0 } }, title),
|
|
192
193
|
node.isCore ? null : React.createElement("dl", { style: { display: "grid", gridTemplateColumns: "64px minmax(0,1fr)", gap: "5px 10px", margin: "0 0 12px", fontSize: "12px", color: "#9fb7ce", maxHeight: "25vh", overflowY: "auto", flexShrink: 0 } },
|
|
193
194
|
rows.map(function (row) { return React.createElement(React.Fragment, { key: row[0] },
|
|
@@ -638,7 +639,7 @@ window.__ModuleLoader__.load({
|
|
|
638
639
|
return function () { if (configRequest.current) configRequest.current.abort(); };
|
|
639
640
|
}, []);
|
|
640
641
|
React.useEffect(function () {
|
|
641
|
-
if (!open) return undefined;
|
|
642
|
+
if (!open || typeof window === "undefined" || typeof window.addEventListener !== "function") return undefined;
|
|
642
643
|
var controller = new AbortController();
|
|
643
644
|
setConfigBusy(true);
|
|
644
645
|
readApi("/memory-engine/api/config", controller.signal).then(function (data) {
|
|
@@ -730,7 +731,7 @@ window.__ModuleLoader__.load({
|
|
|
730
731
|
if (gesture && gesture.moved < 6 && gesture.version === selectionVersion.current) setSelected(null);
|
|
731
732
|
}
|
|
732
733
|
React.useEffect(function () {
|
|
733
|
-
if (!open) return undefined;
|
|
734
|
+
if (!open || typeof window === "undefined" || typeof window.addEventListener !== "function") return undefined;
|
|
734
735
|
var controller = new AbortController();
|
|
735
736
|
var active = true;
|
|
736
737
|
setGuideData(null);
|
|
@@ -748,7 +749,7 @@ window.__ModuleLoader__.load({
|
|
|
748
749
|
return function () { active = false; controller.abort(); };
|
|
749
750
|
}, [open, retry, settings.showGuideOnEmpty]);
|
|
750
751
|
React.useEffect(function () {
|
|
751
|
-
if (!open) return undefined;
|
|
752
|
+
if (!open || typeof window === "undefined" || typeof window.addEventListener !== "function") return undefined;
|
|
752
753
|
function onEscape() {
|
|
753
754
|
if (guideOpen) closeGuide();
|
|
754
755
|
else if (appearanceOpen) { setAppearanceOpen(false); if (appearanceButton.current) appearanceButton.current.focus(); }
|
|
@@ -807,12 +808,13 @@ window.__ModuleLoader__.load({
|
|
|
807
808
|
type: "button",
|
|
808
809
|
onClick: closeMap,
|
|
809
810
|
"aria-label": "关闭记忆图谱",
|
|
810
|
-
|
|
811
|
+
title: "关闭(Esc)",
|
|
812
|
+
style: closeStyle,
|
|
811
813
|
}, "关闭 ×")), appearanceOpen ? React.createElement("section", { ref: appearancePanel, role: "dialog", "aria-label": "外观", "data-memory-engine-appearance-panel": "true",
|
|
812
814
|
style: { position: "absolute", top: "62px", right: "18px", zIndex: 3, boxSizing: "border-box", width: "min(380px,calc(100vw - 36px))", maxHeight: "calc(100vh - 90px)", overflowY: "auto",
|
|
813
815
|
padding: "18px", border: "1px solid var(--memory-border,#3976a0)", borderRadius: "12px", background: "var(--memory-panel,#07111f)", boxShadow: "0 12px 35px #0004" } },
|
|
814
816
|
React.createElement("div", { style: { display: "flex", justifyContent: "space-between", alignItems: "center", marginBottom: "14px", color: "var(--memory-text,#dbeaf8)" } },
|
|
815
|
-
React.createElement("strong", null, "外观"), React.createElement("button", { type: "button", "aria-label": "关闭外观", style:
|
|
817
|
+
React.createElement("strong", null, "外观"), React.createElement("button", { type: "button", "aria-label": "关闭外观", style: closeStyle,
|
|
816
818
|
onClick: function () { setAppearanceOpen(false); if (appearanceButton.current) appearanceButton.current.focus(); } }, "×")),
|
|
817
819
|
React.createElement(AppearanceControls, { settings: settings, busy: configBusy, onChange: saveAppearance, location: "header" })) : null,
|
|
818
820
|
configError ? React.createElement("p", { role: "alert", "data-memory-engine-config-error": "true",
|