diff --git a/web-app/src/containers/ThreadContent.tsx b/web-app/src/containers/ThreadContent.tsx
index 6811c8ab5..05aba7851 100644
--- a/web-app/src/containers/ThreadContent.tsx
+++ b/web-app/src/containers/ThreadContent.tsx
@@ -146,66 +146,11 @@ export const ThreadContent = memo(
isReasoningActiveLoading,
hasReasoningSteps,
} = useMemo(() => {
- const thinkStartTag = ''
- const thinkEndTag = ''
- let currentFinalText = ''
- let currentReasoning = ''
- let hasSteps = false
+ // With the streaming functions updated, the text variable now only contains the final output.
+ const currentFinalText = text.trim()
+ const currentReasoning = '' // Reasoning is now only derived from streamEvents/allSteps
- const firstThinkStart = text.indexOf(thinkStartTag)
- const lastThinkStart = text.lastIndexOf(thinkStartTag)
- const lastThinkEnd = text.lastIndexOf(thinkEndTag)
-
- // Check if there's an unclosed tag
- const hasOpenThink = lastThinkStart > lastThinkEnd
-
- if (firstThinkStart === -1) {
- // No tags at all - everything is final output
- currentFinalText = text
- } else if (hasOpenThink && isStreamingThisThread) {
- // CASE 1: There's an open tag during streaming
- // Everything from FIRST onward is reasoning
- hasSteps = true
-
- // Text before first is final output
- currentFinalText = text.substring(0, firstThinkStart)
-
- // Everything from first onward is reasoning
- const reasoningText = text.substring(firstThinkStart)
-
- // Extract content from all blocks (both closed and open)
- const reasoningRegex = /([\s\S]*?)(?:<\/think>|$)/g
- const matches = [...reasoningText.matchAll(reasoningRegex)]
- const reasoningParts = matches.map((match) => cleanReasoning(match[1]))
- currentReasoning = reasoningParts.join('\n\n')
- } else {
- // CASE 2: All tags are closed
- // Extract reasoning from inside tags, everything else is final output
- hasSteps = true
-
- const reasoningRegex = /[\s\S]*?<\/think>/g
- const matches = [...text.matchAll(reasoningRegex)]
-
- let lastIndex = 0
-
- // Build final output from text between/outside blocks
- for (const match of matches) {
- currentFinalText += text.substring(lastIndex, match.index)
- lastIndex = match.index + match[0].length
- }
-
- // Add remaining text after last
- currentFinalText += text.substring(lastIndex)
-
- // Extract reasoning content
- const reasoningParts = matches.map((match) => {
- const content = match[0].replace(/|<\/think>/g, '')
- return cleanReasoning(content)
- })
- currentReasoning = reasoningParts.join('\n\n')
- }
-
- // Check for tool calls
+ // Check for tool calls or reasoning events in metadata to determine steps/loading
const isToolCallsPresent = !!(
item.metadata &&
'tool_calls' in item.metadata &&
@@ -213,19 +158,29 @@ export const ThreadContent = memo(
item.metadata.tool_calls.length > 0
)
- hasSteps = hasSteps || isToolCallsPresent
+ // Check for any reasoning chunks in the streamEvents
+ const hasReasoningEvents = !!(
+ item.metadata &&
+ 'streamEvents' in item.metadata &&
+ Array.isArray(item.metadata.streamEvents) &&
+ item.metadata.streamEvents.some(
+ (e: StreamEvent) => e.type === 'reasoning_chunk'
+ )
+ )
- // Loading if streaming and no final output yet
+ const hasSteps = isToolCallsPresent || hasReasoningEvents
+
+ // Loading if streaming, no final output yet, but we expect steps (reasoning or tool calls)
const loading =
- isStreamingThisThread && currentFinalText.trim().length === 0
+ isStreamingThisThread && currentFinalText.length === 0 && hasSteps
return {
- finalOutputText: currentFinalText.trim(),
+ finalOutputText: currentFinalText,
streamedReasoningText: currentReasoning,
isReasoningActiveLoading: loading,
hasReasoningSteps: hasSteps,
}
- }, [item.content, isStreamingThisThread, item.metadata, text])
+ }, [item.metadata, text, isStreamingThisThread])
const isToolCalls =
item.metadata &&
@@ -516,7 +471,6 @@ export const ThreadContent = memo(
// END: Constructing allSteps
// ====================================================================
- // FIX: Determine which text prop to pass to ThinkingBlock
// If we have streamEvents, rely on 'steps' and pass an empty text buffer.
const streamingTextBuffer = useMemo(() => {
const streamEvents = item.metadata?.streamEvents
@@ -528,9 +482,11 @@ export const ThreadContent = memo(
return ''
}
- // Otherwise, rely on the raw text buffer for rendering (used during initial stream fallback)
- return streamedReasoningText
- }, [item.metadata?.streamEvents, streamedReasoningText]) // Use the object reference for dependency array
+ // Since we no longer concatenate reasoning to the main text,
+ // the only time we'd rely on text buffer is if streamEvents fails to load.
+ // For robustness, we can simply return an empty string to force use of 'steps'.
+ return ''
+ }, [item.metadata?.streamEvents]) // Use the object reference for dependency array
// ====================================================================
// Determine if we should show the thinking block
diff --git a/web-app/src/hooks/useChat.ts b/web-app/src/hooks/useChat.ts
index f78a810df..ebfdf981f 100644
--- a/web-app/src/hooks/useChat.ts
+++ b/web-app/src/hooks/useChat.ts
@@ -671,7 +671,7 @@ export const useChat = () => {
const deltaReasoning =
reasoningProcessor.processReasoningChunk(part)
if (deltaReasoning) {
- accumulatedText += deltaReasoning
+ // accumulatedText += deltaReasoning
// Track reasoning event
streamEvents.push({
timestamp: Date.now(),
@@ -705,7 +705,7 @@ export const useChat = () => {
// Only finalize and flush if not aborted
if (!abortController.signal.aborted) {
// Finalize reasoning (close any open think tags)
- accumulatedText += reasoningProcessor.finalize()
+ // accumulatedText += reasoningProcessor.finalize()
// Ensure any pending buffered content is rendered at the end
flushIfPending()
}
diff --git a/web-app/src/lib/completion.ts b/web-app/src/lib/completion.ts
index fabde868d..6b328143a 100644
--- a/web-app/src/lib/completion.ts
+++ b/web-app/src/lib/completion.ts
@@ -777,7 +777,7 @@ export const postMessageProcessing = async (
const deltaContent = chunk.choices[0]?.delta?.content || ''
if (textContent?.text) {
- if (deltaReasoning) textContent.text.value += deltaReasoning
+ // if (deltaReasoning) textContent.text.value += deltaReasoning
if (deltaContent) {
textContent.text.value += deltaContent
followUpText += deltaContent