perf(web): narrow message state subscriptions

- web/src/components/AssistantChat/messages/UserMessage.tsx now subscribes only to role, text, status,
  and localId instead of the entire message object to avoid re-renders caused by unrelated message state
  changes (hover/isLast/etc.).
- web/src/components/AssistantChat/messages/SystemMessage.tsx now subscribes only to role, text, and icon
  for the same reason.
- This reduces unnecessary renders observed in React DevTools without changing message UI or behavior.
This commit is contained in:
weishu
2025-12-19 21:48:03 +08:00
parent d2ad977394
commit 05c2cbb724
2 changed files with 28 additions and 13 deletions
@@ -3,13 +3,19 @@ import { getEventPresentation } from '@/chat/presentation'
import type { HappyChatMessageMetadata } from '@/lib/assistant-runtime'
export function HappySystemMessage() {
const message = useAssistantState(({ message }) => message)
if (message.role !== 'system') return null
const role = useAssistantState(({ message }) => message.role)
const text = useAssistantState(({ message }) => {
if (message.role !== 'system') return ''
return message.content[0]?.type === 'text' ? message.content[0].text : ''
})
const icon = useAssistantState(({ message }) => {
if (message.role !== 'system') return null
const custom = message.metadata.custom as Partial<HappyChatMessageMetadata> | undefined
const event = custom?.kind === 'event' ? custom.event : undefined
return event ? getEventPresentation(event).icon : null
})
const text = message.content[0]?.type === 'text' ? message.content[0].text : ''
const custom = message.metadata.custom as Partial<HappyChatMessageMetadata> | undefined
const event = custom?.kind === 'event' ? custom.event : undefined
const icon = event ? getEventPresentation(event).icon : null
if (role !== 'system') return null
return (
<div className="py-1">
@@ -6,14 +6,23 @@ import { MessageStatusIndicator } from '@/components/AssistantChat/messages/Mess
export function HappyUserMessage() {
const ctx = useHappyChatContext()
const message = useAssistantState(({ message }) => message)
const role = useAssistantState(({ message }) => message.role)
const text = useAssistantState(({ message }) => {
if (message.role !== 'user') return ''
return message.content.find((part) => part.type === 'text')?.text ?? ''
})
const status = useAssistantState(({ message }) => {
if (message.role !== 'user') return undefined
const custom = message.metadata.custom as Partial<HappyChatMessageMetadata> | undefined
return custom?.status
})
const localId = useAssistantState(({ message }) => {
if (message.role !== 'user') return null
const custom = message.metadata.custom as Partial<HappyChatMessageMetadata> | undefined
return custom?.localId ?? null
})
if (message.role !== 'user') return null
const text = message.content.find((part) => part.type === 'text')?.text ?? ''
const custom = message.metadata.custom as Partial<HappyChatMessageMetadata> | undefined
const status = custom?.status
const localId = custom?.localId
if (role !== 'user') return null
const canRetry = status === 'failed' && typeof localId === 'string' && Boolean(ctx.onRetryMessage)
const onRetry = canRetry ? () => ctx.onRetryMessage!(localId) : undefined