feat: implement scroll handling with new custom hook

- Added a new hook, useAttachScrollBeyond, to manage scroll behavior and state across components.
- Updated ChatInterface and WelcomeScreen to utilize the new hook for handling scroll events, improving the user experience by ensuring consistent scroll threshold management.
- Refactored AIChatRoot to maintain clean code structure.

These changes enhance the chat interface by providing a centralized way to manage scroll interactions, improving overall usability.

Signed-off-by: Innei <tukon479@gmail.com>
This commit is contained in:
Innei 2025-09-08 22:32:52 +08:00
parent 2a2085a0f1
commit 3776d82d97
No known key found for this signature in database
GPG Key ID: 0F62D33977F021F7
4 changed files with 31 additions and 26 deletions

View File

@ -36,6 +36,7 @@ const AIChatRootInner: FC<AIChatRootProps> = ({ children, chatId: externalChatId
const inputRef = useRef<HTMLTextAreaElement>(null!)
const refsContext = useMemo<AIPanelRefs>(() => ({ panelRef, inputRef }), [panelRef, inputRef])
useAIShortcut()
if (!currentChatId) {
return (
<div className="bg-background flex size-full items-center justify-center">

View File

@ -4,12 +4,11 @@ import { tracker } from "@follow/tracker"
import { clsx, cn, nextFrame } from "@follow/utils"
import type { BizUIMessage } from "@folo-services/ai-tools"
import { ErrorBoundary } from "@sentry/react"
import { useSetAtom } from "jotai"
import type { EditorState, LexicalEditor } from "lexical"
import { AnimatePresence } from "motion/react"
import { nanoid } from "nanoid"
import type { FC, Ref } from "react"
import { Suspense, useCallback, useEffect, useRef, useState } from "react"
import { Suspense, useEffect, useRef, useState } from "react"
import { useEventCallback } from "usehooks-ts"
import { useAISettingKey } from "~/atoms/settings/ai"
@ -32,8 +31,7 @@ import {
useMessages,
} from "~/modules/ai-chat/store/hooks"
import { SCROLLED_BEYOND_THRESHOLD } from "../../constants"
import { useAIRootState } from "../../store/AIChatContext"
import { useAttachScrollBeyond } from "../../hooks/useAttachScrollBeyond"
import type { AIChatContextBlock } from "../../store/types"
import { convertLexicalToMarkdown } from "../../utils/lexical-markdown"
import { GlobalFileDropZone } from "../file/GlobalFileDropZone"
@ -228,15 +226,7 @@ const ChatInterfaceContent = ({ centerInputOnEmpty }: ChatInterfaceProps) => {
const shouldShowScrollToBottom = hasMessages && !isAtBottom && !isLoadingHistory
const { isScrolledBeyondThreshold } = useAIRootState()
const setIsScrolledBeyondThreshold = useSetAtom(isScrolledBeyondThreshold)
const handleScroll = useCallback(
(event: React.UIEvent<HTMLDivElement>) => {
const { scrollTop } = event.currentTarget
setIsScrolledBeyondThreshold(scrollTop > SCROLLED_BEYOND_THRESHOLD)
},
[setIsScrolledBeyondThreshold],
)
const { handleScroll } = useAttachScrollBeyond()
return (
<GlobalFileDropZone className="@container flex size-full flex-col">
<div className="flex min-h-0 flex-1 flex-col" ref={scrollContainerParentRef}>

View File

@ -1,18 +1,15 @@
import { Folo } from "@follow/components/icons/folo.js"
import { ScrollArea } from "@follow/components/ui/scroll-area/ScrollArea.js"
import { clsx } from "@follow/utils"
import { useSetAtom } from "jotai"
import type { EditorState, LexicalEditor } from "lexical"
import { AnimatePresence, m } from "motion/react"
import { useCallback } from "react"
import { useTranslation } from "react-i18next"
import { useAISettingValue } from "~/atoms/settings/ai"
import { AISpline } from "~/modules/ai-chat/components/3d-models/AISpline"
import { SCROLLED_BEYOND_THRESHOLD } from "../../constants"
import { useAttachScrollBeyond } from "../../hooks/useAttachScrollBeyond"
import { useMainEntryId } from "../../hooks/useMainEntryId"
import { useAIRootState } from "../../store/AIChatContext"
import { DefaultWelcomeContent, EntrySummaryCard } from "../welcome"
interface WelcomeScreenProps {
@ -28,15 +25,7 @@ export const WelcomeScreen = ({ onSend, centerInputOnEmpty }: WelcomeScreenProps
const hasEntryContext = !!mainEntryId
const enabledShortcuts = aiSettings.shortcuts?.filter((shortcut) => shortcut.enabled) || []
const { isScrolledBeyondThreshold } = useAIRootState()
const setIsScrolledBeyondThreshold = useSetAtom(isScrolledBeyondThreshold)
const handleScroll = useCallback(
(event: React.UIEvent<HTMLDivElement>) => {
const { scrollTop } = event.currentTarget
setIsScrolledBeyondThreshold(scrollTop > SCROLLED_BEYOND_THRESHOLD)
},
[setIsScrolledBeyondThreshold],
)
const { handleScroll } = useAttachScrollBeyond()
return (
<ScrollArea
rootClassName="flex min-h-0 flex-1"

View File

@ -0,0 +1,25 @@
import { useSetAtom } from "jotai"
import { useCallback, useEffect } from "react"
import { SCROLLED_BEYOND_THRESHOLD } from "../constants"
import { useAIRootState } from "../store/AIChatContext"
import { useCurrentChatId } from "../store/hooks"
export const useAttachScrollBeyond = () => {
const { isScrolledBeyondThreshold } = useAIRootState()
const setIsScrolledBeyondThreshold = useSetAtom(isScrolledBeyondThreshold)
const handleScroll = useCallback(
(event: React.UIEvent<HTMLDivElement>) => {
const { scrollTop } = event.currentTarget
setIsScrolledBeyondThreshold(scrollTop > SCROLLED_BEYOND_THRESHOLD)
},
[setIsScrolledBeyondThreshold],
)
const currentChatId = useCurrentChatId()
useEffect(() => {
if (currentChatId) {
setIsScrolledBeyondThreshold(false)
}
}, [currentChatId, setIsScrolledBeyondThreshold])
return { handleScroll }
}