🚸 Improve auto scroll behavior

Only trigger when the last element is entirely visible
This commit is contained in:
Baptiste Arnaud
2024-04-18 12:11:28 +02:00
parent 3ca1a2f0d6
commit 5aad10e937
13 changed files with 44 additions and 40 deletions

View File

@@ -22,7 +22,7 @@ type Props = Pick<ContinueChatResponse, 'messages' | 'input'> & {
streamingMessageId: ChatChunkType['streamingMessageId']
isTransitionDisabled?: boolean
onNewBubbleDisplayed: (blockId: string) => Promise<void>
onScrollToBottom: (top?: number) => void
onScrollToBottom: (ref?: HTMLDivElement, offset?: number) => void
onSubmit: (input?: string) => void
onSkip: () => void
onAllBubblesDisplayed: () => void
@@ -33,19 +33,17 @@ export const ChatChunk = (props: Props) => {
const [displayedMessageIndex, setDisplayedMessageIndex] = createSignal(
props.isTransitionDisabled ? props.messages.length : 0
)
const [lastBubbleOffsetTop, setLastBubbleOffsetTop] = createSignal<number>()
const [lastBubble, setLastBubble] = createSignal<HTMLDivElement>()
onMount(() => {
if (props.streamingMessageId) return
if (props.messages.length === 0) {
props.onAllBubblesDisplayed()
}
props.onScrollToBottom(
inputRef?.offsetTop ? inputRef?.offsetTop - 50 : undefined
)
props.onScrollToBottom(inputRef, 50)
})
const displayNextMessage = async (bubbleOffsetTop?: number) => {
const displayNextMessage = async (bubbleRef?: HTMLDivElement) => {
if (
(props.settings.typingEmulation?.delayBetweenBubbles ??
defaultSettings.typingEmulation.delayBetweenBubbles) > 0 &&
@@ -66,9 +64,9 @@ export const ChatChunk = (props: Props) => {
? displayedMessageIndex()
: displayedMessageIndex() + 1
)
props.onScrollToBottom(bubbleOffsetTop)
props.onScrollToBottom(bubbleRef)
if (displayedMessageIndex() === props.messages.length) {
setLastBubbleOffsetTop(bubbleOffsetTop)
setLastBubble(bubbleRef)
props.onAllBubblesDisplayed()
}
}
@@ -143,7 +141,7 @@ export const ChatChunk = (props: Props) => {
defaultSettings.general.isInputPrefillEnabled
}
hasError={props.hasError}
onTransitionEnd={() => props.onScrollToBottom(lastBubbleOffsetTop())}
onTransitionEnd={() => props.onScrollToBottom(lastBubble())}
onSubmit={props.onSubmit}
onSkip={props.onSkip}
/>

View File

@@ -224,14 +224,27 @@ export const ConversationContainer = (props: Props) => {
])
}
const autoScrollToBottom = (offsetTop?: number) => {
const chunks = chatChunks()
const lastChunkWasStreaming =
chunks.length >= 2 && chunks[chunks.length - 2].streamingMessageId
if (lastChunkWasStreaming) return
setTimeout(() => {
chatContainer?.scrollTo(0, offsetTop ?? chatContainer.scrollHeight)
}, 50)
const autoScrollToBottom = (lastElement?: HTMLDivElement, offset = 0) => {
if (!chatContainer) return
if (!lastElement) {
setTimeout(() => {
chatContainer?.scrollTo(0, chatContainer.scrollHeight)
}, 50)
return
}
const lastElementRect = lastElement.getBoundingClientRect()
const containerRect = chatContainer.getBoundingClientRect()
const lastElementTopRelative =
lastElementRect.top - containerRect.top + chatContainer.scrollTop
const isLastElementInVisibleArea =
lastElementTopRelative < chatContainer.scrollTop + containerRect.height &&
lastElementTopRelative + lastElementRect.height > chatContainer.scrollTop
if (isLastElementInVisibleArea)
setTimeout(() => {
chatContainer?.scrollTo(0, lastElement.offsetTop - offset)
}, 50)
}
const handleAllBubblesDisplayed = async () => {

View File

@@ -21,7 +21,7 @@ type Props = {
message: ChatMessage
typingEmulation: Settings['typingEmulation']
isTypingSkipped: boolean
onTransitionEnd?: (offsetTop?: number) => void
onTransitionEnd?: (ref?: HTMLDivElement) => void
onCompleted: (reply?: string) => void
}