diff --git a/src/renderer/src/components/native-chat/NativeChatReasoningRow.test.tsx b/src/renderer/src/components/native-chat/NativeChatReasoningRow.test.tsx index fe59a95b546..75a019edcac 100644 --- a/src/renderer/src/components/native-chat/NativeChatReasoningRow.test.tsx +++ b/src/renderer/src/components/native-chat/NativeChatReasoningRow.test.tsx @@ -72,6 +72,19 @@ describe('reasoning disclosure', () => { expect(screen.getByRole('button')).toHaveTextContent('Thought for 1m 5s') }) + it('excludes later turn work after the block stops changing', () => { + const now = vi.spyOn(Date, 'now').mockReturnValue(10_000) + const { rerender } = render() + now.mockReturnValue(22_000) + rerender() + now.mockReturnValue(70_000) + rerender() + expect(screen.getByRole('button')).toHaveTextContent('Thinking...') + now.mockReturnValue(100_000) + rerender() + expect(screen.getByRole('button')).toHaveTextContent('Thought for 12s') + }) + it('resets the clock when block identity changes without a streaming-state transition', () => { const now = vi.spyOn(Date, 'now').mockReturnValue(10_000) const { rerender } = render() diff --git a/src/renderer/src/components/native-chat/NativeChatReasoningRow.tsx b/src/renderer/src/components/native-chat/NativeChatReasoningRow.tsx index 1ed9f02120e..d930d67dbc1 100644 --- a/src/renderer/src/components/native-chat/NativeChatReasoningRow.tsx +++ b/src/renderer/src/components/native-chat/NativeChatReasoningRow.tsx @@ -23,6 +23,8 @@ export function NativeChatReasoningRow({ const [timing, setTiming] = useState(() => ({ blockId, streaming: isStreaming, + markdown, + lastUpdatedAt: Date.now(), startedAt: isStreaming ? Date.now() : null, elapsedMs: null as number | null })) @@ -33,22 +35,31 @@ export function NativeChatReasoningRow({ return { blockId, streaming: isStreaming, + markdown, + lastUpdatedAt: now, startedAt: isStreaming ? now : null, elapsedMs: null } } - if (previous.streaming === isStreaming) { + if (previous.streaming === isStreaming && (!isStreaming || previous.markdown === markdown)) { return previous } // Only an observed stream supplies a duration; journal history has no local start. + const lastUpdatedAt = + (isStreaming && !previous.streaming) || previous.markdown !== markdown + ? now + : previous.lastUpdatedAt return { ...previous, + markdown, + lastUpdatedAt, streaming: isStreaming, - startedAt: isStreaming ? now : previous.startedAt, - elapsedMs: isStreaming || previous.startedAt === null ? null : now - previous.startedAt + startedAt: isStreaming && !previous.streaming ? now : previous.startedAt, + elapsedMs: + isStreaming || previous.startedAt === null ? null : lastUpdatedAt - previous.startedAt } }) - }, [blockId, isStreaming]) + }, [blockId, isStreaming, markdown]) if (!markdown.trim()) { return null }