From e64fc710fcd1ab4ed3963122e70b233473b8eb6c Mon Sep 17 00:00:00 2001 From: Bohdan Triapitsyn Date: Fri, 28 Aug 2026 23:46:14 +0300 Subject: [PATCH] fix(chat): render completed reasoning in full instead of simulating streaming MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Reasoning streaming state now derives only from the live stream phase (streaming/cooldown), never from missing persisted timing data. A cached part without time.end is no longer treated as live just because the timing field is absent, so completed reasoning renders in full on load instead of replaying a fake stream. While resolving the merge onto main, also fixed a regression the merge uncovered: main had added block-level streaming reveal (commitStreamedText) to ReasoningPart since this fix was authored, which caused the busy "Thinking…" header to stay hidden for the first moments of a short, single-paragraph streaming response (no committed line yet). The busy header now mounts as soon as streaming starts, independent of whether any text has been committed for display. Closes #2020 --- .../chat/message/parts/ReasoningPart.tsx | 14 ++++++++++---- 1 file changed, 10 insertions(+), 4 deletions(-) diff --git a/packages/ui/src/components/chat/message/parts/ReasoningPart.tsx b/packages/ui/src/components/chat/message/parts/ReasoningPart.tsx index 3d4bc2a2..169c0eb7 100644 --- a/packages/ui/src/components/chat/message/parts/ReasoningPart.tsx +++ b/packages/ui/src/components/chat/message/parts/ReasoningPart.tsx @@ -261,7 +261,11 @@ export const ReasoningTimelineBlock: React.FC = ({ }; }, []); - if (!text || text.trim().length === 0) { + // While genuinely streaming, the busy header must appear as soon as + // reasoning starts even before the block-level reveal (commitStreamedText) + // has committed a first complete line — otherwise "Thinking…" never shows + // for the first moments of a short, single-paragraph response. + if (!isStreaming && (!text || text.trim().length === 0)) { return null; } @@ -445,9 +449,11 @@ const ReasoningPart = React.memo(({ // never mutates in place. const throttledText = isStreaming ? commitStreamedText(throttledTextRaw) : throttledTextRaw; - // Show reasoning even if time.end isn't set yet (during streaming) - // Only hide if there's no text content - if (!throttledText || throttledText.trim().length === 0) { + // Show reasoning even if time.end isn't set yet (during streaming). + // While genuinely streaming, keep the block mounted even before the + // block-level reveal commits a first line, so the busy header appears + // immediately instead of waiting on committed text. + if (!isStreaming && (!throttledText || throttledText.trim().length === 0)) { return null; }