Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
14 changes: 11 additions & 3 deletions src/features/message/parts/ReasoningPartView.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@ export const ReasoningPartView = memo(function ReasoningPartView({ part, isStrea
const summaryContainerRef = useRef<HTMLDivElement>(null)
const summaryMeasureRef = useRef<HTMLSpanElement>(null)
const [summaryOverflow, setSummaryOverflow] = useState(false)
const [isAtBottom, setIsAtBottom] = useState(true)

const collapsedPreview = useMemo(() => (displayText || '').replace(/\s+/g, ' ').trim(), [displayText])
const thoughtDurationLabel = useMemo(() => {
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -246,7 +254,7 @@ export const ReasoningPartView = memo(function ReasoningPartView({ part, isStrea
>
<div className="min-h-0 min-w-0 overflow-hidden" style={{ clipPath: 'inset(0 -100% 0 -100%)' }}>
{shouldRenderBody && (
<ScrollArea ref={scrollAreaRef} maxHeight={192} className="border-t border-border-300/20 bg-bg-200/30">
<ScrollArea ref={scrollAreaRef} maxHeight={192} onScroll={handleCapsuleScroll} className="border-t border-border-300/20 bg-bg-200/30">
<div className="px-2 py-2 text-text-300 text-[length:var(--fs-sm)] font-mono whitespace-pre-wrap break-words overflow-x-hidden">
{displayText}
</div>
Expand Down
17 changes: 13 additions & 4 deletions src/features/message/tools/renderers/TaskRenderer.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -271,6 +271,7 @@ interface SubSessionViewProps {
const SubSessionView = memo(function SubSessionView({ sessionId }: SubSessionViewProps) {
const { t } = useTranslation('message')
const scrollRef = useRef<HTMLDivElement>(null)
const isAtBottomRef = useRef(true)
const loadedRef = useRef(false)
const subSessionMaxHeight = useResponsiveMaxHeight(0.25, 120, 240)

Expand Down Expand Up @@ -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])

// 过滤有内容的消息
Expand All @@ -340,6 +348,7 @@ const SubSessionView = memo(function SubSessionView({ sessionId }: SubSessionVie
{/* Messages */}
<div
ref={scrollRef}
onScroll={handleScroll}
className="overflow-y-auto custom-scrollbar px-3 py-2 space-y-2"
style={{ maxHeight: subSessionMaxHeight }}
>
Expand Down