fix: high performance convo rendering - virtualizing large convo
This commit is contained in:
parent
73d34ea69b
commit
67721a45ee
@ -16,6 +16,7 @@
|
|||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@janhq/core": "link:./core",
|
"@janhq/core": "link:./core",
|
||||||
"@janhq/joi": "link:./joi",
|
"@janhq/joi": "link:./joi",
|
||||||
|
"@tanstack/react-virtual": "^3.10.9",
|
||||||
"autoprefixer": "10.4.16",
|
"autoprefixer": "10.4.16",
|
||||||
"class-variance-authority": "^0.7.0",
|
"class-variance-authority": "^0.7.0",
|
||||||
"framer-motion": "^10.16.4",
|
"framer-motion": "^10.16.4",
|
||||||
|
|||||||
@ -1,12 +1,10 @@
|
|||||||
import { memo, useEffect, useState } from 'react'
|
import { memo, useEffect, useMemo, useRef, useState } from 'react'
|
||||||
|
|
||||||
import { MessageStatus, ThreadMessage } from '@janhq/core'
|
import { ThreadMessage } from '@janhq/core'
|
||||||
|
import { useVirtualizer } from '@tanstack/react-virtual'
|
||||||
|
|
||||||
import { useAtomValue } from 'jotai'
|
import { useAtomValue } from 'jotai'
|
||||||
|
|
||||||
import ErrorMessage from '@/containers/ErrorMessage'
|
|
||||||
import ListContainer from '@/containers/ListContainer'
|
|
||||||
|
|
||||||
import { loadModelErrorAtom } from '@/hooks/useActiveModel'
|
import { loadModelErrorAtom } from '@/hooks/useActiveModel'
|
||||||
|
|
||||||
import ChatItem from '../ChatItem'
|
import ChatItem from '../ChatItem'
|
||||||
@ -21,6 +19,7 @@ const ChatConfigurator = memo(() => {
|
|||||||
const messages = useAtomValue(getCurrentChatMessagesAtom)
|
const messages = useAtomValue(getCurrentChatMessagesAtom)
|
||||||
|
|
||||||
const [current, setCurrent] = useState<ThreadMessage[]>([])
|
const [current, setCurrent] = useState<ThreadMessage[]>([])
|
||||||
|
const loadModelError = useAtomValue(loadModelErrorAtom)
|
||||||
|
|
||||||
const isMessagesIdentificial = (
|
const isMessagesIdentificial = (
|
||||||
arr1: ThreadMessage[],
|
arr1: ThreadMessage[],
|
||||||
@ -32,14 +31,12 @@ const ChatConfigurator = memo(() => {
|
|||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (
|
if (
|
||||||
messages.length !== current.length ||
|
messages?.length !== current?.length ||
|
||||||
!isMessagesIdentificial(messages, current)
|
!isMessagesIdentificial(messages, current)
|
||||||
) {
|
) {
|
||||||
setCurrent(messages)
|
setCurrent(messages)
|
||||||
}
|
}
|
||||||
}, [messages, current])
|
}, [messages, current, loadModelError])
|
||||||
|
|
||||||
const loadModelError = useAtomValue(loadModelErrorAtom)
|
|
||||||
|
|
||||||
if (!messages.length) return <EmptyThread />
|
if (!messages.length) return <EmptyThread />
|
||||||
return (
|
return (
|
||||||
@ -57,21 +54,93 @@ const ChatBody = memo(
|
|||||||
messages: ThreadMessage[]
|
messages: ThreadMessage[]
|
||||||
loadModelError?: string
|
loadModelError?: string
|
||||||
}) => {
|
}) => {
|
||||||
return (
|
// The scrollable element for your list
|
||||||
<ListContainer>
|
const parentRef = useRef(null)
|
||||||
{messages.map((message, index) => (
|
|
||||||
<div key={message.id}>
|
|
||||||
<ChatItem
|
|
||||||
{...message}
|
|
||||||
key={message.id}
|
|
||||||
loadModelError={loadModelError}
|
|
||||||
isCurrentMessage={index === messages.length - 1}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
))}
|
|
||||||
|
|
||||||
{loadModelError && <LoadModelError />}
|
const count = useMemo(
|
||||||
</ListContainer>
|
() => (messages?.length ?? 0) + (loadModelError ? 1 : 0),
|
||||||
|
[messages, loadModelError]
|
||||||
|
)
|
||||||
|
|
||||||
|
// The virtualizer
|
||||||
|
const virtualizer = useVirtualizer({
|
||||||
|
count,
|
||||||
|
getScrollElement: () => parentRef.current,
|
||||||
|
estimateSize: () => 35,
|
||||||
|
overscan: 5,
|
||||||
|
})
|
||||||
|
useEffect(() => {
|
||||||
|
if (count > 0 && messages && virtualizer) {
|
||||||
|
virtualizer.scrollToIndex(count - 1)
|
||||||
|
}
|
||||||
|
}, [count, virtualizer, messages, loadModelError])
|
||||||
|
|
||||||
|
const items = virtualizer.getVirtualItems()
|
||||||
|
virtualizer.shouldAdjustScrollPositionOnItemSizeChange = (
|
||||||
|
item,
|
||||||
|
_,
|
||||||
|
instance
|
||||||
|
) => {
|
||||||
|
return (
|
||||||
|
// item.start < (instance.scrollOffset ?? 0) &&
|
||||||
|
instance.scrollDirection !== 'backward'
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="flex h-full w-full flex-col overflow-x-hidden">
|
||||||
|
<div
|
||||||
|
ref={parentRef}
|
||||||
|
className="List"
|
||||||
|
style={{
|
||||||
|
flex: 1,
|
||||||
|
height: '100%',
|
||||||
|
width: '100%',
|
||||||
|
overflowY: 'auto',
|
||||||
|
overflowX: 'hidden',
|
||||||
|
contain: 'strict',
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
style={{
|
||||||
|
height: virtualizer.getTotalSize(),
|
||||||
|
width: '100%',
|
||||||
|
position: 'relative',
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
style={{
|
||||||
|
position: 'absolute',
|
||||||
|
top: 0,
|
||||||
|
left: 0,
|
||||||
|
width: '100%',
|
||||||
|
transform: `translateY(${items[0]?.start ?? 0}px)`,
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{items.map((virtualRow) => (
|
||||||
|
<div
|
||||||
|
key={virtualRow.key}
|
||||||
|
data-index={virtualRow.index}
|
||||||
|
ref={virtualizer.measureElement}
|
||||||
|
>
|
||||||
|
{loadModelError && virtualRow.index === count - 1 ? (
|
||||||
|
<LoadModelError />
|
||||||
|
) : (
|
||||||
|
<ChatItem
|
||||||
|
{...messages[virtualRow.index]}
|
||||||
|
// key={messages[virtualRow.index]?.id}
|
||||||
|
loadModelError={loadModelError}
|
||||||
|
isCurrentMessage={
|
||||||
|
virtualRow.index === messages?.length - 1
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|||||||
@ -52,7 +52,7 @@ const ChatItem = forwardRef<Ref, Props>((message, ref) => {
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
{status !== MessageStatus.Error && content.length > 0 && (
|
{status !== MessageStatus.Error && content?.length > 0 && (
|
||||||
<div ref={ref} className="relative">
|
<div ref={ref} className="relative">
|
||||||
<SimpleTextMessage {...message} content={content} status={status} />
|
<SimpleTextMessage {...message} content={content} status={status} />
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@ -66,7 +66,7 @@ const LoadModelError = () => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="mt-10">
|
<div className="flex flex-1">
|
||||||
<div className="flex w-full flex-col items-center text-center font-medium">
|
<div className="flex w-full flex-col items-center text-center font-medium">
|
||||||
<p className="w-[90%]">
|
<p className="w-[90%]">
|
||||||
<ErrorMessage />
|
<ErrorMessage />
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user