fix: 会话快速切换pollute message

This commit is contained in:
2026-07-16 18:50:46 +08:00
parent e7da2fe788
commit 621022ea4f
3 changed files with 111 additions and 4 deletions
@@ -61,6 +61,7 @@
- 桌面端对话框固定宽约 360–400px;移动端抽屉式全宽覆盖;收起态为胶囊/圆形入口按钮。
- 会话管理入口在对话框头部:当前会话标题 + 历史会话下拉(按更新时间倒序)+ 新建对话按钮,全部包在对话框内。
- 快速切换会话或会话轮询刷新产生并发详情请求时,前端只允许最后发起的请求更新当前会话、消息、错误和加载态;旧响应不得覆盖用户最新选择。
- 普通 JSON 消息请求的回包必须绑定发送时的会话:用户在等待期间切换到其他会话后,只更新原会话的列表摘要,不得把原会话的 `deltaMessages` 、错误或画布刷新副作用应用到当前面板。
- 收起对话框只是隐藏面板,不卸载当前会话 hook;普通 JSON 消息请求的等待态和外部生成任务状态必须在收起 / 重新打开之间保持一致。
## 附件
@@ -327,6 +327,103 @@ describe('useEditorAgentConversation', () => {
);
});
it('does not apply a completed message response after switching conversations', async () => {
const client = createClient();
const onCanvasRefreshRequested = vi.fn();
let resolveSend!: (response: EditorAgentMessageResponse) => void;
vi.mocked(client.sendMessage).mockImplementation(
() =>
new Promise<EditorAgentMessageResponse>((resolve) => {
resolveSend = resolve;
}),
);
const { result } = renderHook(() =>
useEditorAgentConversation({
projectId: 'project-1',
client,
onCanvasRefreshRequested,
}),
);
await waitFor(() => {
expect(result.current.activeConversationId).toBe('conversation-1');
});
let sendPromise!: Promise<void>;
act(() => {
sendPromise = result.current.sendMessage('在第一个会话生成图片');
});
await waitFor(() => {
expect(result.current.isWaiting).toBe(true);
});
vi.mocked(client.getConversation).mockResolvedValueOnce({
conversationId: 'conversation-2',
projectId: 'project-1',
title: '第二个会话',
messages: [
{
id: 20,
role: 'assistant',
text: '第二个会话原有消息',
attachments: [],
toolCall: null,
createdAt: '2026-07-03T00:02:00.000Z',
},
],
createdAt: '2026-07-03T00:02:00.000Z',
updatedAt: '2026-07-03T00:02:00.000Z',
});
await act(async () => {
await result.current.selectConversation('conversation-2');
});
await act(async () => {
resolveSend({
conversation: {
conversationId: 'conversation-1',
projectId: 'project-1',
title: '第一个会话已更新',
updatedAt: '2026-07-03T00:03:00.000Z',
},
deltaMessages: [
{
id: 21,
role: 'assistant',
text: '第一个会话生成完成',
attachments: [],
toolCall: {
toolName: 'generate_image',
status: 'completed',
externalJobId: 'task-conversation-1',
args: {},
displayArgs: {
stringArgs: [],
imageArgs: [],
extras: { priceMudPoints: 0 },
},
images: [{ imageSrc: '/conversation-1-result.png' }],
error: null,
},
createdAt: '2026-07-03T00:03:00.000Z',
},
],
errorMessage: null,
});
await sendPromise;
});
expect(result.current.activeConversationId).toBe('conversation-2');
expect(result.current.messages).toHaveLength(1);
expect(result.current.messages[0]?.text).toBe('第二个会话原有消息');
expect(onCanvasRefreshRequested).not.toHaveBeenCalled();
expect(
result.current.conversations.find(
(conversation) => conversation.conversationId === 'conversation-1',
)?.title,
).toBe('第一个会话已更新');
});
it('creates a conversation before sending when the project has no history', async () => {
const client = createClient();
vi.mocked(client.listConversations).mockResolvedValueOnce([]);
@@ -189,6 +189,7 @@ export function useEditorAgentConversation({
const applyConversationDetail = useCallback(
(detail: EditorAgentConversationDetail) => {
activeConversationIdRef.current = detail.conversationId;
setConversations((currentConversations) =>
upsertConversationSummary(
currentConversations,
@@ -240,6 +241,7 @@ export function useEditorAgentConversation({
setIsLoadingMessages(false);
if (!normalizedProjectId) {
setConversations([]);
activeConversationIdRef.current = null;
setActiveConversationId(null);
setMessages([]);
setErrorMessage(null);
@@ -259,6 +261,7 @@ export function useEditorAgentConversation({
setConversations(sortedConversations);
const firstConversation = sortedConversations[0] ?? null;
if (!firstConversation) {
activeConversationIdRef.current = null;
setActiveConversationId(null);
setMessages([]);
return;
@@ -413,6 +416,9 @@ export function useEditorAgentConversation({
response.conversation,
),
);
if (activeConversationIdRef.current !== conversationId) {
return;
}
if (response.errorMessage) {
setErrorMessage(response.errorMessage);
} else {
@@ -424,10 +430,12 @@ export function useEditorAgentConversation({
}
const message =
error instanceof Error ? error.message : '发送画布 Agent 消息失败';
if (activeConversationIdRef.current === conversationId) {
setErrorMessage(message);
setMessages((currentMessages) =>
currentMessages.filter((message) => message !== optimisticMessage),
);
}
throw error;
} finally {
if (activeRequestAbortControllerRef.current === abortController) {
@@ -532,6 +540,7 @@ export function useEditorAgentConversation({
await loadConversation(nextConversation.conversationId);
return;
}
activeConversationIdRef.current = null;
setActiveConversationId(null);
setMessages([]);
} catch (error) {