返回 DeepSeek-Reasonix
rewindCommit.ts
根目录 / desktop / frontend / src / lib / rewindCommit.ts
1 import { app } from "./bridge";
2 import { t } from "./i18n";
3 import type { RewindResultView, TabMeta } from "./types";
4
5 export async function commitRewindWithPreview(
6 sourceTabId: string,
7 turn: number,
8 scope: "conversation" | "code" | "both",
9 ): Promise<RewindResultView> {
10 const plan = await app.PreviewRewindForTab(sourceTabId, turn, scope);
11 const remoteLegacy = !plan?.ok && /remote host uses legacy rewind semantics/i.test(plan?.error || "");
12 if (!remoteLegacy) {
13 const canCommit = scope === "conversation"
14 ? plan?.canConversation
15 : scope === "both"
16 ? plan?.canConversation && plan?.canFiles
17 : plan?.canFiles;
18 if (!plan?.ok || !canCommit) {
19 return {
20 ok: false,
21 error: plan?.error
22 || plan?.disabledReason
23 || (plan?.conflicts?.length ? plan.conflicts.join("; ") : "")
24 || "rewind unavailable",
25 conflicts: plan?.conflicts,
26 };
27 }
28 if (scope !== "conversation" && rewindNeedsCoverageConfirm(plan)) {
29 const gaps = (plan.coverageGaps || []).filter((gap) => !isScratchCoverageGap(gap)).join("\n");
30 if (!window.confirm(t("rewind.confirmPartialCoverage", { gaps: gaps || t("rewind.partialCoverageUnknown") }))) {
31 return { ok: false, error: "rewind cancelled" };
32 }
33 }
34 }
35 return app.CommitRewindForTab(sourceTabId, remoteLegacy ? "" : (plan.planId || ""), turn, scope);
36 }
37
38 export function partialRewindNotice(result: RewindResultView): string {
39 if (!result.partial) return "";
40 const summary = t("rewind.partialRestoreFailed");
41 const detail = result.error
42 || (result.conflicts?.length ? result.conflicts.join("; ") : "");
43 return detail ? `${summary} ${detail}` : summary;
44 }
45
46 function isScratchCoverageGap(gap: string): boolean {
47 return gap === "scratch" || gap.startsWith("scratch:");
48 }
49
50 function rewindNeedsCoverageConfirm(plan: { coverage?: string; coverageGaps?: string[]; expiredFilePayload?: boolean; legacy?: boolean }): boolean {
51 if (plan.expiredFilePayload || plan.legacy) return true;
52 const projectGaps = (plan.coverageGaps || []).filter((gap) => !isScratchCoverageGap(gap));
53 return projectGaps.length > 0 || (plan.coverage === "partial" && projectGaps.length > 0);
54 }
55
56 export function rewindFailureDetail(result?: RewindResultView | null): string {
57 return result?.error
58 || (result?.conflicts?.length ? result.conflicts.join("; ") : "")
59 || "rewind failed";
60 }
61
62 export function rewindOutcome(result: RewindResultView): RewindResultView & { ok: true } {
63 return { ...result, ok: true, tabId: result.tabId || result.tab?.id };
64 }
65
66 export async function settleRewindTarget(
67 result: RewindResultView,
68 adopt: (tab: TabMeta) => Promise<unknown>,
69 waitUntilReady: (tabId: string) => Promise<unknown>,
70 ): Promise<string | undefined> {
71 const targetTabId = result.tab?.id || result.tabId;
72 if (result.tab?.id) await adopt(result.tab);
73 else if (targetTabId) await waitUntilReady(targetTabId);
74 return targetTabId;
75 }
76
77 export function dispatchPartialRewindNotice(
78 notice: string,
79 sourceTabId: string,
80 targetTabId: string | undefined,
81 notify: (tabId: string, text: string) => void,
82 ): void {
83 if (!notice) return;
84 notify(sourceTabId, notice);
85 if (targetTabId && targetTabId !== sourceTabId) notify(targetTabId, notice);
86 }
87
88 export function undoCommittedRewind(sourceTabId: string, transactionId: string): Promise<RewindResultView> {
89 return app.UndoRewindForTab(sourceTabId, transactionId);
90 }
91
91 lines TYPESCRIPT