import { Bot, Check, Copy, RefreshCw } from 'lucide-react' import { memo } from 'react' import { useCopyToClipboard } from '../hook/use-copy-to-clipboard' import { cn } from '../lib/utils' import { Button } from '../ui/button' import { ChatMessageProvider, useChatMessage } from './chat-message.context.js' import { useChatUI } from './chat.context.js' import { Message } from './chat.interface' import { ArtifactPartUI, EventPartUI, FilePartUI, MarkdownPartUI, SourcesPartUI, SuggestionPartUI, TextPart, TextPartType, } from './message-parts' import { ChatPartProvider } from './message-parts/context.js' interface ChatMessageProps extends React.PropsWithChildren { message: Message isLast: boolean className?: string } interface ChatMessageAvatarProps extends React.PropsWithChildren { className?: string } interface ChatMessageContentProps extends React.PropsWithChildren { className?: string } interface ChatMessageActionsProps extends React.PropsWithChildren { className?: string } function ChatMessage(props: ChatMessageProps) { const children = props.children ?? ( <> ) return (
{children}
) } function ChatMessageAvatar(props: ChatMessageAvatarProps) { const { message } = useChatMessage() if (message.role !== 'assistant') return null const children = props.children ?? return (
{children}
) } function ChatMessageContent(props: ChatMessageContentProps) { const { message } = useChatMessage() const children = props.children ?? ( <> ) return (
{message.parts.map((part, index) => ( {children} ))}
) } function ChatMessageActions(props: ChatMessageActionsProps) { const { regenerate, requestData, isLoading } = useChatUI() const { isCopied, copyToClipboard } = useCopyToClipboard({ timeout: 2000 }) const { message, isLast } = useChatMessage() if (message.role !== 'assistant') return null const isLastMessageFromAssistant = message.role === 'assistant' && isLast const showReload = regenerate && !isLoading && isLastMessageFromAssistant // content to copy is all text parts joined by newlines const messageTextContent = message.parts .filter((part): part is TextPart => part.type === TextPartType) .map(part => part.text) .join('\n\n') const children = props.children ?? ( <> {showReload && ( )} ) return (
{children}
) } type ComposibleChatMessagePart = typeof ChatMessageContent & { File: typeof FilePartUI Event: typeof EventPartUI Markdown: typeof MarkdownPartUI Source: typeof SourcesPartUI Suggestion: typeof SuggestionPartUI Artifact: typeof ArtifactPartUI } type ComposibleChatMessage = typeof ChatMessage & { Avatar: typeof ChatMessageAvatar Content: ComposibleChatMessagePart Part: ComposibleChatMessagePart Actions: typeof ChatMessageActions } const PrimiviteChatMessage = memo(ChatMessage, (prevProps, nextProps) => { return ( !nextProps.isLast && prevProps.isLast === nextProps.isLast && prevProps.message === nextProps.message ) }) as unknown as ComposibleChatMessage PrimiviteChatMessage.Content = ChatMessageContent as ComposibleChatMessagePart // create alias Part with available built-in parts PrimiviteChatMessage.Part = ChatMessageContent as ComposibleChatMessagePart PrimiviteChatMessage.Part.Event = EventPartUI PrimiviteChatMessage.Part.File = FilePartUI PrimiviteChatMessage.Part.Markdown = MarkdownPartUI PrimiviteChatMessage.Part.Source = SourcesPartUI PrimiviteChatMessage.Part.Suggestion = SuggestionPartUI PrimiviteChatMessage.Part.Artifact = ArtifactPartUI PrimiviteChatMessage.Avatar = ChatMessageAvatar PrimiviteChatMessage.Actions = ChatMessageActions export default PrimiviteChatMessage