dsh-memory-core 0.1.2 → 0.1.4
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +19 -0
- package/README.md +28 -15
- package/client.js +289 -41
- package/index.js +449 -84
- package/lib/defaults.js +3 -2
- package/lib/render.js +62 -35
- package/lib/runtime-config.js +45 -0
- package/lib/schema.js +1 -0
- package/lib/store.js +5 -1
- package/manage.html +575 -287
- package/package.json +3 -3
package/manage.html
CHANGED
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
<head>
|
|
4
4
|
<meta charset="utf-8">
|
|
5
5
|
<meta name="viewport" content="width=device-width, initial-scale=1">
|
|
6
|
-
<title
|
|
6
|
+
<title>遮蔽会话记忆 — dsh-memory-core</title>
|
|
7
7
|
<link rel="icon" href="data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16'%3E%3Crect width='16' height='16' rx='3' fill='%230ea5e9'/%3E%3Cpath d='M5 4h6v2H5zM5 8h6v2H5zM5 12h4v2H5z' fill='white'/%3E%3C/svg%3E">
|
|
8
8
|
<style>
|
|
9
9
|
:root {
|
|
@@ -49,16 +49,12 @@
|
|
|
49
49
|
header {
|
|
50
50
|
background: var(--card);
|
|
51
51
|
border-bottom: 1px solid var(--border);
|
|
52
|
-
padding: 14px 20px;
|
|
53
|
-
display: flex;
|
|
54
|
-
align-items: center;
|
|
55
|
-
justify-content: space-between;
|
|
56
|
-
flex-wrap: wrap;
|
|
57
|
-
gap: 10px;
|
|
52
|
+
padding: 14px 20px 0;
|
|
58
53
|
position: sticky;
|
|
59
54
|
top: 0;
|
|
60
55
|
z-index: 5;
|
|
61
56
|
}
|
|
57
|
+
.head-row { display: flex; align-items: center; justify-content: space-between; flex-wrap: wrap; gap: 10px; }
|
|
62
58
|
header h1 { font-size: 16px; margin: 0; font-weight: 600; }
|
|
63
59
|
.status-line { display: flex; gap: 8px; flex-wrap: wrap; font-size: 12px; color: var(--muted); }
|
|
64
60
|
.chip {
|
|
@@ -66,148 +62,207 @@
|
|
|
66
62
|
border: 1px solid var(--border);
|
|
67
63
|
border-radius: 999px;
|
|
68
64
|
padding: 2px 10px;
|
|
65
|
+
font-family: ui-monospace, Consolas, monospace;
|
|
66
|
+
font-size: 12px;
|
|
69
67
|
}
|
|
70
|
-
.chip.badge { background: var(--accent-weak); color: var(--accent); border-color: transparent; }
|
|
71
68
|
.chip.ok { background: var(--ok-weak); color: var(--ok); border-color: transparent; }
|
|
72
69
|
.chip.off { background: var(--danger-weak); color: var(--danger); border-color: transparent; }
|
|
73
|
-
|
|
74
|
-
.layout { display: grid; grid-template-columns: 200px 1fr; gap: 16px; }
|
|
75
|
-
@media (max-width: 720px) { .layout { grid-template-columns: 1fr; } }
|
|
76
|
-
nav.tabs {
|
|
77
|
-
background: var(--card);
|
|
78
|
-
border: 1px solid var(--border);
|
|
79
|
-
border-radius: var(--radius);
|
|
80
|
-
padding: 8px;
|
|
81
|
-
align-self: start;
|
|
82
|
-
position: sticky;
|
|
83
|
-
top: 76px;
|
|
84
|
-
}
|
|
70
|
+
nav.tabs { display: flex; gap: 2px; flex-wrap: wrap; margin-top: 10px; }
|
|
85
71
|
nav.tabs button {
|
|
86
|
-
display: block;
|
|
87
|
-
width: 100%;
|
|
88
|
-
text-align: left;
|
|
89
72
|
background: none;
|
|
90
73
|
border: none;
|
|
91
|
-
|
|
92
|
-
|
|
74
|
+
border-bottom: 2px solid transparent;
|
|
75
|
+
padding: 8px 12px;
|
|
93
76
|
cursor: pointer;
|
|
94
77
|
font-size: 13px;
|
|
95
|
-
color: var(--
|
|
96
|
-
|
|
78
|
+
color: var(--muted);
|
|
79
|
+
white-space: nowrap;
|
|
97
80
|
}
|
|
98
|
-
nav.tabs button:hover {
|
|
99
|
-
nav.tabs button.active {
|
|
100
|
-
nav.tabs button .count {
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
81
|
+
nav.tabs button:hover { color: var(--text); }
|
|
82
|
+
nav.tabs button.active { color: var(--text); border-bottom-color: var(--text); font-weight: 600; }
|
|
83
|
+
nav.tabs button .count {
|
|
84
|
+
display: inline-block;
|
|
85
|
+
margin-left: 6px;
|
|
86
|
+
padding: 0 6px;
|
|
87
|
+
border-radius: 10px;
|
|
88
|
+
background: var(--accent-weak);
|
|
89
|
+
color: var(--accent);
|
|
90
|
+
font-size: 11px;
|
|
91
|
+
font-weight: 400;
|
|
92
|
+
}
|
|
93
|
+
main { max-width: 1180px; margin: 18px auto 40px; padding: 0 20px; }
|
|
94
|
+
.warn {
|
|
95
|
+
background: var(--warn-weak);
|
|
96
|
+
color: var(--warn);
|
|
97
|
+
border-radius: 6px;
|
|
98
|
+
padding: 8px 12px;
|
|
99
|
+
font-size: 13px;
|
|
100
|
+
margin-bottom: 10px;
|
|
101
|
+
}
|
|
102
|
+
.cwdline { font-size: 13px; color: var(--muted); margin-bottom: 12px; }
|
|
103
|
+
.cwdline code { font-family: ui-monospace, Consolas, monospace; color: var(--text); }
|
|
104
|
+
.cwdline #cwd-input { width: 320px; max-width: 55vw; font-size: 13px; padding: 4px 8px; border: 1px solid var(--border); border-radius: 6px; background: #fff; color: var(--text); margin-right: 6px; }
|
|
105
|
+
.mini { font-size: 12px; padding: 3px 10px; border: 1px solid var(--border); background: #fff; border-radius: 6px; cursor: pointer; color: var(--text); }
|
|
106
|
+
.mini:disabled { opacity: 0.45; cursor: default; }
|
|
107
|
+
.pager { display: flex; gap: 10px; align-items: center; justify-content: center; margin: 14px 0 4px; font-size: 13px; color: var(--muted); }
|
|
108
|
+
.desc { font-size: 13px; color: var(--muted); margin-bottom: 10px; }
|
|
109
|
+
.filebar {
|
|
110
|
+
display: flex;
|
|
111
|
+
align-items: center;
|
|
112
|
+
gap: 10px;
|
|
113
|
+
flex-wrap: wrap;
|
|
104
114
|
background: var(--card);
|
|
105
115
|
border: 1px solid var(--border);
|
|
106
116
|
border-radius: var(--radius);
|
|
107
|
-
padding: 14px
|
|
108
|
-
margin-bottom:
|
|
117
|
+
padding: 10px 14px;
|
|
118
|
+
margin-bottom: 10px;
|
|
109
119
|
}
|
|
110
|
-
.
|
|
111
|
-
.
|
|
112
|
-
|
|
113
|
-
|
|
120
|
+
.filebar .name { font-weight: 600; font-size: 14px; }
|
|
121
|
+
.filebar .path {
|
|
122
|
+
font-family: ui-monospace, Consolas, monospace;
|
|
123
|
+
font-size: 12px;
|
|
124
|
+
color: var(--muted);
|
|
125
|
+
word-break: break-all;
|
|
126
|
+
}
|
|
127
|
+
.filebar .spacer { flex: 1; }
|
|
128
|
+
.viewbar { display: flex; align-items: center; gap: 10px; margin-bottom: 12px; flex-wrap: wrap; }
|
|
129
|
+
.seg { display: inline-flex; border: 1px solid var(--border); border-radius: 6px; overflow: hidden; }
|
|
130
|
+
.seg button { border: none; background: var(--card); padding: 5px 12px; cursor: pointer; font-size: 13px; color: var(--muted); }
|
|
131
|
+
.seg button.active { background: var(--accent-weak); color: var(--accent); font-weight: 600; }
|
|
132
|
+
#search {
|
|
133
|
+
flex: 1;
|
|
134
|
+
min-width: 220px;
|
|
135
|
+
padding: 6px 10px;
|
|
114
136
|
border: 1px solid var(--border);
|
|
115
137
|
border-radius: 6px;
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
138
|
+
background: var(--card);
|
|
139
|
+
color: var(--text);
|
|
140
|
+
font-size: 13px;
|
|
141
|
+
}
|
|
142
|
+
.card {
|
|
143
|
+
background: var(--card);
|
|
144
|
+
border: 1px solid var(--border);
|
|
145
|
+
border-radius: var(--radius);
|
|
146
|
+
padding: 14px 16px;
|
|
147
|
+
margin-bottom: 12px;
|
|
119
148
|
}
|
|
149
|
+
.card h3 { font-size: 14px; margin: 0 0 10px; display: flex; align-items: center; gap: 8px; flex-wrap: wrap; }
|
|
150
|
+
.card h3 .hint { font-weight: 400; color: var(--muted); font-size: 12px; }
|
|
151
|
+
.entry { border-top: 1px solid var(--border); padding: 10px 0; }
|
|
152
|
+
.entry:first-of-type { border-top: none; }
|
|
120
153
|
.entry pre {
|
|
121
|
-
margin: 0 0 8px;
|
|
122
154
|
white-space: pre-wrap;
|
|
123
155
|
word-break: break-word;
|
|
124
|
-
|
|
125
|
-
font
|
|
156
|
+
margin: 0 0 8px;
|
|
157
|
+
font: inherit;
|
|
126
158
|
color: var(--text);
|
|
127
159
|
}
|
|
128
|
-
.entry .date {
|
|
129
|
-
.entry .actions { display: flex; gap: 6px; flex-wrap: wrap; }
|
|
160
|
+
.entry .date { display: inline-block; margin-right: 8px; color: var(--muted); font-size: 12px; }
|
|
130
161
|
.entry textarea {
|
|
131
162
|
width: 100%;
|
|
132
|
-
min-height:
|
|
163
|
+
min-height: 90px;
|
|
164
|
+
padding: 8px;
|
|
133
165
|
border: 1px solid var(--border);
|
|
134
166
|
border-radius: 6px;
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
167
|
+
background: var(--bg);
|
|
168
|
+
color: var(--text);
|
|
169
|
+
font: inherit;
|
|
170
|
+
margin-bottom: 8px;
|
|
138
171
|
}
|
|
172
|
+
.actions { display: flex; gap: 8px; flex-wrap: wrap; }
|
|
139
173
|
button {
|
|
140
|
-
font: 12px/1.4 inherit;
|
|
141
174
|
border: 1px solid var(--border);
|
|
142
175
|
background: var(--card);
|
|
143
176
|
color: var(--text);
|
|
144
177
|
border-radius: 6px;
|
|
145
178
|
padding: 4px 10px;
|
|
179
|
+
font-size: 12px;
|
|
146
180
|
cursor: pointer;
|
|
147
181
|
}
|
|
148
182
|
button:hover { border-color: var(--accent); color: var(--accent); }
|
|
149
183
|
button.primary { background: var(--accent); border-color: var(--accent); color: #fff; }
|
|
150
|
-
button.primary:hover {
|
|
151
|
-
button.
|
|
152
|
-
button.danger
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
184
|
+
button.primary:hover { color: #fff; opacity: .9; }
|
|
185
|
+
button.ok { color: var(--ok); }
|
|
186
|
+
button.danger { color: var(--danger); }
|
|
187
|
+
.empty { color: var(--muted); font-size: 13px; padding: 6px 0; }
|
|
188
|
+
.raw-view {
|
|
189
|
+
background: var(--card);
|
|
190
|
+
border: 1px solid var(--border);
|
|
191
|
+
border-radius: var(--radius);
|
|
192
|
+
padding: 14px;
|
|
193
|
+
white-space: pre-wrap;
|
|
194
|
+
word-break: break-word;
|
|
195
|
+
font-family: ui-monospace, Consolas, monospace;
|
|
196
|
+
font-size: 12px;
|
|
197
|
+
margin: 0;
|
|
198
|
+
}
|
|
199
|
+
.add-row { display: flex; gap: 8px; }
|
|
157
200
|
.add-row input {
|
|
158
201
|
flex: 1;
|
|
202
|
+
padding: 8px 10px;
|
|
159
203
|
border: 1px solid var(--border);
|
|
160
204
|
border-radius: 6px;
|
|
161
|
-
|
|
162
|
-
|
|
205
|
+
background: var(--bg);
|
|
206
|
+
color: var(--text);
|
|
207
|
+
font: inherit;
|
|
163
208
|
}
|
|
164
209
|
.toast {
|
|
165
210
|
position: fixed;
|
|
166
|
-
bottom: 24px;
|
|
167
211
|
left: 50%;
|
|
168
|
-
|
|
169
|
-
|
|
212
|
+
bottom: 28px;
|
|
213
|
+
transform: translateX(-50%) translateY(20px);
|
|
214
|
+
background: #1f2430;
|
|
170
215
|
color: #fff;
|
|
171
216
|
padding: 8px 16px;
|
|
172
|
-
border-radius:
|
|
217
|
+
border-radius: 6px;
|
|
173
218
|
font-size: 13px;
|
|
174
219
|
opacity: 0;
|
|
175
|
-
transition: opacity .25s;
|
|
176
220
|
pointer-events: none;
|
|
221
|
+
transition: opacity .18s, transform .18s;
|
|
177
222
|
z-index: 20;
|
|
178
|
-
|
|
179
|
-
}
|
|
180
|
-
.
|
|
181
|
-
.
|
|
182
|
-
.
|
|
183
|
-
.
|
|
184
|
-
.full-only { display: none; }
|
|
185
|
-
body.full .full-only { display: initial; }
|
|
223
|
+
}
|
|
224
|
+
.toast.show { opacity: .95; transform: translateX(-50%) translateY(0); }
|
|
225
|
+
.guide h4 { margin: 14px 0 6px; font-size: 13px; }
|
|
226
|
+
.guide p, .guide li { font-size: 13px; color: var(--text); }
|
|
227
|
+
.guide ul { margin: 4px 0; padding-left: 20px; }
|
|
228
|
+
.guide code { font-family: ui-monospace, Consolas, monospace; background: var(--bg); padding: 1px 5px; border-radius: 4px; font-size: 12px; }
|
|
186
229
|
</style>
|
|
187
230
|
</head>
|
|
188
231
|
<body>
|
|
232
|
+
|
|
189
233
|
<header>
|
|
190
|
-
<
|
|
191
|
-
|
|
192
|
-
<
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
234
|
+
<div class="head-row">
|
|
235
|
+
<h1>遮蔽会话记忆</h1>
|
|
236
|
+
<div class="status-line">
|
|
237
|
+
<span class="chip ok" id="chip-mode"></span>
|
|
238
|
+
<span class="chip" id="chip-tracks"></span>
|
|
239
|
+
<span class="chip" id="chip-preset"></span>
|
|
240
|
+
<span class="chip" id="chip-dir"></span>
|
|
241
|
+
</div>
|
|
196
242
|
</div>
|
|
243
|
+
<nav class="tabs" id="tabs"></nav>
|
|
197
244
|
</header>
|
|
198
245
|
|
|
199
246
|
<main>
|
|
200
|
-
<div
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
<
|
|
204
|
-
<
|
|
247
|
+
<div id="notice"></div>
|
|
248
|
+
<div class="desc" id="desc"></div>
|
|
249
|
+
<div class="filebar" id="filebar" style="display:none">
|
|
250
|
+
<span class="name" id="file-title"></span>
|
|
251
|
+
<span class="chip" id="file-mode">结构化</span>
|
|
252
|
+
<span class="chip" id="file-count"></span>
|
|
253
|
+
<span class="path" id="file-path"></span>
|
|
254
|
+
<span class="spacer"></span>
|
|
255
|
+
<button id="open-file">打开文件</button>
|
|
256
|
+
<button id="copy-path">复制路径</button>
|
|
205
257
|
</div>
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
258
|
+
<div class="viewbar" id="viewbar" style="display:none">
|
|
259
|
+
<span class="seg">
|
|
260
|
+
<button id="view-pretty" class="active">美观视图</button>
|
|
261
|
+
<button id="view-raw">纯文本视图</button>
|
|
262
|
+
</span>
|
|
263
|
+
<input id="search" placeholder="搜索内容、时间或标签…">
|
|
210
264
|
</div>
|
|
265
|
+
<div id="content"></div>
|
|
211
266
|
</main>
|
|
212
267
|
|
|
213
268
|
<div class="toast" id="toast"></div>
|
|
@@ -215,258 +270,491 @@
|
|
|
215
270
|
<script>
|
|
216
271
|
(function () {
|
|
217
272
|
"use strict";
|
|
218
|
-
|
|
219
|
-
|
|
273
|
+
var $ = function (sel) { return document.querySelector(sel); };
|
|
274
|
+
var params = new URLSearchParams(location.search);
|
|
275
|
+
var PAGE_SIZE = 20;
|
|
276
|
+
var state = {
|
|
277
|
+
status: null,
|
|
278
|
+
rows: [],
|
|
279
|
+
tab: null,
|
|
280
|
+
view: "pretty",
|
|
281
|
+
query: "",
|
|
282
|
+
page: 1,
|
|
283
|
+
cwd: params.get("cwd") || localStorage.getItem("pmc-cwd") || "",
|
|
284
|
+
cache: {},
|
|
285
|
+
};
|
|
286
|
+
|
|
287
|
+
// 页签顺序与 evolve 记忆页一致;AGENTS 不属于本包(全局规则由 dsh-agent-instructions 管),不设页签。
|
|
288
|
+
var TABS = [
|
|
289
|
+
{ key: "guide", title: "指南" },
|
|
290
|
+
{ key: "pending", title: "待确认记忆建议", desc: "写入后需人工确认的条目(如 key 轨的 writeGuard)。确认即写入对应轨,拒绝即丢弃。" },
|
|
291
|
+
{ key: "project", title: "项目日志", track: "project", desc: "每回合收尾自动记录本回合进展;不注入上下文,模型按需读取。" },
|
|
292
|
+
{ key: "key", title: "项目关键记忆 KEY.md", track: "key", desc: "下次会话不知道就会做错的事实(判据、坑、口径、约定);注入摘要,写入需确认。" },
|
|
293
|
+
{ key: "archive:key", title: "项目关键记忆归档 KEY-archive.md", track: "key", archived: true, desc: "已归档的项目关键记忆,不注入任何会话,可移回主轨。" },
|
|
294
|
+
{ key: "daily", title: "今日日志", track: "daily", desc: "按天记录当天进展;不注入上下文,模型按需读取。" },
|
|
295
|
+
{ key: "user", title: "用户档案 USER.md", track: "user", desc: "关于用户的稳定事实与偏好;每轮注入上下文。" },
|
|
296
|
+
{ key: "memory", title: "长期记忆 MEMORY.md", track: "memory", desc: "跨项目通用的事实与约定;每轮注入上下文。" },
|
|
297
|
+
{ key: "archive:user", title: "归档用户 USER-archive.md", track: "user", archived: true, desc: "已归档的用户档案,不注入,可移回主轨。" },
|
|
298
|
+
{ key: "archive:memory", title: "归档记忆 MEMORY-archive.md", track: "memory", archived: true, desc: "已归档的长期记忆,不注入,可移回主轨。" },
|
|
299
|
+
];
|
|
300
|
+
|
|
301
|
+
function tabOf(key) {
|
|
302
|
+
for (var i = 0; i < TABS.length; i++) if (TABS[i].key === key) return TABS[i];
|
|
303
|
+
return null;
|
|
304
|
+
}
|
|
220
305
|
|
|
221
306
|
function toast(msg) {
|
|
222
|
-
|
|
307
|
+
var el = $("#toast");
|
|
223
308
|
el.textContent = msg;
|
|
224
309
|
el.classList.add("show");
|
|
225
310
|
clearTimeout(el._t);
|
|
226
|
-
el._t = setTimeout(()
|
|
311
|
+
el._t = setTimeout(function () { el.classList.remove("show"); }, 2600);
|
|
227
312
|
}
|
|
228
313
|
|
|
229
314
|
async function api(url, opts) {
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
if (!res.ok || data.ok === false)
|
|
233
|
-
throw new Error(data.message || ("请求失败 HTTP " + res.status));
|
|
234
|
-
}
|
|
315
|
+
var res = await fetch(url, opts);
|
|
316
|
+
var data = await res.json().catch(function () { return {}; });
|
|
317
|
+
if (!res.ok || data.ok === false) throw new Error(data.why || data.message || ("请求失败 HTTP " + res.status));
|
|
235
318
|
return data;
|
|
236
319
|
}
|
|
237
320
|
|
|
238
321
|
function esc(s) {
|
|
239
|
-
return String(s
|
|
322
|
+
return String(s == null ? "" : s)
|
|
323
|
+
.replace(/&/g, "&")
|
|
324
|
+
.replace(/</g, "<")
|
|
325
|
+
.replace(/>/g, ">")
|
|
326
|
+
.replace(/"/g, """);
|
|
240
327
|
}
|
|
241
328
|
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
329
|
+
function fmtDate(entry) {
|
|
330
|
+
var m = /^\[(\d{4}-\d{2}-\d{2})(?: (\d{2}:\d{2}))?\]/.exec(entry);
|
|
331
|
+
return m ? m[1] + (m[2] ? " " + m[2] : "") : "";
|
|
332
|
+
}
|
|
333
|
+
|
|
334
|
+
function rowsUrl() {
|
|
335
|
+
return "/api/memory-core/files" + (state.cwd ? "?cwd=" + encodeURIComponent(state.cwd) : "");
|
|
336
|
+
}
|
|
337
|
+
|
|
338
|
+
function rowOf(track) {
|
|
339
|
+
for (var i = 0; i < state.rows.length; i++) if (state.rows[i].track === track) return state.rows[i];
|
|
340
|
+
return null;
|
|
341
|
+
}
|
|
342
|
+
|
|
343
|
+
async function boot() {
|
|
344
|
+
var files = await api(rowsUrl());
|
|
345
|
+
state.status = files.data;
|
|
346
|
+
state.rows = files.data.rows || [];
|
|
347
|
+
renderHeader();
|
|
255
348
|
renderTabs();
|
|
256
|
-
|
|
349
|
+
var first = tabOf("project");
|
|
350
|
+
var projectRow = rowOf("project");
|
|
351
|
+
if (!projectRow || !projectRow.available) first = tabOf("memory");
|
|
352
|
+
await switchTab(first.key);
|
|
257
353
|
}
|
|
258
354
|
|
|
259
|
-
function
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
} else {
|
|
266
|
-
chipEnabled.textContent = "纯遮蔽状态(无记忆)";
|
|
267
|
-
chipEnabled.className = "chip off";
|
|
268
|
-
}
|
|
269
|
-
$("#chip-tracks").textContent = "tracks: " + (s.tracks || []).join(" / ");
|
|
355
|
+
function renderHeader() {
|
|
356
|
+
var s = state.status;
|
|
357
|
+
var mode = $("#chip-mode");
|
|
358
|
+
mode.textContent = s.memoryTracks === "full" ? "完整记忆(五轨)" : "轻量记忆(三轨)";
|
|
359
|
+
mode.className = "chip ok";
|
|
360
|
+
$("#chip-tracks").textContent = "tracks: " + (s.rows || []).map(function (r) { return r.track; }).join(" / ");
|
|
270
361
|
$("#chip-preset").textContent = "preset: " + s.presetName;
|
|
271
362
|
$("#chip-dir").textContent = s.memoryDir || "";
|
|
363
|
+
var html = '<div class="warn">⚠️ 以下文件为 § 分隔的结构化记忆,用系统工具打开后请谨慎编辑,随意修改可能破坏格式、导致记忆读取错乱。</div>';
|
|
364
|
+
if (state.cwd) {
|
|
365
|
+
html += '<div class="cwdline">当前会话工作目录:<code>' + esc(state.cwd) + '</code> <button class="mini" id="cwd-edit">修改</button></div>';
|
|
366
|
+
} else {
|
|
367
|
+
html += '<div class="cwdline">当前会话工作目录:<input id="cwd-input" placeholder="D:\\项目路径" value="' + esc(state.cwd) + '"> <button class="mini primary" id="cwd-set">使用</button> <span class="hint">项目日志与项目关键记忆需要它,填一次会记住</span></div>';
|
|
368
|
+
}
|
|
369
|
+
$("#notice").innerHTML = html;
|
|
370
|
+
var setBtn = $("#cwd-set");
|
|
371
|
+
if (setBtn) {
|
|
372
|
+
setBtn.addEventListener("click", function () {
|
|
373
|
+
var v = ($("#cwd-input").value || "").trim();
|
|
374
|
+
if (!v) { toast("目录为空"); return; }
|
|
375
|
+
state.cwd = v;
|
|
376
|
+
try { localStorage.setItem("pmc-cwd", v); } catch (e) {}
|
|
377
|
+
state.cache = {};
|
|
378
|
+
state.page = 1;
|
|
379
|
+
load();
|
|
380
|
+
});
|
|
381
|
+
}
|
|
382
|
+
var editBtn = $("#cwd-edit");
|
|
383
|
+
if (editBtn) {
|
|
384
|
+
editBtn.addEventListener("click", function () {
|
|
385
|
+
state.cwd = "";
|
|
386
|
+
try { localStorage.removeItem("pmc-cwd"); } catch (e) {}
|
|
387
|
+
state.cache = {};
|
|
388
|
+
state.page = 1;
|
|
389
|
+
load();
|
|
390
|
+
});
|
|
391
|
+
}
|
|
272
392
|
}
|
|
273
393
|
|
|
274
394
|
function renderTabs() {
|
|
275
|
-
|
|
395
|
+
var tabs = $("#tabs");
|
|
276
396
|
tabs.innerHTML = "";
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
397
|
+
for (var i = 0; i < TABS.length; i++) {
|
|
398
|
+
var t = TABS[i];
|
|
399
|
+
var n = 0;
|
|
400
|
+
if (t.key === "pending") {
|
|
401
|
+
for (var j = 0; j < state.rows.length; j++) n += state.rows[j].pending || 0;
|
|
402
|
+
} else if (t.archived) {
|
|
403
|
+
var ar = rowOf(t.track);
|
|
404
|
+
n = ar ? ar.archived : 0;
|
|
405
|
+
} else if (t.track) {
|
|
406
|
+
var row = rowOf(t.track);
|
|
407
|
+
n = row ? row.count : 0;
|
|
408
|
+
}
|
|
409
|
+
var btn = document.createElement("button");
|
|
410
|
+
btn.dataset.tab = t.key;
|
|
411
|
+
btn.innerHTML = t.key === "guide" ? esc(t.title) : esc(t.title) + '<span class="count">' + n + "</span>";
|
|
412
|
+
if (t.track) {
|
|
413
|
+
var rr = rowOf(t.track);
|
|
414
|
+
if (rr && !rr.available) btn.title = "需要会话工作目录";
|
|
415
|
+
}
|
|
416
|
+
btn.addEventListener("click", (function (key) {
|
|
417
|
+
return function () { switchTab(key); };
|
|
418
|
+
})(t.key));
|
|
286
419
|
tabs.appendChild(btn);
|
|
287
420
|
}
|
|
288
421
|
}
|
|
289
422
|
|
|
290
|
-
function setActiveTab(
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
}
|
|
423
|
+
function setActiveTab(key) {
|
|
424
|
+
var btns = document.querySelectorAll("#tabs button");
|
|
425
|
+
for (var i = 0; i < btns.length; i++) btns[i].classList.toggle("active", btns[i].dataset.tab === key);
|
|
294
426
|
}
|
|
295
427
|
|
|
296
|
-
async function
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
428
|
+
async function switchTab(key) {
|
|
429
|
+
state.tab = key;
|
|
430
|
+
state.query = "";
|
|
431
|
+
state.page = 1;
|
|
432
|
+
$("#search").value = "";
|
|
433
|
+
state.view = "pretty";
|
|
434
|
+
$("#view-pretty").classList.add("active");
|
|
435
|
+
$("#view-raw").classList.remove("active");
|
|
436
|
+
setActiveTab(key);
|
|
437
|
+
var t = tabOf(key);
|
|
438
|
+
$("#desc").textContent = t && t.desc ? t.desc : "";
|
|
439
|
+
|
|
440
|
+
if (key === "guide") {
|
|
441
|
+
$("#filebar").style.display = "none";
|
|
442
|
+
$("#viewbar").style.display = "none";
|
|
443
|
+
renderGuide();
|
|
444
|
+
return;
|
|
445
|
+
}
|
|
446
|
+
|
|
447
|
+
if (key === "pending") {
|
|
448
|
+
$("#filebar").style.display = "none";
|
|
449
|
+
$("#viewbar").style.display = "none";
|
|
450
|
+
await renderPending();
|
|
451
|
+
return;
|
|
452
|
+
}
|
|
300
453
|
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
state.
|
|
304
|
-
|
|
454
|
+
var base = t.archived ? t.track : t.track;
|
|
455
|
+
var q = new URLSearchParams({ track: base });
|
|
456
|
+
if (state.cwd) q.set("cwd", state.cwd);
|
|
457
|
+
var data = await api("/api/memory-core/entries?" + q.toString());
|
|
458
|
+
state.cache[key] = { rows: data.data, tab: t };
|
|
459
|
+
$("#filebar").style.display = "";
|
|
460
|
+
$("#viewbar").style.display = "";
|
|
461
|
+
$("#file-title").textContent = t.title;
|
|
462
|
+
$("#file-count").textContent = (t.archived ? (data.data.archived || []).length : (data.data.entries || []).length) + " 条";
|
|
463
|
+
var row = rowOf(base);
|
|
464
|
+
$("#file-path").textContent = row && row.path ? row.path : "(不可用:需要会话工作目录)";
|
|
465
|
+
$("#open-file").style.display = row && row.path ? "" : "none";
|
|
466
|
+
renderTrack();
|
|
305
467
|
}
|
|
306
468
|
|
|
307
|
-
function
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
memory
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
469
|
+
function renderGuide() {
|
|
470
|
+
$("#content").innerHTML = ''
|
|
471
|
+
+ '<div class="card guide">'
|
|
472
|
+
+ '<h3>这是什么</h3>'
|
|
473
|
+
+ '<p>这是 <code>dsh-memory-core</code> 的管理页:给被 <code>dsh-memory-shield</code> 遮蔽的会话提供一套隔离记忆,与全局 <code>dsh-memory-evolve</code> 的存储互不影响。</p>'
|
|
474
|
+
+ '<h4>五条记忆轨</h4>'
|
|
475
|
+
+ '<ul>'
|
|
476
|
+
+ '<li><code>memory</code> 长期记忆 MEMORY.md:跨项目通用事实,每轮注入。</li>'
|
|
477
|
+
+ '<li><code>user</code> 用户档案 USER.md:关于用户的稳定偏好,每轮注入。</li>'
|
|
478
|
+
+ '<li><code>project</code> 项目日志:按项目(工作目录哈希)记录每回合进展,不注入、按需读取。</li>'
|
|
479
|
+
+ '<li><code>key</code> 项目关键记忆 KEY.md:下次会话不知道就会做错的事实,注入摘要,写入需确认。</li>'
|
|
480
|
+
+ '<li><code>daily</code> 今日日志:按天记录,不注入。</li>'
|
|
481
|
+
+ '</ul>'
|
|
482
|
+
+ '<p>lite 模式只开 memory / user / key 三条;full 模式五条全开(默认)。轨数决定「可写与可见」,不改变注入面。</p>'
|
|
483
|
+
+ '<h4>机制:怎么注入</h4>'
|
|
484
|
+
+ '<ul>'
|
|
485
|
+
+ '<li>每轮把三条注入轨拼成一段快照(section <code>memory-core:snapshot</code>),总预算 <b>2,000 字符</b>;超出时保留最近的条目,从旧条目尾部截断。</li>'
|
|
486
|
+
+ '<li>预算分配:<code>memory</code> 1,000 → <code>user</code> 600 → <code>key</code> 400,按此顺序累加,用完即停。</li>'
|
|
487
|
+
+ '<li><code>key</code> 轨另有加权:先取最近 8 条,按「不要 / 必须 / 注意 / 重要 / 偏好 / 禁止 / 始终 / 永远」的命中数排序取前 5 条,再补最多 600 字符。</li>'
|
|
488
|
+
+ '<li><code>project</code> 与 <code>daily</code> 不进快照,模型要用时通过 <code>memory_core</code> 的 <code>list</code> 现读。</li>'
|
|
489
|
+
+ '<li>写入提醒:连续 2 个回合没写记忆,快照末尾追加一句提醒;子代理会话不提醒。</li>'
|
|
490
|
+
+ '</ul>'
|
|
491
|
+
+ '<h4>机制:怎么写</h4>'
|
|
492
|
+
+ '<ul>'
|
|
493
|
+
+ '<li>工具是 <code>memory_core</code>,动作 <code>add / list / replace / remove / archive / promote / removeArchive / confirm / reject</code>,target 为五轨之一。</li>'
|
|
494
|
+
+ '<li><code>key</code> 轨的 <code>add</code> 先进「待确认记忆建议」,在待确认页签点「允许并写入」才落盘。</li>'
|
|
495
|
+
+ '<li>本页可以直接新增、编辑、归档、删除,改动立即写文件;归档可逆,能从归档页签移回主轨。</li>'
|
|
496
|
+
+ '</ul>'
|
|
497
|
+
+ '<h4>机制:存在哪</h4>'
|
|
498
|
+
+ '<ul>'
|
|
499
|
+
+ '<li>数据目录与 <code>DSH_HOME</code> 同级:<code><DSH_HOME 的上级>/.dsh-plugin-data/<presetName>/memory</code>。</li>'
|
|
500
|
+
+ '<li><code>memory.md</code>、<code>user.md</code>、<code>daily/<日期>.md</code>;项目相关的落 <code>projects/<工作目录哈希>/memory.md</code> 与 <code>key.md</code>。</li>'
|
|
501
|
+
+ '<li>条目以 <code>§</code> 分隔,归档写在同目录的 archive 文件里。</li>'
|
|
502
|
+
+ '<li>整套存储与 <code>dsh-memory-evolve</code> 完全隔离,互相看不到对方的条目。</li>'
|
|
503
|
+
+ '</ul>'
|
|
504
|
+
+ '<h4>为什么没有「全局规则 AGENTS.md」页签</h4>'
|
|
505
|
+
+ '<p>AGENTS.md 归 <code>dsh-agent-instructions</code> 管,不在本包的记忆目录里。</p>'
|
|
506
|
+
+ '<h4>致谢</h4>'
|
|
507
|
+
+ '<p>存储层(<code>lib/store.js</code>)与记忆页的形制参考 <a href="https://github.com/csyangwen/dsh-memory-evolve" target="_blank" rel="noreferrer">dsh-memory-evolve</a>(作者 csyangwen,MIT),按「隔离会话」的需要做了适配。</p>'
|
|
508
|
+
+ '</div>';
|
|
509
|
+
}
|
|
316
510
|
|
|
317
|
-
|
|
318
|
-
html
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
511
|
+
async function renderPending() {
|
|
512
|
+
var html = [];
|
|
513
|
+
var total = 0;
|
|
514
|
+
for (var i = 0; i < state.rows.length; i++) {
|
|
515
|
+
var row = state.rows[i];
|
|
516
|
+
if (!row.pending) continue;
|
|
517
|
+
var q = new URLSearchParams({ track: row.track });
|
|
518
|
+
if (state.cwd) q.set("cwd", state.cwd);
|
|
519
|
+
var data;
|
|
520
|
+
try { data = await api("/api/memory-core/entries?" + q.toString()); } catch (e) { continue; }
|
|
521
|
+
var list = data.data.pending || [];
|
|
522
|
+
if (!list.length) continue;
|
|
523
|
+
total += list.length;
|
|
524
|
+
html.push('<div class="card"><h3>' + esc(row.title) + ' <span class="hint">' + list.length + ' 条</span></h3>');
|
|
525
|
+
for (var j = 0; j < list.length; j++) {
|
|
526
|
+
var p = list[j];
|
|
527
|
+
var text = p.content || p.entry || "";
|
|
528
|
+
html.push('<div class="entry pending-entry"><pre>' + esc(text) + '</pre><div class="actions">');
|
|
529
|
+
html.push('<button data-op="confirm" data-track="' + esc(row.track) + '" data-entry="' + esc(text) + '" class="ok">允许并写入</button>');
|
|
530
|
+
html.push('<button data-op="reject" data-track="' + esc(row.track) + '" data-entry="' + esc(text) + '" class="danger">拒绝</button>');
|
|
332
531
|
html.push('</div></div>');
|
|
333
532
|
}
|
|
533
|
+
html.push('</div>');
|
|
334
534
|
}
|
|
335
|
-
html.push('
|
|
535
|
+
if (!total) html.push('<div class="card"><div class="empty">暂无待确认记忆。</div></div>');
|
|
536
|
+
$("#content").innerHTML = html.join("");
|
|
537
|
+
bindActions();
|
|
538
|
+
}
|
|
336
539
|
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
540
|
+
function filtered(list) {
|
|
541
|
+
if (!state.query) return list;
|
|
542
|
+
var q = state.query.toLowerCase();
|
|
543
|
+
return list.filter(function (e) { return String(e).toLowerCase().indexOf(q) >= 0; });
|
|
544
|
+
}
|
|
545
|
+
|
|
546
|
+
function pagerHtml(pages, total) {
|
|
547
|
+
if (pages <= 1) return "";
|
|
548
|
+
return (
|
|
549
|
+
'<div class="pager"><button class="mini" data-page="prev"' + (state.page <= 1 ? " disabled" : "") + ">上一页</button>" +
|
|
550
|
+
"<span>" + state.page + " / " + pages + " · 共 " + total + " 条</span>" +
|
|
551
|
+
'<button class="mini" data-page="next"' + (state.page >= pages ? " disabled" : "") + ">下一页</button></div>"
|
|
552
|
+
);
|
|
553
|
+
}
|
|
554
|
+
|
|
555
|
+
function bindPager() {
|
|
556
|
+
var btns = document.querySelectorAll("[data-page]");
|
|
557
|
+
for (var i = 0; i < btns.length; i++) {
|
|
558
|
+
btns[i].addEventListener("click", function (ev) {
|
|
559
|
+
var dir = ev.currentTarget.getAttribute("data-page");
|
|
560
|
+
state.page += dir === "next" ? 1 : -1;
|
|
561
|
+
if (state.page < 1) state.page = 1;
|
|
562
|
+
renderTrack();
|
|
563
|
+
});
|
|
351
564
|
}
|
|
352
|
-
|
|
565
|
+
}
|
|
353
566
|
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
567
|
+
function renderTrack() {
|
|
568
|
+
var t = tabOf(state.tab);
|
|
569
|
+
var d = state.cache[state.tab];
|
|
570
|
+
var content = $("#content");
|
|
571
|
+
if (!d || !t) { content.innerHTML = ""; return; }
|
|
572
|
+
var items = (t.archived ? d.rows.archived : d.rows.entries) || [];
|
|
573
|
+
// 最新在前:文件里是追加写,直接渲染会把最旧的放最上面
|
|
574
|
+
var list = filtered(items).slice().reverse();
|
|
575
|
+
var total = list.length;
|
|
576
|
+
var pages = Math.max(1, Math.ceil(total / PAGE_SIZE));
|
|
577
|
+
if (state.page > pages) state.page = pages;
|
|
578
|
+
var start = (state.page - 1) * PAGE_SIZE;
|
|
579
|
+
var pageList = list.slice(start, start + PAGE_SIZE);
|
|
580
|
+
|
|
581
|
+
if (state.view === "raw") {
|
|
582
|
+
content.innerHTML = '<pre class="raw-view">' + esc(pageList.join("\n§\n")) + "</pre>" + pagerHtml(pages, total);
|
|
583
|
+
bindPager();
|
|
584
|
+
return;
|
|
585
|
+
}
|
|
586
|
+
|
|
587
|
+
var html = [];
|
|
588
|
+
|
|
589
|
+
if (t.archived) {
|
|
590
|
+
html.push('<div class="card"><h3>归档 <span class="hint">' + total + ' 条 · 不注入任何会话,可移回主轨</span></h3>');
|
|
591
|
+
if (!pageList.length) html.push('<div class="empty">' + (state.query ? "没有匹配的归档条目。" : "暂无归档条目。") + "</div>");
|
|
592
|
+
for (var i = 0; i < pageList.length; i++) {
|
|
593
|
+
var entry = pageList[i];
|
|
594
|
+
html.push('<div class="entry archived-entry"><pre>' + esc(entry) + '</pre><div class="actions">');
|
|
595
|
+
html.push('<button data-op="promote" data-track="' + esc(t.track) + '" data-entry="' + esc(entry) + '" class="ok">移回主轨</button>');
|
|
596
|
+
html.push('<button data-op="removeArchive" data-track="' + esc(t.track) + '" data-entry="' + esc(entry) + '" class="danger">删除</button>');
|
|
597
|
+
html.push("</div></div>");
|
|
367
598
|
}
|
|
599
|
+
html.push(pagerHtml(pages, total));
|
|
600
|
+
html.push("</div>");
|
|
601
|
+
content.innerHTML = html.join("");
|
|
602
|
+
bindActions();
|
|
603
|
+
bindPager();
|
|
604
|
+
return;
|
|
368
605
|
}
|
|
369
|
-
html.push('</div>');
|
|
370
606
|
|
|
371
|
-
|
|
372
|
-
html.push('<div class="
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
607
|
+
html.push('<div class="card"><h3>' + esc(t.title.replace(/\s+[A-Z-]+\.md$/, "")) + ' · 正式条目 <span class="hint">' + total + ' 条</span></h3>');
|
|
608
|
+
if (!pageList.length) html.push('<div class="empty">' + (state.query ? "没有匹配的条目。" : "暂无记忆条目。") + "</div>");
|
|
609
|
+
for (var k = 0; k < pageList.length; k++) {
|
|
610
|
+
var text = pageList[k];
|
|
611
|
+
var date = fmtDate(text);
|
|
612
|
+
html.push('<div class="entry"><pre>' + (date ? '<span class="date">' + esc(date) + "</span>" : "") + esc(text) + "</pre>");
|
|
613
|
+
html.push('<div class="actions">');
|
|
614
|
+
html.push('<button data-op="edit" data-track="' + esc(t.track) + '" data-entry="' + esc(text) + '">编辑</button>');
|
|
615
|
+
html.push('<button data-op="archive" data-track="' + esc(t.track) + '" data-entry="' + esc(text) + '">归档</button>');
|
|
616
|
+
html.push('<button data-op="delete" data-track="' + esc(t.track) + '" data-entry="' + esc(text) + '" class="danger">删除</button>');
|
|
617
|
+
html.push("</div></div>");
|
|
618
|
+
}
|
|
619
|
+
html.push(pagerHtml(pages, total));
|
|
620
|
+
html.push("</div>");
|
|
621
|
+
|
|
622
|
+
html.push('<div class="card"><h3>新增条目 <span class="hint">写入 ' + esc(t.title) + "</span></h3>");
|
|
623
|
+
html.push('<div class="add-row"><input id="new-entry-input" placeholder="写一条 ' + esc(t.title) + '…"><button id="new-entry-btn" class="primary">写入</button></div></div>');
|
|
377
624
|
|
|
378
|
-
html.push('</section>');
|
|
379
625
|
content.innerHTML = html.join("");
|
|
626
|
+
$("#new-entry-btn").addEventListener("click", addEntry);
|
|
627
|
+
$("#new-entry-input").addEventListener("keydown", function (ev) { if (ev.key === "Enter") addEntry(); });
|
|
628
|
+
bindActions();
|
|
629
|
+
bindPager();
|
|
630
|
+
}
|
|
631
|
+
|
|
632
|
+
function currentTrack() {
|
|
633
|
+
var t = tabOf(state.tab);
|
|
634
|
+
return t && t.track ? t.track : "memory";
|
|
635
|
+
}
|
|
636
|
+
|
|
637
|
+
async function addEntry() {
|
|
638
|
+
var input = $("#new-entry-input");
|
|
639
|
+
var text = input.value.trim();
|
|
640
|
+
if (!text) { toast("内容为空"); return; }
|
|
641
|
+
var track = currentTrack();
|
|
642
|
+
try {
|
|
643
|
+
await api("/api/memory-core/entry", {
|
|
644
|
+
method: "POST",
|
|
645
|
+
headers: { "Content-Type": "application/json" },
|
|
646
|
+
body: JSON.stringify({ action: "add", track: track, content: text, cwd: state.cwd || undefined }),
|
|
647
|
+
});
|
|
648
|
+
toast("已写入 " + track);
|
|
649
|
+
await refreshRows();
|
|
650
|
+
await switchTab(state.tab);
|
|
651
|
+
} catch (e) { toast(e.message); }
|
|
652
|
+
}
|
|
653
|
+
|
|
654
|
+
async function refreshRows() {
|
|
655
|
+
var files = await api(rowsUrl());
|
|
656
|
+
state.status = files.data;
|
|
657
|
+
state.rows = files.data.rows || [];
|
|
658
|
+
renderTabs();
|
|
659
|
+
}
|
|
660
|
+
|
|
661
|
+
function bindActions() {
|
|
662
|
+
var btns = document.querySelectorAll("#content button[data-op]");
|
|
663
|
+
for (var i = 0; i < btns.length; i++) {
|
|
664
|
+
btns[i].addEventListener("click", onClickAction);
|
|
665
|
+
}
|
|
666
|
+
}
|
|
667
|
+
|
|
668
|
+
async function onClickAction(ev) {
|
|
669
|
+
var btn = ev.currentTarget;
|
|
670
|
+
var op = btn.dataset.op;
|
|
671
|
+
var track = btn.dataset.track;
|
|
672
|
+
var entry = btn.dataset.entry;
|
|
380
673
|
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
674
|
+
if (op === "edit") {
|
|
675
|
+
var div = btn.closest(".entry");
|
|
676
|
+
var pre = div.querySelector("pre");
|
|
677
|
+
var ta = document.createElement("textarea");
|
|
678
|
+
ta.value = entry;
|
|
679
|
+
pre.replaceWith(ta);
|
|
680
|
+
btn.textContent = "保存";
|
|
681
|
+
btn.dataset.op = "saveEdit";
|
|
682
|
+
btn.classList.add("primary");
|
|
683
|
+
var cancel = document.createElement("button");
|
|
684
|
+
cancel.textContent = "取消";
|
|
685
|
+
btn.after(cancel);
|
|
686
|
+
cancel.addEventListener("click", function () { switchTab(state.tab); });
|
|
687
|
+
return;
|
|
688
|
+
}
|
|
689
|
+
|
|
690
|
+
if (op === "saveEdit") {
|
|
691
|
+
var ta2 = btn.closest(".entry").querySelector("textarea");
|
|
386
692
|
try {
|
|
387
693
|
await api("/api/memory-core/entry", {
|
|
388
694
|
method: "POST",
|
|
389
695
|
headers: { "Content-Type": "application/json" },
|
|
390
|
-
body: JSON.stringify({ action: "
|
|
696
|
+
body: JSON.stringify({ action: "replace", track: track, entry: entry, content: ta2.value, cwd: state.cwd || undefined }),
|
|
391
697
|
});
|
|
392
|
-
toast("
|
|
393
|
-
await
|
|
698
|
+
toast("已更新");
|
|
699
|
+
await switchTab(state.tab);
|
|
394
700
|
} catch (e) { toast(e.message); }
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
const index = Number(btn.dataset.index);
|
|
405
|
-
const body = { action: op, track };
|
|
406
|
-
if (["edit"].includes(op)) {
|
|
407
|
-
const entryDiv = btn.closest(".entry");
|
|
408
|
-
const pre = entryDiv.querySelector("pre");
|
|
409
|
-
const original = state.cache[track].entries[index];
|
|
410
|
-
if (!original) return;
|
|
411
|
-
const textarea = document.createElement("textarea");
|
|
412
|
-
textarea.value = original;
|
|
413
|
-
pre.replaceWith(textarea);
|
|
414
|
-
btn.textContent = "保存";
|
|
415
|
-
btn.dataset.op = "saveEdit";
|
|
416
|
-
btn.dataset.index = index;
|
|
417
|
-
btn.classList.add("primary");
|
|
418
|
-
const cancelBtn = document.createElement("button");
|
|
419
|
-
cancelBtn.textContent = "取消";
|
|
420
|
-
cancelBtn.dataset.op = "cancelEdit";
|
|
421
|
-
cancelBtn.dataset.index = index;
|
|
422
|
-
btn.after(cancelBtn);
|
|
423
|
-
return;
|
|
424
|
-
}
|
|
425
|
-
if (op === "saveEdit") {
|
|
426
|
-
const entryDiv = btn.closest(".entry");
|
|
427
|
-
const textarea = entryDiv.querySelector("textarea");
|
|
428
|
-
body.entry = state.cache[track].entries[index];
|
|
429
|
-
body.content = textarea.value;
|
|
430
|
-
try {
|
|
431
|
-
await api("/api/memory-core/entry", {
|
|
432
|
-
method: "POST",
|
|
433
|
-
headers: { "Content-Type": "application/json" },
|
|
434
|
-
body: JSON.stringify(body),
|
|
435
|
-
});
|
|
436
|
-
toast("已更新");
|
|
437
|
-
await loadTrack(track);
|
|
438
|
-
} catch (e) { toast(e.message); }
|
|
439
|
-
return;
|
|
440
|
-
}
|
|
441
|
-
if (op === "cancelEdit") {
|
|
442
|
-
await loadTrack(track);
|
|
443
|
-
return;
|
|
444
|
-
}
|
|
445
|
-
const source =
|
|
446
|
-
op === "delete" || op === "archive" ? state.cache[track].entries[index]
|
|
447
|
-
: op === "confirm" || op === "reject" ? state.cache[track].pending[index].content
|
|
448
|
-
: op === "promote" || op === "removeArchive" ? state.cache[track].archived[index]
|
|
449
|
-
: undefined;
|
|
450
|
-
if (source === undefined) return;
|
|
451
|
-
if (op === "delete" && !confirm("确认删除这条记忆?此操作不可撤销。")) return;
|
|
452
|
-
body.entry = source;
|
|
453
|
-
try {
|
|
454
|
-
await api("/api/memory-core/entry", {
|
|
455
|
-
method: "POST",
|
|
456
|
-
headers: { "Content-Type": "application/json" },
|
|
457
|
-
body: JSON.stringify(body),
|
|
458
|
-
});
|
|
459
|
-
const msg = { archive: "已归档", delete: "已删除", confirm: "已允许并写入", reject: "已拒绝", promote: "已移回主轨", removeArchive: "已从归档删除" };
|
|
460
|
-
toast(msg[op] || "完成");
|
|
461
|
-
await loadTrack(track);
|
|
462
|
-
} catch (e) { toast(e.message); }
|
|
701
|
+
return;
|
|
702
|
+
}
|
|
703
|
+
|
|
704
|
+
if (op === "delete" && !confirm("确认删除这条记忆?此操作不可撤销。")) return;
|
|
705
|
+
try {
|
|
706
|
+
await api("/api/memory-core/entry", {
|
|
707
|
+
method: "POST",
|
|
708
|
+
headers: { "Content-Type": "application/json" },
|
|
709
|
+
body: JSON.stringify({ action: op, track: track, entry: entry, cwd: state.cwd || undefined }),
|
|
463
710
|
});
|
|
464
|
-
|
|
711
|
+
var msg = { archive: "已归档", delete: "已删除", confirm: "已允许并写入", reject: "已拒绝", promote: "已移回主轨", removeArchive: "已从归档删除" };
|
|
712
|
+
toast(msg[op] || "完成");
|
|
713
|
+
await refreshRows();
|
|
714
|
+
await switchTab(state.tab);
|
|
715
|
+
} catch (e) { toast(e.message); }
|
|
465
716
|
}
|
|
466
717
|
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
$("#
|
|
718
|
+
$("#view-pretty").addEventListener("click", function () {
|
|
719
|
+
state.view = "pretty";
|
|
720
|
+
$("#view-pretty").classList.add("active");
|
|
721
|
+
$("#view-raw").classList.remove("active");
|
|
722
|
+
renderTrack();
|
|
723
|
+
});
|
|
724
|
+
$("#view-raw").addEventListener("click", function () {
|
|
725
|
+
state.view = "raw";
|
|
726
|
+
$("#view-raw").classList.add("active");
|
|
727
|
+
$("#view-pretty").classList.remove("active");
|
|
728
|
+
renderTrack();
|
|
729
|
+
});
|
|
730
|
+
$("#search").addEventListener("input", function (ev) {
|
|
731
|
+
state.query = ev.target.value.trim();
|
|
732
|
+
state.page = 1;
|
|
733
|
+
renderTrack();
|
|
734
|
+
});
|
|
735
|
+
$("#copy-path").addEventListener("click", async function () {
|
|
736
|
+
var p = $("#file-path").textContent;
|
|
737
|
+
try {
|
|
738
|
+
await navigator.clipboard.writeText(p);
|
|
739
|
+
toast("路径已复制");
|
|
740
|
+
} catch (e) {
|
|
741
|
+
toast(p);
|
|
742
|
+
}
|
|
743
|
+
});
|
|
744
|
+
$("#open-file").addEventListener("click", async function () {
|
|
745
|
+
var p = $("#file-path").textContent;
|
|
746
|
+
if (!p) return;
|
|
747
|
+
try {
|
|
748
|
+
await api("/api/memory-core/reveal", {
|
|
749
|
+
method: "POST",
|
|
750
|
+
headers: { "Content-Type": "application/json" },
|
|
751
|
+
body: JSON.stringify({ path: p }),
|
|
752
|
+
});
|
|
753
|
+
} catch (e) { toast(e.message); }
|
|
754
|
+
});
|
|
755
|
+
|
|
756
|
+
boot().catch(function (e) {
|
|
757
|
+
$("#notice").innerHTML = '<div class="warn">无法连接插件后端(/api/memory-core):' + esc(e.message) + "</div>";
|
|
470
758
|
});
|
|
471
759
|
})();
|
|
472
760
|
</script>
|