refactor(webui): shrink fork implementation
This commit is contained in:
@@ -766,223 +766,6 @@ describe("ThreadShell", () => {
|
||||
);
|
||||
});
|
||||
|
||||
it("shows an error without changing the draft when assistant fork fails", async () => {
|
||||
const client = makeClient();
|
||||
const onForkChat = vi.fn().mockResolvedValue(null);
|
||||
vi.stubGlobal(
|
||||
"fetch",
|
||||
vi.fn(async (input: RequestInfo | URL) => {
|
||||
const url = String(input);
|
||||
if (url.includes("websocket%3Achat-a/webui-thread")) {
|
||||
return httpJson(transcriptFromSimpleMessages([
|
||||
{ role: "user", content: "fork me" },
|
||||
{ role: "assistant", content: "answer" },
|
||||
]));
|
||||
}
|
||||
return {
|
||||
ok: false,
|
||||
status: 404,
|
||||
json: async () => ({}),
|
||||
};
|
||||
}),
|
||||
);
|
||||
|
||||
render(
|
||||
wrap(
|
||||
client,
|
||||
<ThreadShell
|
||||
session={session("chat-a")}
|
||||
title="Chat chat-a"
|
||||
onToggleSidebar={() => {}}
|
||||
onForkChat={onForkChat}
|
||||
/>,
|
||||
),
|
||||
);
|
||||
|
||||
const targetText = await screen.findByText("answer");
|
||||
fireEvent.change(screen.getByLabelText("Message input"), {
|
||||
target: { value: "keep my current draft" },
|
||||
});
|
||||
fireEvent.click(within(targetText.closest(".w-full") as HTMLElement).getByRole("button", {
|
||||
name: "Fork",
|
||||
}));
|
||||
|
||||
await waitFor(() => expect(onForkChat).toHaveBeenCalledWith("chat-a", 1));
|
||||
expect(screen.getByLabelText("Message input")).toHaveValue("keep my current draft");
|
||||
expect(screen.getByRole("alert")).toHaveTextContent("Could not fork this chat");
|
||||
expect(client.sendMessage).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it("hydrates a successful fork from canonical history without later source messages", async () => {
|
||||
const client = makeClient();
|
||||
const onForkChat = vi.fn().mockResolvedValue("chat-fork");
|
||||
vi.stubGlobal(
|
||||
"fetch",
|
||||
vi.fn(async (input: RequestInfo | URL) => {
|
||||
const url = String(input);
|
||||
if (url.includes("websocket%3Achat-a/webui-thread")) {
|
||||
return httpJson(transcriptFromSimpleMessages([
|
||||
{ role: "user", content: "round1" },
|
||||
{ role: "assistant", content: "answer1" },
|
||||
{ role: "user", content: "round2 fork me" },
|
||||
{ role: "assistant", content: "answer2" },
|
||||
{ role: "user", content: "round3 must not appear" },
|
||||
]));
|
||||
}
|
||||
if (url.includes("websocket%3Achat-fork/webui-thread")) {
|
||||
return httpJson(transcriptFromSimpleMessages([
|
||||
{ role: "user", content: "round1" },
|
||||
{ role: "assistant", content: "answer1" },
|
||||
{ role: "user", content: "round2 fork me" },
|
||||
{ role: "assistant", content: "answer2" },
|
||||
]));
|
||||
}
|
||||
if (url.includes("websocket%3Achat-other/webui-thread")) {
|
||||
return httpJson(transcriptFromSimpleMessages([
|
||||
{ role: "user", content: "other chat" },
|
||||
]));
|
||||
}
|
||||
return {
|
||||
ok: false,
|
||||
status: 404,
|
||||
json: async () => ({}),
|
||||
};
|
||||
}),
|
||||
);
|
||||
|
||||
const { rerender } = render(
|
||||
wrap(
|
||||
client,
|
||||
<ThreadShell
|
||||
session={session("chat-a")}
|
||||
title="Chat chat-a"
|
||||
onToggleSidebar={() => {}}
|
||||
onForkChat={onForkChat}
|
||||
/>,
|
||||
),
|
||||
);
|
||||
|
||||
const targetText = await screen.findByText("answer2");
|
||||
fireEvent.click(within(targetText.closest(".w-full") as HTMLElement).getByRole("button", {
|
||||
name: "Fork",
|
||||
}));
|
||||
|
||||
await waitFor(() => expect(onForkChat).toHaveBeenCalledWith("chat-a", 2));
|
||||
await act(async () => {
|
||||
rerender(
|
||||
wrap(
|
||||
client,
|
||||
<ThreadShell
|
||||
session={session("chat-fork")}
|
||||
title="Chat chat-fork"
|
||||
onToggleSidebar={() => {}}
|
||||
onForkChat={onForkChat}
|
||||
/>,
|
||||
),
|
||||
);
|
||||
});
|
||||
|
||||
await waitFor(() => expect(screen.getByText("answer1")).toBeInTheDocument());
|
||||
expect(screen.getByText("answer2")).toBeInTheDocument();
|
||||
expect(screen.queryByText("round3 must not appear")).not.toBeInTheDocument();
|
||||
expect(screen.getByLabelText("Message input")).toHaveValue("");
|
||||
|
||||
await act(async () => {
|
||||
rerender(
|
||||
wrap(
|
||||
client,
|
||||
<ThreadShell
|
||||
session={session("chat-other")}
|
||||
title="Chat chat-other"
|
||||
onToggleSidebar={() => {}}
|
||||
onForkChat={onForkChat}
|
||||
/>,
|
||||
),
|
||||
);
|
||||
});
|
||||
|
||||
await waitFor(() =>
|
||||
expect(screen.getByLabelText("Message input")).toHaveValue(""),
|
||||
);
|
||||
|
||||
await act(async () => {
|
||||
rerender(
|
||||
wrap(
|
||||
client,
|
||||
<ThreadShell
|
||||
session={null}
|
||||
title="New chat"
|
||||
onToggleSidebar={() => {}}
|
||||
onForkChat={onForkChat}
|
||||
/>,
|
||||
),
|
||||
);
|
||||
});
|
||||
|
||||
expect(screen.getByLabelText("Message input")).toHaveValue("");
|
||||
});
|
||||
|
||||
it("forks from completed assistant replies without pre-filling the assistant text", async () => {
|
||||
const client = makeClient();
|
||||
const onForkChat = vi.fn().mockResolvedValue("chat-fork");
|
||||
vi.stubGlobal(
|
||||
"fetch",
|
||||
vi.fn(async (input: RequestInfo | URL) => {
|
||||
const url = String(input);
|
||||
if (url.includes("websocket%3Achat-a/webui-thread")) {
|
||||
return httpJson(transcriptFromSimpleMessages([
|
||||
{ role: "user", content: "round1" },
|
||||
{ role: "assistant", content: "answer1" },
|
||||
]));
|
||||
}
|
||||
if (url.includes("websocket%3Achat-fork/webui-thread")) {
|
||||
return httpJson(transcriptFromSimpleMessages([
|
||||
{ role: "user", content: "round1" },
|
||||
{ role: "assistant", content: "answer1" },
|
||||
]));
|
||||
}
|
||||
return {
|
||||
ok: false,
|
||||
status: 404,
|
||||
json: async () => ({}),
|
||||
};
|
||||
}),
|
||||
);
|
||||
|
||||
const { rerender } = render(
|
||||
wrap(
|
||||
client,
|
||||
<ThreadShell
|
||||
session={session("chat-a")}
|
||||
title="Chat chat-a"
|
||||
onToggleSidebar={() => {}}
|
||||
onForkChat={onForkChat}
|
||||
/>,
|
||||
),
|
||||
);
|
||||
|
||||
await screen.findByText("answer1");
|
||||
fireEvent.click(screen.getAllByRole("button", { name: "Fork" }).at(-1)!);
|
||||
|
||||
await waitFor(() => expect(onForkChat).toHaveBeenCalledWith("chat-a", 1));
|
||||
await act(async () => {
|
||||
rerender(
|
||||
wrap(
|
||||
client,
|
||||
<ThreadShell
|
||||
session={session("chat-fork")}
|
||||
title="Chat chat-fork"
|
||||
onToggleSidebar={() => {}}
|
||||
onForkChat={onForkChat}
|
||||
/>,
|
||||
),
|
||||
);
|
||||
});
|
||||
|
||||
await waitFor(() => expect(screen.getByText("answer1")).toBeInTheDocument());
|
||||
expect(screen.getByLabelText("Message input")).toHaveValue("");
|
||||
});
|
||||
|
||||
it("does not cache optimistic messages under the next chat during a session switch", async () => {
|
||||
const client = makeClient();
|
||||
const onNewChat = vi.fn().mockResolvedValue("chat-b");
|
||||
|
||||
Reference in New Issue
Block a user