fix: prevent silent refresh from overwriting newly created conversations in EditorAgentConversation
This commit is contained in:
+69
@@ -264,6 +264,75 @@ describe('useEditorAgentConversation', () => {
|
||||
expect(result.current.isLoadingMessages).toBe(false);
|
||||
});
|
||||
|
||||
it('does not let a silent refresh replace a newly created conversation', async () => {
|
||||
const client = createClient();
|
||||
let resolveRefresh!: (detail: EditorAgentConversationDetail) => void;
|
||||
let resolveCreate!: (detail: EditorAgentConversationDetail) => void;
|
||||
const refreshPromise = new Promise<EditorAgentConversationDetail>(
|
||||
(resolve) => {
|
||||
resolveRefresh = resolve;
|
||||
},
|
||||
);
|
||||
const createPromise = new Promise<EditorAgentConversationDetail>(
|
||||
(resolve) => {
|
||||
resolveCreate = resolve;
|
||||
},
|
||||
);
|
||||
const { result } = renderHook(() =>
|
||||
useEditorAgentConversation({ projectId: 'project-1', client }),
|
||||
);
|
||||
|
||||
await waitFor(() => {
|
||||
expect(result.current.activeConversationId).toBe('conversation-1');
|
||||
});
|
||||
vi.mocked(client.getConversation).mockReturnValueOnce(refreshPromise);
|
||||
vi.mocked(client.createConversation).mockReturnValueOnce(createPromise);
|
||||
|
||||
let pendingRefresh!: Promise<EditorAgentConversationDetail | null>;
|
||||
let pendingCreate!: Promise<EditorAgentConversationDetail>;
|
||||
act(() => {
|
||||
pendingRefresh = result.current.refreshActiveConversation();
|
||||
pendingCreate = result.current.createConversation();
|
||||
});
|
||||
|
||||
await act(async () => {
|
||||
resolveCreate({
|
||||
conversationId: 'conversation-2',
|
||||
projectId: 'project-1',
|
||||
title: '新对话',
|
||||
messages: [],
|
||||
createdAt: '2026-07-03T00:01:00.000Z',
|
||||
updatedAt: '2026-07-03T00:01:00.000Z',
|
||||
});
|
||||
await pendingCreate;
|
||||
});
|
||||
expect(result.current.activeConversationId).toBe('conversation-2');
|
||||
|
||||
await act(async () => {
|
||||
resolveRefresh({
|
||||
conversationId: 'conversation-1',
|
||||
projectId: 'project-1',
|
||||
title: '旧对话刷新结果',
|
||||
messages: [
|
||||
{
|
||||
id: 10,
|
||||
role: 'assistant',
|
||||
text: '旧会话消息',
|
||||
attachments: [],
|
||||
toolCall: null,
|
||||
createdAt: '2026-07-03T00:02:00.000Z',
|
||||
},
|
||||
],
|
||||
createdAt: '2026-07-03T00:00:00.000Z',
|
||||
updatedAt: '2026-07-03T00:02:00.000Z',
|
||||
});
|
||||
await pendingRefresh;
|
||||
});
|
||||
|
||||
expect(result.current.activeConversationId).toBe('conversation-2');
|
||||
expect(result.current.messages).toEqual([]);
|
||||
});
|
||||
|
||||
it('appends a lazily reconciled tool message delta', async () => {
|
||||
const client = createClient();
|
||||
const pendingMessage: EditorAgentMessage = {
|
||||
|
||||
@@ -224,22 +224,34 @@ export function useEditorAgentConversation({
|
||||
);
|
||||
|
||||
const loadConversation = useCallback(
|
||||
async (conversationId: string, options: { showLoading?: boolean } = {}) => {
|
||||
async (
|
||||
conversationId: string,
|
||||
options: {
|
||||
// `activate` may make conversationId active; `refresh` may apply only
|
||||
// while that same conversationId is still active.
|
||||
mode: 'activate' | 'refresh';
|
||||
showLoading?: boolean;
|
||||
},
|
||||
) => {
|
||||
const requestId = conversationLoadRequestIdRef.current + 1;
|
||||
conversationLoadRequestIdRef.current = requestId;
|
||||
const showLoading = options.showLoading ?? true;
|
||||
const canApplyResult = () =>
|
||||
conversationLoadRequestIdRef.current === requestId &&
|
||||
(options.mode === 'activate' ||
|
||||
activeConversationIdRef.current === conversationId);
|
||||
if (showLoading) {
|
||||
setIsLoadingMessages(true);
|
||||
}
|
||||
setErrorMessage(null);
|
||||
try {
|
||||
const detail = await client.getConversation(conversationId);
|
||||
if (conversationLoadRequestIdRef.current === requestId) {
|
||||
if (canApplyResult()) {
|
||||
applyConversationDetail(detail);
|
||||
}
|
||||
return detail;
|
||||
} catch (error) {
|
||||
if (conversationLoadRequestIdRef.current === requestId) {
|
||||
if (canApplyResult()) {
|
||||
setErrorMessage(
|
||||
error instanceof Error ? error.message : '读取画布 Agent 会话失败',
|
||||
);
|
||||
@@ -326,6 +338,8 @@ export function useEditorAgentConversation({
|
||||
createConversationRequestIdRef.current === requestId &&
|
||||
normalizedProjectIdRef.current === requestedProjectId
|
||||
) {
|
||||
conversationLoadRequestIdRef.current += 1;
|
||||
setIsLoadingMessages(false);
|
||||
applyConversationDetail(detail);
|
||||
}
|
||||
return detail;
|
||||
@@ -354,7 +368,7 @@ export function useEditorAgentConversation({
|
||||
if (conversationId === activeConversationId) {
|
||||
return null;
|
||||
}
|
||||
return loadConversation(conversationId);
|
||||
return loadConversation(conversationId, { mode: 'activate' });
|
||||
},
|
||||
[activeConversationId, loadConversation],
|
||||
);
|
||||
@@ -364,7 +378,10 @@ export function useEditorAgentConversation({
|
||||
if (!conversationId) {
|
||||
return null;
|
||||
}
|
||||
return loadConversation(conversationId, { showLoading: false });
|
||||
return loadConversation(conversationId, {
|
||||
mode: 'refresh',
|
||||
showLoading: false,
|
||||
});
|
||||
}, [loadConversation]);
|
||||
|
||||
const requestCanvasRefreshForMessages = useCallback(
|
||||
@@ -374,9 +391,9 @@ export function useEditorAgentConversation({
|
||||
const toolCall = message.toolCall;
|
||||
return Boolean(
|
||||
toolCall?.externalJobId &&
|
||||
(toolCall.images.length > 0 ||
|
||||
(toolCall.videos?.length ?? 0) > 0 ||
|
||||
(toolCall.audios?.length ?? 0) > 0),
|
||||
(toolCall.images.length > 0 ||
|
||||
(toolCall.videos?.length ?? 0) > 0 ||
|
||||
(toolCall.audios?.length ?? 0) > 0),
|
||||
);
|
||||
})
|
||||
) {
|
||||
@@ -541,6 +558,7 @@ export function useEditorAgentConversation({
|
||||
}
|
||||
|
||||
const detail = await loadConversation(conversationId, {
|
||||
mode: 'refresh',
|
||||
showLoading: false,
|
||||
});
|
||||
const updatedMessage = detail.messages.find(
|
||||
@@ -596,7 +614,9 @@ export function useEditorAgentConversation({
|
||||
setConversations(remainingConversations);
|
||||
const nextConversation = remainingConversations[0] ?? null;
|
||||
if (nextConversation) {
|
||||
await loadConversation(nextConversation.conversationId);
|
||||
await loadConversation(nextConversation.conversationId, {
|
||||
mode: 'activate',
|
||||
});
|
||||
return;
|
||||
}
|
||||
activeConversationIdRef.current = null;
|
||||
|
||||
Reference in New Issue
Block a user