187 lines
5.9 KiB
JavaScript
187 lines
5.9 KiB
JavaScript
// src/components/ChatBot/StepResultCard.js
|
|
// 步骤结果展示卡片(可折叠)
|
|
|
|
import React, { useState } from 'react';
|
|
import {
|
|
Box,
|
|
VStack,
|
|
HStack,
|
|
Text,
|
|
Badge,
|
|
Collapse,
|
|
Icon,
|
|
IconButton,
|
|
Code,
|
|
useColorModeValue,
|
|
Divider,
|
|
} from '@chakra-ui/react';
|
|
import { FiChevronDown, FiChevronUp, FiCheckCircle, FiXCircle, FiClock, FiDatabase } from 'react-icons/fi';
|
|
|
|
/**
|
|
* 步骤结果卡片组件
|
|
*/
|
|
export const StepResultCard = ({ stepResult }) => {
|
|
const [isExpanded, setIsExpanded] = useState(false);
|
|
|
|
const cardBg = useColorModeValue('white', 'rgba(40, 45, 50, 0.8)');
|
|
const borderColor = useColorModeValue('gray.200', 'rgba(255, 215, 0, 0.2)');
|
|
const successColor = useColorModeValue('green.500', 'green.300');
|
|
const errorColor = useColorModeValue('red.500', 'red.300');
|
|
|
|
const getStatusIcon = () => {
|
|
switch (stepResult.status) {
|
|
case 'success':
|
|
return FiCheckCircle;
|
|
case 'failed':
|
|
return FiXCircle;
|
|
default:
|
|
return FiClock;
|
|
}
|
|
};
|
|
|
|
const getStatusColor = () => {
|
|
switch (stepResult.status) {
|
|
case 'success':
|
|
return 'green';
|
|
case 'failed':
|
|
return 'red';
|
|
default:
|
|
return 'gray';
|
|
}
|
|
};
|
|
|
|
const StatusIcon = getStatusIcon();
|
|
const statusColorScheme = getStatusColor();
|
|
|
|
// 格式化数据以便展示
|
|
const formatResult = (data) => {
|
|
if (typeof data === 'string') return data;
|
|
if (Array.isArray(data)) {
|
|
return `找到 ${data.length} 条记录`;
|
|
}
|
|
if (typeof data === 'object') {
|
|
return JSON.stringify(data, null, 2);
|
|
}
|
|
return String(data);
|
|
};
|
|
|
|
return (
|
|
<Box
|
|
bg={cardBg}
|
|
borderRadius="md"
|
|
borderWidth="1px"
|
|
borderColor={borderColor}
|
|
overflow="hidden"
|
|
boxShadow="sm"
|
|
>
|
|
{/* 头部 - 始终可见 */}
|
|
<HStack
|
|
p={3}
|
|
justify="space-between"
|
|
cursor="pointer"
|
|
onClick={() => setIsExpanded(!isExpanded)}
|
|
_hover={{ bg: useColorModeValue('gray.50', 'rgba(50, 55, 60, 0.7)') }}
|
|
>
|
|
<HStack flex={1}>
|
|
<Icon as={StatusIcon} color={`${statusColorScheme}.500`} boxSize={5} />
|
|
<VStack align="stretch" spacing={0} flex={1}>
|
|
<HStack>
|
|
<Text fontSize="sm" fontWeight="bold">
|
|
步骤 {stepResult.step_index + 1}: {stepResult.tool}
|
|
</Text>
|
|
<Badge colorScheme={statusColorScheme} fontSize="xs">
|
|
{stepResult.status === 'success' ? '成功' :
|
|
stepResult.status === 'failed' ? '失败' : '执行中'}
|
|
</Badge>
|
|
</HStack>
|
|
<Text fontSize="xs" color={useColorModeValue('gray.500', '#9BA1A6')}>
|
|
耗时: {stepResult.execution_time?.toFixed(2)}s
|
|
</Text>
|
|
</VStack>
|
|
</HStack>
|
|
|
|
<IconButton
|
|
icon={<Icon as={isExpanded ? FiChevronUp : FiChevronDown} />}
|
|
size="sm"
|
|
variant="ghost"
|
|
aria-label={isExpanded ? "收起" : "展开"}
|
|
/>
|
|
</HStack>
|
|
|
|
{/* 内容 - 可折叠 */}
|
|
<Collapse in={isExpanded} animateOpacity>
|
|
<Box p={3} pt={0}>
|
|
<Divider mb={3} />
|
|
|
|
{/* 参数 */}
|
|
{stepResult.arguments && Object.keys(stepResult.arguments).length > 0 && (
|
|
<VStack align="stretch" spacing={2} mb={3}>
|
|
<HStack>
|
|
<Icon as={FiDatabase} color="blue.500" boxSize={4} />
|
|
<Text fontSize="xs" fontWeight="bold">请求参数:</Text>
|
|
</HStack>
|
|
<Code
|
|
p={2}
|
|
borderRadius="md"
|
|
fontSize="xs"
|
|
whiteSpace="pre-wrap"
|
|
wordBreak="break-word"
|
|
>
|
|
{JSON.stringify(stepResult.arguments, null, 2)}
|
|
</Code>
|
|
</VStack>
|
|
)}
|
|
|
|
{/* 结果或错误 */}
|
|
{stepResult.status === 'success' && stepResult.result && (
|
|
<VStack align="stretch" spacing={2}>
|
|
<Text fontSize="xs" fontWeight="bold">执行结果:</Text>
|
|
<Box
|
|
maxH="300px"
|
|
overflowY="auto"
|
|
p={2}
|
|
bg={useColorModeValue('gray.50', 'rgba(25, 28, 32, 0.6)')}
|
|
borderRadius="md"
|
|
fontSize="xs"
|
|
>
|
|
{typeof stepResult.result === 'string' ? (
|
|
<Text whiteSpace="pre-wrap">{stepResult.result}</Text>
|
|
) : Array.isArray(stepResult.result) ? (
|
|
<VStack align="stretch" spacing={2}>
|
|
<Text fontWeight="bold">找到 {stepResult.result.length} 条记录:</Text>
|
|
{stepResult.result.slice(0, 3).map((item, idx) => (
|
|
<Code key={idx} p={2} borderRadius="md" fontSize="xs">
|
|
{JSON.stringify(item, null, 2)}
|
|
</Code>
|
|
))}
|
|
{stepResult.result.length > 3 && (
|
|
<Text fontSize="xs" color={useColorModeValue('gray.500', '#9BA1A6')}>
|
|
...还有 {stepResult.result.length - 3} 条记录
|
|
</Text>
|
|
)}
|
|
</VStack>
|
|
) : (
|
|
<Code whiteSpace="pre-wrap" wordBreak="break-word">
|
|
{JSON.stringify(stepResult.result, null, 2)}
|
|
</Code>
|
|
)}
|
|
</Box>
|
|
</VStack>
|
|
)}
|
|
|
|
{stepResult.status === 'failed' && stepResult.error && (
|
|
<VStack align="stretch" spacing={2}>
|
|
<Text fontSize="xs" fontWeight="bold" color="red.500">错误信息:</Text>
|
|
<Text fontSize="xs" color={useColorModeValue('red.600', 'red.300')} p={2} bg={useColorModeValue('red.50', 'rgba(220, 38, 38, 0.15)')} borderRadius="md">
|
|
{stepResult.error}
|
|
</Text>
|
|
</VStack>
|
|
)}
|
|
</Box>
|
|
</Collapse>
|
|
</Box>
|
|
);
|
|
};
|
|
|
|
export default StepResultCard;
|