@anionex/dsh-turn-rewind 0.1.2 → 0.2.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +19 -6
- package/README.zh.md +19 -6
- package/SECURITY.md +3 -1
- package/docs/assets/turn-rewind-dialog.png +0 -0
- package/lib/client.js +295 -25
- package/lib/client.js.map +1 -1
- package/lib/engine.js +140 -3
- package/lib/index.js +17 -1
- package/lib/rewind-host.js +104 -9
- package/lib/settings.js +58 -0
- package/lib/store.js +90 -23
- package/lib/types/client/index.d.ts +74 -7
- package/lib/types/engine.d.ts +21 -2
- package/lib/types/index.d.ts +7 -0
- package/lib/types/rewind-host.d.ts +16 -2
- package/lib/types/settings.d.ts +44 -0
- package/lib/types/store.d.ts +20 -0
- package/lib/types/types.d.ts +19 -0
- package/package.json +19 -3
- package/src/client/index.tsx +540 -45
- package/src/engine.ts +154 -3
- package/src/index.ts +20 -1
- package/src/rewind-host.ts +122 -9
- package/src/settings.ts +93 -0
- package/src/store.ts +109 -23
- package/src/types.ts +21 -0
package/lib/client.js
CHANGED
|
@@ -7,6 +7,8 @@ exports.selectRewindMessage = selectRewindMessage;
|
|
|
7
7
|
exports.apply = apply;
|
|
8
8
|
exports.RewindMessagePortals = RewindMessagePortals;
|
|
9
9
|
exports.RewindMessageAction = RewindMessageAction;
|
|
10
|
+
exports.TurnRewindSettingsCard = TurnRewindSettingsCard;
|
|
11
|
+
exports.formatBytes = formatBytes;
|
|
10
12
|
exports.selectRewindMessageTarget = selectRewindMessageTarget;
|
|
11
13
|
exports.fileRecoveryLabel = fileRecoveryLabel;
|
|
12
14
|
const jsx_runtime_1 = require("react/jsx-runtime");
|
|
@@ -14,6 +16,7 @@ const react_1 = require("react");
|
|
|
14
16
|
const react_dom_1 = require("react-dom");
|
|
15
17
|
const dsh_client_ui_primitives_1 = require("@deepseek-ai/dsh-client-ui-primitives");
|
|
16
18
|
const PATH = '/turn-rewind';
|
|
19
|
+
const MANAGE_PATH = '/turn-rewind/manage';
|
|
17
20
|
const STYLE_ID = '@anionex/dsh-turn-rewind';
|
|
18
21
|
const styles = `
|
|
19
22
|
.dcl-rewind-tail{display:inline-flex;align-items:center;align-self:center;order:0;height:24px;margin-left:2px}
|
|
@@ -42,6 +45,36 @@ const styles = `
|
|
|
42
45
|
.dcl-rewind-error{border:1px solid color-mix(in srgb,var(--dsw-alias-state-error-primary) 30%,transparent);color:var(--dsw-alias-state-error-primary)}
|
|
43
46
|
.dcl-rewind-backup{box-sizing:border-box;margin:0;padding:10px 12px;border-radius:10px;background:var(--dsw-alias-bg-layer-2);color:var(--dsw-alias-label-secondary);font-size:12px;line-height:18px}
|
|
44
47
|
.dcl-rewind-retry{align-self:flex-start}
|
|
48
|
+
.dcl-trs-card{display:flex;flex-direction:column;gap:16px;width:100%;min-width:0}
|
|
49
|
+
.dcl-trs-section{display:flex;flex-direction:column;gap:10px;min-width:0}
|
|
50
|
+
.dcl-trs-section-title{display:flex;align-items:center;justify-content:space-between;gap:12px;min-width:0}
|
|
51
|
+
.dcl-trs-section-title strong{color:var(--dsw-alias-label-primary);font-size:14px}
|
|
52
|
+
.dcl-trs-section-title-actions{display:inline-flex;align-items:center;gap:8px}
|
|
53
|
+
.dcl-trs-field{display:flex;flex-wrap:wrap;align-items:center;gap:8px 12px;padding:8px 0;border-bottom:1px solid var(--dsw-alias-border-l1);font-size:13px}
|
|
54
|
+
.dcl-trs-field:last-child{border-bottom:0}
|
|
55
|
+
.dcl-trs-field-label{flex:1 1 200px;min-width:0}
|
|
56
|
+
.dcl-trs-field-label strong{display:block;color:var(--dsw-alias-label-primary);font-size:13px;font-weight:600}
|
|
57
|
+
.dcl-trs-field-desc{display:block;margin-top:2px;color:var(--dsw-alias-label-tertiary);font-size:12px}
|
|
58
|
+
.dcl-trs-field-control{display:inline-flex;align-items:center;gap:8px;flex:none}
|
|
59
|
+
.dcl-trs-field-control input,.dcl-trs-field-control select{box-sizing:border-box;min-width:140px;padding:4px 8px;border:1px solid var(--dsw-alias-border-l2);border-radius:8px;background:var(--dsw-alias-bg-layer-1);color:var(--dsw-alias-label-primary);font-size:13px}
|
|
60
|
+
.dcl-trs-field-control input:disabled,.dcl-trs-field-control select:disabled{opacity:.52;cursor:not-allowed}
|
|
61
|
+
.dcl-trs-override{flex:none;padding:1px 8px;border-radius:999px;background:var(--dsw-alias-state-business-primary);color:var(--dsw-alias-bg-layer-0);font-size:11px;line-height:18px}
|
|
62
|
+
.dcl-trs-manage-total{margin:0;color:var(--dsw-alias-label-secondary);font-size:12px;overflow-wrap:anywhere}
|
|
63
|
+
.dcl-trs-workspace{display:flex;flex-direction:column;gap:6px;padding:10px 12px;border:1px solid var(--dsw-alias-border-l2);border-radius:10px;background:var(--dsw-alias-bg-layer-1)}
|
|
64
|
+
.dcl-trs-workspace-head{display:flex;flex-wrap:wrap;align-items:center;gap:6px 12px;min-width:0}
|
|
65
|
+
.dcl-trs-workspace-path{flex:1 1 auto;min-width:0;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;color:var(--dsw-alias-label-primary);font-size:12px}
|
|
66
|
+
.dcl-trs-workspace-meta{flex:none;color:var(--dsw-alias-label-tertiary);font-size:12px}
|
|
67
|
+
.dcl-trs-badge{flex:none;padding:1px 8px;border-radius:999px;background:var(--dsw-alias-state-warn-tertiary);color:var(--dsw-alias-state-warn-primary);font-size:11px;line-height:18px}
|
|
68
|
+
.dcl-trs-points{display:flex;flex-direction:column;gap:4px;min-width:0;margin:0;padding:0;list-style:none}
|
|
69
|
+
.dcl-trs-point{display:flex;flex-wrap:wrap;align-items:center;gap:4px 12px;padding:6px 8px;border:1px solid var(--dsw-alias-border-l1);border-radius:8px;font-size:12px}
|
|
70
|
+
.dcl-trs-point time{flex:1 1 140px;min-width:0;color:var(--dsw-alias-label-secondary)}
|
|
71
|
+
.dcl-trs-point-kind,.dcl-trs-point-size,.dcl-trs-point-files{flex:none;color:var(--dsw-alias-label-tertiary)}
|
|
72
|
+
.dcl-trs-point code{flex:1 1 140px;min-width:0;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;color:var(--dsw-alias-label-tertiary)}
|
|
73
|
+
.dcl-trs-status{margin:0;color:var(--dsw-alias-label-secondary);font-size:13px;line-height:20px}
|
|
74
|
+
.dcl-trs-notice{box-sizing:border-box;max-width:100%;margin:0;padding:8px 10px;overflow-wrap:anywhere;border-radius:8px;background:var(--dsw-alias-bg-layer-2);color:var(--dsw-alias-label-secondary);font-size:12px;line-height:18px}
|
|
75
|
+
.dcl-trs-notice[data-warning="true"]{background:var(--dsw-alias-state-warn-tertiary);color:var(--dsw-alias-state-warn-primary)}
|
|
76
|
+
.dcl-trs-error{box-sizing:border-box;max-width:100%;margin:0;padding:10px 12px;overflow-wrap:anywhere;word-break:break-word;border:1px solid color-mix(in srgb,var(--dsw-alias-state-error-primary) 30%,transparent);border-radius:10px;color:var(--dsw-alias-state-error-primary);font-size:12px;line-height:18px}
|
|
77
|
+
.dcl-trs-storage{margin:0;padding:8px 10px;border-radius:8px;background:var(--dsw-alias-bg-layer-2);color:var(--dsw-alias-label-tertiary);font-size:12px;overflow-wrap:anywhere}
|
|
45
78
|
`;
|
|
46
79
|
/** Return the rewind anchor and editable text owned by one direct user message. */
|
|
47
80
|
function selectRewindMessage(node) {
|
|
@@ -76,6 +109,13 @@ function apply(ctx) {
|
|
|
76
109
|
},
|
|
77
110
|
}),
|
|
78
111
|
}, RewindMessagePortals));
|
|
112
|
+
ctx.slots.inject('settings.plugin.item', () => ctx.slots.register({
|
|
113
|
+
name: 'settings.plugin.item',
|
|
114
|
+
key: 'turn-rewind',
|
|
115
|
+
inject: () => ({
|
|
116
|
+
scope: ctx.settingsScope?.bind({ namespace: 'turn-rewind' }),
|
|
117
|
+
}),
|
|
118
|
+
}, TurnRewindSettingsCard));
|
|
79
119
|
}
|
|
80
120
|
/** Session-scoped bridge that portals rewind controls into direct user-message action rows. */
|
|
81
121
|
function RewindMessagePortals({ sessionId, openRestoredSession, useSession }) {
|
|
@@ -122,6 +162,7 @@ function RewindMessageAction({ matched, sessionId, openRestoredSession }) {
|
|
|
122
162
|
const [completed, setCompleted] = (0, react_1.useState)(null);
|
|
123
163
|
const loadAbort = (0, react_1.useRef)(null);
|
|
124
164
|
const applyPending = (0, react_1.useRef)(false);
|
|
165
|
+
const modeTouched = (0, react_1.useRef)(false);
|
|
125
166
|
(0, react_1.useEffect)(() => () => {
|
|
126
167
|
loadAbort.current?.abort();
|
|
127
168
|
loadAbort.current = null;
|
|
@@ -138,13 +179,20 @@ function RewindMessageAction({ matched, sessionId, openRestoredSession }) {
|
|
|
138
179
|
const response = await fetch(`${PATH}?sessionId=${encodeURIComponent(sessionId)}&messageSeq=${String(matched.messageSeq)}`, {
|
|
139
180
|
method: 'GET', headers: { accept: 'application/json' }, cache: 'no-store', signal: controller.signal,
|
|
140
181
|
});
|
|
141
|
-
const
|
|
142
|
-
if (loadAbort.current === controller)
|
|
143
|
-
setPreview(
|
|
182
|
+
const decoded = decodePreview(await responseJson(response));
|
|
183
|
+
if (loadAbort.current === controller) {
|
|
184
|
+
setPreview(decoded);
|
|
185
|
+
if (!modeTouched.current) {
|
|
186
|
+
setMode(decoded.status === 'ready' && decoded.totalChanges > 0 ? 'both' : 'messages');
|
|
187
|
+
}
|
|
188
|
+
}
|
|
144
189
|
}
|
|
145
190
|
catch (caught) {
|
|
146
|
-
if (!controller.signal.aborted)
|
|
147
|
-
|
|
191
|
+
if (!controller.signal.aborted) {
|
|
192
|
+
setPreview({ status: 'failed', error: friendlyError(caught) });
|
|
193
|
+
if (!modeTouched.current)
|
|
194
|
+
setMode('messages');
|
|
195
|
+
}
|
|
148
196
|
}
|
|
149
197
|
finally {
|
|
150
198
|
if (loadAbort.current === controller) {
|
|
@@ -157,6 +205,7 @@ function RewindMessageAction({ matched, sessionId, openRestoredSession }) {
|
|
|
157
205
|
setOpen(true);
|
|
158
206
|
setPreview(null);
|
|
159
207
|
setMode('both');
|
|
208
|
+
modeTouched.current = false;
|
|
160
209
|
setStale(false);
|
|
161
210
|
void load();
|
|
162
211
|
};
|
|
@@ -171,6 +220,7 @@ function RewindMessageAction({ matched, sessionId, openRestoredSession }) {
|
|
|
171
220
|
const chooseMode = (next) => {
|
|
172
221
|
if (applying)
|
|
173
222
|
return;
|
|
223
|
+
modeTouched.current = true;
|
|
174
224
|
setMode(next);
|
|
175
225
|
setError(null);
|
|
176
226
|
setCompleted(null);
|
|
@@ -181,16 +231,14 @@ function RewindMessageAction({ matched, sessionId, openRestoredSession }) {
|
|
|
181
231
|
const sharedBlocked = (ready?.activeSessionIds.length ?? 0) > 0;
|
|
182
232
|
const planMissing = hasFileChanges && ready !== null && !sharedBlocked && !driftBlocked
|
|
183
233
|
&& (ready.planId === undefined || ready.confirmation === undefined);
|
|
184
|
-
const canApply =
|
|
234
|
+
const canApply = preview !== null
|
|
185
235
|
&& !loading
|
|
186
236
|
&& !applying
|
|
187
237
|
&& !loadingDetails
|
|
188
238
|
&& completed === null
|
|
189
|
-
&&
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
&& !planMissing
|
|
193
|
-
&& !stale;
|
|
239
|
+
&& (mode === 'messages'
|
|
240
|
+
? true
|
|
241
|
+
: ready !== null && hasFileChanges && !driftBlocked && !sharedBlocked && !planMissing && !stale);
|
|
194
242
|
const loadAllChanges = async () => {
|
|
195
243
|
if (ready === null || loadingDetails || !ready.truncated)
|
|
196
244
|
return;
|
|
@@ -229,18 +277,20 @@ function RewindMessageAction({ matched, sessionId, openRestoredSession }) {
|
|
|
229
277
|
}
|
|
230
278
|
};
|
|
231
279
|
const applyRestore = async () => {
|
|
232
|
-
if (
|
|
280
|
+
if (preview === null || !canApply || applyPending.current)
|
|
233
281
|
return;
|
|
234
282
|
const body = {
|
|
235
283
|
mode,
|
|
236
284
|
sessionId,
|
|
237
|
-
messageSeq:
|
|
238
|
-
checkpointId: ready.checkpointId,
|
|
285
|
+
messageSeq: matched.messageSeq,
|
|
239
286
|
};
|
|
240
|
-
if (
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
287
|
+
if (mode !== 'messages') {
|
|
288
|
+
if (ready === null || ready.planId === undefined || ready.confirmation === undefined)
|
|
289
|
+
return;
|
|
290
|
+
body.checkpointId = ready.checkpointId;
|
|
291
|
+
body.planId = ready.planId;
|
|
292
|
+
body.confirmation = ready.confirmation;
|
|
293
|
+
}
|
|
244
294
|
applyPending.current = true;
|
|
245
295
|
setApplying(true);
|
|
246
296
|
setError(null);
|
|
@@ -254,6 +304,18 @@ function RewindMessageAction({ matched, sessionId, openRestoredSession }) {
|
|
|
254
304
|
const resultMode = requiredString(result.mode, 'mode');
|
|
255
305
|
if (resultMode !== mode)
|
|
256
306
|
throw new Error(`服务器返回了不匹配的回退模式:${resultMode}`);
|
|
307
|
+
if (mode === 'messages') {
|
|
308
|
+
const childSessionId = requiredString(result.sessionId, 'sessionId');
|
|
309
|
+
setCompleted('已创建从这里开始的新对话;项目文件保持不变。');
|
|
310
|
+
try {
|
|
311
|
+
await openRestoredSession(childSessionId, matched.promptText);
|
|
312
|
+
setOpen(false);
|
|
313
|
+
}
|
|
314
|
+
catch (navigationError) {
|
|
315
|
+
setError(`新对话已创建,但没能自动打开:${messageOf(navigationError)}`);
|
|
316
|
+
}
|
|
317
|
+
return;
|
|
318
|
+
}
|
|
257
319
|
if (mode === 'code') {
|
|
258
320
|
requiredString(result.rescuePointId, 'rescuePointId');
|
|
259
321
|
setCompleted('项目文件已恢复;当前对话保持不变。恢复前的文件已自动备份。');
|
|
@@ -281,12 +343,217 @@ function RewindMessageAction({ matched, sessionId, openRestoredSession }) {
|
|
|
281
343
|
setApplying(false);
|
|
282
344
|
}
|
|
283
345
|
};
|
|
284
|
-
const actionLabel = mode === 'both' ? '恢复并从这里继续' : '恢复文件';
|
|
346
|
+
const actionLabel = mode === 'both' ? '恢复并从这里继续' : mode === 'code' ? '恢复文件' : '只回溯消息';
|
|
285
347
|
const radioName = `dcl-rewind-${sessionId}-${String(matched.messageSeq)}`;
|
|
286
348
|
const branchChanged = ready !== null && ready.checkpointBranch !== ready.currentBranch;
|
|
287
|
-
return ((0, jsx_runtime_1.jsxs)("div", { className: "dcl-rewind-tail", children: [(0, jsx_runtime_1.jsx)(dsh_client_ui_primitives_1.Tooltip, { label: "\u6062\u590D\u5230\u53D1\u9001\u8FD9\u6761\u6D88\u606F\u4E4B\u524D", side: "bottom", children: (0, jsx_runtime_1.jsx)("button", { type: "button", className: "dcl-rewind-trigger", onClick: show, "aria-label": "\u6062\u590D\u5230\u53D1\u9001\u8FD9\u6761\u6D88\u606F\u4E4B\u524D", children: (0, jsx_runtime_1.jsx)(RewindIcon, { size: 16 }) }) }), (0, jsx_runtime_1.jsx)(dsh_client_ui_primitives_1.Modal, { open: open, onClose: close, title: "\u6062\u590D\u5230\u53D1\u9001\u8FD9\u6761\u6D88\u606F\u4E4B\u524D", closeLabel: "\u5173\u95ED", description: "\u67E5\u770B\u6062\u590D\u7684\u6587\u4EF6\uFF0C\u9009\u62E9\u9002\u5408\u4F60\u7684\u56DE\u9000\u65B9\u5F0F\u3002\u5F53\u524D\u4F1A\u8BDD\u4E0D\u53D7\u5F71\u54CD\u3002", className: "dcl-rewind-dialog", contentClassName: "dcl-rewind-content", footer: ((0, jsx_runtime_1.jsxs)(jsx_runtime_1.Fragment, { children: [(0, jsx_runtime_1.jsx)(dsh_client_ui_primitives_1.Button, { variant: "outline", onClick: close, disabled: applying, children: "\u53D6\u6D88" }), (0, jsx_runtime_1.jsx)(dsh_client_ui_primitives_1.Button, { variant: "primary", onClick: () => { void applyRestore(); }, disabled: !canApply, children: applying ? '正在恢复…' : completed === null ? actionLabel : '已完成' })] })), children: (0, jsx_runtime_1.jsxs)("div", { className: "dcl-rewind-body", children: [loading && (0, jsx_runtime_1.jsx)("p", { className: "dcl-rewind-status", children: "\u6B63\u5728\u68C0\u67E5\u53EF\u4EE5\u6062\u590D\u7684\u9879\u76EE\u6587\u4EF6\u2026" }), preview?.status === 'pending' && (0, jsx_runtime_1.jsx)("p", { className: "dcl-rewind-status", children: "\u8FD9\u6761\u6D88\u606F\u53D1\u9001\u524D\u7684\u6587\u4EF6\u8FD8\u5728\u4FDD\u5B58\uFF0C\u8BF7\u7A0D\u540E\u518D\u8BD5\u3002" }), preview?.status === 'missing' && (0, jsx_runtime_1.jsx)("p", { className: "dcl-rewind-error", children: "\u6CA1\u6709\u4FDD\u5B58\u8FD9\u6761\u6D88\u606F\u53D1\u9001\u524D\u7684\u6587\u4EF6\u3002\u53EF\u80FD\u662F\u5F53\u65F6\u8FD8\u6CA1\u542F\u7528\u56DE\u9000\u529F\u80FD\
|
|
349
|
+
return ((0, jsx_runtime_1.jsxs)("div", { className: "dcl-rewind-tail", children: [(0, jsx_runtime_1.jsx)(dsh_client_ui_primitives_1.Tooltip, { label: "\u6062\u590D\u5230\u53D1\u9001\u8FD9\u6761\u6D88\u606F\u4E4B\u524D", side: "bottom", children: (0, jsx_runtime_1.jsx)("button", { type: "button", className: "dcl-rewind-trigger", onClick: show, "aria-label": "\u6062\u590D\u5230\u53D1\u9001\u8FD9\u6761\u6D88\u606F\u4E4B\u524D", children: (0, jsx_runtime_1.jsx)(RewindIcon, { size: 16 }) }) }), (0, jsx_runtime_1.jsx)(dsh_client_ui_primitives_1.Modal, { open: open, onClose: close, title: "\u6062\u590D\u5230\u53D1\u9001\u8FD9\u6761\u6D88\u606F\u4E4B\u524D", closeLabel: "\u5173\u95ED", description: "\u67E5\u770B\u6062\u590D\u7684\u6587\u4EF6\uFF0C\u9009\u62E9\u9002\u5408\u4F60\u7684\u56DE\u9000\u65B9\u5F0F\u3002\u5F53\u524D\u4F1A\u8BDD\u4E0D\u53D7\u5F71\u54CD\u3002", className: "dcl-rewind-dialog", contentClassName: "dcl-rewind-content", footer: ((0, jsx_runtime_1.jsxs)(jsx_runtime_1.Fragment, { children: [(0, jsx_runtime_1.jsx)(dsh_client_ui_primitives_1.Button, { variant: "outline", onClick: close, disabled: applying, children: "\u53D6\u6D88" }), (0, jsx_runtime_1.jsx)(dsh_client_ui_primitives_1.Button, { variant: "primary", onClick: () => { void applyRestore(); }, disabled: !canApply, children: applying ? '正在恢复…' : completed === null ? actionLabel : '已完成' })] })), children: (0, jsx_runtime_1.jsxs)("div", { className: "dcl-rewind-body", children: [loading && (0, jsx_runtime_1.jsx)("p", { className: "dcl-rewind-status", children: "\u6B63\u5728\u68C0\u67E5\u53EF\u4EE5\u6062\u590D\u7684\u9879\u76EE\u6587\u4EF6\u2026" }), preview?.status === 'pending' && (0, jsx_runtime_1.jsx)("p", { className: "dcl-rewind-status", children: "\u8FD9\u6761\u6D88\u606F\u53D1\u9001\u524D\u7684\u6587\u4EF6\u8FD8\u5728\u4FDD\u5B58\uFF0C\u8BF7\u7A0D\u540E\u518D\u8BD5\u3002" }), preview?.status === 'missing' && (0, jsx_runtime_1.jsx)("p", { className: "dcl-rewind-error", children: "\u6CA1\u6709\u4FDD\u5B58\u8FD9\u6761\u6D88\u606F\u53D1\u9001\u524D\u7684\u6587\u4EF6\u3002\u53EF\u80FD\u662F\u5F53\u65F6\u8FD8\u6CA1\u542F\u7528\u56DE\u9000\u529F\u80FD\u3001\u8BB0\u5F55\u5DF2\u8D85\u8FC7\u4FDD\u7559\u671F\u9650\uFF0C\u6216\u5DF2\u5173\u95ED\u81EA\u52A8\u68C0\u67E5\u70B9\u3002\u4ECD\u53EF\u53EA\u56DE\u6EAF\u6D88\u606F\u3002" }), preview?.status === 'skipped' && (0, jsx_runtime_1.jsxs)("p", { className: "dcl-rewind-status", children: ["\u4E3A\u907F\u514D\u963B\u585E\u6D88\u606F\u53D1\u9001\uFF0C\u672C\u8F6E\u6CA1\u6709\u81EA\u52A8\u4FDD\u5B58\u6587\u4EF6\uFF1A", preview.reason, "\u4ECD\u53EF\u53EA\u56DE\u6EAF\u6D88\u606F\u3002"] }), preview?.status === 'failed' && (0, jsx_runtime_1.jsxs)("p", { className: "dcl-rewind-error", children: ["\u6CA1\u80FD\u4FDD\u5B58\u8FD9\u6761\u6D88\u606F\u53D1\u9001\u524D\u7684\u6587\u4EF6\uFF1A", preview.error, "\u4ECD\u53EF\u53EA\u56DE\u6EAF\u6D88\u606F\u3002"] }), preview !== null && ((0, jsx_runtime_1.jsxs)("div", { className: "dcl-rewind-options", children: [(0, jsx_runtime_1.jsxs)("label", { className: "dcl-rewind-option", "data-selected": mode === 'both', "data-disabled": applying || !hasFileChanges, children: [(0, jsx_runtime_1.jsx)("input", { type: "radio", name: radioName, checked: mode === 'both', disabled: applying || !hasFileChanges, onChange: () => { chooseMode('both'); } }), (0, jsx_runtime_1.jsxs)("span", { className: "dcl-rewind-option-content", children: [(0, jsx_runtime_1.jsx)("strong", { children: "\u6062\u590D\u6587\u4EF6\u5E76\u4ECE\u8FD9\u91CC\u7EE7\u7EED" }), (0, jsx_runtime_1.jsx)("span", { className: "dcl-rewind-option-description", children: "\u521B\u5EFA\u4E00\u4E2A\u4ECE\u8FD9\u91CC\u5F00\u59CB\u7684\u65B0\u4F1A\u8BDD\uFF08\u5F53\u524D\u5BF9\u8BDD\u4F1A\u4FDD\u7559\uFF09" })] })] }), (0, jsx_runtime_1.jsxs)("label", { className: "dcl-rewind-option", "data-selected": mode === 'code', "data-disabled": applying || !hasFileChanges, children: [(0, jsx_runtime_1.jsx)("input", { type: "radio", name: radioName, checked: mode === 'code', disabled: applying || !hasFileChanges, onChange: () => { chooseMode('code'); } }), (0, jsx_runtime_1.jsxs)("span", { className: "dcl-rewind-option-content", children: [(0, jsx_runtime_1.jsx)("strong", { children: "\u53EA\u6062\u590D\u6587\u4EF6" }), (0, jsx_runtime_1.jsx)("span", { className: "dcl-rewind-option-description", children: "\u6062\u590D\u8FD9\u6761\u6D88\u606F\u53D1\u9001\u524D\u7684\u6587\u4EF6\uFF0C\u5F53\u524D\u5BF9\u8BDD\u4FDD\u6301\u4E0D\u53D8\u3002" })] })] }), (0, jsx_runtime_1.jsxs)("label", { className: "dcl-rewind-option", "data-selected": mode === 'messages', "data-disabled": applying, children: [(0, jsx_runtime_1.jsx)("input", { type: "radio", name: radioName, checked: mode === 'messages', disabled: applying, onChange: () => { chooseMode('messages'); } }), (0, jsx_runtime_1.jsxs)("span", { className: "dcl-rewind-option-content", children: [(0, jsx_runtime_1.jsx)("strong", { children: "\u53EA\u56DE\u6EAF\u6D88\u606F\uFF08\u4E0D\u52A8\u6587\u4EF6\uFF09" }), (0, jsx_runtime_1.jsx)("span", { className: "dcl-rewind-option-description", children: "\u521B\u5EFA\u4E00\u4E2A\u4ECE\u8FD9\u91CC\u5F00\u59CB\u7684\u65B0\u4F1A\u8BDD\uFF0C\u9879\u76EE\u6587\u4EF6\u4FDD\u6301\u5F53\u524D\u72B6\u6001\u3002" })] })] })] })), ready !== null && ((0, jsx_runtime_1.jsxs)(jsx_runtime_1.Fragment, { children: [(0, jsx_runtime_1.jsxs)("div", { className: "dcl-rewind-summary", children: [mode === 'messages'
|
|
350
|
+
? (0, jsx_runtime_1.jsx)("strong", { children: "\u9879\u76EE\u6587\u4EF6\u4FDD\u6301\u4E0D\u53D8" })
|
|
351
|
+
: (0, jsx_runtime_1.jsxs)("strong", { children: ["\u5C06\u6062\u590D ", String(ready.totalChanges), " \u4E2A\u6587\u4EF6"] }), (0, jsx_runtime_1.jsx)("span", { children: mode === 'both' ? '恢复后在新对话里继续' : mode === 'code' ? '当前对话保持不变' : '仅创建从这里继续的新对话' })] }), sharedBlocked && ((0, jsx_runtime_1.jsx)("p", { className: "dcl-rewind-error", children: "\u8FD9\u4E2A\u9879\u76EE\u76EE\u5F55\u8FD8\u6709\u522B\u7684\u5BF9\u8BDD\u6B63\u5728\u8FD0\u884C\u3002\u6062\u590D\u6587\u4EF6\u4F1A\u5F71\u54CD\u5230\u5B83\u4EEC\uFF0C\u56E0\u6B64\u672C\u6B21\u64CD\u4F5C\u5DF2\u88AB\u963B\u6B62\u3002\u8BF7\u7B49\u90A3\u4E9B\u5BF9\u8BDD\u7ED3\u675F\u6216\u505C\u6B62\u540E\uFF0C\u518D\u91CD\u65B0\u68C0\u67E5\u3002" })), ready.headChanged && !ready.operationChanged && ((0, jsx_runtime_1.jsx)("p", { className: "dcl-rewind-warning", children: branchChanged
|
|
288
352
|
? '当前所在的 Git 分支和发送这条消息时不同。恢复不会切换分支,只会把当时的文件内容恢复到当前分支。'
|
|
289
|
-
: '这条消息之后有了新的 Git 提交。恢复只会改文件,不会撤销提交;完成后这些文件会显示为未提交修改。' })), driftBlocked && (0, jsx_runtime_1.jsx)("p", { className: "dcl-rewind-warning", children: "Git \u6B63\u5728\u8FDB\u884C\u5408\u5E76\u3001\u53D8\u57FA\u6216\u7C7B\u4F3C\u64CD\u4F5C\u3002\u8BF7\u5148\u5B8C\u6210\u6216\u53D6\u6D88\u8FD9\u6B21 Git \u64CD\u4F5C\uFF0C\u518D\u91CD\u65B0\u68C0\u67E5\u3002" }), planMissing && (0, jsx_runtime_1.jsx)("p", { className: "dcl-rewind-error", children: "\u6062\u590D\u4FE1\u606F\u5DF2\u7ECF\u5931\u6548\uFF0C\u8BF7\u91CD\u65B0\u68C0\u67E5\u3002" }), stale && (0, jsx_runtime_1.jsx)("p", { className: "dcl-rewind-error", children: "\u9879\u76EE\u6587\u4EF6\u5728\u68C0\u67E5\u540E\u53C8\u53D1\u751F\u4E86\u53D8\u5316\u3002\u4E3A\u907F\u514D\u8986\u76D6\u65B0\u4FEE\u6539\uFF0C\u8FD9\u6B21\u6062\u590D\u5DF2\u5931\u6548\uFF0C\u8BF7\u91CD\u65B0\u68C0\u67E5\u3002" }), ready.totalChanges === 0 && (0, jsx_runtime_1.jsx)("p", { className: "dcl-rewind-status", children: "\u9879\u76EE\u6587\u4EF6\u5DF2\u7ECF\u662F\u8FD9\u6761\u6D88\u606F\u53D1\u9001\u524D\u7684\u72B6\u6001\uFF0C\u65E0\u9700\u6062\u590D\u3002\
|
|
353
|
+
: '这条消息之后有了新的 Git 提交。恢复只会改文件,不会撤销提交;完成后这些文件会显示为未提交修改。' })), driftBlocked && (0, jsx_runtime_1.jsx)("p", { className: "dcl-rewind-warning", children: "Git \u6B63\u5728\u8FDB\u884C\u5408\u5E76\u3001\u53D8\u57FA\u6216\u7C7B\u4F3C\u64CD\u4F5C\u3002\u8BF7\u5148\u5B8C\u6210\u6216\u53D6\u6D88\u8FD9\u6B21 Git \u64CD\u4F5C\uFF0C\u518D\u91CD\u65B0\u68C0\u67E5\u3002" }), planMissing && (0, jsx_runtime_1.jsx)("p", { className: "dcl-rewind-error", children: "\u6062\u590D\u4FE1\u606F\u5DF2\u7ECF\u5931\u6548\uFF0C\u8BF7\u91CD\u65B0\u68C0\u67E5\u3002" }), stale && (0, jsx_runtime_1.jsx)("p", { className: "dcl-rewind-error", children: "\u9879\u76EE\u6587\u4EF6\u5728\u68C0\u67E5\u540E\u53C8\u53D1\u751F\u4E86\u53D8\u5316\u3002\u4E3A\u907F\u514D\u8986\u76D6\u65B0\u4FEE\u6539\uFF0C\u8FD9\u6B21\u6062\u590D\u5DF2\u5931\u6548\uFF0C\u8BF7\u91CD\u65B0\u68C0\u67E5\u3002" }), ready.totalChanges === 0 && (0, jsx_runtime_1.jsx)("p", { className: "dcl-rewind-status", children: "\u9879\u76EE\u6587\u4EF6\u5DF2\u7ECF\u662F\u8FD9\u6761\u6D88\u606F\u53D1\u9001\u524D\u7684\u72B6\u6001\uFF0C\u65E0\u9700\u6062\u590D\u6587\u4EF6\u3002\u53EF\u9009\u62E9\u300C\u53EA\u56DE\u6EAF\u6D88\u606F\u300D\u91CD\u65B0\u5F00\u59CB\u8FD9\u6BB5\u5BF9\u8BDD\u3002" }), ready.changes.length > 0 && ((0, jsx_runtime_1.jsx)("div", { className: "dcl-rewind-files", children: ready.changes.map(change => (0, jsx_runtime_1.jsxs)("div", { className: "dcl-rewind-file", children: [(0, jsx_runtime_1.jsx)("code", { children: change.path }), (0, jsx_runtime_1.jsx)("span", { className: "dcl-rewind-kind", children: fileRecoveryLabel(change.kind) })] }, change.path)) })), ready.truncated && ((0, jsx_runtime_1.jsx)("div", { className: "dcl-rewind-file-actions", children: (0, jsx_runtime_1.jsx)(dsh_client_ui_primitives_1.Button, { variant: "outline", size: "sm", onClick: () => { void loadAllChanges(); }, disabled: loadingDetails, children: loadingDetails ? '正在读取全部文件…' : `查看全部 ${String(ready.totalChanges)} 个文件` }) }))] })), completed !== null && (0, jsx_runtime_1.jsx)("p", { className: "dcl-rewind-status", children: completed }), error !== null && (0, jsx_runtime_1.jsx)("p", { className: "dcl-rewind-error", children: error }), error !== null && (0, jsx_runtime_1.jsx)("p", { className: "dcl-rewind-backup", children: "\u6062\u590D\u524D\u4F1A\u81EA\u52A8\u5907\u4EFD\u5F53\u524D\u6587\u4EF6\uFF1B\u82E5\u6062\u590D\u5931\u8D25\u4F1A\u81EA\u52A8\u8FD8\u539F\uFF0C\u9879\u76EE\u4E0D\u4F1A\u505C\u7559\u5728\u53EA\u6062\u590D\u4E86\u4E00\u90E8\u5206\u7684\u72B6\u6001\u3002" }), !loading && (preview?.status !== 'ready' || stale || planMissing || sharedBlocked || driftBlocked) && (0, jsx_runtime_1.jsx)(dsh_client_ui_primitives_1.Button, { className: "dcl-rewind-retry", variant: "outline", size: "sm", onClick: () => { void load(); }, children: "\u91CD\u65B0\u68C0\u67E5" })] }) })] }));
|
|
354
|
+
}
|
|
355
|
+
const EMPTY_SETTINGS_SNAPSHOT = {
|
|
356
|
+
status: 'unavailable', value: undefined, base: undefined, user: undefined, revision: undefined, writable: false, mode: 'host',
|
|
357
|
+
};
|
|
358
|
+
const NUMBER_FIELDS = [
|
|
359
|
+
{ key: 'maxRestorePoints', label: '用户/救援恢复点上限', description: '每个工作区保留的最大手动与救援恢复点数量' },
|
|
360
|
+
{ key: 'maxTurnCheckpointsPerSession', label: '轮次检查点上限', description: '每个会话保留的最大自动轮次检查点数量(最旧的先清理)' },
|
|
361
|
+
{ key: 'maxFiles', label: '单恢复点文件数上限', description: '一个恢复点最多纳入的文件数量' },
|
|
362
|
+
{ key: 'maxFileBytes', label: '单文件大小上限', description: '读取单个普通文件的最大字节数' },
|
|
363
|
+
{ key: 'maxSnapshotBytes', label: '快照总量上限', description: '单个恢复点读取的最大字节总量' },
|
|
364
|
+
{ key: 'planTtlMs', label: '恢复计划有效期(毫秒)', description: '回溯计划从创建到失效的时间' },
|
|
365
|
+
{ key: 'staleLockMs', label: '锁回收时长(毫秒)', description: '锁属主消失多久后允许回收该锁' },
|
|
366
|
+
{ key: 'turnCheckpointTimeoutMs', label: '检查点超时(毫秒)', description: '单次自动检查点最多阻塞消息发送的时间,超时记录跳过' },
|
|
367
|
+
{ key: 'turnCheckpointMaxNewBytes', label: '检查点读取上限', description: '单次 Git 原生检查点最多读取的未缓存字节数' },
|
|
368
|
+
];
|
|
369
|
+
const CHECKPOINT_MODE_LABELS = {
|
|
370
|
+
off: '关闭(不创建文件检查点)',
|
|
371
|
+
'git-native': 'Git 原生(推荐大仓库)',
|
|
372
|
+
legacy: '完整快照(兼容模式)',
|
|
373
|
+
};
|
|
374
|
+
const TRUST_LABELS = {
|
|
375
|
+
fast: '快速',
|
|
376
|
+
strict: '严格',
|
|
377
|
+
};
|
|
378
|
+
const POINT_KIND_LABELS = {
|
|
379
|
+
user: '手动',
|
|
380
|
+
rescue: '救援',
|
|
381
|
+
turn: '轮次',
|
|
382
|
+
};
|
|
383
|
+
/** Settings card for the `turn-rewind` namespace: runtime options plus checkpoint management. */
|
|
384
|
+
function TurnRewindSettingsCard({ scope }) {
|
|
385
|
+
const snapshot = (0, react_1.useSyncExternalStore)((0, react_1.useCallback)((notify) => scope?.subscribe(notify) ?? (() => { }), [scope]), (0, react_1.useCallback)(() => scope?.getSnapshot() ?? EMPTY_SETTINGS_SNAPSHOT, [scope]));
|
|
386
|
+
const [manage, setManage] = (0, react_1.useState)(null);
|
|
387
|
+
const [manageLoading, setManageLoading] = (0, react_1.useState)(false);
|
|
388
|
+
const [manageError, setManageError] = (0, react_1.useState)(null);
|
|
389
|
+
const [manageNotice, setManageNotice] = (0, react_1.useState)(null);
|
|
390
|
+
const [formError, setFormError] = (0, react_1.useState)(null);
|
|
391
|
+
const [busy, setBusy] = (0, react_1.useState)(null);
|
|
392
|
+
const [drafts, setDrafts] = (0, react_1.useState)({});
|
|
393
|
+
const [confirmClearAll, setConfirmClearAll] = (0, react_1.useState)(false);
|
|
394
|
+
const [collapsed, setCollapsed] = (0, react_1.useState)(new Set());
|
|
395
|
+
const refreshManage = (0, react_1.useCallback)(async () => {
|
|
396
|
+
setManageLoading(true);
|
|
397
|
+
setManageError(null);
|
|
398
|
+
try {
|
|
399
|
+
const response = await fetch(MANAGE_PATH, { method: 'GET', headers: { accept: 'application/json' }, cache: 'no-store' });
|
|
400
|
+
setManage(decodeManageOverview(await responseJson(response)));
|
|
401
|
+
}
|
|
402
|
+
catch (caught) {
|
|
403
|
+
setManageError(messageOf(caught));
|
|
404
|
+
}
|
|
405
|
+
finally {
|
|
406
|
+
setManageLoading(false);
|
|
407
|
+
}
|
|
408
|
+
}, []);
|
|
409
|
+
(0, react_1.useEffect)(() => { void refreshManage(); }, [refreshManage]);
|
|
410
|
+
const userLayer = snapshot.user !== null && typeof snapshot.user === 'object' ? snapshot.user : {};
|
|
411
|
+
const value = snapshot.value;
|
|
412
|
+
const writable = snapshot.writable && snapshot.status === 'ready';
|
|
413
|
+
const commitEnum = (field, next) => {
|
|
414
|
+
if (scope === undefined || !writable)
|
|
415
|
+
return;
|
|
416
|
+
setFormError(null);
|
|
417
|
+
void scope.set(field, next).catch((caught) => { setFormError(messageOf(caught)); });
|
|
418
|
+
};
|
|
419
|
+
const commitNumber = (field) => {
|
|
420
|
+
if (scope === undefined || !writable)
|
|
421
|
+
return;
|
|
422
|
+
const draft = drafts[field];
|
|
423
|
+
if (draft === undefined)
|
|
424
|
+
return;
|
|
425
|
+
setDrafts((current) => { const next = { ...current }; delete next[field]; return next; });
|
|
426
|
+
if (draft.trim() === '') {
|
|
427
|
+
void scope.unset(field).catch((caught) => { setFormError(messageOf(caught)); });
|
|
428
|
+
return;
|
|
429
|
+
}
|
|
430
|
+
const parsed = Number.parseInt(draft, 10);
|
|
431
|
+
if (!Number.isSafeInteger(parsed) || parsed <= 0) {
|
|
432
|
+
setFormError(`${field} 必须是正整数。`);
|
|
433
|
+
return;
|
|
434
|
+
}
|
|
435
|
+
if (parsed === value?.[field])
|
|
436
|
+
return;
|
|
437
|
+
setFormError(null);
|
|
438
|
+
void scope.set(field, parsed).catch((caught) => { setFormError(messageOf(caught)); });
|
|
439
|
+
};
|
|
440
|
+
const runManageAction = async (body, key) => {
|
|
441
|
+
if (busy !== null)
|
|
442
|
+
return;
|
|
443
|
+
setBusy(key);
|
|
444
|
+
setManageError(null);
|
|
445
|
+
setManageNotice(null);
|
|
446
|
+
setConfirmClearAll(false);
|
|
447
|
+
try {
|
|
448
|
+
const result = await responseJson(await fetch(MANAGE_PATH, {
|
|
449
|
+
method: 'POST',
|
|
450
|
+
headers: { accept: 'application/json', 'content-type': 'application/json' },
|
|
451
|
+
body: JSON.stringify(body),
|
|
452
|
+
}));
|
|
453
|
+
setManageNotice(decodeManageActionNotice(result));
|
|
454
|
+
await refreshManage();
|
|
455
|
+
}
|
|
456
|
+
catch (caught) {
|
|
457
|
+
setManageError(messageOf(caught));
|
|
458
|
+
}
|
|
459
|
+
finally {
|
|
460
|
+
setBusy(null);
|
|
461
|
+
}
|
|
462
|
+
};
|
|
463
|
+
const toggleWorkspace = (workspace) => {
|
|
464
|
+
setCollapsed((current) => {
|
|
465
|
+
const next = new Set(current);
|
|
466
|
+
if (next.has(workspace))
|
|
467
|
+
next.delete(workspace);
|
|
468
|
+
else
|
|
469
|
+
next.add(workspace);
|
|
470
|
+
return next;
|
|
471
|
+
});
|
|
472
|
+
};
|
|
473
|
+
return ((0, jsx_runtime_1.jsxs)("div", { className: "dcl-trs-card", children: [(0, jsx_runtime_1.jsxs)("section", { className: "dcl-trs-section", children: [(0, jsx_runtime_1.jsx)("div", { className: "dcl-trs-section-title", children: (0, jsx_runtime_1.jsx)("strong", { children: "Turn Rewind \u56DE\u9000\u8BBE\u7F6E" }) }), snapshot.status === 'loading' && (0, jsx_runtime_1.jsx)("p", { className: "dcl-trs-status", children: "\u6B63\u5728\u52A0\u8F7D\u8BBE\u7F6E\u2026" }), snapshot.status === 'unavailable' && (0, jsx_runtime_1.jsx)("p", { className: "dcl-trs-status", children: "\u5F53\u524D\u90E8\u7F72\u672A\u63D0\u4F9B\u8BBE\u7F6E\u670D\u52A1\uFF0C\u4EE5\u4E0B\u9009\u9879\u4E0D\u53EF\u7528\u3002" }), snapshot.status === 'ready' && !snapshot.writable && (0, jsx_runtime_1.jsx)("p", { className: "dcl-trs-status", children: "\u8BBE\u7F6E\u4E3A\u53EA\u8BFB\uFF08\u5F53\u524D\u6D4F\u89C8\u5668\u8FDB\u7A0B\u5185\u4FDD\u5B58\uFF09\uFF0C\u4FEE\u6539\u4E0D\u53EF\u7528\u3002" }), value !== undefined && ((0, jsx_runtime_1.jsxs)(jsx_runtime_1.Fragment, { children: [(0, jsx_runtime_1.jsxs)("div", { className: "dcl-trs-field", children: [(0, jsx_runtime_1.jsxs)("span", { className: "dcl-trs-field-label", children: [(0, jsx_runtime_1.jsx)("strong", { children: "\u81EA\u52A8\u6587\u4EF6\u68C0\u67E5\u70B9" }), (0, jsx_runtime_1.jsx)("span", { className: "dcl-trs-field-desc", children: "\u5173\u95ED\u540E\u4E0D\u518D\u4E3A\u6BCF\u6761\u6D88\u606F\u4FDD\u5B58\u6587\u4EF6\u68C0\u67E5\u70B9\uFF1B\u56DE\u9000\u5F39\u7A97\u4ECD\u53EF\u53EA\u56DE\u6EAF\u6D88\u606F" })] }), (0, jsx_runtime_1.jsxs)("span", { className: "dcl-trs-field-control", children: [(0, jsx_runtime_1.jsx)("select", { value: value.turnCheckpointMode, disabled: !writable || busy !== null, onChange: (event) => { commitEnum('turnCheckpointMode', event.target.value); }, children: Object.keys(CHECKPOINT_MODE_LABELS).map((option) => ((0, jsx_runtime_1.jsx)("option", { value: option, children: CHECKPOINT_MODE_LABELS[option] }, option))) }), userLayer.turnCheckpointMode !== undefined && (0, jsx_runtime_1.jsx)("span", { className: "dcl-trs-override", children: "\u5DF2\u8986\u76D6" }), userLayer.turnCheckpointMode !== undefined && ((0, jsx_runtime_1.jsx)(dsh_client_ui_primitives_1.Button, { variant: "ghost", size: "sm", disabled: !writable || busy !== null, onClick: () => { setFormError(null); void scope?.unset('turnCheckpointMode').catch((caught) => { setFormError(messageOf(caught)); }); }, children: "\u6062\u590D\u9ED8\u8BA4" }))] })] }), (0, jsx_runtime_1.jsxs)("div", { className: "dcl-trs-field", children: [(0, jsx_runtime_1.jsxs)("span", { className: "dcl-trs-field-label", children: [(0, jsx_runtime_1.jsx)("strong", { children: "\u68C0\u67E5\u70B9\u4FE1\u4EFB\u7B56\u7565" }), (0, jsx_runtime_1.jsx)("span", { className: "dcl-trs-field-desc", children: "\u5FEB\u901F\u4FE1\u4EFB Git/stat \u5143\u6570\u636E\uFF1B\u4E25\u683C\u4F1A\u9010\u4E00\u91CD\u8BFB\u6587\u4EF6\u5185\u5BB9" })] }), (0, jsx_runtime_1.jsxs)("span", { className: "dcl-trs-field-control", children: [(0, jsx_runtime_1.jsx)("select", { value: value.turnCheckpointTrust, disabled: !writable || busy !== null, onChange: (event) => { commitEnum('turnCheckpointTrust', event.target.value); }, children: Object.keys(TRUST_LABELS).map((option) => ((0, jsx_runtime_1.jsx)("option", { value: option, children: TRUST_LABELS[option] }, option))) }), userLayer.turnCheckpointTrust !== undefined && (0, jsx_runtime_1.jsx)("span", { className: "dcl-trs-override", children: "\u5DF2\u8986\u76D6" }), userLayer.turnCheckpointTrust !== undefined && ((0, jsx_runtime_1.jsx)(dsh_client_ui_primitives_1.Button, { variant: "ghost", size: "sm", disabled: !writable || busy !== null, onClick: () => { setFormError(null); void scope?.unset('turnCheckpointTrust').catch((caught) => { setFormError(messageOf(caught)); }); }, children: "\u6062\u590D\u9ED8\u8BA4" }))] })] }), NUMBER_FIELDS.map((field) => ((0, jsx_runtime_1.jsxs)("div", { className: "dcl-trs-field", children: [(0, jsx_runtime_1.jsxs)("span", { className: "dcl-trs-field-label", children: [(0, jsx_runtime_1.jsx)("strong", { children: field.label }), (0, jsx_runtime_1.jsx)("span", { className: "dcl-trs-field-desc", children: field.description })] }), (0, jsx_runtime_1.jsxs)("span", { className: "dcl-trs-field-control", children: [(0, jsx_runtime_1.jsx)("input", { type: "number", min: 1, step: 1, value: drafts[field.key] ?? String(value[field.key]), disabled: !writable || busy !== null, onChange: (event) => { setDrafts((current) => ({ ...current, [field.key]: event.target.value })); }, onBlur: () => { commitNumber(field.key); }, onKeyDown: (event) => { if (event.key === 'Enter')
|
|
474
|
+
commitNumber(field.key); } }), userLayer[field.key] !== undefined && (0, jsx_runtime_1.jsx)("span", { className: "dcl-trs-override", children: "\u5DF2\u8986\u76D6" }), userLayer[field.key] !== undefined && ((0, jsx_runtime_1.jsx)(dsh_client_ui_primitives_1.Button, { variant: "ghost", size: "sm", disabled: !writable || busy !== null, onClick: () => { setFormError(null); void scope?.unset(field.key).catch((caught) => { setFormError(messageOf(caught)); }); }, children: "\u6062\u590D\u9ED8\u8BA4" }))] })] }, field.key)))] })), formError !== null && (0, jsx_runtime_1.jsx)("p", { className: "dcl-trs-error", children: formError }), (0, jsx_runtime_1.jsxs)("p", { className: "dcl-trs-storage", children: ["\u5B58\u50A8\u76EE\u5F55\uFF08\u5728 cordis.patch.yml \u4E2D\u914D\u7F6E\uFF0C\u4E0D\u53EF\u5728\u7EBF\u4FEE\u6539\uFF09\uFF1A", manage?.storageDir ?? '…'] })] }), (0, jsx_runtime_1.jsxs)("section", { className: "dcl-trs-section", children: [(0, jsx_runtime_1.jsxs)("div", { className: "dcl-trs-section-title", children: [(0, jsx_runtime_1.jsx)("strong", { children: "\u68C0\u67E5\u70B9\u7BA1\u7406" }), (0, jsx_runtime_1.jsxs)("span", { className: "dcl-trs-section-title-actions", children: [(0, jsx_runtime_1.jsx)(dsh_client_ui_primitives_1.Button, { variant: "outline", size: "sm", onClick: () => { void refreshManage(); }, disabled: manageLoading, children: manageLoading ? '正在刷新…' : '刷新' }), manage !== null && manage.workspaces.length > 0 && (confirmClearAll
|
|
475
|
+
? ((0, jsx_runtime_1.jsxs)(jsx_runtime_1.Fragment, { children: [(0, jsx_runtime_1.jsx)(dsh_client_ui_primitives_1.Button, { variant: "outline", size: "sm", onClick: () => { setConfirmClearAll(false); }, disabled: busy !== null, children: "\u53D6\u6D88" }), (0, jsx_runtime_1.jsx)(dsh_client_ui_primitives_1.Button, { variant: "primary", size: "sm", onClick: () => { void runManageAction({ action: 'clear-all' }, 'clear-all'); }, disabled: busy !== null, children: busy === 'clear-all' ? '正在清理…' : '确认清空全部' })] }))
|
|
476
|
+
: (0, jsx_runtime_1.jsx)(dsh_client_ui_primitives_1.Button, { variant: "outline", size: "sm", onClick: () => { setConfirmClearAll(true); }, disabled: busy !== null, children: "\u4E00\u952E\u6E05\u7A7A\u5168\u90E8" }))] })] }), (0, jsx_runtime_1.jsx)("p", { className: "dcl-trs-manage-total", children: manage === null
|
|
477
|
+
? '正在读取检查点占用…'
|
|
478
|
+
: `共 ${String(manage.workspaces.length)} 个工作区,${String(manage.totalBytes >= 0 ? manage.workspaces.reduce((total, workspace) => total + workspace.restorePoints.length, 0) : 0)} 个检查点,约 ${formatBytes(manage.totalBytes)}(Git 原生检查点的实际磁盘占用以 Git 回收为准)。` }), manageNotice !== null && (0, jsx_runtime_1.jsx)("p", { className: "dcl-trs-notice", "data-warning": manageNotice.warning, children: manageNotice.message }), manageError !== null && (0, jsx_runtime_1.jsx)("p", { className: "dcl-trs-error", children: manageError }), manage?.workspaces.map((workspace) => ((0, jsx_runtime_1.jsxs)("div", { className: "dcl-trs-workspace", children: [(0, jsx_runtime_1.jsxs)("div", { className: "dcl-trs-workspace-head", children: [(0, jsx_runtime_1.jsx)("span", { className: "dcl-trs-workspace-path", title: workspace.workspace, children: workspace.workspace }), (0, jsx_runtime_1.jsxs)("span", { className: "dcl-trs-workspace-meta", children: [String(workspace.restorePoints.length), " \u4E2A\u68C0\u67E5\u70B9 \u00B7 ", formatBytes(workspace.totalBytes)] }), workspace.recoveryCount > 0 && (0, jsx_runtime_1.jsxs)("span", { className: "dcl-trs-badge", children: [String(workspace.recoveryCount), " \u4E2A\u6062\u590D\u5F85\u5904\u7406"] }), (0, jsx_runtime_1.jsx)(dsh_client_ui_primitives_1.Button, { variant: "ghost", size: "sm", onClick: () => { toggleWorkspace(workspace.workspace); }, children: collapsed.has(workspace.workspace) ? '展开' : '收起' }), (0, jsx_runtime_1.jsx)(dsh_client_ui_primitives_1.Button, { variant: "outline", size: "sm", onClick: () => { void runManageAction({ action: 'clear-workspace', workspace: workspace.workspace }, `clear:${workspace.workspace}`); }, disabled: busy !== null || workspace.restorePoints.length === 0, children: busy === `clear:${workspace.workspace}` ? '正在清理…' : '清空此项目' })] }), !collapsed.has(workspace.workspace) && workspace.restorePoints.length > 0 && ((0, jsx_runtime_1.jsx)("ul", { className: "dcl-trs-points", children: workspace.restorePoints.map((point) => ((0, jsx_runtime_1.jsxs)("li", { className: "dcl-trs-point", children: [(0, jsx_runtime_1.jsx)("time", { children: formatTime(point.createdAt) }), (0, jsx_runtime_1.jsx)("span", { className: "dcl-trs-point-kind", children: POINT_KIND_LABELS[point.kind] ?? point.kind }), (0, jsx_runtime_1.jsx)("span", { className: "dcl-trs-point-size", children: formatBytes(point.totalBytes) }), (0, jsx_runtime_1.jsxs)("span", { className: "dcl-trs-point-files", children: [String(point.fileCount), " \u4E2A\u6587\u4EF6"] }), point.sessionId !== undefined && (0, jsx_runtime_1.jsx)("code", { children: point.sessionId }), (0, jsx_runtime_1.jsx)(dsh_client_ui_primitives_1.Button, { variant: "ghost", size: "sm", onClick: () => { void runManageAction({ action: 'delete', workspace: workspace.workspace, restorePointId: point.id }, `delete:${point.id}`); }, disabled: busy !== null, children: busy === `delete:${point.id}` ? '正在删除…' : '删除' })] }, point.id))) }))] }, workspace.workspace))), manage !== null && manage.workspaces.length === 0 && (0, jsx_runtime_1.jsx)("p", { className: "dcl-trs-status", children: "\u8FD8\u6CA1\u6709\u4EFB\u4F55\u5DF2\u4FDD\u5B58\u7684\u68C0\u67E5\u70B9\u3002" })] })] }));
|
|
479
|
+
}
|
|
480
|
+
function decodeManageOverview(value) {
|
|
481
|
+
const record = recordOf(value);
|
|
482
|
+
const workspacesValue = record.workspaces;
|
|
483
|
+
if (!Array.isArray(workspacesValue))
|
|
484
|
+
throw new Error('管理数据缺少 workspaces');
|
|
485
|
+
return {
|
|
486
|
+
storageDir: requiredString(record.storageDir, 'storageDir'),
|
|
487
|
+
totalBytes: requiredInteger(record.totalBytes, 'totalBytes'),
|
|
488
|
+
workspaces: workspacesValue.map((entry) => {
|
|
489
|
+
const workspace = recordOf(entry);
|
|
490
|
+
const pointsValue = workspace.restorePoints;
|
|
491
|
+
if (!Array.isArray(pointsValue))
|
|
492
|
+
throw new Error('管理数据缺少 restorePoints');
|
|
493
|
+
return {
|
|
494
|
+
workspace: requiredString(workspace.workspace, 'workspace'),
|
|
495
|
+
totalBytes: requiredInteger(workspace.totalBytes, 'totalBytes'),
|
|
496
|
+
recoveryCount: requiredInteger(workspace.recoveryCount, 'recoveryCount'),
|
|
497
|
+
restorePoints: pointsValue.map((pointEntry) => {
|
|
498
|
+
const point = recordOf(pointEntry);
|
|
499
|
+
return {
|
|
500
|
+
id: requiredString(point.id, 'id'),
|
|
501
|
+
kind: requiredString(point.kind, 'kind'),
|
|
502
|
+
format: requiredInteger(point.format, 'format'),
|
|
503
|
+
createdAt: requiredInteger(point.createdAt, 'createdAt'),
|
|
504
|
+
totalBytes: requiredInteger(point.totalBytes, 'totalBytes'),
|
|
505
|
+
fileCount: requiredInteger(point.fileCount, 'fileCount'),
|
|
506
|
+
...optionalRecordString(point, 'sessionId'),
|
|
507
|
+
...optionalRecordString(point, 'label'),
|
|
508
|
+
};
|
|
509
|
+
}),
|
|
510
|
+
};
|
|
511
|
+
}),
|
|
512
|
+
};
|
|
513
|
+
}
|
|
514
|
+
function decodeManageActionNotice(value) {
|
|
515
|
+
const record = recordOf(value);
|
|
516
|
+
const action = requiredString(record.action, 'action');
|
|
517
|
+
if (action === 'clear-all') {
|
|
518
|
+
if (!Array.isArray(record.reports))
|
|
519
|
+
throw new Error('清理结果缺少 reports');
|
|
520
|
+
const totals = record.reports.reduce((current, entry) => {
|
|
521
|
+
const report = recordOf(entry);
|
|
522
|
+
return {
|
|
523
|
+
deleted: current.deleted + requiredInteger(report.deletedRestorePoints, 'deletedRestorePoints'),
|
|
524
|
+
retained: current.retained + requiredInteger(report.retainedRestorePoints, 'retainedRestorePoints'),
|
|
525
|
+
};
|
|
526
|
+
}, { deleted: 0, retained: 0 });
|
|
527
|
+
const failures = Array.isArray(record.failures) ? record.failures.length : 0;
|
|
528
|
+
const warning = record.status === 'partial' || totals.retained > 0 || failures > 0;
|
|
529
|
+
return {
|
|
530
|
+
warning,
|
|
531
|
+
message: `已删除 ${String(totals.deleted)} 个检查点${totals.retained > 0 ? `,${String(totals.retained)} 个受保护检查点未删除` : ''}${failures > 0 ? `,${String(failures)} 个工作区清理失败` : ''}。`,
|
|
532
|
+
};
|
|
533
|
+
}
|
|
534
|
+
const deleted = requiredInteger(record.deletedRestorePoints, 'deletedRestorePoints');
|
|
535
|
+
const retained = requiredInteger(record.retainedRestorePoints, 'retainedRestorePoints');
|
|
536
|
+
return {
|
|
537
|
+
warning: retained > 0,
|
|
538
|
+
message: `已删除 ${String(deleted)} 个检查点${retained > 0 ? `,${String(retained)} 个受保护检查点未删除` : ''}。`,
|
|
539
|
+
};
|
|
540
|
+
}
|
|
541
|
+
/** Format one byte count with human-friendly units. */
|
|
542
|
+
function formatBytes(bytes) {
|
|
543
|
+
if (!Number.isFinite(bytes) || bytes < 0)
|
|
544
|
+
return '0 B';
|
|
545
|
+
const units = ['B', 'KB', 'MB', 'GB', 'TB'];
|
|
546
|
+
let value = bytes;
|
|
547
|
+
let unit = 0;
|
|
548
|
+
while (value >= 1024 && unit < units.length - 1) {
|
|
549
|
+
value /= 1024;
|
|
550
|
+
unit += 1;
|
|
551
|
+
}
|
|
552
|
+
return `${unit === 0 ? String(Math.round(value)) : value.toFixed(value >= 100 ? 0 : 1)} ${units[unit]}`;
|
|
553
|
+
}
|
|
554
|
+
function formatTime(timestamp) {
|
|
555
|
+
const date = new Date(timestamp);
|
|
556
|
+
return Number.isNaN(date.getTime()) ? String(timestamp) : date.toLocaleString();
|
|
290
557
|
}
|
|
291
558
|
function decodePreview(value) {
|
|
292
559
|
const record = recordOf(value);
|
|
@@ -359,9 +626,12 @@ function collectPortalTargets(nodes) {
|
|
|
359
626
|
if (target === null)
|
|
360
627
|
continue;
|
|
361
628
|
const row = rows.get(target.rowKey);
|
|
362
|
-
const messageRoot = row?.querySelector('[data-time-hover-root="true"]');
|
|
363
|
-
|
|
364
|
-
|
|
629
|
+
const messageRoot = row?.querySelector?.('[data-time-hover-root="true"]');
|
|
630
|
+
// Match the actions container by class or last child element
|
|
631
|
+
const actions = (typeof messageRoot?.querySelector === 'function'
|
|
632
|
+
? messageRoot.querySelector('[class*="actions"]')
|
|
633
|
+
: undefined) ?? messageRoot?.lastElementChild;
|
|
634
|
+
if (!(actions instanceof HTMLElement))
|
|
365
635
|
continue;
|
|
366
636
|
targets.push({ container: actions, matched: target.matched });
|
|
367
637
|
}
|
|
@@ -461,7 +731,7 @@ function friendlyError(error) {
|
|
|
461
731
|
case 'REPOSITORY_CHANGED': return '这个项目目录已不属于原来的 Git 工作区,无法恢复。';
|
|
462
732
|
case 'GIT_OPERATION_CHANGED': return 'Git 正在执行其他操作。请先完成或取消该操作,再重新检查。';
|
|
463
733
|
case 'RESTORE_POINT_NOT_FOUND': return '没有找到对应的文件状态,可能已被清理。';
|
|
464
|
-
case 'NO_CHANGES': return '
|
|
734
|
+
case 'NO_CHANGES': return '项目文件已经是这条消息发送前的状态,无需恢复文件。可选择「只回溯消息」重新开始这段对话。';
|
|
465
735
|
case 'RESTORE_FAILED_ROLLED_BACK': return '恢复未能完成,项目文件已自动还原到操作前的状态。';
|
|
466
736
|
case 'CONVERSATION_REWIND_FAILED': return '文件已恢复,但无法创建新对话;项目文件已自动还原。';
|
|
467
737
|
default: return error.message;
|