perf: 优化聊天渲染与后台任务性能

This commit is contained in:
Wxw-Gu
2026-07-17 11:37:02 +08:00
parent 8e1e166856
commit 1f693aa3d7
10 changed files with 588 additions and 74 deletions
+2 -2
View File
@@ -330,7 +330,7 @@ function App(): React.ReactElement {
usernames.length ? 55 : 90
)
let loadedCount = 0
const chunkSize = 8
const chunkSize = 32
for (let index = 0; index < usernames.length; index += chunkSize) {
if (runId !== contactAvatarHydrationRunRef.current) return
const chunk = usernames.slice(index, index + chunkSize)
@@ -361,7 +361,7 @@ function App(): React.ReactElement {
} catch (error) {
console.warn('[Contacts] avatar hydrate failed:', error)
}
await new Promise((resolve) => window.setTimeout(resolve, 150))
await new Promise((resolve) => window.setTimeout(resolve, 50))
}
if (usernames.length) onProgress?.('头像加载完成', 90)
}
+21 -2
View File
@@ -1143,6 +1143,20 @@ body {
background: #f0f2ef;
}
.virtual-message-list {
position: relative;
flex: 0 0 auto;
width: 100%;
}
.virtual-message-group {
position: absolute;
top: 0;
left: 0;
display: flow-root;
width: 100%;
}
.wechat-message-row {
margin-bottom: 14px;
}
@@ -7061,7 +7075,9 @@ body {
border: 1.5px solid var(--wxex-border);
border-radius: 12px;
background: #fff;
transition: border-color 0.2s, background 0.2s;
transition:
border-color 0.2s,
background 0.2s;
}
.report-template-item.active {
@@ -7114,7 +7130,10 @@ body {
background: #fff;
color: var(--wxex-text-primary, #1f2933);
cursor: pointer;
transition: background 0.2s, color 0.2s, border-color 0.2s;
transition:
background 0.2s,
color 0.2s,
border-color 0.2s;
}
.report-template-preview-btn:hover {
+3 -10
View File
@@ -20,7 +20,6 @@ interface ChatWindowProps {
isAiLoading?: boolean
}
const MAX_RENDERED_MESSAGES = 600
const DATE_RANGE_LABELS: Record<string, string> = {
today: '今天',
yesterday: '昨日',
@@ -242,12 +241,6 @@ const ChatWindow: React.FC<ChatWindowProps> = ({
return typeMatch && contentMatch
})
}, [messages, contentFilter])
const hiddenMessageCount = Math.max(0, filteredMessages.length - MAX_RENDERED_MESSAGES)
const renderedMessages = React.useMemo(
() => filteredMessages.slice(-MAX_RENDERED_MESSAGES),
[filteredMessages]
)
if (!contact) return <EmptyConversationState />
const dateRangeLabel = getChatHeaderRangeLabel(dateRange)
@@ -272,8 +265,8 @@ const ChatWindow: React.FC<ChatWindowProps> = ({
<DataTrustBar messageCount={messages.length} />
<MessageList
contact={contact}
messages={renderedMessages}
hiddenMessageCount={hiddenMessageCount}
messages={filteredMessages}
hiddenMessageCount={0}
isLoadingMessages={isLoadingMessages}
isGroupChat={isGroupChat}
showAvatar={showAvatar}
@@ -283,7 +276,7 @@ const ChatWindow: React.FC<ChatWindowProps> = ({
onImageClick={openImagePreview}
/>
<ChatStatusBar
count={renderedMessages.length}
count={filteredMessages.length}
showAvatar={showAvatar}
isAtLatest={isAtLatest}
onShowAvatarChange={setShowAvatar}
@@ -1,4 +1,5 @@
import React from 'react'
import { useVirtualizer } from '@tanstack/react-virtual'
import { Contact, Message } from '../../../../shared/types'
import { MessageGroup } from './MessageGroup'
import { buildMessageGroups } from './messageGrouping'
@@ -29,6 +30,16 @@ export function MessageList({
onImageClick
}: MessageListProps): React.ReactElement {
const groups = React.useMemo(() => buildMessageGroups(messages), [messages])
// TanStack Virtual intentionally exposes mutable measurement methods.
// eslint-disable-next-line react-hooks/incompatible-library
const virtualizer = useVirtualizer({
count: groups.length,
getScrollElement: () => listRef.current,
estimateSize: () => 96,
getItemKey: (index) => groups[index]?.id || index,
overscan: 8
})
const virtualItems = virtualizer.getVirtualItems()
return (
<div className="message-list wechat-message-list" ref={listRef} onScroll={onScroll}>
@@ -40,16 +51,29 @@ export function MessageList({
</div>
</div>
)}
{groups.map((group) => (
<MessageGroup
key={group.id}
group={group}
contact={contact}
isGroupChat={isGroupChat}
showAvatar={showAvatar}
onImageClick={onImageClick}
/>
))}
<div className="virtual-message-list" style={{ height: `${virtualizer.getTotalSize()}px` }}>
{virtualItems.map((virtualItem) => {
const group = groups[virtualItem.index]
if (!group) return null
return (
<div
key={virtualItem.key}
ref={virtualizer.measureElement}
data-index={virtualItem.index}
className="virtual-message-group"
style={{ transform: `translateY(${virtualItem.start}px)` }}
>
<MessageGroup
group={group}
contact={contact}
isGroupChat={isGroupChat}
showAvatar={showAvatar}
onImageClick={onImageClick}
/>
</div>
)
})}
</div>
<div ref={bottomRef} />
</div>
)
@@ -195,8 +195,9 @@ export function AgentHubWorkspace(): React.ReactElement {
<h2></h2>
<p> Agent Hub WechatExplorer</p>
<div className="agent-hub-example">
<span></span>
<span></span>
<strong> 5 </strong>
<strong>xx聊了些什么</strong>
<strong></strong>
</div>
<ul>
@@ -212,6 +213,10 @@ export function AgentHubWorkspace(): React.ReactElement {
<i />
</li>
<li>
<i />
使 AI
</li>
<li>
<i className={status.dataApi === 'online' ? '' : 'offline'} />
API{status.dataApi === 'online' ? '已连接' : '未连接'}