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