diff --git a/web-app/src/containers/ThinkingBlock.tsx b/web-app/src/containers/ThinkingBlock.tsx index b6319c7a3..fe4e5d234 100644 --- a/web-app/src/containers/ThinkingBlock.tsx +++ b/web-app/src/containers/ThinkingBlock.tsx @@ -1,7 +1,6 @@ import { ChevronDown, ChevronUp, Loader, Check } from 'lucide-react' import { create } from 'zustand' import { RenderMarkdown } from './RenderMarkdown' -// import { useAppState } from '@/hooks/useAppState' import { useTranslation } from '@/i18n/react-i18next-compat' import { useMemo } from 'react' import { cn } from '@/lib/utils' @@ -49,7 +48,6 @@ const formatDuration = (ms: number) => { const ThinkingBlock = ({ id, - // text, // Unused internally steps = [], loading: propLoading, duration, @@ -61,8 +59,8 @@ const ThinkingBlock = ({ // Actual loading state comes from prop, determined by whether final text started streaming (Req 2) const loading = propLoading - // Set default expansion state: expanded if loading, collapsed if done. - // If loading transitions to false (textSegment starts), this defaults to collapsed if state is absent. + // Set default expansion state: collapsed if done (not loading). + // If loading transitions to false (textSegment starts), this defaults to collapsed. const isExpanded = thinkingState[id] ?? (loading ? true : false) // Filter out the 'done' step for streaming display @@ -70,15 +68,14 @@ const ThinkingBlock = ({ () => steps.filter((step) => step.type !== 'done'), [steps] ) - const N = stepsWithoutDone.length - // Determine the step to display in the condensed streaming view (Req 3) - // Show step N-2 when N >= 2 (i.e., when step N-1 is streaming, show the previously finished step) + // Determine the step to display in the condensed streaming view + // When step N-1 is streaming, show the previously finished step (N-2). const stepToRenderWhenStreaming = useMemo(() => { - if (!loading) return null // Only apply this logic when actively loading + if (!loading) return null + // If N >= 2, the N-1 step is currently streaming, so we show the finished step N-2. if (N >= 2) { - // Show the penultimate step (index N-2) return stepsWithoutDone[N - 2] } return null @@ -214,11 +211,7 @@ const ThinkingBlock = ({ {/* Streaming/Condensed View - shows previous finished step */} {loading && stepToRenderWhenStreaming && ( -
+
{/* Bullet point */} diff --git a/web-app/src/containers/ThreadContent.tsx b/web-app/src/containers/ThreadContent.tsx index 6888dfdc7..4d4585076 100644 --- a/web-app/src/containers/ThreadContent.tsx +++ b/web-app/src/containers/ThreadContent.tsx @@ -7,6 +7,7 @@ import { useAppState } from '@/hooks/useAppState' import { cn } from '@/lib/utils' import { useMessages } from '@/hooks/useMessages' import ThinkingBlock from '@/containers/ThinkingBlock' +// import ToolCallBlock from '@/containers/ToolCallBlock' import { useChat } from '@/hooks/useChat' import { EditMessageDialog, @@ -251,7 +252,7 @@ export const ThreadContent = memo( | { avatar?: React.ReactNode; name?: React.ReactNode } | undefined - // Constructing allSteps for ThinkingBlock + // Constructing allSteps for ThinkingBlock (Fixing Interleaving and Done step) const allSteps: ThoughtStep[] = useMemo(() => { const steps: ThoughtStep[] = [] @@ -267,7 +268,7 @@ export const ThreadContent = memo( let thoughtIndex = 0 - // 2. Interleave tool steps and thought steps + // Interleave tool steps and thought steps if (isToolCalls && item.metadata?.tool_calls) { const toolCalls = item.metadata.tool_calls as ToolCall[] @@ -312,19 +313,24 @@ export const ThreadContent = memo( thoughtIndex++ } - // Add Done step if not streaming AND the reasoning/tooling process is concluded + // Add Done step only if the sequence is concluded for display const totalTime = item.metadata?.totalThinkingTime as number | undefined + const lastStepType = steps[steps.length - 1]?.type - // If the thread is not streaming, and we had steps or final text output, we add 'done'. - if ( - !isStreamingThisThread && - (hasReasoning || isToolCalls || textSegment) - ) { - steps.push({ - type: 'done', - content: 'Done', - time: totalTime, - }) + // If the message is finalized (not streaming) AND the last step was a tool output + // AND there is no subsequent final text, we suppress 'done' to allow seamless transition + // to the next assistant message/thought block. + const endsInToolOutputWithoutFinalText = + lastStepType === 'tool_output' && textSegment.length === 0 + + if (!isStreamingThisThread && (hasReasoning || isToolCalls)) { + if (textSegment.length > 0 || !endsInToolOutputWithoutFinalText) { + steps.push({ + type: 'done', + content: 'Done', + time: totalTime, + }) + } } return steps @@ -338,7 +344,7 @@ export const ThreadContent = memo( ]) // END: Constructing allSteps - // Determine if reasoning phase is actively loading (Req 2) + // Determine if reasoning phase is actively loading // Loading is true only if streaming is happening AND we haven't started outputting final text yet. const isReasoningActiveLoading = isStreamingThisThread && textSegment.length === 0