fix(chat): guard generated title events
This commit is contained in:
@@ -61,6 +61,7 @@ describe("useAgentChatSession", () => {
|
||||
jest.mocked(streamAgentChat).mockImplementation(async () => undefined);
|
||||
deleteChatSession.mockImplementation(async () => undefined);
|
||||
saveActiveChatState.mockImplementation(async (state) => state.sessionId);
|
||||
updateChatSessionTitle.mockImplementation(async () => undefined);
|
||||
});
|
||||
|
||||
it("does not add a new empty session to history until there is actual chat content", async () => {
|
||||
@@ -522,6 +523,64 @@ describe("useAgentChatSession", () => {
|
||||
);
|
||||
});
|
||||
|
||||
it("does not apply a late generated title to a newly created session", async () => {
|
||||
listChatSessions.mockResolvedValue([]);
|
||||
let emitStreamEvent: ((event: StreamEvent) => void) | undefined;
|
||||
let resolveStream: (() => void) | undefined;
|
||||
jest.mocked(streamAgentChat).mockImplementationOnce(async ({ onEvent }) => {
|
||||
emitStreamEvent = onEvent;
|
||||
await new Promise<void>((resolve) => {
|
||||
resolveStream = resolve;
|
||||
});
|
||||
});
|
||||
|
||||
const { result } = renderHook(() =>
|
||||
useAgentChatSession({
|
||||
projectId: "project-1",
|
||||
onToolCall: jest.fn(),
|
||||
}),
|
||||
);
|
||||
|
||||
await waitFor(() => expect(result.current.isHydrating).toBe(false));
|
||||
|
||||
await act(async () => {
|
||||
void result.current.sendPrompt("帮我分析一下");
|
||||
await Promise.resolve();
|
||||
});
|
||||
|
||||
act(() => {
|
||||
emitStreamEvent?.({
|
||||
type: "done",
|
||||
sessionId: "old-session",
|
||||
});
|
||||
});
|
||||
|
||||
await waitFor(() => expect(result.current.isStreaming).toBe(false));
|
||||
|
||||
act(() => {
|
||||
result.current.createSession();
|
||||
});
|
||||
|
||||
expect(result.current.sessionTitle).toBe("新对话");
|
||||
|
||||
await act(async () => {
|
||||
emitStreamEvent?.({
|
||||
type: "session_title",
|
||||
sessionId: "old-session",
|
||||
title: "旧请求标题",
|
||||
});
|
||||
resolveStream?.();
|
||||
await Promise.resolve();
|
||||
});
|
||||
|
||||
expect(result.current.sessionTitle).toBe("新对话");
|
||||
expect(updateChatSessionTitle).toHaveBeenCalledWith(
|
||||
"old-session",
|
||||
"旧请求标题",
|
||||
{ isTitleManuallyEdited: false },
|
||||
);
|
||||
});
|
||||
|
||||
it("asks the backend to undo the previous user turn before regenerating", async () => {
|
||||
listChatSessions.mockResolvedValue([]);
|
||||
|
||||
|
||||
@@ -234,6 +234,7 @@ export const useAgentChatSession = ({
|
||||
const abortRef = useRef<AbortController | null>(null);
|
||||
const sessionIdRef = useRef<string | undefined>(undefined);
|
||||
const messagesRef = useRef<Message[]>([]);
|
||||
const branchGroupsRef = useRef<BranchGroup[]>([]);
|
||||
const resumeStreamingSessionRef = useRef<((sessionId: string) => void) | null>(null);
|
||||
const isSessionTitleManuallyEditedRef = useRef(false);
|
||||
const cancelPromiseRef = useRef<Promise<void> | null>(null);
|
||||
@@ -256,6 +257,10 @@ export const useAgentChatSession = ({
|
||||
messagesRef.current = messages;
|
||||
}, [messages]);
|
||||
|
||||
useEffect(() => {
|
||||
branchGroupsRef.current = branchGroups;
|
||||
}, [branchGroups]);
|
||||
|
||||
useEffect(() => {
|
||||
isSessionTitleManuallyEditedRef.current = isSessionTitleManuallyEdited;
|
||||
}, [isSessionTitleManuallyEdited]);
|
||||
@@ -444,7 +449,12 @@ export const useAgentChatSession = ({
|
||||
assistantMessageId?: string;
|
||||
},
|
||||
) => {
|
||||
if ("sessionId" in event && event.sessionId && event.sessionId !== sessionIdRef.current) {
|
||||
if (
|
||||
event.type !== "session_title" &&
|
||||
"sessionId" in event &&
|
||||
event.sessionId &&
|
||||
event.sessionId !== sessionIdRef.current
|
||||
) {
|
||||
sessionIdRef.current = event.sessionId;
|
||||
setSessionId(event.sessionId);
|
||||
}
|
||||
@@ -457,6 +467,39 @@ export const useAgentChatSession = ({
|
||||
return;
|
||||
}
|
||||
|
||||
if (event.type === "session_title") {
|
||||
const nextTitle = event.title.trim();
|
||||
if (nextTitle && !isSessionTitleManuallyEditedRef.current) {
|
||||
const currentSessionId = sessionIdRef.current;
|
||||
const targetSessionId = event.sessionId || currentSessionId;
|
||||
if (targetSessionId === currentSessionId) {
|
||||
setSessionTitle(nextTitle);
|
||||
lastPersistedStateKeyRef.current = createPersistedStateKey({
|
||||
sessionId: targetSessionId,
|
||||
title: nextTitle,
|
||||
isTitleManuallyEdited: false,
|
||||
messages: messagesRef.current,
|
||||
branchGroups: branchGroupsRef.current,
|
||||
});
|
||||
}
|
||||
if (targetSessionId) {
|
||||
const currentNonce = ++titleUpdateNonceRef.current;
|
||||
void updateChatSessionTitle(targetSessionId, nextTitle, {
|
||||
isTitleManuallyEdited: false,
|
||||
})
|
||||
.then(() => listChatSessions())
|
||||
.then((sessions) => {
|
||||
if (titleUpdateNonceRef.current !== currentNonce) return;
|
||||
setChatSessions(sessions);
|
||||
})
|
||||
.catch((error) => {
|
||||
console.error("[GlobalChatbox] Failed to persist session title:", error);
|
||||
});
|
||||
}
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
const assistantMessageId = getLastAssistantMessageId(options?.assistantMessageId);
|
||||
if (!assistantMessageId) {
|
||||
return;
|
||||
@@ -519,26 +562,6 @@ export const useAgentChatSession = ({
|
||||
};
|
||||
}),
|
||||
);
|
||||
} else if (event.type === "session_title") {
|
||||
const nextTitle = event.title.trim();
|
||||
if (nextTitle && !isSessionTitleManuallyEditedRef.current) {
|
||||
setSessionTitle(nextTitle);
|
||||
const currentSessionId = sessionIdRef.current;
|
||||
if (currentSessionId) {
|
||||
const currentNonce = ++titleUpdateNonceRef.current;
|
||||
void updateChatSessionTitle(currentSessionId, nextTitle, {
|
||||
isTitleManuallyEdited: false,
|
||||
})
|
||||
.then(() => listChatSessions())
|
||||
.then((sessions) => {
|
||||
if (titleUpdateNonceRef.current !== currentNonce) return;
|
||||
setChatSessions(sessions);
|
||||
})
|
||||
.catch((error) => {
|
||||
console.error("[GlobalChatbox] Failed to persist session title:", error);
|
||||
});
|
||||
}
|
||||
}
|
||||
} else if (event.type === "done") {
|
||||
setMessages((prev) =>
|
||||
prev.map((message) => {
|
||||
|
||||
Reference in New Issue
Block a user