dsh-memory-engine 0.5.5 → 0.5.7
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 +4 -0
- package/lib/render/layout.mjs +43 -4
- package/lib/render/render.mjs +8 -2
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -88,6 +88,10 @@ memory_recall(q="朋友", limit=5)
|
|
|
88
88
|
|
|
89
89
|
召回结果区分直接命中和联想到的记忆。`memory_stats()` 查看总数和分类;`memory_dedupe()` 默认仅预览重复组。`memory_forget` 支持按 id 精确删除;关键词批量删除必须先预览,并经用户确认后传 `confirm=true`。
|
|
90
90
|
|
|
91
|
+
**检索是按字面词匹配,不做语义理解。** 库里写的是「古茗」,`memory_recall(q="奶茶")` 就查不到——必须先有「奶茶」这两个字。想覆盖这种情况,存记忆时把常见说法一起写进去(例如「阿木喝奶茶只点古茗的超A芝士葡萄」)。
|
|
92
|
+
|
|
93
|
+
**联想到的记忆**来自关键词共享:两条记忆提到同一个词,就会被连起来,查其中一条时另一条一起返回。它不是推理,是顺着关联往外摸。扩散默认一跳(`hops=1`),邻居要留存 > 0.4 才会被带出来。
|
|
94
|
+
|
|
91
95
|
## 导入自己机器人的旧记忆
|
|
92
96
|
|
|
93
97
|
三种格式都是你明确指定的本地文件或目录。如果旧格式均不匹配,可让有文件读取能力的 agent 理解文件,再通过 `memory_save` 逐条写入。导入先预览,用户确认后再传 `dry_run=false`。
|
package/lib/render/layout.mjs
CHANGED
|
@@ -1,5 +1,7 @@
|
|
|
1
|
+
const SEED = 20261030;
|
|
2
|
+
|
|
1
3
|
export function layoutMemories(nodes) {
|
|
2
|
-
let seed =
|
|
4
|
+
let seed = SEED;
|
|
3
5
|
const random = () => ((seed = (seed * 16807) % 2147483647) - 1) / 2147483646;
|
|
4
6
|
function brainPoint(radiusScale) {
|
|
5
7
|
const radius = Math.pow(random(), 1 / 3) * radiusScale;
|
|
@@ -11,12 +13,49 @@ export function layoutMemories(nodes) {
|
|
|
11
13
|
z: radius * Math.sin(phi) * Math.sin(theta) * 0.78,
|
|
12
14
|
};
|
|
13
15
|
}
|
|
14
|
-
|
|
16
|
+
|
|
17
|
+
const placed = nodes.map((node) => {
|
|
15
18
|
const inner = ['人格', '印象', '知识'].includes(node.kind) || node.majorEmotion;
|
|
16
|
-
|
|
19
|
+
// 整体半径放大 1.35 倍:原来 298 个节点挤在一起,点谁都容易点错
|
|
20
|
+
const distance = (inner ? 14 + Math.pow(random(), 0.65) * 36 : 48 + Math.min(1, node.ageDays / 30) * 58 + random() * 12) * 1.35;
|
|
17
21
|
const baseRadius = node.majorEmotion ? 3.2 : inner
|
|
18
22
|
? (node.heat === 1 ? 2.4 : node.heat === 2 ? 1.8 : 1.3)
|
|
19
23
|
: (node.heat === 1 ? 2.1 : node.heat === 2 ? 1.5 : 1.0);
|
|
20
|
-
|
|
24
|
+
// 连接数加成收敛得更紧一点,避免枢纽节点长得过大压住邻居
|
|
25
|
+
return { ...node, pos: brainPoint(distance), radius: baseRadius + Math.min(1.1, Math.sqrt(node.degree) * 0.15) };
|
|
21
26
|
});
|
|
27
|
+
|
|
28
|
+
// ── 分离松弛:把互相重叠的节点推开 ──
|
|
29
|
+
// 不做这一步的话,随机落点会让相邻节点的圆重叠,视觉糊成一片,点击也分不清。
|
|
30
|
+
// 只在世界坐标里推开,不改 z 深度关系太多;迭代次数固定,保证同一份数据每次布局一致。
|
|
31
|
+
relax(placed, { iterations: 60, gap: 1.55, strength: 0.5 });
|
|
32
|
+
return placed;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
function relax(items, { iterations, gap, strength }) {
|
|
36
|
+
if (items.length < 2) return;
|
|
37
|
+
for (let round = 0; round < iterations; round++) {
|
|
38
|
+
let moved = false;
|
|
39
|
+
for (let i = 0; i < items.length; i++) {
|
|
40
|
+
const a = items[i];
|
|
41
|
+
for (let j = i + 1; j < items.length; j++) {
|
|
42
|
+
const b = items[j];
|
|
43
|
+
const dx = b.pos.x - a.pos.x;
|
|
44
|
+
const dy = b.pos.y - a.pos.y;
|
|
45
|
+
const dz = b.pos.z - a.pos.z;
|
|
46
|
+
const dist = Math.hypot(dx, dy, dz) || 1e-6;
|
|
47
|
+
const minDist = (a.radius + b.radius) * gap;
|
|
48
|
+
if (dist >= minDist) continue;
|
|
49
|
+
// 单位向量;重合时用一个确定的、由索引决定的偏移方向,避免除零和随机抖动
|
|
50
|
+
const ux = dist > 1e-5 ? dx / dist : Math.cos(i * 2.39996);
|
|
51
|
+
const uy = dist > 1e-5 ? dy / dist : Math.sin(i * 2.39996);
|
|
52
|
+
const uz = dist > 1e-5 ? dz / dist : 0.35;
|
|
53
|
+
const push = (minDist - dist) * strength * 0.5;
|
|
54
|
+
a.pos.x -= ux * push; a.pos.y -= uy * push; a.pos.z -= uz * push;
|
|
55
|
+
b.pos.x += ux * push; b.pos.y += uy * push; b.pos.z += uz * push;
|
|
56
|
+
moved = true;
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
if (!moved) break;
|
|
60
|
+
}
|
|
22
61
|
}
|
package/lib/render/render.mjs
CHANGED
|
@@ -121,11 +121,17 @@ function fire(id,now) {
|
|
|
121
121
|
}
|
|
122
122
|
}
|
|
123
123
|
function select(x,y) {
|
|
124
|
-
|
|
124
|
+
// 命中判定:先按「离光标最近的节点」选,而不是「第一个框住光标的节点」。
|
|
125
|
+
// 原来要求 值<该节点半径,小节点容易被邻居抢走 → 点错。
|
|
126
|
+
// 现在给每个节点一个最小容差,再用距离减去一点半径作为打分(同样近时偏向大的那个)。
|
|
127
|
+
let best=null,bestScore=Infinity;
|
|
125
128
|
for (const star of DATA.stars) {
|
|
126
129
|
if (!star._screen) continue;
|
|
127
130
|
const value=Math.hypot(star._screen.x-x,star._screen.y-y);
|
|
128
|
-
|
|
131
|
+
const tolerance=Math.max(14,star._screen.r*1.15);
|
|
132
|
+
if (value>tolerance) continue;
|
|
133
|
+
const score=value-star._screen.r*0.22;
|
|
134
|
+
if (score<bestScore) {bestScore=score;best=star;}
|
|
129
135
|
}
|
|
130
136
|
if (!best) return;
|
|
131
137
|
fire(best.id,performance.now());
|