From 1bbac32d88e7267734dd1027a2b61d186d071847 Mon Sep 17 00:00:00 2001 From: Faisal Amir Date: Mon, 9 Jun 2025 11:36:36 +0700 Subject: [PATCH] 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 --- web-app/src/containers/StreamingContent.tsx | 35 ++++++++++++++++++++- web-app/src/containers/ThreadContent.tsx | 2 +- 2 files changed, 35 insertions(+), 2 deletions(-) diff --git a/web-app/src/containers/StreamingContent.tsx b/web-app/src/containers/StreamingContent.tsx index 4592e2598..ce88cbc49 100644 --- a/web-app/src/containers/StreamingContent.tsx +++ b/web-app/src/containers/StreamingContent.tsx @@ -1,20 +1,53 @@ import { useAppState } from '@/hooks/useAppState' import { ThreadContent } from './ThreadContent' -import { memo } from 'react' +import { memo, useMemo } from 'react' import { useMessages } from '@/hooks/useMessages' type Props = { threadId: string } +// Helper to extract ... segment +function extractReasoningSegment(text: string) { + if (!text) return '' + const match = text.match(/([\s\S]*?)<\/think>/) + if (match) return match[0].trim() + // If only opening and no closing, take everything after + const openIdx = text.indexOf('') + if (openIdx !== -1) return text.slice(openIdx).trim() + return '' +} + // 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) => { const { streamingContent } = useAppState() const { getMessages } = useMessages() 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 (streamingReasoning && streamingReasoning === lastAssistantReasoning) { + return null + } + // Pass a new object to ThreadContent to avoid reference issues // The streaming content is always the last message return ( diff --git a/web-app/src/containers/ThreadContent.tsx b/web-app/src/containers/ThreadContent.tsx index dea8fe7d9..7d72034c1 100644 --- a/web-app/src/containers/ThreadContent.tsx +++ b/web-app/src/containers/ThreadContent.tsx @@ -302,7 +302,7 @@ export const ThreadContent = memo(