249 lines
8.8 KiB
JavaScript
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;
|