fix(chat): 修复 abort 后 progress 仍显示工作中的问题

This commit is contained in:
2026-06-05 13:08:56 +08:00
parent 709b029c4e
commit 5bbac4007d
2 changed files with 91 additions and 1 deletions
@@ -353,6 +353,66 @@ describe("useAgentChatSession", () => {
expect(abortAgentChat).toHaveBeenCalledWith("session-loaded");
});
it("finalizes running progress when aborting an active prompt", async () => {
listChatSessions.mockResolvedValue([]);
jest.mocked(streamAgentChat).mockImplementationOnce(
({ onEvent, signal }) =>
new Promise<void>((_, reject) => {
onEvent({
type: "progress",
sessionId: "session-1",
id: "request-received",
phase: "start",
status: "running",
title: "开始分析",
startedAt: 1000,
} satisfies StreamEvent);
signal.addEventListener("abort", () => {
reject(new Error("aborted"));
});
}),
);
const { result } = renderHook(() =>
useAgentChatSession({
projectId: "project-1",
onToolCall: jest.fn(),
}),
);
await waitFor(() => expect(result.current.isHydrating).toBe(false));
act(() => {
void result.current.sendPrompt("测试中断");
});
await waitFor(() => expect(result.current.isStreaming).toBe(true));
act(() => {
result.current.abort();
});
await waitFor(() => expect(result.current.isStreaming).toBe(false));
expect(result.current.messages.at(-1)).toEqual(
expect.objectContaining({
role: "assistant",
content: "⚠️ **请求已中断**",
isError: true,
progress: [
expect.objectContaining({
id: "request-received",
status: "completed",
durationMs: expect.any(Number),
endedAt: expect.any(Number),
}),
],
}),
);
expect(abortAgentChat).toHaveBeenCalledWith("session-1");
});
it("ignores generated session titles after the title was edited manually", async () => {
listChatSessions.mockResolvedValue([]);
jest.mocked(streamAgentChat).mockImplementationOnce(async ({ onEvent }) => {
@@ -130,6 +130,25 @@ const completeRunningProgress = (progress: ChatProgress[] | undefined) =>
};
});
const finalizeAssistantMessageAfterAbort = (message: Message): Message => {
const completedProgress = completeRunningProgress(message.progress);
const hasVisibleOutput =
message.content.trim().length > 0 ||
Boolean(message.artifacts?.length) ||
Boolean(completedProgress?.length);
if (!hasVisibleOutput) {
return message;
}
return {
...message,
content: message.content || "⚠️ **请求已中断**",
isError: true,
progress: completedProgress,
};
};
const createUserMessage = (content: string, branchRootId?: string): Message => {
const id = createId();
return {
@@ -605,6 +624,17 @@ export const useAgentChatSession = ({
const controller = abortRef.current;
controller?.abort();
setIsStreaming(false);
const assistantMessageId = getLastAssistantMessageId();
if (assistantMessageId) {
setMessages((prev) =>
prev.map((message) =>
message.id === assistantMessageId
? finalizeAssistantMessageAfterAbort(message)
: message,
),
);
}
const cancelPromise = abortAgentChat(sessionIdRef.current).catch((error) => {
console.error("[GlobalChatbox] Failed to abort agent session:", error);
@@ -615,7 +645,7 @@ export const useAgentChatSession = ({
}
});
cancelPromiseRef.current = trackedCancelPromise;
}, []);
}, [getLastAssistantMessageId]);
const createSession = useCallback(() => {
if (isHydrating || isStreaming) return;