dsh-memory-engine 0.7.6 → 0.8.0
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 +32 -6
- package/lib/client.js +232 -42
- package/lib/client.src.js +111 -22
- package/lib/config-write.js +2 -0
- package/lib/config.js +7 -2
- package/lib/index.js +8 -4
- package/lib/index.js.bak-before-titlefix +235 -0
- package/lib/map.js +1 -0
- package/lib/render/canvas.js +90 -20
- package/lib/render/layout.mjs +33 -0
- package/lib/render/render.mjs +15 -2
- package/package.json +1 -1
- package/scripts/build-client.mjs +4 -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,7 +11,33 @@ DSH 插件的本地记忆引擎,带神经网络可视化。它让 agent 保存
|
|
|
11
11
|
|
|
12
12
|

|
|
13
13
|
|
|
14
|
-
当前版本:0.
|
|
14
|
+
当前版本:0.8.0。Node ≥22,零新增依赖。新增四套配色与三种布局,默认保留原来的经典星空与球体。自动记忆默认关闭,由用户在图谱头部或设置页手动打开;开启后每轮对话会额外调用一次模型,可能产生费用。自动导入默认只收聊天会话的用户消息,支持来源白名单、工作指令过滤、流程话过滤、规则分类、错误原因、一次性重扫和账本清理。
|
|
15
|
+
|
|
16
|
+
## 0.8.0 图谱外观
|
|
17
|
+
|
|
18
|
+
图谱头部点击「外观」,可组合选择四套配色与三种布局;「设置」页也有同一组选项。点选立即应用并保存,刷新后保留;保存失败会回滚并显示原因。「恢复默认」同时恢复经典星空和球体。
|
|
19
|
+
|
|
20
|
+
| 维度 | 选项 |
|
|
21
|
+
| --- | --- |
|
|
22
|
+
| 配色 | 经典星空 `classic` / 赛博霓虹 `neon` / 铅绘素描 `paper` / 极简单色 `mono` |
|
|
23
|
+
| 布局 | 球体 `sphere` / 平面圆 `flat` / 螺旋银河 `spiral` |
|
|
24
|
+
|
|
25
|
+
铅绘素描采用素纸底色、石墨灰节点和极淡光晕。平面圆按连接数分为三个同心环;螺旋银河沿三条旋臂排列。三种布局都保留角色卡中心、旋转、缩放和点击详情。切换时保留已选节点、相机及动画状态,不重新请求记忆数据,不改变记忆的重要度、留存或节点尺寸算法。
|
|
26
|
+
|
|
27
|
+
在现有配置中可合并 `"graphTheme": "classic"` 和 `"graphLayout": "sphere"`。这两个字段通过现有配置接口和备份、原子写流程保存,非法值返回 400。未配置时外观与升级前相同;没有角色卡的库仍保留原来的默认脑状布局。CLI 生成的离线图谱也读取同一配置。
|
|
28
|
+
|
|
29
|
+
## 0.7.7 修复:工具参数名撞 JSON Schema 保留字
|
|
30
|
+
|
|
31
|
+
`memory_map` 的 `title`、`memory_forget` 的 `id` 是 JSON Schema 的保留关键字。宿主如果没把参数表转换成完整 schema,直接把这些名字当关键字发送,DeepSeek 会整轮返回 400:
|
|
32
|
+
|
|
33
|
+
```
|
|
34
|
+
Invalid schema for function 'memory_map':
|
|
35
|
+
{"type":"string","description":"页面标题..."} is not of type "string"
|
|
36
|
+
```
|
|
37
|
+
|
|
38
|
+
修复:改名为 `pageTitle` / `memoryId`,旧参数名仍作为别名兼容,已有调用不受影响。
|
|
39
|
+
|
|
40
|
+
**给插件作者**:工具参数名不要用 JSON Schema 关键字——`title`、`description`、`type`、`id`、`$ref`、`enum`、`default`、`required`、`properties`、`items`、`additionalProperties` 等。宿主把参数表当 schema 解析时会直接报错。
|
|
15
41
|
|
|
16
42
|
## 从 0.6.1 升级到 0.7.0 的变化
|
|
17
43
|
|
|
@@ -120,7 +146,7 @@ peerDependencies 已放宽为 >=0.1.0-rc.8 <0.3.0 并标为 optional——宿主
|
|
|
120
146
|
|
|
121
147
|
`lib/render/canvas.js` 是唯一绘制源码,导出 `createRenderer(canvas, data, options)`,每个实例独立维护相机、节点缓存、脉冲和动画生命周期。它不导入模块,也不依赖 ModuleLoader。
|
|
122
148
|
|
|
123
|
-
`lib/client.src.js` 是 ModuleLoader
|
|
149
|
+
`lib/client.src.js` 是 ModuleLoader 工厂模板;修改它、Canvas 源码或布局源码后运行 `pnpm build`(也可 `npm run build`),生成单文件 `lib/client.js`。`layout.mjs` 中的 `layoutPositions()` 是新布局的唯一实现,构建与离线 HTML 均直接内联它;默认球体仍沿用原有服务器布局,切换回来恢复原坐标。产物头部标有「别直接改这个文件」。构建先写同目录临时文件,再原子替换;相同输入得到逐字节相同的产物,失败会非零退出。
|
|
124
150
|
|
|
125
151
|
`lib/render/render.mjs` 只保留 HTML、标题、图例、复习清单和原有详情外壳,读取同一份 Canvas 源码并内联。两个消费者共用 `scripts/lib/strip-export.mjs` 去掉导出声明,发布客户端和离线 HTML 都不需要运行时模块加载。离线 HTML 可直接双击打开。
|
|
126
152
|
|
|
@@ -131,10 +157,10 @@ peerDependencies 已放宽为 >=0.1.0-rc.8 <0.3.0 并标为 optional——宿主
|
|
|
131
157
|
| 方法 | 路径 | 返回内容 |
|
|
132
158
|
| --- | --- | --- |
|
|
133
159
|
| GET | `/memory-engine/` | 独立浏览器可直接访问的整页 HTML 图谱;应用内界面不再加载此页面 |
|
|
134
|
-
| GET | `/memory-engine/api/map` | `{ nodes, links, review, stats, generatedAt, core, spinSpeed }
|
|
160
|
+
| GET | `/memory-engine/api/map` | `{ nodes, links, review, stats, generatedAt, core, spinSpeed, graphTheme, graphLayout }`;nodes 保留原始球体坐标,客户端按 graphLayout 显示;配置角色卡时 nodes[0] 为 isCore,core 含 id/title/preview,否则为 null;stats 统计图谱节点 |
|
|
135
161
|
| GET | `/memory-engine/api/stats` | 记忆总数、分类、衰减状态及存储位置 |
|
|
136
162
|
| GET | `/memory-engine/api/onboard` | `{ empty, prompt, steps, dataDir }`,空库和手动帮助共用 |
|
|
137
|
-
| GET | `/memory-engine/api/config` |
|
|
163
|
+
| GET | `/memory-engine/api/config` | 包含 `graphTheme`、`graphLayout`,保留既有公开字段;不返回 raw、来源路径或模型配置 |
|
|
138
164
|
| PUT | `/memory-engine/api/config` | 合并合法白名单字段,返回公开配置和 `backup` 文件名 |
|
|
139
165
|
| GET | `/memory-engine/api/list?q=&kind=&sort=&limit=&offset=` | `{ total, allTotal, limit, offset, items }`;包含留存、连接数和 60 字预览 |
|
|
140
166
|
| GET | `/memory-engine/api/item/:id` | 完整条目、留存和连接数;不存在返回 404 |
|
package/lib/client.js
CHANGED
|
@@ -20,8 +20,43 @@ window.__ModuleLoader__.load({
|
|
|
20
20
|
const performance = window.performance;
|
|
21
21
|
const ctx = canvas.getContext('2d', { alpha: false });
|
|
22
22
|
if (!ctx) throw new Error('Canvas 2D 不可用');
|
|
23
|
-
const
|
|
24
|
-
|
|
23
|
+
const THEMES = {
|
|
24
|
+
classic: {
|
|
25
|
+
colors: { '人格':[255,215,106], '印象':[143,216,255], '知识':[126,231,178], '日记':[190,160,255], '对话':[255,160,140], '情绪':[255,90,90] },
|
|
26
|
+
fallback:[145,205,255], background:'#03060c', grid:'rgba(40,90,140,0.055)',
|
|
27
|
+
link:'rgba(110,190,255,0.30)', linkHighlight:'rgba(160,220,255,0.22)',
|
|
28
|
+
pulse:[205,245,255], center:[255,255,255], fading:[255,110,110], glow:[0.7,0.19],
|
|
29
|
+
vignette:['rgba(0,0,0,0)','rgba(0,0,0,.78)'],
|
|
30
|
+
},
|
|
31
|
+
neon: {
|
|
32
|
+
colors: { '人格':[255,255,255], '印象':[63,242,224], '知识':[255,95,210], '事件':[165,107,255], '日记':[192,125,255], '对话':[255,147,233], '情绪':[255,66,130] },
|
|
33
|
+
fallback:[145,143,255], background:'#0a0410', grid:'rgba(156,70,210,0.07)',
|
|
34
|
+
link:['rgba(255,95,210,0.36)','rgba(165,107,255,0.36)'], linkHighlight:'rgba(224,175,255,0.24)',
|
|
35
|
+
pulse:[245,210,255], center:[255,255,255], fading:[255,90,165], glow:[0.7,0.19],
|
|
36
|
+
vignette:['rgba(10,4,16,0)','rgba(10,4,16,.78)'],
|
|
37
|
+
},
|
|
38
|
+
paper: {
|
|
39
|
+
colors: { '人格':[40,40,40], '印象':[107,107,107], '知识':[64,64,64], '事件':[85,85,85], '日记':[133,133,133], '对话':[116,116,116], '情绪':[76,76,76] },
|
|
40
|
+
fallback:[96,96,96], background:'#f7f5f0', grid:'rgba(80,78,73,0.022)',
|
|
41
|
+
link:'rgba(65,65,65,0.34)', linkHighlight:'rgba(100,100,100,0.055)',
|
|
42
|
+
pulse:[48,48,48], center:[36,36,36], fading:[126,126,126], glow:[0.025,0.006],
|
|
43
|
+
vignette:['rgba(135,132,125,0)','rgba(135,132,125,.08)'],
|
|
44
|
+
},
|
|
45
|
+
mono: {
|
|
46
|
+
colors: { '人格':[232,232,232], '印象':[200,200,200], '知识':[255,255,255], '事件':[138,138,138], '日记':[174,174,174], '对话':[218,218,218], '情绪':[160,160,160] },
|
|
47
|
+
fallback:[185,185,185], background:'#0d0d0d', grid:'rgba(130,130,130,0.045)',
|
|
48
|
+
link:'rgba(178,178,178,0.30)', linkHighlight:'rgba(210,210,210,0.22)',
|
|
49
|
+
pulse:[235,235,235], center:[255,255,255], fading:[150,150,150], glow:[0.7,0.19],
|
|
50
|
+
vignette:['rgba(0,0,0,0)','rgba(0,0,0,.78)'],
|
|
51
|
+
},
|
|
52
|
+
};
|
|
53
|
+
let themeName = options.graphTheme ?? data.graphTheme ?? 'classic';
|
|
54
|
+
if (!Object.hasOwn(THEMES, themeName)) throw new Error('未知图谱配色');
|
|
55
|
+
let theme = THEMES[themeName];
|
|
56
|
+
let layoutName = options.graphLayout ?? data.graphLayout ?? 'sphere';
|
|
57
|
+
if (!['sphere', 'flat', 'spiral'].includes(layoutName)) throw new Error('未知图谱布局');
|
|
58
|
+
let sphereNodes = [];
|
|
59
|
+
const layouts = new Map();
|
|
25
60
|
let nodes = [], links = [], hubs = [], linkTwinkle = [], visibleLinks = [];
|
|
26
61
|
const byId = new Map(), adjacency = new Map(), sprites = new Map();
|
|
27
62
|
const pulses = [], firing = new Map(), selections = new Set();
|
|
@@ -40,13 +75,44 @@ function glowSprite(rgb) {
|
|
|
40
75
|
off.width = off.height = 96;
|
|
41
76
|
const brush = off.getContext('2d');
|
|
42
77
|
const gradient = brush.createRadialGradient(48,48,1,48,48,48);
|
|
43
|
-
gradient.addColorStop(0,'rgba('+rgb.join(',')+',0
|
|
44
|
-
gradient.addColorStop(0.4,'rgba('+rgb.join(',')+',
|
|
78
|
+
gradient.addColorStop(0,'rgba('+rgb.join(',')+','+theme.glow[0]+')');
|
|
79
|
+
gradient.addColorStop(0.4,'rgba('+rgb.join(',')+','+theme.glow[1]+')');
|
|
45
80
|
gradient.addColorStop(1,'rgba('+rgb.join(',')+',0)');
|
|
46
81
|
brush.fillStyle = gradient; brush.fillRect(0,0,96,96);
|
|
47
82
|
return off;
|
|
48
83
|
}
|
|
49
84
|
|
|
85
|
+
function recolor() {
|
|
86
|
+
sprites.clear();
|
|
87
|
+
for (const star of nodes) {
|
|
88
|
+
star._rgb = theme.colors[star.kind] || theme.fallback;
|
|
89
|
+
const key = star._rgb.join(',');
|
|
90
|
+
if (!sprites.has(key)) sprites.set(key, glowSprite(star._rgb));
|
|
91
|
+
star._glow = sprites.get(key);
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
function setTheme(nextTheme) {
|
|
96
|
+
if (destroyed || nextTheme === themeName) return;
|
|
97
|
+
if (!Object.hasOwn(THEMES, nextTheme)) throw new Error('未知图谱配色');
|
|
98
|
+
themeName = nextTheme;
|
|
99
|
+
theme = THEMES[nextTheme];
|
|
100
|
+
recolor();
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
function setLayout(nextLayout) {
|
|
104
|
+
if (destroyed) return;
|
|
105
|
+
if (!['sphere', 'flat', 'spiral'].includes(nextLayout)) throw new Error('未知图谱布局');
|
|
106
|
+
if (layoutName === nextLayout && layouts.has(nextLayout)) return;
|
|
107
|
+
if (!layouts.has(nextLayout)) {
|
|
108
|
+
if (typeof options.layoutNodes !== 'function') throw new Error('缺少图谱布局函数');
|
|
109
|
+
layouts.set(nextLayout, options.layoutNodes(sphereNodes, nextLayout).map(node => ({ ...node.pos })));
|
|
110
|
+
}
|
|
111
|
+
const positions = layouts.get(nextLayout);
|
|
112
|
+
nodes.forEach((node, index) => { node.pos = { ...positions[index] }; });
|
|
113
|
+
layoutName = nextLayout;
|
|
114
|
+
}
|
|
115
|
+
|
|
50
116
|
// 字符串 → 32 位无符号哈希。给连线的闪烁参数做种子:
|
|
51
117
|
// 用 id 而不是 Math.random,保证同一条线在每次打开时节奏一致(不会每次刷新都跳),
|
|
52
118
|
// 但不同连线之间互不同步。
|
|
@@ -63,6 +129,10 @@ function glowSprite(rgb) {
|
|
|
63
129
|
if (destroyed) return;
|
|
64
130
|
spinSpeed=nextData.spinSpeed??0.35;
|
|
65
131
|
nodes = nextData.nodes.map(node => ({ ...node, pos: { ...node.pos } }));
|
|
132
|
+
sphereNodes = nextData.nodes.map(node => ({ ...node, pos: { ...node.pos } }));
|
|
133
|
+
layouts.clear();
|
|
134
|
+
layouts.set('sphere', sphereNodes.map(node => ({ ...node.pos })));
|
|
135
|
+
if (layoutName !== 'sphere') setLayout(layoutName);
|
|
66
136
|
links = nextData.links.map(link => [...link]);
|
|
67
137
|
byId.clear(); adjacency.clear(); sprites.clear(); pulses.length = 0; firing.clear();
|
|
68
138
|
selectedId=typeof options.selectedId==='string'?options.selectedId:null;
|
|
@@ -110,7 +180,6 @@ function glowSprite(rgb) {
|
|
|
110
180
|
.slice(0, LINK_DRAW_CAP);
|
|
111
181
|
}
|
|
112
182
|
for (const star of nodes) {
|
|
113
|
-
star._rgb = colors[star.kind] || fallback;
|
|
114
183
|
const count = Math.min(9, 4 + Math.round(Math.sqrt(star.degree) * 1.2));
|
|
115
184
|
star._dendrites = Array.from({length:count},(_,index) => ({
|
|
116
185
|
angle:index/count*Math.PI*2+random()*0.7,
|
|
@@ -118,11 +187,7 @@ for (const star of nodes) {
|
|
|
118
187
|
fork:random()<0.55 ? (random()-0.5)*1.5 : null
|
|
119
188
|
}));
|
|
120
189
|
}
|
|
121
|
-
|
|
122
|
-
const key = star._rgb.join(',');
|
|
123
|
-
if (!sprites.has(key)) sprites.set(key,glowSprite(star._rgb));
|
|
124
|
-
star._glow = sprites.get(key);
|
|
125
|
-
}
|
|
190
|
+
recolor();
|
|
126
191
|
|
|
127
192
|
}
|
|
128
193
|
|
|
@@ -226,8 +291,8 @@ function frame(now) {
|
|
|
226
291
|
}
|
|
227
292
|
nextFire=now+1400+Math.random()*3200;
|
|
228
293
|
}
|
|
229
|
-
ctx.globalCompositeOperation='source-over'; ctx.fillStyle=
|
|
230
|
-
ctx.strokeStyle=
|
|
294
|
+
ctx.globalCompositeOperation='source-over'; ctx.fillStyle=theme.background; ctx.fillRect(0,0,width,height);
|
|
295
|
+
ctx.strokeStyle=theme.grid;ctx.lineWidth=1;
|
|
231
296
|
for (let index=0;index<20;index++) {
|
|
232
297
|
const y=index/20*height;
|
|
233
298
|
ctx.beginPath();ctx.moveTo(0,y);ctx.bezierCurveTo(width*.3,y+Math.sin(now*.00007+index)*30,width*.7,y-Math.cos(now*.00006+index)*30,width,y);ctx.stroke();
|
|
@@ -262,13 +327,18 @@ function frame(now) {
|
|
|
262
327
|
}
|
|
263
328
|
}
|
|
264
329
|
if (linkPaths.length) {
|
|
265
|
-
|
|
330
|
+
if (Array.isArray(theme.link)) {
|
|
331
|
+
const gradient = ctx.createLinearGradient(0,0,width,height);
|
|
332
|
+
gradient.addColorStop(0,theme.link[0]);
|
|
333
|
+
gradient.addColorStop(1,theme.link[1]);
|
|
334
|
+
ctx.strokeStyle=gradient;
|
|
335
|
+
} else ctx.strokeStyle=theme.link;
|
|
266
336
|
ctx.lineWidth=1;
|
|
267
337
|
ctx.beginPath();
|
|
268
338
|
for (const p of linkPaths) { ctx.moveTo(p[0],p[1]); ctx.quadraticCurveTo(p[2],p[3],p[4],p[5]); }
|
|
269
339
|
ctx.stroke();
|
|
270
340
|
// 更亮的一层,让关联线看得清
|
|
271
|
-
ctx.strokeStyle=
|
|
341
|
+
ctx.strokeStyle=theme.linkHighlight;
|
|
272
342
|
ctx.lineWidth=2;
|
|
273
343
|
ctx.stroke();
|
|
274
344
|
}
|
|
@@ -281,7 +351,7 @@ function frame(now) {
|
|
|
281
351
|
const inverse=1-fraction;
|
|
282
352
|
const x=inverse*inverse*first.p.x+2*inverse*fraction*mx+fraction*fraction*second.p.x;
|
|
283
353
|
const y=inverse*inverse*first.p.y+2*inverse*fraction*my+fraction*fraction*second.p.y;
|
|
284
|
-
ctx.fillStyle='rgba(
|
|
354
|
+
ctx.fillStyle='rgba('+theme.pulse.join(',')+','+(.9*inverse)+')';ctx.beginPath();ctx.arc(x,y,2.5+2*inverse,0,Math.PI*2);ctx.fill();
|
|
285
355
|
}
|
|
286
356
|
for (const {star,p} of points) {
|
|
287
357
|
const act=firing.get(star.id)||0,ret=Math.max(.05,Math.min(1.2,star.retention));
|
|
@@ -321,9 +391,9 @@ function frame(now) {
|
|
|
321
391
|
ctx.globalAlpha=Math.min(1,.4+intensity*.7);ctx.drawImage(star._glow,p.x-glowRadius,p.y-glowRadius,glowRadius*2,glowRadius*2);ctx.globalAlpha=1;
|
|
322
392
|
ctx.fillStyle='rgba('+rgb+','+Math.min(1,.42*intensity)+')';ctx.beginPath();ctx.arc(p.x,p.y,radius,0,Math.PI*2);ctx.fill();
|
|
323
393
|
ctx.strokeStyle='rgba('+rgb+','+Math.min(1,.75*intensity)+')';ctx.lineWidth=1;ctx.stroke();
|
|
324
|
-
ctx.fillStyle='rgba(
|
|
394
|
+
ctx.fillStyle='rgba('+theme.center.join(',')+','+Math.min(1,.6*intensity)+')';ctx.beginPath();ctx.arc(p.x,p.y,radius*.42,0,Math.PI*2);ctx.fill();
|
|
325
395
|
if (star.retention<.25) {
|
|
326
|
-
ctx.strokeStyle='rgba(
|
|
396
|
+
ctx.strokeStyle='rgba('+theme.fading.join(',')+','+Math.max(0,.45-star.retention)+')';ctx.setLineDash([3,4]);
|
|
327
397
|
ctx.beginPath();ctx.arc(p.x,p.y,radius*3.2,0,Math.PI*2);ctx.stroke();ctx.setLineDash([]);
|
|
328
398
|
}
|
|
329
399
|
star._screen={x:p.x,y:p.y,r:Math.max(8,radius*2.6)};
|
|
@@ -334,7 +404,7 @@ function frame(now) {
|
|
|
334
404
|
}
|
|
335
405
|
ctx.globalCompositeOperation='source-over';
|
|
336
406
|
const vignette=ctx.createRadialGradient(width/2,height/2,Math.min(width,height)*.34,width/2,height/2,Math.max(width,height)*.8);
|
|
337
|
-
vignette.addColorStop(0,
|
|
407
|
+
vignette.addColorStop(0,theme.vignette[0]);vignette.addColorStop(1,theme.vignette[1]);
|
|
338
408
|
ctx.fillStyle=vignette;ctx.fillRect(0,0,width,height);
|
|
339
409
|
}
|
|
340
410
|
function start() {
|
|
@@ -387,12 +457,12 @@ function frame(now) {
|
|
|
387
457
|
if (destroyed) return;
|
|
388
458
|
stop(); destroyed=true;
|
|
389
459
|
selections.clear(); byId.clear(); adjacency.clear(); sprites.clear();
|
|
390
|
-
nodes=[]; links=[]; hubs=[];
|
|
460
|
+
nodes=[]; links=[]; hubs=[]; sphereNodes=[]; layouts.clear();
|
|
391
461
|
}
|
|
392
462
|
|
|
393
463
|
setData(data);
|
|
394
464
|
return {
|
|
395
|
-
start, stop, resize, setData, destroy,
|
|
465
|
+
start, stop, resize, setData, setTheme, setLayout, destroy,
|
|
396
466
|
fireNeuron(id) { if (!destroyed && byId.has(id)) fire(id,performance.now()); },
|
|
397
467
|
onSelect(callback) {
|
|
398
468
|
if (!destroyed) selections.add(callback);
|
|
@@ -402,11 +472,71 @@ function frame(now) {
|
|
|
402
472
|
}
|
|
403
473
|
|
|
404
474
|
|
|
475
|
+
function layoutPositions(nodes, layout = 'sphere') {
|
|
476
|
+
if (!['sphere', 'flat', 'spiral'].includes(layout)) throw new Error('未知图谱布局');
|
|
477
|
+
if (layout === 'sphere') return nodes.map(node => ({ ...node, pos: { ...node.pos } }));
|
|
478
|
+
const memories = nodes.filter(node => !node.isCore).sort((left, right) =>
|
|
479
|
+
(right.degree || 0) - (left.degree || 0) || (String(left.id) < String(right.id) ? -1 : String(left.id) > String(right.id) ? 1 : 0));
|
|
480
|
+
const positions = new Map();
|
|
481
|
+
function jitter(id, axis) {
|
|
482
|
+
let hash = 20261030;
|
|
483
|
+
for (const character of String(id) + ':' + axis) hash = Math.imul(hash ^ character.codePointAt(0), 16777619);
|
|
484
|
+
hash = Math.imul(hash ^ (hash >>> 16), 0x45d9f3b);
|
|
485
|
+
return ((hash ^ (hash >>> 16)) >>> 0) / 4294967295 * 2 - 1;
|
|
486
|
+
}
|
|
487
|
+
if (layout === 'flat') {
|
|
488
|
+
for (let ring = 0; ring < 3; ring++) {
|
|
489
|
+
const members = memories.slice(Math.ceil(memories.length * ring / 3), Math.ceil(memories.length * (ring + 1) / 3));
|
|
490
|
+
const radius = [30, 55, 80][ring];
|
|
491
|
+
members.forEach((node, index) => {
|
|
492
|
+
const angle = index / members.length * Math.PI * 2;
|
|
493
|
+
positions.set(node.id, { x: Math.cos(angle) * radius + jitter(node.id, 'x') * 3,
|
|
494
|
+
y: Math.sin(angle) * radius + jitter(node.id, 'y') * 3, z: 0 });
|
|
495
|
+
});
|
|
496
|
+
}
|
|
497
|
+
} else {
|
|
498
|
+
memories.forEach((node, index) => {
|
|
499
|
+
const fraction = index / Math.max(1, memories.length - 1);
|
|
500
|
+
const angle = fraction * 3.2 * Math.PI + index % 3 * Math.PI * 2 / 3 + jitter(node.id, 'angle') * 0.35;
|
|
501
|
+
const radius = 18 + fraction * 92;
|
|
502
|
+
positions.set(node.id, { x: Math.cos(angle) * radius, y: Math.sin(angle) * radius, z: jitter(node.id, 'z') * 14 });
|
|
503
|
+
});
|
|
504
|
+
}
|
|
505
|
+
return nodes.map(node => ({ ...node, pos: node.isCore ? { x: 0, y: 0, z: 0 } : positions.get(node.id) }));
|
|
506
|
+
}
|
|
405
507
|
|
|
406
|
-
var defaultSettings = { hideButton: false, showGuideOnEmpty: true, autoMemory: { enabled: false, maxPerTurn: 2, dailyCap: 40 }, showAutoMemoryCostHint: true };
|
|
407
|
-
var actionStyle = { padding: "7px 12px", borderRadius: "8px", border: "1px solid
|
|
508
|
+
var defaultSettings = { hideButton: false, showGuideOnEmpty: true, graphTheme: "classic", graphLayout: "sphere", autoMemory: { enabled: false, maxPerTurn: 2, dailyCap: 40 }, showAutoMemoryCostHint: true };
|
|
509
|
+
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" };
|
|
408
510
|
var fieldStyle = { boxSizing: "border-box", padding: "10px 12px", borderRadius: "8px", border: "1px solid #35536b", background: "#091422", color: "#e2f4ff", font: "inherit", minWidth: 0 };
|
|
409
511
|
var kinds = ["知识", "印象", "对话", "事件", "日记"];
|
|
512
|
+
var appearanceOptions = [
|
|
513
|
+
{ key: "graphTheme", label: "配色", choices: [["classic", "经典星空"], ["neon", "赛博霓虹"], ["paper", "铅绘素描"], ["mono", "极简单色"]] },
|
|
514
|
+
{ key: "graphLayout", label: "布局", choices: [["sphere", "球体"], ["flat", "平面圆"], ["spiral", "螺旋银河"]] },
|
|
515
|
+
];
|
|
516
|
+
var appearanceStyles = {
|
|
517
|
+
classic: {},
|
|
518
|
+
neon: { "--memory-text": "#f4dfff", "--memory-muted": "#ba9aca", "--memory-accent": "#ff9de1", "--memory-border": "#704580", "--memory-panel": "#170d21ee", "--memory-button": "#271333", "--memory-shadow": "none" },
|
|
519
|
+
paper: { "--memory-text": "#303030", "--memory-muted": "#72716d", "--memory-accent": "#404040", "--memory-border": "#b5b1a9", "--memory-panel": "#f7f5f0f2", "--memory-button": "#eeece6", "--memory-shadow": "none" },
|
|
520
|
+
mono: { "--memory-text": "#eeeeee", "--memory-muted": "#aaaaaa", "--memory-accent": "#eeeeee", "--memory-border": "#666666", "--memory-panel": "#171717ee", "--memory-button": "#252525", "--memory-shadow": "none" },
|
|
521
|
+
};
|
|
522
|
+
|
|
523
|
+
function AppearanceControls(props) {
|
|
524
|
+
return React.createElement("section", { "aria-label": "图谱外观", "data-memory-engine-appearance-options": props.location,
|
|
525
|
+
style: { color: "var(--memory-text,#dbeaf8)", font: "14px/1.7 system-ui,sans-serif" } },
|
|
526
|
+
appearanceOptions.map(function (group) {
|
|
527
|
+
return React.createElement("fieldset", { key: group.key, disabled: props.busy, style: { border: 0, margin: "0 0 14px", padding: 0 } },
|
|
528
|
+
React.createElement("legend", { style: { marginBottom: "8px", color: "var(--memory-muted,#9fb7ce)" } }, group.label),
|
|
529
|
+
React.createElement("div", { style: { display: "flex", flexWrap: "wrap", gap: "8px 14px" } }, group.choices.map(function (choice) {
|
|
530
|
+
return React.createElement("label", { key: choice[0], style: { display: "inline-flex", alignItems: "center", gap: "6px", cursor: props.busy ? "wait" : "pointer" } },
|
|
531
|
+
React.createElement("input", { type: "radio", name: "memory-" + props.location + "-" + group.key, value: choice[0],
|
|
532
|
+
"data-memory-engine-appearance-choice": group.key + ":" + choice[0],
|
|
533
|
+
checked: props.settings[group.key] === choice[0], style: { margin: 0, accentColor: "var(--memory-accent,#a4e6ff)" },
|
|
534
|
+
onChange: function () { var changes = {}; changes[group.key] = choice[0]; props.onChange(changes); } }), choice[1]);
|
|
535
|
+
})));
|
|
536
|
+
}), React.createElement("button", { type: "button", disabled: props.busy, style: actionStyle,
|
|
537
|
+
"data-memory-engine-appearance-reset": props.location,
|
|
538
|
+
onClick: function () { props.onChange({ graphTheme: "classic", graphLayout: "sphere" }); } }, "恢复默认"));
|
|
539
|
+
}
|
|
410
540
|
|
|
411
541
|
function requestApi(url, options) {
|
|
412
542
|
return fetch(url, Object.assign({ cache: "no-store" }, options)).then(function (response) {
|
|
@@ -560,6 +690,8 @@ function frame(now) {
|
|
|
560
690
|
|
|
561
691
|
function MemoryCanvas(props) {
|
|
562
692
|
var canvas = React.useRef(null);
|
|
693
|
+
var appearance = React.useRef(null);
|
|
694
|
+
appearance.current = { graphTheme: props.graphTheme || "classic", graphLayout: props.graphLayout || "sphere" };
|
|
563
695
|
var dataState = React.useState(null);
|
|
564
696
|
var data = dataState[0];
|
|
565
697
|
var setData = dataState[1];
|
|
@@ -576,7 +708,7 @@ function frame(now) {
|
|
|
576
708
|
setError("");
|
|
577
709
|
readApi("/memory-engine/api/map", controller.signal).then(function (nextData) {
|
|
578
710
|
if (!active) return;
|
|
579
|
-
renderer = createRenderer(canvas.current, nextData, { frameInterval: 25 });
|
|
711
|
+
renderer = createRenderer(canvas.current, nextData, Object.assign({ frameInterval: 25, layoutNodes: layoutPositions }, appearance.current));
|
|
580
712
|
props.rendererRef.current = renderer;
|
|
581
713
|
renderer.onSelect(function (node) { props.selectionVersion.current++; props.onSelect(node); });
|
|
582
714
|
renderer.start();
|
|
@@ -595,6 +727,11 @@ function frame(now) {
|
|
|
595
727
|
if (props.rendererRef.current === renderer) props.rendererRef.current = null;
|
|
596
728
|
};
|
|
597
729
|
}, [retry, props.rendererRef, props.selectionVersion, props.onSelect]);
|
|
730
|
+
React.useEffect(function () {
|
|
731
|
+
if (!props.rendererRef.current) return;
|
|
732
|
+
props.rendererRef.current.setTheme(appearance.current.graphTheme);
|
|
733
|
+
props.rendererRef.current.setLayout(appearance.current.graphLayout);
|
|
734
|
+
}, [props.graphTheme, props.graphLayout]);
|
|
598
735
|
return React.createElement(React.Fragment, null,
|
|
599
736
|
React.createElement("canvas", {
|
|
600
737
|
ref: canvas, "data-memory-engine-canvas": "true", "aria-label": "记忆神经网络",
|
|
@@ -602,21 +739,21 @@ function frame(now) {
|
|
|
602
739
|
}),
|
|
603
740
|
React.createElement("div", {
|
|
604
741
|
"data-memory-engine-summary": "true",
|
|
605
|
-
style: { position: "absolute", top: "16px", left: "18px", pointerEvents: "none", color: "
|
|
606
|
-
}, React.createElement("strong", { style: { color: "
|
|
742
|
+
style: { position: "absolute", top: "16px", left: "18px", pointerEvents: "none", color: "var(--memory-text,#d8e9f8)", font: "13px/1.65 system-ui,sans-serif", textShadow: "var(--memory-shadow,0 0 12px #3388bb88)" },
|
|
743
|
+
}, React.createElement("strong", { style: { color: "var(--memory-accent,#a4e6ff)", fontSize: "16px" } }, "记忆神经网络"),
|
|
607
744
|
React.createElement("div", null, data ? data.nodes.length + " 个神经元 · " + data.links.length + " 条突触" : "正在加载图谱……"),
|
|
608
|
-
React.createElement("div", { style: { color: "
|
|
745
|
+
React.createElement("div", { style: { color: "var(--memory-muted,#7491a8)", fontSize: "12px" } }, "大小=重要度和连接数 · 亮度=留存")),
|
|
609
746
|
data ? React.createElement("section", {
|
|
610
747
|
"aria-label": "复习清单",
|
|
611
|
-
style: { position: "absolute", bottom: "16px", left: "18px", width: "min(400px,calc(100% - 36px))", boxSizing: "border-box", maxHeight: "32%", overflow: "auto", padding: "15px", background: "
|
|
612
|
-
}, React.createElement("h2", { style: { fontSize: "14px", color: "
|
|
748
|
+
style: { position: "absolute", bottom: "16px", left: "18px", width: "min(400px,calc(100% - 36px))", boxSizing: "border-box", maxHeight: "32%", overflow: "auto", padding: "15px", background: "var(--memory-panel,#07111fee)", border: "1px solid var(--memory-border,#3976a055)", borderRadius: "14px", boxShadow: "var(--memory-shadow,0 0 40px #1763a033)", backdropFilter: "blur(8px)", font: "12px/1.65 system-ui,sans-serif", color: "var(--memory-text,#b1c6d9)" },
|
|
749
|
+
}, React.createElement("h2", { style: { fontSize: "14px", color: "var(--memory-accent,#ffdb82)", margin: "0 0 8px" } }, "复习清单 · 留存最低的记忆"),
|
|
613
750
|
data.review.map(function (item, index) {
|
|
614
751
|
return React.createElement("div", { key: item.id || index, style: { display: "flex", gap: "7px", alignItems: "center", margin: "5px 0" } },
|
|
615
752
|
React.createElement("span", { style: { flex: 1, overflow: "hidden", textOverflow: "ellipsis", whiteSpace: "nowrap" } }, (index + 1) + ". [" + item.kind + "] " + item.title),
|
|
616
|
-
React.createElement("span", { style: { width: "52px", height: "5px", borderRadius: "5px", background: "
|
|
617
|
-
React.createElement("i", { style: { display: "block", height: "100%", background: "
|
|
753
|
+
React.createElement("span", { style: { width: "52px", height: "5px", borderRadius: "5px", background: "var(--memory-border,#335066)", overflow: "hidden" } },
|
|
754
|
+
React.createElement("i", { style: { display: "block", height: "100%", background: "var(--memory-accent,#6bc9fa)", width: Math.min(100, Math.max(0, item.retention * 100)) + "%" } })));
|
|
618
755
|
})) : null,
|
|
619
|
-
React.createElement("div", { style: { position: "absolute", right: "18px", bottom: "18px", pointerEvents: "none", color: "
|
|
756
|
+
React.createElement("div", { style: { position: "absolute", right: "18px", bottom: "18px", pointerEvents: "none", color: "var(--memory-muted,#7b99ad)", font: "13px system-ui,sans-serif" } }, "拖拽旋转 · 滚轮缩放 · 点光点看关联"),
|
|
620
757
|
error ? React.createElement("div", { role: "alert", style: { position: "absolute", inset: "40% 20% auto", textAlign: "center", color: "#d8e9f8" } },
|
|
621
758
|
React.createElement("p", null, "图谱加载失败:" + error),
|
|
622
759
|
React.createElement("button", { type: "button", style: actionStyle, onClick: function () { setRetry(function (value) { return value + 1; }); } }, "重试")) : null);
|
|
@@ -904,6 +1041,9 @@ function frame(now) {
|
|
|
904
1041
|
React.createElement("span", null, props.settings.autoImport.message), " ",
|
|
905
1042
|
React.createElement("button", { type: "button", "data-memory-engine-use-auto-import": "true", disabled: props.configBusy,
|
|
906
1043
|
onClick: props.onUseAutoImport, style: actionStyle }, "改用宽松模式")) : null,
|
|
1044
|
+
React.createElement("section", { style: { marginBottom: "24px", padding: "20px", border: "1px solid var(--memory-border,#263f54)", borderRadius: "10px", background: "var(--memory-panel,#0b1624)" } },
|
|
1045
|
+
React.createElement("h2", { style: { margin: "0 0 12px", fontSize: "20px", color: "var(--memory-accent,#b7e9ff)" } }, "图谱外观"),
|
|
1046
|
+
React.createElement(AppearanceControls, { settings: props.settings, busy: props.configBusy, onChange: props.onAppearanceChange, location: "settings" })),
|
|
907
1047
|
React.createElement("section", { style: { marginBottom: "24px", paddingBottom: "20px", borderBottom: "1px solid #263f54" } },
|
|
908
1048
|
React.createElement("h2", { style: { margin: "0 0 12px", fontSize: "20px", color: "#b7e9ff" } }, "自动记忆"),
|
|
909
1049
|
React.createElement(AutoMemoryToggle, { settings: props.settings, busy: props.configBusy, onToggle: props.onToggle, location: "settings" }),
|
|
@@ -968,6 +1108,21 @@ function frame(now) {
|
|
|
968
1108
|
var pointer = React.useRef(null);
|
|
969
1109
|
var help = React.useRef(null);
|
|
970
1110
|
var dismissed = React.useRef(false);
|
|
1111
|
+
var appearanceState = React.useState(false);
|
|
1112
|
+
var appearanceOpen = appearanceState[0], setAppearanceOpen = appearanceState[1];
|
|
1113
|
+
var appearancePanel = React.useRef(null);
|
|
1114
|
+
var appearanceButton = React.useRef(null);
|
|
1115
|
+
React.useEffect(function () {
|
|
1116
|
+
if (!open || !appearanceOpen) return undefined;
|
|
1117
|
+
var first = appearancePanel.current && appearancePanel.current.querySelector("input:checked");
|
|
1118
|
+
if (first) first.focus();
|
|
1119
|
+
function outside(event) {
|
|
1120
|
+
if (appearancePanel.current && !appearancePanel.current.contains(event.target)
|
|
1121
|
+
&& appearanceButton.current && !appearanceButton.current.contains(event.target)) setAppearanceOpen(false);
|
|
1122
|
+
}
|
|
1123
|
+
window.addEventListener("pointerdown", outside);
|
|
1124
|
+
return function () { window.removeEventListener("pointerdown", outside); };
|
|
1125
|
+
}, [open, appearanceOpen]);
|
|
971
1126
|
React.useEffect(function () {
|
|
972
1127
|
return function () { if (configRequest.current) configRequest.current.abort(); };
|
|
973
1128
|
}, []);
|
|
@@ -982,6 +1137,23 @@ function frame(now) {
|
|
|
982
1137
|
}).finally(function () { if (!controller.signal.aborted) setConfigBusy(false); });
|
|
983
1138
|
return function () { controller.abort(); };
|
|
984
1139
|
}, [open]);
|
|
1140
|
+
function saveAppearance(changes) {
|
|
1141
|
+
if (configBusy || configRequest.current) return;
|
|
1142
|
+
if (Object.keys(changes).every(function (key) { return settings[key] === changes[key]; })) return;
|
|
1143
|
+
var previous = settings;
|
|
1144
|
+
var controller = new AbortController();
|
|
1145
|
+
var timer = setTimeout(function () { controller.abort(); }, 10000);
|
|
1146
|
+
configRequest.current = controller;
|
|
1147
|
+
setConfigBusy(true); setConfigError("");
|
|
1148
|
+
setSettings(Object.assign({}, settings, changes));
|
|
1149
|
+
requestApi("/memory-engine/api/config", { method: "PUT", headers: { "Content-Type": "application/json" },
|
|
1150
|
+
body: JSON.stringify(changes), signal: controller.signal }).then(function (data) {
|
|
1151
|
+
if (!controller.signal.aborted) setSettings(Object.assign({}, defaultSettings, data));
|
|
1152
|
+
}).catch(function (failure) {
|
|
1153
|
+
setSettings(previous);
|
|
1154
|
+
setConfigError("外观保存失败,已恢复原设置:" + (controller.signal.aborted ? "请求超时或已中断,请刷新确认状态" : failure.message));
|
|
1155
|
+
}).finally(function () { clearTimeout(timer); configRequest.current = null; setConfigBusy(false); });
|
|
1156
|
+
}
|
|
985
1157
|
function saveAutoMemory(enabled) {
|
|
986
1158
|
if (configRequest.current) return;
|
|
987
1159
|
var previous = settings;
|
|
@@ -1027,12 +1199,13 @@ function frame(now) {
|
|
|
1027
1199
|
dismissed.current = false;
|
|
1028
1200
|
setGuideOpen(false);
|
|
1029
1201
|
setSelected(null);
|
|
1202
|
+
setAppearanceOpen(false);
|
|
1030
1203
|
setTab("map");
|
|
1031
1204
|
setOpen(Date.now());
|
|
1032
1205
|
}
|
|
1033
|
-
function closeMap() { setSelected(null); setCostHint(false); setOpen(false); }
|
|
1206
|
+
function closeMap() { setSelected(null); setCostHint(false); setAppearanceOpen(false); setOpen(false); }
|
|
1034
1207
|
function startPointer(event) {
|
|
1035
|
-
if (event.target.closest("[data-memory-engine-detail], [data-memory-engine-guide-layer], [data-memory-engine-controls]")) { pointer.current = null; return; }
|
|
1208
|
+
if (event.target.closest("[data-memory-engine-detail], [data-memory-engine-guide-layer], [data-memory-engine-controls], [data-memory-engine-appearance-panel]")) { pointer.current = null; return; }
|
|
1036
1209
|
pointer.current = { x: event.clientX, y: event.clientY, moved: 0, version: selectionVersion.current };
|
|
1037
1210
|
}
|
|
1038
1211
|
function movePointer(event) {
|
|
@@ -1065,7 +1238,12 @@ function frame(now) {
|
|
|
1065
1238
|
}, [open, retry, settings.showGuideOnEmpty]);
|
|
1066
1239
|
React.useEffect(function () {
|
|
1067
1240
|
if (!open) return undefined;
|
|
1068
|
-
function onEscape() {
|
|
1241
|
+
function onEscape() {
|
|
1242
|
+
if (guideOpen) closeGuide();
|
|
1243
|
+
else if (appearanceOpen) { setAppearanceOpen(false); if (appearanceButton.current) appearanceButton.current.focus(); }
|
|
1244
|
+
else if (selected) setSelected(null);
|
|
1245
|
+
else closeMap();
|
|
1246
|
+
}
|
|
1069
1247
|
function onKey(event) {
|
|
1070
1248
|
if (event.key !== "Escape") return;
|
|
1071
1249
|
if (event.target && event.target.closest && event.target.closest("[data-memory-engine-confirm]")) return;
|
|
@@ -1075,7 +1253,7 @@ function frame(now) {
|
|
|
1075
1253
|
}
|
|
1076
1254
|
window.addEventListener("keydown", onKey, true);
|
|
1077
1255
|
return function () { window.removeEventListener("keydown", onKey, true); };
|
|
1078
|
-
}, [open, guideOpen, selected]);
|
|
1256
|
+
}, [open, guideOpen, selected, appearanceOpen]);
|
|
1079
1257
|
var button = React.createElement("button", {
|
|
1080
1258
|
type: "button",
|
|
1081
1259
|
onClick: openMap,
|
|
@@ -1089,12 +1267,15 @@ function frame(now) {
|
|
|
1089
1267
|
role: "dialog",
|
|
1090
1268
|
"aria-modal": "true",
|
|
1091
1269
|
"aria-label": "记忆图谱",
|
|
1270
|
+
"data-memory-engine-theme": settings.graphTheme, "data-memory-engine-layout": settings.graphLayout,
|
|
1092
1271
|
onPointerDownCapture: startPointer, onPointerMoveCapture: movePointer,
|
|
1093
1272
|
onPointerCancelCapture: function () { pointer.current = null; }, onClick: dismissOutside,
|
|
1094
|
-
style: { position: "fixed", inset: 0, maxWidth: "none", maxHeight: "none", zIndex: 9999, background: "#03060c" },
|
|
1095
|
-
}, tab === "map" ? React.createElement(MemoryCanvas, { key: open, rendererRef: renderer, selectionVersion: selectionVersion, onSelect: setSelected
|
|
1273
|
+
style: Object.assign({ position: "fixed", inset: 0, maxWidth: "none", maxHeight: "none", zIndex: 9999, background: "#03060c", colorScheme: settings.graphTheme === "paper" ? "light" : "dark" }, appearanceStyles[settings.graphTheme]),
|
|
1274
|
+
}, tab === "map" ? React.createElement(MemoryCanvas, { key: open, rendererRef: renderer, selectionVersion: selectionVersion, onSelect: setSelected,
|
|
1275
|
+
graphTheme: settings.graphTheme, graphLayout: settings.graphLayout })
|
|
1096
1276
|
: tab === "memories" ? React.createElement(MemoryList, { onChanged: function () { setRetry(function (value) { return value + 1; }); } })
|
|
1097
1277
|
: React.createElement(MemorySettings, { settings: settings, configBusy: configBusy, onToggle: toggleAutoMemory, onUseAutoImport: useAutoImport,
|
|
1278
|
+
onAppearanceChange: saveAppearance,
|
|
1098
1279
|
onCleared: function () { openMap(); setGuideOpen(true); } }),
|
|
1099
1280
|
tab !== "map" ? React.createElement("strong", { style: { position: "absolute", top: "23px", left: "24px", font: "600 18px system-ui,sans-serif", color: "#b7e9ff" } }, "记忆管理") : null,
|
|
1100
1281
|
React.createElement("style", null, "@keyframes dsh-memory-detail-in{from{opacity:0;transform:translateX(24px)}to{opacity:1;transform:translateX(0)}}@media(prefers-reduced-motion:reduce){[data-memory-engine-detail]{animation:none!important}}"),
|
|
@@ -1104,9 +1285,11 @@ function frame(now) {
|
|
|
1104
1285
|
}, React.createElement("div", { role: "tablist", "aria-label": "记忆面板", style: { display: "flex", gap: "4px", marginRight: "8px" } },
|
|
1105
1286
|
[["map", "图谱"], ["memories", "记忆"], ["settings", "设置"]].map(function (entry) {
|
|
1106
1287
|
return React.createElement("button", { key: entry[0], type: "button", role: "tab", "aria-selected": tab === entry[0], "data-memory-engine-tab": entry[0],
|
|
1107
|
-
onClick: function () { setSelected(null); setTab(entry[0]); },
|
|
1108
|
-
style: Object.assign({}, actionStyle, { background: tab === entry[0] ? "
|
|
1109
|
-
})), React.createElement(
|
|
1288
|
+
onClick: function () { setSelected(null); setAppearanceOpen(false); setTab(entry[0]); },
|
|
1289
|
+
style: Object.assign({}, actionStyle, { background: tab === entry[0] ? "var(--memory-button,#204762)" : "var(--memory-panel,#091422)", borderColor: tab === entry[0] ? "var(--memory-accent,#8acded)" : "var(--memory-border,#35536b)" }) }, entry[1]);
|
|
1290
|
+
})), React.createElement("button", { ref: appearanceButton, type: "button", "data-memory-engine-appearance": "true", "aria-expanded": appearanceOpen,
|
|
1291
|
+
"aria-haspopup": "dialog", onClick: function () { setAppearanceOpen(!appearanceOpen); }, style: actionStyle }, "外观"),
|
|
1292
|
+
React.createElement(AutoMemoryToggle, { settings: settings, busy: configBusy, onToggle: toggleAutoMemory, location: "header" }), React.createElement("button", {
|
|
1110
1293
|
ref: help, type: "button", onClick: function () { setGuideOpen(true); },
|
|
1111
1294
|
"aria-label": "查看使用指南", title: "使用指南", style: actionStyle,
|
|
1112
1295
|
}, "?"), React.createElement("button", {
|
|
@@ -1114,8 +1297,15 @@ function frame(now) {
|
|
|
1114
1297
|
onClick: closeMap,
|
|
1115
1298
|
"aria-label": "关闭记忆图谱",
|
|
1116
1299
|
style: actionStyle,
|
|
1117
|
-
}, "关闭 ×")),
|
|
1118
|
-
style: { position: "absolute", top: "
|
|
1300
|
+
}, "关闭 ×")), appearanceOpen ? React.createElement("section", { ref: appearancePanel, role: "dialog", "aria-label": "外观", "data-memory-engine-appearance-panel": "true",
|
|
1301
|
+
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
|
+
padding: "18px", border: "1px solid var(--memory-border,#3976a0)", borderRadius: "12px", background: "var(--memory-panel,#07111f)", boxShadow: "0 12px 35px #0004" } },
|
|
1303
|
+
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: actionStyle,
|
|
1305
|
+
onClick: function () { setAppearanceOpen(false); if (appearanceButton.current) appearanceButton.current.focus(); } }, "×")),
|
|
1306
|
+
React.createElement(AppearanceControls, { settings: settings, busy: configBusy, onChange: saveAppearance, location: "header" })) : null,
|
|
1307
|
+
configError ? React.createElement("p", { role: "alert", "data-memory-engine-config-error": "true",
|
|
1308
|
+
style: { position: "absolute", top: "50px", left: "18px", zIndex: 4, color: "#ff9c9c", background: "#271923", padding: "8px 12px", borderRadius: "8px", maxWidth: "min(600px,90vw)" } }, configError) : null,
|
|
1119
1309
|
costHint ? React.createElement(ConfirmationDialog, { title: "开启自动记忆", message: "自动记忆会在每轮对话后调用一次模型,大约每轮几分钱。可以随时在这里关掉。实际费用以模型及计费方案为准。",
|
|
1120
1310
|
submitLabel: "知道了,开启", busy: configBusy, onClose: function () { setCostHint(false); }, onConfirm: function () { saveAutoMemory(true); } }) : null,
|
|
1121
1311
|
selected ? React.createElement(MemoryDetails, {
|