Files
vf_react/src/views/AgentChat/components/ChatArea/MessageRenderer.js

249 lines
8.8 KiB
JavaScript

// src/views/AgentChat/components/ChatArea/MessageRenderer.js
// 消息渲染器组件
import React from 'react';
import { motion } from 'framer-motion';
import {
Card,
CardBody,
Avatar,
Badge,
Spinner,
Tooltip,
IconButton,
HStack,
Flex,
Text,
Box,
} from '@chakra-ui/react';
import { Cpu, User, Copy, ThumbsUp, ThumbsDown, File } from 'lucide-react';
import { MessageTypes } from '../../constants/messageTypes';
import ExecutionStepsDisplay from './ExecutionStepsDisplay';
/**
* MessageRenderer - 消息渲染器组件
*
* @param {Object} props
* @param {Object} props.message - 消息对象
* @param {string} props.userAvatar - 用户头像 URL
* @returns {JSX.Element|null}
*/
const MessageRenderer = ({ message, userAvatar }) => {
switch (message.type) {
case MessageTypes.USER:
return (
<Flex justify="flex-end">
<HStack align="start" spacing={3} maxW="75%">
<motion.div
initial={{ opacity: 0, x: 20 }}
animate={{ opacity: 1, x: 0 }}
transition={{ type: 'spring', stiffness: 300, damping: 30 }}
>
<Card
bgGradient="linear(to-br, blue.500, purple.600)"
boxShadow="0 8px 20px rgba(139, 92, 246, 0.4)"
>
<CardBody px={5} py={3}>
<Text fontSize="sm" color="white" whiteSpace="pre-wrap">
{message.content}
</Text>
{message.files && message.files.length > 0 && (
<HStack mt={2} flexWrap="wrap" spacing={2}>
{message.files.map((file, idx) => (
<Badge
key={idx}
bg="rgba(255, 255, 255, 0.2)"
color="white"
display="flex"
alignItems="center"
gap={1}
>
<File className="w-3 h-3" />
{file.name}
</Badge>
))}
</HStack>
)}
</CardBody>
</Card>
</motion.div>
<Avatar
src={userAvatar}
icon={<User className="w-4 h-4" />}
size="sm"
bgGradient="linear(to-br, blue.500, purple.600)"
boxShadow="0 0 12px rgba(139, 92, 246, 0.4)"
/>
</HStack>
</Flex>
);
case MessageTypes.AGENT_THINKING:
return (
<Flex justify="flex-start">
<HStack align="start" spacing={3} maxW="75%">
<Avatar
icon={<Cpu className="w-4 h-4" />}
size="sm"
bgGradient="linear(to-br, purple.500, pink.500)"
boxShadow="0 0 12px rgba(236, 72, 153, 0.4)"
/>
<motion.div initial={{ opacity: 0, x: -20 }} animate={{ opacity: 1, x: 0 }}>
<Card
bg="rgba(255, 255, 255, 0.05)"
backdropFilter="blur(16px) saturate(180%)"
border="1px solid"
borderColor="rgba(255, 255, 255, 0.1)"
boxShadow="0 8px 32px 0 rgba(31, 38, 135, 0.37)"
>
<CardBody px={5} py={3}>
<HStack spacing={2}>
<Spinner
size="sm"
color="purple.500"
emptyColor="gray.700"
thickness="3px"
speed="0.65s"
/>
<Text fontSize="sm" color="gray.300">
{message.content}
</Text>
</HStack>
</CardBody>
</Card>
</motion.div>
</HStack>
</Flex>
);
case MessageTypes.AGENT_RESPONSE:
return (
<Flex justify="flex-start">
<HStack align="start" spacing={3} maxW="75%">
<Avatar
icon={<Cpu className="w-4 h-4" />}
size="sm"
bgGradient="linear(to-br, purple.500, pink.500)"
boxShadow="0 0 12px rgba(236, 72, 153, 0.4)"
/>
<motion.div
initial={{ opacity: 0, x: -20 }}
animate={{ opacity: 1, x: 0 }}
transition={{ type: 'spring', stiffness: 300, damping: 30 }}
>
<Card
bg="rgba(255, 255, 255, 0.05)"
backdropFilter="blur(16px) saturate(180%)"
border="1px solid"
borderColor="rgba(255, 255, 255, 0.1)"
boxShadow="0 8px 32px 0 rgba(31, 38, 135, 0.37)"
>
<CardBody px={5} py={3}>
<Text fontSize="sm" color="gray.100" whiteSpace="pre-wrap" lineHeight="relaxed">
{message.content}
</Text>
{message.stepResults && message.stepResults.length > 0 && (
<Box mt={3}>
<ExecutionStepsDisplay steps={message.stepResults} plan={message.plan} />
</Box>
)}
<Flex
align="center"
gap={2}
mt={3}
pt={3}
borderTop="1px solid"
borderColor="rgba(255, 255, 255, 0.1)"
>
<Tooltip label="复制">
<motion.div whileHover={{ scale: 1.1 }} whileTap={{ scale: 0.9 }}>
<IconButton
size="sm"
variant="ghost"
icon={<Copy className="w-4 h-4" />}
onClick={() => navigator.clipboard.writeText(message.content)}
bg="rgba(255, 255, 255, 0.05)"
color="gray.400"
_hover={{
color: 'white',
bg: 'rgba(255, 255, 255, 0.1)',
}}
/>
</motion.div>
</Tooltip>
<Tooltip label="点赞">
<motion.div whileHover={{ scale: 1.1 }} whileTap={{ scale: 0.9 }}>
<IconButton
size="sm"
variant="ghost"
icon={<ThumbsUp className="w-4 h-4" />}
bg="rgba(255, 255, 255, 0.05)"
color="gray.400"
_hover={{
color: 'green.400',
bg: 'rgba(16, 185, 129, 0.1)',
boxShadow: '0 0 12px rgba(16, 185, 129, 0.3)',
}}
/>
</motion.div>
</Tooltip>
<Tooltip label="点踩">
<motion.div whileHover={{ scale: 1.1 }} whileTap={{ scale: 0.9 }}>
<IconButton
size="sm"
variant="ghost"
icon={<ThumbsDown className="w-4 h-4" />}
bg="rgba(255, 255, 255, 0.05)"
color="gray.400"
_hover={{
color: 'red.400',
bg: 'rgba(239, 68, 68, 0.1)',
boxShadow: '0 0 12px rgba(239, 68, 68, 0.3)',
}}
/>
</motion.div>
</Tooltip>
<Text fontSize="xs" color="gray.500" ml="auto">
{new Date(message.timestamp).toLocaleTimeString('zh-CN', {
hour: '2-digit',
minute: '2-digit',
})}
</Text>
</Flex>
</CardBody>
</Card>
</motion.div>
</HStack>
</Flex>
);
case MessageTypes.ERROR:
return (
<Flex justify="center">
<motion.div initial={{ opacity: 0, scale: 0.9 }} animate={{ opacity: 1, scale: 1 }}>
<Card
bg="rgba(239, 68, 68, 0.1)"
backdropFilter="blur(16px)"
border="1px solid"
borderColor="rgba(239, 68, 68, 0.5)"
boxShadow="0 8px 32px 0 rgba(239, 68, 68, 0.37)"
>
<CardBody px={5} py={3}>
<Text fontSize="sm" color="red.400">
{message.content}
</Text>
</CardBody>
</Card>
</motion.div>
</Flex>
);
default:
return null;
}
};
export default MessageRenderer;