cc-haha/desktop/src/components/chat/UserMessage.tsx
程序员阿江(Relakkes) 659c63e5ba perf(desktop): memoize chat row subtree to stop window-slide re-renders
When the virtualization window slid during scroll, every visible row reconciled
from scratch because the heavy chat-row subtree had no memo barriers, and
because renderTranscriptItem rebuilt fresh branchAction / toolResult object
literals each render that broke MessageBlock's existing memo.

Wraps AssistantMessage, UserMessage, ToolCallBlock, ToolCallGroup, ToolResultBlock,
and MarkdownRenderer in React.memo, and hoists the per-message branchAction and
toolResult lookups into useMemo'd Maps keyed by message id. Window slides now
keep referentially-stable props for unchanged rows, so reconciliation skips them
and only newly entering rows pay full render cost.

Tested: 722/722 desktop vitest suites pass

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-23 23:41:25 +08:00

50 lines
1.5 KiB
TypeScript

import { memo } from 'react'
import type { UIAttachment } from '../../types/chat'
import { AttachmentGallery } from './AttachmentGallery'
import { MessageActionBar, type MessageBranchAction } from './MessageActionBar'
type Props = {
content: string
attachments?: UIAttachment[]
branchAction?: MessageBranchAction
}
export const UserMessage = memo(function UserMessage({ content, attachments, branchAction }: Props) {
const hasText = content.trim().length > 0
return (
<div className="group mb-5 flex justify-end">
<div
data-message-shell="user"
className="flex min-w-0 w-full max-w-[82%] flex-col items-end gap-2 sm:max-w-[78%] lg:max-w-[72%]"
>
{attachments && attachments.length > 0 && (
<AttachmentGallery attachments={attachments} variant="message" />
)}
{hasText && (
<div
className="min-w-0 max-w-full bg-[var(--color-surface-user-msg)] px-4 py-3 text-sm leading-relaxed text-[var(--color-text-primary)] whitespace-pre-wrap break-words"
style={{
borderRadius: '18px 4px 18px 18px',
overflowWrap: 'anywhere',
wordBreak: 'break-word',
}}
>
{content}
</div>
)}
{hasText && (
<MessageActionBar
copyText={content}
copyLabel="Copy prompt"
branchAction={branchAction}
align="end"
/>
)}
</div>
</div>
)
})