Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Refactor: useScroll hook #376

Merged
merged 11 commits into from
Feb 17, 2025
52 changes: 31 additions & 21 deletions apps/masterbots.ai/components/routes/chat/chat.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -35,40 +35,40 @@
import { ChatList } from '@/components/routes/chat/chat-list'
import { ChatPanel } from '@/components/routes/chat/chat-panel'
import { ChatScrollAnchor } from '@/components/routes/chat/chat-scroll-anchor'
import { useAtBottom } from '@/lib/hooks/use-at-bottom'
import { useMBChat } from '@/lib/hooks/use-mb-chat'
import { useSidebar } from '@/lib/hooks/use-sidebar'
import { useThread } from '@/lib/hooks/use-thread'
import { useThreadVisibility } from '@/lib/hooks/use-thread-visibility'
import { cn, scrollToBottomOfElement } from '@/lib/utils'
import { cn } from '@/lib/utils'
import type { ChatProps } from '@/types/types'
import type { Message as UiUtilsMessage } from '@ai-sdk/ui-utils'
import { useScroll } from 'framer-motion'
import type { Chatbot } from 'mb-genql'
import { useParams, usePathname } from 'next/navigation'
import React, { useEffect } from 'react'
import { useScroll } from '@/lib/hooks/use-scroll'

export function Chat({
chatbot: chatbotProps,
className,
chatPanelClassName,
isPopup,
scrollToBottom: scrollToBottomOfPopup,
scrollToBottomOfPopup,
isAtBottom: isAtBottomOfPopup,
}: ChatProps) {
const {
activeThread,
loadingState,
isOpenPopup,
sectionRef,
isAtBottom: isAtBottomOfSection,
isAtBottomOfSection,
setActiveThread,
setIsOpenPopup,
setLoadingState,
} = useThread()
const { activeChatbot } = useSidebar()
const { isContinuousThread } = useThreadVisibility()
const containerRef = React.useRef<HTMLDivElement>()
const containerRef = React.useRef<HTMLDivElement>(null)
const threadRef = React.useRef<HTMLDivElement>(null)
const params = useParams<{ chatbot: string; threadId: string }>()
const chatbot = chatbotProps || activeThread?.chatbot || (activeChatbot as Chatbot)
const [
Expand All @@ -78,31 +78,35 @@ export function Chat({
const pathname = usePathname()
const prevPathname = React.useRef(pathname)

const { scrollY } = useScroll({
container: containerRef as React.RefObject<HTMLElement>,
})

const { isAtBottom } = useAtBottom({
ref: containerRef,
scrollY,
const { isNearBottom, smoothScrollToBottom, scrollToTop } = useScroll({
containerRef,
threadRef,
isNewContent: isLoading,
hasMore: false, //* true for implementing infinite scroll
isLast: true,
loading: isLoading,
loadMore: () => {}, //* Implement if adding infinite scroll
})

// ? safer way to debounce scroll to bottom
// biome-ignore lint/suspicious/noExplicitAny: <explanation>
let timeoutId: any
const debounceScrollToBottom = (element: HTMLElement | undefined) => {
clearTimeout(timeoutId)
timeoutId = setTimeout(() => {
scrollToBottomOfElement(element)
if (element) {
smoothScrollToBottom()
}
clearTimeout(timeoutId)
}, 150) //? Adjustable delay as necessary
}, 150)
}

const scrollToBottom = () => {
if ((params.threadId && containerRef.current) || (!params.threadId && sectionRef.current)) {
let element: any
let element: HTMLElement | undefined = undefined
if (sectionRef.current) {
element = sectionRef.current
} else {
} else if (containerRef.current) {
element = containerRef.current
}
debounceScrollToBottom(element)
Expand Down Expand Up @@ -156,14 +160,16 @@ export function Chat({
<>
{params.threadId && (
<div
ref={containerRef as React.Ref<HTMLDivElement>}
ref={containerRef}
className={cn('pb-[200px] pt-4 md:pt-10 h-full overflow-auto', className)}
>
<ChatList />
<div ref={threadRef}>
<ChatList />
</div>
<ChatScrollAnchor
isAtBottom={
params.threadId
? isAtBottom
? isNearBottom
: isPopup
? Boolean(isAtBottomOfPopup)
: isAtBottomOfSection
Expand All @@ -189,7 +195,11 @@ export function Chat({
placeholder={chatbot ? chatSearchMessage(isNewChat, isContinuousThread, allMessages) : ''}
showReload={!isNewChat}
isAtBottom={
params.threadId ? isAtBottom : isPopup ? Boolean(isAtBottomOfPopup) : isAtBottomOfSection
params.threadId
? isNearBottom
: isPopup
? Boolean(isAtBottomOfPopup)
: isAtBottomOfSection
}
/>
</>
Expand Down
102 changes: 52 additions & 50 deletions apps/masterbots.ai/components/routes/thread/thread-popup.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,51 +6,51 @@ import { ChatList } from '@/components/routes/chat/chat-list'
import { Button } from '@/components/ui/button'
import { IconClose } from '@/components/ui/icons'
import { Skeleton } from '@/components/ui/skeleton'
import { useAtBottom } from '@/lib/hooks/use-at-bottom'
import { useMBChat } from '@/lib/hooks/use-mb-chat'
import { useSidebar } from '@/lib/hooks/use-sidebar'
import { useThread } from '@/lib/hooks/use-thread'
import { cn, getRouteType, scrollToBottomOfElement } from '@/lib/utils'
import { cn, getRouteType } from '@/lib/utils'
import { getMessages } from '@/services/hasura'
import type { Message as AiMessage } from 'ai'
import { useScroll } from 'framer-motion'
import type { Chatbot, Message } from 'mb-genql'
import { usePathname } from 'next/navigation'
import { useEffect, useRef, useState } from 'react'
import { useScroll } from '@/lib/hooks/use-scroll'

export function ThreadPopup({ className }: { className?: string }) {
const { activeChatbot } = useSidebar()
const { isOpenPopup, activeThread } = useThread()
const [{ allMessages, isLoading }, { sendMessageFromResponse }] = useMBChat()
const [browseMessages, setBrowseMessages] = useState<Message[]>([])
const popupContentRef = useRef<HTMLDivElement>()
const popupContentRef = useRef<HTMLDivElement>(null)
const threadRef = useRef<HTMLDivElement>(null)
const pathname = usePathname()

const { scrollY } = useScroll({
container: popupContentRef as React.RefObject<HTMLElement>,
})

const { isAtBottom } = useAtBottom({
ref: popupContentRef,
scrollY,
const { isNearBottom, smoothScrollToBottom } = useScroll({
containerRef: popupContentRef,
threadRef,
isNewContent: isLoading,
hasMore: false,
isLast: true,
loading: isLoading,
loadMore: () => {},
})

const scrollToBottom = () => {
if (popupContentRef.current) {
const element = popupContentRef.current
scrollToBottomOfElement(element)
smoothScrollToBottom()
}
}

// biome-ignore lint/correctness/useExhaustiveDependencies: <explanation>
// Update effect to use smoothScrollToBottom from custom hook
useEffect(() => {
if (isLoading && isOpenPopup) {
const timeout = setTimeout(() => {
scrollToBottom()
smoothScrollToBottom()
clearTimeout(timeout)
}, 150)
}
}, [isLoading, isOpenPopup])
}, [isLoading, isOpenPopup, smoothScrollToBottom])

// Fetch browse messages when activeThread changes
useEffect(() => {
Expand Down Expand Up @@ -95,46 +95,48 @@ export function ThreadPopup({ className }: { className?: string }) {
/>

<div
ref={popupContentRef}
className={cn(
'flex flex-col dark:bg-[#18181b] bg-white grow rounded-b-[8px] scrollbar h-full',
isBrowseView ? 'pb-2 md:pb-4' : 'pb-[120px] md:pb-[180px]',
isBrowseView ? '' :'max-h-[calc(100%-240px)] md:max-h-[calc(100%-220px)]',
isBrowseView ? '' : 'max-h-[calc(100%-240px)] md:max-h-[calc(100%-220px)]',
className,
)}
ref={popupContentRef as React.Ref<HTMLDivElement>}
>
{isBrowseView ? (
// Browse view
<div className="px-8 py-4">
<BrowseChatMessageList
chatbot={activeThread?.chatbot}
user={activeThread?.user || undefined}
messages={browseMessages}
threadId={activeThread?.threadId}
/>
</div>
) : (
// Chat view
<>
<ChatList
isThread={false}
messages={allMessages}
sendMessageFn={sendMessageFromResponse}
chatbot={activeThread?.chatbot || (activeChatbot as Chatbot)}
chatContentClass="!border-x-gray-300 !px-[16px] !mx-0 max-h-[none] dark:!border-x-mirage"
className="max-w-full !px-[32px] !mx-0"
chatArrowClass="!right-0 !mr-0"
chatTitleClass="!px-[11px]"
/>

<Chat
isPopup
chatPanelClassName="!pl-0 rounded-b-[8px] overflow-hidden !absolute"
scrollToBottom={scrollToBottom}
isAtBottom={isAtBottom}
/>
</>
)}
<div ref={threadRef}>
{isBrowseView ? (
// Browse view
<div className="px-8 py-4">
<BrowseChatMessageList
chatbot={activeThread?.chatbot}
user={activeThread?.user || undefined}
messages={browseMessages}
threadId={activeThread?.threadId}
/>
</div>
) : (
// Chat view
<>
<ChatList
isThread={false}
messages={allMessages}
sendMessageFn={sendMessageFromResponse}
chatbot={activeThread?.chatbot || (activeChatbot as Chatbot)}
chatContentClass="!border-x-gray-300 !px-[16px] !mx-0 max-h-[none] dark:!border-x-mirage"
className="max-w-full !px-[32px] !mx-0"
chatArrowClass="!right-0 !mr-0"
chatTitleClass="!px-[11px]"
/>

<Chat
isPopup
chatPanelClassName="!pl-0 rounded-b-[8px] overflow-hidden !absolute"
scrollToBottom={scrollToBottom}
isAtBottom={isNearBottom}
/>
</>
)}
</div>
</div>
</div>
</div>
Expand Down
1 change: 1 addition & 0 deletions apps/masterbots.ai/lib/context/thread-context.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@

Loading