dsh-easygit-plugin 0.2.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 ADDED
@@ -0,0 +1,2064 @@
1
+ window.__ModuleLoader__.load({ id: "dsh-easygit-plugin", factory: (require) => { var module = { exports: {} }; var exports = module.exports;
2
+ "use strict";
3
+
4
+ // src/client/panel-controller.ts
5
+ function errorText(error) {
6
+ return error instanceof Error ? error.message : String(error);
7
+ }
8
+ function markWorkbenchOpen(open) {
9
+ if (typeof document === "undefined" || !document.documentElement) return;
10
+ if (open) document.documentElement.setAttribute("data-git-guide-workbench-open", "");
11
+ else document.documentElement.removeAttribute("data-git-guide-workbench-open");
12
+ }
13
+ function createPanelController(options) {
14
+ const slots = options.slots;
15
+ const layout = options.layout;
16
+ const renderPanel = options.renderPanel;
17
+ const listeners = /* @__PURE__ */ new Set();
18
+ let detailsReady = false;
19
+ let activeSessionId = null;
20
+ let disposePanel = null;
21
+ let error = "";
22
+ const snapshot = () => ({
23
+ detailsReady,
24
+ activeSessionId,
25
+ open: activeSessionId !== null && disposePanel !== null,
26
+ error
27
+ });
28
+ const notify = () => {
29
+ const state = snapshot();
30
+ for (const listener of listeners) listener(state);
31
+ };
32
+ const close = (sessionId) => {
33
+ if (sessionId !== void 0 && sessionId !== null && activeSessionId !== String(sessionId)) return false;
34
+ const dispose = disposePanel;
35
+ const wasOpen = activeSessionId !== null || typeof dispose === "function";
36
+ activeSessionId = null;
37
+ disposePanel = null;
38
+ markWorkbenchOpen(false);
39
+ if (typeof dispose === "function") {
40
+ try {
41
+ dispose();
42
+ } catch (caught) {
43
+ error = errorText(caught);
44
+ }
45
+ }
46
+ if (wasOpen && layout && typeof layout.closeDetails === "function") {
47
+ try {
48
+ layout.closeDetails();
49
+ } catch (caught) {
50
+ error = errorText(caught);
51
+ }
52
+ }
53
+ notify();
54
+ return wasOpen;
55
+ };
56
+ const open = (sessionId) => {
57
+ const targetSessionId = String(sessionId || "");
58
+ if (!targetSessionId) {
59
+ error = "\u5F53\u524D\u4F1A\u8BDD\u4E0D\u53EF\u7528\uFF0C\u65E0\u6CD5\u6253\u5F00 Git \u5DE5\u4F5C\u53F0";
60
+ notify();
61
+ return false;
62
+ }
63
+ if (!detailsReady || !slots || typeof slots.register !== "function") {
64
+ error = "\u5F53\u524D Harness \u5C1A\u672A\u63D0\u4F9B\u53F3\u4FA7\u8BE6\u60C5\u680F\uFF0C\u65E0\u6CD5\u6253\u5F00 Git \u5DE5\u4F5C\u53F0";
65
+ notify();
66
+ return false;
67
+ }
68
+ if (!layout || typeof layout.openDetails !== "function" || typeof layout.closeDetails !== "function") {
69
+ error = "\u5F53\u524D Harness \u4E0D\u652F\u6301\u8BE6\u60C5\u680F\u5F00\u5173\uFF0C\u65E0\u6CD5\u6253\u5F00 Git \u5DE5\u4F5C\u53F0";
70
+ notify();
71
+ return false;
72
+ }
73
+ if (activeSessionId === targetSessionId && typeof disposePanel === "function") {
74
+ try {
75
+ layout.openDetails();
76
+ error = "";
77
+ } catch (caught) {
78
+ error = errorText(caught);
79
+ }
80
+ notify();
81
+ return error === "";
82
+ }
83
+ if (activeSessionId !== null || typeof disposePanel === "function") close();
84
+ let dispose = null;
85
+ try {
86
+ layout.openDetails();
87
+ dispose = slots.register(
88
+ { name: "details", priority: -10 },
89
+ (props) => {
90
+ const currentSessionId = String(props.sessionId || "");
91
+ if (currentSessionId !== targetSessionId || activeSessionId !== targetSessionId) return null;
92
+ return renderPanel({ sessionId: currentSessionId, close: () => close(currentSessionId) });
93
+ }
94
+ );
95
+ if (typeof dispose !== "function") throw new Error("details \u63D2\u69FD\u672A\u8FD4\u56DE\u53EF\u91CA\u653E\u7684\u6CE8\u518C\u53E5\u67C4");
96
+ activeSessionId = targetSessionId;
97
+ disposePanel = dispose;
98
+ markWorkbenchOpen(true);
99
+ error = "";
100
+ notify();
101
+ return true;
102
+ } catch (caught) {
103
+ if (typeof dispose === "function") {
104
+ try {
105
+ dispose();
106
+ } catch (disposeError) {
107
+ }
108
+ }
109
+ activeSessionId = null;
110
+ disposePanel = null;
111
+ markWorkbenchOpen(false);
112
+ try {
113
+ layout.closeDetails();
114
+ } catch (closeError) {
115
+ }
116
+ error = errorText(caught);
117
+ notify();
118
+ return false;
119
+ }
120
+ };
121
+ return {
122
+ attachDetails() {
123
+ detailsReady = true;
124
+ error = "";
125
+ notify();
126
+ return () => {
127
+ detailsReady = false;
128
+ close();
129
+ };
130
+ },
131
+ open,
132
+ close,
133
+ toggle(sessionId) {
134
+ return activeSessionId === String(sessionId || "") ? close(sessionId) : open(sessionId);
135
+ },
136
+ isOpen(sessionId) {
137
+ return activeSessionId === String(sessionId || "") && typeof disposePanel === "function";
138
+ },
139
+ subscribe(listener) {
140
+ listeners.add(listener);
141
+ return () => {
142
+ listeners.delete(listener);
143
+ };
144
+ },
145
+ snapshot
146
+ };
147
+ }
148
+
149
+ // src/client/view-model.ts
150
+ var WORKBENCH_RATIO_KEY = "dsh-easygit-plugin:workbench-ratio";
151
+ var WORKBENCH_TRACK = "--dsh-easygit-plugin-workbench-width";
152
+ var WORKBENCH_DEFAULT_RATIO = 0.36;
153
+ var WORKBENCH_MIN_RATIO = 0.24;
154
+ var WORKBENCH_MAX_RATIO = 0.75;
155
+ function appendCommandLog(current, entry) {
156
+ return [...current, entry].slice(-100);
157
+ }
158
+ function filterLocalBranches(branches, query) {
159
+ const normalized = query.trim().toLocaleLowerCase();
160
+ if (!normalized) return branches;
161
+ return branches.filter((branch) => String(branch.name || "").toLocaleLowerCase().includes(normalized));
162
+ }
163
+ function isCurrentCommitRequest(selectedHash, requestedHash, currentSequence, requestSequence) {
164
+ return selectedHash === requestedHash && currentSequence === requestSequence;
165
+ }
166
+ function nextCommitSelection(currentHash, requestedHash) {
167
+ return currentHash === requestedHash ? "" : requestedHash;
168
+ }
169
+ function commitFileTone(status) {
170
+ if (/^[AC]/.test(status)) return " added";
171
+ if (/^D/.test(status)) return " deleted";
172
+ return " modified";
173
+ }
174
+ function isLatestRequest(currentSequence, requestSequence) {
175
+ return currentSequence === requestSequence;
176
+ }
177
+ function beginTrackedRequest(ref) {
178
+ ref.current.controller?.abort();
179
+ const controller = new AbortController();
180
+ const sequence = ref.current.sequence + 1;
181
+ ref.current = { controller, sequence };
182
+ return { sequence, signal: controller.signal };
183
+ }
184
+ function cancelTrackedRequest(ref) {
185
+ ref.current.controller?.abort();
186
+ ref.current = { controller: null, sequence: ref.current.sequence + 1 };
187
+ }
188
+ function isTrackedRequestCurrent(ref, request) {
189
+ return ref.current.sequence === request.sequence && !request.signal.aborted;
190
+ }
191
+ function isAbortError(error) {
192
+ return error instanceof Error && error.name === "AbortError";
193
+ }
194
+ function deriveCommitGraph(commits) {
195
+ let lanes = [];
196
+ return commits.map((commit) => {
197
+ const hash = String(commit.hash || "");
198
+ let lane = lanes.indexOf(hash);
199
+ if (lane < 0) {
200
+ lane = lanes.length;
201
+ lanes.push(hash);
202
+ }
203
+ const before = [...lanes];
204
+ const parents = Array.isArray(commit.parents) ? commit.parents.map(String).filter(Boolean) : [];
205
+ let after = [...before];
206
+ if (parents.length === 0) after.splice(lane, 1);
207
+ else {
208
+ after[lane] = parents[0];
209
+ for (let index = 1; index < parents.length; index += 1) {
210
+ const parent = parents[index];
211
+ if (!after.includes(parent)) after.splice(lane + index, 0, parent);
212
+ }
213
+ }
214
+ after = after.filter((value, index) => value && after.indexOf(value) === index);
215
+ const edges = [];
216
+ before.forEach((value, from) => {
217
+ if (value === hash) {
218
+ if (parents.length === 0) edges.push({ from, to: null, active: true });
219
+ else parents.forEach((parent) => edges.push({ from, to: after.indexOf(parent), active: true }));
220
+ } else {
221
+ const to = after.indexOf(value);
222
+ if (to >= 0) edges.push({ from, to, active: false });
223
+ }
224
+ });
225
+ const row = { commit, lane, laneCount: Math.max(1, before.length, after.length), edges };
226
+ lanes = after;
227
+ return row;
228
+ });
229
+ }
230
+ function clampWorkbenchRatio(value) {
231
+ return Math.min(WORKBENCH_MAX_RATIO, Math.max(WORKBENCH_MIN_RATIO, value));
232
+ }
233
+ function readWorkbenchRatio() {
234
+ if (typeof window === "undefined" || !window.localStorage) return WORKBENCH_DEFAULT_RATIO;
235
+ try {
236
+ const value = Number(window.localStorage.getItem(WORKBENCH_RATIO_KEY));
237
+ return Number.isFinite(value) && value > 0 ? clampWorkbenchRatio(value) : WORKBENCH_DEFAULT_RATIO;
238
+ } catch (error) {
239
+ return WORKBENCH_DEFAULT_RATIO;
240
+ }
241
+ }
242
+ function persistWorkbenchRatio(value) {
243
+ if (typeof window === "undefined" || !window.localStorage) return;
244
+ try {
245
+ if (value === null) window.localStorage.removeItem(WORKBENCH_RATIO_KEY);
246
+ else window.localStorage.setItem(WORKBENCH_RATIO_KEY, String(value));
247
+ } catch (error) {
248
+ }
249
+ }
250
+ function workbenchTrackForRatio(ratio) {
251
+ return `${Number((ratio * 100).toFixed(2))}vw`;
252
+ }
253
+ function repositoryName(topLevel) {
254
+ const normalized = String(topLevel || "").replace(/[\\/]+$/, "");
255
+ const parts = normalized.split(/[\\/]/).filter(Boolean);
256
+ return parts[parts.length - 1] || normalized || "Git \u4ED3\u5E93";
257
+ }
258
+ function displayShellArg(value) {
259
+ return "'" + String(value ?? "").replace(/'/g, "'\\''") + "'";
260
+ }
261
+ function mutationCommand(action, payload = {}) {
262
+ const paths = Array.isArray(payload.paths) ? payload.paths.map(displayShellArg).join(" ") : "";
263
+ if (action === "stage-paths") return { label: "\u6682\u5B58\u6587\u4EF6", command: "git add -- " + paths };
264
+ if (action === "unstage-paths") return { label: "\u53D6\u6D88\u6682\u5B58\u6587\u4EF6", command: "git reset HEAD -- " + paths };
265
+ if (action === "stage-all") return { label: "\u5168\u90E8\u6682\u5B58", command: "git add -A" };
266
+ if (action === "unstage-all") return { label: "\u53D6\u6D88\u5168\u90E8\u6682\u5B58", command: "git reset HEAD -- :/" };
267
+ if (action === "commit") return { label: "\u63D0\u4EA4\u53D8\u66F4", command: "git commit -m " + displayShellArg(payload.message) };
268
+ if (action === "create-branch") return { label: "\u65B0\u5EFA\u5206\u652F", command: "git switch -c " + displayShellArg(payload.name) + " " + displayShellArg(payload.base) };
269
+ if (action === "switch-branch") return { label: "\u5207\u6362\u5206\u652F", command: "git switch " + displayShellArg(payload.name) };
270
+ if (action === "delete-branch") return {
271
+ label: payload.force ? "\u5F3A\u5236\u5220\u9664\u5206\u652F" : "\u5B89\u5168\u5220\u9664\u5206\u652F",
272
+ command: "git branch " + (payload.force ? "-D" : "-d") + " -- " + displayShellArg(payload.name)
273
+ };
274
+ return null;
275
+ }
276
+ function viewportWidth() {
277
+ return typeof window === "undefined" ? 0 : Math.max(1, window.innerWidth);
278
+ }
279
+ function sidebarTrackWidth(layout) {
280
+ const rectWidth = layout.sidebar.getBoundingClientRect().width;
281
+ if (rectWidth > 0) return rectWidth;
282
+ const styleWidth = Number.parseFloat(window.getComputedStyle(layout.sidebar).width);
283
+ return Number.isFinite(styleWidth) ? styleWidth : 0;
284
+ }
285
+ function findWorkbenchHostSplit(anchor) {
286
+ if (typeof window === "undefined") return null;
287
+ const detailsRoot = anchor.closest("[data-side='details']");
288
+ let directChild = detailsRoot instanceof HTMLElement ? detailsRoot : anchor;
289
+ for (let candidate = directChild.parentElement; candidate; candidate = candidate.parentElement) {
290
+ if (window.getComputedStyle(candidate).display === "grid") {
291
+ const children = Array.from(candidate.children).filter((child) => child instanceof HTMLElement);
292
+ const detailsIndex = children.indexOf(directChild);
293
+ const sidebar = children[0];
294
+ const center = children[detailsIndex - 1];
295
+ if (detailsIndex >= 2 && sidebar && center) return { frame: candidate, sidebar, center, details: directChild };
296
+ }
297
+ directChild = candidate;
298
+ }
299
+ return null;
300
+ }
301
+ function buildFileTree(files) {
302
+ const root = { name: "", path: "", folders: [], files: [] };
303
+ const folder = (parent, name) => {
304
+ const existing = parent.folders.find((entry) => entry.name === name);
305
+ if (existing) return existing;
306
+ const created = { name, path: parent.path ? parent.path + "/" + name : name, folders: [], files: [] };
307
+ parent.folders.push(created);
308
+ return created;
309
+ };
310
+ for (const file of files) {
311
+ const sourcePath = String(file.path || "");
312
+ const directory = /[\\/]$/.test(sourcePath);
313
+ const parts = sourcePath.split(String.fromCharCode(92)).join("/").split("/").filter(Boolean);
314
+ if (!parts.length) continue;
315
+ let current = root;
316
+ const folderCount = directory ? parts.length : parts.length - 1;
317
+ for (let index = 0; index < folderCount; index += 1) current = folder(current, parts[index]);
318
+ if (!directory) current.files.push(file);
319
+ }
320
+ const sort = (node) => {
321
+ node.folders.sort((left, right) => left.name.localeCompare(right.name));
322
+ node.files.sort((left, right) => String(left.path || "").localeCompare(String(right.path || "")));
323
+ node.folders.forEach(sort);
324
+ };
325
+ sort(root);
326
+ return root;
327
+ }
328
+ function parseReviewRows(diff) {
329
+ const rows = [];
330
+ let inHunk = false;
331
+ let oldLine = 0;
332
+ let newLine = 0;
333
+ let previousOldNext = 1;
334
+ let previousNewNext = 1;
335
+ for (const line of diff.split(/\r?\n/)) {
336
+ const hunk = /^@@ -(\d+)(?:,(\d+))? \+(\d+)(?:,(\d+))? @@/.exec(line);
337
+ if (hunk) {
338
+ const oldStart = Number(hunk[1]);
339
+ const newStart = Number(hunk[3]);
340
+ const skipped = Math.max(oldStart - previousOldNext, newStart - previousNewNext);
341
+ if (skipped > 0) rows.push({ kind: "skipped", oldNumber: null, newNumber: null, text: skipped + " \u884C\u672A\u4FEE\u6539\u5185\u5BB9\uFF08\u7531 Git \u7701\u7565\uFF09" });
342
+ oldLine = oldStart;
343
+ newLine = newStart;
344
+ inHunk = true;
345
+ continue;
346
+ }
347
+ if (!inHunk) continue;
348
+ if (line.charCodeAt(0) === 92) {
349
+ rows.push({ kind: "annotation", oldNumber: null, newNumber: null, text: line.slice(1).trim() || "\u6587\u4EF6\u672B\u5C3E\u6CA1\u6709\u6362\u884C\u7B26" });
350
+ continue;
351
+ }
352
+ if (line.startsWith(" ")) {
353
+ rows.push({ kind: "context", oldNumber: oldLine, newNumber: newLine, text: line.slice(1) });
354
+ oldLine += 1;
355
+ newLine += 1;
356
+ previousOldNext = oldLine;
357
+ previousNewNext = newLine;
358
+ continue;
359
+ }
360
+ if (line.startsWith("-")) {
361
+ rows.push({ kind: "deleted", oldNumber: oldLine, newNumber: null, text: line.slice(1) });
362
+ oldLine += 1;
363
+ previousOldNext = oldLine;
364
+ previousNewNext = newLine;
365
+ continue;
366
+ }
367
+ if (line.startsWith("+")) {
368
+ rows.push({ kind: "added", oldNumber: null, newNumber: newLine, text: line.slice(1) });
369
+ newLine += 1;
370
+ previousOldNext = oldLine;
371
+ previousNewNext = newLine;
372
+ }
373
+ }
374
+ return rows;
375
+ }
376
+ function diffLineClass(line) {
377
+ if (line.startsWith("+++") || line.startsWith("---") || line.startsWith("diff --git") || line.startsWith("index ")) return "gg-diff-meta";
378
+ if (line.startsWith("@@")) return "gg-diff-modified";
379
+ if (line.startsWith("+")) return "gg-diff-added";
380
+ if (line.startsWith("-")) return "gg-diff-deleted";
381
+ return "gg-diff-context";
382
+ }
383
+
384
+ // src/client/index.ts
385
+ var React = require("react");
386
+ var RPC_URL = "/git-guide";
387
+ function rpc(body, signal) {
388
+ return fetch(RPC_URL, {
389
+ method: "POST",
390
+ headers: { "Content-Type": "application/json" },
391
+ body: JSON.stringify(body || {}),
392
+ signal
393
+ }).then((r) => r.json());
394
+ }
395
+ function rpcRepositoryMutation(action, sessionId, payload = {}) {
396
+ const request = { action, sessionId, operationId: operationId(action), ...payload };
397
+ return rpc(request);
398
+ }
399
+ function errorText2(error) {
400
+ return error instanceof Error ? error.message : String(error);
401
+ }
402
+ function injectStyles() {
403
+ if (typeof document === "undefined") return () => {
404
+ };
405
+ if (document.getElementById("dsh-easygit-plugin-css")) return () => {
406
+ };
407
+ const tag = document.createElement("style");
408
+ tag.id = "dsh-easygit-plugin-css";
409
+ tag.textContent = `
410
+ .gg-dock { margin: 2px 0; padding: 6px 10px; font-size: 13px; line-height: 1.5; color: inherit; }
411
+ .gg-dock-full { border: 1px solid rgba(127,127,127,.35); border-radius: 8px; background: rgba(127,127,127,.06); }
412
+ .gg-idle { display: flex; align-items: center; gap: 8px; flex-wrap: wrap; }
413
+ .gg-idletext { opacity: .65; font-size: 12px; }
414
+ .gg-head { display: flex; align-items: center; gap: 8px; font-weight: 600; margin-bottom: 6px; }
415
+ .gg-toggle { margin-left: auto; padding: 0 8px; font-size: 12px; line-height: 18px; }
416
+ .gg-recovery { margin-top: 8px; }
417
+ .gg-badge { font-size: 11px; padding: 1px 8px; border-radius: 999px; font-weight: 600; }
418
+ .gg-badge.safe { color: #0a7d33; background: rgba(10,125,51,.15); }
419
+ .gg-badge.normal { color: #b26a00; background: rgba(178,106,0,.15); }
420
+ .gg-badge.hard { color: #c62828; background: rgba(198,40,40,.18); }
421
+ .gg-intent { opacity: .75; font-size: 12px; margin-bottom: 6px; }
422
+ .gg-steps { display: flex; flex-direction: column; gap: 4px; margin: 6px 0; }
423
+ .gg-step { display: flex; gap: 6px; align-items: baseline; }
424
+ .gg-stepnum { flex: none; font-weight: 600; opacity: .6; font-size: 12px; }
425
+ .gg-stepcode { font-family: ui-monospace, SFMono-Regular, Menlo, Consolas, monospace; font-size: 12.5px; background: rgba(127,127,127,.12); border-radius: 4px; padding: 3px 8px; overflow-x: auto; white-space: pre-wrap; word-break: break-all; user-select: all; }
426
+ .gg-stepres { display: flex; gap: 6px; align-items: baseline; font-size: 12px; }
427
+ .gg-expl { opacity: .9; margin: 8px 0; }
428
+ .gg-actions { display: flex; gap: 8px; flex-wrap: wrap; align-items: center; }
429
+ .gg-btn { border: 1px solid rgba(180,180,180,.48); border-radius: 4px; padding: 5px 8px; cursor: pointer; font-size: 12px; background: transparent; color: inherit; }
430
+ .gg-btn:disabled { opacity: .45; cursor: not-allowed; }
431
+ .gg-btn.primary { background: rgba(0,197,139,.14); border-color: #00c58b; color: #53f1bc; }
432
+ .gg-btn.danger { background: #c62828; border-color: #c62828; color: #fff; }
433
+ .gg-riskline { font-size: 12px; color: #c62828; margin: 6px 0; }
434
+ .gg-check { display: flex; gap: 6px; align-items: center; cursor: pointer; font-size: 12.5px; margin: 6px 0; }
435
+ .gg-out { margin-top: 8px; font-size: 12px; }
436
+ .gg-ran { margin: 8px 0; }
437
+ .gg-pre { font-family: ui-monospace, Menlo, Consolas, monospace; font-size: 11.5px; background: rgba(127,127,127,.1); border-radius: 6px; padding: 8px; overflow-x: auto; white-space: pre-wrap; word-break: break-all; margin: 4px 0; }
438
+ .gg-ok { color: #0a7d33; font-weight: 600; }
439
+ .gg-fail { color: #c62828; font-weight: 600; }
440
+ .gg-workbench-action { position: relative; display: inline-flex; height: 28px; align-items: center; gap: 5px; border: 0; border-radius: 8px; padding: 0 8px; background: transparent; color: var(--dsw-alias-label-secondary, inherit); font-size: 13px; line-height: 20px; font-weight: 500; transition: background-color 100ms ease, box-shadow 100ms ease, color 100ms ease; }
441
+ .gg-workbench-action:hover:not(:disabled) { background: var(--dsw-alias-interactive-bg-hover, rgba(127,127,127,.12)); box-shadow: var(--dsw-shadow-lv1, 0 2px 4px rgba(0,0,0,.12)); }
442
+ .gg-workbench-action:focus-visible { outline: 2px solid var(--dsw-alias-state-business-primary, #3964fe); outline-offset: 2px; }
443
+ .gg-workbench-action[aria-pressed="true"] { border: 0; background: var(--dsw-alias-button-ghost-active-fill, rgba(127,127,127,.16)); color: var(--dsw-alias-state-business-primary, #3964fe); }
444
+ .gg-workbench-action[aria-pressed="true"]:hover:not(:disabled) { background: var(--dsw-alias-button-ghost-active-hover, rgba(127,127,127,.22)); }
445
+ .gg-workbench-action-dot { width: 6px; height: 6px; border-radius: 50%; background: #e17b00; display: inline-block; }
446
+ html[data-git-guide-workbench-open] div[data-side='details'][data-side='details'] { display: none !important; pointer-events: none !important; }
447
+ .gg-workbench { position: fixed; z-index: 1; inset: 0 0 0 auto; box-sizing: border-box; width: var(--dsh-easygit-plugin-workbench-width, 36vw); max-width: 100vw; min-width: 0; display: flex; flex-direction: column; border-left: 1px solid rgba(174,180,184,.75); color: #e9ecef; background: #202224; box-shadow: none; font-family: ui-monospace, SFMono-Regular, Menlo, Consolas, monospace; }
448
+ .gg-workbench-resize { position: absolute; z-index: 5; top: 0; bottom: 0; left: -6px; width: 12px; padding: 0; border: 0; background: transparent; cursor: col-resize; touch-action: none; }
449
+ .gg-workbench-resize::after { content: ''; position: absolute; top: 0; bottom: 0; left: 4px; width: 2px; background: rgba(127,127,127,.32); transition: background-color .12s ease, box-shadow .12s ease; }
450
+ .gg-workbench-resize:hover::after, .gg-workbench-resize:focus-visible::after, .gg-workbench-resize.dragging::after { background: #00c58b; box-shadow: 0 0 0 1px rgba(0,197,139,.28); }
451
+ .gg-workbench-resize:focus-visible { outline: 2px solid #00c58b; outline-offset: -2px; }
452
+ html[data-git-guide-workbench-resizing], html[data-git-guide-workbench-resizing] * { cursor: col-resize !important; user-select: none !important; }
453
+ .gg-workbench-head { box-sizing: border-box; display: flex; min-height: 75px; flex: none; align-items: center; gap: 8px; padding: 14px 12px 12px; border-bottom: 1px solid #aeb4b8; }
454
+ .gg-workbench-title { font-size: 14px; line-height: 20px; font-weight: 500; color: #f4f4f4; }
455
+ .gg-workbench-close { display: grid; width: 28px; height: 28px; margin-left: auto; place-items: center; border: 0; border-radius: 999px; padding: 0; background: transparent; color: var(--dsw-alias-label-secondary, inherit); }
456
+ .gg-workbench-close:hover:not(:disabled) { background: var(--dsw-alias-interactive-bg-hover, rgba(127,127,127,.12)); }
457
+ .gg-workbench-body { min-height: 0; flex: 1; overflow: auto; padding: 7px; }
458
+ .gg-command-log { display: flex; min-height: 110px; max-height: 210px; flex: 0 0 auto; flex-direction: column; border-top: 1px solid #aeb4b8; background: #181a1b; }
459
+ .gg-command-log-head { flex: none; padding: 5px 8px 3px; color: #dce1e4; font-size: 11px; }
460
+ .gg-command-log-body { min-height: 0; overflow: auto; padding: 0 8px 6px; }
461
+ .gg-command-entry { display: grid; grid-template-columns: minmax(0,1fr) auto; gap: 2px 8px; padding: 3px 0; font-size: 11px; }
462
+ .gg-command-label { overflow: hidden; color: #ff8a24; text-overflow: ellipsis; white-space: nowrap; }
463
+ .gg-command-status { font-size: 10px; }
464
+ .gg-command-status.running { color: #ffd166; }
465
+ .gg-command-status.succeeded { color: #55e58b; }
466
+ .gg-command-status.failed { color: #ff7878; }
467
+ .gg-command-code { grid-column: 1 / -1; overflow-wrap: anywhere; color: #e3e7e9; white-space: pre-wrap; }
468
+ .gg-workbench-error { color: #ff6c6c; font-size: 12px; margin: 2px 0 0; }
469
+ .gg-diagnostics { max-height: 120px; margin: 0; overflow: auto; border: 1px solid rgba(255,108,108,.7); border-radius: 3px; padding: 6px; color: #ffb0b0; background: rgba(135,22,22,.22); font-size: 11px; white-space: pre-wrap; }
470
+ .gg-tabs { display: flex; gap: 4px; overflow-x: auto; border-bottom: 1px solid #aeb4b8; padding-bottom: 7px; }
471
+ .gg-tab { flex: none; border: 1px solid transparent; border-radius: 3px; padding: 4px 7px; background: transparent; color: #d4d9dc; font-size: 12px; cursor: pointer; }
472
+ .gg-tab.active { border-color: #00c58b; color: #54f0bd; background: rgba(0,197,139,.12); }
473
+ .gg-tab-content { display: flex; min-height: 0; flex-direction: column; gap: 8px; padding-top: 8px; }
474
+ .gg-tab-toolbar { display: flex; gap: 6px; align-items: center; flex-wrap: wrap; }
475
+ .gg-change-layout { display: flex; min-width: 0; flex-direction: column; gap: 8px; }
476
+ .gg-change-files { display: flex; min-width: 0; flex-direction: column; gap: 8px; }
477
+ .gg-file-group, .gg-branch-list, .gg-commit-list, .gg-stash-list, .gg-commit-form, .gg-branch-form { display: flex; flex-direction: column; gap: 5px; border: 1px solid rgba(215,220,222,.72); border-radius: 3px; padding: 6px; }
478
+ .gg-file-group > strong { color: #51efba; font-size: 12px; }
479
+ .gg-file-tree { display: flex; flex-direction: column; min-width: 0; }
480
+ .gg-tree-folder { display: flex; flex-direction: column; min-width: 0; }
481
+ .gg-folder-toggle { display: flex; min-width: 0; align-items: center; gap: 4px; border: 0; padding: 4px 2px; background: transparent; color: #dfe7e8; text-align: left; cursor: pointer; font: inherit; font-size: 12px; }
482
+ .gg-folder-arrow { width: 12px; flex: none; color: #85d7c0; }
483
+ .gg-folder-name { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
484
+ .gg-file, .gg-branch-row { display: flex; min-width: 0; align-items: center; gap: 6px; padding: 4px 0; border-bottom: 1px solid rgba(200,200,200,.15); }
485
+ .gg-file.active { background: rgba(0,197,139,.12); }
486
+ .gg-file.added code { color: #55e58b; }
487
+ .gg-file.deleted code { color: #ff7878; }
488
+ .gg-file.modified code { color: #ffd166; }
489
+ .gg-file-path { min-width: 0; flex: 1; overflow: hidden; border: 0; background: transparent; color: inherit; text-align: left; text-overflow: ellipsis; white-space: nowrap; cursor: pointer; }
490
+ .gg-branch-row code { flex: none; font-size: 11px; }
491
+ .gg-branch-row .gg-idletext { min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
492
+ .gg-reference-tabs { display: flex; gap: 4px; }
493
+ .gg-local-branches { display: flex; flex-direction: column; gap: 6px; }
494
+ .gg-reference-empty { padding: 6px 0; }
495
+ .gg-reference-row code:first-child { min-width: 0; flex: 1; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
496
+ .gg-reference-hash { color: #7f8a91; }
497
+ .gg-branch-row.confirming { flex-wrap: wrap; }
498
+ .gg-branch-confirm { display: flex; width: 100%; flex-direction: column; gap: 5px; padding: 6px; border: 1px solid rgba(198,40,40,.65); border-radius: 4px; background: rgba(198,40,40,.1); }
499
+ .gg-branch-confirm-actions { display: flex; gap: 6px; flex-wrap: wrap; }
500
+ .gg-commit-layout { display: flex; min-width: 0; align-items: flex-start; gap: 8px; flex-wrap: wrap; }
501
+ .gg-commit-list { min-width: 0; flex: 1 1 280px; gap: 0; overflow-x: hidden; padding: 4px 2px; }
502
+ .gg-commit-row { display: grid; width: 100%; min-width: 0; min-height: 36px; grid-template-columns: auto minmax(0, 1fr); border: 0; padding: 0; background: transparent; color: inherit; text-align: left; cursor: pointer; }
503
+ .gg-commit-row:hover, .gg-commit-row.active { background: rgba(0,197,139,.12); }
504
+ .gg-commit-row:focus-visible { outline: 1px solid #00c58b; outline-offset: -1px; }
505
+ .gg-commit-graph { display: block; align-self: stretch; overflow: visible; }
506
+ .gg-commit-copy { display: flex; min-width: 0; flex-direction: column; justify-content: center; padding: 2px 4px 2px 3px; }
507
+ .gg-commit-main { display: flex; min-width: 0; align-items: center; gap: 5px; }
508
+ .gg-commit-subject { min-width: 36px; flex: 0 1 auto; overflow: hidden; color: #e7e9ea; text-overflow: ellipsis; white-space: nowrap; }
509
+ .gg-commit-refs { display: flex; min-width: 0; flex: 0 1 auto; gap: 4px; overflow: hidden; }
510
+ .gg-ref { max-width: 190px; flex: 0 1 auto; overflow: hidden; border: 1px solid currentColor; border-radius: 999px; padding: 0 7px; font-size: 10.5px; line-height: 18px; text-overflow: ellipsis; white-space: nowrap; }
511
+ .gg-ref.branch { color: #62a9ff; background: rgba(56,132,224,.18); }
512
+ .gg-ref.current { color: #83bdff; background: rgba(54,142,247,.34); }
513
+ .gg-ref.remote { color: #ff8a24; background: rgba(230,100,0,.22); }
514
+ .gg-ref.tag { color: #ce8cff; background: rgba(153,73,212,.22); }
515
+ .gg-commit-meta { display: flex; min-width: 0; gap: 7px; color: #8f979d; font-size: 10.5px; }
516
+ .gg-commit-author { min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
517
+ .gg-commit-hash { flex: none; color: #737d84; }
518
+ .gg-commit-detail { display: flex; min-width: 0; flex: 1 1 340px; flex-direction: column; gap: 7px; border: 1px solid rgba(215,220,222,.72); border-radius: 3px; padding: 8px; background: #1b1d1f; }
519
+ .gg-commit-detail-head { display: flex; min-width: 0; align-items: flex-start; gap: 8px; }
520
+ .gg-commit-detail-title { min-width: 0; flex: 1; overflow-wrap: anywhere; color: #f0f2f3; }
521
+ .gg-commit-detail-hash { flex: none; color: #879198; font-size: 10.5px; }
522
+ .gg-commit-detail-close { flex: none; min-width: 26px; padding: 1px 6px; }
523
+ .gg-commit-detail-meta { display: grid; grid-template-columns: auto minmax(0, 1fr); gap: 3px 8px; font-size: 11px; }
524
+ .gg-commit-detail-meta dt { color: #8f979d; }
525
+ .gg-commit-detail-meta dd { min-width: 0; margin: 0; overflow-wrap: anywhere; }
526
+ .gg-commit-message { max-height: 130px; margin: 0; overflow: auto; border-radius: 3px; padding: 6px; background: #151718; color: #d8dde0; white-space: pre-wrap; }
527
+ .gg-commit-summary { display: flex; gap: 9px; color: #aeb8c2; font-size: 11px; flex-wrap: wrap; }
528
+ .gg-additions { color: #55e58b; }
529
+ .gg-deletions { color: #ff7878; }
530
+ .gg-commit-files { display: flex; max-height: 210px; min-width: 0; flex-direction: column; overflow: auto; border: 1px solid rgba(180,180,180,.25); border-radius: 3px; }
531
+ .gg-commit-file { display: grid; min-width: 0; grid-template-columns: auto minmax(0, 1fr) auto auto; gap: 6px; padding: 3px 5px; border-bottom: 1px solid rgba(180,180,180,.12); font-size: 11px; }
532
+ .gg-commit-file:last-child { border-bottom: 0; }
533
+ .gg-commit-file.added { color: #55e58b; }
534
+ .gg-commit-file.deleted { color: #ff7878; }
535
+ .gg-commit-file.modified { color: #ffd166; }
536
+ .gg-commit-file-path { min-width: 0; overflow: hidden; color: #d8dde0; text-overflow: ellipsis; white-space: nowrap; }
537
+ .gg-commit-diff { max-height: 430px; overflow: auto; }
538
+ .gg-stash-list { gap: 0; }
539
+ .gg-stash-row { display: grid; min-width: 0; grid-template-columns: auto minmax(0, 1fr) auto; gap: 4px 8px; padding: 6px 3px; border-bottom: 1px solid rgba(180,180,180,.16); }
540
+ .gg-stash-row:last-child { border-bottom: 0; }
541
+ .gg-stash-selector { color: #ce8cff; }
542
+ .gg-stash-subject { min-width: 0; overflow: hidden; color: #e7e9ea; text-overflow: ellipsis; white-space: nowrap; }
543
+ .gg-stash-hash { color: #737d84; font-size: 10.5px; }
544
+ .gg-stash-meta { display: flex; min-width: 0; grid-column: 1 / -1; gap: 8px; color: #8f979d; font-size: 10.5px; }
545
+ .gg-stash-author { min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
546
+ .gg-stash-date { flex: none; }
547
+ .gg-input { min-width: 0; width: 100%; box-sizing: border-box; border: 1px solid rgba(200,200,200,.55); border-radius: 3px; padding: 6px 8px; background: #181a1b; color: inherit; font-size: 12px; }
548
+ .gg-diff { box-sizing: border-box; display: flex; min-width: 0; flex-direction: column; border: 1px solid rgba(215,220,222,.72); border-radius: 3px; padding: 6px; }
549
+ .gg-review-toolbar { display: flex; gap: 5px; align-items: center; margin-bottom: 6px; }
550
+ .gg-review-mode { padding: 3px 6px; font-size: 11px; }
551
+ .gg-review-mode.active { border-color: #00c58b; color: #53f1bc; background: rgba(0,197,139,.12); }
552
+ .gg-review { min-height: 180px; overflow: auto; border-radius: 3px; background: #181a1b; font-family: ui-monospace, Menlo, Consolas, monospace; font-size: 11.5px; line-height: 1.55; }
553
+ .gg-review-content, .gg-diff-content { display: block; box-sizing: border-box; width: max-content; min-width: 100%; }
554
+ .gg-review-line { display: grid; box-sizing: border-box; width: 100%; grid-template-columns: 38px 38px minmax(0, 1fr); }
555
+ .gg-review-number { padding: 0 5px; color: #97a2aa; background: rgba(127,127,127,.09); text-align: right; user-select: none; }
556
+ .gg-review-code { min-width: 0; padding: 0 6px; color: #d8dde0; white-space: pre; }
557
+ .gg-review-line.added { color: #b7f6c6; background: rgba(27,142,72,.35); }
558
+ .gg-review-line.deleted { color: #ffb5b5; background: rgba(173,38,38,.38); }
559
+ .gg-review-line.added .gg-review-number, .gg-review-line.added .gg-review-code,
560
+ .gg-review-line.deleted .gg-review-number, .gg-review-line.deleted .gg-review-code { color: inherit; background: transparent; }
561
+ .gg-review-skip { box-sizing: border-box; width: 100%; padding: 4px 8px; color: #ffe18a; background: rgba(181,132,13,.25); font-size: 11px; }
562
+ .gg-review-annotation { box-sizing: border-box; width: 100%; padding: 1px 8px; color: #aeb8c2; background: rgba(132,146,162,.12); font-size: 11px; }
563
+ .gg-diff-code { min-height: 180px; margin: 0; background: #181a1b; color: #d8dde0; white-space: pre; word-break: normal; }
564
+ .gg-diff-code span { display: block; box-sizing: border-box; width: 100%; padding: 0 3px; }
565
+ .gg-diff-meta { color: #aeb8c2; background: rgba(132,146,162,.12); }
566
+ .gg-diff-added { color: #b7f6c6; background: rgba(27,142,72,.35); }
567
+ .gg-diff-deleted { color: #ffb5b5; background: rgba(173,38,38,.38); }
568
+ .gg-diff-modified { color: #ffe18a; background: rgba(181,132,13,.34); }
569
+ .gg-diff-context { color: #d8dde0; }
570
+ @media (min-width: 440px) {
571
+ .gg-change-layout { display: grid; grid-template-columns: minmax(175px, 38%) minmax(0, 1fr); align-items: stretch; }
572
+ .gg-diff { min-height: 0; }
573
+ }
574
+ `;
575
+ document.head.appendChild(tag);
576
+ return () => {
577
+ try {
578
+ tag.remove();
579
+ } catch (e) {
580
+ }
581
+ };
582
+ }
583
+ function GitWorkbenchAction(props) {
584
+ const sessionId = String(props.sessionId || "");
585
+ const controller = props.controller;
586
+ const intervalFn = props.intervalFn || null;
587
+ const [state, setState] = React.useState(() => controller.snapshot());
588
+ const [pending, setPending] = React.useState(false);
589
+ const pendingProposalId = React.useRef(null);
590
+ const stateRequestRef = React.useRef({ controller: null, sequence: 0 });
591
+ React.useEffect(() => controller.subscribe(setState), [controller]);
592
+ React.useEffect(() => {
593
+ const refresh = () => {
594
+ const request = beginTrackedRequest(stateRequestRef);
595
+ rpc({ action: "state", sessionId }, request.signal).then((res) => {
596
+ if (!isTrackedRequestCurrent(stateRequestRef, request) || !res || res.ok !== true) return;
597
+ const proposal = res.proposal;
598
+ const isPending = !!(proposal && proposal.status === "pending" && proposal.proposalId);
599
+ setPending(isPending);
600
+ const proposalId = isPending ? proposal.proposalId : null;
601
+ if (proposalId && proposalId !== pendingProposalId.current) controller.open(sessionId);
602
+ pendingProposalId.current = proposalId;
603
+ }).catch((error) => {
604
+ if (isTrackedRequestCurrent(stateRequestRef, request) && !isAbortError(error)) setPending(false);
605
+ });
606
+ };
607
+ refresh();
608
+ const stop = intervalFn ? intervalFn(refresh, 1500) : null;
609
+ return () => {
610
+ cancelTrackedRequest(stateRequestRef);
611
+ if (typeof stop === "function") {
612
+ try {
613
+ stop();
614
+ } catch (err) {
615
+ }
616
+ }
617
+ controller.close(sessionId);
618
+ };
619
+ }, [controller, intervalFn, sessionId]);
620
+ const open = state.open && state.activeSessionId === sessionId;
621
+ return React.createElement(
622
+ "button",
623
+ {
624
+ type: "button",
625
+ className: "gg-btn gg-workbench-action",
626
+ title: state.error || (open ? "\u5173\u95ED Git \u5DE5\u4F5C\u53F0" : "\u6253\u5F00 Git \u5DE5\u4F5C\u53F0"),
627
+ "aria-label": state.error || "Git \u5DE5\u4F5C\u53F0",
628
+ "aria-pressed": open,
629
+ onClick: () => controller.toggle(sessionId)
630
+ },
631
+ React.createElement("span", null, "Git"),
632
+ pending ? React.createElement("span", { className: "gg-workbench-action-dot", "aria-hidden": true }) : null
633
+ );
634
+ }
635
+ function actionError(response) {
636
+ return String(response && (response.message || response.error) || "\u8BF7\u6C42\u5931\u8D25");
637
+ }
638
+ function actionDiagnostics(response) {
639
+ return typeof (response && response.diagnostics) === "string" ? response.diagnostics : "";
640
+ }
641
+ function refreshButtonLabel(state) {
642
+ if (state === "loading") return "\u6B63\u5728\u5237\u65B0\u2026";
643
+ if (state === "succeeded") return "\u5DF2\u5237\u65B0";
644
+ if (state === "failed") return "\u5237\u65B0\u5931\u8D25";
645
+ return "\u5237\u65B0";
646
+ }
647
+ function useManualRefreshFeedback() {
648
+ const [state, setState] = React.useState("idle");
649
+ const resetTimerRef = React.useRef(null);
650
+ const clearResetTimer = () => {
651
+ if (resetTimerRef.current === null) return;
652
+ window.clearTimeout(resetTimerRef.current);
653
+ resetTimerRef.current = null;
654
+ };
655
+ const begin = () => {
656
+ clearResetTimer();
657
+ setState("loading");
658
+ };
659
+ const finish = (succeeded) => {
660
+ clearResetTimer();
661
+ setState(succeeded ? "succeeded" : "failed");
662
+ resetTimerRef.current = window.setTimeout(() => {
663
+ resetTimerRef.current = null;
664
+ setState("idle");
665
+ }, 1200);
666
+ };
667
+ React.useEffect(() => clearResetTimer, []);
668
+ return { state, begin, finish };
669
+ }
670
+ function operationId(prefix) {
671
+ return "ui:" + prefix + ":" + Date.now().toString(36) + ":" + Math.random().toString(36).slice(2, 10);
672
+ }
673
+ function renderDiff(diff) {
674
+ return diff.split("\n").map((line, index) => React.createElement("span", { className: diffLineClass(line), key: "diff-" + index }, line || " "));
675
+ }
676
+ function renderReview(diff) {
677
+ const rows = parseReviewRows(diff);
678
+ if (!rows.length) return React.createElement("div", { className: "gg-idletext" }, diff ? "\u6CA1\u6709\u53EF\u5BA1\u9605\u7684\u4EE3\u7801\u884C\u3002" : "\u6CA1\u6709\u53EF\u663E\u793A\u7684\u5DEE\u5F02\u3002");
679
+ return rows.map((row, index) => {
680
+ if (row.kind === "skipped") return React.createElement("div", { className: "gg-review-skip", key: "review-" + index }, "\u2304 " + row.text);
681
+ if (row.kind === "annotation") return React.createElement("div", { className: "gg-review-annotation", key: "review-" + index }, row.text);
682
+ return React.createElement(
683
+ "div",
684
+ { className: "gg-review-line " + row.kind, key: "review-" + index },
685
+ React.createElement("span", { className: "gg-review-number" }, row.oldNumber === null ? "" : String(row.oldNumber)),
686
+ React.createElement("span", { className: "gg-review-number" }, row.newNumber === null ? "" : String(row.newNumber)),
687
+ React.createElement("code", { className: "gg-review-code" }, row.text || " ")
688
+ );
689
+ });
690
+ }
691
+ function renderRawDiffSurface(diff) {
692
+ return React.createElement("code", { className: "gg-diff-content" }, renderDiff(diff));
693
+ }
694
+ function renderReviewSurface(diff) {
695
+ return React.createElement("div", { className: "gg-review-content" }, renderReview(diff));
696
+ }
697
+ function GitChangesTab(props) {
698
+ const { sessionId, intervalFn, revision, onChanged, onCommand } = props;
699
+ const [summary, setSummary] = React.useState(null);
700
+ const [selected, setSelected] = React.useState(null);
701
+ const [diff, setDiff] = React.useState("");
702
+ const [busy, setBusy] = React.useState(false);
703
+ const [message, setMessage] = React.useState("");
704
+ const [diagnostics, setDiagnostics] = React.useState("");
705
+ const [commitMessage, setCommitMessage] = React.useState("");
706
+ const [collapsedFolders, setCollapsedFolders] = React.useState({});
707
+ const [reviewMode, setReviewMode] = React.useState("review");
708
+ const refreshFeedback = useManualRefreshFeedback();
709
+ const selectedRef = React.useRef(null);
710
+ const manualRefreshRef = React.useRef(false);
711
+ const summaryRequestRef = React.useRef({ controller: null, sequence: 0 });
712
+ const diffRequestRef = React.useRef({ controller: null, sequence: 0 });
713
+ const loadDiff = (selection, showLoading = true) => {
714
+ if (showLoading) setDiff("\u6B63\u5728\u52A0\u8F7D\u5DEE\u5F02\u2026");
715
+ const request = beginTrackedRequest(diffRequestRef);
716
+ return rpc({ action: "get-diff", sessionId, path: selection.path, staged: selection.staged }, request.signal).then((response) => {
717
+ if (!isTrackedRequestCurrent(diffRequestRef, request)) return false;
718
+ if (response && response.ok === true) {
719
+ setDiff(String(response.data.diff || "\u6CA1\u6709\u53EF\u663E\u793A\u7684\u5DEE\u5F02\u3002"));
720
+ return true;
721
+ }
722
+ setDiff(actionError(response));
723
+ return false;
724
+ }).catch((error) => {
725
+ if (!isTrackedRequestCurrent(diffRequestRef, request) || isAbortError(error)) return false;
726
+ setDiff(errorText2(error));
727
+ return false;
728
+ });
729
+ };
730
+ const load = (manual = false) => {
731
+ if (!manual && manualRefreshRef.current) return Promise.resolve(false);
732
+ if (manual) {
733
+ manualRefreshRef.current = true;
734
+ refreshFeedback.begin();
735
+ }
736
+ const request = beginTrackedRequest(summaryRequestRef);
737
+ const summaryLoad = rpc({ action: "get-summary", sessionId }, request.signal).then((response) => {
738
+ if (!isTrackedRequestCurrent(summaryRequestRef, request)) return false;
739
+ if (response && response.ok === true) {
740
+ setSummary(response.data);
741
+ setMessage("");
742
+ setDiagnostics("");
743
+ return true;
744
+ } else {
745
+ setMessage(actionError(response));
746
+ setDiagnostics(actionDiagnostics(response));
747
+ return false;
748
+ }
749
+ }).catch((error) => {
750
+ if (!isTrackedRequestCurrent(summaryRequestRef, request) || isAbortError(error)) return false;
751
+ setMessage(errorText2(error));
752
+ setDiagnostics("");
753
+ return false;
754
+ });
755
+ const selection = manual ? selectedRef.current : null;
756
+ const diffLoad = selection ? loadDiff(selection, true) : Promise.resolve(true);
757
+ return Promise.all([summaryLoad, diffLoad]).then(([summarySucceeded, diffSucceeded]) => {
758
+ const current = isTrackedRequestCurrent(summaryRequestRef, request);
759
+ if (manual && current) refreshFeedback.finish(summarySucceeded && diffSucceeded);
760
+ if (manual) manualRefreshRef.current = false;
761
+ return current && summarySucceeded && diffSucceeded;
762
+ });
763
+ };
764
+ React.useEffect(() => {
765
+ load();
766
+ const stop = intervalFn ? intervalFn(load, 1800) : null;
767
+ return () => {
768
+ cancelTrackedRequest(summaryRequestRef);
769
+ if (typeof stop === "function") stop();
770
+ };
771
+ }, [sessionId, intervalFn, revision]);
772
+ React.useEffect(() => {
773
+ cancelTrackedRequest(diffRequestRef);
774
+ selectedRef.current = null;
775
+ setSelected(null);
776
+ setDiff("");
777
+ }, [sessionId]);
778
+ React.useEffect(() => () => {
779
+ cancelTrackedRequest(summaryRequestRef);
780
+ cancelTrackedRequest(diffRequestRef);
781
+ }, []);
782
+ const runMutation = (action, payload = {}) => {
783
+ const description = mutationCommand(action, payload);
784
+ const completeCommand = description ? onCommand(description.label, description.command) : null;
785
+ setBusy(true);
786
+ setMessage("");
787
+ setDiagnostics("");
788
+ return rpcRepositoryMutation(action, sessionId, payload).then((response) => {
789
+ if (!response || response.ok !== true) {
790
+ if (completeCommand) completeCommand(false);
791
+ setMessage(actionError(response));
792
+ setDiagnostics(actionDiagnostics(response));
793
+ return false;
794
+ } else {
795
+ if (completeCommand) completeCommand(true);
796
+ cancelTrackedRequest(summaryRequestRef);
797
+ setSummary(response.data);
798
+ onChanged();
799
+ return true;
800
+ }
801
+ }).catch((error) => {
802
+ if (completeCommand) completeCommand(false);
803
+ setMessage(errorText2(error));
804
+ setDiagnostics("");
805
+ return false;
806
+ }).then((succeeded) => {
807
+ setBusy(false);
808
+ return succeeded;
809
+ });
810
+ };
811
+ const selectFile = (file, staged) => {
812
+ const path = String(file.path || "");
813
+ if (!path) return;
814
+ const selection = { path, staged };
815
+ selectedRef.current = selection;
816
+ setSelected(selection);
817
+ void loadDiff(selection);
818
+ };
819
+ const files = summary && Array.isArray(summary.files) ? summary.files : [];
820
+ const stagedFiles = files.filter((file) => file.indexStatus && file.indexStatus !== " " && file.indexStatus !== "?");
821
+ const unstagedFiles = files.filter((file) => file.workTreeStatus && file.workTreeStatus !== " " || file.indexStatus === "?");
822
+ const fileRow = (file, staged, key, depth) => {
823
+ const status = String(file.indexStatus || " ") + String(file.workTreeStatus || " ");
824
+ const tone = /[?A]/.test(status) ? " added" : /D/.test(status) ? " deleted" : " modified";
825
+ const active = !!(selected && selected.path === file.path && selected.staged === staged);
826
+ return React.createElement(
827
+ "div",
828
+ { className: "gg-file" + tone + (active ? " active" : ""), key, style: { paddingLeft: 4 + depth * 14 } },
829
+ React.createElement(
830
+ "button",
831
+ { className: "gg-file-path", type: "button", onClick: () => selectFile(file, staged) },
832
+ React.createElement("code", null, status + " " + String(file.path || ""))
833
+ ),
834
+ React.createElement("button", {
835
+ className: "gg-btn",
836
+ disabled: busy,
837
+ onClick: () => runMutation(staged ? "unstage-paths" : "stage-paths", { paths: [String(file.path || "")] })
838
+ }, staged ? "\u53D6\u6D88\u6682\u5B58" : "\u6682\u5B58")
839
+ );
840
+ };
841
+ const renderFileTree = (groupFiles, staged, group) => {
842
+ const countFiles = (node) => node.files.length + node.folders.reduce((total, folder) => total + countFiles(folder), 0);
843
+ const renderNode = (node, depth) => {
844
+ const entries = [];
845
+ for (const folder of node.folders) {
846
+ const folderKey = group + ":" + folder.path;
847
+ const collapsed = collapsedFolders[folderKey] === true;
848
+ entries.push(React.createElement(
849
+ "div",
850
+ { className: "gg-tree-folder", key: folderKey },
851
+ React.createElement(
852
+ "button",
853
+ {
854
+ className: "gg-folder-toggle",
855
+ type: "button",
856
+ "aria-expanded": !collapsed,
857
+ style: { paddingLeft: 2 + depth * 14 },
858
+ onClick: () => setCollapsedFolders((current) => ({ ...current, [folderKey]: !current[folderKey] }))
859
+ },
860
+ React.createElement("span", { className: "gg-folder-arrow", "aria-hidden": true }, collapsed ? "\u203A" : "\u2304"),
861
+ React.createElement("span", { className: "gg-folder-name" }, folder.name + " (" + countFiles(folder) + ")")
862
+ ),
863
+ collapsed ? null : renderNode(folder, depth + 1)
864
+ ));
865
+ }
866
+ for (const file of node.files) entries.push(fileRow(file, staged, group + ":" + String(file.path || ""), depth));
867
+ return entries;
868
+ };
869
+ return React.createElement("div", { className: "gg-file-tree" }, renderNode(buildFileTree(groupFiles), 0));
870
+ };
871
+ return React.createElement(
872
+ "section",
873
+ { className: "gg-tab-content" },
874
+ React.createElement(
875
+ "div",
876
+ { className: "gg-tab-toolbar" },
877
+ React.createElement("span", { className: "gg-idletext" }, summary ? repositoryName(summary.topLevel) + " \u2192 " + String(summary.branch || summary.head || "\u5206\u79BB HEAD") : "\u6B63\u5728\u8BFB\u53D6\u4ED3\u5E93\u2026"),
878
+ React.createElement("button", {
879
+ className: "gg-btn",
880
+ disabled: busy || refreshFeedback.state === "loading",
881
+ onClick: () => {
882
+ void load(true);
883
+ }
884
+ }, refreshButtonLabel(refreshFeedback.state)),
885
+ React.createElement("button", { className: "gg-btn", disabled: busy, onClick: () => runMutation("stage-all") }, "\u5168\u90E8\u6682\u5B58"),
886
+ React.createElement("button", { className: "gg-btn", disabled: busy, onClick: () => runMutation("unstage-all") }, "\u5168\u90E8\u53D6\u6D88\u6682\u5B58")
887
+ ),
888
+ message ? React.createElement("div", { className: "gg-workbench-error" }, message) : null,
889
+ diagnostics ? React.createElement("pre", { className: "gg-diagnostics" }, diagnostics) : null,
890
+ React.createElement(
891
+ "div",
892
+ { className: "gg-change-layout" },
893
+ React.createElement(
894
+ "div",
895
+ { className: "gg-change-files" },
896
+ React.createElement(
897
+ "div",
898
+ { className: "gg-file-group" },
899
+ React.createElement("strong", null, "\u672A\u6682\u5B58 (" + unstagedFiles.length + ")"),
900
+ unstagedFiles.length ? renderFileTree(unstagedFiles, false, "unstaged") : React.createElement("div", { className: "gg-idletext" }, "\u6CA1\u6709\u672A\u6682\u5B58\u7684\u53D8\u66F4\u3002")
901
+ ),
902
+ React.createElement(
903
+ "div",
904
+ { className: "gg-file-group" },
905
+ React.createElement("strong", null, "\u5DF2\u6682\u5B58 (" + stagedFiles.length + ")"),
906
+ stagedFiles.length ? renderFileTree(stagedFiles, true, "staged") : React.createElement("div", { className: "gg-idletext" }, "\u6CA1\u6709\u5DF2\u6682\u5B58\u7684\u53D8\u66F4\u3002")
907
+ ),
908
+ React.createElement(
909
+ "div",
910
+ { className: "gg-commit-form" },
911
+ React.createElement("input", {
912
+ className: "gg-input",
913
+ value: commitMessage,
914
+ placeholder: "\u63D0\u4EA4\u8BF4\u660E",
915
+ disabled: busy,
916
+ onChange: (event) => setCommitMessage(String(event.target.value || ""))
917
+ }),
918
+ React.createElement("button", {
919
+ className: "gg-btn primary",
920
+ disabled: busy || !commitMessage.trim() || stagedFiles.length === 0,
921
+ onClick: () => runMutation("commit", { message: commitMessage }).then((succeeded) => {
922
+ if (succeeded) setCommitMessage("");
923
+ })
924
+ }, "\u63D0\u4EA4")
925
+ )
926
+ ),
927
+ React.createElement(
928
+ "div",
929
+ { className: "gg-diff" },
930
+ React.createElement("div", { className: "gg-intent" }, selected ? String(selected.path) + (selected.staged ? "\uFF08\u5DF2\u6682\u5B58\uFF09" : "\uFF08\u672A\u6682\u5B58\uFF09") : "\u9009\u62E9\u6587\u4EF6\u4EE5\u67E5\u770B\u5DEE\u5F02"),
931
+ selected ? React.createElement(
932
+ "div",
933
+ { className: "gg-review-toolbar" },
934
+ React.createElement("button", { className: "gg-btn gg-review-mode" + (reviewMode === "review" ? " active" : ""), type: "button", onClick: () => setReviewMode("review") }, "\u6587\u4EF6\u5BA1\u9605"),
935
+ React.createElement("button", { className: "gg-btn gg-review-mode" + (reviewMode === "raw" ? " active" : ""), type: "button", onClick: () => setReviewMode("raw") }, "\u539F\u59CB Diff")
936
+ ) : null,
937
+ selected && reviewMode === "review" ? React.createElement("div", { className: "gg-review" }, renderReviewSurface(diff)) : React.createElement(
938
+ "pre",
939
+ { className: "gg-pre gg-diff-code" },
940
+ renderRawDiffSurface(selected ? diff : "\u5C1A\u672A\u9009\u62E9\u6587\u4EF6\u3002")
941
+ )
942
+ )
943
+ )
944
+ );
945
+ }
946
+ function GitBranchesTab(props) {
947
+ const { sessionId, revision, onChanged, onCommand } = props;
948
+ const [branches, setBranches] = React.useState([]);
949
+ const [remotes, setRemotes] = React.useState([]);
950
+ const [tags, setTags] = React.useState([]);
951
+ const [referenceTab, setReferenceTab] = React.useState("local");
952
+ const [branchQuery, setBranchQuery] = React.useState("");
953
+ const [name, setName] = React.useState("");
954
+ const [base, setBase] = React.useState("");
955
+ const [busy, setBusy] = React.useState(false);
956
+ const [message, setMessage] = React.useState("");
957
+ const [diagnostics, setDiagnostics] = React.useState("");
958
+ const [confirmDelete, setConfirmDelete] = React.useState(null);
959
+ const [forceDelete, setForceDelete] = React.useState(null);
960
+ const [riskAccepted, setRiskAccepted] = React.useState(false);
961
+ const refreshFeedback = useManualRefreshFeedback();
962
+ const listRequestRef = React.useRef({ controller: null, sequence: 0 });
963
+ const load = (manual = false) => {
964
+ if (manual) refreshFeedback.begin();
965
+ const request = beginTrackedRequest(listRequestRef);
966
+ return rpc({ action: "get-branches", sessionId }, request.signal).then((response) => {
967
+ if (!isTrackedRequestCurrent(listRequestRef, request)) return false;
968
+ if (response && response.ok === true) {
969
+ const data = response.data && !Array.isArray(response.data) ? response.data : { branches: response.data, remotes: [], tags: [] };
970
+ const next = Array.isArray(data.branches) ? data.branches : [];
971
+ setBranches(next);
972
+ setRemotes(Array.isArray(data.remotes) ? data.remotes : []);
973
+ setTags(Array.isArray(data.tags) ? data.tags : []);
974
+ const current = next.find((branch) => branch.current);
975
+ if (!base && current) setBase(String(current.name || ""));
976
+ if (confirmDelete && !next.some((branch) => branch.name === confirmDelete)) setConfirmDelete(null);
977
+ if (forceDelete && !next.some((branch) => branch.name === forceDelete)) setForceDelete(null);
978
+ setMessage("");
979
+ setDiagnostics("");
980
+ return true;
981
+ } else {
982
+ setMessage(actionError(response));
983
+ setDiagnostics(actionDiagnostics(response));
984
+ return false;
985
+ }
986
+ }).catch((error) => {
987
+ if (!isTrackedRequestCurrent(listRequestRef, request) || isAbortError(error)) return false;
988
+ setMessage(errorText2(error));
989
+ setDiagnostics("");
990
+ return false;
991
+ }).then((succeeded) => {
992
+ if (manual && isTrackedRequestCurrent(listRequestRef, request)) refreshFeedback.finish(succeeded);
993
+ return succeeded;
994
+ });
995
+ };
996
+ React.useEffect(() => {
997
+ load();
998
+ return () => cancelTrackedRequest(listRequestRef);
999
+ }, [sessionId, revision]);
1000
+ const mutate = (action, payload) => {
1001
+ const description = mutationCommand(action, payload);
1002
+ const completeCommand = description ? onCommand(description.label, description.command) : null;
1003
+ setBusy(true);
1004
+ setMessage("");
1005
+ setDiagnostics("");
1006
+ rpcRepositoryMutation(action, sessionId, payload).then((response) => {
1007
+ if (!response || response.ok !== true) {
1008
+ if (completeCommand) completeCommand(false);
1009
+ setMessage(actionError(response));
1010
+ setDiagnostics(actionDiagnostics(response));
1011
+ } else {
1012
+ if (completeCommand) completeCommand(true);
1013
+ onChanged();
1014
+ load();
1015
+ }
1016
+ }).catch((error) => {
1017
+ if (completeCommand) completeCommand(false);
1018
+ setMessage(errorText2(error));
1019
+ setDiagnostics("");
1020
+ }).then(() => setBusy(false));
1021
+ };
1022
+ const cancelDelete = () => {
1023
+ setConfirmDelete(null);
1024
+ setForceDelete(null);
1025
+ setRiskAccepted(false);
1026
+ };
1027
+ const deleteBranch = (branchName, force) => {
1028
+ const description = mutationCommand("delete-branch", { name: branchName, force });
1029
+ const completeCommand = onCommand(description.label, description.command);
1030
+ setBusy(true);
1031
+ setMessage("");
1032
+ setDiagnostics("");
1033
+ rpc({
1034
+ action: "delete-branch",
1035
+ sessionId,
1036
+ operationId: operationId(force ? "force-delete-branch" : "delete-branch"),
1037
+ name: branchName,
1038
+ force,
1039
+ confirmRisk: force && riskAccepted
1040
+ }).then((response) => {
1041
+ if (response && response.ok === true) {
1042
+ completeCommand(true);
1043
+ cancelDelete();
1044
+ onChanged();
1045
+ load();
1046
+ return;
1047
+ }
1048
+ completeCommand(false);
1049
+ setMessage(actionError(response));
1050
+ setDiagnostics(actionDiagnostics(response));
1051
+ if (!force && response && response.reason === "UNMERGED_BRANCH") {
1052
+ setConfirmDelete(null);
1053
+ setForceDelete(branchName);
1054
+ setRiskAccepted(false);
1055
+ }
1056
+ }).catch((error) => {
1057
+ completeCommand(false);
1058
+ setMessage(errorText2(error));
1059
+ setDiagnostics("");
1060
+ }).then(() => setBusy(false));
1061
+ };
1062
+ const referenceRows = (entries, emptyText) => entries.length ? entries.map((entry, index) => React.createElement(
1063
+ "div",
1064
+ { className: "gg-branch-row gg-reference-row", key: String(entry.name || index) },
1065
+ React.createElement("code", null, String(entry.name || "")),
1066
+ React.createElement("code", { className: "gg-reference-hash" }, String(entry.hash || "")),
1067
+ entry.subject ? React.createElement("span", { className: "gg-idletext", title: String(entry.subject) }, String(entry.subject)) : null
1068
+ )) : React.createElement("div", { className: "gg-idletext gg-reference-empty" }, emptyText);
1069
+ const visibleBranches = filterLocalBranches(branches, branchQuery);
1070
+ return React.createElement(
1071
+ "section",
1072
+ { className: "gg-tab-content" },
1073
+ React.createElement(
1074
+ "div",
1075
+ { className: "gg-tab-toolbar" },
1076
+ React.createElement("button", {
1077
+ className: "gg-btn",
1078
+ disabled: busy || refreshFeedback.state === "loading",
1079
+ onClick: () => {
1080
+ void load(true);
1081
+ }
1082
+ }, refreshButtonLabel(refreshFeedback.state))
1083
+ ),
1084
+ message ? React.createElement("div", { className: "gg-workbench-error" }, message) : null,
1085
+ diagnostics ? React.createElement("pre", { className: "gg-diagnostics" }, diagnostics) : null,
1086
+ React.createElement("div", { className: "gg-reference-tabs", role: "tablist", "aria-label": "Git \u5F15\u7528\u7C7B\u578B" }, [
1087
+ { id: "local", label: "\u672C\u5730 (" + branches.length + ")" },
1088
+ { id: "remote", label: "\u8FDC\u7A0B (" + remotes.length + ")" },
1089
+ { id: "tag", label: "\u6807\u7B7E (" + tags.length + ")" }
1090
+ ].map((entry) => React.createElement("button", {
1091
+ className: "gg-tab" + (referenceTab === entry.id ? " active" : ""),
1092
+ type: "button",
1093
+ role: "tab",
1094
+ key: entry.id,
1095
+ "aria-selected": referenceTab === entry.id,
1096
+ onClick: () => setReferenceTab(entry.id)
1097
+ }, entry.label))),
1098
+ referenceTab === "local" ? React.createElement(
1099
+ "div",
1100
+ { className: "gg-local-branches" },
1101
+ React.createElement("input", {
1102
+ className: "gg-input",
1103
+ type: "search",
1104
+ value: branchQuery,
1105
+ placeholder: "\u641C\u7D22\u672C\u5730\u5206\u652F",
1106
+ "aria-label": "\u641C\u7D22\u672C\u5730\u5206\u652F",
1107
+ onChange: (event) => setBranchQuery(String(event.target.value || ""))
1108
+ }),
1109
+ React.createElement("div", { className: "gg-branch-list" }, visibleBranches.length ? visibleBranches.map((branch, index) => {
1110
+ const branchName = String(branch.name || "");
1111
+ const confirming = confirmDelete === branchName;
1112
+ const forcing = forceDelete === branchName;
1113
+ return React.createElement(
1114
+ "div",
1115
+ { className: "gg-branch-row" + (confirming || forcing ? " confirming" : ""), key: branchName || String(index) },
1116
+ React.createElement("code", null, (branch.current ? "* " : "") + branchName),
1117
+ branch.upstream ? React.createElement("span", { className: "gg-idletext" }, String(branch.upstream)) : null,
1118
+ branch.current ? null : React.createElement("button", { className: "gg-btn", disabled: busy, onClick: () => {
1119
+ cancelDelete();
1120
+ mutate("switch-branch", { name: branchName });
1121
+ } }, "\u5207\u6362"),
1122
+ branch.current || confirming || forcing ? null : React.createElement("button", {
1123
+ className: "gg-btn",
1124
+ disabled: busy,
1125
+ onClick: () => {
1126
+ setConfirmDelete(branchName);
1127
+ setForceDelete(null);
1128
+ setRiskAccepted(false);
1129
+ }
1130
+ }, "\u5220\u9664"),
1131
+ confirming ? React.createElement(
1132
+ "div",
1133
+ { className: "gg-branch-confirm" },
1134
+ React.createElement("span", null, `\u786E\u5B9A\u5B89\u5168\u5220\u9664\u5206\u652F\u201C${branchName}\u201D\u5417\uFF1F`),
1135
+ React.createElement(
1136
+ "div",
1137
+ { className: "gg-branch-confirm-actions" },
1138
+ React.createElement("button", { className: "gg-btn danger", disabled: busy, onClick: () => deleteBranch(branchName, false) }, "\u786E\u8BA4\u5B89\u5168\u5220\u9664"),
1139
+ React.createElement("button", { className: "gg-btn", disabled: busy, onClick: cancelDelete }, "\u53D6\u6D88")
1140
+ )
1141
+ ) : null,
1142
+ forcing ? React.createElement(
1143
+ "div",
1144
+ { className: "gg-branch-confirm" },
1145
+ React.createElement("div", { className: "gg-riskline" }, `\u5206\u652F\u201C${branchName}\u201D\u5305\u542B\u672A\u5408\u5E76\u63D0\u4EA4\u3002\u5F3A\u5236\u5220\u9664\u53EF\u80FD\u5BFC\u81F4\u8FD9\u4E9B\u63D0\u4EA4\u6C38\u4E45\u4E22\u5931\u3002`),
1146
+ React.createElement(
1147
+ "label",
1148
+ { className: "gg-check" },
1149
+ React.createElement("input", { type: "checkbox", checked: riskAccepted, disabled: busy, onChange: (event) => setRiskAccepted(!!event.target.checked) }),
1150
+ "\u6211\u5DF2\u77E5\u6653\u672A\u5408\u5E76\u63D0\u4EA4\u53EF\u80FD\u6C38\u4E45\u4E22\u5931"
1151
+ ),
1152
+ React.createElement(
1153
+ "div",
1154
+ { className: "gg-branch-confirm-actions" },
1155
+ React.createElement("button", { className: "gg-btn danger", disabled: busy || !riskAccepted, onClick: () => deleteBranch(branchName, true) }, "\u5F3A\u5236\u5220\u9664"),
1156
+ React.createElement("button", { className: "gg-btn", disabled: busy, onClick: cancelDelete }, "\u53D6\u6D88")
1157
+ )
1158
+ ) : null
1159
+ );
1160
+ }) : React.createElement("div", { className: "gg-idletext gg-reference-empty" }, branches.length ? "\u6CA1\u6709\u5339\u914D\u7684\u672C\u5730\u5206\u652F\u3002" : "\u6CA1\u6709\u672C\u5730\u5206\u652F\u3002"))
1161
+ ) : referenceTab === "remote" ? React.createElement("div", { className: "gg-branch-list" }, referenceRows(remotes, "\u6CA1\u6709\u8FDC\u7A0B\u5206\u652F\u3002")) : React.createElement("div", { className: "gg-branch-list" }, referenceRows(tags, "\u6CA1\u6709\u6807\u7B7E\u3002")),
1162
+ referenceTab === "local" ? React.createElement(
1163
+ "div",
1164
+ { className: "gg-branch-form" },
1165
+ React.createElement("input", { className: "gg-input", value: name, placeholder: "\u65B0\u5206\u652F\u540D\u79F0", disabled: busy, onChange: (event) => setName(String(event.target.value || "")) }),
1166
+ React.createElement("input", { className: "gg-input", value: base, placeholder: "\u57FA\u7840\u5206\u652F", disabled: busy, onChange: (event) => setBase(String(event.target.value || "")) }),
1167
+ React.createElement("button", {
1168
+ className: "gg-btn primary",
1169
+ disabled: busy || !name.trim() || !base.trim(),
1170
+ onClick: () => mutate("create-branch", { name, base })
1171
+ }, "\u65B0\u5EFA\u5206\u652F")
1172
+ ) : null
1173
+ );
1174
+ }
1175
+ var COMMIT_GRAPH_COLORS = ["#ff7500", "#ffbf16", "#3ba7ff", "#c57cff", "#38d996", "#ff5c8a"];
1176
+ function CommitGraph(props) {
1177
+ const { row } = props;
1178
+ const width = row.laneCount * 16 + 12;
1179
+ const laneX = (lane) => 8 + lane * 16;
1180
+ const paths = row.edges.map((edge, index) => {
1181
+ const fromX = laneX(edge.from);
1182
+ const color = COMMIT_GRAPH_COLORS[edge.from % COMMIT_GRAPH_COLORS.length];
1183
+ if (edge.to === null) {
1184
+ return React.createElement("path", { key: "root-" + index, d: `M ${fromX} 0 L ${fromX} 11`, stroke: color, strokeWidth: 2, fill: "none" });
1185
+ }
1186
+ const toX = laneX(edge.to);
1187
+ return React.createElement("path", {
1188
+ key: "edge-" + index,
1189
+ d: `M ${fromX} 0 C ${fromX} 18, ${toX} 18, ${toX} 36`,
1190
+ stroke: color,
1191
+ strokeWidth: 2,
1192
+ fill: "none"
1193
+ });
1194
+ });
1195
+ const nodeColor = COMMIT_GRAPH_COLORS[row.lane % COMMIT_GRAPH_COLORS.length];
1196
+ const merge = Array.isArray(row.commit.parents) && row.commit.parents.length > 1;
1197
+ return React.createElement(
1198
+ "svg",
1199
+ {
1200
+ className: "gg-commit-graph",
1201
+ width,
1202
+ height: 36,
1203
+ viewBox: `0 0 ${width} 36`,
1204
+ "aria-hidden": "true"
1205
+ },
1206
+ paths,
1207
+ merge ? React.createElement("circle", { cx: laneX(row.lane), cy: 11, r: 7, fill: "#202224", stroke: nodeColor, strokeWidth: 2 }) : null,
1208
+ React.createElement("circle", { cx: laneX(row.lane), cy: 11, r: merge ? 3 : 5, fill: nodeColor })
1209
+ );
1210
+ }
1211
+ function GitCommitsTab(props) {
1212
+ const { sessionId, revision } = props;
1213
+ const [commits, setCommits] = React.useState([]);
1214
+ const [message, setMessage] = React.useState("");
1215
+ const [selectedHash, setSelectedHash] = React.useState("");
1216
+ const [detail, setDetail] = React.useState(null);
1217
+ const [detailLoading, setDetailLoading] = React.useState(false);
1218
+ const [detailMessage, setDetailMessage] = React.useState("");
1219
+ const [commitDiff, setCommitDiff] = React.useState(null);
1220
+ const [diffLoading, setDiffLoading] = React.useState(false);
1221
+ const [diffMessage, setDiffMessage] = React.useState("");
1222
+ const refreshFeedback = useManualRefreshFeedback();
1223
+ const selectedHashRef = React.useRef("");
1224
+ const listRequestRef = React.useRef({ controller: null, sequence: 0 });
1225
+ const detailRequestRef = React.useRef({ controller: null, sequence: 0 });
1226
+ const diffRequestRef = React.useRef({ controller: null, sequence: 0 });
1227
+ const sessionRef = React.useRef(sessionId);
1228
+ const clearSelection = () => {
1229
+ selectedHashRef.current = "";
1230
+ cancelTrackedRequest(detailRequestRef);
1231
+ cancelTrackedRequest(diffRequestRef);
1232
+ setSelectedHash("");
1233
+ setDetail(null);
1234
+ setDetailLoading(false);
1235
+ setDetailMessage("");
1236
+ setCommitDiff(null);
1237
+ setDiffLoading(false);
1238
+ setDiffMessage("");
1239
+ };
1240
+ const loadCommitDetail = (hash, resetView) => {
1241
+ selectedHashRef.current = hash;
1242
+ if (resetView) {
1243
+ cancelTrackedRequest(diffRequestRef);
1244
+ setDetail(null);
1245
+ setCommitDiff(null);
1246
+ setDiffLoading(false);
1247
+ setDiffMessage("");
1248
+ }
1249
+ const request = beginTrackedRequest(detailRequestRef);
1250
+ setSelectedHash(hash);
1251
+ setDetailLoading(true);
1252
+ setDetailMessage("");
1253
+ return rpc({ action: "get-commit-detail", sessionId, hash }, request.signal).then((response) => {
1254
+ if (selectedHashRef.current !== hash || !isTrackedRequestCurrent(detailRequestRef, request)) return false;
1255
+ if (response && response.ok === true) {
1256
+ setDetail(response.data);
1257
+ return true;
1258
+ }
1259
+ setDetailMessage(actionError(response));
1260
+ return false;
1261
+ }).catch((error) => {
1262
+ if (selectedHashRef.current !== hash || !isTrackedRequestCurrent(detailRequestRef, request) || isAbortError(error)) return false;
1263
+ setDetailMessage(errorText2(error));
1264
+ return false;
1265
+ }).then((succeeded) => {
1266
+ if (selectedHashRef.current === hash && isTrackedRequestCurrent(detailRequestRef, request)) setDetailLoading(false);
1267
+ return succeeded;
1268
+ });
1269
+ };
1270
+ const load = (manual = false) => {
1271
+ if (manual) refreshFeedback.begin();
1272
+ const request = beginTrackedRequest(listRequestRef);
1273
+ return rpc({ action: "get-commits", sessionId, limit: 80 }, request.signal).then(async (response) => {
1274
+ if (!isTrackedRequestCurrent(listRequestRef, request)) return false;
1275
+ if (response && response.ok === true) {
1276
+ const next = Array.isArray(response.data) ? response.data : [];
1277
+ setCommits(next);
1278
+ setMessage("");
1279
+ const activeHash = selectedHashRef.current;
1280
+ if (activeHash && !next.some((commit) => commit.hash === activeHash)) {
1281
+ clearSelection();
1282
+ return true;
1283
+ }
1284
+ if (manual && activeHash) return loadCommitDetail(activeHash, false);
1285
+ return true;
1286
+ }
1287
+ setMessage(actionError(response));
1288
+ return false;
1289
+ }).catch((error) => {
1290
+ if (!isTrackedRequestCurrent(listRequestRef, request) || isAbortError(error)) return false;
1291
+ setMessage(errorText2(error));
1292
+ return false;
1293
+ }).then((succeeded) => {
1294
+ if (manual && isTrackedRequestCurrent(listRequestRef, request)) refreshFeedback.finish(succeeded);
1295
+ return succeeded;
1296
+ });
1297
+ };
1298
+ React.useEffect(() => {
1299
+ if (sessionRef.current !== sessionId) {
1300
+ sessionRef.current = sessionId;
1301
+ clearSelection();
1302
+ }
1303
+ load();
1304
+ return () => cancelTrackedRequest(listRequestRef);
1305
+ }, [sessionId, revision]);
1306
+ React.useEffect(() => () => {
1307
+ cancelTrackedRequest(listRequestRef);
1308
+ cancelTrackedRequest(detailRequestRef);
1309
+ cancelTrackedRequest(diffRequestRef);
1310
+ }, []);
1311
+ const selectCommit = (commit) => {
1312
+ const hash = String(commit.hash || "");
1313
+ if (!hash) return;
1314
+ if (!nextCommitSelection(selectedHashRef.current, hash)) {
1315
+ clearSelection();
1316
+ return;
1317
+ }
1318
+ void loadCommitDetail(hash, true);
1319
+ };
1320
+ const loadCommitDiff = () => {
1321
+ const hash = selectedHashRef.current;
1322
+ if (!hash || diffLoading) return;
1323
+ const request = beginTrackedRequest(diffRequestRef);
1324
+ setDiffLoading(true);
1325
+ setDiffMessage("");
1326
+ rpc({ action: "get-commit-diff", sessionId, hash }, request.signal).then((response) => {
1327
+ const current = selectedHashRef.current === hash && isTrackedRequestCurrent(diffRequestRef, request);
1328
+ if (!current) return;
1329
+ if (response && response.ok === true) setCommitDiff(response.data);
1330
+ else setDiffMessage(actionError(response));
1331
+ }).catch((error) => {
1332
+ const current = selectedHashRef.current === hash && isTrackedRequestCurrent(diffRequestRef, request);
1333
+ if (current && !isAbortError(error)) setDiffMessage(errorText2(error));
1334
+ }).then(() => {
1335
+ const current = selectedHashRef.current === hash && isTrackedRequestCurrent(diffRequestRef, request);
1336
+ if (current) setDiffLoading(false);
1337
+ });
1338
+ };
1339
+ const rows = deriveCommitGraph(commits);
1340
+ const detailPanel = !selectedHash ? null : React.createElement(
1341
+ "div",
1342
+ { className: "gg-commit-detail" },
1343
+ React.createElement(
1344
+ "div",
1345
+ { className: "gg-commit-detail-head" },
1346
+ React.createElement("strong", { className: "gg-commit-detail-title" }, detail ? String(detail.subject || "\uFF08\u65E0\u63D0\u4EA4\u8BF4\u660E\uFF09") : "\u63D0\u4EA4\u8BE6\u60C5"),
1347
+ detail ? React.createElement("code", { className: "gg-commit-detail-hash", title: String(detail.hash || "") }, String(detail.hash || "").slice(0, 12)) : null,
1348
+ React.createElement("button", {
1349
+ className: "gg-btn gg-commit-detail-close",
1350
+ type: "button",
1351
+ title: "\u5173\u95ED\u63D0\u4EA4\u8BE6\u60C5",
1352
+ "aria-label": "\u5173\u95ED\u63D0\u4EA4\u8BE6\u60C5",
1353
+ onClick: clearSelection
1354
+ }, "\xD7")
1355
+ ),
1356
+ detailLoading ? React.createElement("div", { className: "gg-idletext" }, "\u6B63\u5728\u52A0\u8F7D\u63D0\u4EA4\u8BE6\u60C5\u2026") : null,
1357
+ detailMessage ? React.createElement("div", { className: "gg-workbench-error" }, detailMessage) : null,
1358
+ detail ? React.createElement(
1359
+ React.Fragment,
1360
+ null,
1361
+ detail.body ? React.createElement("pre", { className: "gg-commit-message" }, String(detail.body)) : null,
1362
+ React.createElement(
1363
+ "dl",
1364
+ { className: "gg-commit-detail-meta" },
1365
+ React.createElement("dt", null, "\u4F5C\u8005"),
1366
+ React.createElement("dd", null, String(detail.authorName || "\u672A\u77E5\u4F5C\u8005") + (detail.authorEmail ? " <" + String(detail.authorEmail) + ">" : "")),
1367
+ React.createElement("dt", null, "\u4F5C\u8005\u65F6\u95F4"),
1368
+ React.createElement("dd", null, String(detail.authoredAt || "\u672A\u77E5")),
1369
+ React.createElement("dt", null, "\u63D0\u4EA4\u8005"),
1370
+ React.createElement("dd", null, String(detail.committerName || "\u672A\u77E5\u63D0\u4EA4\u8005") + (detail.committerEmail ? " <" + String(detail.committerEmail) + ">" : "")),
1371
+ React.createElement("dt", null, "\u63D0\u4EA4\u65F6\u95F4"),
1372
+ React.createElement("dd", null, String(detail.committedAt || "\u672A\u77E5")),
1373
+ React.createElement("dt", null, "\u7236\u63D0\u4EA4"),
1374
+ React.createElement("dd", null, Array.isArray(detail.parents) && detail.parents.length ? detail.parents.map((parent) => parent.slice(0, 12)).join(", ") : "\u6839\u63D0\u4EA4"),
1375
+ Array.isArray(detail.parents) && detail.parents.length > 1 ? React.createElement("dt", null, "\u5DEE\u5F02\u57FA\u7EBF") : null,
1376
+ Array.isArray(detail.parents) && detail.parents.length > 1 ? React.createElement("dd", null, "\u7B2C\u4E00\u7236\u63D0\u4EA4 " + String(detail.comparisonBase || "").slice(0, 12)) : null
1377
+ ),
1378
+ React.createElement(
1379
+ "div",
1380
+ { className: "gg-commit-summary" },
1381
+ React.createElement("span", null, String(detail.totals?.files ?? 0) + " \u4E2A\u6587\u4EF6"),
1382
+ React.createElement("span", { className: "gg-additions" }, "+" + String(detail.totals?.additions ?? 0)),
1383
+ React.createElement("span", { className: "gg-deletions" }, "-" + String(detail.totals?.deletions ?? 0)),
1384
+ detail.totals?.binary ? React.createElement("span", null, String(detail.totals.binary) + " \u4E2A\u4E8C\u8FDB\u5236\u6587\u4EF6") : null
1385
+ ),
1386
+ React.createElement("div", { className: "gg-commit-files" }, Array.isArray(detail.files) && detail.files.length ? detail.files.map((file, index) => React.createElement(
1387
+ "div",
1388
+ {
1389
+ className: "gg-commit-file" + commitFileTone(String(file.status || "")),
1390
+ key: String(file.path || index)
1391
+ },
1392
+ React.createElement("code", null, String(file.status || "?")),
1393
+ React.createElement(
1394
+ "span",
1395
+ { className: "gg-commit-file-path", title: String(file.path || "") },
1396
+ file.previousPath ? String(file.previousPath) + " \u2192 " + String(file.path || "") : String(file.path || "")
1397
+ ),
1398
+ React.createElement("span", { className: "gg-additions" }, file.additions === null ? "\u4E8C\u8FDB\u5236" : "+" + String(file.additions)),
1399
+ React.createElement("span", { className: "gg-deletions" }, file.deletions === null ? "" : "-" + String(file.deletions))
1400
+ )) : React.createElement("div", { className: "gg-idletext gg-reference-empty" }, "\u8BE5\u63D0\u4EA4\u6CA1\u6709\u53EF\u663E\u793A\u7684\u6587\u4EF6\u53D8\u66F4\u3002")),
1401
+ detail.filesTruncated ? React.createElement("div", { className: "gg-riskline" }, "\u6587\u4EF6\u5217\u8868\u8FC7\u957F\uFF0C\u4EC5\u663E\u793A\u524D 500 \u9879\u3002") : null,
1402
+ React.createElement(
1403
+ "button",
1404
+ { className: "gg-btn", type: "button", disabled: diffLoading, onClick: loadCommitDiff },
1405
+ diffLoading ? "\u6B63\u5728\u52A0\u8F7D\u8BE6\u7EC6 Diff\u2026" : commitDiff ? "\u91CD\u65B0\u52A0\u8F7D\u8BE6\u7EC6 Diff" : "\u52A0\u8F7D\u8BE6\u7EC6 Diff"
1406
+ ),
1407
+ diffMessage ? React.createElement("div", { className: "gg-workbench-error" }, diffMessage) : null,
1408
+ commitDiff ? React.createElement(
1409
+ React.Fragment,
1410
+ null,
1411
+ commitDiff.truncated ? React.createElement("div", { className: "gg-riskline" }, "Diff \u8FC7\u957F\uFF0C\u5DF2\u622A\u65AD\u4E3A\u524D 300,000 \u4E2A\u5B57\u7B26\u3002") : null,
1412
+ React.createElement(
1413
+ "pre",
1414
+ { className: "gg-pre gg-diff-code gg-commit-diff" },
1415
+ renderRawDiffSurface(String(commitDiff.diff || "\u6CA1\u6709\u53EF\u663E\u793A\u7684\u5DEE\u5F02\u3002"))
1416
+ )
1417
+ ) : null
1418
+ ) : null
1419
+ );
1420
+ return React.createElement(
1421
+ "section",
1422
+ { className: "gg-tab-content" },
1423
+ React.createElement("div", { className: "gg-tab-toolbar" }, React.createElement("button", {
1424
+ className: "gg-btn",
1425
+ disabled: refreshFeedback.state === "loading",
1426
+ onClick: () => {
1427
+ void load(true);
1428
+ }
1429
+ }, refreshButtonLabel(refreshFeedback.state))),
1430
+ message ? React.createElement("div", { className: "gg-workbench-error" }, message) : null,
1431
+ React.createElement(
1432
+ "div",
1433
+ { className: "gg-commit-layout" },
1434
+ React.createElement("div", { className: "gg-commit-list" }, rows.length ? rows.map((row, index) => {
1435
+ const commit = row.commit;
1436
+ const refs = Array.isArray(commit.refs) ? commit.refs : [];
1437
+ const hash = String(commit.hash || "");
1438
+ const activate = () => selectCommit(commit);
1439
+ return React.createElement(
1440
+ "div",
1441
+ {
1442
+ className: "gg-commit-row" + (selectedHash === hash ? " active" : ""),
1443
+ key: hash || String(index),
1444
+ title: [commit.hash, commit.author, commit.date].filter(Boolean).join(" \xB7 "),
1445
+ role: "button",
1446
+ tabIndex: 0,
1447
+ "aria-pressed": selectedHash === hash,
1448
+ onClick: activate,
1449
+ onKeyDown: (event) => {
1450
+ if (event.key === "Enter" || event.key === " ") {
1451
+ event.preventDefault();
1452
+ activate();
1453
+ }
1454
+ }
1455
+ },
1456
+ React.createElement(CommitGraph, { row }),
1457
+ React.createElement(
1458
+ "div",
1459
+ { className: "gg-commit-copy" },
1460
+ React.createElement(
1461
+ "div",
1462
+ { className: "gg-commit-main" },
1463
+ React.createElement("span", { className: "gg-commit-subject" }, String(commit.subject || "\uFF08\u65E0\u63D0\u4EA4\u8BF4\u660E\uFF09")),
1464
+ React.createElement("span", { className: "gg-commit-refs" }, refs.map((ref, refIndex) => React.createElement("span", {
1465
+ className: "gg-ref " + String(ref.type || "branch") + (ref.current ? " current" : ""),
1466
+ key: String(ref.type || "") + ":" + String(ref.name || refIndex),
1467
+ title: String(ref.name || "")
1468
+ }, String(ref.name || ""))))
1469
+ ),
1470
+ React.createElement(
1471
+ "div",
1472
+ { className: "gg-commit-meta" },
1473
+ React.createElement("span", { className: "gg-commit-author" }, String(commit.author || "\u672A\u77E5\u4F5C\u8005")),
1474
+ React.createElement("code", { className: "gg-commit-hash" }, hash.slice(0, 8))
1475
+ )
1476
+ )
1477
+ );
1478
+ }) : React.createElement("div", { className: "gg-idletext" }, "\u6CA1\u6709\u63D0\u4EA4\u8BB0\u5F55\u3002")),
1479
+ detailPanel
1480
+ )
1481
+ );
1482
+ }
1483
+ function GitStashesTab(props) {
1484
+ const { sessionId, revision } = props;
1485
+ const [stashes, setStashes] = React.useState([]);
1486
+ const [loading, setLoading] = React.useState(false);
1487
+ const [message, setMessage] = React.useState("");
1488
+ const listRequestRef = React.useRef({ controller: null, sequence: 0 });
1489
+ const load = () => {
1490
+ const request = beginTrackedRequest(listRequestRef);
1491
+ setLoading(true);
1492
+ setMessage("");
1493
+ rpc({ action: "get-stashes", sessionId }, request.signal).then((response) => {
1494
+ if (!isTrackedRequestCurrent(listRequestRef, request)) return;
1495
+ if (response && response.ok === true) setStashes(Array.isArray(response.data) ? response.data.slice(0, 100) : []);
1496
+ else setMessage(actionError(response));
1497
+ }).catch((error) => {
1498
+ if (isTrackedRequestCurrent(listRequestRef, request) && !isAbortError(error)) setMessage(errorText2(error));
1499
+ }).then(() => {
1500
+ if (isTrackedRequestCurrent(listRequestRef, request)) setLoading(false);
1501
+ });
1502
+ };
1503
+ React.useEffect(() => {
1504
+ load();
1505
+ return () => cancelTrackedRequest(listRequestRef);
1506
+ }, [sessionId, revision]);
1507
+ return React.createElement(
1508
+ "section",
1509
+ { className: "gg-tab-content" },
1510
+ React.createElement(
1511
+ "div",
1512
+ { className: "gg-tab-toolbar" },
1513
+ React.createElement("span", { className: "gg-idletext" }, "\u8D2E\u85CF (" + stashes.length + ")"),
1514
+ React.createElement("button", { className: "gg-btn", type: "button", disabled: loading, onClick: load }, loading ? "\u6B63\u5728\u5237\u65B0\u2026" : "\u5237\u65B0")
1515
+ ),
1516
+ message ? React.createElement("div", { className: "gg-workbench-error" }, message) : null,
1517
+ React.createElement("div", { className: "gg-stash-list" }, loading && stashes.length === 0 ? React.createElement("div", { className: "gg-idletext gg-reference-empty" }, "\u6B63\u5728\u8BFB\u53D6\u8D2E\u85CF\u5217\u8868\u2026") : stashes.length ? stashes.map((stash, index) => React.createElement(
1518
+ "div",
1519
+ {
1520
+ className: "gg-stash-row",
1521
+ key: String(stash.hash || stash.selector || index),
1522
+ title: [stash.selector, stash.hash, stash.subject, stash.author, stash.date].filter(Boolean).join(" \xB7 ")
1523
+ },
1524
+ React.createElement("code", { className: "gg-stash-selector" }, String(stash.selector || "stash@{?}")),
1525
+ React.createElement("span", { className: "gg-stash-subject" }, String(stash.subject || "\uFF08\u65E0\u8D2E\u85CF\u8BF4\u660E\uFF09")),
1526
+ React.createElement("code", { className: "gg-stash-hash" }, String(stash.hash || "").slice(0, 8)),
1527
+ React.createElement(
1528
+ "div",
1529
+ { className: "gg-stash-meta" },
1530
+ React.createElement("span", { className: "gg-stash-author" }, String(stash.author || "\u672A\u77E5\u4F5C\u8005")),
1531
+ React.createElement("span", { className: "gg-stash-date" }, String(stash.date || "\u672A\u77E5\u65F6\u95F4"))
1532
+ )
1533
+ )) : React.createElement("div", { className: "gg-idletext gg-reference-empty" }, "\u5F53\u524D\u4ED3\u5E93\u6CA1\u6709\u8D2E\u85CF\u3002"))
1534
+ );
1535
+ }
1536
+ function GitWorkbenchPanel(props) {
1537
+ const [tab, setTab] = React.useState("changes");
1538
+ const [revision, setRevision] = React.useState(0);
1539
+ const [ratio, setRatio] = React.useState(readWorkbenchRatio);
1540
+ const [currentViewportWidth, setCurrentViewportWidth] = React.useState(viewportWidth);
1541
+ const [isResizing, setIsResizing] = React.useState(false);
1542
+ const [commandLogs, setCommandLogs] = React.useState([]);
1543
+ const rootRef = React.useRef(null);
1544
+ const hostSplitRef = React.useRef(null);
1545
+ const resizeDragRef = React.useRef(null);
1546
+ const commandSeqRef = React.useRef(0);
1547
+ const commandLogBodyRef = React.useRef(null);
1548
+ const refresh = () => setRevision((current) => current + 1);
1549
+ const reportCommand = (label, command) => {
1550
+ commandSeqRef.current += 1;
1551
+ const id = commandSeqRef.current;
1552
+ setCommandLogs((current) => appendCommandLog(current, { id, label, command, status: "running" }));
1553
+ let completed = false;
1554
+ return (succeeded) => {
1555
+ if (completed) return;
1556
+ completed = true;
1557
+ setCommandLogs((current) => current.map((entry) => entry.id === id ? { ...entry, status: succeeded ? "succeeded" : "failed" } : entry));
1558
+ };
1559
+ };
1560
+ React.useEffect(() => {
1561
+ const controller = new AbortController();
1562
+ rpc({ action: "state", sessionId: props.sessionId }, controller.signal).then((response) => {
1563
+ if (!controller.signal.aborted && response && response.ok === true && response.proposal && response.proposal.status === "pending") setTab("proposal");
1564
+ }).catch(() => {
1565
+ });
1566
+ return () => controller.abort();
1567
+ }, [props.sessionId]);
1568
+ React.useEffect(() => {
1569
+ setCommandLogs([]);
1570
+ commandSeqRef.current = 0;
1571
+ }, [props.sessionId]);
1572
+ React.useEffect(() => {
1573
+ const element = commandLogBodyRef.current;
1574
+ if (element) element.scrollTop = element.scrollHeight;
1575
+ }, [commandLogs]);
1576
+ React.useLayoutEffect(() => {
1577
+ if (!rootRef.current) return void 0;
1578
+ const layout = findWorkbenchHostSplit(rootRef.current);
1579
+ if (!layout) return void 0;
1580
+ const previousGridTemplateColumns = layout.frame.style.gridTemplateColumns;
1581
+ const previousTrack = layout.frame.style.getPropertyValue(WORKBENCH_TRACK);
1582
+ const previousDetailsWidth = layout.details.style.width;
1583
+ const previousDetailsMinWidth = layout.details.style.minWidth;
1584
+ const previousDetailsMaxWidth = layout.details.style.maxWidth;
1585
+ const previousDetailsBorderLeft = layout.details.style.borderLeft;
1586
+ const splitColumns = `${sidebarTrackWidth(layout)}px minmax(0, 1fr) var(${WORKBENCH_TRACK})`;
1587
+ layout.frame.style.setProperty(WORKBENCH_TRACK, workbenchTrackForRatio(ratio));
1588
+ layout.frame.style.gridTemplateColumns = splitColumns;
1589
+ layout.details.style.width = "100%";
1590
+ layout.details.style.minWidth = "0";
1591
+ layout.details.style.maxWidth = "none";
1592
+ layout.details.style.borderLeft = "none";
1593
+ hostSplitRef.current = {
1594
+ layout,
1595
+ splitColumns,
1596
+ previousGridTemplateColumns,
1597
+ previousTrack,
1598
+ previousDetailsWidth,
1599
+ previousDetailsMinWidth,
1600
+ previousDetailsMaxWidth,
1601
+ previousDetailsBorderLeft
1602
+ };
1603
+ return () => {
1604
+ if (layout.frame.style.gridTemplateColumns === splitColumns) layout.frame.style.gridTemplateColumns = previousGridTemplateColumns;
1605
+ if (previousTrack) layout.frame.style.setProperty(WORKBENCH_TRACK, previousTrack);
1606
+ else layout.frame.style.removeProperty(WORKBENCH_TRACK);
1607
+ layout.details.style.width = previousDetailsWidth;
1608
+ layout.details.style.minWidth = previousDetailsMinWidth;
1609
+ layout.details.style.maxWidth = previousDetailsMaxWidth;
1610
+ layout.details.style.borderLeft = previousDetailsBorderLeft;
1611
+ hostSplitRef.current = null;
1612
+ };
1613
+ }, [props.sessionId, currentViewportWidth]);
1614
+ React.useEffect(() => {
1615
+ const resize = () => setCurrentViewportWidth(viewportWidth());
1616
+ window.addEventListener("resize", resize);
1617
+ return () => {
1618
+ window.removeEventListener("resize", resize);
1619
+ document.documentElement.removeAttribute("data-git-guide-workbench-resizing");
1620
+ };
1621
+ }, []);
1622
+ const setWorkbenchRatio = (nextValue, persist) => {
1623
+ const next = clampWorkbenchRatio(nextValue);
1624
+ const active = hostSplitRef.current;
1625
+ if (active) active.layout.frame.style.setProperty(WORKBENCH_TRACK, workbenchTrackForRatio(next));
1626
+ setRatio(next);
1627
+ if (persist) persistWorkbenchRatio(next);
1628
+ };
1629
+ const onResizePointerDown = (event) => {
1630
+ if (event.button !== 0) return;
1631
+ resizeDragRef.current = {
1632
+ pointerId: event.pointerId,
1633
+ startX: event.clientX,
1634
+ startWidth: viewportWidth() * ratio,
1635
+ currentRatio: ratio
1636
+ };
1637
+ if (event.currentTarget.focus) event.currentTarget.focus();
1638
+ document.documentElement.setAttribute("data-git-guide-workbench-resizing", "");
1639
+ setIsResizing(true);
1640
+ event.preventDefault();
1641
+ };
1642
+ React.useEffect(() => {
1643
+ if (!isResizing) return void 0;
1644
+ const move = (event) => {
1645
+ const drag = resizeDragRef.current;
1646
+ if (!drag || drag.pointerId !== event.pointerId) return;
1647
+ drag.currentRatio = clampWorkbenchRatio((drag.startWidth + drag.startX - event.clientX) / viewportWidth());
1648
+ setWorkbenchRatio(drag.currentRatio, false);
1649
+ event.preventDefault();
1650
+ };
1651
+ const finish = (event) => {
1652
+ const drag = resizeDragRef.current;
1653
+ if (!drag || drag.pointerId !== event.pointerId) return;
1654
+ resizeDragRef.current = null;
1655
+ document.documentElement.removeAttribute("data-git-guide-workbench-resizing");
1656
+ setIsResizing(false);
1657
+ persistWorkbenchRatio(drag.currentRatio);
1658
+ };
1659
+ document.addEventListener("pointermove", move, { passive: false });
1660
+ document.addEventListener("pointerup", finish);
1661
+ document.addEventListener("pointercancel", finish);
1662
+ return () => {
1663
+ document.removeEventListener("pointermove", move);
1664
+ document.removeEventListener("pointerup", finish);
1665
+ document.removeEventListener("pointercancel", finish);
1666
+ };
1667
+ }, [isResizing]);
1668
+ const tabs = [
1669
+ { id: "changes", label: "\u53D8\u66F4" },
1670
+ { id: "branches", label: "\u5206\u652F" },
1671
+ { id: "commits", label: "\u63D0\u4EA4\u8BB0\u5F55" },
1672
+ { id: "stashes", label: "\u8D2E\u85CF" },
1673
+ { id: "proposal", label: "\u5EFA\u8BAE" }
1674
+ ];
1675
+ const content = tab === "changes" ? React.createElement(GitChangesTab, { sessionId: props.sessionId, intervalFn: props.intervalFn, revision, onChanged: refresh, onCommand: reportCommand }) : tab === "branches" ? React.createElement(GitBranchesTab, { sessionId: props.sessionId, revision, onChanged: refresh, onCommand: reportCommand }) : tab === "commits" ? React.createElement(GitCommitsTab, { sessionId: props.sessionId, revision }) : tab === "stashes" ? React.createElement(GitStashesTab, { sessionId: props.sessionId, revision }) : React.createElement(GitDock, { sessionId: props.sessionId, intervalFn: props.intervalFn, timeoutFn: props.timeoutFn });
1676
+ return React.createElement(
1677
+ "aside",
1678
+ {
1679
+ className: "gg-workbench",
1680
+ "aria-label": "Git \u5DE5\u4F5C\u53F0",
1681
+ ref: rootRef,
1682
+ style: { [WORKBENCH_TRACK]: workbenchTrackForRatio(ratio) }
1683
+ },
1684
+ React.createElement("div", {
1685
+ className: "gg-workbench-resize" + (isResizing ? " dragging" : ""),
1686
+ "aria-hidden": "true",
1687
+ title: "\u5DE6\u53F3\u62D6\u52A8\u8C03\u6574\u5DE5\u4F5C\u53F0\u5BBD\u5EA6",
1688
+ onPointerDown: onResizePointerDown
1689
+ }),
1690
+ React.createElement(
1691
+ "div",
1692
+ { className: "gg-workbench-head" },
1693
+ React.createElement("span", { className: "gg-workbench-title" }, "Git \u5DE5\u4F5C\u53F0"),
1694
+ React.createElement("button", {
1695
+ type: "button",
1696
+ className: "gg-btn gg-workbench-close",
1697
+ title: "\u5173\u95ED Git \u5DE5\u4F5C\u53F0",
1698
+ "aria-label": "\u5173\u95ED Git \u5DE5\u4F5C\u53F0",
1699
+ onClick: props.close
1700
+ }, "\xD7")
1701
+ ),
1702
+ React.createElement(
1703
+ "div",
1704
+ { className: "gg-workbench-body" },
1705
+ React.createElement("div", { className: "gg-tabs", role: "tablist", "aria-label": "Git \u5DE5\u4F5C\u53F0\u533A\u57DF" }, tabs.map((entry) => React.createElement("button", {
1706
+ className: "gg-tab" + (tab === entry.id ? " active" : ""),
1707
+ type: "button",
1708
+ role: "tab",
1709
+ "aria-selected": tab === entry.id,
1710
+ onClick: () => setTab(entry.id),
1711
+ key: entry.id
1712
+ }, entry.label))),
1713
+ content
1714
+ ),
1715
+ React.createElement(
1716
+ "section",
1717
+ { className: "gg-command-log", "aria-label": "\u547D\u4EE4\u65E5\u5FD7" },
1718
+ React.createElement("strong", { className: "gg-command-log-head" }, "\u547D\u4EE4\u65E5\u5FD7"),
1719
+ React.createElement("div", { className: "gg-command-log-body", ref: commandLogBodyRef }, commandLogs.length ? commandLogs.map((entry) => React.createElement(
1720
+ "div",
1721
+ { className: "gg-command-entry", key: entry.id },
1722
+ React.createElement("span", { className: "gg-command-label" }, entry.label),
1723
+ React.createElement("span", { className: "gg-command-status " + entry.status }, entry.status === "running" ? "\u6267\u884C\u4E2D" : entry.status === "succeeded" ? "\u6210\u529F" : "\u5931\u8D25"),
1724
+ React.createElement("code", { className: "gg-command-code" }, entry.command)
1725
+ )) : React.createElement("div", { className: "gg-idletext" }, "\u5C1A\u672A\u6267\u884C\u4FEE\u6539\u547D\u4EE4\u3002"))
1726
+ )
1727
+ );
1728
+ }
1729
+ function GitDock(props) {
1730
+ const sessionId = props.sessionId || "";
1731
+ const intervalFn = props.intervalFn || null;
1732
+ const timeoutFn = props.timeoutFn || null;
1733
+ const [view, setView] = React.useState(null);
1734
+ const [busy, setBusy] = React.useState(false);
1735
+ const [understood, setUnderstood] = React.useState(false);
1736
+ const [outcome, setOutcome] = React.useState(null);
1737
+ const [ranInfo, setRanInfo] = React.useState(null);
1738
+ const [partialInfo, setPartialInfo] = React.useState(null);
1739
+ const [verifyMsg, setVerifyMsg] = React.useState(null);
1740
+ const [collapsed, setCollapsed] = React.useState(false);
1741
+ const currentProposalId = React.useRef(null);
1742
+ const scheduledCloses = React.useRef(/* @__PURE__ */ new Set());
1743
+ const closeDisposers = React.useRef([]);
1744
+ const resetProposalState = () => {
1745
+ setBusy(false);
1746
+ setUnderstood(false);
1747
+ setOutcome(null);
1748
+ setRanInfo(null);
1749
+ setPartialInfo(null);
1750
+ setVerifyMsg(null);
1751
+ };
1752
+ const doDismiss = (pid) => {
1753
+ rpc({ action: "dismiss", sessionId: String(sessionId), proposalId: pid }).then(refresh).catch(refresh);
1754
+ };
1755
+ const scheduleClose = (pid) => {
1756
+ if (scheduledCloses.current.has(pid)) return;
1757
+ scheduledCloses.current.add(pid);
1758
+ if (timeoutFn) {
1759
+ let dispose;
1760
+ const close = () => {
1761
+ scheduledCloses.current.delete(pid);
1762
+ closeDisposers.current = closeDisposers.current.filter((item) => item !== dispose);
1763
+ doDismiss(pid);
1764
+ };
1765
+ dispose = timeoutFn(close, 4e3);
1766
+ if (typeof dispose === "function") closeDisposers.current.push(dispose);
1767
+ } else {
1768
+ scheduledCloses.current.delete(pid);
1769
+ doDismiss(pid);
1770
+ }
1771
+ };
1772
+ const refresh = () => {
1773
+ rpc({ action: "state", sessionId: String(sessionId) }).then((res) => {
1774
+ if (res && res.ok === true) {
1775
+ const nextId = res.proposal && res.proposal.proposalId ? res.proposal.proposalId : null;
1776
+ if (nextId !== currentProposalId.current) {
1777
+ currentProposalId.current = nextId;
1778
+ resetProposalState();
1779
+ }
1780
+ setView(res.proposal);
1781
+ if (res.verified === true && res.proposal && res.proposal.proposalId) {
1782
+ setRanInfo(res.changedState || "\u68C0\u6D4B\u5230\u9884\u671F\u7ED3\u679C\u5DF2\u8FBE\u6210");
1783
+ scheduleClose(res.proposal.proposalId);
1784
+ } else if (res.partial === true && res.proposal && res.proposal.proposalId) {
1785
+ setPartialInfo({ message: res.message || "\u9884\u671F\u7ED3\u679C\u672A\u8FBE\u6210", state: res.changedState || "" });
1786
+ } else if (res.proposal && (res.proposal.status === "succeeded" || res.proposal.status === "verified")) {
1787
+ setRanInfo(res.proposal.status === "verified" ? "\u624B\u52A8\u6267\u884C\u7684\u9884\u671F\u7ED3\u679C\u5DF2\u9A8C\u8BC1" : "\u547D\u4EE4\u5DF2\u6267\u884C\u6210\u529F");
1788
+ scheduleClose(res.proposal.proposalId);
1789
+ } else if (res.proposal && res.proposal.status === "failed") {
1790
+ setBusy(false);
1791
+ setOutcome((current) => current || {
1792
+ ok: false,
1793
+ error: "\u8BE5\u63D0\u8BAE\u6267\u884C\u5931\u8D25\uFF1B\u5982\u9700\u91CD\u8BD5\uFF0C\u8BF7\u521B\u5EFA\u65B0\u7684\u63D0\u8BAE",
1794
+ steps: (res.proposal?.steps || []).map((step) => ({ command: step.command, ok: !!(step.result && step.result.ok) }))
1795
+ });
1796
+ } else {
1797
+ setPartialInfo(null);
1798
+ if (res.message) setVerifyMsg(res.message);
1799
+ }
1800
+ }
1801
+ }).catch((err) => {
1802
+ console.log("git-guide state \u8C03\u7528\u5931\u8D25", errorText2(err));
1803
+ });
1804
+ };
1805
+ React.useEffect(() => {
1806
+ currentProposalId.current = null;
1807
+ resetProposalState();
1808
+ refresh();
1809
+ if (!intervalFn) return void 0;
1810
+ const disp = intervalFn(refresh, 1200);
1811
+ return () => {
1812
+ try {
1813
+ if (typeof disp === "function") disp();
1814
+ } catch (e) {
1815
+ }
1816
+ };
1817
+ }, [sessionId, intervalFn]);
1818
+ React.useEffect(() => () => {
1819
+ for (const dispose of closeDisposers.current.splice(0)) {
1820
+ try {
1821
+ dispose();
1822
+ } catch (e) {
1823
+ }
1824
+ }
1825
+ }, []);
1826
+ if (!view) {
1827
+ return React.createElement(
1828
+ "div",
1829
+ { className: "gg-dock" },
1830
+ React.createElement(
1831
+ "div",
1832
+ { className: "gg-idle" },
1833
+ React.createElement("span", { className: "gg-badge normal" }, "Git \u64CD\u4F5C\u5EFA\u8BAE \xB7 \u7A7A\u95F2"),
1834
+ React.createElement("span", { className: "gg-idletext" }, "\u7B49\u5F85\u65B0\u7684 git \u64CD\u4F5C\u63D0\u8BAE\u2026\uFF08git_propose \u767B\u8BB0\u540E\u8FD9\u91CC\u4F1A\u51FA\u73B0\u547D\u4EE4\u4E0E\u6309\u94AE\uFF09")
1835
+ )
1836
+ );
1837
+ }
1838
+ const proposal = view;
1839
+ const steps = proposal.steps && proposal.steps.length ? proposal.steps : [{ command: proposal.command, result: null }];
1840
+ const isHard = proposal.risk === "hard";
1841
+ const isCopied = proposal.copied === true;
1842
+ const isPending = !proposal.status || proposal.status === "pending";
1843
+ const canRun = isPending && !busy && (!isHard || understood);
1844
+ const canCopy = isPending && !busy && (!isHard || understood);
1845
+ const onRun = () => {
1846
+ if (!canRun) return;
1847
+ setBusy(true);
1848
+ setOutcome(null);
1849
+ rpc({ action: "execute", sessionId: String(sessionId), proposalId: proposal.proposalId, confirm: understood }).then((res) => {
1850
+ setOutcome(res || { ok: false, error: "\u65E0\u8FD4\u56DE" });
1851
+ if (res && res.ok === true) scheduleClose(proposal.proposalId);
1852
+ }).catch((err) => {
1853
+ setOutcome({ ok: false, error: errorText2(err) });
1854
+ }).then(() => setBusy(false));
1855
+ };
1856
+ const onCopy = () => {
1857
+ try {
1858
+ const nav = typeof navigator !== "undefined" ? navigator : null;
1859
+ if (nav && nav.clipboard && typeof nav.clipboard.writeText === "function") {
1860
+ const text = steps.map((s) => s.command).join(" && \\\n");
1861
+ nav.clipboard.writeText(text).then(() => rpc({ action: "mark-copied", sessionId: String(sessionId), proposalId: proposal.proposalId, confirm: understood })).then((res) => {
1862
+ if (!res || res.ok !== true) setVerifyMsg(res && res.error || "\u65E0\u6CD5\u8BB0\u5F55\u590D\u5236\u72B6\u6001");
1863
+ refresh();
1864
+ }).catch((err) => setVerifyMsg("\u590D\u5236\u5931\u8D25\uFF1A" + errorText2(err)));
1865
+ } else setVerifyMsg("\u5F53\u524D\u73AF\u5883\u4E0D\u652F\u6301\u526A\u8D34\u677F API\uFF0C\u8BF7\u9010\u6761\u9009\u62E9\u547D\u4EE4\u540E\u624B\u52A8\u590D\u5236");
1866
+ } catch (e) {
1867
+ setVerifyMsg("\u590D\u5236\u5931\u8D25\uFF1A" + errorText2(e));
1868
+ }
1869
+ };
1870
+ const onVerify = () => {
1871
+ setVerifyMsg(null);
1872
+ setPartialInfo(null);
1873
+ rpc({ action: "verify", sessionId: String(sessionId), proposalId: proposal.proposalId }).then((res) => {
1874
+ if (res && res.verified === true) {
1875
+ setRanInfo(res && res.changedState || "\u68C0\u6D4B\u5230\u9884\u671F\u7ED3\u679C\u5DF2\u8FBE\u6210");
1876
+ scheduleClose(proposal.proposalId);
1877
+ } else if (res && res.partial === true) {
1878
+ setPartialInfo({ message: res.message || "\u9884\u671F\u7ED3\u679C\u672A\u8FBE\u6210", state: res && res.changedState || "" });
1879
+ } else {
1880
+ setVerifyMsg(res && res.message || "\u672A\u68C0\u6D4B\u5230\u9884\u671F\u7ED3\u679C\uFF0C\u770B\u8D77\u6765\u8FD8\u6CA1\u6709\u6267\u884C");
1881
+ }
1882
+ }).catch((err) => {
1883
+ setVerifyMsg(errorText2(err));
1884
+ });
1885
+ };
1886
+ const riskLabel = isHard ? "\u9AD8\u98CE\u9669" : proposal.risk === "safe" ? "\u5B89\u5168" : "\u5E38\u89C4";
1887
+ const badgeCls = isHard ? "gg-badge hard" : proposal.risk === "safe" ? "gg-badge safe" : "gg-badge normal";
1888
+ const renderSteps = () => steps.map((s, i) => React.createElement(
1889
+ "div",
1890
+ { className: "gg-step", key: "step" + i },
1891
+ React.createElement("span", { className: "gg-stepnum" }, String(i + 1) + "."),
1892
+ React.createElement("code", { className: "gg-stepcode" }, String(s.command))
1893
+ ));
1894
+ const headerEl = React.createElement(
1895
+ "div",
1896
+ { className: "gg-head", key: "head" },
1897
+ React.createElement("span", null, "Git \u64CD\u4F5C\u5EFA\u8BAE"),
1898
+ React.createElement("span", { className: badgeCls }, riskLabel),
1899
+ React.createElement(
1900
+ "button",
1901
+ { className: "gg-btn gg-toggle", onClick: () => setCollapsed(!collapsed), title: collapsed ? "\u5C55\u5F00" : "\u6536\u7F29" },
1902
+ collapsed ? "\u25B8" : "\u25BE"
1903
+ )
1904
+ );
1905
+ if (collapsed) {
1906
+ return React.createElement("div", { className: "gg-dock gg-dock-full" }, headerEl);
1907
+ }
1908
+ const lines = [headerEl];
1909
+ if (proposal.intent) lines.push(React.createElement("div", { className: "gg-intent", key: "intent" }, String(proposal.intent)));
1910
+ lines.push(React.createElement("div", { className: "gg-steps", key: "steps" }, renderSteps()));
1911
+ if (proposal.explanation) lines.push(React.createElement("div", { className: "gg-expl", key: "expl" }, String(proposal.explanation)));
1912
+ if (ranInfo) {
1913
+ lines.push(React.createElement("div", { className: "gg-ok gg-ran", key: "ran" }, "\u2714 \u68C0\u6D4B\u5230\u9884\u671F\u7ED3\u679C\u5DF2\u8FBE\u6210\uFF08\u5DF2\u6267\u884C\uFF09\uFF0C\u5373\u5C06\u5173\u95ED\u6B64\u5EFA\u8BAE"));
1914
+ lines.push(React.createElement("pre", { className: "gg-pre", key: "ranstate" }, String(ranInfo)));
1915
+ return React.createElement("div", { className: "gg-dock gg-dock-full" }, lines);
1916
+ }
1917
+ if (partialInfo) {
1918
+ lines.push(React.createElement("div", { className: "gg-riskline", key: "pmsg" }, "\u26A0 " + String(partialInfo.message)));
1919
+ if (partialInfo.state) lines.push(React.createElement("pre", { className: "gg-pre", key: "pstate" }, String(partialInfo.state)));
1920
+ const acts = [];
1921
+ acts.push(React.createElement("button", { key: "verify", className: "gg-btn primary", onClick: onVerify }, "\u91CD\u65B0\u68C0\u6D4B"));
1922
+ acts.push(React.createElement("button", { key: "drop", className: "gg-btn", onClick: () => doDismiss(proposal.proposalId) }, "\u653E\u5F03\u5EFA\u8BAE"));
1923
+ lines.push(React.createElement("div", { className: "gg-actions", key: "actions" }, acts));
1924
+ return React.createElement("div", { className: "gg-dock gg-dock-full" }, lines);
1925
+ }
1926
+ if (isCopied && isPending) {
1927
+ lines.push(React.createElement("div", { className: "gg-intent", key: "copied" }, "\u547D\u4EE4\u5DF2\u7528 && \u8FDE\u63A5\u540E\u590D\u5236\uFF0C\u4EFB\u4E00\u6B65\u5931\u8D25\u90FD\u4F1A\u505C\u6B62\u3002\u9762\u677F\u4F1A\u6BD4\u5BF9\u590D\u5236\u524D\u540E\u7684\u76EE\u6807\u72B6\u6001\u3002"));
1928
+ if (verifyMsg) lines.push(React.createElement("div", { className: "gg-riskline", key: "vmsg" }, verifyMsg));
1929
+ const acts = [];
1930
+ acts.push(React.createElement("button", { key: "verify", className: "gg-btn primary", onClick: onVerify }, "\u91CD\u65B0\u68C0\u6D4B"));
1931
+ acts.push(React.createElement("button", { key: "drop", className: "gg-btn", onClick: () => doDismiss(proposal.proposalId) }, "\u653E\u5F03\u5EFA\u8BAE"));
1932
+ lines.push(React.createElement("div", { className: "gg-actions", key: "actions" }, acts));
1933
+ return React.createElement("div", { className: "gg-dock gg-dock-full" }, lines);
1934
+ }
1935
+ if (proposal.status === "running") {
1936
+ lines.push(React.createElement("div", { className: "gg-intent", key: "running" }, "\u547D\u4EE4\u6B63\u5728\u6267\u884C\uFF0C\u8BF7\u52FF\u91CD\u590D\u63D0\u4EA4\u2026"));
1937
+ } else if (proposal.status === "failed") {
1938
+ lines.push(React.createElement("div", { className: "gg-riskline", key: "failed" }, "\u8BE5\u63D0\u8BAE\u5DF2\u7ECF\u5931\u8D25\u5E76\u9501\u5B9A\u3002\u8BF7\u6839\u636E\u8BCA\u65AD\u521B\u5EFA\u4FEE\u6B63\u63D0\u8BAE\uFF0C\u4E0D\u4F1A\u81EA\u52A8\u91CD\u653E\u3002"));
1939
+ lines.push(React.createElement(
1940
+ "div",
1941
+ { className: "gg-actions", key: "failed-actions" },
1942
+ React.createElement("button", { className: "gg-btn", onClick: () => doDismiss(proposal.proposalId) }, "\u5173\u95ED\u5931\u8D25\u63D0\u8BAE")
1943
+ ));
1944
+ } else if (isPending) {
1945
+ if (isHard) {
1946
+ lines.push(React.createElement("div", { className: "gg-riskline", key: "risk" }, "\u26A0 " + (proposal.reasons && proposal.reasons.length ? proposal.reasons.join("\uFF1B") : "\u8BE5\u64CD\u4F5C\u98CE\u9669\u8F83\u9AD8\uFF0C\u53EF\u80FD\u9020\u6210\u4E0D\u53EF\u9006\u7684\u6539\u52A8")));
1947
+ lines.push(React.createElement(
1948
+ "label",
1949
+ { className: "gg-check", key: "ck" },
1950
+ React.createElement("input", { type: "checkbox", checked: understood, onChange: (e) => setUnderstood(e.target.checked) }),
1951
+ React.createElement("span", null, "\u6211\u5DF2\u4E86\u89E3\u98CE\u9669\uFF0C\u786E\u8BA4\u6267\u884C\u6216\u590D\u5236")
1952
+ ));
1953
+ }
1954
+ const actions = [];
1955
+ actions.push(React.createElement(
1956
+ "button",
1957
+ { key: "run", className: "gg-btn " + (isHard ? "danger" : "primary"), disabled: !canRun, onClick: onRun },
1958
+ busy ? "\u6267\u884C\u4E2D\u2026" : isHard ? "\u786E\u8BA4\u5E76\u76F4\u63A5\u6267\u884C" : "\u76F4\u63A5\u6267\u884C"
1959
+ ));
1960
+ actions.push(React.createElement("button", { key: "copy", className: "gg-btn", disabled: !canCopy, onClick: onCopy }, "\u590D\u5236\u547D\u4EE4\uFF08\u624B\u52A8\u6267\u884C\uFF09"));
1961
+ lines.push(React.createElement("div", { className: "gg-actions", key: "actions" }, actions));
1962
+ }
1963
+ if (outcome) {
1964
+ const ok = outcome.ok === true;
1965
+ const oLines = [];
1966
+ if (outcome.error) oLines.push(String(outcome.error));
1967
+ if (outcome.stdout) oLines.push(String(outcome.stdout));
1968
+ if (outcome.stderr) oLines.push(String(outcome.stderr));
1969
+ const text = oLines.join("\n").trim() || "\uFF08\u65E0\u8F93\u51FA\uFF09";
1970
+ lines.push(React.createElement(
1971
+ "div",
1972
+ { className: "gg-out", key: "out" },
1973
+ React.createElement("div", { className: ok ? "gg-ok" : "gg-fail" }, ok ? "\u2714 \u6267\u884C\u6210\u529F" : "\u2718 \u6267\u884C\u5931\u8D25"),
1974
+ React.createElement("pre", null, text)
1975
+ ));
1976
+ if (outcome.steps && outcome.steps.length) {
1977
+ const stepLines = outcome.steps.map((s, i) => React.createElement(
1978
+ "div",
1979
+ { className: "gg-stepres", key: "sr" + i },
1980
+ React.createElement("span", { className: s.ok ? "gg-ok" : "gg-fail" }, s.ok ? "\u2713" : "\u2717"),
1981
+ React.createElement("code", { className: "gg-stepcode" }, String(s.command))
1982
+ ));
1983
+ lines.push(React.createElement("div", { className: "gg-out", key: "stepsout" }, stepLines));
1984
+ }
1985
+ if (!ok) {
1986
+ if (outcome.diagnostics) lines.push(React.createElement(
1987
+ "div",
1988
+ { className: "gg-out", key: "diag" },
1989
+ React.createElement("div", { className: "gg-intent" }, "\u4ED3\u5E93\u8BCA\u65AD\u4FE1\u606F\uFF1A"),
1990
+ React.createElement("pre", null, String(outcome.diagnostics))
1991
+ ));
1992
+ if (outcome.recovery && outcome.recovery.command) {
1993
+ lines.push(React.createElement(
1994
+ "div",
1995
+ { className: "gg-out gg-recovery", key: "recovery" },
1996
+ React.createElement("div", { className: "gg-ok" }, "\u{1F4A1} \u5DF2\u751F\u6210\u4FEE\u6B63\u5EFA\u8BAE" + (outcome.recovery.proposalId ? "\uFF08\u5DF2\u767B\u8BB0\u4E3A\u65B0\u7684\u63D0\u8BAE\uFF0C\u53EF\u76F4\u63A5\u6267\u884C\uFF09" : "")),
1997
+ React.createElement("div", { className: "gg-intent" }, String(outcome.recovery.suggestion || "")),
1998
+ React.createElement("code", { className: "gg-stepcode" }, String(outcome.recovery.command))
1999
+ ));
2000
+ } else if (outcome.recovery && outcome.recovery.suggestion) {
2001
+ lines.push(React.createElement(
2002
+ "div",
2003
+ { className: "gg-out gg-recovery", key: "recovery" },
2004
+ React.createElement("div", { className: "gg-riskline" }, "\u{1F4A1} " + String(outcome.recovery.suggestion))
2005
+ ));
2006
+ } else {
2007
+ lines.push(React.createElement("div", { className: "gg-intent", key: "hint" }, "\u6267\u884C\u5931\u8D25\u3002\u4F60\u53EF\u4EE5\u63CF\u8FF0\u4E0B\u4E00\u6B65\uFF0C\u6216\u8BA9\u6211\u5206\u6790\u539F\u56E0\u5E76\u7ED9\u51FA\u4FEE\u6B63\u547D\u4EE4\u3002"));
2008
+ }
2009
+ }
2010
+ }
2011
+ return React.createElement("div", { className: "gg-dock gg-dock-full" }, lines);
2012
+ }
2013
+ var plugin = {
2014
+ inject: ["slots", "timer", "layout"],
2015
+ apply(ctx) {
2016
+ if (typeof ctx.effect === "function") ctx.effect(injectStyles, "git-guide: styles");
2017
+ else injectStyles();
2018
+ const slots = ctx.get("slots");
2019
+ if (!slots) return;
2020
+ const timer = ctx.get("timer") || ctx.timer;
2021
+ const intervalFn = timer && typeof timer.interval === "function" ? timer.interval.bind(timer) : null;
2022
+ const timeoutFn = timer && typeof timer.timeout === "function" ? timer.timeout.bind(timer) : null;
2023
+ const layout = ctx.get("layout") || ctx.layout;
2024
+ const controller = createPanelController({
2025
+ slots,
2026
+ layout,
2027
+ renderPanel: (panelProps) => React.createElement(GitWorkbenchPanel, {
2028
+ sessionId: panelProps.sessionId,
2029
+ close: panelProps.close,
2030
+ intervalFn,
2031
+ timeoutFn
2032
+ })
2033
+ });
2034
+ slots.inject("details", () => controller.attachDetails());
2035
+ slots.inject("conversation.input.left", () => slots.register(
2036
+ { name: "conversation.input.left", id: "git-workbench", order: 30, label: "Git \u5DE5\u4F5C\u53F0" },
2037
+ (props) => React.createElement(GitWorkbenchAction, { sessionId: props.sessionId, controller, intervalFn })
2038
+ ));
2039
+ },
2040
+ __testing: {
2041
+ createPanelController,
2042
+ buildFileTree,
2043
+ parseReviewRows,
2044
+ renderRawDiffSurface,
2045
+ renderReviewSurface,
2046
+ injectStyles,
2047
+ filterLocalBranches,
2048
+ clampWorkbenchRatio,
2049
+ deriveCommitGraph,
2050
+ repositoryName,
2051
+ mutationCommand,
2052
+ appendCommandLog,
2053
+ refreshButtonLabel,
2054
+ isCurrentCommitRequest,
2055
+ nextCommitSelection,
2056
+ commitFileTone,
2057
+ isLatestRequest,
2058
+ beginTrackedRequest,
2059
+ cancelTrackedRequest,
2060
+ isTrackedRequestCurrent
2061
+ }
2062
+ };
2063
+ module.exports = plugin;
2064
+ return module.exports; } });