fix: do not add done after tool call output when there is another reasoning step
This commit is contained in:
parent
6d4d7d371f
commit
2f00ae0d33
@ -1,7 +1,6 @@
|
|||||||
import { ChevronDown, ChevronUp, Loader, Check } from 'lucide-react'
|
import { ChevronDown, ChevronUp, Loader, Check } from 'lucide-react'
|
||||||
import { create } from 'zustand'
|
import { create } from 'zustand'
|
||||||
import { RenderMarkdown } from './RenderMarkdown'
|
import { RenderMarkdown } from './RenderMarkdown'
|
||||||
// import { useAppState } from '@/hooks/useAppState'
|
|
||||||
import { useTranslation } from '@/i18n/react-i18next-compat'
|
import { useTranslation } from '@/i18n/react-i18next-compat'
|
||||||
import { useMemo } from 'react'
|
import { useMemo } from 'react'
|
||||||
import { cn } from '@/lib/utils'
|
import { cn } from '@/lib/utils'
|
||||||
@ -49,7 +48,6 @@ const formatDuration = (ms: number) => {
|
|||||||
|
|
||||||
const ThinkingBlock = ({
|
const ThinkingBlock = ({
|
||||||
id,
|
id,
|
||||||
// text, // Unused internally
|
|
||||||
steps = [],
|
steps = [],
|
||||||
loading: propLoading,
|
loading: propLoading,
|
||||||
duration,
|
duration,
|
||||||
@ -61,8 +59,8 @@ const ThinkingBlock = ({
|
|||||||
// Actual loading state comes from prop, determined by whether final text started streaming (Req 2)
|
// Actual loading state comes from prop, determined by whether final text started streaming (Req 2)
|
||||||
const loading = propLoading
|
const loading = propLoading
|
||||||
|
|
||||||
// Set default expansion state: expanded if loading, collapsed if done.
|
// Set default expansion state: collapsed if done (not loading).
|
||||||
// If loading transitions to false (textSegment starts), this defaults to collapsed if state is absent.
|
// If loading transitions to false (textSegment starts), this defaults to collapsed.
|
||||||
const isExpanded = thinkingState[id] ?? (loading ? true : false)
|
const isExpanded = thinkingState[id] ?? (loading ? true : false)
|
||||||
|
|
||||||
// Filter out the 'done' step for streaming display
|
// Filter out the 'done' step for streaming display
|
||||||
@ -70,15 +68,14 @@ const ThinkingBlock = ({
|
|||||||
() => steps.filter((step) => step.type !== 'done'),
|
() => steps.filter((step) => step.type !== 'done'),
|
||||||
[steps]
|
[steps]
|
||||||
)
|
)
|
||||||
|
|
||||||
const N = stepsWithoutDone.length
|
const N = stepsWithoutDone.length
|
||||||
|
|
||||||
// Determine the step to display in the condensed streaming view (Req 3)
|
// Determine the step to display in the condensed streaming view
|
||||||
// Show step N-2 when N >= 2 (i.e., when step N-1 is streaming, show the previously finished step)
|
// When step N-1 is streaming, show the previously finished step (N-2).
|
||||||
const stepToRenderWhenStreaming = useMemo(() => {
|
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) {
|
if (N >= 2) {
|
||||||
// Show the penultimate step (index N-2)
|
|
||||||
return stepsWithoutDone[N - 2]
|
return stepsWithoutDone[N - 2]
|
||||||
}
|
}
|
||||||
return null
|
return null
|
||||||
@ -214,11 +211,7 @@ const ThinkingBlock = ({
|
|||||||
|
|
||||||
{/* Streaming/Condensed View - shows previous finished step */}
|
{/* Streaming/Condensed View - shows previous finished step */}
|
||||||
{loading && stepToRenderWhenStreaming && (
|
{loading && stepToRenderWhenStreaming && (
|
||||||
<div
|
<div className={cn('mt-2 pl-6 pr-4 text-main-view-fg/60')}>
|
||||||
className={cn(
|
|
||||||
'mt-2 pl-6 pr-4 text-main-view-fg/60 transition-opacity duration-150 ease-in'
|
|
||||||
)}
|
|
||||||
>
|
|
||||||
<div className="relative border-l border-dashed border-main-view-fg/20 ml-1.5">
|
<div className="relative border-l border-dashed border-main-view-fg/20 ml-1.5">
|
||||||
<div className="relative pl-6 pb-2">
|
<div className="relative pl-6 pb-2">
|
||||||
{/* Bullet point */}
|
{/* Bullet point */}
|
||||||
|
|||||||
@ -7,6 +7,7 @@ import { useAppState } from '@/hooks/useAppState'
|
|||||||
import { cn } from '@/lib/utils'
|
import { cn } from '@/lib/utils'
|
||||||
import { useMessages } from '@/hooks/useMessages'
|
import { useMessages } from '@/hooks/useMessages'
|
||||||
import ThinkingBlock from '@/containers/ThinkingBlock'
|
import ThinkingBlock from '@/containers/ThinkingBlock'
|
||||||
|
// import ToolCallBlock from '@/containers/ToolCallBlock'
|
||||||
import { useChat } from '@/hooks/useChat'
|
import { useChat } from '@/hooks/useChat'
|
||||||
import {
|
import {
|
||||||
EditMessageDialog,
|
EditMessageDialog,
|
||||||
@ -251,7 +252,7 @@ export const ThreadContent = memo(
|
|||||||
| { avatar?: React.ReactNode; name?: React.ReactNode }
|
| { avatar?: React.ReactNode; name?: React.ReactNode }
|
||||||
| undefined
|
| undefined
|
||||||
|
|
||||||
// Constructing allSteps for ThinkingBlock
|
// Constructing allSteps for ThinkingBlock (Fixing Interleaving and Done step)
|
||||||
const allSteps: ThoughtStep[] = useMemo(() => {
|
const allSteps: ThoughtStep[] = useMemo(() => {
|
||||||
const steps: ThoughtStep[] = []
|
const steps: ThoughtStep[] = []
|
||||||
|
|
||||||
@ -267,7 +268,7 @@ export const ThreadContent = memo(
|
|||||||
|
|
||||||
let thoughtIndex = 0
|
let thoughtIndex = 0
|
||||||
|
|
||||||
// 2. Interleave tool steps and thought steps
|
// Interleave tool steps and thought steps
|
||||||
if (isToolCalls && item.metadata?.tool_calls) {
|
if (isToolCalls && item.metadata?.tool_calls) {
|
||||||
const toolCalls = item.metadata.tool_calls as ToolCall[]
|
const toolCalls = item.metadata.tool_calls as ToolCall[]
|
||||||
|
|
||||||
@ -312,19 +313,24 @@ export const ThreadContent = memo(
|
|||||||
thoughtIndex++
|
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 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 the message is finalized (not streaming) AND the last step was a tool output
|
||||||
if (
|
// AND there is no subsequent final text, we suppress 'done' to allow seamless transition
|
||||||
!isStreamingThisThread &&
|
// to the next assistant message/thought block.
|
||||||
(hasReasoning || isToolCalls || textSegment)
|
const endsInToolOutputWithoutFinalText =
|
||||||
) {
|
lastStepType === 'tool_output' && textSegment.length === 0
|
||||||
steps.push({
|
|
||||||
type: 'done',
|
if (!isStreamingThisThread && (hasReasoning || isToolCalls)) {
|
||||||
content: 'Done',
|
if (textSegment.length > 0 || !endsInToolOutputWithoutFinalText) {
|
||||||
time: totalTime,
|
steps.push({
|
||||||
})
|
type: 'done',
|
||||||
|
content: 'Done',
|
||||||
|
time: totalTime,
|
||||||
|
})
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return steps
|
return steps
|
||||||
@ -338,7 +344,7 @@ export const ThreadContent = memo(
|
|||||||
])
|
])
|
||||||
// END: Constructing allSteps
|
// 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.
|
// Loading is true only if streaming is happening AND we haven't started outputting final text yet.
|
||||||
const isReasoningActiveLoading =
|
const isReasoningActiveLoading =
|
||||||
isStreamingThisThread && textSegment.length === 0
|
isStreamingThisThread && textSegment.length === 0
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user