From c5aa59ca7542e8c168c503f4a37ad7f01dff6764 Mon Sep 17 00:00:00 2001 From: Shantur Rathore Date: Sun, 22 Mar 2026 20:04:45 +0000 Subject: [PATCH] fix(ui): keep reasoning streams pinned to bottom --- packages/ui/src/components/message-block.tsx | 27 ++++++++++++++++++++ 1 file changed, 27 insertions(+) diff --git a/packages/ui/src/components/message-block.tsx b/packages/ui/src/components/message-block.tsx index 1d59cf88..0fd21b04 100644 --- a/packages/ui/src/components/message-block.tsx +++ b/packages/ui/src/components/message-block.tsx @@ -902,6 +902,7 @@ export default function MessageBlock(props: MessageBlockProps) { onDeleteMessagesUpTo={props.onDeleteMessagesUpTo} selectedMessageIds={props.selectedMessageIds} onToggleSelectedMessage={props.onToggleSelectedMessage} + onContentRendered={props.onContentRendered} /> @@ -1280,6 +1281,7 @@ interface ReasoningCardProps { onDeleteMessagesUpTo?: (messageId: string) => void | Promise selectedMessageIds?: () => Set onToggleSelectedMessage?: (messageId: string, selected: boolean) => void + onContentRendered?: () => void } function ReasoningCard(props: ReasoningCardProps) { @@ -1288,6 +1290,25 @@ function ReasoningCard(props: ReasoningCardProps) { const [deletingMessage, setDeletingMessage] = createSignal(false) const [deletingUpTo, setDeletingUpTo] = createSignal(false) const isSelectedForDeletion = () => Boolean(props.selectedMessageIds?.().has(props.messageId)) + let pendingRenderNotificationFrame: number | null = null + + const notifyContentRendered = () => { + if (!props.onContentRendered || typeof requestAnimationFrame !== "function") return + if (pendingRenderNotificationFrame !== null) { + cancelAnimationFrame(pendingRenderNotificationFrame) + } + pendingRenderNotificationFrame = requestAnimationFrame(() => { + pendingRenderNotificationFrame = null + props.onContentRendered?.() + }) + } + + onCleanup(() => { + if (pendingRenderNotificationFrame !== null) { + cancelAnimationFrame(pendingRenderNotificationFrame) + pendingRenderNotificationFrame = null + } + }) createEffect(() => { setExpanded(Boolean(props.defaultExpanded)) @@ -1356,6 +1377,12 @@ function ReasoningCard(props: ReasoningCardProps) { const viewHideLabel = () => expanded() ? t("messageBlock.reasoning.indicator.hide") : t("messageBlock.reasoning.indicator.view") + createEffect(() => { + if (!expanded()) return + reasoningText() + notifyContentRendered() + }) + const canDeleteMessage = () => Boolean(props.showDeleteMessage) && !deletingMessage() const handleDeleteMessage = async (event: MouseEvent) => {