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