mirror of
https://github.com/esengine/DeepSeek-Reasonix.git
synced 2026-11-07 10:45:59 -10:00
fix(studio): preserve message edits when rewind fails
This commit is contained in:
1 parent
f73421aa65
commit
ff48415d47
2 files changed
+62
-13
No files matched your search
@@ -46,17 +46,13 @@ export function useReplyActions({ port, items, checkpoints, running, model, subm
|
||||
// the word "regenerate" makes.
|
||||
const regenerate = useCallback(
|
||||
async (turn: number, text: string) => {
|
||||
try {
|
||||
const plan = await port.prepareRewind(turn, "conversation");
|
||||
if (!plan.canConversation) throw new Error(plan.disabledReason || t("这一轮无法重新生成"));
|
||||
await port.commitRewind(plan.planId);
|
||||
await reloadSession();
|
||||
await submit(text);
|
||||
} catch (e) {
|
||||
onError(e);
|
||||
}
|
||||
const plan = await port.prepareRewind(turn, "conversation");
|
||||
if (!plan.canConversation) throw new Error(plan.disabledReason || t("这一轮无法重新生成"));
|
||||
await port.commitRewind(plan.planId);
|
||||
await reloadSession();
|
||||
await submit(text);
|
||||
},
|
||||
[port, submit, reloadSession, onError],
|
||||
[port, submit, reloadSession],
|
||||
);
|
||||
|
||||
// A reply belongs to the most recent user turn, including when that turn
|
||||
@@ -109,13 +105,13 @@ export function useReplyActions({ port, items, checkpoints, running, model, subm
|
||||
onRegenerate: (id: string) => {
|
||||
if (running) return;
|
||||
const ask = replyTurns.get(id);
|
||||
if (ask) void regenerate(ask.turn, ask.text);
|
||||
if (ask) void regenerate(ask.turn, ask.text).catch(onError);
|
||||
},
|
||||
model,
|
||||
onConfigureModel: () => onSettings("model"),
|
||||
onRunDetail,
|
||||
}),
|
||||
[replyTurns, running, regenerate, model, onSettings, onRunDetail, turnOf],
|
||||
[replyTurns, running, regenerate, model, onSettings, onRunDetail, turnOf, onError],
|
||||
);
|
||||
|
||||
// Rewriting a message is the same act with different words: the turn goes
|
||||
|
||||
@@ -4,7 +4,7 @@ import { act, cleanup, fireEvent, render, renderHook, screen, waitFor } from "@t
|
||||
import { afterEach, describe, expect, it, vi } from "vitest";
|
||||
import { Pane } from "./Pane";
|
||||
import { MockPort } from "../port/mock";
|
||||
import type { HistoryMessage, RewindResult } from "../port/port";
|
||||
import { HttpError, type HistoryMessage, type RewindResult } from "../port/port";
|
||||
import type { RuntimeView } from "../port/hub";
|
||||
import { useRewindActions } from "./rewind";
|
||||
|
||||
@@ -186,6 +186,59 @@ describe("restoring a rewound prompt to the composer", () => {
|
||||
expect(box().value).toBe("Separate draft");
|
||||
});
|
||||
|
||||
it.each(["prepareRewind", "commitRewind"] as const)("keeps a failed edit available for retry after %s rejects", async (stage) => {
|
||||
const { port, reads } = await open();
|
||||
vi.mocked(port[stage]).mockRejectedValueOnce(new HttpError(500, "checkpoint is gone"));
|
||||
type("Separate draft");
|
||||
fireEvent.click(screen.getByRole("button", { name: "改写" }));
|
||||
const edit = screen.getByRole("textbox", { name: "改写这条消息" }) as HTMLTextAreaElement;
|
||||
const revised = "A revised prompt\n Keep this indentation ";
|
||||
fireEvent.change(edit, { target: { value: revised } });
|
||||
const before = reads.mock.calls.length;
|
||||
fireEvent.click(screen.getByRole("button", { name: "改完重发" }));
|
||||
await screen.findByText("checkpoint is gone");
|
||||
expect(screen.getByRole("textbox", { name: "改写这条消息" })).toBe(edit);
|
||||
expect(edit.value).toBe(revised);
|
||||
expect(edit.closest(".reask")?.textContent).toContain("checkpoint is gone");
|
||||
expect(box().value).toBe("Separate draft");
|
||||
expect(port.submit).not.toHaveBeenCalled();
|
||||
expect(reads.mock.calls).toHaveLength(before);
|
||||
|
||||
fireEvent.click(screen.getByRole("button", { name: "改完重发" }));
|
||||
await waitFor(() => expect(port.submit).toHaveBeenCalledExactlyOnceWith(revised.trim(), undefined));
|
||||
expect(screen.queryByRole("textbox", { name: "改写这条消息" })).toBeNull();
|
||||
expect(box().value).toBe("Separate draft");
|
||||
});
|
||||
|
||||
it("keeps the edited words when the prepared plan cannot rewind the conversation", async () => {
|
||||
const { port } = await open();
|
||||
vi.mocked(port.prepareRewind).mockResolvedValue({
|
||||
planId: "unavailable", turn: 1, coverage: "full", canFiles: false, canConversation: false,
|
||||
fileCount: 0, requiresConfirmation: false, disabledReason: "conversation boundary is unavailable",
|
||||
});
|
||||
fireEvent.click(screen.getByRole("button", { name: "改写" }));
|
||||
const edit = screen.getByRole("textbox", { name: "改写这条消息" }) as HTMLTextAreaElement;
|
||||
fireEvent.change(edit, { target: { value: "Keep these revised words" } });
|
||||
fireEvent.click(screen.getByRole("button", { name: "改完重发" }));
|
||||
await screen.findByText("conversation boundary is unavailable");
|
||||
expect(screen.getByRole("textbox", { name: "改写这条消息" })).toBe(edit);
|
||||
expect(edit.value).toBe("Keep these revised words");
|
||||
expect(port.commitRewind).not.toHaveBeenCalled();
|
||||
expect(port.submit).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it.each(["prepareRewind", "commitRewind"] as const)("reports a regeneration failure when %s rejects", async (stage) => {
|
||||
const { port, reads } = await open();
|
||||
vi.mocked(port[stage]).mockRejectedValueOnce(new HttpError(500, "checkpoint is gone"));
|
||||
const before = reads.mock.calls.length;
|
||||
fireEvent.click(screen.getByRole("button", { name: "重新生成" }));
|
||||
fireEvent.click(screen.getByRole("menuitem", { name: /按当前配置重试/ }));
|
||||
await screen.findByText("checkpoint is gone");
|
||||
expect(screen.getByText("The image has been reviewed.")).toBeTruthy();
|
||||
expect(port.submit).not.toHaveBeenCalled();
|
||||
expect(reads.mock.calls).toHaveLength(before);
|
||||
});
|
||||
|
||||
it("leaves restoration out of undo, single-file revert and commits without a prompt", async () => {
|
||||
const port = new MockPort();
|
||||
vi.spyOn(port, "commitRewind").mockResolvedValue({ ok: true, conversationOk: true });
|
||||
|
||||
Reference in new issue
Block a user