feat: 创建 ChatArea 组件(含 MessageRenderer、ExecutionStepsDisplay 子组件)
This commit is contained in:
248
src/views/AgentChat/components/ChatArea/MessageRenderer.js
Normal file
248
src/views/AgentChat/components/ChatArea/MessageRenderer.js
Normal file
@@ -0,0 +1,248 @@
|
||||
// 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;
|
||||
Reference in New Issue
Block a user