Message.Display.tsx 2.6 KB

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