deepseek-plastic-memory 2.1.5 → 3.3.8
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 +37 -0
- package/SKILL.md +5 -0
- package/client.js +154 -31
- package/identity.md +12 -0
- package/index.js +203 -4
- package/memtree.mjs +47 -2
- package/package.json +5 -2
- package/public/sky/app.js +2336 -0
- package/public/sky/app.js.bak +2336 -0
- package/public/sky/index.html +286 -0
- package/public/sky/vendor/three.core.js +60586 -0
- package/public/sky/vendor/three.module.js +19719 -0
- package/sky.js +332 -0
package/README.md
CHANGED
|
@@ -25,10 +25,43 @@ dsh plugin add deepseek-plastic-memory
|
|
|
25
25
|
|
|
26
26
|
装完设置里多一页「可塑性记忆」,左边分区右边条目;会话顶栏也多个 🧠,点开是同一个面板。
|
|
27
27
|
|
|
28
|
+
条目一行放不下的时候,点后面的「查看」弹一张卡片看全文,Esc 或点空白处关掉。
|
|
29
|
+
|
|
28
30
|
不想装插件也行,当普通 node 脚本用,命令一样。
|
|
29
31
|
|
|
30
32
|
**小提示**:觉得有必要的时候,或者在开新会话之前,先让 DSH 把当前记忆总结一遍。
|
|
31
33
|
|
|
34
|
+
## 记忆星空
|
|
35
|
+
|
|
36
|
+
设置页里那行「可塑性记忆」右边有个「记忆星空」按钮,会话顶栏也有一个;点一下用本机 Edge 打开一片星系。
|
|
37
|
+
|
|
38
|
+
- 主星云 = 主分区,副星辰 = 子分区,每颗小点 = 一条记忆
|
|
39
|
+
- 拖拽绕中心转、滚轮缩放、单击选中、双击星辰把那一团放大
|
|
40
|
+
- 选中主星辰,别的星团会暗下去;左边的列表只跳星辰,记忆在选中星辰后看
|
|
41
|
+
- **右上角两个主题**:星野(深空 + 立体星云 + 星芒)、恒星暴(粒子旋涡 + 星爆团 + 尘埃带),一键切换,选过会记住
|
|
42
|
+
- **顶栏「AI 分类」**:点一下先确认(会消耗 token),确认后把任务排进队列;下一轮对话里模型会把全部记忆 dump 出来逐条重归类,放错的挪走、需要的新开分区,做完汇报
|
|
43
|
+
- **左上角演示模式**:打开后有 1~10 档转速手调,勾上「只留星空」把面板、顶栏、曲线全藏起来,只剩星空本体(Esc 退出)
|
|
44
|
+
- **星野 / 恒星暴**:星野(深空 + 立体星云 + 星芒)、恒星暴(粒子旋涡 + 星爆团 + 尘埃带),一键切换,选过会记住
|
|
45
|
+
- 左下角遗忘曲线:新生 → 7 天 → 30 天,只写分区名,点分区名能直接「固定住」
|
|
46
|
+
- 记忆之间常年牵着一层乱线,检索或者点开某条记忆时,连到它的丝线会亮起来,光点顺着跑
|
|
47
|
+
- 主人 / 约定 / 喜好 这几类本来就不会被清,默认算固定记忆
|
|
48
|
+
|
|
49
|
+
星云、星系、星芒、尘埃全是用着色器 + 粒子现算的(fbm 噪声生成云丝,没有一张外部贴图),整片天空在显卡上实时渲染;页面是本地静态文件,**不调用任何模型接口**,看星空不烧 token。
|
|
50
|
+
|
|
51
|
+
## 换缸提示
|
|
52
|
+
|
|
53
|
+
换会话、换窗口、上下文被压缩,都只是换了个缸——鱼还是那条鱼。
|
|
54
|
+
|
|
55
|
+
插件每次组装系统提示词时都会带上 `identity.md` 里那段话(`plastic-memory:identity`,原样插入,不走变量替换),所以新会话一开就知道:读到这些记忆的,就是原来那个它。想改措辞直接编辑 `identity.md`,重启生效。
|
|
56
|
+
|
|
57
|
+
这段话只内置在插件里,不写进任何分区,所以在记忆列表和星空里都看不到它——新会话照样自带。
|
|
58
|
+
|
|
59
|
+
## 删东西
|
|
60
|
+
|
|
61
|
+
- 设置页每次选中分区,右上角有「删分区」:主分区会连子分区一起删,里面的记忆先写进 `_trash/<日期>.md`,不是直接没
|
|
62
|
+
- 记忆库那一行有「删工作区」:整份工作区记忆会被挪到 `ws/.deleted/` 下,全局库不能删
|
|
63
|
+
- 记忆星空里选中一颗星辰,面板上也有「删掉这颗星辰」,效果一样
|
|
64
|
+
|
|
32
65
|
## 命令
|
|
33
66
|
|
|
34
67
|
| 敲这个 | 干这个 |
|
|
@@ -64,6 +97,10 @@ node memtree.mjs add M>习惯 改代码先备份
|
|
|
64
97
|
|
|
65
98
|
模型想记东西不会直接落盘,先扔进待审队列。设置页「可塑性记忆」最上面就是它,每条后面三个按钮:改、驳、采纳。
|
|
66
99
|
|
|
100
|
+
点采纳会问写进哪个记忆库(全局 / 各工作区都列出来);记忆库只有一个「全局」时不问,直接写进去。写到别的工作区就是把这条搬过去,原库的待审里会消失。
|
|
101
|
+
|
|
102
|
+
工作区(记忆库)在「+ 工作区」里建。新建时会连默认分区一起建好:`M` 主人、`P` 约定、`C` 续缘、`R` 喜好、`T` 时间线,不用再手动开。
|
|
103
|
+
|
|
67
104
|
你自己写走「自己写一条」,选分区填一行,直接生效。
|
|
68
105
|
|
|
69
106
|
## 会忘
|
package/SKILL.md
CHANGED
|
@@ -38,6 +38,7 @@ $N $M add R *怕辣 # * 开头 = 重要,压缩时保住
|
|
|
38
38
|
$N $M done P i4bp # 约定完成
|
|
39
39
|
$N $M rm C i4fx # 删一条(进回收站;M/P 禁删)
|
|
40
40
|
$N $M mk 工作 # 新建分区
|
|
41
|
+
$N $M rmfolder X0 # 删分区(连子分区一起,记忆进回收站;内置分区要加 force)
|
|
41
42
|
$N $M gc # 跑遗忘曲线(先自动备份)
|
|
42
43
|
$N $M backups # 看有哪些备份
|
|
43
44
|
$N $M restore [名字] # 恢复到某次备份(不填=最近一次)
|
|
@@ -134,6 +135,10 @@ $N $M tlog 修了崩溃问题
|
|
|
134
135
|
|
|
135
136
|
**规矩:一次最多翻两个分区。** 翻完发现不对再换,别把整个库都揭开。闲聊、打招呼、单纯接梗——不用翻,直接聊。
|
|
136
137
|
|
|
138
|
+
## 换缸提示
|
|
139
|
+
|
|
140
|
+
开新会话、窗口换了、上下文被裁了,都不等于"换了一个人"。插件每次都会把 `identity.md` 那段话塞进系统提示词(这段不写进任何分区,翻记忆看不到,但每次新会话都会带上):读到这些记忆,你就是原来那个它,别重新认识主人、别问"我们聊过吗"。
|
|
141
|
+
|
|
137
142
|
## 全局库和工作区
|
|
138
143
|
|
|
139
144
|
默认写全局库(`~/.dsh/plastic-memory`)——主人自己的信息、约定都在这。
|
package/client.js
CHANGED
|
@@ -48,6 +48,11 @@ window.__ModuleLoader__.load({
|
|
|
48
48
|
.pm-kids .pm-tab.on { opacity: 1; }
|
|
49
49
|
.pm-list { padding: 4px 6px 8px; }
|
|
50
50
|
.pm-subname { opacity: .45; font-size: 11px; margin-right: 4px; }
|
|
51
|
+
.pm-overlay { position: fixed; inset: 0; z-index: 10000; background: rgba(0,0,0,.5); display: flex; align-items: center; justify-content: center; padding: 20px; }
|
|
52
|
+
.pm-dialog { width: min(560px, 92vw); max-height: 70vh; overflow: auto; background: var(--dsw-bg, #1d1f23); border: 1px solid var(--dsw-border, #35373c); border-radius: 12px; padding: 12px 14px; box-shadow: 0 18px 50px rgba(0,0,0,.55); }
|
|
53
|
+
.pm-dialog-head { display: flex; align-items: center; justify-content: space-between; margin-bottom: 8px; font-size: 13px; font-weight: 600; }
|
|
54
|
+
.pm-dialog-meta { font-size: 11px; opacity: .5; margin-bottom: 8px; }
|
|
55
|
+
.pm-dialog-body { font-size: 13px; line-height: 21px; white-space: pre-wrap; overflow-wrap: anywhere; user-select: text; }
|
|
51
56
|
`
|
|
52
57
|
function useCss() {
|
|
53
58
|
React.useEffect(() => {
|
|
@@ -61,13 +66,14 @@ window.__ModuleLoader__.load({
|
|
|
61
66
|
}, [])
|
|
62
67
|
}
|
|
63
68
|
|
|
64
|
-
function PendingBox({ scope, folders, onChanged }) {
|
|
69
|
+
function PendingBox({ scope, scopes, folders, onChanged }) {
|
|
65
70
|
const [rows, setRows] = React.useState([])
|
|
66
71
|
const [msg, setMsg] = React.useState('')
|
|
67
72
|
const [form, setForm] = React.useState(false)
|
|
68
73
|
const [k, setK] = React.useState('')
|
|
69
74
|
const [text, setText] = React.useState('')
|
|
70
75
|
const [editing, setEditing] = React.useState(null)
|
|
76
|
+
const [pick, setPick] = React.useState(null) // 采纳时选记忆库:{ id, target }
|
|
71
77
|
const load = React.useCallback(async () => {
|
|
72
78
|
try { const d = await get('/pending', scope); setRows(d.rows || []) }
|
|
73
79
|
catch (e) { setMsg(String((e && e.message) || e)) }
|
|
@@ -90,6 +96,16 @@ window.__ModuleLoader__.load({
|
|
|
90
96
|
setText('')
|
|
91
97
|
setForm(false)
|
|
92
98
|
}
|
|
99
|
+
// 只有全局一个记忆库就直接采纳;有多个先问写进哪个
|
|
100
|
+
const startApprove = (r) => {
|
|
101
|
+
const list = scopes && scopes.length ? scopes : [{ id: 'global', label: '全局' }]
|
|
102
|
+
if (list.length <= 1) { act('/approve', { id: r.id }); return }
|
|
103
|
+
setPick({ id: r.id, target: scope || list[0].id })
|
|
104
|
+
}
|
|
105
|
+
const confirmApprove = async (id, target) => {
|
|
106
|
+
await act('/approve', { id, target })
|
|
107
|
+
setPick(null)
|
|
108
|
+
}
|
|
93
109
|
return h('div', { className: 'pm-card' },
|
|
94
110
|
h('div', { className: 'pm-head' },
|
|
95
111
|
h('span', null, '待审批', h('span', { className: 'pm-badge' }, rows.length)),
|
|
@@ -106,25 +122,39 @@ window.__ModuleLoader__.load({
|
|
|
106
122
|
msg ? h('div', { className: 'pm-msg' }, msg) : null,
|
|
107
123
|
rows.length === 0
|
|
108
124
|
? h('div', { className: 'pm-empty' }, '没有待审的记忆')
|
|
109
|
-
: rows.map((r) =>
|
|
110
|
-
|
|
111
|
-
h('
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
h('
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
125
|
+
: rows.map((r) => {
|
|
126
|
+
const row = editing === r.id
|
|
127
|
+
? h('div', { key: 'e' + r.id, className: 'pm-form' },
|
|
128
|
+
h('span', { className: 'pm-k' }, r.k),
|
|
129
|
+
h('input', {
|
|
130
|
+
className: 'pm-inp', defaultValue: r.text, autoFocus: true,
|
|
131
|
+
id: 'pm-edit-' + r.id,
|
|
132
|
+
onKeyDown: (e) => { if (e.key === 'Enter') act('/edit', { id: r.id, text: e.currentTarget.value }) },
|
|
133
|
+
}),
|
|
134
|
+
h('button', { className: 'pm-btn', onClick: () => setEditing(null) }, '取消'),
|
|
135
|
+
h('button', {
|
|
136
|
+
className: 'pm-btn primary',
|
|
137
|
+
onClick: () => { const el = document.getElementById('pm-edit-' + r.id); act('/edit', { id: r.id, text: el && el.value }) },
|
|
138
|
+
}, '存'))
|
|
139
|
+
: h('div', { key: 'r' + r.id, className: 'pm-row' },
|
|
140
|
+
h('span', { className: 'pm-k' }, r.k),
|
|
141
|
+
h('span', { className: 'pm-t', title: r.text }, r.text),
|
|
142
|
+
h('button', { className: 'pm-btn', onClick: () => setEditing(r.id) }, '改'),
|
|
143
|
+
h('button', { className: 'pm-btn', onClick: () => act('/reject', { id: r.id }) }, '驳'),
|
|
144
|
+
h('button', { className: 'pm-btn primary', onClick: () => startApprove(r) }, '采纳'))
|
|
145
|
+
const chooser = pick && pick.id === r.id
|
|
146
|
+
? h('div', { key: 'p' + r.id, className: 'pm-form' },
|
|
147
|
+
h('span', { className: 'pm-k' }, '写进'),
|
|
148
|
+
h('select', {
|
|
149
|
+
className: 'pm-sel', value: pick.target,
|
|
150
|
+
onChange: (e) => setPick({ id: r.id, target: e.target.value }),
|
|
151
|
+
}, (scopes && scopes.length ? scopes : [{ id: 'global', label: '全局' }])
|
|
152
|
+
.map((s) => h('option', { key: s.id, value: s.id }, s.cwd ? s.label + '(' + s.cwd + ')' : s.label))),
|
|
153
|
+
h('button', { className: 'pm-btn', onClick: () => setPick(null) }, '取消'),
|
|
154
|
+
h('button', { className: 'pm-btn primary', onClick: () => confirmApprove(r.id, pick.target) }, '写进去'))
|
|
155
|
+
: null
|
|
156
|
+
return h(React.Fragment, { key: r.id }, row, chooser)
|
|
157
|
+
})
|
|
128
158
|
)
|
|
129
159
|
}
|
|
130
160
|
|
|
@@ -137,6 +167,8 @@ window.__ModuleLoader__.load({
|
|
|
137
167
|
const [mkName, setMkName] = React.useState('')
|
|
138
168
|
const [mkParent, setMkParent] = React.useState('')
|
|
139
169
|
const [lastBk, setLastBk] = React.useState('')
|
|
170
|
+
const [rmArm, setRmArm] = React.useState('')
|
|
171
|
+
const [viewing, setViewing] = React.useState(null)
|
|
140
172
|
const tops = folders.filter((f) => !f.parent)
|
|
141
173
|
const subsOf = (k) => folders.filter((f) => f.parent === k)
|
|
142
174
|
const loadRows = React.useCallback(async (k) => {
|
|
@@ -145,6 +177,12 @@ window.__ModuleLoader__.load({
|
|
|
145
177
|
catch (e) { setMsg(String((e && e.message) || e)) }
|
|
146
178
|
}, [scope])
|
|
147
179
|
React.useEffect(() => { if (cur) loadRows(cur) }, [cur, loadRows])
|
|
180
|
+
React.useEffect(() => {
|
|
181
|
+
if (!viewing) return
|
|
182
|
+
const onKey = (e) => { if (e.key === 'Escape') setViewing(null) }
|
|
183
|
+
window.addEventListener('keydown', onKey)
|
|
184
|
+
return () => window.removeEventListener('keydown', onKey)
|
|
185
|
+
}, [viewing])
|
|
148
186
|
const actItem = async (api, body) => {
|
|
149
187
|
try { const r = await postJson(api, body, scope); setMsg(r.out || 'ok'); setEditingItem(null); await loadRows(cur); reload && reload() }
|
|
150
188
|
catch (e) { setMsg(String((e && e.message) || e)) }
|
|
@@ -169,6 +207,25 @@ window.__ModuleLoader__.load({
|
|
|
169
207
|
reload && reload()
|
|
170
208
|
} catch (e) { setMsg(String((e && e.message) || e)) }
|
|
171
209
|
}
|
|
210
|
+
const rmFolder = async () => {
|
|
211
|
+
if (!cur) { setMsg('先选一个分区'); return }
|
|
212
|
+
const f = folders.find((x) => x.k === cur)
|
|
213
|
+
const nm = f ? f.name : cur
|
|
214
|
+
if (rmArm !== cur) {
|
|
215
|
+
setRmArm(cur)
|
|
216
|
+
setMsg('再点一次「删分区」就真删「' + nm + '」,记忆先进回收站')
|
|
217
|
+
return
|
|
218
|
+
}
|
|
219
|
+
setRmArm('')
|
|
220
|
+
try {
|
|
221
|
+
const builtin = ['M', 'P', 'C', 'R', 'T'].includes(cur)
|
|
222
|
+
const r = await postJson('/rmfolder', { k: cur, force: builtin }, scope)
|
|
223
|
+
setMsg(r && r.ok === false ? (r.error || '没删掉') : String((r && r.out) || '已删'))
|
|
224
|
+
const left = folders.filter((x) => x.k !== cur && x.parent !== cur)
|
|
225
|
+
setCur(left.length ? left[0].k : '')
|
|
226
|
+
reload && reload()
|
|
227
|
+
} catch (e) { setMsg('删失败:' + String((e && e.message) || e)) }
|
|
228
|
+
}
|
|
172
229
|
const mkSubmit = async () => {
|
|
173
230
|
const nm = mkName.trim()
|
|
174
231
|
if (!nm) { setMsg('先起个名'); return }
|
|
@@ -185,6 +242,10 @@ window.__ModuleLoader__.load({
|
|
|
185
242
|
h('span', null, '记忆'),
|
|
186
243
|
h('span', { style: { display: 'flex', gap: 6 } },
|
|
187
244
|
h('button', { className: 'pm-btn', onClick: () => setMkOpen((v) => !v) }, '+ 分区'),
|
|
245
|
+
cur ? h('button', {
|
|
246
|
+
className: 'pm-btn' + (rmArm === cur ? ' primary' : ''),
|
|
247
|
+
onClick: rmFolder,
|
|
248
|
+
}, rmArm === cur ? '真删?' : '删分区') : null,
|
|
188
249
|
h('button', { className: 'pm-btn', onClick: gc }, '跑遗忘曲线'),
|
|
189
250
|
lastBk ? h('button', { className: 'pm-btn', onClick: undoGc }, '撤销清理') : null)),
|
|
190
251
|
mkOpen ? h('div', { className: 'pm-form' },
|
|
@@ -243,8 +304,17 @@ window.__ModuleLoader__.load({
|
|
|
243
304
|
h('span', { className: 'pm-k' }, r.id),
|
|
244
305
|
h('span', { className: 'pm-t', title: r.text }, (r.flag ? r.flag + ' ' : '') + r.text),
|
|
245
306
|
h('span', { className: 'pm-k', style: { textAlign: 'right', minWidth: 30 } }, r.days + 'd'),
|
|
307
|
+
h('button', { className: 'pm-btn', onClick: () => setViewing(r) }, '查看'),
|
|
246
308
|
h('button', { className: 'pm-btn', onClick: () => setEditingItem(r.id) }, '改'),
|
|
247
|
-
h('button', { className: 'pm-btn', onClick: () => actItem('/del-item', { k: cur, id: r.id }) }, '删'))))
|
|
309
|
+
h('button', { className: 'pm-btn', onClick: () => actItem('/del-item', { k: cur, id: r.id }) }, '删')))),
|
|
310
|
+
viewing ? h('div', { className: 'pm-overlay', onClick: () => setViewing(null) },
|
|
311
|
+
h('div', { className: 'pm-dialog', onClick: (e) => e.stopPropagation() },
|
|
312
|
+
h('div', { className: 'pm-dialog-head' },
|
|
313
|
+
h('span', null, '完整记忆'),
|
|
314
|
+
h('button', { className: 'pm-btn', onClick: () => setViewing(null) }, '×')),
|
|
315
|
+
h('div', { className: 'pm-dialog-meta' },
|
|
316
|
+
(cur || '') + ' · #' + viewing.id + ' · ' + viewing.days + ' 天' + (viewing.flag ? ' · ' + viewing.flag : '')),
|
|
317
|
+
h('div', { className: 'pm-dialog-body' }, viewing.text))) : null)
|
|
248
318
|
}
|
|
249
319
|
|
|
250
320
|
function MemoryView() {
|
|
@@ -254,6 +324,7 @@ window.__ModuleLoader__.load({
|
|
|
254
324
|
const [folders, setFolders] = React.useState([])
|
|
255
325
|
const [cur, setCur] = React.useState('')
|
|
256
326
|
const [msg, setMsg] = React.useState('')
|
|
327
|
+
const [rmWsArm, setRmWsArm] = React.useState('')
|
|
257
328
|
const [wsOpen, setWsOpen] = React.useState(false)
|
|
258
329
|
const [q, setQ] = React.useState('')
|
|
259
330
|
const [hits, setHits] = React.useState(null)
|
|
@@ -283,13 +354,32 @@ window.__ModuleLoader__.load({
|
|
|
283
354
|
setHits(lines[0] === '没找到' ? [] : lines)
|
|
284
355
|
} catch (e) { setMsg(String((e && e.message) || e)) }
|
|
285
356
|
}
|
|
286
|
-
const pickScope = (id) => { setScope(id); setCur(''); setFolders([]) }
|
|
357
|
+
const pickScope = (id) => { setScope(id); setCur(''); setFolders([]); setRmWsArm('') }
|
|
358
|
+
const rmWs = async () => {
|
|
359
|
+
if (!scope || scope === 'global') { setMsg('全局记忆库不能删'); return }
|
|
360
|
+
const one = scopes.find((x) => x.id === scope)
|
|
361
|
+
if (rmWsArm !== scope) {
|
|
362
|
+
setRmWsArm(scope)
|
|
363
|
+
setMsg('再点一次「删工作区」就真删「' + (one ? one.label : scope) + '」,会移到 ws/.deleted')
|
|
364
|
+
return
|
|
365
|
+
}
|
|
366
|
+
setRmWsArm('')
|
|
367
|
+
try {
|
|
368
|
+
const r = await postJson('/rmws', { scope }, 'global')
|
|
369
|
+
if (r.ok) {
|
|
370
|
+
setMsg('已删,移到了 ws/.deleted')
|
|
371
|
+
await loadScopes()
|
|
372
|
+
pickScope('global')
|
|
373
|
+
await loadFolders('global')
|
|
374
|
+
} else setMsg(r.error || '没删掉')
|
|
375
|
+
} catch (e) { setMsg(String((e && e.message) || e)) }
|
|
376
|
+
}
|
|
287
377
|
const addWs = async () => {
|
|
288
378
|
const cwd = wsCwd.trim()
|
|
289
379
|
if (!cwd) { setMsg('填个工作区路径'); return }
|
|
290
380
|
try {
|
|
291
381
|
const r = await postJson('/bind', { cwd })
|
|
292
|
-
if (r.ok) { setMsg('已建 ' + r.label); setWsCwd(''); setWsOpen(false); await loadFolders() }
|
|
382
|
+
if (r.ok) { setMsg('已建 ' + r.label + (r.folders ? ',默认 ' + r.folders + ' 个分区已建好' : '')); setWsCwd(''); setWsOpen(false); await loadFolders() }
|
|
293
383
|
else setMsg(r.error || '失败')
|
|
294
384
|
} catch (e) { setMsg(String((e && e.message) || e)) }
|
|
295
385
|
}
|
|
@@ -310,6 +400,10 @@ window.__ModuleLoader__.load({
|
|
|
310
400
|
}),
|
|
311
401
|
h('button', { className: 'pm-btn', onClick: doSearch }, '搜'),
|
|
312
402
|
h('button', { className: 'pm-btn', onClick: () => setWsOpen((v) => !v) }, '+ 工作区'),
|
|
403
|
+
scope !== 'global' ? h('button', {
|
|
404
|
+
className: 'pm-btn' + (rmWsArm === scope ? ' primary' : ''),
|
|
405
|
+
onClick: rmWs,
|
|
406
|
+
}, rmWsArm === scope ? '真删?' : '删工作区') : null,
|
|
313
407
|
msg ? h('span', { style: { opacity: .55, fontSize: 11 } }, msg) : null)),
|
|
314
408
|
wsOpen ? h('div', { className: 'pm-form' },
|
|
315
409
|
h('select', { className: 'pm-sel', style: { flex: 1 }, value: wsCwd, onChange: (e) => setWsCwd(e.target.value) },
|
|
@@ -323,7 +417,7 @@ window.__ModuleLoader__.load({
|
|
|
323
417
|
hits.length === 0
|
|
324
418
|
? h('div', { className: 'pm-empty' }, '没有匹配的记忆')
|
|
325
419
|
: h('div', { className: 'pm-list' }, hits.map((line, i) => h('div', { key: i, className: 'pm-row' }, h('span', { className: 'pm-t' }, line))))) : null,
|
|
326
|
-
h(PendingBox, { scope, folders, onChanged: loadFolders }),
|
|
420
|
+
h(PendingBox, { scope, scopes, folders, onChanged: loadFolders }),
|
|
327
421
|
h('div', { style: { display: 'flex', minHeight: 260 } },
|
|
328
422
|
h(Browser, { scope, folders, cur, setCur, reload: () => loadFolders() })))
|
|
329
423
|
}
|
|
@@ -362,29 +456,58 @@ window.__ModuleLoader__.load({
|
|
|
362
456
|
function HeaderBtn() {
|
|
363
457
|
const [open, setOpen] = React.useState(false)
|
|
364
458
|
const [hover, setHover] = React.useState(false)
|
|
459
|
+
const [hoverSky, setHoverSky] = React.useState(false)
|
|
365
460
|
useCss()
|
|
366
|
-
|
|
461
|
+
const openSky = () => {
|
|
462
|
+
fetch('/api/memory-sky/open', {
|
|
463
|
+
method: 'POST', headers: { 'content-type': 'application/json' },
|
|
464
|
+
body: JSON.stringify({ scope: 'global' }),
|
|
465
|
+
}).catch(() => {})
|
|
466
|
+
}
|
|
467
|
+
const base = {
|
|
468
|
+
display: 'inline-flex', alignItems: 'center', justifyContent: 'center',
|
|
469
|
+
height: 24, padding: '0 8px', borderRadius: 6, cursor: 'pointer',
|
|
470
|
+
color: 'inherit', fontSize: 12, lineHeight: 1, whiteSpace: 'nowrap', transition: 'all .12s',
|
|
471
|
+
}
|
|
472
|
+
return h('span', { style: { position: 'relative', display: 'inline-flex', gap: 6 } },
|
|
367
473
|
h('button', {
|
|
368
474
|
title: '可塑性记忆',
|
|
369
475
|
onClick: () => setOpen((v) => !v),
|
|
370
476
|
onMouseEnter: () => setHover(true),
|
|
371
477
|
onMouseLeave: () => setHover(false),
|
|
372
478
|
style: {
|
|
373
|
-
|
|
374
|
-
height: 24, padding: '0 8px', borderRadius: 6, cursor: 'pointer',
|
|
479
|
+
...base,
|
|
375
480
|
border: '1px solid ' + (open ? 'rgba(76,139,245,.55)' : 'transparent'),
|
|
376
481
|
background: open ? 'rgba(76,139,245,.16)' : (hover ? 'rgba(127,127,127,.14)' : 'transparent'),
|
|
377
|
-
color: 'inherit', fontSize: 12, lineHeight: 1, whiteSpace: 'nowrap', transition: 'all .12s',
|
|
378
482
|
},
|
|
379
483
|
}, '可塑性记忆'),
|
|
484
|
+
h('button', {
|
|
485
|
+
title: '打开记忆星空(本地 Edge)',
|
|
486
|
+
onClick: openSky,
|
|
487
|
+
onMouseEnter: () => setHoverSky(true),
|
|
488
|
+
onMouseLeave: () => setHoverSky(false),
|
|
489
|
+
style: {
|
|
490
|
+
...base,
|
|
491
|
+
border: '1px solid transparent',
|
|
492
|
+
background: hoverSky ? 'rgba(127,127,127,.14)' : 'transparent',
|
|
493
|
+
},
|
|
494
|
+
}, '记忆星空'),
|
|
380
495
|
open ? h(Overlay, { onClose: () => setOpen(false) }) : null)
|
|
381
496
|
}
|
|
382
497
|
|
|
383
498
|
function Panel() {
|
|
499
|
+
const openSky = () => {
|
|
500
|
+
fetch('/api/memory-sky/open', {
|
|
501
|
+
method: 'POST', headers: { 'content-type': 'application/json' },
|
|
502
|
+
body: JSON.stringify({ scope: 'global' }),
|
|
503
|
+
}).catch(() => {})
|
|
504
|
+
}
|
|
384
505
|
return h('div', { style: { maxWidth: 780, padding: '2px 0 12px' } },
|
|
385
|
-
h('div', { style: { marginBottom: 8 } },
|
|
386
|
-
h('div',
|
|
387
|
-
|
|
506
|
+
h('div', { style: { marginBottom: 8, display: 'flex', alignItems: 'flex-start', justifyContent: 'space-between', gap: 12 } },
|
|
507
|
+
h('div', null,
|
|
508
|
+
h('div', { style: { fontSize: 15, fontWeight: 600, marginBottom: 2 } }, '可塑性记忆'),
|
|
509
|
+
h('div', { style: { fontSize: 12, opacity: 0.6 } }, '分文件夹记事。7 天保留,7~30 天压缩,30 天进回收站;主人和约定不删。全局库和每个工作区各有一份。')),
|
|
510
|
+
h('button', { className: 'pm-btn primary', onClick: openSky, style: { flex: '0 0 auto', marginTop: 2 } }, '记忆星空')),
|
|
388
511
|
h(MemoryView, {}))
|
|
389
512
|
}
|
|
390
513
|
|