import { clsx } from 'clsx'; import { User, Bot, Image, FileText, Download, ExternalLink, CheckCircle, Clock, XCircle, AlertCircle, ShoppingBag } from 'lucide-react'; import ReactMarkdown from 'react-markdown'; import { QRCodeSVG } from 'qrcode.react'; import { FileAttachment } from '../stores/chatStore'; import { AssessmentResultCard } from './AssessmentResultCard'; interface Message { id: string; role: 'user' | 'assistant' | 'system'; content: string; createdAt: string; attachments?: FileAttachment[]; metadata?: { toolCalls?: Array<{ name: string; result: unknown; }>; }; } interface MessageBubbleProps { message: Message; isStreaming?: boolean; } export function MessageBubble({ message, isStreaming }: MessageBubbleProps) { const isUser = message.role === 'user'; const hasAttachments = message.attachments && message.attachments.length > 0; return (
{/* Avatar */}
{isUser ? ( ) : ( )}
{/* Message content */}
{/* 文件附件 */} {hasAttachments && (
{message.attachments!.map((attachment) => ( ))}
)} {/* 文本内容 */} {message.content && (
{isUser ? (

{message.content}

) : (

{children}

, h2: ({ children }) =>

{children}

, h3: ({ children }) =>

{children}

, p: ({ children }) =>

{children}

, ul: ({ children }) =>
    {children}
, ol: ({ children }) =>
    {children}
, li: ({ children }) =>
  • {children}
  • , strong: ({ children }) => {children}, a: ({ href, children }) => ( {children} ), }} > {message.content}
    )} {isStreaming && ( )}
    {/* Tool call results (e.g., payment QR code) */} {message.metadata?.toolCalls?.map((toolCall, index) => ( ))}
    )}
    ); } function AttachmentPreview({ attachment, isUser, }: { attachment: FileAttachment; isUser: boolean; }) { const isImage = attachment.type === 'image'; const formatFileSize = (bytes: number) => { if (bytes < 1024) return `${bytes} B`; if (bytes < 1024 * 1024) return `${(bytes / 1024).toFixed(1)} KB`; return `${(bytes / (1024 * 1024)).toFixed(1)} MB`; }; if (isImage && (attachment.downloadUrl || attachment.thumbnailUrl)) { return ( {attachment.originalName}
    ); } return ( {isImage ? ( ) : ( )}
    {attachment.originalName} {formatFileSize(attachment.size)}
    ); } function ToolCallResult({ toolCall, }: { toolCall: { name: string; result: unknown }; }) { if (toolCall.name === 'generate_payment') { const result = toolCall.result as { success?: boolean; qrCodeUrl?: string; paymentUrl?: string; amount?: number; currency?: string; message?: string; orderId?: string; expiresAt?: string; error?: string; }; if (!result.success) { return (

    {result.error || '支付创建失败'}

    ); } return (

    {result.message}

    {result.qrCodeUrl ? (
    ) : result.paymentUrl ? ( 前往支付 ) : null}

    ¥{result.amount}

    {result.orderId && (

    订单号: {result.orderId}

    )} {result.expiresAt && (

    有效期至: {new Date(result.expiresAt).toLocaleTimeString('zh-CN')}

    )}
    ); } if (toolCall.name === 'check_payment_status') { const result = toolCall.result as { success?: boolean; orderId?: string; status?: string; statusLabel?: string; paidAt?: string; error?: string; }; if (!result.success) { return (

    {result.error || '查询失败'}

    ); } const statusConfig: Record = { PAID: { icon: , color: 'bg-green-50 border-green-200' }, COMPLETED: { icon: , color: 'bg-green-50 border-green-200' }, PENDING_PAYMENT: { icon: , color: 'bg-yellow-50 border-yellow-200' }, CREATED: { icon: , color: 'bg-yellow-50 border-yellow-200' }, CANCELLED: { icon: , color: 'bg-red-50 border-red-200' }, REFUNDED: { icon: , color: 'bg-orange-50 border-orange-200' }, }; const config = statusConfig[result.status || ''] || { icon: , color: 'bg-secondary-50 border-secondary-200' }; return (
    {config.icon} {result.statusLabel || result.status}
    {result.orderId && (

    订单号: {result.orderId}

    )} {result.paidAt && (

    支付时间: {new Date(result.paidAt).toLocaleString('zh-CN')}

    )}
    ); } if (toolCall.name === 'query_order_history') { const result = toolCall.result as { success?: boolean; totalOrders?: number; orders?: Array<{ orderId: string; serviceType: string; serviceCategory?: string; amount: number; currency: string; status: string; paidAt?: string; createdAt: string; }>; error?: string; }; if (!result.success) { return (

    {result.error || '查询失败'}

    ); } if (!result.orders || result.orders.length === 0) { return (

    暂无订单记录

    ); } const statusLabels: Record = { CREATED: '待支付', PENDING_PAYMENT: '待支付', PAID: '已支付', PROCESSING: '处理中', COMPLETED: '已完成', CANCELLED: '已取消', REFUNDED: '已退款', }; return (
    订单记录 ({result.totalOrders})
    {result.orders.map((order) => (
    {order.serviceCategory || order.serviceType} {new Date(order.createdAt).toLocaleDateString('zh-CN')}
    ¥{order.amount} {statusLabels[order.status] || order.status}
    ))}
    ); } if (toolCall.name === 'invoke_assessment_expert') { // Assessment expert returns a JSON string; parse it try { const raw = toolCall.result; const data = typeof raw === 'string' ? JSON.parse(raw) : raw; if (data?.assessments && Array.isArray(data.assessments)) { return ; } } catch { // Not parseable — fall through to null } } if (toolCall.name === 'cancel_order') { const result = toolCall.result as { success?: boolean; orderId?: string; message?: string; error?: string; }; return (
    {result.success ? ( ) : ( )} {result.success ? result.message : result.error}
    {result.orderId && (

    订单号: {result.orderId}

    )}
    ); } return null; }