Add chat scroll-to-bottom button

This commit is contained in:
SiYue-ZO 2026-04-19 15:02:54 +08:00 committed by SiYue
parent 52ec17eee5
commit cf5563c3ad
2 changed files with 49 additions and 7 deletions

View file

@ -1,4 +1,4 @@
import { IconPlus } from "@tabler/icons-react"
import { IconArrowDown, IconPlus } from "@tabler/icons-react"
import { useAtom } from "jotai"
import { type ChangeEvent, useRef, useState } from "react"
import { useTranslation } from "react-i18next"
@ -178,8 +178,14 @@ export function ChatPage() {
const hasStreamingMessage =
streamingEnabled &&
messages.some((message) => message.role === "assistant" && message.streaming)
const { scrollRef, hasScrolled, handleScroll, handleManualScrollIntent } =
useChatAutoScroll({
const {
scrollRef,
isAtBottom,
hasScrolled,
handleScroll,
handleManualScrollIntent,
scrollToBottom,
} = useChatAutoScroll({
deps: [messages, isTyping],
streaming: hasStreamingMessage,
})
@ -259,7 +265,7 @@ export function ChatPage() {
canInput && (Boolean(input.trim()) || attachments.length > 0)
return (
<div className="bg-background/95 flex h-full flex-col">
<div className="bg-background/95 relative flex h-full flex-col">
<PageHeader
title={t("navigation.chat")}
className={`transition-shadow ${
@ -374,6 +380,18 @@ export function ChatPage() {
onChange={handleImageSelection}
/>
{!isAtBottom && messages.length > 0 && (
<Button
type="button"
size="icon"
className="absolute right-6 bottom-28 z-20 rounded-full shadow-lg md:right-8"
aria-label="Scroll to bottom"
onClick={() => scrollToBottom()}
>
<IconArrowDown className="size-4" />
</Button>
)}
<ChatComposer
input={input}
attachments={attachments}

View file

@ -137,6 +137,29 @@ export function useChatAutoScroll({
cancelAnimation()
}, [cancelAnimation])
const scrollToBottom = useCallback(
(options?: { immediate?: boolean }) => {
const element = scrollRef.current
if (!element) {
return
}
stickyRef.current = true
setIsAtBottom(true)
targetScrollTopRef.current = getBottomScrollTop(element)
if (options?.immediate) {
cancelAnimation()
element.scrollTop = targetScrollTopRef.current
syncStateFromElement(element, { programmatic: true })
return
}
animateToBottom()
},
[animateToBottom, cancelAnimation, syncStateFromElement],
)
useLayoutEffect(() => {
const element = scrollRef.current
if (!element) {
@ -164,5 +187,6 @@ export function useChatAutoScroll({
hasScrolled,
handleScroll,
handleManualScrollIntent,
scrollToBottom,
}
}