Message.Display.tsx91 lines · main
1import { UIMessage as VercelMessage } from '@ai-sdk/react'
2import { type PropsWithChildren } from 'react'
3import { cn } from 'ui'
4
5import { useMessageInfoContext } from './Message.Context'
6import { MessagePartSwitcher } from './Message.Parts'
7import { MessageMarkdown } from './MessageMarkdown'
8import { ProfileImage as ProfileImageDisplay } from '@/components/ui/ProfileImage'
9import { useProfileNameAndPicture } from '@/lib/profile'
10
11function MessageDisplayProfileImage() {
12 const { username, avatarUrl } = useProfileNameAndPicture()
13 return (
14 <ProfileImageDisplay
15 alt={username}
16 src={avatarUrl}
17 className="w-5 h-5 shrink-0 rounded-full translate-y-0.5"
18 />
19 )
20}
21
22function MessageDisplayContainer({
23 children,
24 onClick,
25 className,
26}: PropsWithChildren<{ onClick?: () => void; className?: string }>) {
27 return (
28 <div
29 className={cn('group text-foreground-light text-sm first:mt-0', className)}
30 onClick={onClick}
31 >
32 {children}
33 </div>
34 )
35}
36
37function MessageDisplayMainArea({
38 children,
39 className,
40}: PropsWithChildren<{ className?: string }>) {
41 return <div className={cn('flex gap-4 w-auto overflow-hidden group', className)}>{children}</div>
42}
43
44function MessageDisplayContent({ message }: { message: VercelMessage }) {
45 const { id, isLoading, readOnly } = useMessageInfoContext()
46
47 const messageParts = message.parts
48 const content =
49 ('content' in message && typeof message.content === 'string' && message.content.trim()) ||
50 undefined
51
52 return (
53 <div className="flex-1 min-w-0">
54 {messageParts?.length > 0
55 ? messageParts.map((part: NonNullable<VercelMessage['parts'][number]>, idx) => {
56 const isLastPart = idx === messageParts.length - 1
57 return <MessagePartSwitcher key={idx} part={part} isLastPart={isLastPart} />
58 })
59 : content && (
60 <MessageDisplayTextMessage id={id} isLoading={isLoading} readOnly={readOnly}>
61 {content}
62 </MessageDisplayTextMessage>
63 )}
64 </div>
65 )
66}
67
68function MessageDisplayTextMessage({
69 id,
70 isLoading,
71 readOnly,
72 children,
73}: PropsWithChildren<{ id: string; isLoading: boolean; readOnly?: boolean }>) {
74 return (
75 <MessageMarkdown
76 id={id}
77 isLoading={isLoading}
78 readOnly={readOnly}
79 className="prose prose-sm max-w-none wrap-break-word prose-h2:font-medium"
80 >
81 {children}
82 </MessageMarkdown>
83 )
84}
85
86export const MessageDisplay = {
87 Container: MessageDisplayContainer,
88 Content: MessageDisplayContent,
89 MainArea: MessageDisplayMainArea,
90 ProfileImage: MessageDisplayProfileImage,
91}