修复会话记录可能存储两次的bug;更改会话行为,默认进入新对话
Build Push and Deploy / docker-image (push) Successful in 1m1s
Build Push and Deploy / deploy-fallback-log (push) Has been skipped

This commit is contained in:
2026-05-22 14:19:14 +08:00
parent 54fbf15be8
commit 6b447eb398
5 changed files with 103 additions and 163 deletions
@@ -4,6 +4,7 @@ import { act, renderHook, waitFor } from "@testing-library/react";
import { useAgentChatSession } from "./useAgentChatSession";
import { streamAgentChat } from "@/lib/chatStream";
import type { StreamEvent } from "@/lib/chatStream";
jest.mock("@/lib/chatStream", () => ({
abortAgentChat: jest.fn(async () => undefined),
@@ -13,6 +14,7 @@ jest.mock("@/lib/chatStream", () => ({
const loadActiveChatState = jest.fn();
const listChatSessions = jest.fn();
const saveActiveChatState = jest.fn();
const updateChatSessionTitle = jest.fn();
jest.mock("../chatStorage", () => ({
@@ -27,7 +29,7 @@ jest.mock("../chatStorage", () => ({
sessionId: undefined,
branchGroups: [],
})),
saveActiveChatState: jest.fn(async (state) => state.storageSessionId),
saveActiveChatState: (...args: unknown[]) => saveActiveChatState(...args),
updateChatSessionTitle: (...args: unknown[]) => updateChatSessionTitle(...args),
}));
@@ -35,8 +37,10 @@ describe("useAgentChatSession", () => {
beforeEach(() => {
loadActiveChatState.mockReset();
listChatSessions.mockReset();
saveActiveChatState.mockReset();
updateChatSessionTitle.mockReset();
jest.mocked(streamAgentChat).mockReset();
saveActiveChatState.mockImplementation(async (state) => state.storageSessionId);
loadActiveChatState.mockResolvedValue({
storageSessionId: undefined,
@@ -105,6 +109,59 @@ describe("useAgentChatSession", () => {
]);
});
it("waits for the stream session id before persisting a new streaming conversation", async () => {
listChatSessions.mockResolvedValue([]);
let emitStreamEvent: ((event: StreamEvent) => void) | undefined;
jest.mocked(streamAgentChat).mockImplementationOnce(async ({ onEvent }) => {
emitStreamEvent = onEvent;
await new Promise<void>(() => undefined);
});
const { result } = renderHook(() =>
useAgentChatSession({
projectId: "project-1",
onToolCall: jest.fn(),
}),
);
await waitFor(() => expect(result.current.isHydrating).toBe(false));
jest.useFakeTimers();
try {
await act(async () => {
void result.current.sendPrompt("第一条消息");
await Promise.resolve();
});
expect(result.current.isStreaming).toBe(true);
await act(async () => {
jest.advanceTimersByTime(200);
});
expect(saveActiveChatState).not.toHaveBeenCalled();
act(() => {
emitStreamEvent?.({
type: "token",
sessionId: "chat-stream-1",
content: "收到",
});
});
await act(async () => {
jest.advanceTimersByTime(200);
});
expect(saveActiveChatState).toHaveBeenCalledTimes(1);
expect(saveActiveChatState.mock.calls[0][0]).toMatchObject({
sessionId: "chat-stream-1",
});
} finally {
jest.useRealTimers();
}
});
it("ignores generated session titles after the title was edited manually", async () => {
listChatSessions.mockResolvedValue([]);
loadActiveChatState.mockResolvedValue({
@@ -269,6 +269,15 @@ export const useAgentChatSession = ({
sessionId,
branchGroups,
};
if (
isStreaming &&
!state.storageSessionId &&
!state.sessionId &&
state.messages.length > 0
) {
return;
}
const currentStateKey = createPersistedStateKey(state);
if (currentStateKey === lastPersistedStateKeyRef.current) {
return;
@@ -296,7 +305,7 @@ export const useAgentChatSession = ({
return () => {
window.clearTimeout(persistTimer);
};
}, [branchGroups, isHydrating, isSessionTitleManuallyEdited, messages, projectId, sessionId, sessionTitle]);
}, [branchGroups, isHydrating, isSessionTitleManuallyEdited, isStreaming, messages, projectId, sessionId, sessionTitle]);
useEffect(() => {
setBranchGroups((prev) => {
@@ -538,42 +547,7 @@ export const useAgentChatSession = ({
cancelPromiseRef.current = trackedCancelPromise;
}, []);
const reset = useCallback(() => {
const controller = abortRef.current;
controller?.abort();
const activeSessionId = sessionIdRef.current;
if (activeSessionId) {
const cancelPromise = abortAgentChat(activeSessionId).catch((error) => {
console.error("[GlobalChatbox] Failed to abort agent session during reset:", error);
});
const trackedCancelPromise = cancelPromise.finally(() => {
if (cancelPromiseRef.current === trackedCancelPromise) {
cancelPromiseRef.current = null;
}
});
cancelPromiseRef.current = trackedCancelPromise;
}
setMessages([]);
setSessionTitle(undefined);
setIsSessionTitleManuallyEdited(false);
setBranchGroups([]);
setBranchTransition(null);
setSessionId(undefined);
sessionIdRef.current = undefined;
storageSessionIdRef.current = undefined;
lastPersistedStateKeyRef.current = createPersistedStateKey({
storageSessionId: undefined,
title: undefined,
isTitleManuallyEdited: false,
messages: [],
sessionId: undefined,
branchGroups: [],
});
titleUpdateNonceRef.current += 1;
setIsStreaming(false);
}, []);
const createSession = useCallback(async () => {
const createSession = useCallback(() => {
if (isHydrating || isStreaming) return;
const controller = abortRef.current;
@@ -903,7 +877,6 @@ export const useAgentChatSession = ({
cycleBranch,
abort,
createSession,
reset,
renameSession,
removeSession,
switchSession,