diff --git a/src/features/message/parts/ReasoningPartView.tsx b/src/features/message/parts/ReasoningPartView.tsx index 932735c9..fc62c2fa 100644 --- a/src/features/message/parts/ReasoningPartView.tsx +++ b/src/features/message/parts/ReasoningPartView.tsx @@ -30,6 +30,7 @@ export const ReasoningPartView = memo(function ReasoningPartView({ part, isStrea const summaryContainerRef = useRef(null) const summaryMeasureRef = useRef(null) const [summaryOverflow, setSummaryOverflow] = useState(false) + const [isAtBottom, setIsAtBottom] = useState(true) const collapsedPreview = useMemo(() => (displayText || '').replace(/\s+/g, ' ').trim(), [displayText]) const thoughtDurationLabel = useMemo(() => { @@ -71,12 +72,19 @@ export const ReasoningPartView = memo(function ReasoningPartView({ part, isStrea } }, [isPartStreaming, hasContent]) + const handleCapsuleScroll = useCallback(() => { + const el = scrollAreaRef.current + if (!el) return + const atBottom = el.scrollHeight - el.scrollTop - el.clientHeight < 60 + setIsAtBottom(atBottom) + }, []) + useEffect(() => { if (reasoningDisplayMode !== 'capsule') return - if (isPartStreaming && expanded && scrollAreaRef.current) { + if (isPartStreaming && expanded && isAtBottom && scrollAreaRef.current) { scrollAreaRef.current.scrollTop = scrollAreaRef.current.scrollHeight } - }, [displayText, isPartStreaming, expanded, reasoningDisplayMode]) + }, [displayText, isPartStreaming, expanded, isAtBottom, reasoningDisplayMode]) useEffect(() => { if (reasoningDisplayMode !== 'italic' && reasoningDisplayMode !== 'markdown') return @@ -246,7 +254,7 @@ export const ReasoningPartView = memo(function ReasoningPartView({ part, isStrea >
{shouldRenderBody && ( - +
{displayText}
diff --git a/src/features/message/tools/renderers/TaskRenderer.tsx b/src/features/message/tools/renderers/TaskRenderer.tsx index fa3a1821..3b64f7ef 100644 --- a/src/features/message/tools/renderers/TaskRenderer.tsx +++ b/src/features/message/tools/renderers/TaskRenderer.tsx @@ -271,6 +271,7 @@ interface SubSessionViewProps { const SubSessionView = memo(function SubSessionView({ sessionId }: SubSessionViewProps) { const { t } = useTranslation('message') const scrollRef = useRef(null) + const isAtBottomRef = useRef(true) const loadedRef = useRef(false) const subSessionMaxHeight = useResponsiveMaxHeight(0.25, 120, 240) @@ -310,11 +311,18 @@ const SubSessionView = memo(function SubSessionView({ sessionId }: SubSessionVie }) }, [sessionId]) - // 自动滚动 + const handleScroll = useCallback(() => { + const el = scrollRef.current + if (!el) return + isAtBottomRef.current = el.scrollHeight - el.scrollTop - el.clientHeight < 60 + }, []) + + // 自动滚动(仅在用户已在底部时跟随) useEffect(() => { - if (scrollRef.current && isStreaming) { - scrollRef.current.scrollTop = scrollRef.current.scrollHeight - } + if (!isStreaming) return + const el = scrollRef.current + if (!el || !isAtBottomRef.current) return + el.scrollTop = el.scrollHeight }, [messages, isStreaming]) // 过滤有内容的消息 @@ -340,6 +348,7 @@ const SubSessionView = memo(function SubSessionView({ sessionId }: SubSessionVie {/* Messages */}