From 682771cd4ee452e9d915526da768efab1f9f59d4 Mon Sep 17 00:00:00 2001 From: Innei Date: Mon, 8 Sep 2025 21:07:30 +0800 Subject: [PATCH] feat: implement scroll threshold state management - Introduced AIRootStateContext to manage the scroll threshold state across components. - Updated AIChatRoot, ChatHeader, ChatInterface, and WelcomeScreen to utilize the new context for scroll behavior. - Added SCROLLED_BEYOND_THRESHOLD constant to define the scroll limit for triggering state changes. These changes enhance the chat interface by allowing components to react to scroll position, improving user experience and interaction dynamics. Signed-off-by: Innei --- .../ai-chat/components/layouts/AIChatRoot.tsx | 18 ++++++++++++++++-- .../ai-chat/components/layouts/ChatHeader.tsx | 13 ++++++++++++- .../components/layouts/ChatInterface.tsx | 15 ++++++++++++++- .../components/layouts/WelcomeScreen.tsx | 14 ++++++++++++++ .../src/modules/ai-chat/constants/index.ts | 1 + .../src/modules/ai-chat/store/AIChatContext.ts | 15 +++++++++++++++ 6 files changed, 72 insertions(+), 4 deletions(-) create mode 100644 apps/desktop/layer/renderer/src/modules/ai-chat/constants/index.ts diff --git a/apps/desktop/layer/renderer/src/modules/ai-chat/components/layouts/AIChatRoot.tsx b/apps/desktop/layer/renderer/src/modules/ai-chat/components/layouts/AIChatRoot.tsx index 636c8c438..7a8a55b84 100644 --- a/apps/desktop/layer/renderer/src/modules/ai-chat/components/layouts/AIChatRoot.tsx +++ b/apps/desktop/layer/renderer/src/modules/ai-chat/components/layouts/AIChatRoot.tsx @@ -1,3 +1,4 @@ +import { atom } from "jotai" import type { FC, PropsWithChildren } from "react" import { useEffect, useMemo, useRef } from "react" @@ -5,7 +6,11 @@ import { Focusable } from "~/components/common/Focusable" import { HotkeyScope } from "~/constants" import type { AIPanelRefs } from "../../store/AIChatContext" -import { AIChatStoreContext, AIPanelRefsContext } from "../../store/AIChatContext" +import { + AIChatStoreContext, + AIPanelRefsContext, + AIRootStateContext, +} from "../../store/AIChatContext" import { ChatSliceActions } from "../../store/chat-core/chat-actions" import { useChatActions, useCurrentChatId } from "../../store/hooks" import { createAIChatStore } from "../../store/store" @@ -58,7 +63,16 @@ export const AIChatRoot: FC = ({ const Element = ( - {children} + ({ + isScrolledBeyondThreshold: atom(false), + }), + [], + )} + > + {children} + ) diff --git a/apps/desktop/layer/renderer/src/modules/ai-chat/components/layouts/ChatHeader.tsx b/apps/desktop/layer/renderer/src/modules/ai-chat/components/layouts/ChatHeader.tsx index e5bc2a3da..c881affca 100644 --- a/apps/desktop/layer/renderer/src/modules/ai-chat/components/layouts/ChatHeader.tsx +++ b/apps/desktop/layer/renderer/src/modules/ai-chat/components/layouts/ChatHeader.tsx @@ -1,11 +1,13 @@ import { ActionButton } from "@follow/components/ui/button/index.js" import { cn } from "@follow/utils" +import { useAtomValue } from "jotai" import type { ReactNode } from "react" import { useCallback } from "react" import { AIChatPanelStyle, setAIPanelVisibility, useAIChatPanelStyle } from "~/atoms/settings/ai" import { useBlockActions, useChatActions, useCurrentTitle } from "~/modules/ai-chat/store/hooks" +import { useAIRootState } from "../../store/AIChatContext" import { ChatMoreDropdown } from "./ChatMoreDropdown" import { EditableTitle } from "./EditableTitle" import { TaskReportDropdown } from "./TaskReportDropdown" @@ -43,8 +45,17 @@ const ChatHeaderLayout = ({ const isFloating = useAIChatPanelStyle() === AIChatPanelStyle.Floating + const { isScrolledBeyondThreshold } = useAIRootState() + const isScrolledBeyondThresholdValue = useAtomValue(isScrolledBeyondThreshold) + return ( -
+
{isFloating && (
{ const shouldShowScrollToBottom = hasMessages && !isAtBottom && !isLoadingHistory + const { isScrolledBeyondThreshold } = useAIRootState() + const setIsScrolledBeyondThreshold = useSetAtom(isScrolledBeyondThreshold) + const handleScroll = useCallback( + (event: React.UIEvent) => { + const { scrollTop } = event.currentTarget + setIsScrolledBeyondThreshold(scrollTop > SCROLLED_BEYOND_THRESHOLD) + }, + [setIsScrolledBeyondThreshold], + ) return (
@@ -234,6 +246,7 @@ const ChatInterfaceContent = ({ centerInputOnEmpty }: ChatInterfaceProps) => { ) : ( <> shortcut.enabled) || [] + const { isScrolledBeyondThreshold } = useAIRootState() + const setIsScrolledBeyondThreshold = useSetAtom(isScrolledBeyondThreshold) + const handleScroll = useCallback( + (event: React.UIEvent) => { + const { scrollTop } = event.currentTarget + setIsScrolledBeyondThreshold(scrollTop > SCROLLED_BEYOND_THRESHOLD) + }, + [setIsScrolledBeyondThreshold], + ) return (
{/* Header Section - Always Present */} diff --git a/apps/desktop/layer/renderer/src/modules/ai-chat/constants/index.ts b/apps/desktop/layer/renderer/src/modules/ai-chat/constants/index.ts new file mode 100644 index 000000000..e3b6e31f7 --- /dev/null +++ b/apps/desktop/layer/renderer/src/modules/ai-chat/constants/index.ts @@ -0,0 +1 @@ +export const SCROLLED_BEYOND_THRESHOLD = 100 diff --git a/apps/desktop/layer/renderer/src/modules/ai-chat/store/AIChatContext.ts b/apps/desktop/layer/renderer/src/modules/ai-chat/store/AIChatContext.ts index 39e51d9f3..03181c769 100644 --- a/apps/desktop/layer/renderer/src/modules/ai-chat/store/AIChatContext.ts +++ b/apps/desktop/layer/renderer/src/modules/ai-chat/store/AIChatContext.ts @@ -1,3 +1,4 @@ +import type { PrimitiveAtom } from "jotai" import { createContext, use } from "react" import type { StoreApi } from "zustand" import type { UseBoundStoreWithEqualityFn } from "zustand/traditional" @@ -22,3 +23,17 @@ export const useAIChatStore = () => { } return store } + +export type AIRootStateContext = { + isScrolledBeyondThreshold: PrimitiveAtom +} + +export const AIRootStateContext = createContext(null!) + +export const useAIRootState = () => { + const context = use(AIRootStateContext) + if (!context) { + throw new Error("useAIRootState must be used within a AIRootStateContext") + } + return context +}