Skip to content

Commit

Permalink
feat: optimize topics list performance with virtual list
Browse files Browse the repository at this point in the history
  • Loading branch information
happyZYM authored and kangfenmao committed Jan 27, 2025
1 parent 790caae commit 5f8f640
Show file tree
Hide file tree
Showing 3 changed files with 134 additions and 12 deletions.
2 changes: 2 additions & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -62,6 +62,7 @@
"@llm-tools/embedjs-loader-xml": "^0.1.25",
"@llm-tools/embedjs-openai": "^0.1.25",
"@types/react-infinite-scroll-component": "^5.0.0",
"@types/react-window": "^1.8.8",
"adm-zip": "^0.5.16",
"apache-arrow": "^18.1.0",
"docx": "^9.0.2",
Expand All @@ -73,6 +74,7 @@
"html2canvas": "^1.4.1",
"markdown-it": "^14.1.0",
"officeparser": "^4.1.1",
"react-window": "^1.8.11",
"tokenx": "^0.4.1",
"webdav": "4.11.4"
},
Expand Down
104 changes: 92 additions & 12 deletions src/renderer/src/pages/home/Tabs/TopicsTab.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -22,9 +22,11 @@ import { exportTopicAsMarkdown, topicToMarkdown } from '@renderer/utils/export'
import { Dropdown, MenuProps } from 'antd'
import dayjs from 'dayjs'
import { findIndex } from 'lodash'
import { FC, useCallback } from 'react'
import { FC, useCallback, useState } from 'react'
import { useTranslation } from 'react-i18next'
import styled from 'styled-components'
import { FixedSizeList as List } from 'react-window'
import { DragDropContext, Draggable, Droppable } from '@hello-pangea/dnd'

interface Props {
assistant: Assistant
Expand All @@ -37,6 +39,7 @@ const Topics: FC<Props> = ({ assistant: _assistant, activeTopic, setActiveTopic
const { assistant, removeTopic, moveTopic, updateTopic, updateTopics } = useAssistant(_assistant.id)
const { t } = useTranslation()
const { showTopicTime, topicPosition } = useSettings()
const [isDragging, setIsDragging] = useState(false)

const borderRadius = showTopicTime ? 12 : 'var(--list-item-border-radius)'

Expand Down Expand Up @@ -176,13 +179,57 @@ const Topics: FC<Props> = ({ assistant: _assistant, activeTopic, setActiveTopic
[assistant, assistants, onClearMessages, onDeleteTopic, onMoveTopic, t, updateTopic]
)

return (
<Container right={topicPosition === 'right'} className="topics-tab">
<DragableList list={assistant.topics} onUpdate={updateTopics}>
{(topic) => {
const isActive = topic.id === activeTopic?.id
return (
<Dropdown menu={{ items: getTopicMenuItems(topic) }} trigger={['contextMenu']} key={topic.id}>
const onDragEnd = (result: any) => {
setIsDragging(false)
if (!result.destination) return

const newTopics = [...assistant.topics]
const [removed] = newTopics.splice(result.source.index, 1)
newTopics.splice(result.destination.index, 0, removed)
updateTopics(newTopics)
}

// 创建一个内部组件来处理虚拟列表的渲染
const VirtualList = ({ children, ...props }: any) => {
const outerRef = useCallback((node: any) => {
if (node !== null) {
// 将 Droppable 的 ref 传递给外部容器
props.provided.innerRef(node)
}
}, [props.provided])

return (
<div {...props.provided.droppableProps} ref={outerRef} style={{ height: '100%' }}>
<List
height={window.innerHeight - 100}
width="100%"
itemCount={assistant.topics.length}
itemSize={45}
outerElementType={(props) => <div {...props} style={{ ...props.style, overflow: 'auto' }} />}>
{children}
</List>
{props.provided.placeholder}
</div>
)
}

const renderRow = ({ index, style }: { index: number; style: React.CSSProperties }) => {
const topic = assistant.topics[index]
const isActive = topic.id === activeTopic?.id

return (
<Draggable draggableId={topic.id} index={index} key={topic.id}>
{(provided) => (
<div
ref={provided.innerRef}
{...provided.draggableProps}
{...provided.dragHandleProps}
style={{
...style,
...provided.draggableProps.style,
padding: '0 4px'
}}>
<Dropdown menu={{ items: getTopicMenuItems(topic) }} trigger={['contextMenu']}>
<TopicListItem
className={isActive ? 'active' : ''}
onClick={() => onSwitchTopic(topic)}
Expand All @@ -206,10 +253,43 @@ const Topics: FC<Props> = ({ assistant: _assistant, activeTopic, setActiveTopic
)}
</TopicListItem>
</Dropdown>
)
}}
</DragableList>
<div style={{ minHeight: '10px' }}></div>
</div>
)}
</Draggable>
)
}

return (
<Container right={topicPosition === 'right'} className="topics-tab" style={{ overflow: 'hidden' }}>
<DragDropContext
onDragStart={() => setIsDragging(true)}
onDragEnd={onDragEnd}>
<Droppable
droppableId="topics-list"
mode="virtual"
renderClone={(provided, snapshot, rubric) => {
const topic = assistant.topics[rubric.source.index]
return (
<div
ref={provided.innerRef}
{...provided.draggableProps}
{...provided.dragHandleProps}
style={{
...provided.draggableProps.style,
padding: '0 4px'
}}>
<TopicListItem style={{ borderRadius }}>
<TopicName>{topic.name.replace('`', '')}</TopicName>
{showTopicTime && (
<TopicTime>{dayjs(topic.createdAt).format('MM/DD HH:mm')}</TopicTime>
)}
</TopicListItem>
</div>
)
}}>
{(provided) => <VirtualList provided={provided}>{renderRow}</VirtualList>}
</Droppable>
</DragDropContext>
</Container>
)
}
Expand Down
40 changes: 40 additions & 0 deletions yarn.lock
Original file line number Diff line number Diff line change
Expand Up @@ -309,6 +309,15 @@ __metadata:
languageName: node
linkType: hard

"@babel/runtime@npm:^7.0.0":
version: 7.26.7
resolution: "@babel/runtime@npm:7.26.7"
dependencies:
regenerator-runtime: "npm:^0.14.0"
checksum: 10c0/60199c049f90e5e41c687687430052a370aca60bac7859ff4ee761c5c1739b8ba1604d391d01588c22dc0e93828cbadb8ada742578ad1b1df240746bce98729a
languageName: node
linkType: hard

"@babel/runtime@npm:^7.10.1, @babel/runtime@npm:^7.10.4, @babel/runtime@npm:^7.11.1, @babel/runtime@npm:^7.11.2, @babel/runtime@npm:^7.12.1, @babel/runtime@npm:^7.16.7, @babel/runtime@npm:^7.18.0, @babel/runtime@npm:^7.18.3, @babel/runtime@npm:^7.20.0, @babel/runtime@npm:^7.20.7, @babel/runtime@npm:^7.21.0, @babel/runtime@npm:^7.22.5, @babel/runtime@npm:^7.23.2, @babel/runtime@npm:^7.23.6, @babel/runtime@npm:^7.23.9, @babel/runtime@npm:^7.24.1, @babel/runtime@npm:^7.24.4, @babel/runtime@npm:^7.24.7, @babel/runtime@npm:^7.24.8, @babel/runtime@npm:^7.25.7, @babel/runtime@npm:^7.26.0, @babel/runtime@npm:^7.7.2, @babel/runtime@npm:^7.9.2":
version: 7.26.0
resolution: "@babel/runtime@npm:7.26.0"
Expand Down Expand Up @@ -2705,6 +2714,15 @@ __metadata:
languageName: node
linkType: hard

"@types/react-window@npm:^1.8.8":
version: 1.8.8
resolution: "@types/react-window@npm:1.8.8"
dependencies:
"@types/react": "npm:*"
checksum: 10c0/2170a3957752603e8b994840c5d31b72ddf94c427c0f42b0175b343cc54f50fe66161d8871e11786ec7a59906bd33861945579a3a8f745455a3744268ec1069f
languageName: node
linkType: hard

"@types/react@npm:*":
version: 19.0.3
resolution: "@types/react@npm:19.0.3"
Expand Down Expand Up @@ -3006,6 +3024,7 @@ __metadata:
"@types/react": "npm:^18.2.48"
"@types/react-dom": "npm:^18.2.18"
"@types/react-infinite-scroll-component": "npm:^5.0.0"
"@types/react-window": "npm:^1.8.8"
"@types/tinycolor2": "npm:^1"
"@vitejs/plugin-react": "npm:^4.2.1"
adm-zip: "npm:^0.5.16"
Expand Down Expand Up @@ -3054,6 +3073,7 @@ __metadata:
react-router: "npm:6"
react-router-dom: "npm:6"
react-spinners: "npm:^0.14.1"
react-window: "npm:^1.8.11"
redux: "npm:^5.0.1"
redux-persist: "npm:^6.0.0"
rehype-katex: "npm:^7.0.1"
Expand Down Expand Up @@ -8982,6 +9002,13 @@ __metadata:
languageName: node
linkType: hard

"memoize-one@npm:>=3.1.1 <6":
version: 5.2.1
resolution: "memoize-one@npm:5.2.1"
checksum: 10c0/fd22dbe9a978a2b4f30d6a491fc02fb90792432ad0dab840dc96c1734d2bd7c9cdeb6a26130ec60507eb43230559523615873168bcbe8fafab221c30b11d54c1
languageName: node
linkType: hard

"memoize-one@npm:^6.0.0":
version: 6.0.0
resolution: "memoize-one@npm:6.0.0"
Expand Down Expand Up @@ -11678,6 +11705,19 @@ __metadata:
languageName: node
linkType: hard

"react-window@npm:^1.8.11":
version: 1.8.11
resolution: "react-window@npm:1.8.11"
dependencies:
"@babel/runtime": "npm:^7.0.0"
memoize-one: "npm:>=3.1.1 <6"
peerDependencies:
react: ^15.0.0 || ^16.0.0 || ^17.0.0 || ^18.0.0 || ^19.0.0
react-dom: ^15.0.0 || ^16.0.0 || ^17.0.0 || ^18.0.0 || ^19.0.0
checksum: 10c0/5ae8da1bc5c47d8f0a428b28a600256e2db511975573e52cb65a9b27ed1a0e5b9f7b3bee5a54fb0da93956d782c24010be434be451072f46ba5a89159d2b3944
languageName: node
linkType: hard

"react@npm:^18.2.0":
version: 18.3.1
resolution: "react@npm:18.3.1"
Expand Down

0 comments on commit 5f8f640

Please sign in to comment.