85 lines
3.8 KiB
TypeScript
85 lines
3.8 KiB
TypeScript
'use client'
|
|
|
|
import { User, Bot, Loader2 } from 'lucide-react'
|
|
import { cn } from '@/lib/utils'
|
|
import ReactMarkdown from 'react-markdown'
|
|
import remarkGfm from 'remark-gfm'
|
|
import { Avatar, AvatarFallback } from '@/components/ui/avatar'
|
|
import { useLanguage } from '@/lib/i18n'
|
|
|
|
interface ChatMessagesProps {
|
|
messages: any[]
|
|
isLoading?: boolean
|
|
}
|
|
|
|
function getMessageContent(msg: any): string {
|
|
if (typeof msg.content === 'string') return msg.content
|
|
if (msg.parts && Array.isArray(msg.parts)) {
|
|
return msg.parts
|
|
.filter((p: any) => p.type === 'text')
|
|
.map((p: any) => p.text)
|
|
.join('')
|
|
}
|
|
return ''
|
|
}
|
|
|
|
export function ChatMessages({ messages, isLoading }: ChatMessagesProps) {
|
|
const { t } = useLanguage()
|
|
|
|
return (
|
|
<div className="w-full max-w-4xl flex flex-col pt-8 pb-4">
|
|
{messages.length === 0 && !isLoading && (
|
|
<div className="flex flex-col items-center justify-center h-[60vh] text-center space-y-6">
|
|
<div className="p-5 bg-gradient-to-br from-primary/10 to-primary/5 rounded-full shadow-inner ring-1 ring-primary/10">
|
|
<Bot className="h-12 w-12 text-primary opacity-60" />
|
|
</div>
|
|
<p className="text-muted-foreground text-sm md:text-base max-w-md px-4 font-medium">
|
|
{t('chat.welcome')}
|
|
</p>
|
|
</div>
|
|
)}
|
|
|
|
{messages.map((msg, index) => {
|
|
const content = getMessageContent(msg)
|
|
const isLastAssistant = msg.role === 'assistant' && index === messages.length - 1 && isLoading
|
|
|
|
return (
|
|
<div
|
|
key={msg.id || index}
|
|
className={cn(
|
|
"flex w-full px-4 md:px-0 py-6 my-2 group",
|
|
msg.role === 'user' ? "justify-end" : "justify-start border-y border-transparent dark:border-transparent"
|
|
)}
|
|
>
|
|
{msg.role === 'user' ? (
|
|
<div dir="auto" className="max-w-[85%] md:max-w-[70%] bg-[#f4f4f5] dark:bg-[#2a2d36] text-slate-800 dark:text-slate-100 rounded-3xl rounded-br-md px-6 py-4 shadow-sm border border-slate-200/50 dark:border-white/5">
|
|
<div className="prose prose-sm dark:prose-invert max-w-none text-[15px] leading-relaxed">
|
|
<ReactMarkdown remarkPlugins={[remarkGfm]}>{content}</ReactMarkdown>
|
|
</div>
|
|
</div>
|
|
) : (
|
|
<div className="flex gap-4 md:gap-6 w-full max-w-3xl">
|
|
<Avatar className="h-8 w-8 shrink-0 bg-transparent border border-primary/20 text-primary mt-1 shadow-sm">
|
|
<AvatarFallback className="bg-transparent"><Bot className="h-4 w-4" /></AvatarFallback>
|
|
</Avatar>
|
|
<div dir="auto" className="flex-1 overflow-hidden pt-1">
|
|
{content ? (
|
|
<div className="prose prose-slate dark:prose-invert max-w-none prose-p:leading-relaxed prose-pre:bg-slate-900 prose-pre:shadow-sm prose-pre:border prose-pre:border-slate-800 prose-headings:font-semibold marker:text-primary/50 text-[15px] prose-table:border prose-table:border-slate-300 prose-th:border prose-th:border-slate-300 prose-th:px-3 prose-th:py-2 prose-th:bg-slate-100 dark:prose-th:bg-slate-800 prose-td:border prose-td:border-slate-300 prose-td:px-3 prose-td:py-2">
|
|
<ReactMarkdown remarkPlugins={[remarkGfm]}>{content}</ReactMarkdown>
|
|
</div>
|
|
) : isLastAssistant ? (
|
|
<div className="flex items-center gap-3 text-muted-foreground">
|
|
<Loader2 className="h-4 w-4 animate-spin text-primary" />
|
|
<span className="text-[15px] animate-pulse">{t('chat.searching')}</span>
|
|
</div>
|
|
) : null}
|
|
</div>
|
|
</div>
|
|
)}
|
|
</div>
|
|
)
|
|
})}
|
|
</div>
|
|
)
|
|
}
|