fix: glitch UI thinking or duplicate content when multiple think after tools call (#5217)
* fix: update id streaming content think * fix: glitch UI or duplicate content when multiple thinking
This commit is contained in:
parent
44b5310a6a
commit
1bbac32d88
@ -1,20 +1,53 @@
|
|||||||
import { useAppState } from '@/hooks/useAppState'
|
import { useAppState } from '@/hooks/useAppState'
|
||||||
import { ThreadContent } from './ThreadContent'
|
import { ThreadContent } from './ThreadContent'
|
||||||
import { memo } from 'react'
|
import { memo, useMemo } from 'react'
|
||||||
import { useMessages } from '@/hooks/useMessages'
|
import { useMessages } from '@/hooks/useMessages'
|
||||||
|
|
||||||
type Props = {
|
type Props = {
|
||||||
threadId: string
|
threadId: string
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Helper to extract <think>...</think> segment
|
||||||
|
function extractReasoningSegment(text: string) {
|
||||||
|
if (!text) return ''
|
||||||
|
const match = text.match(/<think>([\s\S]*?)<\/think>/)
|
||||||
|
if (match) return match[0].trim()
|
||||||
|
// If only opening <think> and no closing, take everything after <think>
|
||||||
|
const openIdx = text.indexOf('<think>')
|
||||||
|
if (openIdx !== -1) return text.slice(openIdx).trim()
|
||||||
|
return ''
|
||||||
|
}
|
||||||
|
|
||||||
// Use memo with no dependencies to allow re-renders when props change
|
// Use memo with no dependencies to allow re-renders when props change
|
||||||
|
// Avoid duplicate reasoning segments after tool calls
|
||||||
export const StreamingContent = memo(({ threadId }: Props) => {
|
export const StreamingContent = memo(({ threadId }: Props) => {
|
||||||
const { streamingContent } = useAppState()
|
const { streamingContent } = useAppState()
|
||||||
const { getMessages } = useMessages()
|
const { getMessages } = useMessages()
|
||||||
const messages = getMessages(threadId)
|
const messages = getMessages(threadId)
|
||||||
|
|
||||||
|
const streamingReasoning = useMemo(() => {
|
||||||
|
const text =
|
||||||
|
streamingContent?.content?.find((e) => e.type === 'text')?.text?.value ||
|
||||||
|
''
|
||||||
|
return extractReasoningSegment(text)
|
||||||
|
}, [streamingContent])
|
||||||
|
|
||||||
|
const lastAssistant = useMemo(() => {
|
||||||
|
return [...messages].reverse().find((m) => m.role === 'assistant')
|
||||||
|
}, [messages])
|
||||||
|
const lastAssistantReasoning = useMemo(() => {
|
||||||
|
if (!lastAssistant) return ''
|
||||||
|
const text =
|
||||||
|
lastAssistant.content?.find((e) => e.type === 'text')?.text?.value || ''
|
||||||
|
return extractReasoningSegment(text)
|
||||||
|
}, [lastAssistant])
|
||||||
|
|
||||||
if (!streamingContent || streamingContent.thread_id !== threadId) return null
|
if (!streamingContent || streamingContent.thread_id !== threadId) return null
|
||||||
|
|
||||||
|
if (streamingReasoning && streamingReasoning === lastAssistantReasoning) {
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
|
||||||
// Pass a new object to ThreadContent to avoid reference issues
|
// Pass a new object to ThreadContent to avoid reference issues
|
||||||
// The streaming content is always the last message
|
// The streaming content is always the last message
|
||||||
return (
|
return (
|
||||||
|
|||||||
@ -302,7 +302,7 @@ export const ThreadContent = memo(
|
|||||||
<ThinkingBlock
|
<ThinkingBlock
|
||||||
id={
|
id={
|
||||||
item.isLastMessage
|
item.isLastMessage
|
||||||
? `${item.thread_id}-last`
|
? `${item.thread_id}-last-${reasoningSegment.slice(0, 50).replace(/\s/g, '').slice(-10)}`
|
||||||
: `${item.thread_id}-${item.index ?? item.id}`
|
: `${item.thread_id}-${item.index ?? item.id}`
|
||||||
}
|
}
|
||||||
text={reasoningSegment}
|
text={reasoningSegment}
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user