diff --git a/desktop/src/stores/chatStore.test.ts b/desktop/src/stores/chatStore.test.ts index 7694708e..b7d817e5 100644 --- a/desktop/src/stores/chatStore.test.ts +++ b/desktop/src/stores/chatStore.test.ts @@ -3486,6 +3486,42 @@ describe('chatStore history mapping', () => { vi.useRealTimers() }) + it('does not duplicate the current prompt when CLI replays it after thinking starts', () => { + const prompt = '# 角色与目标\n构建一个协同编辑器' + useChatStore.setState({ + sessions: { + [TEST_SESSION_ID]: makeSession({ + messages: [ + { + id: 'live-user', + type: 'user_text', + content: prompt, + timestamp: 1, + }, + ], + chatState: 'thinking', + }), + }, + }) + + useChatStore.getState().handleServerMessage(TEST_SESSION_ID, { + type: 'thinking', + text: 'I need to plan the implementation.', + }) + useChatStore.getState().handleServerMessage(TEST_SESSION_ID, { + type: 'user_message_replay', + content: prompt, + }) + + const userMessages = useChatStore.getState().sessions[TEST_SESSION_ID]?.messages + .filter((message) => message.type === 'user_text') + expect(userMessages).toHaveLength(1) + expect(useChatStore.getState().sessions[TEST_SESSION_ID]?.messages).toMatchObject([ + { type: 'user_text', content: prompt }, + { type: 'thinking', content: 'I need to plan the implementation.' }, + ]) + }) + it('flushes pending text before appending an error message', () => { vi.useFakeTimers() diff --git a/desktop/src/stores/chatStore.ts b/desktop/src/stores/chatStore.ts index 321c07a6..143b0227 100644 --- a/desktop/src/stores/chatStore.ts +++ b/desktop/src/stores/chatStore.ts @@ -2755,24 +2755,17 @@ function appendReplayedUserMessage( if (!displayContent) return messages const modelContent = parsed.modelContent ?? content.trim() - const optimisticIndex = findOptimisticQueuedUserMessageIndex(messages, modelContent) - if (optimisticIndex >= 0) { - const optimisticMessage = messages[optimisticIndex] - if (optimisticMessage?.type === 'user_text') { + const currentTurnUserIndex = findCurrentTurnUserMessageIndex(messages, modelContent) + if (currentTurnUserIndex >= 0) { + const optimisticMessage = messages[currentTurnUserIndex] + if (optimisticMessage?.type === 'user_text' && optimisticMessage.optimisticQueued) { const { optimisticQueued: _optimisticQueued, ...confirmedMessage } = optimisticMessage return [ - ...messages.slice(0, optimisticIndex), + ...messages.slice(0, currentTurnUserIndex), confirmedMessage, - ...messages.slice(optimisticIndex + 1), + ...messages.slice(currentTurnUserIndex + 1), ] } - } - - const last = messages[messages.length - 1] - if ( - last?.type === 'user_text' && - (last.modelContent ?? last.content).trim() === modelContent - ) { return messages } @@ -2829,19 +2822,16 @@ function mapQueuedDisplayAttachments(attachments?: AttachmentRef[]): UIAttachmen })) } -function findOptimisticQueuedUserMessageIndex( +function findCurrentTurnUserMessageIndex( messages: UIMessage[], modelContent: string, ): number { for (let index = messages.length - 1; index >= 0; index -= 1) { const message = messages[index] - if ( - message?.type === 'user_text' && - message.optimisticQueued && - (message.modelContent ?? message.content).trim() === modelContent - ) { - return index + if (message?.type !== 'user_text') { + continue } + return (message.modelContent ?? message.content).trim() === modelContent ? index : -1 } return -1 }