refactor(chat): remove frontend state saves

This commit is contained in:
2026-06-10 19:29:45 +08:00
parent 9c0a7a2864
commit e2a6bb0e7d
6 changed files with 112 additions and 255 deletions
+102 -39
View File
@@ -1,6 +1,9 @@
import {
createEmptyChatState,
saveActiveChatState,
deleteChatSession,
listChatSessions,
loadChatSessionById,
updateChatSessionTitle,
} from "./chatStorage";
const apiFetch = jest.fn();
@@ -9,7 +12,7 @@ jest.mock("@/lib/apiFetch", () => ({
apiFetch: (...args: unknown[]) => apiFetch(...args),
}));
describe("chatStorage backend-only persistence", () => {
describe("chatStorage backend session operations", () => {
beforeEach(() => {
apiFetch.mockReset();
});
@@ -25,46 +28,106 @@ describe("chatStorage backend-only persistence", () => {
expect(apiFetch).not.toHaveBeenCalled();
});
it("creates a backend conversation when saving the first non-empty state", async () => {
apiFetch.mockImplementation(async (url: string, init?: RequestInit) => {
if (url.endsWith("/api/v1/agent/chat/session")) {
expect(init?.method).toBe("POST");
return {
ok: true,
json: async () => ({ session_id: "chat-new-1" }),
} as Response;
}
if (url.endsWith("/api/v1/agent/chat/session/chat-new-1")) {
expect(init?.method).toBe("PUT");
expect(JSON.parse(String(init?.body))).toMatchObject({
title: "新对话",
is_title_manually_edited: false,
});
return {
ok: true,
json: async () => ({ id: "chat-new-1", session_id: "chat-new-1" }),
} as Response;
}
throw new Error(`Unexpected request ${url}`);
});
const savedSessionId = await saveActiveChatState(
{
title: "新对话",
isTitleManuallyEdited: false,
messages: [
it("lists backend sessions sorted by created time", async () => {
apiFetch.mockResolvedValueOnce({
ok: true,
json: async () => ({
sessions: [
{
id: "message-2",
role: "user",
content: "第一条消息",
id: "session-old",
title: "旧会话",
created_at: "2026-01-01T00:00:00.000Z",
updated_at: "2026-01-02T00:00:00.000Z",
},
{
id: "session-new",
title: "新会话",
created_at: "2026-01-03T00:00:00.000Z",
updated_at: "2026-01-03T00:00:00.000Z",
is_streaming: true,
run_status: "running",
},
],
sessionId: undefined,
},
);
}),
});
expect(savedSessionId).toBe("chat-new-1");
await expect(listChatSessions()).resolves.toEqual([
expect.objectContaining({
id: "session-new",
title: "新会话",
isStreaming: true,
runStatus: "running",
}),
expect.objectContaining({
id: "session-old",
title: "旧会话",
}),
]);
expect(apiFetch.mock.calls[0][1]).toMatchObject({ method: "GET" });
});
it("loads a backend session state", async () => {
apiFetch.mockResolvedValueOnce({
ok: true,
json: async () => ({
id: "session-1",
title: "管网分析",
is_title_manually_edited: true,
messages: [{ id: "message-1", role: "user", content: "查压力" }],
is_streaming: false,
}),
});
await expect(loadChatSessionById("session-1")).resolves.toMatchObject({
title: "管网分析",
isTitleManuallyEdited: true,
sessionId: "session-1",
messages: [{ id: "message-1", role: "user", content: "查压力" }],
});
expect(String(apiFetch.mock.calls[0][0])).toContain("/session/session-1");
expect(apiFetch.mock.calls[0][1]).toMatchObject({ method: "GET" });
});
it("updates a backend session title through the title endpoint", async () => {
apiFetch.mockResolvedValueOnce({
ok: true,
text: async () => "",
});
await updateChatSessionTitle("session-1", " 新标题 ", {
isTitleManuallyEdited: true,
});
expect(String(apiFetch.mock.calls[0][0])).toContain("/session/session-1/title");
expect(apiFetch.mock.calls[0][1]).toMatchObject({ method: "PATCH" });
expect(JSON.parse(String(apiFetch.mock.calls[0][1]?.body))).toEqual({
title: "新标题",
is_title_manually_edited: true,
});
});
it("deletes a backend session and returns the next active session id", async () => {
apiFetch
.mockResolvedValueOnce({
ok: true,
text: async () => "",
})
.mockResolvedValueOnce({
ok: true,
json: async () => ({
sessions: [
{
id: "session-next",
title: "下一会话",
created_at: "2026-01-01T00:00:00.000Z",
updated_at: "2026-01-01T00:00:00.000Z",
},
],
}),
});
await expect(deleteChatSession("session-1")).resolves.toBe("session-next");
expect(apiFetch.mock.calls[0][1]).toMatchObject({ method: "DELETE" });
expect(apiFetch.mock.calls[1][1]).toMatchObject({ method: "GET" });
});
});