dsh-recall-plugin 2.2.1 → 2.3.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/lib/client.js CHANGED
@@ -1,1472 +1,1476 @@
1
- // src/client/css.js
2
- var CSS = [
3
- ".dsh-recall-row{flex-direction:column;align-items:flex-end;gap:6px;display:flex}",
4
- ".dsh-recall-stack{flex-direction:column;align-items:flex-end;gap:8px;min-width:0;max-width:min(525px,82%);display:flex}",
5
- ".dsh-recall-bubble{background:var(--dsw-specific-bubble);max-width:100%;color:var(--dsw-alias-label-primary);border-radius:22px;padding:10px 16px;font-size:16px;line-height:24px;white-space:pre-wrap;word-break:break-word}",
6
- ".dsh-recall-json{margin:0;max-width:100%;font-size:12px;line-height:18px;color:var(--dsw-alias-label-tertiary);white-space:pre-wrap;word-break:break-word;border:1px solid var(--dsw-alias-border-l1);border-radius:10px;padding:8px 10px;background:var(--dsw-alias-markdown-code-block)}",
7
- ".dsh-recall-actions{align-items:center;gap:10px;height:28px;display:flex}",
8
- ".dsh-recall-time{color:var(--dsw-alias-label-tertiary);white-space:nowrap;padding-right:12px;font-size:14px;line-height:24px}",
9
- ".dsh-recall-action{width:28px;height:28px;color:var(--dsw-alias-label-tertiary);cursor:pointer;background:0 0;border:none;border-radius:28px;justify-content:center;align-items:center;padding:6px;display:inline-flex}",
10
- ".dsh-recall-action:hover{background:var(--dsw-alias-interactive-bg-hover);color:var(--dsw-alias-label-secondary)}",
11
- "@media (hover:hover){[data-time-hover-root] .dsh-recall-time{opacity:0;transition:opacity 80ms}[data-time-hover-root]:hover .dsh-recall-time,[data-time-hover-root]:focus-within .dsh-recall-time{opacity:1}}",
12
- ".dsh-recall-panel{width:min(480px,100%);box-sizing:border-box;background:var(--dsw-alias-bg-base);border:1px solid var(--dsw-alias-border-l1);border-radius:12px;padding:12px 14px;display:flex;flex-direction:column;gap:8px;text-align:left;box-shadow:0 8px 28px rgba(0,0,0,.22)}",
13
- ".dsh-recall-panel-title{color:var(--dsw-alias-label-primary);font-size:14px;font-weight:600;line-height:22px}",
14
- ".dsh-recall-panel-note{color:var(--dsw-alias-label-secondary);font-size:13px;line-height:20px;word-break:break-word}",
15
- ".dsh-recall-list{max-height:220px;overflow:auto;display:flex;flex-direction:column;gap:2px;padding:4px 0}",
16
- ".dsh-recall-file{display:flex;gap:8px;align-items:baseline;font-size:12px;line-height:20px}",
17
- ".dsh-recall-badge{flex:none;font-size:11px;line-height:18px;padding:0 6px;border-radius:6px}",
18
- ".dsh-recall-badge-modified{color:var(--dsw-alias-state-error-primary);background:var(--dsw-alias-interactive-bg-hover)}",
19
- ".dsh-recall-badge-restored{color:var(--dsw-alias-label-secondary);background:var(--dsw-alias-interactive-bg-hover)}",
20
- ".dsh-recall-badge-added{color:var(--dsw-alias-label-tertiary);background:var(--dsw-alias-interactive-bg-hover)}",
21
- ".dsh-recall-rel{min-width:0;color:var(--dsw-alias-label-primary);word-break:break-all;font-family:var(--dsw-font-code, ui-monospace, SFMono-Regular, Consolas, monospace)}",
22
- ".dsh-recall-panel-actions{display:flex;gap:8px;justify-content:flex-end;margin-top:2px}",
23
- ".dsh-recall-btn{border:none;border-radius:8px;padding:5px 14px;font-size:13px;line-height:20px;cursor:pointer;color:var(--dsw-alias-label-secondary);background:var(--dsw-alias-interactive-bg-hover)}",
24
- ".dsh-recall-btn:hover{color:var(--dsw-alias-label-primary)}",
25
- ".dsh-recall-btn-danger{background:var(--dsw-alias-state-error-primary);color:#fff}",
26
- ".dsh-recall-btn-danger:hover{color:#fff;filter:brightness(1.08)}",
27
- ".dsh-recall-toast{position:fixed;top:18px;left:50%;transform:translateX(-50%);z-index:10000;max-width:min(560px,86vw);box-sizing:border-box;background:var(--dsw-alias-bg-base);color:var(--dsw-alias-label-primary);border:1px solid var(--dsw-alias-border-l2);border-radius:12px;padding:10px 16px;font-size:13px;line-height:20px;box-shadow:0 8px 28px rgba(0,0,0,.22);display:flex;align-items:baseline;gap:8px;opacity:0;transition:opacity .25s ease;pointer-events:auto}",
28
- ".dsh-recall-toast.dsh-recall-toast-in{opacity:1}",
29
- ".dsh-recall-toast-tag{flex:none;font-weight:600;color:var(--dsw-alias-state-error-primary)}",
30
- ".dsh-recall-ex-card{display:flex;flex-direction:column;gap:8px}",
31
- ".dsh-recall-ex-title{color:var(--dsw-alias-label-primary);font-size:14px;font-weight:600;line-height:22px}",
32
- ".dsh-recall-ex-note{color:var(--dsw-alias-label-secondary);font-size:13px;line-height:20px;word-break:break-word}",
33
- ".dsh-recall-ex-area{width:100%;box-sizing:border-box;background:var(--dsw-alias-bg-base);color:var(--dsw-alias-label-primary);border:1px solid var(--dsw-alias-border-l1);border-radius:10px;padding:8px 10px;font-size:12px;line-height:20px;font-family:var(--dsw-font-code, ui-monospace, SFMono-Regular, Consolas, monospace);resize:vertical;min-height:120px}",
34
- ".dsh-recall-ex-quick{display:flex;flex-wrap:wrap;gap:8px;align-items:center}",
35
- ".dsh-recall-ex-input{flex:1;min-width:180px;box-sizing:border-box;background:var(--dsw-alias-bg-base);color:var(--dsw-alias-label-primary);border:1px solid var(--dsw-alias-border-l1);border-radius:8px;padding:5px 10px;font-size:13px;line-height:20px}",
36
- ".dsh-recall-ex-chip{border:none;border-radius:6px;padding:2px 8px;font-size:12px;line-height:18px;cursor:pointer;color:var(--dsw-alias-label-secondary);background:var(--dsw-alias-interactive-bg-hover)}",
37
- ".dsh-recall-ex-chip:hover{color:var(--dsw-alias-label-primary)}",
38
- ".dsh-recall-ex-status{margin-right:auto;font-size:12px;line-height:20px;color:var(--dsw-alias-label-tertiary)}",
39
- ".dsh-recall-ex-status-error{color:var(--dsw-alias-state-error-primary)}",
40
- ".dsh-recall-ex-status-success{color:var(--dsw-alias-state-success-primary)}",
41
- ".dsh-recall-tree{display:flex;flex-direction:column;gap:2px;padding:4px 0}",
42
- ".dsh-recall-tree-node{display:flex;flex-direction:column;gap:1px}",
43
- ".dsh-recall-tree-row{display:flex;gap:6px;align-items:center;min-width:0;padding:2px 4px;border-radius:6px;cursor:default}",
44
- ".dsh-recall-tree-row:hover{background:var(--dsw-alias-interactive-bg-hover)}",
45
- ".dsh-recall-tree-toggle{flex:none;width:18px;height:18px;display:inline-flex;align-items:center;justify-content:center;color:var(--dsw-alias-label-tertiary);cursor:pointer;border-radius:4px;font-size:11px;line-height:18px;user-select:none}",
46
- ".dsh-recall-tree-toggle:hover{color:var(--dsw-alias-label-primary);background:var(--dsw-alias-interactive-bg-hover)}",
47
- ".dsh-recall-tree-toggle-placeholder{flex:none;width:18px;height:18px}",
48
- ".dsh-recall-tree-label{flex:1;min-width:0;display:flex;gap:8px;align-items:baseline;font-size:12px;line-height:20px;overflow:hidden}",
49
- ".dsh-recall-tree-name{flex:none;font-weight:600;color:var(--dsw-alias-label-secondary)}",
50
- ".dsh-recall-tree-title{min-width:0;color:var(--dsw-alias-label-primary);white-space:nowrap;overflow:hidden;text-overflow:ellipsis}",
51
- ".dsh-recall-tree-meta{flex:none;font-size:11px;line-height:18px;color:var(--dsw-alias-label-tertiary);white-space:nowrap}",
52
- ".dsh-recall-tree-children{display:flex;flex-direction:column;gap:1px;margin-left:16px;border-left:1px solid var(--dsw-alias-border-l1);padding-left:8px}",
53
- ".dsh-recall-tree-confirm{display:flex;gap:8px;align-items:center;font-size:12px;line-height:20px;color:var(--dsw-alias-label-secondary)}",
54
- ".dsh-recall-card{border:1px solid var(--dsw-alias-border-l2);background:var(--dsw-alias-bg-layer-3);border-radius:12px;list-style:none;transition:border-color .16s,background .16s;display:flex;flex-direction:column;text-align:left}",
55
- ".dsh-recall-card.dsh-recall-card-open{background:var(--dsw-alias-bg-layer-2);border-color:var(--dsw-alias-label-dimmed)}",
56
- ".dsh-recall-card-head{flex-direction:column;flex:1;gap:4px;min-width:0;display:flex}",
57
- ".dsh-recall-card-name{color:var(--dsw-alias-label-primary);font-size:15px;font-weight:600;line-height:1.4}",
58
- ".dsh-recall-card-desc{color:var(--dsw-alias-label-tertiary);font-size:13px;line-height:1.5}",
59
- ".dsh-recall-card-body{border-top:1px solid var(--dsw-alias-border-l2);margin:0 16px;padding:12px 0 8px;display:flex;flex-direction:column;gap:12px}",
60
- ".dsh-recall-cardbtn{appearance:none;width:100%;font:inherit;color:inherit;text-align:left;cursor:pointer;background:0 0;border:0;border-radius:12px;align-items:center;gap:12px;padding:14px 16px;display:flex}",
61
- ".dsh-recall-cfg-row{display:flex;flex-direction:column;gap:4px}",
62
- ".dsh-recall-cfg-line{display:flex;align-items:center;gap:8px}",
63
- ".dsh-recall-cfg-label{flex:none;width:130px;color:var(--dsw-alias-label-secondary);font-size:13px;line-height:20px}",
64
- ".dsh-recall-cfg-input{flex:1;min-width:0;box-sizing:border-box;font:inherit;font-size:13px;color:var(--dsw-alias-label-primary);background:var(--dsw-alias-bg-base);border:1px solid var(--dsw-alias-border-l2);border-radius:8px;padding:4px 8px}",
65
- ".dsh-recall-cfg-input:disabled{opacity:.5}",
66
- ".dsh-recall-cfg-area{font-family:inherit;font-size:12px;line-height:18px;color:var(--dsw-alias-label-primary);background:var(--dsw-alias-bg-base);border:1px solid var(--dsw-alias-border-l2);border-radius:8px;padding:6px 8px;min-height:64px}",
67
- ".dsh-recall-cfg-hint{color:var(--dsw-alias-label-tertiary);font-size:12px;line-height:18px;padding-left:138px}",
68
- ".dsh-recall-cfg-tag{flex:none;font-size:11px;line-height:16px;padding:1px 6px;border-radius:6px;background:var(--dsw-alias-interactive-bg-hover);color:var(--dsw-alias-label-tertiary)}"
69
- ].join("");
70
-
71
- // src/client/util.js
72
- function clockText(ms) {
73
- try {
74
- if (!ms || isNaN(new Date(ms).getTime())) return "";
75
- const d = new Date(ms);
76
- const now = /* @__PURE__ */ new Date();
77
- const sameDay = d.getFullYear() === now.getFullYear() && d.getMonth() === now.getMonth() && d.getDate() === now.getDate();
78
- const hh = String(d.getHours()).padStart(2, "0");
79
- const mm = String(d.getMinutes()).padStart(2, "0");
80
- return sameDay ? hh + ":" + mm : d.getMonth() + 1 + "/" + d.getDate() + " " + hh + ":" + mm;
81
- } catch (e) {
82
- return "";
83
- }
84
- }
85
- function sizeText(bytes) {
86
- if (!bytes || bytes <= 0) return "0 MB";
87
- if (bytes < 1048576) return (bytes / 1024).toFixed(0) + " KB";
88
- if (bytes < 1073741824) return (bytes / 1048576).toFixed(1) + " MB";
89
- return (bytes / 1073741824).toFixed(2) + " GB";
90
- }
91
- function bytesToMb(bytes) {
92
- const n = Number(bytes);
93
- if (!Number.isFinite(n) || n <= 0) return "";
94
- return String(Math.round(n / 1048576 * 100) / 100);
95
- }
96
- function buildTree(list) {
97
- const workspaces = /* @__PURE__ */ new Map();
98
- for (const it of list || []) {
99
- const rootKey = it.root || "unknown-root";
100
- if (!workspaces.has(rootKey)) workspaces.set(rootKey, { root: it.root || null, name: it.workspace || "\u672A\u77E5\u5DE5\u4F5C\u533A", sessions: /* @__PURE__ */ new Map() });
101
- const ws = workspaces.get(rootKey);
102
- const sidKey = it.sessionId || "unknown-session";
103
- if (!ws.sessions.has(sidKey)) ws.sessions.set(sidKey, { root: ws.root, sessionId: it.sessionId || null, title: it.sessionTitle || null, items: [] });
104
- ws.sessions.get(sidKey).items.push(it);
105
- }
106
- const wsList = Array.from(workspaces.values());
107
- wsList.sort((a, b) => (a.name || "").localeCompare(b.name || ""));
108
- for (const ws of wsList) {
109
- ws.sessions = Array.from(ws.sessions.values());
110
- ws.sessions.sort((a, b) => (a.title || "").localeCompare(b.title || ""));
111
- for (const s of ws.sessions) s.items.sort((a, b) => (b.time || 0) - (a.time || 0));
112
- }
113
- return wsList;
114
- }
115
- function buildUtil() {
116
- function api(name, args) {
117
- return fetch("/api/recall/" + name, {
118
- method: "POST",
119
- headers: { "content-type": "application/json" },
120
- body: JSON.stringify(args || {})
121
- }).then((r) => r.json());
122
- }
123
- const CODE_TEXT = {
124
- STALE: "\u9884\u89C8\u540E\u9879\u76EE\u6587\u4EF6\u53D1\u751F\u4E86\u53D8\u5316\uFF0C\u8BF7\u91CD\u65B0\u9884\u89C8\u786E\u8BA4",
125
- AGENT_BUSY: "Agent \u6B63\u5728\u8FD0\u884C\u4E2D\uFF0C\u8BF7\u5148\u505C\u6B62\u540E\u518D\u64A4\u56DE",
126
- NO_SNAPSHOT: "\u8BE5\u6D88\u606F\u6CA1\u6709\u53EF\u7528\u7684\u9879\u76EE\u5FEB\u7167",
127
- NO_STORE: "\u5FEB\u7167\u5B58\u50A8\u4E0D\u53EF\u7528",
128
- ROLLBACK_FAILED: "\u56DE\u9000\u5931\u8D25"
129
- };
130
- function messageFor(res, fallback) {
131
- if (!res) return fallback;
132
- const code = res && res.code;
133
- if (code && CODE_TEXT[code]) return CODE_TEXT[code];
134
- return res && (res.message || res.error) || fallback;
135
- }
136
- const noticeShown = /* @__PURE__ */ new Set();
137
- function mountToast(text) {
138
- if (typeof document === "undefined") return;
139
- try {
140
- let dismiss = function() {
141
- if (dismissed) return;
142
- dismissed = true;
143
- clearTimeout(timer);
144
- el.classList.remove("dsh-recall-toast-in");
145
- setTimeout(() => el.remove(), 300);
146
- };
147
- const el = document.createElement("div");
148
- el.className = "dsh-recall-toast";
149
- const tag = document.createElement("span");
150
- tag.className = "dsh-recall-toast-tag";
151
- tag.textContent = "\u64A4\u56DE\u63D2\u4EF6";
152
- const body = document.createElement("span");
153
- body.textContent = text;
154
- el.appendChild(tag);
155
- el.appendChild(body);
156
- el.addEventListener("click", () => dismiss(), { once: true });
157
- document.body.appendChild(el);
158
- requestAnimationFrame(() => el.classList.add("dsh-recall-toast-in"));
159
- const timer = setTimeout(dismiss, 7e3);
160
- let dismissed = false;
161
- } catch (e) {
162
- }
163
- }
164
- function showNotice(kind, text) {
165
- if (noticeShown.has(kind)) return;
166
- noticeShown.add(kind);
167
- mountToast(text);
168
- }
169
- const toastLastShown = /* @__PURE__ */ new Map();
170
- function showThrottledToast(text) {
171
- const key = String(text).slice(0, 80);
172
- const now = Date.now();
173
- if (now - (toastLastShown.get(key) || 0) < 10 * 60 * 1e3) return;
174
- if (toastLastShown.size > 50) toastLastShown.clear();
175
- toastLastShown.set(key, now);
176
- mountToast(text);
177
- }
178
- const pluginConfig = { refillDraft: true, archiveOriginal: true };
179
- const initMap = /* @__PURE__ */ new Map();
180
- function ensureInit(sessionId) {
181
- if (!sessionId) return Promise.resolve();
182
- const cached = initMap.get(sessionId);
183
- if (cached) return cached;
184
- const done = api("init", { sessionId }).then((res) => {
185
- if (res && res.config && typeof res.config === "object") {
186
- if (typeof res.config.refillDraft === "boolean") pluginConfig.refillDraft = res.config.refillDraft;
187
- if (typeof res.config.archiveOriginal === "boolean") pluginConfig.archiveOriginal = res.config.archiveOriginal;
188
- }
189
- const notice = res && res.notice;
190
- if (notice && notice.unsupported) {
191
- showNotice("unsupported", "\u64A4\u56DE\u63D2\u4EF6\u4EC5\u652F\u6301 Windows / Linux / macOS\uFF0C\u5F53\u524D\u5E73\u53F0\u7684\u5FEB\u7167\u4E0D\u53EF\u7528\u3002");
192
- }
193
- if (notice && notice.gitMissing) {
194
- showNotice("git", "\u672A\u68C0\u6D4B\u5230 git CLI\uFF0C\u64A4\u56DE\u529F\u80FD\u4E0D\u53EF\u7528\uFF08\u5FEB\u7167\u5F15\u64CE\u4F9D\u8D56 git\uFF09\u3002\u5B89\u88C5 git \u5E76\u91CD\u542F DSH \u540E\u5373\u53EF\u4F7F\u7528\u3002");
195
- }
196
- if (notice && notice.homeFallback) {
197
- showNotice("home", "home \u76EE\u5F55\u4E0D\u53EF\u5199\uFF0C\u5FEB\u7167\u5DF2\u964D\u7EA7\u5B58\u50A8\u5230\u9879\u76EE\u5185 .dsh-recall-snapshots \u76EE\u5F55\u3002");
198
- }
199
- }).catch(() => {
200
- initMap.delete(sessionId);
201
- });
202
- initMap.set(sessionId, done);
203
- return done;
204
- }
205
- function writeClipboard(text) {
206
- try {
207
- if (navigator && navigator.clipboard && typeof navigator.clipboard.writeText === "function") {
208
- return navigator.clipboard.writeText(text).then(() => true, () => false);
209
- }
210
- } catch (e) {
211
- }
212
- try {
213
- if (typeof document !== "undefined" && typeof document.execCommand === "function") {
214
- const el = document.createElement("textarea");
215
- el.value = text;
216
- el.setAttribute("readonly", "");
217
- el.style.position = "fixed";
218
- el.style.left = "-9999px";
219
- document.body.appendChild(el);
220
- el.select();
221
- try {
222
- return Promise.resolve(document.execCommand("copy"));
223
- } finally {
224
- el.remove();
225
- }
226
- }
227
- } catch (e) {
228
- }
229
- return Promise.resolve(false);
230
- }
231
- return { api, messageFor, showNotice, showThrottledToast, ensureInit, clockText, writeClipboard, sizeText, bytesToMb, buildTree, pluginConfig };
232
- }
233
-
234
- // src/client/recall-node.js
235
- var KIND_INFO = {
236
- modified: { label: "\u4FEE\u6539", cls: "modified" },
237
- restored: { label: "\u6062\u590D", cls: "restored" },
238
- added: { label: "\u5220\u9664", cls: "added" }
239
- };
240
- function summaryText(counts) {
241
- const parts = [];
242
- for (const kind of Object.keys(KIND_INFO)) {
243
- if (counts[kind] > 0) parts.push(KIND_INFO[kind].label + " " + counts[kind]);
244
- }
245
- return parts.join(" \xB7 ");
246
- }
247
- function buildRecallNode(React, util, ctx, sessionsSvc, workspacesSvc) {
248
- const { api, ensureInit, showThrottledToast, writeClipboard, clockText: clockText2, pluginConfig, messageFor } = util;
249
- function CopyIcon() {
250
- return React.createElement(
251
- "svg",
252
- { width: 16, height: 16, viewBox: "0 0 16 16", fill: "none", stroke: "currentColor", strokeWidth: 1.4, "aria-hidden": true },
253
- React.createElement("rect", { x: 5.5, y: 5.5, width: 8, height: 8, rx: 1.5 }),
254
- React.createElement("path", { d: "M10.5 5.5V4a1.5 1.5 0 0 0-1.5-1.5H4A1.5 1.5 0 0 0 2.5 4v5A1.5 1.5 0 0 0 4 10.5h1.5" })
255
- );
256
- }
257
- function CheckIcon() {
258
- return React.createElement(
259
- "svg",
260
- { width: 16, height: 16, viewBox: "0 0 16 16", fill: "none", stroke: "currentColor", strokeWidth: 1.6, strokeLinecap: "round", strokeLinejoin: "round", "aria-hidden": true },
261
- React.createElement("path", { d: "m3 8.5 3.2 3.2L13 5" })
262
- );
263
- }
264
- function UndoIcon() {
265
- return React.createElement(
266
- "svg",
267
- { width: 16, height: 16, viewBox: "0 0 16 16", fill: "none", stroke: "currentColor", strokeWidth: 1.4, strokeLinecap: "round", strokeLinejoin: "round", "aria-hidden": true },
268
- React.createElement("path", { d: "M7.5 3.5 3.5 7.5l4 4" }),
269
- React.createElement("path", { d: "M4.5 7.5h5a3 3 0 0 1 0 6H8" })
270
- );
271
- }
272
- function recallPanel(recall, closePanel, executeRecall) {
273
- if (recall.stage === "loading") {
274
- return React.createElement(
275
- "div",
276
- { className: "dsh-recall-panel" },
277
- React.createElement("div", { className: "dsh-recall-panel-title" }, "\u6B63\u5728\u8BA1\u7B97\u53D8\u66F4\u2026")
278
- );
279
- }
280
- if (recall.stage === "error") {
281
- return React.createElement(
282
- "div",
283
- { className: "dsh-recall-panel" },
284
- React.createElement("div", { className: "dsh-recall-panel-title" }, "\u65E0\u6CD5\u56DE\u9000"),
285
- React.createElement("div", { className: "dsh-recall-panel-note" }, recall.message || ""),
286
- React.createElement(
287
- "div",
288
- { className: "dsh-recall-panel-actions" },
289
- React.createElement("button", { type: "button", className: "dsh-recall-btn", onClick: closePanel }, "\u5173\u95ED")
290
- )
291
- );
292
- }
293
- if (recall.stage === "confirm") {
294
- const changes = recall.changes || [];
295
- const total = typeof recall.total === "number" ? recall.total : changes.length;
296
- const counts = { modified: 0, restored: 0, added: 0 };
297
- for (const c of changes) {
298
- if (c && counts[c.kind] !== void 0) counts[c.kind]++;
299
- }
300
- const rows = changes.map((c, i) => {
301
- const info = KIND_INFO[c.kind];
302
- return React.createElement(
303
- "div",
304
- { className: "dsh-recall-file", key: i },
305
- React.createElement("span", { className: "dsh-recall-badge dsh-recall-badge-" + (info ? info.cls : "") }, info ? info.label : c.kind || ""),
306
- React.createElement("span", { className: "dsh-recall-rel" }, c.rel || "")
307
- );
308
- });
309
- if (recall.truncated) {
310
- rows.push(React.createElement("div", { className: "dsh-recall-panel-note", key: "truncated" }, "\u2026\u4EC5\u663E\u793A\u524D " + changes.length + " \u6761\uFF0C\u5171 " + total + " \u4E2A\u6587\u4EF6\u5C06\u53D8\u66F4"));
311
- }
312
- const canRevertChat = typeof recall.cutSeq === "number";
313
- return React.createElement(
314
- "div",
315
- { className: "dsh-recall-panel" },
316
- React.createElement("div", { className: "dsh-recall-panel-title" }, "\u6574\u6BB5\u56DE\u9000"),
317
- React.createElement(
318
- "div",
319
- { className: "dsh-recall-panel-note" },
320
- "\u5C06\u9879\u76EE\u6062\u590D\u5230" + (recall.time ? " " + clockText2(recall.time) + " " : " ") + "\u53D1\u9001\u8BE5\u6D88\u606F\u65F6\u7684\u72B6\u6001\u3002\u5171 " + total + " \u4E2A\u6587\u4EF6\u5C06\u53D8\u66F4" + (summaryText(counts) ? "\uFF08" + summaryText(counts) + "\uFF09" : "") + "\u3002\u6B64\u64CD\u4F5C\u4F1A\u8986\u76D6\u5F53\u524D\u6587\u4EF6\u5185\u5BB9\uFF1B\u56DE\u9000\u524D\u4F1A\u81EA\u52A8\u4FDD\u5B58\u4E00\u4EFD\u5F53\u524D\u72B6\u6001\u7684\u5B89\u5168\u5FEB\u7167\uFF08\u4E0D\u542B\u5728\u4E0B\u65B9\u6E05\u5355\u5185\uFF09\u3002"
321
- ),
322
- React.createElement(
323
- "div",
324
- { className: "dsh-recall-panel-note" },
325
- canRevertChat ? "\u5BF9\u8BDD\u5C06\u4E00\u5E76\u56DE\u9000\u5230\u8BE5\u6D88\u606F\u4E4B\u524D\uFF1A\u8BE5\u6D88\u606F\u53CA\u4E4B\u540E\u7684\u5168\u90E8\u5BF9\u8BDD\u4F1A\u4ECE\u5F53\u524D\u89C6\u56FE\u79FB\u9664\uFF0C\u539F\u4F1A\u8BDD\u5F52\u6863\u4FDD\u5B58\uFF08\u53EF\u4ECE\u5F52\u6863\u627E\u56DE\uFF09\u3002" : "\u8BE5\u6D88\u606F\u662F\u672C\u4F1A\u8BDD\u4E2D\u7B2C\u4E00\u6761\u7528\u6237\u6D88\u606F\uFF0C\u65E0\u6CD5\u56DE\u9000\u5BF9\u8BDD\uFF1B\u786E\u8BA4\u540E\u4EC5\u56DE\u9000\u9879\u76EE\u6587\u4EF6\u3002"
326
- ),
327
- changes.length > 0 ? React.createElement("div", { className: "dsh-recall-list" }, ...rows) : null,
328
- React.createElement(
329
- "div",
330
- { className: "dsh-recall-panel-actions" },
331
- React.createElement("button", { type: "button", className: "dsh-recall-btn", onClick: closePanel }, "\u53D6\u6D88"),
332
- React.createElement("button", { type: "button", className: "dsh-recall-btn dsh-recall-btn-danger", onClick: executeRecall }, "\u786E\u8BA4\u56DE\u9000")
333
- )
334
- );
335
- }
336
- if (recall.stage === "executing") {
337
- return React.createElement(
338
- "div",
339
- { className: "dsh-recall-panel" },
340
- React.createElement("div", { className: "dsh-recall-panel-title" }, "\u6B63\u5728\u56DE\u9000\u2026")
341
- );
342
- }
343
- if (recall.stage === "done") {
344
- return React.createElement(
345
- "div",
346
- { className: "dsh-recall-panel" },
347
- React.createElement("div", { className: "dsh-recall-panel-title" }, "\u56DE\u9000\u5B8C\u6210"),
348
- React.createElement(
349
- "div",
350
- { className: "dsh-recall-panel-note" },
351
- recall.chatReverted ? "\u9879\u76EE\u6587\u4EF6\u4E0E\u5BF9\u8BDD\u5DF2\u56DE\u9000\u5230\u8BE5\u6D88\u606F\u4E4B\u524D\u3002\u65B0\u4F1A\u8BDD\u5DF2\u6253\u5F00\uFF0C\u539F\u4F1A\u8BDD\u5DF2\u5F52\u6863\uFF08\u53EF\u4ECE\u5F52\u6863\u627E\u56DE\uFF09\u3002" : "\u9879\u76EE\u5DF2\u6062\u590D\u5230\u53D1\u9001\u8BE5\u6D88\u606F\u65F6\u7684\u72B6\u6001\u3002" + (recall.chatError ? " \u5BF9\u8BDD\u56DE\u9000\u5931\u8D25\uFF1A" + recall.chatError : "")
352
- ),
353
- React.createElement(
354
- "div",
355
- { className: "dsh-recall-panel-actions" },
356
- React.createElement("button", { type: "button", className: "dsh-recall-btn", onClick: closePanel }, "\u5173\u95ED")
357
- )
358
- );
359
- }
360
- return null;
361
- }
362
- function fillDraft(targetSessionId, draftText) {
363
- if (!draftText || !targetSessionId) return;
364
- let attempts = 0;
365
- const attempt = () => {
366
- try {
367
- const conversation = ctx.get("conversation");
368
- if (conversation && conversation.input && typeof conversation.input.shell === "function") {
369
- const shell = conversation.input.shell(targetSessionId);
370
- if (shell) {
371
- if (shell.actions && typeof shell.actions.setDraft === "function") {
372
- shell.actions.setDraft(draftText);
373
- return;
374
- }
375
- if (typeof shell.setDraft === "function") {
376
- shell.setDraft(draftText);
377
- return;
378
- }
379
- }
380
- }
381
- } catch (e) {
382
- }
383
- if (attempts++ < 8) setTimeout(attempt, 150);
384
- };
385
- attempt();
386
- }
387
- function UserRecallNode(props) {
388
- const node = props && props.node;
389
- const renderMessageImages = props && props.renderMessageImages;
390
- const sessionId = props && props.sessionId;
391
- const data = node && node.data ? node.data : {};
392
- const messageId = node ? String(node.id || node.key || "") : "";
393
- const blocks = Array.isArray(data.content) ? data.content : [];
394
- const text = blocks.filter((b) => b && b.type === "text" && typeof b.text === "string").map((b) => b.text).join("");
395
- const imageBlocks = blocks.filter((b) => b && b.type === "image" && b.attachment).map((b) => ({ attachment: b.attachment }));
396
- const rest = blocks.filter((b) => !b || !(b.type === "text" && typeof b.text === "string") && !(b.type === "image" && b.attachment));
397
- const [copied, setCopied] = React.useState(false);
398
- const [hasSnapshot, setHasSnapshot] = React.useState(false);
399
- const [recall, setRecall] = React.useState({ stage: "idle" });
400
- React.useEffect(() => {
401
- let alive = true;
402
- let timer = null;
403
- let attempts = 0;
404
- const RETRY_WINDOW_MS = 5 * 60 * 1e3;
405
- const MAX_ATTEMPTS = 20;
406
- const RETRY_MS = 1e3;
407
- const msgTime = data && typeof data.time === "number" ? data.time : NaN;
408
- const recent = !isNaN(msgTime) && Date.now() - msgTime <= RETRY_WINDOW_MS;
409
- function schedule() {
410
- if (!alive || !messageId) return;
411
- attempts++;
412
- api("snapshot-info", { messageId, sessionId }).then((res) => {
413
- if (!alive) return;
414
- if (res && res.has) {
415
- if (recent && Array.isArray(res.skipped) && res.skipped.length) {
416
- const names = res.skipped.slice(0, 5).join("\u3001") + (res.skipped.length > 5 ? " \u7B49 " + res.skipped.length + " \u9879" : "");
417
- showThrottledToast("\u5FEB\u7167\u5DF2\u8DF3\u8FC7\u672A\u7EB3\u5165\u7684\u8DEF\u5F84\uFF1A" + names + "\uFF08\u64A4\u56DE\u4E0D\u4F1A\u6062\u590D\u6216\u5220\u9664\u8FD9\u4E9B\u8DEF\u5F84\uFF09");
418
- }
419
- setHasSnapshot(true);
420
- return;
421
- }
422
- if (res && res.failed) {
423
- if (recent) showThrottledToast("\u5FEB\u7167\u5931\u8D25\uFF1A" + String(res.error || "\u672A\u77E5\u539F\u56E0").slice(0, 140));
424
- return;
425
- }
426
- if (recent && attempts < MAX_ATTEMPTS) timer = setTimeout(schedule, RETRY_MS);
427
- }).catch(() => {
428
- if (alive && recent && attempts < MAX_ATTEMPTS) timer = setTimeout(schedule, RETRY_MS);
429
- });
430
- }
431
- ensureInit(sessionId).then(() => {
432
- if (!messageId || !alive) return;
433
- schedule();
434
- }).catch(() => {
435
- if (alive && messageId) timer = setTimeout(schedule, RETRY_MS);
436
- });
437
- return () => {
438
- alive = false;
439
- if (timer !== null) clearTimeout(timer);
440
- };
441
- }, [messageId, sessionId]);
442
- const onCopy = () => {
443
- if (copied) return;
444
- writeClipboard(text).then(() => {
445
- setCopied(true);
446
- const timer = ctx.get("timer");
447
- if (timer && typeof timer.timeout === "function") {
448
- timer.timeout(() => setCopied(false), 1200);
449
- } else {
450
- setTimeout(() => setCopied(false), 1200);
451
- }
452
- });
453
- };
454
- const openPreview = () => {
455
- if (recall.stage === "loading" || recall.stage === "executing") return;
456
- setRecall({ stage: "loading" });
457
- api("preview", { messageId, sessionId }).then((res) => {
458
- if (!res || !res.ok) {
459
- setRecall({ stage: "error", message: messageFor(res, "\u65E0\u6CD5\u83B7\u53D6\u5FEB\u7167") });
460
- return;
461
- }
462
- setRecall({
463
- stage: "confirm",
464
- changes: res.changes || [],
465
- total: typeof res.total === "number" ? res.total : (res.changes || []).length,
466
- truncated: Boolean(res.truncated),
467
- treeId: res.treeId || null,
468
- time: res.time || null,
469
- cutSeq: typeof res.cutSeq === "number" ? res.cutSeq : null
470
- });
471
- }).catch((error) => {
472
- setRecall({ stage: "error", message: String(error) });
473
- });
474
- };
475
- const executeRecall = () => {
476
- if (recall.stage !== "confirm") return;
477
- const changes = recall.changes || [];
478
- const previewCut = typeof recall.cutSeq === "number" ? recall.cutSeq : null;
479
- const previewTotal = typeof recall.total === "number" ? recall.total : changes.length;
480
- setRecall({ stage: "executing", changes });
481
- api("execute", { messageId, sessionId, previewTotal, previewTreeId: recall.treeId || void 0, previewAt: Date.now() }).then(async (res) => {
482
- if (!res || !res.ok) {
483
- if (res && res.code === "STALE") {
484
- setRecall({ stage: "loading" });
485
- api("preview", { messageId, sessionId }).then((res2) => {
486
- if (!res2 || !res2.ok) {
487
- setRecall({ stage: "error", message: messageFor(res2, "\u65E0\u6CD5\u83B7\u53D6\u5FEB\u7167") });
488
- return;
489
- }
490
- setRecall({
491
- stage: "confirm",
492
- changes: res2.changes || [],
493
- total: typeof res2.total === "number" ? res2.total : (res2.changes || []).length,
494
- truncated: Boolean(res2.truncated),
495
- treeId: res2.treeId || null,
496
- time: res2.time || null,
497
- cutSeq: typeof res2.cutSeq === "number" ? res2.cutSeq : null
498
- });
499
- }).catch((error) => {
500
- setRecall({ stage: "error", message: String(error) });
501
- });
502
- return;
503
- }
504
- setRecall({ stage: "error", message: messageFor(res, "\u56DE\u9000\u5931\u8D25") });
505
- return;
506
- }
507
- const cutSeq = typeof res.cutSeq === "number" ? res.cutSeq : previewCut;
508
- let chatReverted = false;
509
- let chatError = "";
510
- let fillTarget = sessionId;
511
- if (cutSeq !== null && sessionsSvc && typeof sessionsSvc.fork === "function") {
512
- try {
513
- const childId = await sessionsSvc.fork({ sessionId, atSeq: cutSeq });
514
- if (childId) {
515
- if (typeof sessionsSvc.open === "function") sessionsSvc.open(childId);
516
- chatReverted = true;
517
- fillTarget = childId;
518
- api("lineage-record", { childId, parentId: sessionId }).catch(() => {
519
- });
520
- if (pluginConfig.archiveOriginal && workspacesSvc && typeof workspacesSvc.archiveSession === "function") {
521
- workspacesSvc.archiveSession(sessionId).catch(() => {
522
- });
523
- }
524
- } else {
525
- chatError = "\u672A\u8FD4\u56DE\u65B0\u4F1A\u8BDD";
526
- }
527
- } catch (error) {
528
- chatError = String(error);
529
- }
530
- }
531
- if (pluginConfig.refillDraft) fillDraft(fillTarget, text);
532
- setHasSnapshot(false);
533
- setRecall({ stage: "done", count: typeof res.count === "number" ? res.count : changes.length, chatReverted, chatError });
534
- }).catch((error) => {
535
- setRecall({ stage: "error", message: String(error) });
536
- });
537
- };
538
- const closePanel = () => setRecall({ stage: "idle" });
539
- const bubbleChildren = [];
540
- if (imageBlocks.length && typeof renderMessageImages === "function") {
541
- bubbleChildren.push(React.createElement(
542
- React.Fragment,
543
- { key: "images" },
544
- renderMessageImages({ images: imageBlocks, align: "end" })
545
- ));
546
- }
547
- if (text !== "") bubbleChildren.push(React.createElement("div", { className: "dsh-recall-bubble", key: "text" }, text));
548
- for (let i = 0; i < rest.length; i++) {
549
- bubbleChildren.push(React.createElement("pre", { className: "dsh-recall-json", key: "rest-" + i }, JSON.stringify(rest[i], null, 2)));
550
- }
551
- const actions = [];
552
- actions.push(React.createElement("span", { className: "dsh-recall-time", key: "time" }, clockText2(data.time)));
553
- actions.push(React.createElement("button", {
554
- key: "copy",
555
- type: "button",
556
- className: "dsh-recall-action",
557
- "aria-label": copied ? "\u5DF2\u590D\u5236" : "\u590D\u5236",
558
- title: copied ? "\u5DF2\u590D\u5236" : "\u590D\u5236",
559
- onClick: onCopy
560
- }, copied ? React.createElement(CheckIcon, {}) : React.createElement(CopyIcon, {})));
561
- if (hasSnapshot) {
562
- actions.push(React.createElement("button", {
563
- key: "recall",
564
- type: "button",
565
- className: "dsh-recall-action",
566
- "aria-label": "\u64A4\u56DE",
567
- title: "\u6574\u6BB5\u56DE\u9000\uFF1A\u6587\u4EF6\u4E0E\u5BF9\u8BDD\u4E00\u5E76\u56DE\u5230\u8BE5\u6D88\u606F\u4E4B\u524D",
568
- onClick: openPreview
569
- }, React.createElement(UndoIcon, {})));
570
- }
571
- return React.createElement(
572
- "div",
573
- { className: "dsh-recall-row", "data-time-hover-root": true },
574
- bubbleChildren.length > 0 ? React.createElement("div", { className: "dsh-recall-stack", key: "stack" }, ...bubbleChildren) : null,
575
- React.createElement("div", { className: "dsh-recall-actions", key: "actions" }, ...actions),
576
- recallPanel(recall, closePanel, executeRecall)
577
- );
578
- }
579
- return { UserRecallNode };
580
- }
581
-
582
- // src/client/settings-cards.js
583
- function groupByLineage(ids, lineage) {
584
- const childOf = /* @__PURE__ */ new Map();
585
- const childrenOf = /* @__PURE__ */ new Map();
586
- for (const e of lineage || []) {
587
- if (e && e.childId && e.parentId) {
588
- const child = String(e.childId);
589
- const parent = String(e.parentId);
590
- childOf.set(child, parent);
591
- const kids = childrenOf.get(parent) || [];
592
- kids.push(child);
593
- childrenOf.set(parent, kids);
594
- }
595
- }
596
- const idSet = new Set((ids || []).map(String));
597
- const result = /* @__PURE__ */ new Map();
598
- const assigned = /* @__PURE__ */ new Set();
599
- for (const id of idSet) {
600
- if (assigned.has(id)) continue;
601
- let root = id;
602
- const seen = /* @__PURE__ */ new Set();
603
- while (childOf.has(root) && idSet.has(childOf.get(root)) && !seen.has(root)) {
604
- seen.add(root);
605
- root = childOf.get(root);
606
- }
607
- const chain = [];
608
- const queue = [root];
609
- while (queue.length) {
610
- const cur = queue.shift();
611
- if (!cur || !idSet.has(cur) || assigned.has(cur)) continue;
612
- chain.push(cur);
613
- assigned.add(cur);
614
- for (const k of childrenOf.get(cur) || []) queue.push(k);
615
- }
616
- if (chain.length > 1) {
617
- chain.forEach((sid, i) => result.set(sid, { family: chain, index: i + 1 }));
618
- }
619
- }
620
- return result;
621
- }
622
- function buildSettingsCards(React, util, sessionsSvc) {
623
- const { api, clockText: clockText2, sizeText: sizeText2, bytesToMb: bytesToMb2, buildTree: buildTree2 } = util;
624
- const EXCLUDE_SUGGESTIONS = ["dist/", "build/", "out/", "coverage/", "*.log", ".env"];
625
- function ExcludeCard(props) {
626
- const file = props.file;
627
- const [draft, setDraft] = React.useState(file.content || "");
628
- const [baseline, setBaseline] = React.useState(file.content || "");
629
- const [quick, setQuick] = React.useState("");
630
- const [state, setState] = React.useState({ busy: false, message: "", error: false });
631
- const dirty = draft !== baseline;
632
- function appendPattern(pattern) {
633
- setDraft((d) => (d && !d.endsWith("\n") ? d + "\n" : d) + pattern + "\n");
634
- }
635
- function addQuick() {
636
- const t = quick.trim();
637
- if (!t) return;
638
- appendPattern(t);
639
- setQuick("");
640
- }
641
- function save() {
642
- if (state.busy || !dirty) return;
643
- setState({ busy: true, message: "\u4FDD\u5B58\u4E2D\u2026", error: false });
644
- api("exclude-set", { path: file.path, content: draft }).then((res) => {
645
- if (res && res.ok) {
646
- setBaseline(draft);
647
- setState({ busy: false, message: "\u5DF2\u4FDD\u5B58\uFF0C\u4E0B\u4E00\u6B21\u5FEB\u7167 / \u9884\u89C8 / \u56DE\u9000\u65F6\u751F\u6548", error: false });
648
- } else {
649
- setState({ busy: false, message: res && (res.message || res.error) || "\u4FDD\u5B58\u5931\u8D25", error: true });
650
- }
651
- }).catch((error) => {
652
- setState({ busy: false, message: String(error), error: true });
653
- });
654
- }
655
- function discard() {
656
- if (state.busy) return;
657
- setDraft(baseline);
658
- setState({ busy: false, message: "", error: false });
659
- }
660
- const draftLines = draft.split("\n").map((l) => l.trim());
661
- const suggestions = EXCLUDE_SUGGESTIONS.filter((s) => draftLines.indexOf(s) < 0);
662
- return React.createElement(
663
- "div",
664
- { className: "dsh-recall-ex-card" },
665
- React.createElement("div", { className: "dsh-recall-ex-title" }, "\u5FEB\u7167\u6392\u9664\u9879"),
666
- React.createElement(
667
- "div",
668
- { className: "dsh-recall-ex-note" },
669
- file.home ? "\u6B64\u914D\u7F6E\u5168\u5C40\u5171\u4EAB\uFF0C\u5BF9\u6240\u6709\u5DE5\u4F5C\u533A\u7684\u5FEB\u7167\u751F\u6548\uFF08\u5B58\u50A8\u4F4D\u7F6E\uFF1A" + file.path + "\uFF09\u3002" : "home \u76EE\u5F55\u4E0D\u53EF\u5199\u65F6\u6B64\u5DE5\u4F5C\u533A\u964D\u7EA7\u5B58\u50A8\uFF0C\u6392\u9664\u914D\u7F6E\u72EC\u7ACB\u751F\u6548\uFF08\u5B58\u50A8\u4F4D\u7F6E\uFF1A" + file.path + "\uFF09\u3002"
670
- ),
671
- React.createElement("div", { className: "dsh-recall-ex-note" }, "gitignore \u8BED\u6CD5\uFF0C\u4E00\u884C\u4E00\u6761\uFF0C\u652F\u6301 # \u6CE8\u91CA\uFF1B\u547D\u4E2D\u6392\u9664\u7684\u6587\u4EF6\u4E0E\u76EE\u5F55\u4E0D\u8FDB\u5165\u5FEB\u7167\uFF0C\u4E5F\u4E0D\u4F1A\u88AB\u56DE\u9000\u89E6\u78B0\u3002"),
672
- React.createElement("textarea", {
673
- className: "dsh-recall-ex-area",
674
- value: draft,
675
- spellCheck: false,
676
- onChange: (e) => setDraft(e.target.value)
677
- }),
678
- React.createElement(
679
- "div",
680
- { className: "dsh-recall-ex-quick" },
681
- React.createElement("input", {
682
- className: "dsh-recall-ex-input",
683
- value: quick,
684
- placeholder: "\u8F93\u5165\u8DEF\u5F84\u6216\u6A21\u5F0F\uFF0C\u56DE\u8F66\u5FEB\u901F\u6DFB\u52A0",
685
- onChange: (e) => setQuick(e.target.value),
686
- onKeyDown: (e) => {
687
- if (e.key === "Enter") {
688
- e.preventDefault();
689
- addQuick();
690
- }
691
- }
692
- }),
693
- React.createElement("button", { type: "button", className: "dsh-recall-btn", onClick: addQuick }, "\u6DFB\u52A0"),
694
- ...suggestions.map((s) => React.createElement("button", {
695
- key: "chip-" + s,
696
- type: "button",
697
- className: "dsh-recall-ex-chip",
698
- title: "\u70B9\u51FB\u8FFD\u52A0 " + s,
699
- onClick: () => appendPattern(s)
700
- }, s))
701
- ),
702
- React.createElement(
703
- "div",
704
- { className: "dsh-recall-panel-actions" },
705
- state.message ? React.createElement("span", { className: "dsh-recall-ex-status" + (state.error ? " dsh-recall-ex-status-error" : " dsh-recall-ex-status-success") }, state.message) : null,
706
- React.createElement("button", { type: "button", className: "dsh-recall-btn", disabled: !dirty || state.busy, onClick: discard }, "\u653E\u5F03\u4FEE\u6539"),
707
- React.createElement("button", { type: "button", className: "dsh-recall-btn dsh-recall-btn-danger", disabled: !dirty || state.busy, onClick: save }, "\u4FDD\u5B58")
708
- )
709
- );
710
- }
711
- function ManageCard(props) {
712
- const [items, setItems] = React.useState(null);
713
- const [usage, setUsage] = React.useState(null);
714
- const [errors, setErrors] = React.useState(null);
715
- const [state, setState] = React.useState({ busy: false, message: "", error: false });
716
- const [limit, setLimit] = React.useState(200);
717
- const [total, setTotal] = React.useState(0);
718
- const [health, setHealth] = React.useState(null);
719
- const [query, setQuery] = React.useState("");
720
- const [showAllErrors, setShowAllErrors] = React.useState(false);
721
- const [titlesPending, setTitlesPending] = React.useState(false);
722
- const [lineage, setLineage] = React.useState([]);
723
- function fetchTitles(list) {
724
- const missing = Array.from(new Set(
725
- (list || []).filter((it) => it.sessionId && !it.sessionTitle).map((it) => it.sessionId)
726
- )).slice(0, 100);
727
- if (!missing.length) {
728
- setTitlesPending(false);
729
- return;
730
- }
731
- setTitlesPending(true);
732
- api("manage", { op: "titles", sessionIds: missing }).then((res) => {
733
- const map = res && res.ok ? res.titles : null;
734
- if (map) {
735
- setItems((prev) => (prev || []).map((it) => it.sessionId && map[it.sessionId] ? Object.assign({}, it, { sessionTitle: map[it.sessionId] }) : it));
736
- }
737
- setTitlesPending(false);
738
- }).catch(() => setTitlesPending(false));
739
- }
740
- function fetchMessages(list) {
741
- const requests = (list || []).filter((it) => it.sessionId && it.id && !Object.prototype.hasOwnProperty.call(it, "messageText")).map((it) => ({ sessionId: it.sessionId, messageId: it.id })).slice(0, 200);
742
- if (!requests.length) return;
743
- api("manage", { op: "messages", requests }).then((res) => {
744
- const map = res && res.ok ? res.messageTexts : null;
745
- if (map) {
746
- setItems((prev) => (prev || []).map((it) => it.id && Object.prototype.hasOwnProperty.call(map, it.id) ? Object.assign({}, it, { messageText: map[it.id] }) : it));
747
- }
748
- }).catch(() => {
749
- });
750
- }
751
- function refresh(overLimit) {
752
- const useLimit = overLimit || limit;
753
- api("manage", { op: "list", limit: useLimit }).then((res) => {
754
- if (res && res.ok) {
755
- setItems(res.items || []);
756
- setTotal(typeof res.total === "number" ? res.total : (res.items || []).length);
757
- fetchTitles(res.items || []);
758
- fetchMessages(res.items || []);
759
- if (res.stale) {
760
- api("manage", { op: "list", limit: useLimit }).then((res2) => {
761
- if (res2 && res2.ok && !res2.stale) {
762
- setItems(res2.items || []);
763
- setTotal(typeof res2.total === "number" ? res2.total : (res2.items || []).length);
764
- fetchTitles(res2.items || []);
765
- fetchMessages(res2.items || []);
766
- }
767
- }).catch(() => {
768
- });
769
- }
770
- }
771
- api("manage", { op: "lineage" }).then((res2) => {
772
- if (res2 && res2.ok && Array.isArray(res2.lineage)) setLineage(res2.lineage);
773
- }).catch(() => {
774
- });
775
- api("manage", { op: "usage" }).then((res2) => {
776
- if (res2 && res2.ok) {
777
- setUsage(res2.bytes || 0);
778
- setHealth({ gitAvailable: res2.gitAvailable !== false, homeStores: res2.homeStores || 0, fallbackStores: res2.fallbackStores || 0 });
779
- }
780
- }).catch(() => {
781
- });
782
- api("status", {}).then((res2) => {
783
- if (res2 && res2.ok) setErrors(res2.errors || []);
784
- }).catch(() => {
785
- });
786
- }).catch(() => {
787
- api("manage", { op: "usage" }).then((res) => {
788
- if (res && res.ok) {
789
- setUsage(res.bytes || 0);
790
- setHealth({ gitAvailable: res.gitAvailable !== false, homeStores: res.homeStores || 0, fallbackStores: res.fallbackStores || 0 });
791
- }
792
- }).catch(() => {
793
- });
794
- api("status", {}).then((res) => {
795
- if (res && res.ok) setErrors(res.errors || []);
796
- }).catch(() => {
797
- });
798
- });
799
- }
800
- React.useEffect(() => {
801
- refresh();
802
- }, []);
803
- function clearErrors() {
804
- setErrors([]);
805
- api("status", { op: "clear" }).catch(() => {
806
- });
807
- }
808
- function run(op, extra, doneText) {
809
- if (state.busy) return;
810
- setState({ busy: true, message: "\u6267\u884C\u4E2D\u2026", error: false });
811
- api("manage", Object.assign({ op }, extra || {})).then((res) => {
812
- if (res && res.ok) {
813
- setState({ busy: false, message: typeof res.deleted === "number" ? "\u5DF2\u5220\u9664 " + res.deleted + " \u6761\u5FEB\u7167" : doneText, error: false });
814
- refresh();
815
- } else {
816
- setState({ busy: false, message: res && (res.message || res.error) || "\u64CD\u4F5C\u5931\u8D25", error: true });
817
- }
818
- }).catch((e) => setState({ busy: false, message: String(e), error: true }));
819
- }
820
- const [expanded, setExpanded] = React.useState(() => /* @__PURE__ */ new Set());
821
- const [confirming, setConfirming] = React.useState(null);
822
- function renderDeleteAllConfirm() {
823
- if (!confirming || confirming.kind !== "all") return null;
824
- return React.createElement(
825
- "div",
826
- { className: "dsh-recall-tree-confirm" },
827
- "\u786E\u8BA4\u5220\u9664\u6240\u6709\u5DE5\u4F5C\u533A\u7684\u5168\u90E8\u5FEB\u7167\uFF1F\u6B64\u64CD\u4F5C\u4E0D\u53EF\u6062\u590D\u3002",
828
- React.createElement("button", {
829
- type: "button",
830
- className: "dsh-recall-btn dsh-recall-btn-danger",
831
- onClick: () => {
832
- setConfirming(null);
833
- run("deleteAll", {}, "\u5DF2\u6E05\u7A7A\u5168\u90E8\u5FEB\u7167");
834
- }
835
- }, "\u786E\u8BA4\u5168\u90E8\u5220\u9664"),
836
- React.createElement("button", { type: "button", className: "dsh-recall-ex-chip", onClick: () => setConfirming(null) }, "\u53D6\u6D88")
837
- );
838
- }
839
- function toggle(key) {
840
- setExpanded((prev) => {
841
- const next = new Set(prev);
842
- if (next.has(key)) next.delete(key);
843
- else next.add(key);
844
- return next;
845
- });
846
- }
847
- const q = query.trim().toLowerCase();
848
- const filteredItems = q ? (items || []).filter(
849
- (it) => (it.workspace || "").toLowerCase().indexOf(q) >= 0 || (it.sessionTitle || "").toLowerCase().indexOf(q) >= 0 || (it.messageText || "").toLowerCase().indexOf(q) >= 0 || String(it.id || "").toLowerCase().indexOf(q) >= 0
850
- ) : items;
851
- const tree = buildTree2(filteredItems);
852
- const allSessionIds = Array.from(new Set((items || []).map((it) => it.sessionId).filter(Boolean)));
853
- const versionMap = groupByLineage(allSessionIds, lineage);
854
- let listById = null;
855
- try {
856
- if (sessionsSvc && sessionsSvc.list && typeof sessionsSvc.list.getSnapshot === "function") {
857
- listById = sessionsSvc.list.getSnapshot().byId || null;
858
- }
859
- } catch (e) {
860
- listById = null;
861
- }
862
- function confirmDelete(kind, key, extra, text) {
863
- setConfirming({ kind, key, extra, text });
864
- }
865
- function renderConfirm(kind, key, extra, text) {
866
- if (!confirming || confirming.kind !== kind || confirming.key !== key) return null;
867
- return React.createElement(
868
- "div",
869
- { className: "dsh-recall-tree-confirm" },
870
- text,
871
- React.createElement("button", {
872
- type: "button",
873
- className: "dsh-recall-ex-chip",
874
- onClick: () => {
875
- const c = confirming;
876
- setConfirming(null);
877
- run("delete", c.extra, "\u5DF2\u5220\u9664");
878
- }
879
- }, "\u786E\u8BA4"),
880
- React.createElement("button", { type: "button", className: "dsh-recall-ex-chip", onClick: () => setConfirming(null) }, "\u53D6\u6D88")
881
- );
882
- }
883
- function renderLeaf(it) {
884
- const key = "snap-" + it.id;
885
- const text = it.messageText;
886
- const title = text || it.id;
887
- const label = text ? clockText2(it.time) + " " + text : clockText2(it.time) + " " + it.id.slice(0, 12) + "\u2026";
888
- return React.createElement(
889
- "div",
890
- { className: "dsh-recall-tree-node", key },
891
- React.createElement(
892
- "div",
893
- { className: "dsh-recall-tree-row", title },
894
- React.createElement("span", { className: "dsh-recall-tree-toggle-placeholder" }),
895
- React.createElement(
896
- "span",
897
- { className: "dsh-recall-tree-label" },
898
- React.createElement("span", { className: "dsh-recall-tree-title" }, label)
899
- ),
900
- React.createElement("button", {
901
- type: "button",
902
- className: "dsh-recall-ex-chip",
903
- title: "\u5220\u9664\u8BE5\u5FEB\u7167\uFF08tag \u4E0E\u7D22\u5F15\u6761\u76EE\uFF09",
904
- onClick: () => confirmDelete("snapshot", key, { messageId: it.id, root: it.root || null }, "\u786E\u8BA4\u5220\u9664\u8BE5\u5FEB\u7167\uFF1F\u6B64\u64CD\u4F5C\u4E0D\u53EF\u6062\u590D\u3002")
905
- }, "\u5220\u9664")
906
- ),
907
- renderConfirm("snapshot", key, { messageId: it.id, root: it.root || null }, "\u786E\u8BA4\u5220\u9664\u8BE5\u5FEB\u7167\uFF1F\u6B64\u64CD\u4F5C\u4E0D\u53EF\u6062\u590D\u3002")
908
- );
909
- }
910
- function renderSession(s) {
911
- const key = "session-" + (s.root || "") + "-" + s.sessionId;
912
- const open = expanded.has(key);
913
- const label = s.title || (titlesPending && s.sessionId ? "\u2026" : "\uFF08\u5DF2\u5220\u9664\u4F1A\u8BDD\uFF09");
914
- const version = s.sessionId ? versionMap.get(String(s.sessionId)) : null;
915
- const switchable = Boolean(s.sessionId && listById && listById[s.sessionId]);
916
- return React.createElement(
917
- "div",
918
- { className: "dsh-recall-tree-node", key },
919
- React.createElement(
920
- "div",
921
- { className: "dsh-recall-tree-row" },
922
- React.createElement("span", {
923
- className: "dsh-recall-tree-toggle",
924
- onClick: () => toggle(key)
925
- }, open ? "\u25BE" : "\u25B8"),
926
- React.createElement(
927
- "span",
928
- { className: "dsh-recall-tree-label", title: s.sessionId || "" },
929
- React.createElement("span", { className: "dsh-recall-tree-title" }, label),
930
- version ? React.createElement("span", { className: "dsh-recall-tree-meta", title: "\u7248\u672C\u5BB6\u65CF\uFF1A" + version.family.join(" \u2192 ") }, "v" + version.index + "/" + version.family.length) : null,
931
- React.createElement("span", { className: "dsh-recall-tree-meta" }, s.items.length + " \u6761")
932
- ),
933
- switchable ? React.createElement("button", {
934
- type: "button",
935
- className: "dsh-recall-ex-chip",
936
- title: "\u5207\u6362\u5230\u8BE5\u7248\u672C\u4F1A\u8BDD",
937
- onClick: () => {
938
- try {
939
- sessionsSvc.open(s.sessionId);
940
- } catch (e) {
941
- }
942
- }
943
- }, "\u5207\u6362") : null,
944
- s.sessionId ? React.createElement("button", {
945
- type: "button",
946
- className: "dsh-recall-ex-chip",
947
- title: "\u5220\u9664\u8BE5\u4F1A\u8BDD\u5168\u90E8\u5FEB\u7167",
948
- onClick: () => confirmDelete("session", key, { scope: "session", sessionId: s.sessionId, root: s.root || null }, "\u786E\u8BA4\u5220\u9664\u8BE5\u4F1A\u8BDD\u5168\u90E8\u5FEB\u7167\uFF1F\u6B64\u64CD\u4F5C\u4E0D\u53EF\u6062\u590D\u3002")
949
- }, "\u5220\u9664") : null
950
- ),
951
- open ? React.createElement("div", { className: "dsh-recall-tree-children" }, ...s.items.map(renderLeaf)) : null,
952
- s.sessionId ? renderConfirm("session", key, { scope: "session", sessionId: s.sessionId, root: s.root || null }, "\u786E\u8BA4\u5220\u9664\u8BE5\u4F1A\u8BDD\u5168\u90E8\u5FEB\u7167\uFF1F\u6B64\u64CD\u4F5C\u4E0D\u53EF\u6062\u590D\u3002") : null
953
- );
954
- }
955
- function renderWorkspace(ws) {
956
- const key = "ws-" + ws.root;
957
- const open = expanded.has(key);
958
- const sessionCount = ws.sessions.length;
959
- const snapCount = ws.sessions.reduce((n, s) => n + s.items.length, 0);
960
- return React.createElement(
961
- "div",
962
- { className: "dsh-recall-tree-node", key },
963
- React.createElement(
964
- "div",
965
- { className: "dsh-recall-tree-row" },
966
- React.createElement("span", {
967
- className: "dsh-recall-tree-toggle",
968
- onClick: () => toggle(key)
969
- }, open ? "\u25BE" : "\u25B8"),
970
- React.createElement(
971
- "span",
972
- { className: "dsh-recall-tree-label", title: ws.root || "" },
973
- React.createElement("span", { className: "dsh-recall-tree-name" }, ws.name),
974
- React.createElement("span", { className: "dsh-recall-tree-meta" }, sessionCount + " \u4F1A\u8BDD / " + snapCount + " \u5FEB\u7167")
975
- ),
976
- ws.root ? React.createElement("button", {
977
- type: "button",
978
- className: "dsh-recall-ex-chip",
979
- title: "\u5220\u9664\u8BE5\u5DE5\u4F5C\u533A\u5168\u90E8\u5FEB\u7167",
980
- onClick: () => confirmDelete("workspace", key, { scope: "workspace", root: ws.root }, "\u786E\u8BA4\u5220\u9664\u8BE5\u5DE5\u4F5C\u533A\u5168\u90E8\u5FEB\u7167\uFF1F\u6B64\u64CD\u4F5C\u4E0D\u53EF\u6062\u590D\u3002")
981
- }, "\u5220\u9664") : null
982
- ),
983
- open ? React.createElement("div", { className: "dsh-recall-tree-children" }, ...ws.sessions.map(renderSession)) : null,
984
- ws.root ? renderConfirm("workspace", key, { scope: "workspace", root: ws.root }, "\u786E\u8BA4\u5220\u9664\u8BE5\u5DE5\u4F5C\u533A\u5168\u90E8\u5FEB\u7167\uFF1F\u6B64\u64CD\u4F5C\u4E0D\u53EF\u6062\u590D\u3002") : null
985
- );
986
- }
987
- const treeNodes = tree.map(renderWorkspace);
988
- const loaded = items ? items.length : null;
989
- const countText = loaded === null ? "\u5171 \u2026 \u6761\u5FEB\u7167" : "\u5171 " + total + " \u6761\u5FEB\u7167" + (limit < total ? "\uFF08\u5F53\u524D\u663E\u793A\u6700\u65B0 " + loaded + " \u6761\uFF09" : "");
990
- function loadMore() {
991
- const next = Math.min(Math.max(total, limit), 2e3);
992
- if (next <= limit) return;
993
- setLimit(next);
994
- refresh(next);
995
- }
996
- return React.createElement(
997
- "div",
998
- { className: "dsh-recall-ex-card" },
999
- React.createElement("div", { className: "dsh-recall-ex-title" }, "\u5FEB\u7167\u7BA1\u7406"),
1000
- React.createElement(
1001
- "div",
1002
- { className: "dsh-recall-ex-note" },
1003
- usage === null ? countText + "\u3002" : countText + "\uFF0C\u5168\u90E8\u5DE5\u4F5C\u533A\u5FEB\u7167\u5B58\u50A8\u5360\u7528 " + sizeText2(usage) + "\u3002"
1004
- ),
1005
- health ? React.createElement(
1006
- "div",
1007
- { className: "dsh-recall-ex-note", key: "health" },
1008
- React.createElement("span", {
1009
- className: health.gitAvailable ? "" : "dsh-recall-ex-status-error"
1010
- }, health.gitAvailable ? "git \u53EF\u7528" : "git \u4E0D\u53EF\u7528\uFF08\u5FEB\u7167\u5F15\u64CE\u4F9D\u8D56 git\uFF09"),
1011
- " \xB7 \u5FEB\u7167\u5B58\u50A8\uFF1Ahome " + health.homeStores + " \u4E2A\u5DE5\u4F5C\u533A" + (health.fallbackStores ? "\uFF0C\u964D\u7EA7 " + health.fallbackStores + " \u4E2A" : "")
1012
- ) : null,
1013
- React.createElement("input", {
1014
- className: "dsh-recall-ex-input",
1015
- placeholder: "\u641C\u7D22\u5DE5\u4F5C\u533A / \u4F1A\u8BDD\u6807\u9898 / \u6D88\u606F\u5185\u5BB9 / ID",
1016
- value: query,
1017
- spellCheck: false,
1018
- onChange: (e) => setQuery(e.target.value)
1019
- }),
1020
- treeNodes.length > 0 ? React.createElement("div", { className: "dsh-recall-tree" }, ...treeNodes) : null,
1021
- items && items.length === 0 && !q ? React.createElement("div", { className: "dsh-recall-ex-note", key: "empty" }, "\u5728\u4EFB\u610F\u5DE5\u4F5C\u533A\u53D1\u9001\u4E00\u6761\u6D88\u606F\u540E\uFF0C\u8FD9\u91CC\u4F1A\u51FA\u73B0\u5FEB\u7167\u3002") : null,
1022
- q && filteredItems && filteredItems.length === 0 ? React.createElement("div", { className: "dsh-recall-ex-note", key: "no-match" }, "\u65E0\u5339\u914D\u5FEB\u7167") : null,
1023
- renderDeleteAllConfirm(),
1024
- React.createElement(
1025
- "div",
1026
- { className: "dsh-recall-panel-actions" },
1027
- state.message ? React.createElement("span", { className: "dsh-recall-ex-status" + (state.error ? " dsh-recall-ex-status-error" : " dsh-recall-ex-status-success") }, state.message) : null,
1028
- limit < total ? React.createElement("button", {
1029
- type: "button",
1030
- className: "dsh-recall-btn",
1031
- disabled: state.busy,
1032
- onClick: loadMore
1033
- }, "\u52A0\u8F7D\u66F4\u591A") : null,
1034
- React.createElement("button", { type: "button", className: "dsh-recall-btn", disabled: state.busy, onClick: refresh }, "\u5237\u65B0"),
1035
- React.createElement("button", {
1036
- type: "button",
1037
- className: "dsh-recall-btn dsh-recall-btn-danger",
1038
- disabled: state.busy,
1039
- title: "\u5220\u9664\u5168\u90E8\u5DE5\u4F5C\u533A\u7684\u6240\u6709\u5FEB\u7167\uFF1B\u4F1A\u76F4\u63A5\u6838\u5BF9\u5E76\u5220\u9664 git tag\uFF08\u5373\u4F7F\u5217\u8868\u4E3A\u7A7A\u4E5F\u53EF\u6E05\u7406\u6B8B\u7559\uFF09",
1040
- onClick: () => setConfirming({ kind: "all" })
1041
- }, "\u5168\u90E8\u5220\u9664"),
1042
- React.createElement("button", {
1043
- type: "button",
1044
- className: "dsh-recall-btn",
1045
- disabled: state.busy,
1046
- title: "\u7ACB\u5373\u5BF9\u5168\u90E8\u5DE5\u4F5C\u533A\u6267\u884C\u4E00\u6B21 git gc\uFF08\u538B\u7F29\u5BF9\u8C61\u5E93\u91CA\u653E\u7A7A\u95F4\uFF09",
1047
- onClick: () => run("gc", {}, "gc \u5B8C\u6210")
1048
- }, "\u7ACB\u5373 gc")
1049
- ),
1050
- errors && errors.length > 0 ? React.createElement(
1051
- "div",
1052
- { className: "dsh-recall-ex-note", key: "errors" },
1053
- React.createElement(
1054
- "div",
1055
- { className: "dsh-recall-ex-status" },
1056
- "\u6700\u8FD1\u9519\u8BEF\uFF1A",
1057
- (showAllErrors ? errors : errors.slice(0, 5)).map((e, i) => React.createElement("div", { key: i, className: "dsh-recall-ex-note" }, clockText2(e.time) + " " + e.message))
1058
- ),
1059
- React.createElement(
1060
- "div",
1061
- { className: "dsh-recall-panel-actions" },
1062
- errors.length > 5 ? React.createElement("button", { type: "button", className: "dsh-recall-ex-chip", onClick: () => setShowAllErrors((v) => !v) }, showAllErrors ? "\u6536\u8D77" : "\u5C55\u5F00\u5168\u90E8 (" + errors.length + ")") : null,
1063
- React.createElement("button", { type: "button", className: "dsh-recall-ex-chip", onClick: clearErrors }, "\u6E05\u7A7A")
1064
- )
1065
- ) : null
1066
- );
1067
- }
1068
- function ExcludeFilesSection() {
1069
- const [files, setFiles] = React.useState(null);
1070
- const [error, setError] = React.useState("");
1071
- function load() {
1072
- api("exclude-get", {}).then((res) => {
1073
- if (res && res.ok) {
1074
- setFiles(res.files || []);
1075
- setError("");
1076
- return;
1077
- }
1078
- if (res && res.unsupported) {
1079
- setError("\u5F53\u524D\u5E73\u53F0\u4E0D\u652F\u6301\u5FEB\u7167\u529F\u80FD\uFF0C\u6392\u9664\u914D\u7F6E\u4E0D\u53EF\u7528\u3002");
1080
- return;
1081
- }
1082
- setError(res && (res.message || res.error) || "\u65E0\u6CD5\u8BFB\u53D6\u6392\u9664\u914D\u7F6E");
1083
- }).catch((e) => setError(String(e)));
1084
- }
1085
- React.useEffect(() => {
1086
- load();
1087
- }, []);
1088
- if (error) {
1089
- return React.createElement(
1090
- "div",
1091
- { className: "dsh-recall-ex-card" },
1092
- React.createElement("div", { className: "dsh-recall-ex-note" }, error),
1093
- React.createElement(
1094
- "div",
1095
- { className: "dsh-recall-panel-actions" },
1096
- React.createElement("button", { type: "button", className: "dsh-recall-btn", onClick: load }, "\u91CD\u8BD5")
1097
- )
1098
- );
1099
- }
1100
- if (files === null) {
1101
- return React.createElement("div", { className: "dsh-recall-ex-note" }, "\u6B63\u5728\u52A0\u8F7D\u6392\u9664\u914D\u7F6E\u2026");
1102
- }
1103
- if (!files.length) {
1104
- return React.createElement("div", { className: "dsh-recall-ex-note" }, "\u5C1A\u672A\u521B\u5EFA\u4EFB\u4F55\u5FEB\u7167\u5B58\u50A8\uFF1A\u5728\u4EFB\u610F\u5DE5\u4F5C\u533A\u53D1\u9001\u4E00\u6761\u6D88\u606F\u540E\uFF0C\u8FD9\u91CC\u4F1A\u51FA\u73B0\u53EF\u7F16\u8F91\u7684\u6392\u9664\u914D\u7F6E\u3002");
1105
- }
1106
- return React.createElement(
1107
- "div",
1108
- { className: "dsh-recall-ex-card" },
1109
- ...files.map((f) => React.createElement(ExcludeCard, { key: f.path, file: f }))
1110
- );
1111
- }
1112
- function ConfigForm() {
1113
- const [baseline, setBaseline] = React.useState(null);
1114
- const [draft, setDraft] = React.useState(null);
1115
- const [envLocks, setEnvLocks] = React.useState({});
1116
- const [overridden, setOverridden] = React.useState({});
1117
- const [writable, setWritable] = React.useState(true);
1118
- const [state, setState] = React.useState({ busy: false, message: "", error: false });
1119
- const [showAdvanced, setShowAdvanced] = React.useState(false);
1120
- function load() {
1121
- api("config-get", {}).then((res) => {
1122
- if (res && res.ok) {
1123
- const v = res.values || {};
1124
- const next = {
1125
- gcSnaps: String(v.gcSnaps == null ? "" : v.gcSnaps),
1126
- gcHours: String(v.gcHours == null ? "" : v.gcHours),
1127
- maxFileBytes: bytesToMb2(v.maxFileBytes),
1128
- maxSnapshotsPerWorkspace: String(v.maxSnapshotsPerWorkspace == null ? "" : v.maxSnapshotsPerWorkspace),
1129
- baseExcludes: Array.isArray(v.baseExcludes) ? v.baseExcludes.join("\n") : "",
1130
- refillDraft: v.refillDraft !== false,
1131
- snapshotEnabled: v.snapshotEnabled !== false,
1132
- archiveOriginal: v.archiveOriginal !== false,
1133
- retentionDays: String(v.retentionDays == null ? "" : v.retentionDays)
1134
- };
1135
- setDraft(next);
1136
- setBaseline(next);
1137
- setEnvLocks(res.envLocks || {});
1138
- setOverridden(res.overridden || {});
1139
- setWritable(res.writable !== false);
1140
- } else {
1141
- setState({ busy: false, message: res && (res.message || res.error) || "\u65E0\u6CD5\u8BFB\u53D6\u914D\u7F6E", error: true });
1142
- }
1143
- }).catch((e) => setState({ busy: false, message: String(e), error: true }));
1144
- }
1145
- React.useEffect(() => {
1146
- load();
1147
- }, []);
1148
- function edit(key, value) {
1149
- setDraft((d) => Object.assign({}, d, { [key]: value }));
1150
- }
1151
- function save() {
1152
- if (state.busy || !draft || !baseline) return;
1153
- const patch = {};
1154
- for (const key of ["gcSnaps", "gcHours", "maxFileBytes", "maxSnapshotsPerWorkspace", "baseExcludes", "refillDraft", "snapshotEnabled", "archiveOriginal", "retentionDays"]) {
1155
- if (draft[key] !== baseline[key]) patch[key] = draft[key];
1156
- }
1157
- if (!Object.keys(patch).length) {
1158
- setState({ busy: false, message: "\u6CA1\u6709\u4FEE\u6539", error: false });
1159
- return;
1160
- }
1161
- const clean = {};
1162
- if (patch.gcSnaps !== void 0) {
1163
- const n = parseInt(patch.gcSnaps, 10);
1164
- if (!Number.isFinite(n) || n < 1) {
1165
- setState({ busy: false, message: "\u5FEB\u7167\u6761\u6570\u9608\u503C\u5FC5\u987B\u662F >= 1 \u7684\u6574\u6570", error: true });
1166
- return;
1167
- }
1168
- clean.gcSnaps = n;
1169
- }
1170
- if (patch.gcHours !== void 0) {
1171
- const n = parseInt(patch.gcHours, 10);
1172
- if (!Number.isFinite(n) || n < 1) {
1173
- setState({ busy: false, message: "gc \u5C0F\u65F6\u9608\u503C\u5FC5\u987B\u662F >= 1 \u7684\u6574\u6570", error: true });
1174
- return;
1175
- }
1176
- clean.gcHours = n;
1177
- }
1178
- if (patch.maxFileBytes !== void 0) {
1179
- const mb = Number(patch.maxFileBytes);
1180
- if (!Number.isFinite(mb) || mb < 0.01) {
1181
- setState({ busy: false, message: "\u6587\u4EF6\u5927\u5C0F\u4E0A\u9650\u81F3\u5C11 0.01 MB", error: true });
1182
- return;
1183
- }
1184
- clean.maxFileBytes = Math.round(mb * 1048576);
1185
- }
1186
- if (patch.maxSnapshotsPerWorkspace !== void 0) {
1187
- const n = parseInt(patch.maxSnapshotsPerWorkspace, 10);
1188
- if (!Number.isFinite(n) || n < 0) {
1189
- setState({ busy: false, message: "\u5FEB\u7167\u603B\u91CF\u4E0A\u9650\u5FC5\u987B\u662F >= 0 \u7684\u6574\u6570\uFF080 \u8868\u793A\u4E0D\u9650\u5236\uFF09", error: true });
1190
- return;
1191
- }
1192
- clean.maxSnapshotsPerWorkspace = n;
1193
- }
1194
- if (patch.refillDraft !== void 0) clean.refillDraft = Boolean(patch.refillDraft);
1195
- if (patch.snapshotEnabled !== void 0) clean.snapshotEnabled = Boolean(patch.snapshotEnabled);
1196
- if (patch.archiveOriginal !== void 0) clean.archiveOriginal = Boolean(patch.archiveOriginal);
1197
- if (patch.retentionDays !== void 0) {
1198
- const n = parseInt(patch.retentionDays, 10);
1199
- if (!Number.isFinite(n) || n < 0) {
1200
- setState({ busy: false, message: "\u4FDD\u7559\u5929\u6570\u5FC5\u987B\u662F >= 0 \u7684\u6574\u6570\uFF080 \u8868\u793A\u4E0D\u542F\u7528\uFF09", error: true });
1201
- return;
1202
- }
1203
- clean.retentionDays = n;
1204
- }
1205
- if (patch.baseExcludes !== void 0) {
1206
- clean.baseExcludes = String(patch.baseExcludes).split("\n").map((l) => l.trim()).filter(Boolean);
1207
- }
1208
- setState({ busy: true, message: "\u4FDD\u5B58\u4E2D\u2026", error: false });
1209
- api("config-set", { patch: clean }).then((res) => {
1210
- if (res && res.ok) {
1211
- setState({ busy: false, message: "\u5DF2\u4FDD\u5B58\u5E76\u5373\u65F6\u751F\u6548", error: false });
1212
- load();
1213
- } else {
1214
- setState({ busy: false, message: res && (res.message || res.error) || "\u4FDD\u5B58\u5931\u8D25", error: true });
1215
- }
1216
- }).catch((e) => setState({ busy: false, message: String(e), error: true }));
1217
- }
1218
- function numRow(key, label, hint, opts) {
1219
- const locked = Boolean(envLocks && envLocks[key]);
1220
- const changed = Boolean(draft && baseline && draft[key] !== baseline[key]);
1221
- return React.createElement(
1222
- "div",
1223
- { className: "dsh-recall-cfg-row", key },
1224
- React.createElement(
1225
- "div",
1226
- { className: "dsh-recall-cfg-line" },
1227
- React.createElement("label", { className: "dsh-recall-cfg-label" }, label),
1228
- React.createElement("input", {
1229
- className: "dsh-recall-cfg-input",
1230
- type: "number",
1231
- value: draft ? draft[key] : "",
1232
- disabled: locked || !writable,
1233
- min: opts && opts.min,
1234
- step: opts && opts.step,
1235
- onChange: (e) => edit(key, e.target.value)
1236
- }),
1237
- opts && opts.suffix ? React.createElement("span", { className: "dsh-recall-cfg-tag" }, opts.suffix) : null,
1238
- changed && !locked ? React.createElement("span", { className: "dsh-recall-cfg-tag" }, "\u5DF2\u4FEE\u6539") : null,
1239
- overridden && overridden[key] !== void 0 ? React.createElement("span", { className: "dsh-recall-cfg-tag" }, "\u5DF2\u8986\u76D6") : null,
1240
- locked ? React.createElement("span", { className: "dsh-recall-cfg-tag" }, "\u73AF\u5883\u53D8\u91CF\u9501\u5B9A") : null
1241
- ),
1242
- React.createElement("div", { className: "dsh-recall-cfg-hint" }, hint)
1243
- );
1244
- }
1245
- function resetDefaults() {
1246
- if (state.busy || !writable) return;
1247
- setState({ busy: true, message: "\u6062\u590D\u9ED8\u8BA4\u4E2D\u2026", error: false });
1248
- api("config-reset", {}).then((res) => {
1249
- if (res && res.ok) {
1250
- load();
1251
- setState({ busy: false, message: "\u5DF2\u6062\u590D\u9ED8\u8BA4\u503C", error: false });
1252
- } else {
1253
- setState({ busy: false, message: res && (res.message || res.error) || "\u6062\u590D\u9ED8\u8BA4\u5931\u8D25", error: true });
1254
- }
1255
- }).catch((e) => setState({ busy: false, message: String(e), error: true }));
1256
- }
1257
- if (!draft) {
1258
- return React.createElement("div", { className: "dsh-recall-ex-note" }, state.message || "\u6B63\u5728\u8BFB\u53D6\u914D\u7F6E\u2026");
1259
- }
1260
- return React.createElement(
1261
- "div",
1262
- { className: "dsh-recall-ex-card" },
1263
- React.createElement(
1264
- "div",
1265
- { className: "dsh-recall-cfg-row", key: "snapshotEnabled" },
1266
- React.createElement(
1267
- "div",
1268
- { className: "dsh-recall-cfg-line" },
1269
- React.createElement("label", { className: "dsh-recall-cfg-label", htmlFor: "dsh-recall-cfg-snapshot" }, "\u542F\u7528\u5FEB\u7167"),
1270
- React.createElement("input", {
1271
- id: "dsh-recall-cfg-snapshot",
1272
- type: "checkbox",
1273
- checked: Boolean(draft.snapshotEnabled),
1274
- disabled: !writable,
1275
- onChange: (e) => edit("snapshotEnabled", e.target.checked)
1276
- }),
1277
- draft.snapshotEnabled !== baseline.snapshotEnabled ? React.createElement("span", { className: "dsh-recall-cfg-tag" }, "\u5DF2\u4FEE\u6539") : null,
1278
- overridden && overridden.snapshotEnabled !== void 0 ? React.createElement("span", { className: "dsh-recall-cfg-tag" }, "\u5DF2\u8986\u76D6") : null
1279
- ),
1280
- React.createElement("div", { className: "dsh-recall-cfg-hint" }, "\u5173\u95ED\u540E\u4E0D\u518D\u65B0\u5EFA\u5FEB\u7167\uFF08\u5DF2\u6709\u5FEB\u7167\u4ECD\u53EF\u64A4\u56DE\uFF09\uFF0C\u9002\u5408\u4E34\u65F6\u7981\u7528\u5FEB\u7167\u7684\u573A\u5408")
1281
- ),
1282
- numRow("gcSnaps", "gc \u89E6\u53D1\u6761\u6570", "\u6BCF\u79EF\u7D2F\u591A\u5C11\u6761\u5FEB\u7167\u89E6\u53D1\u4E00\u6B21 git gc", { min: 1, step: 1 }),
1283
- numRow("gcHours", "gc \u89E6\u53D1\u5C0F\u65F6", "\u8DDD\u4E0A\u6B21 gc \u8D85\u8FC7\u591A\u5C11\u5C0F\u65F6\u89E6\u53D1\uFF08\u4E0E\u6761\u6570\u5148\u5230\u5148\u89E6\u53D1\uFF09", { min: 1, step: 1 }),
1284
- numRow("maxFileBytes", "\u6587\u4EF6\u5927\u5C0F\u4E0A\u9650", "\u8D85\u8FC7\u8BE5\u5927\u5C0F\u7684\u6587\u4EF6\u4E0D\u8FDB\u5FEB\u7167\u3001\u4E0D\u88AB\u56DE\u9000\u89E6\u78B0\uFF08\u5355\u4F4D MB\uFF0C\u652F\u6301\u5C0F\u6570\uFF09", { suffix: "MB", min: 0.01, step: 0.5 }),
1285
- numRow("maxSnapshotsPerWorkspace", "\u5FEB\u7167\u603B\u91CF\u4E0A\u9650", "\u6BCF\u4E2A\u5DE5\u4F5C\u533A\u4FDD\u7559\u7684\u6700\u5927\u5FEB\u7167\u6570\uFF0C\u8D85\u9650\u81EA\u52A8\u5220\u9664\u6700\u65E7\u7684\uFF1B\u586B 0 \u8868\u793A\u4E0D\u9650\u5236", { min: 0, step: 1 }),
1286
- numRow("retentionDays", "\u5FEB\u7167\u4FDD\u7559\u5929\u6570", "\u6309\u5929\u6570\u4FDD\u7559\u5FEB\u7167\uFF0C\u8D85\u671F\u81EA\u52A8\u5220\u9664\u6700\u65E7\u7684\uFF1B\u586B 0 \u8868\u793A\u4E0D\u542F\u7528\uFF08\u4E0E\u5FEB\u7167\u603B\u6570\u4E0A\u9650\u5404\u81EA\u751F\u6548\uFF09", { min: 0, step: 1 }),
1287
- React.createElement(
1288
- "div",
1289
- { className: "dsh-recall-cfg-row", key: "refillDraft" },
1290
- React.createElement(
1291
- "div",
1292
- { className: "dsh-recall-cfg-line" },
1293
- React.createElement("label", { className: "dsh-recall-cfg-label", htmlFor: "dsh-recall-cfg-refill" }, "\u64A4\u56DE\u540E\u56DE\u586B\u8F93\u5165\u6846"),
1294
- React.createElement("input", {
1295
- id: "dsh-recall-cfg-refill",
1296
- type: "checkbox",
1297
- checked: Boolean(draft.refillDraft),
1298
- disabled: !writable,
1299
- onChange: (e) => edit("refillDraft", e.target.checked)
1300
- }),
1301
- draft.refillDraft !== baseline.refillDraft ? React.createElement("span", { className: "dsh-recall-cfg-tag" }, "\u5DF2\u4FEE\u6539") : null,
1302
- overridden && overridden.refillDraft !== void 0 ? React.createElement("span", { className: "dsh-recall-cfg-tag" }, "\u5DF2\u8986\u76D6") : null
1303
- ),
1304
- React.createElement("div", { className: "dsh-recall-cfg-hint" }, "\u64A4\u56DE\u6210\u529F\u540E\u628A\u88AB\u64A4\u56DE\u7684\u6D88\u606F\u6587\u672C\u56DE\u586B\u5230\u8F93\u5165\u6846\uFF0C\u65B9\u4FBF\u4FEE\u6539\u540E\u91CD\u65B0\u53D1\u9001")
1305
- ),
1306
- React.createElement(
1307
- "div",
1308
- { className: "dsh-recall-cfg-row", key: "archiveOriginal" },
1309
- React.createElement(
1310
- "div",
1311
- { className: "dsh-recall-cfg-line" },
1312
- React.createElement("label", { className: "dsh-recall-cfg-label", htmlFor: "dsh-recall-cfg-archive" }, "\u64A4\u56DE\u540E\u5F52\u6863\u539F\u4F1A\u8BDD"),
1313
- React.createElement("input", {
1314
- id: "dsh-recall-cfg-archive",
1315
- type: "checkbox",
1316
- checked: Boolean(draft.archiveOriginal),
1317
- disabled: !writable,
1318
- onChange: (e) => edit("archiveOriginal", e.target.checked)
1319
- }),
1320
- draft.archiveOriginal !== baseline.archiveOriginal ? React.createElement("span", { className: "dsh-recall-cfg-tag" }, "\u5DF2\u4FEE\u6539") : null,
1321
- overridden && overridden.archiveOriginal !== void 0 ? React.createElement("span", { className: "dsh-recall-cfg-tag" }, "\u5DF2\u8986\u76D6") : null
1322
- ),
1323
- React.createElement("div", { className: "dsh-recall-cfg-hint" }, "\u64A4\u56DE\u540E\u539F\u4F1A\u8BDD\u4ECE\u5217\u8868\u5F52\u6863\u9690\u85CF\uFF08\u53EF\u4ECE\u5F52\u6863\u627E\u56DE\uFF09\uFF1B\u5173\u95ED\u5219\u4FDD\u7559\u5728\u5217\u8868\u4E2D\uFF0C\u65B9\u4FBF\u5BF9\u7167\u56DE\u9000\u524D\u540E\u7684\u4E0A\u4E0B\u6587")
1324
- ),
1325
- React.createElement(SectionToggle, { title: "\u9AD8\u7EA7\uFF1A\u57FA\u7840\u6392\u9664\u8868", open: showAdvanced, onToggle: () => setShowAdvanced((v) => !v) }),
1326
- showAdvanced ? React.createElement(
1327
- "div",
1328
- { className: "dsh-recall-cfg-row", key: "baseExcludes" },
1329
- React.createElement(
1330
- "div",
1331
- { className: "dsh-recall-cfg-line" },
1332
- React.createElement("label", { className: "dsh-recall-cfg-label" }, "\u57FA\u7840\u6392\u9664\u8868"),
1333
- draft.baseExcludes !== baseline.baseExcludes ? React.createElement("span", { className: "dsh-recall-cfg-tag" }, "\u5DF2\u4FEE\u6539") : null,
1334
- overridden && overridden.baseExcludes !== void 0 ? React.createElement("span", { className: "dsh-recall-cfg-tag" }, "\u5DF2\u8986\u76D6") : null
1335
- ),
1336
- React.createElement("textarea", {
1337
- className: "dsh-recall-cfg-area",
1338
- rows: 4,
1339
- value: draft.baseExcludes,
1340
- disabled: !writable,
1341
- onChange: (e) => edit("baseExcludes", e.target.value)
1342
- }),
1343
- React.createElement("div", { className: "dsh-recall-cfg-hint" }, "\u5185\u7F6E\u89C4\u5219\uFF0C\u6BCF\u4E2A\u5DE5\u4F5C\u533A\u5171\u4EAB\uFF0C\u5EFA\u8BAE\u4FDD\u6301\u9ED8\u8BA4\uFF1Bgitignore \u8BED\u6CD5\u6BCF\u884C\u4E00\u6761\uFF0C\u4F18\u5148\u7EA7\u4F4E\u4E8E\u300C\u6392\u9664\u914D\u7F6E\u300D\u91CC\u7684 exclude.txt\uFF08S3-2 \u6298\u53E0\uFF09")
1344
- ) : null,
1345
- React.createElement(
1346
- "div",
1347
- { className: "dsh-recall-panel-actions" },
1348
- state.message ? React.createElement("span", { className: "dsh-recall-ex-status" + (state.error ? " dsh-recall-ex-status-error" : " dsh-recall-ex-status-success") }, state.message) : null,
1349
- React.createElement("button", { type: "button", className: "dsh-recall-btn", disabled: state.busy || !writable, onClick: () => setDraft(Object.assign({}, baseline)) }, "\u653E\u5F03\u4FEE\u6539"),
1350
- React.createElement("button", {
1351
- type: "button",
1352
- className: "dsh-recall-btn",
1353
- disabled: state.busy || !writable,
1354
- title: "\u628A\u6240\u6709\u5B57\u6BB5\u6062\u590D\u5230\u63D2\u4EF6\u51FA\u5382\u9ED8\u8BA4\u503C",
1355
- onClick: resetDefaults
1356
- }, "\u6062\u590D\u9ED8\u8BA4"),
1357
- React.createElement("button", { type: "button", className: "dsh-recall-btn", disabled: state.busy || !writable, onClick: save }, "\u4FDD\u5B58"),
1358
- !writable ? React.createElement("span", { className: "dsh-recall-cfg-tag" }, "\u53EA\u8BFB\u8BBE\u7F6E\u6E90") : null
1359
- )
1360
- );
1361
- }
1362
- function SectionToggle(props) {
1363
- return React.createElement(
1364
- "button",
1365
- {
1366
- type: "button",
1367
- className: "dsh-recall-cardbtn",
1368
- "aria-expanded": props.open,
1369
- onClick: props.onToggle
1370
- },
1371
- React.createElement("span", { className: "dsh-recall-tree-toggle" }, props.open ? "\u25BE" : "\u25B8"),
1372
- React.createElement("span", { style: { fontWeight: 600, fontSize: "14px", lineHeight: "22px" } }, props.title),
1373
- props.meta ? React.createElement("span", { className: "dsh-recall-tree-meta" }, props.meta) : null
1374
- );
1375
- }
1376
- function RecallSettingsCard() {
1377
- const [open, setOpen] = React.useState(false);
1378
- const [sections, setSections] = React.useState({ exclude: false, manage: false });
1379
- function toggle(key) {
1380
- setSections((prev) => Object.assign({}, prev, { [key]: !prev[key] }));
1381
- }
1382
- return React.createElement(
1383
- "li",
1384
- { className: "dsh-recall-card" + (open ? " dsh-recall-card-open" : "") },
1385
- React.createElement(
1386
- "button",
1387
- {
1388
- type: "button",
1389
- className: "dsh-recall-cardbtn",
1390
- "aria-expanded": open,
1391
- "aria-label": (open ? "\u6536\u8D77" : "\u5C55\u5F00") + ": \u64A4\u56DE\u63D2\u4EF6",
1392
- onClick: () => setOpen((v) => !v)
1393
- },
1394
- React.createElement(
1395
- "span",
1396
- { className: "dsh-recall-card-head" },
1397
- React.createElement("span", { className: "dsh-recall-card-name" }, "\u64A4\u56DE\u63D2\u4EF6"),
1398
- React.createElement("span", { className: "dsh-recall-card-desc" }, "\u6D88\u606F\u64A4\u56DE\uFF08\u6587\u4EF6\u5FEB\u7167 + \u5BF9\u8BDD\u56DE\u9000\uFF09\u7684\u9608\u503C\u4E0E\u6CBB\u7406")
1399
- ),
1400
- React.createElement("svg", {
1401
- width: 14,
1402
- height: 14,
1403
- viewBox: "0 0 16 16",
1404
- style: { color: "var(--dsw-alias-label-tertiary)", flex: "none", transition: "transform .16s", transform: open ? "rotate(180deg)" : "none" }
1405
- }, React.createElement("path", { d: "M4 6l4 4 4-4", fill: "none", stroke: "currentColor", strokeWidth: 1.5, strokeLinecap: "round", strokeLinejoin: "round" }))
1406
- ),
1407
- open ? React.createElement(
1408
- "div",
1409
- { className: "dsh-recall-card-body" },
1410
- React.createElement(ConfigForm),
1411
- React.createElement(SectionToggle, { title: "\u6392\u9664\u914D\u7F6E\uFF08exclude.txt\uFF09", open: sections.exclude, onToggle: () => toggle("exclude") }),
1412
- sections.exclude ? React.createElement(ExcludeFilesSection) : null,
1413
- React.createElement(SectionToggle, { title: "\u5FEB\u7167\u7BA1\u7406", open: sections.manage, onToggle: () => toggle("manage") }),
1414
- sections.manage ? React.createElement(ManageCard) : null
1415
- ) : null
1416
- );
1417
- }
1418
- return { RecallSettingsCard };
1419
- }
1420
-
1421
- // src/client/app.js
1422
- function createApp(React) {
1423
- return function apply(ctx) {
1424
- const slots = ctx.get("slots");
1425
- if (!slots) return;
1426
- const sessionsSvc = ctx.get("sessions");
1427
- const workspacesSvc = ctx.get("workspaces");
1428
- const stylesSvc = ctx.get("styles");
1429
- if (stylesSvc && typeof stylesSvc.insert === "function") {
1430
- stylesSvc.insert(CSS);
1431
- } else if (typeof document !== "undefined") {
1432
- const tag = document.createElement("style");
1433
- tag.setAttribute("data-plugin", "dsh-recall-plugin");
1434
- tag.textContent = CSS;
1435
- document.head.appendChild(tag);
1436
- }
1437
- const util = buildUtil();
1438
- const { UserRecallNode } = buildRecallNode(React, util, ctx, sessionsSvc, workspacesSvc);
1439
- const { RecallSettingsCard } = buildSettingsCards(React, util, sessionsSvc);
1440
- for (const slotKey of ["user", "steering"]) {
1441
- let mounted = false;
1442
- for (let priority = -1; priority >= -3 && !mounted; priority--) {
1443
- try {
1444
- slots.inject("conversation.chat.node", () => slots.register(
1445
- { name: "conversation.chat.node", key: slotKey, priority },
1446
- UserRecallNode
1447
- ));
1448
- mounted = true;
1449
- } catch (error) {
1450
- if (priority === -3) console.error("[dsh-recall-plugin] slot register failed (" + slotKey + "):", error);
1451
- }
1452
- }
1453
- }
1454
- try {
1455
- slots.inject("settings.plugin.item", () => slots.register(
1456
- { name: "settings.plugin.item", key: "dsh-recall" },
1457
- RecallSettingsCard
1458
- ));
1459
- } catch (error) {
1460
- console.error("[dsh-recall-plugin] settings card register failed:", error);
1461
- }
1462
- };
1463
- }
1464
-
1465
- // src/client/entry.js
1466
- window.__ModuleLoader__.load({
1467
- id: "dsh-recall-plugin",
1468
- factory: (require2) => {
1469
- const React = require2("react");
1470
- return { name: "dsh-recall-plugin", apply: createApp(React) };
1471
- }
1472
- });
1
+ // src/client/css.js
2
+ var CSS = [
3
+ ".dsh-recall-row{flex-direction:column;align-items:flex-end;gap:6px;display:flex}",
4
+ ".dsh-recall-stack{flex-direction:column;align-items:flex-end;gap:8px;min-width:0;max-width:min(525px,82%);display:flex}",
5
+ ".dsh-recall-bubble{background:var(--dsw-specific-bubble);max-width:100%;color:var(--dsw-alias-label-primary);border-radius:22px;padding:10px 16px;font-size:16px;line-height:24px;white-space:pre-wrap;word-break:break-word}",
6
+ ".dsh-recall-json{margin:0;max-width:100%;font-size:12px;line-height:18px;color:var(--dsw-alias-label-tertiary);white-space:pre-wrap;word-break:break-word;border:1px solid var(--dsw-alias-border-l1);border-radius:10px;padding:8px 10px;background:var(--dsw-alias-markdown-code-block)}",
7
+ ".dsh-recall-actions{align-items:center;gap:10px;height:28px;display:flex}",
8
+ ".dsh-recall-time{color:var(--dsw-alias-label-tertiary);white-space:nowrap;padding-right:12px;font-size:14px;line-height:24px}",
9
+ ".dsh-recall-action{width:28px;height:28px;color:var(--dsw-alias-label-tertiary);cursor:pointer;background:0 0;border:none;border-radius:28px;justify-content:center;align-items:center;padding:6px;display:inline-flex}",
10
+ ".dsh-recall-action:hover{background:var(--dsw-alias-interactive-bg-hover);color:var(--dsw-alias-label-secondary)}",
11
+ "@media (hover:hover){[data-time-hover-root] .dsh-recall-time{opacity:0;transition:opacity 80ms}[data-time-hover-root]:hover .dsh-recall-time,[data-time-hover-root]:focus-within .dsh-recall-time{opacity:1}}",
12
+ ".dsh-recall-panel{width:min(480px,100%);box-sizing:border-box;background:var(--dsw-alias-bg-base);border:1px solid var(--dsw-alias-border-l1);border-radius:12px;padding:12px 14px;display:flex;flex-direction:column;gap:8px;text-align:left;box-shadow:0 8px 28px rgba(0,0,0,.22)}",
13
+ ".dsh-recall-panel-title{color:var(--dsw-alias-label-primary);font-size:14px;font-weight:600;line-height:22px}",
14
+ ".dsh-recall-panel-note{color:var(--dsw-alias-label-secondary);font-size:13px;line-height:20px;word-break:break-word}",
15
+ ".dsh-recall-list{max-height:220px;overflow:auto;display:flex;flex-direction:column;gap:2px;padding:4px 0}",
16
+ ".dsh-recall-file{display:flex;gap:8px;align-items:baseline;font-size:12px;line-height:20px}",
17
+ ".dsh-recall-badge{flex:none;font-size:11px;line-height:18px;padding:0 6px;border-radius:6px}",
18
+ ".dsh-recall-badge-modified{color:var(--dsw-alias-state-error-primary);background:var(--dsw-alias-interactive-bg-hover)}",
19
+ ".dsh-recall-badge-restored{color:var(--dsw-alias-label-secondary);background:var(--dsw-alias-interactive-bg-hover)}",
20
+ ".dsh-recall-badge-added{color:var(--dsw-alias-label-tertiary);background:var(--dsw-alias-interactive-bg-hover)}",
21
+ ".dsh-recall-rel{min-width:0;color:var(--dsw-alias-label-primary);word-break:break-all;font-family:var(--dsw-font-code, ui-monospace, SFMono-Regular, Consolas, monospace)}",
22
+ ".dsh-recall-panel-actions{display:flex;gap:8px;justify-content:flex-end;margin-top:2px}",
23
+ ".dsh-recall-btn{border:none;border-radius:8px;padding:5px 14px;font-size:13px;line-height:20px;cursor:pointer;color:var(--dsw-alias-label-secondary);background:var(--dsw-alias-interactive-bg-hover)}",
24
+ ".dsh-recall-btn:hover{color:var(--dsw-alias-label-primary)}",
25
+ ".dsh-recall-btn-danger{background:var(--dsw-alias-state-error-primary);color:#fff}",
26
+ ".dsh-recall-btn-danger:hover{color:#fff;filter:brightness(1.08)}",
27
+ ".dsh-recall-toast{position:fixed;top:18px;left:50%;transform:translateX(-50%);z-index:10000;max-width:min(560px,86vw);box-sizing:border-box;background:var(--dsw-alias-bg-base);color:var(--dsw-alias-label-primary);border:1px solid var(--dsw-alias-border-l2);border-radius:12px;padding:10px 16px;font-size:13px;line-height:20px;box-shadow:0 8px 28px rgba(0,0,0,.22);display:flex;align-items:baseline;gap:8px;opacity:0;transition:opacity .25s ease;pointer-events:auto}",
28
+ ".dsh-recall-toast.dsh-recall-toast-in{opacity:1}",
29
+ ".dsh-recall-toast-tag{flex:none;font-weight:600;color:var(--dsw-alias-state-error-primary)}",
30
+ ".dsh-recall-ex-card{display:flex;flex-direction:column;gap:8px}",
31
+ ".dsh-recall-ex-title{color:var(--dsw-alias-label-primary);font-size:14px;font-weight:600;line-height:22px}",
32
+ ".dsh-recall-ex-note{color:var(--dsw-alias-label-secondary);font-size:13px;line-height:20px;word-break:break-word}",
33
+ ".dsh-recall-ex-area{width:100%;box-sizing:border-box;background:var(--dsw-alias-bg-base);color:var(--dsw-alias-label-primary);border:1px solid var(--dsw-alias-border-l1);border-radius:10px;padding:8px 10px;font-size:12px;line-height:20px;font-family:var(--dsw-font-code, ui-monospace, SFMono-Regular, Consolas, monospace);resize:vertical;min-height:120px}",
34
+ ".dsh-recall-ex-quick{display:flex;flex-wrap:wrap;gap:8px;align-items:center}",
35
+ ".dsh-recall-ex-input{flex:1;min-width:180px;box-sizing:border-box;background:var(--dsw-alias-bg-base);color:var(--dsw-alias-label-primary);border:1px solid var(--dsw-alias-border-l1);border-radius:8px;padding:5px 10px;font-size:13px;line-height:20px}",
36
+ ".dsh-recall-ex-chip{border:none;border-radius:6px;padding:2px 8px;font-size:12px;line-height:18px;cursor:pointer;color:var(--dsw-alias-label-secondary);background:var(--dsw-alias-interactive-bg-hover)}",
37
+ ".dsh-recall-ex-chip:hover{color:var(--dsw-alias-label-primary)}",
38
+ ".dsh-recall-ex-status{margin-right:auto;font-size:12px;line-height:20px;color:var(--dsw-alias-label-tertiary)}",
39
+ ".dsh-recall-ex-status-error{color:var(--dsw-alias-state-error-primary)}",
40
+ ".dsh-recall-ex-status-success{color:var(--dsw-alias-state-success-primary)}",
41
+ ".dsh-recall-tree{display:flex;flex-direction:column;gap:2px;padding:4px 0}",
42
+ ".dsh-recall-tree-node{display:flex;flex-direction:column;gap:1px}",
43
+ ".dsh-recall-tree-row{display:flex;gap:6px;align-items:center;min-width:0;padding:2px 4px;border-radius:6px;cursor:default}",
44
+ ".dsh-recall-tree-row:hover{background:var(--dsw-alias-interactive-bg-hover)}",
45
+ ".dsh-recall-tree-toggle{flex:none;width:18px;height:18px;display:inline-flex;align-items:center;justify-content:center;color:var(--dsw-alias-label-tertiary);cursor:pointer;border-radius:4px;font-size:11px;line-height:18px;user-select:none}",
46
+ ".dsh-recall-tree-toggle:hover{color:var(--dsw-alias-label-primary);background:var(--dsw-alias-interactive-bg-hover)}",
47
+ ".dsh-recall-tree-toggle-placeholder{flex:none;width:18px;height:18px}",
48
+ ".dsh-recall-tree-label{flex:1;min-width:0;display:flex;gap:8px;align-items:baseline;font-size:12px;line-height:20px;overflow:hidden}",
49
+ ".dsh-recall-tree-name{flex:none;font-weight:600;color:var(--dsw-alias-label-secondary)}",
50
+ ".dsh-recall-tree-title{min-width:0;color:var(--dsw-alias-label-primary);white-space:nowrap;overflow:hidden;text-overflow:ellipsis}",
51
+ ".dsh-recall-tree-meta{flex:none;font-size:11px;line-height:18px;color:var(--dsw-alias-label-tertiary);white-space:nowrap}",
52
+ ".dsh-recall-tree-children{display:flex;flex-direction:column;gap:1px;margin-left:16px;border-left:1px solid var(--dsw-alias-border-l1);padding-left:8px}",
53
+ ".dsh-recall-tree-confirm{display:flex;gap:8px;align-items:center;font-size:12px;line-height:20px;color:var(--dsw-alias-label-secondary)}",
54
+ ".dsh-recall-card{border:1px solid var(--dsw-alias-border-l2);background:var(--dsw-alias-bg-layer-3);border-radius:12px;list-style:none;transition:border-color .16s,background .16s;display:flex;flex-direction:column;text-align:left}",
55
+ ".dsh-recall-card.dsh-recall-card-open{background:var(--dsw-alias-bg-layer-2);border-color:var(--dsw-alias-label-dimmed)}",
56
+ ".dsh-recall-card-head{flex-direction:column;flex:1;gap:4px;min-width:0;display:flex}",
57
+ ".dsh-recall-card-name{color:var(--dsw-alias-label-primary);font-size:15px;font-weight:600;line-height:1.4}",
58
+ ".dsh-recall-card-desc{color:var(--dsw-alias-label-tertiary);font-size:13px;line-height:1.5}",
59
+ ".dsh-recall-card-body{border-top:1px solid var(--dsw-alias-border-l2);margin:0 16px;padding:12px 0 8px;display:flex;flex-direction:column;gap:12px}",
60
+ ".dsh-recall-cardbtn{appearance:none;width:100%;font:inherit;color:inherit;text-align:left;cursor:pointer;background:0 0;border:0;border-radius:12px;align-items:center;gap:12px;padding:14px 16px;display:flex}",
61
+ ".dsh-recall-cfg-row{display:flex;flex-direction:column;gap:4px}",
62
+ ".dsh-recall-cfg-line{display:flex;align-items:center;gap:8px}",
63
+ ".dsh-recall-cfg-label{flex:none;width:130px;color:var(--dsw-alias-label-secondary);font-size:13px;line-height:20px}",
64
+ ".dsh-recall-cfg-input{flex:1;min-width:0;box-sizing:border-box;font:inherit;font-size:13px;color:var(--dsw-alias-label-primary);background:var(--dsw-alias-bg-base);border:1px solid var(--dsw-alias-border-l2);border-radius:8px;padding:4px 8px}",
65
+ ".dsh-recall-cfg-input:disabled{opacity:.5}",
66
+ ".dsh-recall-cfg-area{font-family:inherit;font-size:12px;line-height:18px;color:var(--dsw-alias-label-primary);background:var(--dsw-alias-bg-base);border:1px solid var(--dsw-alias-border-l2);border-radius:8px;padding:6px 8px;min-height:64px}",
67
+ ".dsh-recall-cfg-hint{color:var(--dsw-alias-label-tertiary);font-size:12px;line-height:18px;padding-left:138px}",
68
+ ".dsh-recall-cfg-tag{flex:none;font-size:11px;line-height:16px;padding:1px 6px;border-radius:6px;background:var(--dsw-alias-interactive-bg-hover);color:var(--dsw-alias-label-tertiary)}"
69
+ ].join("");
70
+
71
+ // src/client/util.js
72
+ function clockText(ms) {
73
+ try {
74
+ if (!ms || isNaN(new Date(ms).getTime())) return "";
75
+ const d = new Date(ms);
76
+ const now = /* @__PURE__ */ new Date();
77
+ const sameDay = d.getFullYear() === now.getFullYear() && d.getMonth() === now.getMonth() && d.getDate() === now.getDate();
78
+ const hh = String(d.getHours()).padStart(2, "0");
79
+ const mm = String(d.getMinutes()).padStart(2, "0");
80
+ return sameDay ? hh + ":" + mm : d.getMonth() + 1 + "/" + d.getDate() + " " + hh + ":" + mm;
81
+ } catch (e) {
82
+ return "";
83
+ }
84
+ }
85
+ function sizeText(bytes) {
86
+ if (!bytes || bytes <= 0) return "0 MB";
87
+ if (bytes < 1048576) return (bytes / 1024).toFixed(0) + " KB";
88
+ if (bytes < 1073741824) return (bytes / 1048576).toFixed(1) + " MB";
89
+ return (bytes / 1073741824).toFixed(2) + " GB";
90
+ }
91
+ function bytesToMb(bytes) {
92
+ const n = Number(bytes);
93
+ if (!Number.isFinite(n) || n <= 0) return "";
94
+ return String(Math.round(n / 1048576 * 100) / 100);
95
+ }
96
+ function buildTree(list) {
97
+ const workspaces = /* @__PURE__ */ new Map();
98
+ for (const it of list || []) {
99
+ const rootKey = it.root || "unknown-root";
100
+ if (!workspaces.has(rootKey)) workspaces.set(rootKey, { root: it.root || null, name: it.workspace || "\u672A\u77E5\u5DE5\u4F5C\u533A", sessions: /* @__PURE__ */ new Map() });
101
+ const ws = workspaces.get(rootKey);
102
+ const sidKey = it.sessionId || "unknown-session";
103
+ if (!ws.sessions.has(sidKey)) ws.sessions.set(sidKey, { root: ws.root, sessionId: it.sessionId || null, title: it.sessionTitle || null, items: [] });
104
+ ws.sessions.get(sidKey).items.push(it);
105
+ }
106
+ const wsList = Array.from(workspaces.values());
107
+ wsList.sort((a, b) => (a.name || "").localeCompare(b.name || ""));
108
+ for (const ws of wsList) {
109
+ ws.sessions = Array.from(ws.sessions.values());
110
+ ws.sessions.sort((a, b) => (a.title || "").localeCompare(b.title || ""));
111
+ for (const s of ws.sessions) s.items.sort((a, b) => (b.time || 0) - (a.time || 0));
112
+ }
113
+ return wsList;
114
+ }
115
+ function buildUtil() {
116
+ function api(name, args) {
117
+ return fetch("/api/recall/" + name, {
118
+ method: "POST",
119
+ headers: { "content-type": "application/json" },
120
+ body: JSON.stringify(args || {})
121
+ }).then((r) => r.json());
122
+ }
123
+ const CODE_TEXT = {
124
+ STALE: "\u9884\u89C8\u540E\u9879\u76EE\u6587\u4EF6\u53D1\u751F\u4E86\u53D8\u5316\uFF0C\u8BF7\u91CD\u65B0\u9884\u89C8\u786E\u8BA4",
125
+ AGENT_BUSY: "Agent \u6B63\u5728\u8FD0\u884C\u4E2D\uFF0C\u8BF7\u5148\u505C\u6B62\u540E\u518D\u64A4\u56DE",
126
+ NO_SNAPSHOT: "\u8BE5\u6D88\u606F\u6CA1\u6709\u53EF\u7528\u7684\u9879\u76EE\u5FEB\u7167",
127
+ NO_STORE: "\u5FEB\u7167\u5B58\u50A8\u4E0D\u53EF\u7528",
128
+ ROLLBACK_FAILED: "\u56DE\u9000\u5931\u8D25"
129
+ };
130
+ function messageFor(res, fallback) {
131
+ if (!res) return fallback;
132
+ const code = res && res.code;
133
+ if (code && CODE_TEXT[code]) return CODE_TEXT[code];
134
+ return res && (res.message || res.error) || fallback;
135
+ }
136
+ const noticeShown = /* @__PURE__ */ new Set();
137
+ function mountToast(text) {
138
+ if (typeof document === "undefined") return;
139
+ try {
140
+ let dismiss = function() {
141
+ if (dismissed) return;
142
+ dismissed = true;
143
+ clearTimeout(timer);
144
+ el.classList.remove("dsh-recall-toast-in");
145
+ setTimeout(() => el.remove(), 300);
146
+ };
147
+ const el = document.createElement("div");
148
+ el.className = "dsh-recall-toast";
149
+ const tag = document.createElement("span");
150
+ tag.className = "dsh-recall-toast-tag";
151
+ tag.textContent = "\u64A4\u56DE\u63D2\u4EF6";
152
+ const body = document.createElement("span");
153
+ body.textContent = text;
154
+ el.appendChild(tag);
155
+ el.appendChild(body);
156
+ el.addEventListener("click", () => dismiss(), { once: true });
157
+ document.body.appendChild(el);
158
+ requestAnimationFrame(() => el.classList.add("dsh-recall-toast-in"));
159
+ const timer = setTimeout(dismiss, 7e3);
160
+ let dismissed = false;
161
+ } catch (e) {
162
+ }
163
+ }
164
+ function showNotice(kind, text) {
165
+ if (noticeShown.has(kind)) return;
166
+ noticeShown.add(kind);
167
+ mountToast(text);
168
+ }
169
+ const toastLastShown = /* @__PURE__ */ new Map();
170
+ function showThrottledToast(text) {
171
+ const key = String(text).slice(0, 80);
172
+ const now = Date.now();
173
+ if (now - (toastLastShown.get(key) || 0) < 10 * 60 * 1e3) return;
174
+ if (toastLastShown.size > 50) toastLastShown.clear();
175
+ toastLastShown.set(key, now);
176
+ mountToast(text);
177
+ }
178
+ const pluginConfig = { refillDraft: true, archiveOriginal: true };
179
+ const initMap = /* @__PURE__ */ new Map();
180
+ function ensureInit(sessionId) {
181
+ if (!sessionId) return Promise.resolve();
182
+ const cached = initMap.get(sessionId);
183
+ if (cached) return cached;
184
+ const done = api("init", { sessionId }).then((res) => {
185
+ if (res && res.config && typeof res.config === "object") {
186
+ if (typeof res.config.refillDraft === "boolean") pluginConfig.refillDraft = res.config.refillDraft;
187
+ if (typeof res.config.archiveOriginal === "boolean") pluginConfig.archiveOriginal = res.config.archiveOriginal;
188
+ }
189
+ const notice = res && res.notice;
190
+ if (notice && notice.unsupported) {
191
+ showNotice("unsupported", "\u64A4\u56DE\u63D2\u4EF6\u4EC5\u652F\u6301 Windows / Linux / macOS\uFF0C\u5F53\u524D\u5E73\u53F0\u7684\u5FEB\u7167\u4E0D\u53EF\u7528\u3002");
192
+ }
193
+ if (notice && notice.gitMissing) {
194
+ showNotice("git", "\u672A\u68C0\u6D4B\u5230 git CLI\uFF0C\u64A4\u56DE\u529F\u80FD\u4E0D\u53EF\u7528\uFF08\u5FEB\u7167\u5F15\u64CE\u4F9D\u8D56 git\uFF09\u3002\u5B89\u88C5 git \u5E76\u91CD\u542F DSH \u540E\u5373\u53EF\u4F7F\u7528\u3002");
195
+ }
196
+ if (notice && notice.homeFallback) {
197
+ showNotice("home", "home \u76EE\u5F55\u4E0D\u53EF\u5199\uFF0C\u5FEB\u7167\u5DF2\u964D\u7EA7\u5B58\u50A8\u5230\u9879\u76EE\u5185 .dsh-recall-snapshots \u76EE\u5F55\u3002");
198
+ }
199
+ }).catch(() => {
200
+ initMap.delete(sessionId);
201
+ });
202
+ initMap.set(sessionId, done);
203
+ return done;
204
+ }
205
+ function writeClipboard(text) {
206
+ try {
207
+ if (navigator && navigator.clipboard && typeof navigator.clipboard.writeText === "function") {
208
+ return navigator.clipboard.writeText(text).then(() => true, () => false);
209
+ }
210
+ } catch (e) {
211
+ }
212
+ try {
213
+ if (typeof document !== "undefined" && typeof document.execCommand === "function") {
214
+ const el = document.createElement("textarea");
215
+ el.value = text;
216
+ el.setAttribute("readonly", "");
217
+ el.style.position = "fixed";
218
+ el.style.left = "-9999px";
219
+ document.body.appendChild(el);
220
+ el.select();
221
+ try {
222
+ return Promise.resolve(document.execCommand("copy"));
223
+ } finally {
224
+ el.remove();
225
+ }
226
+ }
227
+ } catch (e) {
228
+ }
229
+ return Promise.resolve(false);
230
+ }
231
+ return { api, messageFor, showNotice, showThrottledToast, ensureInit, clockText, writeClipboard, sizeText, bytesToMb, buildTree, pluginConfig };
232
+ }
233
+
234
+ // src/client/recall-node.js
235
+ var KIND_INFO = {
236
+ modified: { label: "\u4FEE\u6539", cls: "modified" },
237
+ restored: { label: "\u6062\u590D", cls: "restored" },
238
+ added: { label: "\u5220\u9664", cls: "added" }
239
+ };
240
+ function summaryText(counts) {
241
+ const parts = [];
242
+ for (const kind of Object.keys(KIND_INFO)) {
243
+ if (counts[kind] > 0) parts.push(KIND_INFO[kind].label + " " + counts[kind]);
244
+ }
245
+ return parts.join(" \xB7 ");
246
+ }
247
+ function buildRecallNode(React, util, ctx, sessionsSvc, workspacesSvc) {
248
+ const { api, ensureInit, showThrottledToast, writeClipboard, clockText: clockText2, pluginConfig, messageFor } = util;
249
+ function CopyIcon() {
250
+ return React.createElement(
251
+ "svg",
252
+ { width: 16, height: 16, viewBox: "0 0 16 16", fill: "none", stroke: "currentColor", strokeWidth: 1.4, "aria-hidden": true },
253
+ React.createElement("rect", { x: 5.5, y: 5.5, width: 8, height: 8, rx: 1.5 }),
254
+ React.createElement("path", { d: "M10.5 5.5V4a1.5 1.5 0 0 0-1.5-1.5H4A1.5 1.5 0 0 0 2.5 4v5A1.5 1.5 0 0 0 4 10.5h1.5" })
255
+ );
256
+ }
257
+ function CheckIcon() {
258
+ return React.createElement(
259
+ "svg",
260
+ { width: 16, height: 16, viewBox: "0 0 16 16", fill: "none", stroke: "currentColor", strokeWidth: 1.6, strokeLinecap: "round", strokeLinejoin: "round", "aria-hidden": true },
261
+ React.createElement("path", { d: "m3 8.5 3.2 3.2L13 5" })
262
+ );
263
+ }
264
+ function UndoIcon() {
265
+ return React.createElement(
266
+ "svg",
267
+ { width: 16, height: 16, viewBox: "0 0 16 16", fill: "none", stroke: "currentColor", strokeWidth: 1.4, strokeLinecap: "round", strokeLinejoin: "round", "aria-hidden": true },
268
+ React.createElement("path", { d: "M7.5 3.5 3.5 7.5l4 4" }),
269
+ React.createElement("path", { d: "M4.5 7.5h5a3 3 0 0 1 0 6H8" })
270
+ );
271
+ }
272
+ function recallPanel(recall, closePanel, executeRecall) {
273
+ if (recall.stage === "loading") {
274
+ return React.createElement(
275
+ "div",
276
+ { className: "dsh-recall-panel" },
277
+ React.createElement("div", { className: "dsh-recall-panel-title" }, "\u6B63\u5728\u8BA1\u7B97\u53D8\u66F4\u2026")
278
+ );
279
+ }
280
+ if (recall.stage === "error") {
281
+ return React.createElement(
282
+ "div",
283
+ { className: "dsh-recall-panel" },
284
+ React.createElement("div", { className: "dsh-recall-panel-title" }, "\u65E0\u6CD5\u56DE\u9000"),
285
+ React.createElement("div", { className: "dsh-recall-panel-note" }, recall.message || ""),
286
+ React.createElement(
287
+ "div",
288
+ { className: "dsh-recall-panel-actions" },
289
+ React.createElement("button", { type: "button", className: "dsh-recall-btn", onClick: closePanel }, "\u5173\u95ED")
290
+ )
291
+ );
292
+ }
293
+ if (recall.stage === "confirm") {
294
+ const changes = recall.changes || [];
295
+ const total = typeof recall.total === "number" ? recall.total : changes.length;
296
+ const counts = { modified: 0, restored: 0, added: 0 };
297
+ for (const c of changes) {
298
+ if (c && counts[c.kind] !== void 0) counts[c.kind]++;
299
+ }
300
+ const rows = changes.map((c, i) => {
301
+ const info = KIND_INFO[c.kind];
302
+ return React.createElement(
303
+ "div",
304
+ { className: "dsh-recall-file", key: i },
305
+ React.createElement("span", { className: "dsh-recall-badge dsh-recall-badge-" + (info ? info.cls : "") }, info ? info.label : c.kind || ""),
306
+ React.createElement("span", { className: "dsh-recall-rel" }, c.rel || "")
307
+ );
308
+ });
309
+ if (recall.truncated) {
310
+ rows.push(React.createElement("div", { className: "dsh-recall-panel-note", key: "truncated" }, "\u2026\u4EC5\u663E\u793A\u524D " + changes.length + " \u6761\uFF0C\u5171 " + total + " \u4E2A\u6587\u4EF6\u5C06\u53D8\u66F4"));
311
+ }
312
+ const canRevertChat = typeof recall.cutSeq === "number";
313
+ return React.createElement(
314
+ "div",
315
+ { className: "dsh-recall-panel" },
316
+ React.createElement("div", { className: "dsh-recall-panel-title" }, "\u6574\u6BB5\u56DE\u9000"),
317
+ React.createElement(
318
+ "div",
319
+ { className: "dsh-recall-panel-note" },
320
+ "\u5C06\u9879\u76EE\u6062\u590D\u5230" + (recall.time ? " " + clockText2(recall.time) + " " : " ") + "\u53D1\u9001\u8BE5\u6D88\u606F\u65F6\u7684\u72B6\u6001\u3002\u5171 " + total + " \u4E2A\u6587\u4EF6\u5C06\u53D8\u66F4" + (summaryText(counts) ? "\uFF08" + summaryText(counts) + "\uFF09" : "") + "\u3002\u6B64\u64CD\u4F5C\u4F1A\u8986\u76D6\u5F53\u524D\u6587\u4EF6\u5185\u5BB9\uFF1B\u56DE\u9000\u524D\u4F1A\u81EA\u52A8\u4FDD\u5B58\u4E00\u4EFD\u5F53\u524D\u72B6\u6001\u7684\u5B89\u5168\u5FEB\u7167\uFF08\u4E0D\u542B\u5728\u4E0B\u65B9\u6E05\u5355\u5185\uFF09\u3002"
321
+ ),
322
+ React.createElement(
323
+ "div",
324
+ { className: "dsh-recall-panel-note" },
325
+ canRevertChat ? "\u5BF9\u8BDD\u5C06\u4E00\u5E76\u56DE\u9000\u5230\u8BE5\u6D88\u606F\u4E4B\u524D\uFF1A\u8BE5\u6D88\u606F\u53CA\u4E4B\u540E\u7684\u5168\u90E8\u5BF9\u8BDD\u4F1A\u4ECE\u5F53\u524D\u89C6\u56FE\u79FB\u9664\uFF0C\u539F\u4F1A\u8BDD\u5F52\u6863\u4FDD\u5B58\uFF08\u53EF\u4ECE\u5F52\u6863\u627E\u56DE\uFF09\u3002" : "\u8BE5\u6D88\u606F\u662F\u672C\u4F1A\u8BDD\u4E2D\u7B2C\u4E00\u6761\u7528\u6237\u6D88\u606F\uFF0C\u65E0\u6CD5\u56DE\u9000\u5BF9\u8BDD\uFF1B\u786E\u8BA4\u540E\u4EC5\u56DE\u9000\u9879\u76EE\u6587\u4EF6\u3002"
326
+ ),
327
+ changes.length > 0 ? React.createElement("div", { className: "dsh-recall-list" }, ...rows) : null,
328
+ React.createElement(
329
+ "div",
330
+ { className: "dsh-recall-panel-actions" },
331
+ React.createElement("button", { type: "button", className: "dsh-recall-btn", onClick: closePanel }, "\u53D6\u6D88"),
332
+ React.createElement("button", { type: "button", className: "dsh-recall-btn dsh-recall-btn-danger", onClick: executeRecall }, "\u786E\u8BA4\u56DE\u9000")
333
+ )
334
+ );
335
+ }
336
+ if (recall.stage === "executing") {
337
+ return React.createElement(
338
+ "div",
339
+ { className: "dsh-recall-panel" },
340
+ React.createElement("div", { className: "dsh-recall-panel-title" }, "\u6B63\u5728\u56DE\u9000\u2026")
341
+ );
342
+ }
343
+ if (recall.stage === "done") {
344
+ return React.createElement(
345
+ "div",
346
+ { className: "dsh-recall-panel" },
347
+ React.createElement("div", { className: "dsh-recall-panel-title" }, "\u56DE\u9000\u5B8C\u6210"),
348
+ React.createElement(
349
+ "div",
350
+ { className: "dsh-recall-panel-note" },
351
+ recall.chatReverted ? "\u9879\u76EE\u6587\u4EF6\u4E0E\u5BF9\u8BDD\u5DF2\u56DE\u9000\u5230\u8BE5\u6D88\u606F\u4E4B\u524D\u3002\u65B0\u4F1A\u8BDD\u5DF2\u6253\u5F00\uFF0C\u539F\u4F1A\u8BDD\u5DF2\u5F52\u6863\uFF08\u53EF\u4ECE\u5F52\u6863\u627E\u56DE\uFF09\u3002" : "\u9879\u76EE\u5DF2\u6062\u590D\u5230\u53D1\u9001\u8BE5\u6D88\u606F\u65F6\u7684\u72B6\u6001\u3002" + (recall.chatError ? " \u5BF9\u8BDD\u56DE\u9000\u5931\u8D25\uFF1A" + recall.chatError : "")
352
+ ),
353
+ React.createElement(
354
+ "div",
355
+ { className: "dsh-recall-panel-actions" },
356
+ React.createElement("button", { type: "button", className: "dsh-recall-btn", onClick: closePanel }, "\u5173\u95ED")
357
+ )
358
+ );
359
+ }
360
+ return null;
361
+ }
362
+ function fillDraft(targetSessionId, draftText) {
363
+ if (!draftText || !targetSessionId) return;
364
+ let attempts = 0;
365
+ const attempt = () => {
366
+ try {
367
+ const conversation = ctx.get("conversation");
368
+ if (conversation && conversation.input && typeof conversation.input.shell === "function") {
369
+ const shell = conversation.input.shell(targetSessionId);
370
+ if (shell) {
371
+ if (shell.actions && typeof shell.actions.setDraft === "function") {
372
+ shell.actions.setDraft(draftText);
373
+ return;
374
+ }
375
+ if (typeof shell.setDraft === "function") {
376
+ shell.setDraft(draftText);
377
+ return;
378
+ }
379
+ }
380
+ }
381
+ } catch (e) {
382
+ }
383
+ if (attempts++ < 8) setTimeout(attempt, 150);
384
+ };
385
+ attempt();
386
+ }
387
+ function UserRecallNode(props) {
388
+ const node = props && props.node;
389
+ const renderMessageImages = props && props.renderMessageImages;
390
+ const sessionId = props && props.sessionId;
391
+ const data = node && node.data ? node.data : {};
392
+ const messageId = node ? String(node.id || node.key || "") : "";
393
+ const blocks = Array.isArray(data.content) ? data.content : [];
394
+ const text = blocks.filter((b) => b && b.type === "text" && typeof b.text === "string").map((b) => b.text).join("");
395
+ const imageBlocks = blocks.filter((b) => b && b.type === "image" && b.attachment).map((b) => ({ attachment: b.attachment }));
396
+ const rest = blocks.filter((b) => !b || !(b.type === "text" && typeof b.text === "string") && !(b.type === "image" && b.attachment));
397
+ const [copied, setCopied] = React.useState(false);
398
+ const [hasSnapshot, setHasSnapshot] = React.useState(false);
399
+ const [recall, setRecall] = React.useState({ stage: "idle" });
400
+ React.useEffect(() => {
401
+ let alive = true;
402
+ let timer = null;
403
+ let attempts = 0;
404
+ const RETRY_WINDOW_MS = 5 * 60 * 1e3;
405
+ const MAX_ATTEMPTS = 20;
406
+ const RETRY_MS = 1e3;
407
+ const msgTime = data && typeof data.time === "number" ? data.time : NaN;
408
+ const recent = !isNaN(msgTime) && Date.now() - msgTime <= RETRY_WINDOW_MS;
409
+ function schedule() {
410
+ if (!alive || !messageId) return;
411
+ attempts++;
412
+ api("snapshot-info", { messageId, sessionId }).then((res) => {
413
+ if (!alive) return;
414
+ if (res && res.has) {
415
+ if (recent && Array.isArray(res.skipped) && res.skipped.length) {
416
+ const names = res.skipped.slice(0, 5).join("\u3001") + (res.skipped.length > 5 ? " \u7B49 " + res.skipped.length + " \u9879" : "");
417
+ showThrottledToast("\u5FEB\u7167\u5DF2\u8DF3\u8FC7\u672A\u7EB3\u5165\u7684\u8DEF\u5F84\uFF1A" + names + "\uFF08\u64A4\u56DE\u4E0D\u4F1A\u6062\u590D\u6216\u5220\u9664\u8FD9\u4E9B\u8DEF\u5F84\uFF09");
418
+ }
419
+ setHasSnapshot(true);
420
+ return;
421
+ }
422
+ if (res && res.failed) {
423
+ if (recent) showThrottledToast("\u5FEB\u7167\u5931\u8D25\uFF1A" + String(res.error || "\u672A\u77E5\u539F\u56E0").slice(0, 140));
424
+ return;
425
+ }
426
+ if (recent && attempts < MAX_ATTEMPTS) timer = setTimeout(schedule, RETRY_MS);
427
+ }).catch(() => {
428
+ if (alive && recent && attempts < MAX_ATTEMPTS) timer = setTimeout(schedule, RETRY_MS);
429
+ });
430
+ }
431
+ ensureInit(sessionId).then(() => {
432
+ if (!messageId || !alive) return;
433
+ schedule();
434
+ }).catch(() => {
435
+ if (alive && messageId) timer = setTimeout(schedule, RETRY_MS);
436
+ });
437
+ return () => {
438
+ alive = false;
439
+ if (timer !== null) clearTimeout(timer);
440
+ };
441
+ }, [messageId, sessionId]);
442
+ const onCopy = () => {
443
+ if (copied) return;
444
+ writeClipboard(text).then(() => {
445
+ setCopied(true);
446
+ const timer = ctx.timer;
447
+ if (timer && typeof timer.timeout === "function") {
448
+ timer.timeout(() => setCopied(false), 1200);
449
+ } else {
450
+ setTimeout(() => setCopied(false), 1200);
451
+ }
452
+ });
453
+ };
454
+ const openPreview = () => {
455
+ if (recall.stage === "loading" || recall.stage === "executing") return;
456
+ setRecall({ stage: "loading" });
457
+ api("preview", { messageId, sessionId }).then((res) => {
458
+ if (!res || !res.ok) {
459
+ setRecall({ stage: "error", message: messageFor(res, "\u65E0\u6CD5\u83B7\u53D6\u5FEB\u7167") });
460
+ return;
461
+ }
462
+ setRecall({
463
+ stage: "confirm",
464
+ changes: res.changes || [],
465
+ total: typeof res.total === "number" ? res.total : (res.changes || []).length,
466
+ truncated: Boolean(res.truncated),
467
+ treeId: res.treeId || null,
468
+ time: res.time || null,
469
+ cutSeq: typeof res.cutSeq === "number" ? res.cutSeq : null
470
+ });
471
+ }).catch((error) => {
472
+ setRecall({ stage: "error", message: String(error) });
473
+ });
474
+ };
475
+ const executeRecall = () => {
476
+ if (recall.stage !== "confirm") return;
477
+ const changes = recall.changes || [];
478
+ const previewCut = typeof recall.cutSeq === "number" ? recall.cutSeq : null;
479
+ const previewTotal = typeof recall.total === "number" ? recall.total : changes.length;
480
+ setRecall({ stage: "executing", changes });
481
+ api("execute", { messageId, sessionId, previewTotal, previewTreeId: recall.treeId || void 0, previewAt: Date.now() }).then(async (res) => {
482
+ if (!res || !res.ok) {
483
+ if (res && res.code === "STALE") {
484
+ setRecall({ stage: "loading" });
485
+ api("preview", { messageId, sessionId }).then((res2) => {
486
+ if (!res2 || !res2.ok) {
487
+ setRecall({ stage: "error", message: messageFor(res2, "\u65E0\u6CD5\u83B7\u53D6\u5FEB\u7167") });
488
+ return;
489
+ }
490
+ setRecall({
491
+ stage: "confirm",
492
+ changes: res2.changes || [],
493
+ total: typeof res2.total === "number" ? res2.total : (res2.changes || []).length,
494
+ truncated: Boolean(res2.truncated),
495
+ treeId: res2.treeId || null,
496
+ time: res2.time || null,
497
+ cutSeq: typeof res2.cutSeq === "number" ? res2.cutSeq : null
498
+ });
499
+ }).catch((error) => {
500
+ setRecall({ stage: "error", message: String(error) });
501
+ });
502
+ return;
503
+ }
504
+ setRecall({ stage: "error", message: messageFor(res, "\u56DE\u9000\u5931\u8D25") });
505
+ return;
506
+ }
507
+ const cutSeq = typeof res.cutSeq === "number" ? res.cutSeq : previewCut;
508
+ let chatReverted = false;
509
+ let chatError = "";
510
+ let fillTarget = sessionId;
511
+ if (cutSeq !== null && sessionsSvc && typeof sessionsSvc.fork === "function") {
512
+ try {
513
+ const childId = await sessionsSvc.fork({ sessionId, atSeq: cutSeq });
514
+ if (childId) {
515
+ if (typeof sessionsSvc.open === "function") sessionsSvc.open(childId);
516
+ chatReverted = true;
517
+ fillTarget = childId;
518
+ api("lineage-record", { childId, parentId: sessionId }).catch(() => {
519
+ });
520
+ if (pluginConfig.archiveOriginal && workspacesSvc && typeof workspacesSvc.archiveSession === "function") {
521
+ workspacesSvc.archiveSession(sessionId).catch(() => {
522
+ });
523
+ }
524
+ } else {
525
+ chatError = "\u672A\u8FD4\u56DE\u65B0\u4F1A\u8BDD";
526
+ }
527
+ } catch (error) {
528
+ chatError = String(error);
529
+ }
530
+ }
531
+ if (pluginConfig.refillDraft) fillDraft(fillTarget, text);
532
+ setHasSnapshot(false);
533
+ setRecall({ stage: "done", count: typeof res.count === "number" ? res.count : changes.length, chatReverted, chatError });
534
+ }).catch((error) => {
535
+ setRecall({ stage: "error", message: String(error) });
536
+ });
537
+ };
538
+ const closePanel = () => setRecall({ stage: "idle" });
539
+ const bubbleChildren = [];
540
+ if (imageBlocks.length && typeof renderMessageImages === "function") {
541
+ bubbleChildren.push(React.createElement(
542
+ React.Fragment,
543
+ { key: "images" },
544
+ renderMessageImages({ images: imageBlocks, align: "end" })
545
+ ));
546
+ }
547
+ if (text !== "") bubbleChildren.push(React.createElement("div", { className: "dsh-recall-bubble", key: "text" }, text));
548
+ for (let i = 0; i < rest.length; i++) {
549
+ bubbleChildren.push(React.createElement("pre", { className: "dsh-recall-json", key: "rest-" + i }, JSON.stringify(rest[i], null, 2)));
550
+ }
551
+ const actions = [];
552
+ actions.push(React.createElement("span", { className: "dsh-recall-time", key: "time" }, clockText2(data.time)));
553
+ actions.push(React.createElement("button", {
554
+ key: "copy",
555
+ type: "button",
556
+ className: "dsh-recall-action",
557
+ "aria-label": copied ? "\u5DF2\u590D\u5236" : "\u590D\u5236",
558
+ title: copied ? "\u5DF2\u590D\u5236" : "\u590D\u5236",
559
+ onClick: onCopy
560
+ }, copied ? React.createElement(CheckIcon, {}) : React.createElement(CopyIcon, {})));
561
+ if (hasSnapshot) {
562
+ actions.push(React.createElement("button", {
563
+ key: "recall",
564
+ type: "button",
565
+ className: "dsh-recall-action",
566
+ "aria-label": "\u64A4\u56DE",
567
+ title: "\u6574\u6BB5\u56DE\u9000\uFF1A\u6587\u4EF6\u4E0E\u5BF9\u8BDD\u4E00\u5E76\u56DE\u5230\u8BE5\u6D88\u606F\u4E4B\u524D",
568
+ onClick: openPreview
569
+ }, React.createElement(UndoIcon, {})));
570
+ }
571
+ return React.createElement(
572
+ "div",
573
+ { className: "dsh-recall-row", "data-time-hover-root": true },
574
+ bubbleChildren.length > 0 ? React.createElement("div", { className: "dsh-recall-stack", key: "stack" }, ...bubbleChildren) : null,
575
+ React.createElement("div", { className: "dsh-recall-actions", key: "actions" }, ...actions),
576
+ recallPanel(recall, closePanel, executeRecall)
577
+ );
578
+ }
579
+ return { UserRecallNode };
580
+ }
581
+
582
+ // src/client/settings-cards.js
583
+ function groupByLineage(ids, lineage) {
584
+ const childOf = /* @__PURE__ */ new Map();
585
+ const childrenOf = /* @__PURE__ */ new Map();
586
+ for (const e of lineage || []) {
587
+ if (e && e.childId && e.parentId) {
588
+ const child = String(e.childId);
589
+ const parent = String(e.parentId);
590
+ childOf.set(child, parent);
591
+ const kids = childrenOf.get(parent) || [];
592
+ kids.push(child);
593
+ childrenOf.set(parent, kids);
594
+ }
595
+ }
596
+ const idSet = new Set((ids || []).map(String));
597
+ const result = /* @__PURE__ */ new Map();
598
+ const assigned = /* @__PURE__ */ new Set();
599
+ for (const id of idSet) {
600
+ if (assigned.has(id)) continue;
601
+ let root = id;
602
+ const seen = /* @__PURE__ */ new Set();
603
+ while (childOf.has(root) && idSet.has(childOf.get(root)) && !seen.has(root)) {
604
+ seen.add(root);
605
+ root = childOf.get(root);
606
+ }
607
+ const chain = [];
608
+ const queue = [root];
609
+ while (queue.length) {
610
+ const cur = queue.shift();
611
+ if (!cur || !idSet.has(cur) || assigned.has(cur)) continue;
612
+ chain.push(cur);
613
+ assigned.add(cur);
614
+ for (const k of childrenOf.get(cur) || []) queue.push(k);
615
+ }
616
+ if (chain.length > 1) {
617
+ chain.forEach((sid, i) => result.set(sid, { family: chain, index: i + 1 }));
618
+ }
619
+ }
620
+ return result;
621
+ }
622
+ function buildSettingsCards(React, util, sessionsSvc) {
623
+ const { api, clockText: clockText2, sizeText: sizeText2, bytesToMb: bytesToMb2, buildTree: buildTree2 } = util;
624
+ const EXCLUDE_SUGGESTIONS = ["dist/", "build/", "out/", "coverage/", "*.log", ".env"];
625
+ function ExcludeCard(props) {
626
+ const file = props.file;
627
+ const [draft, setDraft] = React.useState(file.content || "");
628
+ const [baseline, setBaseline] = React.useState(file.content || "");
629
+ const [quick, setQuick] = React.useState("");
630
+ const [state, setState] = React.useState({ busy: false, message: "", error: false });
631
+ const dirty = draft !== baseline;
632
+ function appendPattern(pattern) {
633
+ setDraft((d) => (d && !d.endsWith("\n") ? d + "\n" : d) + pattern + "\n");
634
+ }
635
+ function addQuick() {
636
+ const t = quick.trim();
637
+ if (!t) return;
638
+ appendPattern(t);
639
+ setQuick("");
640
+ }
641
+ function save() {
642
+ if (state.busy || !dirty) return;
643
+ setState({ busy: true, message: "\u4FDD\u5B58\u4E2D\u2026", error: false });
644
+ api("exclude-set", { path: file.path, content: draft }).then((res) => {
645
+ if (res && res.ok) {
646
+ setBaseline(draft);
647
+ setState({ busy: false, message: "\u5DF2\u4FDD\u5B58\uFF0C\u4E0B\u4E00\u6B21\u5FEB\u7167 / \u9884\u89C8 / \u56DE\u9000\u65F6\u751F\u6548", error: false });
648
+ } else {
649
+ setState({ busy: false, message: res && (res.message || res.error) || "\u4FDD\u5B58\u5931\u8D25", error: true });
650
+ }
651
+ }).catch((error) => {
652
+ setState({ busy: false, message: String(error), error: true });
653
+ });
654
+ }
655
+ function discard() {
656
+ if (state.busy) return;
657
+ setDraft(baseline);
658
+ setState({ busy: false, message: "", error: false });
659
+ }
660
+ const draftLines = draft.split("\n").map((l) => l.trim());
661
+ const suggestions = EXCLUDE_SUGGESTIONS.filter((s) => draftLines.indexOf(s) < 0);
662
+ return React.createElement(
663
+ "div",
664
+ { className: "dsh-recall-ex-card" },
665
+ React.createElement("div", { className: "dsh-recall-ex-title" }, "\u5FEB\u7167\u6392\u9664\u9879"),
666
+ React.createElement(
667
+ "div",
668
+ { className: "dsh-recall-ex-note" },
669
+ file.home ? "\u6B64\u914D\u7F6E\u5168\u5C40\u5171\u4EAB\uFF0C\u5BF9\u6240\u6709\u5DE5\u4F5C\u533A\u7684\u5FEB\u7167\u751F\u6548\uFF08\u5B58\u50A8\u4F4D\u7F6E\uFF1A" + file.path + "\uFF09\u3002" : "home \u76EE\u5F55\u4E0D\u53EF\u5199\u65F6\u6B64\u5DE5\u4F5C\u533A\u964D\u7EA7\u5B58\u50A8\uFF0C\u6392\u9664\u914D\u7F6E\u72EC\u7ACB\u751F\u6548\uFF08\u5B58\u50A8\u4F4D\u7F6E\uFF1A" + file.path + "\uFF09\u3002"
670
+ ),
671
+ React.createElement("div", { className: "dsh-recall-ex-note" }, "gitignore \u8BED\u6CD5\uFF0C\u4E00\u884C\u4E00\u6761\uFF0C\u652F\u6301 # \u6CE8\u91CA\uFF1B\u547D\u4E2D\u6392\u9664\u7684\u6587\u4EF6\u4E0E\u76EE\u5F55\u4E0D\u8FDB\u5165\u5FEB\u7167\uFF0C\u4E5F\u4E0D\u4F1A\u88AB\u56DE\u9000\u89E6\u78B0\u3002"),
672
+ React.createElement("textarea", {
673
+ className: "dsh-recall-ex-area",
674
+ value: draft,
675
+ spellCheck: false,
676
+ onChange: (e) => setDraft(e.target.value)
677
+ }),
678
+ React.createElement(
679
+ "div",
680
+ { className: "dsh-recall-ex-quick" },
681
+ React.createElement("input", {
682
+ className: "dsh-recall-ex-input",
683
+ value: quick,
684
+ placeholder: "\u8F93\u5165\u8DEF\u5F84\u6216\u6A21\u5F0F\uFF0C\u56DE\u8F66\u5FEB\u901F\u6DFB\u52A0",
685
+ onChange: (e) => setQuick(e.target.value),
686
+ onKeyDown: (e) => {
687
+ if (e.key === "Enter") {
688
+ e.preventDefault();
689
+ addQuick();
690
+ }
691
+ }
692
+ }),
693
+ React.createElement("button", { type: "button", className: "dsh-recall-btn", onClick: addQuick }, "\u6DFB\u52A0"),
694
+ ...suggestions.map((s) => React.createElement("button", {
695
+ key: "chip-" + s,
696
+ type: "button",
697
+ className: "dsh-recall-ex-chip",
698
+ title: "\u70B9\u51FB\u8FFD\u52A0 " + s,
699
+ onClick: () => appendPattern(s)
700
+ }, s))
701
+ ),
702
+ React.createElement(
703
+ "div",
704
+ { className: "dsh-recall-panel-actions" },
705
+ state.message ? React.createElement("span", { className: "dsh-recall-ex-status" + (state.error ? " dsh-recall-ex-status-error" : " dsh-recall-ex-status-success") }, state.message) : null,
706
+ React.createElement("button", { type: "button", className: "dsh-recall-btn", disabled: !dirty || state.busy, onClick: discard }, "\u653E\u5F03\u4FEE\u6539"),
707
+ React.createElement("button", { type: "button", className: "dsh-recall-btn dsh-recall-btn-danger", disabled: !dirty || state.busy, onClick: save }, "\u4FDD\u5B58")
708
+ )
709
+ );
710
+ }
711
+ function ManageCard(props) {
712
+ const [items, setItems] = React.useState(null);
713
+ const [usage, setUsage] = React.useState(null);
714
+ const [errors, setErrors] = React.useState(null);
715
+ const [state, setState] = React.useState({ busy: false, message: "", error: false });
716
+ const [limit, setLimit] = React.useState(200);
717
+ const [total, setTotal] = React.useState(0);
718
+ const [health, setHealth] = React.useState(null);
719
+ const [query, setQuery] = React.useState("");
720
+ const [showAllErrors, setShowAllErrors] = React.useState(false);
721
+ const [titlesPending, setTitlesPending] = React.useState(false);
722
+ const [lineage, setLineage] = React.useState([]);
723
+ function fetchTitles(list) {
724
+ const missing = Array.from(new Set(
725
+ (list || []).filter((it) => it.sessionId && !it.sessionTitle).map((it) => it.sessionId)
726
+ )).slice(0, 100);
727
+ if (!missing.length) {
728
+ setTitlesPending(false);
729
+ return;
730
+ }
731
+ setTitlesPending(true);
732
+ api("manage", { op: "titles", sessionIds: missing }).then((res) => {
733
+ const map = res && res.ok ? res.titles : null;
734
+ if (map) {
735
+ setItems((prev) => (prev || []).map((it) => it.sessionId && map[it.sessionId] ? Object.assign({}, it, { sessionTitle: map[it.sessionId] }) : it));
736
+ }
737
+ setTitlesPending(false);
738
+ }).catch(() => setTitlesPending(false));
739
+ }
740
+ function fetchMessages(list) {
741
+ const requests = (list || []).filter((it) => it.sessionId && it.id && !Object.prototype.hasOwnProperty.call(it, "messageText")).map((it) => ({ sessionId: it.sessionId, messageId: it.id })).slice(0, 200);
742
+ if (!requests.length) return;
743
+ api("manage", { op: "messages", requests }).then((res) => {
744
+ const map = res && res.ok ? res.messageTexts : null;
745
+ if (map) {
746
+ setItems((prev) => (prev || []).map((it) => it.id && Object.prototype.hasOwnProperty.call(map, it.id) ? Object.assign({}, it, { messageText: map[it.id] }) : it));
747
+ }
748
+ }).catch(() => {
749
+ });
750
+ }
751
+ function refresh(overLimit) {
752
+ const useLimit = overLimit || limit;
753
+ api("manage", { op: "list", limit: useLimit }).then((res) => {
754
+ if (res && res.ok) {
755
+ setItems(res.items || []);
756
+ setTotal(typeof res.total === "number" ? res.total : (res.items || []).length);
757
+ fetchTitles(res.items || []);
758
+ fetchMessages(res.items || []);
759
+ if (res.stale) {
760
+ api("manage", { op: "list", limit: useLimit }).then((res2) => {
761
+ if (res2 && res2.ok && !res2.stale) {
762
+ setItems(res2.items || []);
763
+ setTotal(typeof res2.total === "number" ? res2.total : (res2.items || []).length);
764
+ fetchTitles(res2.items || []);
765
+ fetchMessages(res2.items || []);
766
+ }
767
+ }).catch(() => {
768
+ });
769
+ }
770
+ }
771
+ api("manage", { op: "lineage" }).then((res2) => {
772
+ if (res2 && res2.ok && Array.isArray(res2.lineage)) setLineage(res2.lineage);
773
+ }).catch(() => {
774
+ });
775
+ api("manage", { op: "usage" }).then((res2) => {
776
+ if (res2 && res2.ok) {
777
+ setUsage(res2.bytes || 0);
778
+ setHealth({ gitAvailable: res2.gitAvailable !== false, homeStores: res2.homeStores || 0, fallbackStores: res2.fallbackStores || 0 });
779
+ }
780
+ }).catch(() => {
781
+ });
782
+ api("status", {}).then((res2) => {
783
+ if (res2 && res2.ok) setErrors(res2.errors || []);
784
+ }).catch(() => {
785
+ });
786
+ }).catch(() => {
787
+ api("manage", { op: "usage" }).then((res) => {
788
+ if (res && res.ok) {
789
+ setUsage(res.bytes || 0);
790
+ setHealth({ gitAvailable: res.gitAvailable !== false, homeStores: res.homeStores || 0, fallbackStores: res.fallbackStores || 0 });
791
+ }
792
+ }).catch(() => {
793
+ });
794
+ api("status", {}).then((res) => {
795
+ if (res && res.ok) setErrors(res.errors || []);
796
+ }).catch(() => {
797
+ });
798
+ });
799
+ }
800
+ React.useEffect(() => {
801
+ refresh();
802
+ }, []);
803
+ function clearErrors() {
804
+ setErrors([]);
805
+ api("status", { op: "clear" }).catch(() => {
806
+ });
807
+ }
808
+ function run(op, extra, doneText) {
809
+ if (state.busy) return;
810
+ setState({ busy: true, message: "\u6267\u884C\u4E2D\u2026", error: false });
811
+ api("manage", Object.assign({ op }, extra || {})).then((res) => {
812
+ if (res && res.ok) {
813
+ setState({ busy: false, message: typeof res.deleted === "number" ? "\u5DF2\u5220\u9664 " + res.deleted + " \u6761\u5FEB\u7167" : doneText, error: false });
814
+ refresh();
815
+ } else {
816
+ setState({ busy: false, message: res && (res.message || res.error) || "\u64CD\u4F5C\u5931\u8D25", error: true });
817
+ }
818
+ }).catch((e) => setState({ busy: false, message: String(e), error: true }));
819
+ }
820
+ const [expanded, setExpanded] = React.useState(() => /* @__PURE__ */ new Set());
821
+ const [confirming, setConfirming] = React.useState(null);
822
+ function renderDeleteAllConfirm() {
823
+ if (!confirming || confirming.kind !== "all") return null;
824
+ return React.createElement(
825
+ "div",
826
+ { className: "dsh-recall-tree-confirm" },
827
+ "\u786E\u8BA4\u5220\u9664\u6240\u6709\u5DE5\u4F5C\u533A\u7684\u5168\u90E8\u5FEB\u7167\uFF1F\u6B64\u64CD\u4F5C\u4E0D\u53EF\u6062\u590D\u3002",
828
+ React.createElement("button", {
829
+ type: "button",
830
+ className: "dsh-recall-btn dsh-recall-btn-danger",
831
+ onClick: () => {
832
+ setConfirming(null);
833
+ run("deleteAll", {}, "\u5DF2\u6E05\u7A7A\u5168\u90E8\u5FEB\u7167");
834
+ }
835
+ }, "\u786E\u8BA4\u5168\u90E8\u5220\u9664"),
836
+ React.createElement("button", { type: "button", className: "dsh-recall-ex-chip", onClick: () => setConfirming(null) }, "\u53D6\u6D88")
837
+ );
838
+ }
839
+ function toggle(key) {
840
+ setExpanded((prev) => {
841
+ const next = new Set(prev);
842
+ if (next.has(key)) next.delete(key);
843
+ else next.add(key);
844
+ return next;
845
+ });
846
+ }
847
+ const q = query.trim().toLowerCase();
848
+ const filteredItems = q ? (items || []).filter(
849
+ (it) => (it.workspace || "").toLowerCase().indexOf(q) >= 0 || (it.sessionTitle || "").toLowerCase().indexOf(q) >= 0 || (it.messageText || "").toLowerCase().indexOf(q) >= 0 || String(it.id || "").toLowerCase().indexOf(q) >= 0
850
+ ) : items;
851
+ const tree = buildTree2(filteredItems);
852
+ const allSessionIds = Array.from(new Set((items || []).map((it) => it.sessionId).filter(Boolean)));
853
+ const versionMap = groupByLineage(allSessionIds, lineage);
854
+ let listById = null;
855
+ try {
856
+ if (sessionsSvc && sessionsSvc.list && typeof sessionsSvc.list.getSnapshot === "function") {
857
+ listById = sessionsSvc.list.getSnapshot().byId || null;
858
+ }
859
+ } catch (e) {
860
+ listById = null;
861
+ }
862
+ function confirmDelete(kind, key, extra, text) {
863
+ setConfirming({ kind, key, extra, text });
864
+ }
865
+ function renderConfirm(kind, key, extra, text) {
866
+ if (!confirming || confirming.kind !== kind || confirming.key !== key) return null;
867
+ return React.createElement(
868
+ "div",
869
+ { className: "dsh-recall-tree-confirm" },
870
+ text,
871
+ React.createElement("button", {
872
+ type: "button",
873
+ className: "dsh-recall-ex-chip",
874
+ onClick: () => {
875
+ const c = confirming;
876
+ setConfirming(null);
877
+ run("delete", c.extra, "\u5DF2\u5220\u9664");
878
+ }
879
+ }, "\u786E\u8BA4"),
880
+ React.createElement("button", { type: "button", className: "dsh-recall-ex-chip", onClick: () => setConfirming(null) }, "\u53D6\u6D88")
881
+ );
882
+ }
883
+ function renderLeaf(it) {
884
+ const key = "snap-" + it.id;
885
+ const text = it.messageText;
886
+ const title = text || it.id;
887
+ const label = text ? clockText2(it.time) + " " + text : clockText2(it.time) + " " + it.id.slice(0, 12) + "\u2026";
888
+ return React.createElement(
889
+ "div",
890
+ { className: "dsh-recall-tree-node", key },
891
+ React.createElement(
892
+ "div",
893
+ { className: "dsh-recall-tree-row", title },
894
+ React.createElement("span", { className: "dsh-recall-tree-toggle-placeholder" }),
895
+ React.createElement(
896
+ "span",
897
+ { className: "dsh-recall-tree-label" },
898
+ React.createElement("span", { className: "dsh-recall-tree-title" }, label)
899
+ ),
900
+ React.createElement("button", {
901
+ type: "button",
902
+ className: "dsh-recall-ex-chip",
903
+ title: "\u5220\u9664\u8BE5\u5FEB\u7167\uFF08tag \u4E0E\u7D22\u5F15\u6761\u76EE\uFF09",
904
+ onClick: () => confirmDelete("snapshot", key, { messageId: it.id, root: it.root || null }, "\u786E\u8BA4\u5220\u9664\u8BE5\u5FEB\u7167\uFF1F\u6B64\u64CD\u4F5C\u4E0D\u53EF\u6062\u590D\u3002")
905
+ }, "\u5220\u9664")
906
+ ),
907
+ renderConfirm("snapshot", key, { messageId: it.id, root: it.root || null }, "\u786E\u8BA4\u5220\u9664\u8BE5\u5FEB\u7167\uFF1F\u6B64\u64CD\u4F5C\u4E0D\u53EF\u6062\u590D\u3002")
908
+ );
909
+ }
910
+ function renderSession(s) {
911
+ const key = "session-" + (s.root || "") + "-" + s.sessionId;
912
+ const open = expanded.has(key);
913
+ const label = s.title || (titlesPending && s.sessionId ? "\u2026" : "\uFF08\u5DF2\u5220\u9664\u4F1A\u8BDD\uFF09");
914
+ const version = s.sessionId ? versionMap.get(String(s.sessionId)) : null;
915
+ const switchable = Boolean(s.sessionId && listById && listById[s.sessionId]);
916
+ return React.createElement(
917
+ "div",
918
+ { className: "dsh-recall-tree-node", key },
919
+ React.createElement(
920
+ "div",
921
+ { className: "dsh-recall-tree-row" },
922
+ React.createElement("span", {
923
+ className: "dsh-recall-tree-toggle",
924
+ onClick: () => toggle(key)
925
+ }, open ? "\u25BE" : "\u25B8"),
926
+ React.createElement(
927
+ "span",
928
+ { className: "dsh-recall-tree-label", title: s.sessionId || "" },
929
+ React.createElement("span", { className: "dsh-recall-tree-title" }, label),
930
+ version ? React.createElement("span", { className: "dsh-recall-tree-meta", title: "\u7248\u672C\u5BB6\u65CF\uFF1A" + version.family.join(" \u2192 ") }, "v" + version.index + "/" + version.family.length) : null,
931
+ React.createElement("span", { className: "dsh-recall-tree-meta" }, s.items.length + " \u6761")
932
+ ),
933
+ switchable ? React.createElement("button", {
934
+ type: "button",
935
+ className: "dsh-recall-ex-chip",
936
+ title: "\u5207\u6362\u5230\u8BE5\u7248\u672C\u4F1A\u8BDD",
937
+ onClick: () => {
938
+ try {
939
+ sessionsSvc.open(s.sessionId);
940
+ } catch (e) {
941
+ }
942
+ }
943
+ }, "\u5207\u6362") : null,
944
+ s.sessionId ? React.createElement("button", {
945
+ type: "button",
946
+ className: "dsh-recall-ex-chip",
947
+ title: "\u5220\u9664\u8BE5\u4F1A\u8BDD\u5168\u90E8\u5FEB\u7167",
948
+ onClick: () => confirmDelete("session", key, { scope: "session", sessionId: s.sessionId, root: s.root || null }, "\u786E\u8BA4\u5220\u9664\u8BE5\u4F1A\u8BDD\u5168\u90E8\u5FEB\u7167\uFF1F\u6B64\u64CD\u4F5C\u4E0D\u53EF\u6062\u590D\u3002")
949
+ }, "\u5220\u9664") : null
950
+ ),
951
+ open ? React.createElement("div", { className: "dsh-recall-tree-children" }, ...s.items.map(renderLeaf)) : null,
952
+ s.sessionId ? renderConfirm("session", key, { scope: "session", sessionId: s.sessionId, root: s.root || null }, "\u786E\u8BA4\u5220\u9664\u8BE5\u4F1A\u8BDD\u5168\u90E8\u5FEB\u7167\uFF1F\u6B64\u64CD\u4F5C\u4E0D\u53EF\u6062\u590D\u3002") : null
953
+ );
954
+ }
955
+ function renderWorkspace(ws) {
956
+ const key = "ws-" + ws.root;
957
+ const open = expanded.has(key);
958
+ const sessionCount = ws.sessions.length;
959
+ const snapCount = ws.sessions.reduce((n, s) => n + s.items.length, 0);
960
+ return React.createElement(
961
+ "div",
962
+ { className: "dsh-recall-tree-node", key },
963
+ React.createElement(
964
+ "div",
965
+ { className: "dsh-recall-tree-row" },
966
+ React.createElement("span", {
967
+ className: "dsh-recall-tree-toggle",
968
+ onClick: () => toggle(key)
969
+ }, open ? "\u25BE" : "\u25B8"),
970
+ React.createElement(
971
+ "span",
972
+ { className: "dsh-recall-tree-label", title: ws.root || "" },
973
+ React.createElement("span", { className: "dsh-recall-tree-name" }, ws.name),
974
+ React.createElement("span", { className: "dsh-recall-tree-meta" }, sessionCount + " \u4F1A\u8BDD / " + snapCount + " \u5FEB\u7167")
975
+ ),
976
+ ws.root ? React.createElement("button", {
977
+ type: "button",
978
+ className: "dsh-recall-ex-chip",
979
+ title: "\u5220\u9664\u8BE5\u5DE5\u4F5C\u533A\u5168\u90E8\u5FEB\u7167",
980
+ onClick: () => confirmDelete("workspace", key, { scope: "workspace", root: ws.root }, "\u786E\u8BA4\u5220\u9664\u8BE5\u5DE5\u4F5C\u533A\u5168\u90E8\u5FEB\u7167\uFF1F\u6B64\u64CD\u4F5C\u4E0D\u53EF\u6062\u590D\u3002")
981
+ }, "\u5220\u9664") : null
982
+ ),
983
+ open ? React.createElement("div", { className: "dsh-recall-tree-children" }, ...ws.sessions.map(renderSession)) : null,
984
+ ws.root ? renderConfirm("workspace", key, { scope: "workspace", root: ws.root }, "\u786E\u8BA4\u5220\u9664\u8BE5\u5DE5\u4F5C\u533A\u5168\u90E8\u5FEB\u7167\uFF1F\u6B64\u64CD\u4F5C\u4E0D\u53EF\u6062\u590D\u3002") : null
985
+ );
986
+ }
987
+ const treeNodes = tree.map(renderWorkspace);
988
+ const loaded = items ? items.length : null;
989
+ const countText = loaded === null ? "\u5171 \u2026 \u6761\u5FEB\u7167" : "\u5171 " + total + " \u6761\u5FEB\u7167" + (limit < total ? "\uFF08\u5F53\u524D\u663E\u793A\u6700\u65B0 " + loaded + " \u6761\uFF09" : "");
990
+ function loadMore() {
991
+ const next = Math.min(Math.max(total, limit), 2e3);
992
+ if (next <= limit) return;
993
+ setLimit(next);
994
+ refresh(next);
995
+ }
996
+ return React.createElement(
997
+ "div",
998
+ { className: "dsh-recall-ex-card" },
999
+ React.createElement("div", { className: "dsh-recall-ex-title" }, "\u5FEB\u7167\u7BA1\u7406"),
1000
+ React.createElement(
1001
+ "div",
1002
+ { className: "dsh-recall-ex-note" },
1003
+ usage === null ? countText + "\u3002" : countText + "\uFF0C\u5168\u90E8\u5DE5\u4F5C\u533A\u5FEB\u7167\u5B58\u50A8\u5360\u7528 " + sizeText2(usage) + "\u3002"
1004
+ ),
1005
+ health ? React.createElement(
1006
+ "div",
1007
+ { className: "dsh-recall-ex-note", key: "health" },
1008
+ React.createElement("span", {
1009
+ className: health.gitAvailable ? "" : "dsh-recall-ex-status-error"
1010
+ }, health.gitAvailable ? "git \u53EF\u7528" : "git \u4E0D\u53EF\u7528\uFF08\u5FEB\u7167\u5F15\u64CE\u4F9D\u8D56 git\uFF09"),
1011
+ " \xB7 \u5FEB\u7167\u5B58\u50A8\uFF1Ahome " + health.homeStores + " \u4E2A\u5DE5\u4F5C\u533A" + (health.fallbackStores ? "\uFF0C\u964D\u7EA7 " + health.fallbackStores + " \u4E2A" : "")
1012
+ ) : null,
1013
+ React.createElement("input", {
1014
+ className: "dsh-recall-ex-input",
1015
+ placeholder: "\u641C\u7D22\u5DE5\u4F5C\u533A / \u4F1A\u8BDD\u6807\u9898 / \u6D88\u606F\u5185\u5BB9 / ID",
1016
+ value: query,
1017
+ spellCheck: false,
1018
+ onChange: (e) => setQuery(e.target.value)
1019
+ }),
1020
+ treeNodes.length > 0 ? React.createElement("div", { className: "dsh-recall-tree" }, ...treeNodes) : null,
1021
+ items && items.length === 0 && !q ? React.createElement("div", { className: "dsh-recall-ex-note", key: "empty" }, "\u5728\u4EFB\u610F\u5DE5\u4F5C\u533A\u53D1\u9001\u4E00\u6761\u6D88\u606F\u540E\uFF0C\u8FD9\u91CC\u4F1A\u51FA\u73B0\u5FEB\u7167\u3002") : null,
1022
+ q && filteredItems && filteredItems.length === 0 ? React.createElement("div", { className: "dsh-recall-ex-note", key: "no-match" }, "\u65E0\u5339\u914D\u5FEB\u7167") : null,
1023
+ renderDeleteAllConfirm(),
1024
+ React.createElement(
1025
+ "div",
1026
+ { className: "dsh-recall-panel-actions" },
1027
+ state.message ? React.createElement("span", { className: "dsh-recall-ex-status" + (state.error ? " dsh-recall-ex-status-error" : " dsh-recall-ex-status-success") }, state.message) : null,
1028
+ limit < total ? React.createElement("button", {
1029
+ type: "button",
1030
+ className: "dsh-recall-btn",
1031
+ disabled: state.busy,
1032
+ onClick: loadMore
1033
+ }, "\u52A0\u8F7D\u66F4\u591A") : null,
1034
+ React.createElement("button", { type: "button", className: "dsh-recall-btn", disabled: state.busy, onClick: refresh }, "\u5237\u65B0"),
1035
+ React.createElement("button", {
1036
+ type: "button",
1037
+ className: "dsh-recall-btn dsh-recall-btn-danger",
1038
+ disabled: state.busy,
1039
+ title: "\u5220\u9664\u5168\u90E8\u5DE5\u4F5C\u533A\u7684\u6240\u6709\u5FEB\u7167\uFF1B\u4F1A\u76F4\u63A5\u6838\u5BF9\u5E76\u5220\u9664 git tag\uFF08\u5373\u4F7F\u5217\u8868\u4E3A\u7A7A\u4E5F\u53EF\u6E05\u7406\u6B8B\u7559\uFF09",
1040
+ onClick: () => setConfirming({ kind: "all" })
1041
+ }, "\u5168\u90E8\u5220\u9664"),
1042
+ React.createElement("button", {
1043
+ type: "button",
1044
+ className: "dsh-recall-btn",
1045
+ disabled: state.busy,
1046
+ title: "\u7ACB\u5373\u5BF9\u5168\u90E8\u5DE5\u4F5C\u533A\u6267\u884C\u4E00\u6B21 git gc\uFF08\u538B\u7F29\u5BF9\u8C61\u5E93\u91CA\u653E\u7A7A\u95F4\uFF09",
1047
+ onClick: () => run("gc", {}, "gc \u5B8C\u6210")
1048
+ }, "\u7ACB\u5373 gc")
1049
+ ),
1050
+ errors && errors.length > 0 ? React.createElement(
1051
+ "div",
1052
+ { className: "dsh-recall-ex-note", key: "errors" },
1053
+ React.createElement(
1054
+ "div",
1055
+ { className: "dsh-recall-ex-status" },
1056
+ "\u6700\u8FD1\u9519\u8BEF\uFF1A",
1057
+ (showAllErrors ? errors : errors.slice(0, 5)).map((e, i) => React.createElement("div", { key: i, className: "dsh-recall-ex-note" }, clockText2(e.time) + " " + e.message))
1058
+ ),
1059
+ React.createElement(
1060
+ "div",
1061
+ { className: "dsh-recall-panel-actions" },
1062
+ errors.length > 5 ? React.createElement("button", { type: "button", className: "dsh-recall-ex-chip", onClick: () => setShowAllErrors((v) => !v) }, showAllErrors ? "\u6536\u8D77" : "\u5C55\u5F00\u5168\u90E8 (" + errors.length + ")") : null,
1063
+ React.createElement("button", { type: "button", className: "dsh-recall-ex-chip", onClick: clearErrors }, "\u6E05\u7A7A")
1064
+ )
1065
+ ) : null
1066
+ );
1067
+ }
1068
+ function ExcludeFilesSection() {
1069
+ const [files, setFiles] = React.useState(null);
1070
+ const [error, setError] = React.useState("");
1071
+ function load() {
1072
+ api("exclude-get", {}).then((res) => {
1073
+ if (res && res.ok) {
1074
+ setFiles(res.files || []);
1075
+ setError("");
1076
+ return;
1077
+ }
1078
+ if (res && res.unsupported) {
1079
+ setError("\u5F53\u524D\u5E73\u53F0\u4E0D\u652F\u6301\u5FEB\u7167\u529F\u80FD\uFF0C\u6392\u9664\u914D\u7F6E\u4E0D\u53EF\u7528\u3002");
1080
+ return;
1081
+ }
1082
+ setError(res && (res.message || res.error) || "\u65E0\u6CD5\u8BFB\u53D6\u6392\u9664\u914D\u7F6E");
1083
+ }).catch((e) => setError(String(e)));
1084
+ }
1085
+ React.useEffect(() => {
1086
+ load();
1087
+ }, []);
1088
+ if (error) {
1089
+ return React.createElement(
1090
+ "div",
1091
+ { className: "dsh-recall-ex-card" },
1092
+ React.createElement("div", { className: "dsh-recall-ex-note" }, error),
1093
+ React.createElement(
1094
+ "div",
1095
+ { className: "dsh-recall-panel-actions" },
1096
+ React.createElement("button", { type: "button", className: "dsh-recall-btn", onClick: load }, "\u91CD\u8BD5")
1097
+ )
1098
+ );
1099
+ }
1100
+ if (files === null) {
1101
+ return React.createElement("div", { className: "dsh-recall-ex-note" }, "\u6B63\u5728\u52A0\u8F7D\u6392\u9664\u914D\u7F6E\u2026");
1102
+ }
1103
+ if (!files.length) {
1104
+ return React.createElement("div", { className: "dsh-recall-ex-note" }, "\u5C1A\u672A\u521B\u5EFA\u4EFB\u4F55\u5FEB\u7167\u5B58\u50A8\uFF1A\u5728\u4EFB\u610F\u5DE5\u4F5C\u533A\u53D1\u9001\u4E00\u6761\u6D88\u606F\u540E\uFF0C\u8FD9\u91CC\u4F1A\u51FA\u73B0\u53EF\u7F16\u8F91\u7684\u6392\u9664\u914D\u7F6E\u3002");
1105
+ }
1106
+ return React.createElement(
1107
+ "div",
1108
+ { className: "dsh-recall-ex-card" },
1109
+ ...files.map((f) => React.createElement(ExcludeCard, { key: f.path, file: f }))
1110
+ );
1111
+ }
1112
+ function ConfigForm() {
1113
+ const [baseline, setBaseline] = React.useState(null);
1114
+ const [draft, setDraft] = React.useState(null);
1115
+ const [envLocks, setEnvLocks] = React.useState({});
1116
+ const [overridden, setOverridden] = React.useState({});
1117
+ const [writable, setWritable] = React.useState(true);
1118
+ const [state, setState] = React.useState({ busy: false, message: "", error: false });
1119
+ const [showAdvanced, setShowAdvanced] = React.useState(false);
1120
+ function load() {
1121
+ api("config-get", {}).then((res) => {
1122
+ if (res && res.ok) {
1123
+ const v = res.values || {};
1124
+ const next = {
1125
+ gcSnaps: String(v.gcSnaps == null ? "" : v.gcSnaps),
1126
+ gcHours: String(v.gcHours == null ? "" : v.gcHours),
1127
+ maxFileBytes: bytesToMb2(v.maxFileBytes),
1128
+ maxSnapshotsPerWorkspace: String(v.maxSnapshotsPerWorkspace == null ? "" : v.maxSnapshotsPerWorkspace),
1129
+ baseExcludes: Array.isArray(v.baseExcludes) ? v.baseExcludes.join("\n") : "",
1130
+ refillDraft: v.refillDraft !== false,
1131
+ snapshotEnabled: v.snapshotEnabled !== false,
1132
+ archiveOriginal: v.archiveOriginal !== false,
1133
+ retentionDays: String(v.retentionDays == null ? "" : v.retentionDays)
1134
+ };
1135
+ setDraft(next);
1136
+ setBaseline(next);
1137
+ setEnvLocks(res.envLocks || {});
1138
+ setOverridden(res.overridden || {});
1139
+ setWritable(res.writable !== false);
1140
+ } else {
1141
+ setState({ busy: false, message: res && (res.message || res.error) || "\u65E0\u6CD5\u8BFB\u53D6\u914D\u7F6E", error: true });
1142
+ }
1143
+ }).catch((e) => setState({ busy: false, message: String(e), error: true }));
1144
+ }
1145
+ React.useEffect(() => {
1146
+ load();
1147
+ }, []);
1148
+ function edit(key, value) {
1149
+ setDraft((d) => Object.assign({}, d, { [key]: value }));
1150
+ }
1151
+ function save() {
1152
+ if (state.busy || !draft || !baseline) return;
1153
+ const patch = {};
1154
+ for (const key of ["gcSnaps", "gcHours", "maxFileBytes", "maxSnapshotsPerWorkspace", "baseExcludes", "refillDraft", "snapshotEnabled", "archiveOriginal", "retentionDays"]) {
1155
+ if (draft[key] !== baseline[key]) patch[key] = draft[key];
1156
+ }
1157
+ if (!Object.keys(patch).length) {
1158
+ setState({ busy: false, message: "\u6CA1\u6709\u4FEE\u6539", error: false });
1159
+ return;
1160
+ }
1161
+ const clean = {};
1162
+ if (patch.gcSnaps !== void 0) {
1163
+ const n = parseInt(patch.gcSnaps, 10);
1164
+ if (!Number.isFinite(n) || n < 1) {
1165
+ setState({ busy: false, message: "\u5FEB\u7167\u6761\u6570\u9608\u503C\u5FC5\u987B\u662F >= 1 \u7684\u6574\u6570", error: true });
1166
+ return;
1167
+ }
1168
+ clean.gcSnaps = n;
1169
+ }
1170
+ if (patch.gcHours !== void 0) {
1171
+ const n = parseInt(patch.gcHours, 10);
1172
+ if (!Number.isFinite(n) || n < 1) {
1173
+ setState({ busy: false, message: "gc \u5C0F\u65F6\u9608\u503C\u5FC5\u987B\u662F >= 1 \u7684\u6574\u6570", error: true });
1174
+ return;
1175
+ }
1176
+ clean.gcHours = n;
1177
+ }
1178
+ if (patch.maxFileBytes !== void 0) {
1179
+ const mb = Number(patch.maxFileBytes);
1180
+ if (!Number.isFinite(mb) || mb < 0.01) {
1181
+ setState({ busy: false, message: "\u6587\u4EF6\u5927\u5C0F\u4E0A\u9650\u81F3\u5C11 0.01 MB", error: true });
1182
+ return;
1183
+ }
1184
+ clean.maxFileBytes = Math.round(mb * 1048576);
1185
+ }
1186
+ if (patch.maxSnapshotsPerWorkspace !== void 0) {
1187
+ const n = parseInt(patch.maxSnapshotsPerWorkspace, 10);
1188
+ if (!Number.isFinite(n) || n < 0) {
1189
+ setState({ busy: false, message: "\u5FEB\u7167\u603B\u91CF\u4E0A\u9650\u5FC5\u987B\u662F >= 0 \u7684\u6574\u6570\uFF080 \u8868\u793A\u4E0D\u9650\u5236\uFF09", error: true });
1190
+ return;
1191
+ }
1192
+ clean.maxSnapshotsPerWorkspace = n;
1193
+ }
1194
+ if (patch.refillDraft !== void 0) clean.refillDraft = Boolean(patch.refillDraft);
1195
+ if (patch.snapshotEnabled !== void 0) clean.snapshotEnabled = Boolean(patch.snapshotEnabled);
1196
+ if (patch.archiveOriginal !== void 0) clean.archiveOriginal = Boolean(patch.archiveOriginal);
1197
+ if (patch.retentionDays !== void 0) {
1198
+ const n = parseInt(patch.retentionDays, 10);
1199
+ if (!Number.isFinite(n) || n < 0) {
1200
+ setState({ busy: false, message: "\u4FDD\u7559\u5929\u6570\u5FC5\u987B\u662F >= 0 \u7684\u6574\u6570\uFF080 \u8868\u793A\u4E0D\u542F\u7528\uFF09", error: true });
1201
+ return;
1202
+ }
1203
+ clean.retentionDays = n;
1204
+ }
1205
+ if (patch.baseExcludes !== void 0) {
1206
+ clean.baseExcludes = String(patch.baseExcludes).split("\n").map((l) => l.trim()).filter(Boolean);
1207
+ }
1208
+ setState({ busy: true, message: "\u4FDD\u5B58\u4E2D\u2026", error: false });
1209
+ api("config-set", { patch: clean }).then((res) => {
1210
+ if (res && res.ok) {
1211
+ setState({ busy: false, message: "\u5DF2\u4FDD\u5B58\u5E76\u5373\u65F6\u751F\u6548", error: false });
1212
+ load();
1213
+ } else {
1214
+ setState({ busy: false, message: res && (res.message || res.error) || "\u4FDD\u5B58\u5931\u8D25", error: true });
1215
+ }
1216
+ }).catch((e) => setState({ busy: false, message: String(e), error: true }));
1217
+ }
1218
+ function numRow(key, label, hint, opts) {
1219
+ const locked = Boolean(envLocks && envLocks[key]);
1220
+ const changed = Boolean(draft && baseline && draft[key] !== baseline[key]);
1221
+ return React.createElement(
1222
+ "div",
1223
+ { className: "dsh-recall-cfg-row", key },
1224
+ React.createElement(
1225
+ "div",
1226
+ { className: "dsh-recall-cfg-line" },
1227
+ React.createElement("label", { className: "dsh-recall-cfg-label" }, label),
1228
+ React.createElement("input", {
1229
+ className: "dsh-recall-cfg-input",
1230
+ type: "number",
1231
+ value: draft ? draft[key] : "",
1232
+ disabled: locked || !writable,
1233
+ min: opts && opts.min,
1234
+ step: opts && opts.step,
1235
+ onChange: (e) => edit(key, e.target.value)
1236
+ }),
1237
+ opts && opts.suffix ? React.createElement("span", { className: "dsh-recall-cfg-tag" }, opts.suffix) : null,
1238
+ changed && !locked ? React.createElement("span", { className: "dsh-recall-cfg-tag" }, "\u5DF2\u4FEE\u6539") : null,
1239
+ overridden && overridden[key] !== void 0 ? React.createElement("span", { className: "dsh-recall-cfg-tag" }, "\u5DF2\u8986\u76D6") : null,
1240
+ locked ? React.createElement("span", { className: "dsh-recall-cfg-tag" }, "\u73AF\u5883\u53D8\u91CF\u9501\u5B9A") : null
1241
+ ),
1242
+ React.createElement("div", { className: "dsh-recall-cfg-hint" }, hint)
1243
+ );
1244
+ }
1245
+ function resetDefaults() {
1246
+ if (state.busy || !writable) return;
1247
+ setState({ busy: true, message: "\u6062\u590D\u9ED8\u8BA4\u4E2D\u2026", error: false });
1248
+ api("config-reset", {}).then((res) => {
1249
+ if (res && res.ok) {
1250
+ load();
1251
+ setState({ busy: false, message: "\u5DF2\u6062\u590D\u9ED8\u8BA4\u503C", error: false });
1252
+ } else {
1253
+ setState({ busy: false, message: res && (res.message || res.error) || "\u6062\u590D\u9ED8\u8BA4\u5931\u8D25", error: true });
1254
+ }
1255
+ }).catch((e) => setState({ busy: false, message: String(e), error: true }));
1256
+ }
1257
+ if (!draft) {
1258
+ return React.createElement("div", { className: "dsh-recall-ex-note" }, state.message || "\u6B63\u5728\u8BFB\u53D6\u914D\u7F6E\u2026");
1259
+ }
1260
+ return React.createElement(
1261
+ "div",
1262
+ { className: "dsh-recall-ex-card" },
1263
+ React.createElement(
1264
+ "div",
1265
+ { className: "dsh-recall-cfg-row", key: "snapshotEnabled" },
1266
+ React.createElement(
1267
+ "div",
1268
+ { className: "dsh-recall-cfg-line" },
1269
+ React.createElement("label", { className: "dsh-recall-cfg-label", htmlFor: "dsh-recall-cfg-snapshot" }, "\u542F\u7528\u5FEB\u7167"),
1270
+ React.createElement("input", {
1271
+ id: "dsh-recall-cfg-snapshot",
1272
+ type: "checkbox",
1273
+ checked: Boolean(draft.snapshotEnabled),
1274
+ disabled: !writable,
1275
+ onChange: (e) => edit("snapshotEnabled", e.target.checked)
1276
+ }),
1277
+ draft.snapshotEnabled !== baseline.snapshotEnabled ? React.createElement("span", { className: "dsh-recall-cfg-tag" }, "\u5DF2\u4FEE\u6539") : null,
1278
+ overridden && overridden.snapshotEnabled !== void 0 ? React.createElement("span", { className: "dsh-recall-cfg-tag" }, "\u5DF2\u8986\u76D6") : null
1279
+ ),
1280
+ React.createElement("div", { className: "dsh-recall-cfg-hint" }, "\u5173\u95ED\u540E\u4E0D\u518D\u65B0\u5EFA\u5FEB\u7167\uFF08\u5DF2\u6709\u5FEB\u7167\u4ECD\u53EF\u64A4\u56DE\uFF09\uFF0C\u9002\u5408\u4E34\u65F6\u7981\u7528\u5FEB\u7167\u7684\u573A\u5408")
1281
+ ),
1282
+ numRow("gcSnaps", "gc \u89E6\u53D1\u6761\u6570", "\u6BCF\u79EF\u7D2F\u591A\u5C11\u6761\u5FEB\u7167\u89E6\u53D1\u4E00\u6B21 git gc", { min: 1, step: 1 }),
1283
+ numRow("gcHours", "gc \u89E6\u53D1\u5C0F\u65F6", "\u8DDD\u4E0A\u6B21 gc \u8D85\u8FC7\u591A\u5C11\u5C0F\u65F6\u89E6\u53D1\uFF08\u4E0E\u6761\u6570\u5148\u5230\u5148\u89E6\u53D1\uFF09", { min: 1, step: 1 }),
1284
+ numRow("maxFileBytes", "\u6587\u4EF6\u5927\u5C0F\u4E0A\u9650", "\u8D85\u8FC7\u8BE5\u5927\u5C0F\u7684\u6587\u4EF6\u4E0D\u8FDB\u5FEB\u7167\u3001\u4E0D\u88AB\u56DE\u9000\u89E6\u78B0\uFF08\u5355\u4F4D MB\uFF0C\u652F\u6301\u5C0F\u6570\uFF09", { suffix: "MB", min: 0.01, step: 0.5 }),
1285
+ numRow("maxSnapshotsPerWorkspace", "\u5FEB\u7167\u603B\u91CF\u4E0A\u9650", "\u6BCF\u4E2A\u5DE5\u4F5C\u533A\u4FDD\u7559\u7684\u6700\u5927\u5FEB\u7167\u6570\uFF0C\u8D85\u9650\u81EA\u52A8\u5220\u9664\u6700\u65E7\u7684\uFF1B\u586B 0 \u8868\u793A\u4E0D\u9650\u5236", { min: 0, step: 1 }),
1286
+ numRow("retentionDays", "\u5FEB\u7167\u4FDD\u7559\u5929\u6570", "\u6309\u5929\u6570\u4FDD\u7559\u5FEB\u7167\uFF0C\u8D85\u671F\u81EA\u52A8\u5220\u9664\u6700\u65E7\u7684\uFF1B\u586B 0 \u8868\u793A\u4E0D\u542F\u7528\uFF08\u4E0E\u5FEB\u7167\u603B\u6570\u4E0A\u9650\u5404\u81EA\u751F\u6548\uFF09", { min: 0, step: 1 }),
1287
+ React.createElement(
1288
+ "div",
1289
+ { className: "dsh-recall-cfg-row", key: "refillDraft" },
1290
+ React.createElement(
1291
+ "div",
1292
+ { className: "dsh-recall-cfg-line" },
1293
+ React.createElement("label", { className: "dsh-recall-cfg-label", htmlFor: "dsh-recall-cfg-refill" }, "\u64A4\u56DE\u540E\u56DE\u586B\u8F93\u5165\u6846"),
1294
+ React.createElement("input", {
1295
+ id: "dsh-recall-cfg-refill",
1296
+ type: "checkbox",
1297
+ checked: Boolean(draft.refillDraft),
1298
+ disabled: !writable,
1299
+ onChange: (e) => edit("refillDraft", e.target.checked)
1300
+ }),
1301
+ draft.refillDraft !== baseline.refillDraft ? React.createElement("span", { className: "dsh-recall-cfg-tag" }, "\u5DF2\u4FEE\u6539") : null,
1302
+ overridden && overridden.refillDraft !== void 0 ? React.createElement("span", { className: "dsh-recall-cfg-tag" }, "\u5DF2\u8986\u76D6") : null
1303
+ ),
1304
+ React.createElement("div", { className: "dsh-recall-cfg-hint" }, "\u64A4\u56DE\u6210\u529F\u540E\u628A\u88AB\u64A4\u56DE\u7684\u6D88\u606F\u6587\u672C\u56DE\u586B\u5230\u8F93\u5165\u6846\uFF0C\u65B9\u4FBF\u4FEE\u6539\u540E\u91CD\u65B0\u53D1\u9001")
1305
+ ),
1306
+ React.createElement(
1307
+ "div",
1308
+ { className: "dsh-recall-cfg-row", key: "archiveOriginal" },
1309
+ React.createElement(
1310
+ "div",
1311
+ { className: "dsh-recall-cfg-line" },
1312
+ React.createElement("label", { className: "dsh-recall-cfg-label", htmlFor: "dsh-recall-cfg-archive" }, "\u64A4\u56DE\u540E\u5F52\u6863\u539F\u4F1A\u8BDD"),
1313
+ React.createElement("input", {
1314
+ id: "dsh-recall-cfg-archive",
1315
+ type: "checkbox",
1316
+ checked: Boolean(draft.archiveOriginal),
1317
+ disabled: !writable,
1318
+ onChange: (e) => edit("archiveOriginal", e.target.checked)
1319
+ }),
1320
+ draft.archiveOriginal !== baseline.archiveOriginal ? React.createElement("span", { className: "dsh-recall-cfg-tag" }, "\u5DF2\u4FEE\u6539") : null,
1321
+ overridden && overridden.archiveOriginal !== void 0 ? React.createElement("span", { className: "dsh-recall-cfg-tag" }, "\u5DF2\u8986\u76D6") : null
1322
+ ),
1323
+ React.createElement("div", { className: "dsh-recall-cfg-hint" }, "\u64A4\u56DE\u540E\u539F\u4F1A\u8BDD\u4ECE\u5217\u8868\u5F52\u6863\u9690\u85CF\uFF08\u53EF\u4ECE\u5F52\u6863\u627E\u56DE\uFF09\uFF1B\u5173\u95ED\u5219\u4FDD\u7559\u5728\u5217\u8868\u4E2D\uFF0C\u65B9\u4FBF\u5BF9\u7167\u56DE\u9000\u524D\u540E\u7684\u4E0A\u4E0B\u6587")
1324
+ ),
1325
+ React.createElement(SectionToggle, { title: "\u9AD8\u7EA7\uFF1A\u57FA\u7840\u6392\u9664\u8868", open: showAdvanced, onToggle: () => setShowAdvanced((v) => !v) }),
1326
+ showAdvanced ? React.createElement(
1327
+ "div",
1328
+ { className: "dsh-recall-cfg-row", key: "baseExcludes" },
1329
+ React.createElement(
1330
+ "div",
1331
+ { className: "dsh-recall-cfg-line" },
1332
+ React.createElement("label", { className: "dsh-recall-cfg-label" }, "\u57FA\u7840\u6392\u9664\u8868"),
1333
+ draft.baseExcludes !== baseline.baseExcludes ? React.createElement("span", { className: "dsh-recall-cfg-tag" }, "\u5DF2\u4FEE\u6539") : null,
1334
+ overridden && overridden.baseExcludes !== void 0 ? React.createElement("span", { className: "dsh-recall-cfg-tag" }, "\u5DF2\u8986\u76D6") : null
1335
+ ),
1336
+ React.createElement("textarea", {
1337
+ className: "dsh-recall-cfg-area",
1338
+ rows: 4,
1339
+ value: draft.baseExcludes,
1340
+ disabled: !writable,
1341
+ onChange: (e) => edit("baseExcludes", e.target.value)
1342
+ }),
1343
+ React.createElement("div", { className: "dsh-recall-cfg-hint" }, "\u5185\u7F6E\u89C4\u5219\uFF0C\u6BCF\u4E2A\u5DE5\u4F5C\u533A\u5171\u4EAB\uFF0C\u5EFA\u8BAE\u4FDD\u6301\u9ED8\u8BA4\uFF1Bgitignore \u8BED\u6CD5\u6BCF\u884C\u4E00\u6761\uFF0C\u4F18\u5148\u7EA7\u4F4E\u4E8E\u300C\u6392\u9664\u914D\u7F6E\u300D\u91CC\u7684 exclude.txt\uFF08S3-2 \u6298\u53E0\uFF09")
1344
+ ) : null,
1345
+ React.createElement(
1346
+ "div",
1347
+ { className: "dsh-recall-panel-actions" },
1348
+ state.message ? React.createElement("span", { className: "dsh-recall-ex-status" + (state.error ? " dsh-recall-ex-status-error" : " dsh-recall-ex-status-success") }, state.message) : null,
1349
+ React.createElement("button", { type: "button", className: "dsh-recall-btn", disabled: state.busy || !writable, onClick: () => setDraft(Object.assign({}, baseline)) }, "\u653E\u5F03\u4FEE\u6539"),
1350
+ React.createElement("button", {
1351
+ type: "button",
1352
+ className: "dsh-recall-btn",
1353
+ disabled: state.busy || !writable,
1354
+ title: "\u628A\u6240\u6709\u5B57\u6BB5\u6062\u590D\u5230\u63D2\u4EF6\u51FA\u5382\u9ED8\u8BA4\u503C",
1355
+ onClick: resetDefaults
1356
+ }, "\u6062\u590D\u9ED8\u8BA4"),
1357
+ React.createElement("button", { type: "button", className: "dsh-recall-btn", disabled: state.busy || !writable, onClick: save }, "\u4FDD\u5B58"),
1358
+ !writable ? React.createElement("span", { className: "dsh-recall-cfg-tag" }, "\u53EA\u8BFB\u8BBE\u7F6E\u6E90") : null
1359
+ )
1360
+ );
1361
+ }
1362
+ function SectionToggle(props) {
1363
+ return React.createElement(
1364
+ "button",
1365
+ {
1366
+ type: "button",
1367
+ className: "dsh-recall-cardbtn",
1368
+ "aria-expanded": props.open,
1369
+ onClick: props.onToggle
1370
+ },
1371
+ React.createElement("span", { className: "dsh-recall-tree-toggle" }, props.open ? "\u25BE" : "\u25B8"),
1372
+ React.createElement("span", { style: { fontWeight: 600, fontSize: "14px", lineHeight: "22px" } }, props.title),
1373
+ props.meta ? React.createElement("span", { className: "dsh-recall-tree-meta" }, props.meta) : null
1374
+ );
1375
+ }
1376
+ function RecallSettingsCard() {
1377
+ const [open, setOpen] = React.useState(false);
1378
+ const [sections, setSections] = React.useState({ exclude: false, manage: false });
1379
+ function toggle(key) {
1380
+ setSections((prev) => Object.assign({}, prev, { [key]: !prev[key] }));
1381
+ }
1382
+ return React.createElement(
1383
+ "li",
1384
+ { className: "dsh-recall-card" + (open ? " dsh-recall-card-open" : "") },
1385
+ React.createElement(
1386
+ "button",
1387
+ {
1388
+ type: "button",
1389
+ className: "dsh-recall-cardbtn",
1390
+ "aria-expanded": open,
1391
+ "aria-label": (open ? "\u6536\u8D77" : "\u5C55\u5F00") + ": \u64A4\u56DE\u63D2\u4EF6",
1392
+ onClick: () => setOpen((v) => !v)
1393
+ },
1394
+ React.createElement(
1395
+ "span",
1396
+ { className: "dsh-recall-card-head" },
1397
+ React.createElement("span", { className: "dsh-recall-card-name" }, "\u64A4\u56DE\u63D2\u4EF6"),
1398
+ React.createElement("span", { className: "dsh-recall-card-desc" }, "\u6D88\u606F\u64A4\u56DE\uFF08\u6587\u4EF6\u5FEB\u7167 + \u5BF9\u8BDD\u56DE\u9000\uFF09\u7684\u9608\u503C\u4E0E\u6CBB\u7406")
1399
+ ),
1400
+ React.createElement("svg", {
1401
+ width: 14,
1402
+ height: 14,
1403
+ viewBox: "0 0 16 16",
1404
+ style: { color: "var(--dsw-alias-label-tertiary)", flex: "none", transition: "transform .16s", transform: open ? "rotate(180deg)" : "none" }
1405
+ }, React.createElement("path", { d: "M4 6l4 4 4-4", fill: "none", stroke: "currentColor", strokeWidth: 1.5, strokeLinecap: "round", strokeLinejoin: "round" }))
1406
+ ),
1407
+ open ? React.createElement(
1408
+ "div",
1409
+ { className: "dsh-recall-card-body" },
1410
+ React.createElement(ConfigForm),
1411
+ React.createElement(SectionToggle, { title: "\u6392\u9664\u914D\u7F6E\uFF08exclude.txt\uFF09", open: sections.exclude, onToggle: () => toggle("exclude") }),
1412
+ sections.exclude ? React.createElement(ExcludeFilesSection) : null,
1413
+ React.createElement(SectionToggle, { title: "\u5FEB\u7167\u7BA1\u7406", open: sections.manage, onToggle: () => toggle("manage") }),
1414
+ sections.manage ? React.createElement(ManageCard) : null
1415
+ ) : null
1416
+ );
1417
+ }
1418
+ return { RecallSettingsCard };
1419
+ }
1420
+
1421
+ // src/client/app.js
1422
+ function createApp(React) {
1423
+ return function apply(ctx) {
1424
+ const slots = ctx.slots;
1425
+ if (!slots) return;
1426
+ const sessionsSvc = ctx.sessions;
1427
+ const workspacesSvc = ctx.workspaces;
1428
+ const stylesSvc = ctx.get("styles");
1429
+ if (stylesSvc && typeof stylesSvc.insert === "function") {
1430
+ stylesSvc.insert(CSS);
1431
+ } else if (typeof document !== "undefined") {
1432
+ const tag = document.createElement("style");
1433
+ tag.setAttribute("data-plugin", "dsh-recall-plugin");
1434
+ tag.textContent = CSS;
1435
+ document.head.appendChild(tag);
1436
+ }
1437
+ const util = buildUtil();
1438
+ const { UserRecallNode } = buildRecallNode(React, util, ctx, sessionsSvc, workspacesSvc);
1439
+ const { RecallSettingsCard } = buildSettingsCards(React, util, sessionsSvc);
1440
+ for (const slotKey of ["user", "steering"]) {
1441
+ let mounted = false;
1442
+ for (let priority = -1; priority >= -3 && !mounted; priority--) {
1443
+ try {
1444
+ slots.inject("conversation.chat.node", () => slots.register(
1445
+ { name: "conversation.chat.node", key: slotKey, priority },
1446
+ UserRecallNode
1447
+ ));
1448
+ mounted = true;
1449
+ } catch (error) {
1450
+ if (priority === -3) console.error("[dsh-recall-plugin] slot register failed (" + slotKey + "):", error);
1451
+ }
1452
+ }
1453
+ }
1454
+ try {
1455
+ slots.inject("settings.plugin.item", () => slots.register(
1456
+ { name: "settings.plugin.item", key: "dsh-recall" },
1457
+ RecallSettingsCard
1458
+ ));
1459
+ } catch (error) {
1460
+ console.error("[dsh-recall-plugin] settings card register failed:", error);
1461
+ }
1462
+ };
1463
+ }
1464
+
1465
+ // src/client/entry.js
1466
+ window.__ModuleLoader__.load({
1467
+ id: "dsh-recall-plugin",
1468
+ factory: (require2) => {
1469
+ const React = require2("react");
1470
+ return {
1471
+ name: "dsh-recall-plugin",
1472
+ inject: ["slots", "sessions", "workspaces", "timer"],
1473
+ apply: createApp(React)
1474
+ };
1475
+ }
1476
+ });