jan/web-app/src/containers/ScrollToBottom.tsx
2025-10-01 19:21:34 +07:00

77 lines
2.7 KiB
TypeScript

import { useThreadScrolling } from '@/hooks/useThreadScrolling'
import { memo } from 'react'
import { GenerateResponseButton } from './GenerateResponseButton'
import { useMessages } from '@/hooks/useMessages'
import { useShallow } from 'zustand/react/shallow'
import { useAppearance } from '@/hooks/useAppearance'
import { cn } from '@/lib/utils'
import { ArrowDown } from 'lucide-react'
import { useTranslation } from '@/i18n/react-i18next-compat'
import { useAppState } from '@/hooks/useAppState'
import { MessageStatus } from '@janhq/core'
const ScrollToBottom = ({
threadId,
scrollContainerRef,
}: {
threadId: string
scrollContainerRef: React.RefObject<HTMLDivElement | null>
}) => {
const { t } = useTranslation()
const appMainViewBgColor = useAppearance((state) => state.appMainViewBgColor)
const { showScrollToBottomBtn, scrollToBottom } =
useThreadScrolling(threadId, scrollContainerRef)
const { messages } = useMessages(
useShallow((state) => ({
messages: state.messages[threadId],
}))
)
const streamingContent = useAppState((state) => state.streamingContent)
// Check if last message is a partial assistant response and show continue buton (user interrupted)
const isPartialResponse =
messages.length >= 2 &&
messages[messages.length - 1]?.role === 'assistant' &&
messages[messages.length - 1]?.status === MessageStatus.Stopped &&
messages[messages.length - 2]?.role === 'user' &&
!messages[messages.length - 1]?.metadata?.tool_calls
const showGenerateAIResponseBtn =
((messages[messages.length - 1]?.role === 'user' ||
(messages[messages.length - 1]?.metadata &&
'tool_calls' in (messages[messages.length - 1].metadata ?? {})) ||
isPartialResponse) &&
!streamingContent)
return (
<div
className={cn(
'absolute z-0 -top-6 h-8 py-1 flex w-full justify-center pointer-events-none opacity-0 visibility-hidden',
appMainViewBgColor.a === 1
? 'from-main-view/20 bg-gradient-to-b to-main-view backdrop-blur'
: 'bg-transparent',
(showScrollToBottomBtn || showGenerateAIResponseBtn) &&
'visibility-visible opacity-100'
)}
>
{showScrollToBottomBtn && (
<div
className="bg-main-view-fg/10 px-2 border border-main-view-fg/5 flex items-center justify-center rounded-xl gap-x-2 cursor-pointer pointer-events-auto"
onClick={() => {
scrollToBottom(true)
}}
>
<p className="text-xs">{t('scrollToBottom')}</p>
<ArrowDown size={12} />
</div>
)}
{showGenerateAIResponseBtn && (
<GenerateResponseButton threadId={threadId} />
)}
</div>
)
}
export default memo(ScrollToBottom)