update pay function
This commit is contained in:
@@ -14,15 +14,27 @@ export const EChartsRenderer = ({ option, height = 400 }) => {
|
|||||||
const chartRef = useRef(null);
|
const chartRef = useRef(null);
|
||||||
const chartInstance = useRef(null);
|
const chartInstance = useRef(null);
|
||||||
const bgColor = useColorModeValue('white', 'gray.800');
|
const bgColor = useColorModeValue('white', 'gray.800');
|
||||||
|
const isDarkMode = useColorModeValue(false, true);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (!chartRef.current || !option) return;
|
if (!chartRef.current || !option) return;
|
||||||
|
|
||||||
// 初始化图表
|
// 初始化图表(支持深色模式)
|
||||||
if (!chartInstance.current) {
|
if (!chartInstance.current) {
|
||||||
chartInstance.current = echarts.init(chartRef.current);
|
chartInstance.current = echarts.init(chartRef.current, isDarkMode ? 'dark' : null);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 深色模式下的默认文字颜色
|
||||||
|
const darkModeTextStyle = isDarkMode
|
||||||
|
? {
|
||||||
|
textStyle: { color: '#e5e7eb' },
|
||||||
|
title: { textStyle: { color: '#f3f4f6' }, ...option?.title },
|
||||||
|
legend: { textStyle: { color: '#d1d5db' }, ...option?.legend },
|
||||||
|
xAxis: { axisLabel: { color: '#9ca3af' }, axisLine: { lineStyle: { color: '#4b5563' } }, ...option?.xAxis },
|
||||||
|
yAxis: { axisLabel: { color: '#9ca3af' }, axisLine: { lineStyle: { color: '#4b5563' } }, splitLine: { lineStyle: { color: '#374151' } }, ...option?.yAxis },
|
||||||
|
}
|
||||||
|
: {};
|
||||||
|
|
||||||
// 设置默认主题配置
|
// 设置默认主题配置
|
||||||
const defaultOption = {
|
const defaultOption = {
|
||||||
backgroundColor: 'transparent',
|
backgroundColor: 'transparent',
|
||||||
@@ -33,6 +45,7 @@ export const EChartsRenderer = ({ option, height = 400 }) => {
|
|||||||
containLabel: true,
|
containLabel: true,
|
||||||
},
|
},
|
||||||
...option,
|
...option,
|
||||||
|
...darkModeTextStyle,
|
||||||
};
|
};
|
||||||
|
|
||||||
// 设置图表配置
|
// 设置图表配置
|
||||||
@@ -49,7 +62,7 @@ export const EChartsRenderer = ({ option, height = 400 }) => {
|
|||||||
window.removeEventListener('resize', handleResize);
|
window.removeEventListener('resize', handleResize);
|
||||||
// chartInstance.current?.dispose(); // 不要销毁,避免重新渲染时闪烁
|
// chartInstance.current?.dispose(); // 不要销毁,避免重新渲染时闪烁
|
||||||
};
|
};
|
||||||
}, [option]);
|
}, [option, isDarkMode]);
|
||||||
|
|
||||||
// 组件卸载时销毁图表
|
// 组件卸载时销毁图表
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
|
|||||||
@@ -2,7 +2,7 @@
|
|||||||
// 支持 ECharts 图表的 Markdown 渲染组件
|
// 支持 ECharts 图表的 Markdown 渲染组件
|
||||||
|
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
import { Box, Alert, AlertIcon, Text, VStack, Code } from '@chakra-ui/react';
|
import { Box, Alert, AlertIcon, Text, VStack, Code, useColorModeValue } from '@chakra-ui/react';
|
||||||
import ReactMarkdown from 'react-markdown';
|
import ReactMarkdown from 'react-markdown';
|
||||||
import { EChartsRenderer } from './EChartsRenderer';
|
import { EChartsRenderer } from './EChartsRenderer';
|
||||||
import { logger } from '@utils/logger';
|
import { logger } from '@utils/logger';
|
||||||
@@ -59,6 +59,12 @@ const parseMarkdownWithCharts = (markdown) => {
|
|||||||
export const MarkdownWithCharts = ({ content }) => {
|
export const MarkdownWithCharts = ({ content }) => {
|
||||||
const parts = parseMarkdownWithCharts(content);
|
const parts = parseMarkdownWithCharts(content);
|
||||||
|
|
||||||
|
// 深色/浅色模式颜色
|
||||||
|
const textColor = useColorModeValue('gray.700', 'inherit');
|
||||||
|
const headingColor = useColorModeValue('gray.800', 'inherit');
|
||||||
|
const blockquoteColor = useColorModeValue('gray.600', 'gray.300');
|
||||||
|
const codeBg = useColorModeValue('gray.100', 'rgba(255, 255, 255, 0.1)');
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<VStack align="stretch" spacing={4}>
|
<VStack align="stretch" spacing={4}>
|
||||||
{parts.map((part, index) => {
|
{parts.map((part, index) => {
|
||||||
@@ -70,22 +76,22 @@ export const MarkdownWithCharts = ({ content }) => {
|
|||||||
components={{
|
components={{
|
||||||
// 自定义渲染样式
|
// 自定义渲染样式
|
||||||
p: ({ children }) => (
|
p: ({ children }) => (
|
||||||
<Text mb={2} fontSize="sm">
|
<Text mb={2} fontSize="sm" color={textColor}>
|
||||||
{children}
|
{children}
|
||||||
</Text>
|
</Text>
|
||||||
),
|
),
|
||||||
h1: ({ children }) => (
|
h1: ({ children }) => (
|
||||||
<Text fontSize="xl" fontWeight="bold" mb={3}>
|
<Text fontSize="xl" fontWeight="bold" mb={3} color={headingColor}>
|
||||||
{children}
|
{children}
|
||||||
</Text>
|
</Text>
|
||||||
),
|
),
|
||||||
h2: ({ children }) => (
|
h2: ({ children }) => (
|
||||||
<Text fontSize="lg" fontWeight="bold" mb={2}>
|
<Text fontSize="lg" fontWeight="bold" mb={2} color={headingColor}>
|
||||||
{children}
|
{children}
|
||||||
</Text>
|
</Text>
|
||||||
),
|
),
|
||||||
h3: ({ children }) => (
|
h3: ({ children }) => (
|
||||||
<Text fontSize="md" fontWeight="bold" mb={2}>
|
<Text fontSize="md" fontWeight="bold" mb={2} color={headingColor}>
|
||||||
{children}
|
{children}
|
||||||
</Text>
|
</Text>
|
||||||
),
|
),
|
||||||
@@ -100,17 +106,17 @@ export const MarkdownWithCharts = ({ content }) => {
|
|||||||
</Box>
|
</Box>
|
||||||
),
|
),
|
||||||
li: ({ children }) => (
|
li: ({ children }) => (
|
||||||
<Box as="li" fontSize="sm" mb={1}>
|
<Box as="li" fontSize="sm" mb={1} color={textColor}>
|
||||||
{children}
|
{children}
|
||||||
</Box>
|
</Box>
|
||||||
),
|
),
|
||||||
code: ({ inline, children }) =>
|
code: ({ inline, children }) =>
|
||||||
inline ? (
|
inline ? (
|
||||||
<Code fontSize="sm" px={1}>
|
<Code fontSize="sm" px={1} bg={codeBg}>
|
||||||
{children}
|
{children}
|
||||||
</Code>
|
</Code>
|
||||||
) : (
|
) : (
|
||||||
<Code display="block" p={3} borderRadius="md" fontSize="sm" whiteSpace="pre-wrap">
|
<Code display="block" p={3} borderRadius="md" fontSize="sm" whiteSpace="pre-wrap" bg={codeBg}>
|
||||||
{children}
|
{children}
|
||||||
</Code>
|
</Code>
|
||||||
),
|
),
|
||||||
@@ -121,7 +127,7 @@ export const MarkdownWithCharts = ({ content }) => {
|
|||||||
pl={4}
|
pl={4}
|
||||||
py={2}
|
py={2}
|
||||||
fontStyle="italic"
|
fontStyle="italic"
|
||||||
color="gray.600"
|
color={blockquoteColor}
|
||||||
>
|
>
|
||||||
{children}
|
{children}
|
||||||
</Box>
|
</Box>
|
||||||
|
|||||||
@@ -1,8 +1,8 @@
|
|||||||
// src/views/AgentChat/components/ChatArea/ExecutionStepsDisplay.js
|
// src/views/AgentChat/components/ChatArea/ExecutionStepsDisplay.js
|
||||||
// 执行步骤显示组件
|
// 执行步骤显示组件
|
||||||
|
|
||||||
import React from 'react';
|
import React, { useState } from 'react';
|
||||||
import { motion } from 'framer-motion';
|
import { motion, AnimatePresence } from 'framer-motion';
|
||||||
import {
|
import {
|
||||||
Accordion,
|
Accordion,
|
||||||
AccordionItem,
|
AccordionItem,
|
||||||
@@ -16,8 +16,318 @@ import {
|
|||||||
VStack,
|
VStack,
|
||||||
Flex,
|
Flex,
|
||||||
Text,
|
Text,
|
||||||
|
Box,
|
||||||
|
Code,
|
||||||
|
IconButton,
|
||||||
|
Tooltip,
|
||||||
|
Collapse,
|
||||||
} from '@chakra-ui/react';
|
} from '@chakra-ui/react';
|
||||||
import { Activity } from 'lucide-react';
|
import { Activity, ChevronDown, ChevronRight, Copy, Check, Database, FileJson } from 'lucide-react';
|
||||||
|
import { MarkdownWithCharts } from '@components/ChatBot/MarkdownWithCharts';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 格式化结果数据用于显示
|
||||||
|
*/
|
||||||
|
const formatResultData = (data) => {
|
||||||
|
if (data === null || data === undefined) return null;
|
||||||
|
if (typeof data === 'string') return data;
|
||||||
|
try {
|
||||||
|
return JSON.stringify(data, null, 2);
|
||||||
|
} catch {
|
||||||
|
return String(data);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取结果数据的预览文本
|
||||||
|
*/
|
||||||
|
const getResultPreview = (result) => {
|
||||||
|
if (!result) return '无数据';
|
||||||
|
|
||||||
|
// 如果有 data 字段
|
||||||
|
if (result.data) {
|
||||||
|
const data = result.data;
|
||||||
|
// 检查常见的数据结构
|
||||||
|
if (data.chart_data) {
|
||||||
|
return `图表数据: ${data.chart_data.labels?.length || 0} 项`;
|
||||||
|
}
|
||||||
|
if (data.sector_data) {
|
||||||
|
const sectorCount = Object.keys(data.sector_data).length;
|
||||||
|
return `${sectorCount} 个板块分析`;
|
||||||
|
}
|
||||||
|
if (data.stocks) {
|
||||||
|
return `${data.stocks.length} 只股票`;
|
||||||
|
}
|
||||||
|
if (Array.isArray(data)) {
|
||||||
|
return `${data.length} 条记录`;
|
||||||
|
}
|
||||||
|
if (data.date || data.formatted_date) {
|
||||||
|
return `日期: ${data.formatted_date || data.date}`;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 如果结果本身是数组
|
||||||
|
if (Array.isArray(result)) {
|
||||||
|
return `${result.length} 条记录`;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 如果是对象,返回键数量
|
||||||
|
if (typeof result === 'object') {
|
||||||
|
const keys = Object.keys(result);
|
||||||
|
return `${keys.length} 个字段`;
|
||||||
|
}
|
||||||
|
|
||||||
|
return '查看详情';
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 单个步骤卡片组件
|
||||||
|
*/
|
||||||
|
const StepCard = ({ result, idx }) => {
|
||||||
|
const [isExpanded, setIsExpanded] = useState(false);
|
||||||
|
const [copied, setCopied] = useState(false);
|
||||||
|
|
||||||
|
const hasResult = result.result && (
|
||||||
|
typeof result.result === 'object'
|
||||||
|
? Object.keys(result.result).length > 0
|
||||||
|
: result.result
|
||||||
|
);
|
||||||
|
|
||||||
|
const handleCopy = async (e) => {
|
||||||
|
e.stopPropagation();
|
||||||
|
try {
|
||||||
|
await navigator.clipboard.writeText(formatResultData(result.result));
|
||||||
|
setCopied(true);
|
||||||
|
setTimeout(() => setCopied(false), 2000);
|
||||||
|
} catch (err) {
|
||||||
|
console.error('复制失败:', err);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// 渲染结果数据
|
||||||
|
const renderResultData = () => {
|
||||||
|
if (!result.result) return null;
|
||||||
|
|
||||||
|
const data = result.result;
|
||||||
|
|
||||||
|
// 如果有 echarts 图表数据,尝试生成图表
|
||||||
|
if (data.data?.chart_data) {
|
||||||
|
const chartData = data.data.chart_data;
|
||||||
|
const echartsConfig = {
|
||||||
|
title: { text: `${data.data.formatted_date || ''} 涨停概念分布` },
|
||||||
|
tooltip: { trigger: 'axis' },
|
||||||
|
xAxis: {
|
||||||
|
type: 'category',
|
||||||
|
data: chartData.labels || [],
|
||||||
|
axisLabel: { rotate: 30, fontSize: 10 },
|
||||||
|
},
|
||||||
|
yAxis: { type: 'value' },
|
||||||
|
series: [
|
||||||
|
{
|
||||||
|
name: '涨停家数',
|
||||||
|
type: 'bar',
|
||||||
|
data: chartData.counts || [],
|
||||||
|
itemStyle: {
|
||||||
|
color: {
|
||||||
|
type: 'linear',
|
||||||
|
x: 0, y: 0, x2: 0, y2: 1,
|
||||||
|
colorStops: [
|
||||||
|
{ offset: 0, color: '#ff7043' },
|
||||||
|
{ offset: 1, color: '#ff5722' },
|
||||||
|
],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
],
|
||||||
|
};
|
||||||
|
|
||||||
|
const markdownContent = `\`\`\`echarts
|
||||||
|
${JSON.stringify(echartsConfig)}
|
||||||
|
\`\`\``;
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Box mt={3}>
|
||||||
|
<MarkdownWithCharts content={markdownContent} />
|
||||||
|
|
||||||
|
{/* 板块详情 */}
|
||||||
|
{data.data?.sector_data && (
|
||||||
|
<Box mt={3}>
|
||||||
|
<Text fontSize="xs" color="gray.400" mb={2}>
|
||||||
|
板块详情 ({Object.keys(data.data.sector_data).length} 个板块)
|
||||||
|
</Text>
|
||||||
|
<Box
|
||||||
|
maxH="300px"
|
||||||
|
overflowY="auto"
|
||||||
|
fontSize="xs"
|
||||||
|
sx={{
|
||||||
|
'&::-webkit-scrollbar': { width: '4px' },
|
||||||
|
'&::-webkit-scrollbar-track': { bg: 'transparent' },
|
||||||
|
'&::-webkit-scrollbar-thumb': { bg: 'gray.600', borderRadius: 'full' },
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{Object.entries(data.data.sector_data).map(([sector, info]) => (
|
||||||
|
<Box
|
||||||
|
key={sector}
|
||||||
|
mb={2}
|
||||||
|
p={2}
|
||||||
|
bg="rgba(255, 255, 255, 0.02)"
|
||||||
|
borderRadius="md"
|
||||||
|
border="1px solid"
|
||||||
|
borderColor="rgba(255, 255, 255, 0.05)"
|
||||||
|
>
|
||||||
|
<HStack justify="space-between" mb={1}>
|
||||||
|
<Badge colorScheme="purple" fontSize="xs">{sector}</Badge>
|
||||||
|
<Text color="gray.500">{info.count} 只</Text>
|
||||||
|
</HStack>
|
||||||
|
{info.stocks?.slice(0, 3).map((stock, i) => (
|
||||||
|
<Box key={i} mt={1} pl={2} borderLeft="2px solid" borderColor="purple.500">
|
||||||
|
<Text color="gray.300" fontWeight="medium">
|
||||||
|
{stock.sname} ({stock.scode})
|
||||||
|
</Text>
|
||||||
|
{stock.brief && (
|
||||||
|
<Text color="gray.500" fontSize="xs" noOfLines={2}>
|
||||||
|
{stock.brief.replace(/<br>/g, ' ')}
|
||||||
|
</Text>
|
||||||
|
)}
|
||||||
|
</Box>
|
||||||
|
))}
|
||||||
|
{info.stocks?.length > 3 && (
|
||||||
|
<Text color="gray.600" fontSize="xs" mt={1}>
|
||||||
|
还有 {info.stocks.length - 3} 只...
|
||||||
|
</Text>
|
||||||
|
)}
|
||||||
|
</Box>
|
||||||
|
))}
|
||||||
|
</Box>
|
||||||
|
</Box>
|
||||||
|
)}
|
||||||
|
</Box>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 默认显示 JSON 数据
|
||||||
|
return (
|
||||||
|
<Box mt={3}>
|
||||||
|
<Code
|
||||||
|
display="block"
|
||||||
|
p={3}
|
||||||
|
borderRadius="md"
|
||||||
|
fontSize="xs"
|
||||||
|
whiteSpace="pre-wrap"
|
||||||
|
bg="rgba(0, 0, 0, 0.3)"
|
||||||
|
color="gray.300"
|
||||||
|
maxH="300px"
|
||||||
|
overflowY="auto"
|
||||||
|
sx={{
|
||||||
|
'&::-webkit-scrollbar': { width: '4px' },
|
||||||
|
'&::-webkit-scrollbar-track': { bg: 'transparent' },
|
||||||
|
'&::-webkit-scrollbar-thumb': { bg: 'gray.600', borderRadius: 'full' },
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{formatResultData(data)}
|
||||||
|
</Code>
|
||||||
|
</Box>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<motion.div
|
||||||
|
initial={{ opacity: 0, x: -10 }}
|
||||||
|
animate={{ opacity: 1, x: 0 }}
|
||||||
|
transition={{ delay: idx * 0.05 }}
|
||||||
|
>
|
||||||
|
<Card
|
||||||
|
bg="rgba(255, 255, 255, 0.03)"
|
||||||
|
backdropFilter="blur(10px)"
|
||||||
|
border="1px solid"
|
||||||
|
borderColor={isExpanded ? 'rgba(192, 132, 252, 0.3)' : 'rgba(255, 255, 255, 0.1)'}
|
||||||
|
transition="all 0.2s"
|
||||||
|
_hover={{
|
||||||
|
borderColor: 'rgba(192, 132, 252, 0.2)',
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<CardBody p={3}>
|
||||||
|
{/* 步骤头部 - 可点击展开 */}
|
||||||
|
<Flex
|
||||||
|
align="center"
|
||||||
|
justify="space-between"
|
||||||
|
gap={2}
|
||||||
|
cursor={hasResult ? 'pointer' : 'default'}
|
||||||
|
onClick={() => hasResult && setIsExpanded(!isExpanded)}
|
||||||
|
>
|
||||||
|
<HStack flex={1} spacing={2}>
|
||||||
|
{hasResult && (
|
||||||
|
<Box color="gray.500" transition="transform 0.2s" transform={isExpanded ? 'rotate(90deg)' : 'rotate(0deg)'}>
|
||||||
|
<ChevronRight className="w-3 h-3" />
|
||||||
|
</Box>
|
||||||
|
)}
|
||||||
|
<Text fontSize="xs" fontWeight="medium" color="gray.300">
|
||||||
|
步骤 {idx + 1}: {result.tool_name || result.tool}
|
||||||
|
</Text>
|
||||||
|
</HStack>
|
||||||
|
|
||||||
|
<HStack spacing={2}>
|
||||||
|
{hasResult && (
|
||||||
|
<Tooltip label={copied ? '已复制' : '复制数据'} placement="top">
|
||||||
|
<IconButton
|
||||||
|
size="xs"
|
||||||
|
variant="ghost"
|
||||||
|
icon={copied ? <Check className="w-3 h-3" /> : <Copy className="w-3 h-3" />}
|
||||||
|
onClick={handleCopy}
|
||||||
|
color={copied ? 'green.400' : 'gray.500'}
|
||||||
|
_hover={{ bg: 'rgba(255, 255, 255, 0.1)' }}
|
||||||
|
aria-label="复制"
|
||||||
|
/>
|
||||||
|
</Tooltip>
|
||||||
|
)}
|
||||||
|
<Badge
|
||||||
|
bgGradient={
|
||||||
|
result.status === 'success'
|
||||||
|
? 'linear(to-r, green.500, teal.500)'
|
||||||
|
: 'linear(to-r, red.500, orange.500)'
|
||||||
|
}
|
||||||
|
color="white"
|
||||||
|
variant="subtle"
|
||||||
|
boxShadow={
|
||||||
|
result.status === 'success'
|
||||||
|
? '0 2px 8px rgba(16, 185, 129, 0.3)'
|
||||||
|
: '0 2px 8px rgba(239, 68, 68, 0.3)'
|
||||||
|
}
|
||||||
|
>
|
||||||
|
{result.status}
|
||||||
|
</Badge>
|
||||||
|
</HStack>
|
||||||
|
</Flex>
|
||||||
|
|
||||||
|
{/* 步骤元信息 */}
|
||||||
|
<HStack mt={1} spacing={3} fontSize="xs" color="gray.500">
|
||||||
|
{result.execution_time && (
|
||||||
|
<Text>⏱️ {result.execution_time.toFixed(2)}s</Text>
|
||||||
|
)}
|
||||||
|
{hasResult && (
|
||||||
|
<HStack spacing={1}>
|
||||||
|
<Database className="w-3 h-3" />
|
||||||
|
<Text>{getResultPreview(result.result)}</Text>
|
||||||
|
</HStack>
|
||||||
|
)}
|
||||||
|
</HStack>
|
||||||
|
|
||||||
|
{/* 错误信息 */}
|
||||||
|
{result.error && (
|
||||||
|
<Text fontSize="xs" color="red.400" mt={1}>
|
||||||
|
⚠️ {result.error}
|
||||||
|
</Text>
|
||||||
|
)}
|
||||||
|
|
||||||
|
{/* 展开的详细数据 */}
|
||||||
|
<Collapse in={isExpanded} animateOpacity>
|
||||||
|
{isExpanded && renderResultData()}
|
||||||
|
</Collapse>
|
||||||
|
</CardBody>
|
||||||
|
</Card>
|
||||||
|
</motion.div>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* ExecutionStepsDisplay - 执行步骤显示组件
|
* ExecutionStepsDisplay - 执行步骤显示组件
|
||||||
@@ -61,51 +371,7 @@ const ExecutionStepsDisplay = ({ steps, plan }) => {
|
|||||||
<AccordionPanel pb={4}>
|
<AccordionPanel pb={4}>
|
||||||
<VStack spacing={2} align="stretch">
|
<VStack spacing={2} align="stretch">
|
||||||
{steps.map((result, idx) => (
|
{steps.map((result, idx) => (
|
||||||
<motion.div
|
<StepCard key={idx} result={result} idx={idx} />
|
||||||
key={idx}
|
|
||||||
initial={{ opacity: 0, x: -10 }}
|
|
||||||
animate={{ opacity: 1, x: 0 }}
|
|
||||||
transition={{ delay: idx * 0.05 }}
|
|
||||||
>
|
|
||||||
<Card
|
|
||||||
bg="rgba(255, 255, 255, 0.03)"
|
|
||||||
backdropFilter="blur(10px)"
|
|
||||||
border="1px solid"
|
|
||||||
borderColor="rgba(255, 255, 255, 0.1)"
|
|
||||||
>
|
|
||||||
<CardBody p={3}>
|
|
||||||
<Flex align="start" justify="space-between" gap={2}>
|
|
||||||
<Text fontSize="xs" fontWeight="medium" color="gray.300">
|
|
||||||
步骤 {idx + 1}: {result.tool_name}
|
|
||||||
</Text>
|
|
||||||
<Badge
|
|
||||||
bgGradient={
|
|
||||||
result.status === 'success'
|
|
||||||
? 'linear(to-r, green.500, teal.500)'
|
|
||||||
: 'linear(to-r, red.500, orange.500)'
|
|
||||||
}
|
|
||||||
color="white"
|
|
||||||
variant="subtle"
|
|
||||||
boxShadow={
|
|
||||||
result.status === 'success'
|
|
||||||
? '0 2px 8px rgba(16, 185, 129, 0.3)'
|
|
||||||
: '0 2px 8px rgba(239, 68, 68, 0.3)'
|
|
||||||
}
|
|
||||||
>
|
|
||||||
{result.status}
|
|
||||||
</Badge>
|
|
||||||
</Flex>
|
|
||||||
<Text fontSize="xs" color="gray.500" mt={1}>
|
|
||||||
{result.execution_time?.toFixed(2)}s
|
|
||||||
</Text>
|
|
||||||
{result.error && (
|
|
||||||
<Text fontSize="xs" color="red.400" mt={1}>
|
|
||||||
⚠️ {result.error}
|
|
||||||
</Text>
|
|
||||||
)}
|
|
||||||
</CardBody>
|
|
||||||
</Card>
|
|
||||||
</motion.div>
|
|
||||||
))}
|
))}
|
||||||
</VStack>
|
</VStack>
|
||||||
</AccordionPanel>
|
</AccordionPanel>
|
||||||
|
|||||||
@@ -19,6 +19,7 @@ import {
|
|||||||
import { Cpu, User, Copy, ThumbsUp, ThumbsDown, File } from 'lucide-react';
|
import { Cpu, User, Copy, ThumbsUp, ThumbsDown, File } from 'lucide-react';
|
||||||
import { MessageTypes } from '../../constants/messageTypes';
|
import { MessageTypes } from '../../constants/messageTypes';
|
||||||
import ExecutionStepsDisplay from './ExecutionStepsDisplay';
|
import ExecutionStepsDisplay from './ExecutionStepsDisplay';
|
||||||
|
import { MarkdownWithCharts } from '@components/ChatBot/MarkdownWithCharts';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* MessageRenderer - 消息渲染器组件
|
* MessageRenderer - 消息渲染器组件
|
||||||
@@ -139,9 +140,21 @@ const MessageRenderer = ({ message, userAvatar }) => {
|
|||||||
boxShadow="0 8px 32px 0 rgba(31, 38, 135, 0.37)"
|
boxShadow="0 8px 32px 0 rgba(31, 38, 135, 0.37)"
|
||||||
>
|
>
|
||||||
<CardBody px={5} py={3}>
|
<CardBody px={5} py={3}>
|
||||||
<Text fontSize="sm" color="gray.100" whiteSpace="pre-wrap" lineHeight="relaxed">
|
<Box
|
||||||
{message.content}
|
fontSize="sm"
|
||||||
</Text>
|
color="gray.100"
|
||||||
|
lineHeight="relaxed"
|
||||||
|
sx={{
|
||||||
|
'& p': { mb: 2 },
|
||||||
|
'& h1, & h2, & h3': { color: 'gray.50' },
|
||||||
|
'& ul, & ol': { pl: 4 },
|
||||||
|
'& li': { mb: 1 },
|
||||||
|
'& code': { bg: 'rgba(255,255,255,0.1)', px: 1, borderRadius: 'sm' },
|
||||||
|
'& blockquote': { borderLeftColor: 'purple.400', color: 'gray.300' },
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<MarkdownWithCharts content={message.content} />
|
||||||
|
</Box>
|
||||||
|
|
||||||
{message.stepResults && message.stepResults.length > 0 && (
|
{message.stepResults && message.stepResults.length > 0 && (
|
||||||
<Box mt={3}>
|
<Box mt={3}>
|
||||||
|
|||||||
@@ -164,7 +164,7 @@ export const useAgentChat = ({
|
|||||||
isUser: m.type === MessageTypes.USER,
|
isUser: m.type === MessageTypes.USER,
|
||||||
content: m.content,
|
content: m.content,
|
||||||
})),
|
})),
|
||||||
user_id: user?.id || 'anonymous',
|
user_id: user?.id ? String(user.id) : 'anonymous',
|
||||||
user_nickname: user?.nickname || '匿名用户',
|
user_nickname: user?.nickname || '匿名用户',
|
||||||
user_avatar: user?.avatar || '',
|
user_avatar: user?.avatar || '',
|
||||||
subscription_type: user?.subscription_type || 'free',
|
subscription_type: user?.subscription_type || 'free',
|
||||||
|
|||||||
@@ -95,7 +95,7 @@ export const useAgentSessions = ({
|
|||||||
setIsLoadingSessions(true);
|
setIsLoadingSessions(true);
|
||||||
try {
|
try {
|
||||||
const response = await axios.get('/mcp/agent/sessions', {
|
const response = await axios.get('/mcp/agent/sessions', {
|
||||||
params: { user_id: user.id, limit: 50 },
|
params: { user_id: String(user.id), limit: 50 },
|
||||||
});
|
});
|
||||||
|
|
||||||
if (response.data.success) {
|
if (response.data.success) {
|
||||||
|
|||||||
Reference in New Issue
Block a user