import React, { useState, useEffect } from 'react'; import { ChatMessage, MessageSender, MessagePurpose } from '../types'; import { Lightbulb, MessageSquareText, UserCircle, Zap, AlertTriangle, Copy, Check, MoreHorizontal } from 'lucide-react'; import { marked } from 'marked'; import DOMPurify from 'dompurify'; interface SenderIconProps { sender: MessageSender; purpose: MessagePurpose; messageText: string; } const SenderIcon: React.FC = ({ sender, purpose, messageText }) => { const iconClass = "w-5 h-5 mr-2 flex-shrink-0"; switch (sender) { case MessageSender.User: return ; case MessageSender.Cognito: return ; case MessageSender.Muse: return ; case MessageSender.System: if ( purpose === MessagePurpose.SystemNotification && (messageText.toLowerCase().includes("error") || messageText.toLowerCase().includes("错误") || messageText.toLowerCase().includes("警告")) ) { return ; } return ; default: // For dynamic role names, use a generic bot icon with dynamic color const colorClasses = [ 'text-green-400', 'text-purple-400', 'text-blue-400', 'text-yellow-400', 'text-pink-400', 'text-indigo-400' ]; const colorIndex = typeof sender === 'string' ? sender.length % colorClasses.length : 0; return ; } }; const getSenderNameStyle = (sender: MessageSender): string => { switch (sender) { case MessageSender.User: return "text-blue-300"; case MessageSender.Cognito: return "text-green-300"; case MessageSender.Muse: return "text-purple-300"; case MessageSender.System: return "text-gray-400"; default: // For dynamic role names, apply dynamic colors const colorClasses = [ 'text-green-300', 'text-purple-300', 'text-blue-300', 'text-yellow-300', 'text-pink-300', 'text-indigo-300' ]; const colorIndex = typeof sender === 'string' ? sender.length % colorClasses.length : 0; return colorClasses[colorIndex]; } } const getBubbleStyle = (sender: MessageSender, purpose: MessagePurpose, messageText: string): string => { let baseStyle = "mb-4 p-4 rounded-lg shadow-md max-w-xl break-words relative "; if (purpose === MessagePurpose.SystemNotification) { if ( messageText.toLowerCase().includes("error") || messageText.toLowerCase().includes("错误") || messageText.toLowerCase().includes("警告") || messageText.toLowerCase().includes("critical") || messageText.toLowerCase().includes("严重") ) { return baseStyle + "bg-red-800 border border-red-700 text-center text-sm italic mx-auto text-red-200"; } return baseStyle + "bg-gray-700 text-center text-sm italic mx-auto"; } switch (sender) { case MessageSender.User: return baseStyle + "bg-blue-600 ml-auto rounded-br-none"; case MessageSender.Cognito: return baseStyle + "bg-green-700 mr-auto rounded-bl-none"; case MessageSender.Muse: return baseStyle + "bg-purple-700 mr-auto rounded-bl-none"; default: // For dynamic role names, use varied background colors const bgColors = [ 'bg-green-700', 'bg-purple-700', 'bg-blue-700', 'bg-yellow-700', 'bg-pink-700', 'bg-indigo-700' ]; const bgIndex = typeof sender === 'string' ? sender.length % bgColors.length : 0; return baseStyle + bgColors[bgIndex] + " mr-auto rounded-bl-none"; } }; const getPurposePrefix = (purpose: MessagePurpose, sender: MessageSender): string => { switch (purpose) { case MessagePurpose.CognitoToMuse: return `致 ${MessageSender.Muse}的消息: `; case MessagePurpose.MuseToCognito: return `致 ${MessageSender.Cognito}的消息: `; case MessagePurpose.FinalResponse: return `最终答案: `; default: return ""; } } interface MessageBubbleProps { message: ChatMessage; } const MessageBubble: React.FC = ({ message }) => { const { text: messageText, sender, purpose, timestamp, durationMs, image } = message; const formattedTime = new Date(timestamp).toLocaleTimeString([], { hour: '2-digit', minute: '2-digit' }); const [isCopied, setIsCopied] = useState(false); // 移除所有流式动画逻辑,直接显示内容 const isDiscussionStep = purpose === MessagePurpose.CognitoToMuse || purpose === MessagePurpose.MuseToCognito; const isFinalResponse = purpose === MessagePurpose.FinalResponse; const showDuration = durationMs !== undefined && durationMs > 0; const shouldRenderMarkdown = (sender === MessageSender.User || sender === MessageSender.Cognito || sender === MessageSender.Muse || typeof sender === 'string') && purpose !== MessagePurpose.SystemNotification; let sanitizedHtml = ''; if (shouldRenderMarkdown && messageText) { const rawHtml = marked.parse(messageText) as string; sanitizedHtml = DOMPurify.sanitize(rawHtml); } const handleCopy = async () => { try { await navigator.clipboard.writeText(messageText); setIsCopied(true); setTimeout(() => setIsCopied(false), 2000); } catch (err) { console.error('无法复制文本: ', err); } }; const canCopy = (sender === MessageSender.User || sender === MessageSender.Cognito || sender === MessageSender.Muse || typeof sender === 'string') && purpose !== MessagePurpose.SystemNotification; return (
{canCopy && ( )}
{sender} {isDiscussionStep && (内部讨论)}
{messageText ? ( shouldRenderMarkdown ? (
) : (

{messageText}

) ) : (

正在生成回复...

)} {image && sender === MessageSender.User && (
{image.name
)}
{formattedTime} {showDuration && ( (耗时: {(durationMs / 1000).toFixed(2)}s) )}
); }; export default MessageBubble;