update pay ui

This commit is contained in:
2025-12-17 12:40:14 +08:00
parent b89aba07e2
commit 8c6da953f3
3 changed files with 275 additions and 5 deletions

View File

@@ -2,7 +2,7 @@
// 消息渲染器组件
import React from 'react';
import { motion } from 'framer-motion';
import { motion, AnimatePresence } from 'framer-motion';
import {
Card,
CardBody,
@@ -12,11 +12,13 @@ import {
Tooltip,
IconButton,
HStack,
VStack,
Flex,
Text,
Box,
Progress,
} from '@chakra-ui/react';
import { Cpu, User, Copy, ThumbsUp, ThumbsDown, File } from 'lucide-react';
import { Cpu, User, Copy, ThumbsUp, ThumbsDown, File, ListChecks, Play, CheckCircle, XCircle, Clock } from 'lucide-react';
import { MessageTypes } from '../../constants/messageTypes';
import ExecutionStepsDisplay from './ExecutionStepsDisplay';
import { MarkdownWithCharts } from '@components/ChatBot/MarkdownWithCharts';
@@ -239,6 +241,272 @@ const MessageRenderer = ({ message, userAvatar }) => {
</Flex>
);
case MessageTypes.AGENT_PLAN:
return (
<Flex justify="flex-start" w="100%">
<HStack align="start" spacing={3} maxW={{ base: '95%', md: '85%', lg: '80%' }} w="100%">
<Avatar
src="/images/agent/基金经理.png"
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)"
flexShrink={0}
/>
<motion.div
initial={{ opacity: 0, x: -20 }}
animate={{ opacity: 1, x: 0 }}
transition={{ type: 'spring', stiffness: 300, damping: 30 }}
style={{ flex: 1, minWidth: 0 }}
>
<Card
bg="rgba(255, 255, 255, 0.05)"
backdropFilter="blur(16px) saturate(180%)"
border="1px solid"
borderColor="rgba(139, 92, 246, 0.3)"
boxShadow="0 8px 32px 0 rgba(139, 92, 246, 0.2)"
w="100%"
>
<CardBody px={5} py={3}>
<HStack spacing={2} mb={3}>
<ListChecks className="w-4 h-4" color="#C084FC" />
<Text fontSize="sm" fontWeight="medium" color="purple.300">
执行计划
</Text>
{message.plan?.steps && (
<Badge
bgGradient="linear(to-r, purple.500, pink.500)"
color="white"
fontSize="xs"
>
{message.plan.steps.length} 个步骤
</Badge>
)}
</HStack>
{message.plan?.goal && (
<Text fontSize="sm" color="gray.300" mb={3}>
🎯 {message.plan.goal}
</Text>
)}
{message.plan?.steps && (
<VStack align="stretch" spacing={2}>
{message.plan.steps.map((step, idx) => (
<motion.div
key={idx}
initial={{ opacity: 0, x: -10 }}
animate={{ opacity: 1, x: 0 }}
transition={{ delay: idx * 0.1 }}
>
<HStack
p={2}
bg="rgba(255, 255, 255, 0.03)"
borderRadius="md"
border="1px solid"
borderColor="rgba(255, 255, 255, 0.1)"
spacing={2}
>
<Badge
bg="rgba(139, 92, 246, 0.2)"
color="purple.300"
fontSize="xs"
minW="24px"
textAlign="center"
>
{idx + 1}
</Badge>
<Text fontSize="xs" color="gray.400" flex={1}>
{step.tool}
</Text>
</HStack>
</motion.div>
))}
</VStack>
)}
</CardBody>
</Card>
</motion.div>
</HStack>
</Flex>
);
case MessageTypes.AGENT_EXECUTING:
const steps = message.plan?.steps || [];
const completedSteps = message.stepResults || [];
const currentStepIndex = completedSteps.length;
const progress = steps.length > 0 ? (completedSteps.length / steps.length) * 100 : 0;
return (
<Flex justify="flex-start" w="100%">
<HStack align="start" spacing={3} maxW={{ base: '95%', md: '85%', lg: '80%' }} w="100%">
<Avatar
src="/images/agent/基金经理.png"
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)"
flexShrink={0}
/>
<motion.div
initial={{ opacity: 0, x: -20 }}
animate={{ opacity: 1, x: 0 }}
style={{ flex: 1, minWidth: 0 }}
>
<Card
bg="rgba(255, 255, 255, 0.05)"
backdropFilter="blur(16px) saturate(180%)"
border="1px solid"
borderColor="rgba(59, 130, 246, 0.3)"
boxShadow="0 8px 32px 0 rgba(59, 130, 246, 0.2)"
w="100%"
>
<CardBody px={5} py={3}>
<HStack spacing={2} mb={3}>
<motion.div
animate={{ rotate: 360 }}
transition={{ duration: 2, repeat: Infinity, ease: 'linear' }}
>
<Play className="w-4 h-4" color="#60A5FA" />
</motion.div>
<Text fontSize="sm" fontWeight="medium" color="blue.300">
正在执行
</Text>
<Badge colorScheme="blue" fontSize="xs">
{completedSteps.length} / {steps.length}
</Badge>
</HStack>
{/* 进度条 */}
<Progress
value={progress}
size="xs"
colorScheme="blue"
bg="rgba(255, 255, 255, 0.1)"
borderRadius="full"
mb={3}
hasStripe
isAnimated
/>
{/* 步骤列表 */}
<VStack align="stretch" spacing={2}>
<AnimatePresence>
{steps.map((step, idx) => {
const result = completedSteps[idx];
const isCompleted = idx < completedSteps.length;
const isRunning = idx === currentStepIndex;
const isPending = idx > currentStepIndex;
return (
<motion.div
key={idx}
initial={{ opacity: 0, y: -10 }}
animate={{ opacity: 1, y: 0 }}
transition={{ delay: idx * 0.05 }}
>
<HStack
p={2}
bg={
isCompleted
? result?.status === 'success'
? 'rgba(16, 185, 129, 0.1)'
: 'rgba(239, 68, 68, 0.1)'
: isRunning
? 'rgba(59, 130, 246, 0.15)'
: 'rgba(255, 255, 255, 0.03)'
}
borderRadius="md"
border="1px solid"
borderColor={
isCompleted
? result?.status === 'success'
? 'rgba(16, 185, 129, 0.3)'
: 'rgba(239, 68, 68, 0.3)'
: isRunning
? 'rgba(59, 130, 246, 0.4)'
: 'rgba(255, 255, 255, 0.1)'
}
spacing={2}
transition="all 0.3s"
>
{/* 状态图标 */}
<Box w="20px" h="20px" display="flex" alignItems="center" justifyContent="center">
{isCompleted ? (
result?.status === 'success' ? (
<motion.div
initial={{ scale: 0 }}
animate={{ scale: 1 }}
transition={{ type: 'spring', stiffness: 500 }}
>
<CheckCircle className="w-4 h-4" color="#10B981" />
</motion.div>
) : (
<XCircle className="w-4 h-4" color="#EF4444" />
)
) : isRunning ? (
<Spinner size="xs" color="blue.400" thickness="2px" />
) : (
<Clock className="w-3 h-3" color="#6B7280" />
)}
</Box>
{/* 步骤序号 */}
<Badge
bg={
isCompleted
? result?.status === 'success'
? 'rgba(16, 185, 129, 0.2)'
: 'rgba(239, 68, 68, 0.2)'
: isRunning
? 'rgba(59, 130, 246, 0.2)'
: 'rgba(107, 114, 128, 0.2)'
}
color={
isCompleted
? result?.status === 'success'
? 'green.300'
: 'red.300'
: isRunning
? 'blue.300'
: 'gray.500'
}
fontSize="xs"
minW="20px"
textAlign="center"
>
{idx + 1}
</Badge>
{/* 工具名称 */}
<Text
fontSize="xs"
color={isPending ? 'gray.500' : 'gray.300'}
flex={1}
fontWeight={isRunning ? 'medium' : 'normal'}
>
{step.tool}
</Text>
{/* 执行时间 */}
{result?.execution_time && (
<Text fontSize="xs" color="gray.500">
{result.execution_time.toFixed(2)}s
</Text>
)}
</HStack>
</motion.div>
);
})}
</AnimatePresence>
</VStack>
</CardBody>
</Card>
</motion.div>
</HStack>
</Flex>
);
case MessageTypes.ERROR:
return (
<Flex justify="center">

View File

@@ -70,8 +70,9 @@ export interface AgentMessage extends BaseMessage {
plan?: any;
/** 执行步骤结果 */
stepResults?: Array<{
tool_name: string;
status: 'success' | 'error';
tool: string;
status: 'success' | 'error' | 'failed';
result?: any;
execution_time?: number;
error?: string;
}>;

View File

@@ -404,9 +404,10 @@ export const useAgentChat = ({
break;
case 'step_start':
// 步骤开始执行
// 步骤开始执行 - 保留已完成的步骤结果,更新当前执行状态
updateLastMessage(MessageTypes.AGENT_EXECUTING, {
content: `正在执行步骤 ${(data?.step_index || 0) + 1}: ${data?.tool || '工具'}...`,
stepResults: [...streamStateRef.current.stepResults], // 保留已完成的步骤
});
break;