update pay function
This commit is contained in:
@@ -1,8 +1,8 @@
|
|||||||
// src/components/ChatBot/EChartsRenderer.js
|
// src/components/ChatBot/EChartsRenderer.js
|
||||||
// ECharts 图表渲染组件
|
// ECharts 图表渲染组件
|
||||||
|
|
||||||
import React, { useEffect, useRef } from 'react';
|
import React, { useEffect, useRef, useCallback, useState } from 'react';
|
||||||
import { Box, useColorModeValue } from '@chakra-ui/react';
|
import { Box, useColorModeValue, Skeleton } from '@chakra-ui/react';
|
||||||
import * as echarts from 'echarts';
|
import * as echarts from 'echarts';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -67,6 +67,9 @@ const isValidOption = (option) => {
|
|||||||
export const EChartsRenderer = ({ option, height = 400, variant = 'auto' }) => {
|
export const EChartsRenderer = ({ option, height = 400, variant = 'auto' }) => {
|
||||||
const chartRef = useRef(null);
|
const chartRef = useRef(null);
|
||||||
const chartInstance = useRef(null);
|
const chartInstance = useRef(null);
|
||||||
|
const resizeObserverRef = useRef(null);
|
||||||
|
const [isReady, setIsReady] = useState(false);
|
||||||
|
const initTimeoutRef = useRef(null);
|
||||||
|
|
||||||
// 系统颜色模式
|
// 系统颜色模式
|
||||||
const systemBgColor = useColorModeValue('white', 'transparent');
|
const systemBgColor = useColorModeValue('white', 'transparent');
|
||||||
@@ -76,7 +79,8 @@ export const EChartsRenderer = ({ option, height = 400, variant = 'auto' }) => {
|
|||||||
const isDarkMode = variant === 'dark' ? true : variant === 'light' ? false : systemIsDark;
|
const isDarkMode = variant === 'dark' ? true : variant === 'light' ? false : systemIsDark;
|
||||||
const bgColor = variant === 'dark' ? 'transparent' : variant === 'light' ? 'white' : systemBgColor;
|
const bgColor = variant === 'dark' ? 'transparent' : variant === 'light' ? 'white' : systemBgColor;
|
||||||
|
|
||||||
useEffect(() => {
|
// 初始化或更新图表的函数
|
||||||
|
const initChart = useCallback(() => {
|
||||||
if (!chartRef.current || !option) return;
|
if (!chartRef.current || !option) return;
|
||||||
|
|
||||||
// 验证配置是否有效
|
// 验证配置是否有效
|
||||||
@@ -85,9 +89,21 @@ export const EChartsRenderer = ({ option, height = 400, variant = 'auto' }) => {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 确保容器有有效尺寸
|
||||||
|
const containerWidth = chartRef.current.offsetWidth;
|
||||||
|
const containerHeight = chartRef.current.offsetHeight;
|
||||||
|
|
||||||
|
if (containerWidth < 50 || containerHeight < 50) {
|
||||||
|
// 容器尺寸太小,延迟重试
|
||||||
|
initTimeoutRef.current = setTimeout(initChart, 100);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
// 初始化图表(支持深色模式)
|
// 初始化图表(支持深色模式)
|
||||||
if (!chartInstance.current) {
|
if (!chartInstance.current) {
|
||||||
chartInstance.current = echarts.init(chartRef.current, isDarkMode ? 'dark' : null);
|
chartInstance.current = echarts.init(chartRef.current, isDarkMode ? 'dark' : null, {
|
||||||
|
renderer: 'canvas',
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
// 深色模式下的默认文字颜色
|
// 深色模式下的默认文字颜色
|
||||||
@@ -117,6 +133,7 @@ export const EChartsRenderer = ({ option, height = 400, variant = 'auto' }) => {
|
|||||||
// 设置图表配置(使用 try-catch 防止 ECharts 内部错误)
|
// 设置图表配置(使用 try-catch 防止 ECharts 内部错误)
|
||||||
try {
|
try {
|
||||||
chartInstance.current.setOption(defaultOption, true);
|
chartInstance.current.setOption(defaultOption, true);
|
||||||
|
setIsReady(true);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('EChartsRenderer: Failed to render chart', error);
|
console.error('EChartsRenderer: Failed to render chart', error);
|
||||||
// 销毁出错的图表实例
|
// 销毁出错的图表实例
|
||||||
@@ -124,36 +141,93 @@ export const EChartsRenderer = ({ option, height = 400, variant = 'auto' }) => {
|
|||||||
chartInstance.current = null;
|
chartInstance.current = null;
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
}, [option, isDarkMode]);
|
||||||
|
|
||||||
// 响应式调整大小
|
// 处理容器尺寸变化
|
||||||
const handleResize = () => {
|
const handleResize = useCallback(() => {
|
||||||
chartInstance.current?.resize();
|
if (chartInstance.current) {
|
||||||
};
|
// 使用 requestAnimationFrame 确保在下一帧渲染时调整大小
|
||||||
|
requestAnimationFrame(() => {
|
||||||
|
chartInstance.current?.resize();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
// 使用 setTimeout 确保 DOM 已经完全渲染
|
||||||
|
initTimeoutRef.current = setTimeout(() => {
|
||||||
|
initChart();
|
||||||
|
}, 50);
|
||||||
|
|
||||||
|
// 使用 ResizeObserver 监听容器尺寸变化
|
||||||
|
if (chartRef.current && typeof ResizeObserver !== 'undefined') {
|
||||||
|
resizeObserverRef.current = new ResizeObserver((entries) => {
|
||||||
|
// 防抖处理
|
||||||
|
if (initTimeoutRef.current) {
|
||||||
|
clearTimeout(initTimeoutRef.current);
|
||||||
|
}
|
||||||
|
initTimeoutRef.current = setTimeout(() => {
|
||||||
|
handleResize();
|
||||||
|
}, 100);
|
||||||
|
});
|
||||||
|
resizeObserverRef.current.observe(chartRef.current);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 窗口 resize 事件作为备用
|
||||||
window.addEventListener('resize', handleResize);
|
window.addEventListener('resize', handleResize);
|
||||||
|
|
||||||
return () => {
|
return () => {
|
||||||
window.removeEventListener('resize', handleResize);
|
window.removeEventListener('resize', handleResize);
|
||||||
// chartInstance.current?.dispose(); // 不要销毁,避免重新渲染时闪烁
|
if (resizeObserverRef.current) {
|
||||||
|
resizeObserverRef.current.disconnect();
|
||||||
|
}
|
||||||
|
if (initTimeoutRef.current) {
|
||||||
|
clearTimeout(initTimeoutRef.current);
|
||||||
|
}
|
||||||
};
|
};
|
||||||
}, [option, isDarkMode, variant]);
|
}, [initChart, handleResize]);
|
||||||
|
|
||||||
|
// option 变化时重新渲染
|
||||||
|
useEffect(() => {
|
||||||
|
if (chartInstance.current && option && isValidOption(option)) {
|
||||||
|
initChart();
|
||||||
|
}
|
||||||
|
}, [option, initChart]);
|
||||||
|
|
||||||
// 组件卸载时销毁图表
|
// 组件卸载时销毁图表
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
return () => {
|
return () => {
|
||||||
chartInstance.current?.dispose();
|
if (chartInstance.current) {
|
||||||
chartInstance.current = null;
|
chartInstance.current.dispose();
|
||||||
|
chartInstance.current = null;
|
||||||
|
}
|
||||||
};
|
};
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Box
|
<Box position="relative" width="100%" height={`${height}px`}>
|
||||||
ref={chartRef}
|
{!isReady && (
|
||||||
width="100%"
|
<Skeleton
|
||||||
height={`${height}px`}
|
position="absolute"
|
||||||
bg={bgColor}
|
top={0}
|
||||||
borderRadius="md"
|
left={0}
|
||||||
boxShadow="sm"
|
width="100%"
|
||||||
/>
|
height="100%"
|
||||||
|
borderRadius="md"
|
||||||
|
startColor={isDarkMode ? 'gray.700' : 'gray.100'}
|
||||||
|
endColor={isDarkMode ? 'gray.600' : 'gray.200'}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
<Box
|
||||||
|
ref={chartRef}
|
||||||
|
width="100%"
|
||||||
|
height="100%"
|
||||||
|
bg={bgColor}
|
||||||
|
borderRadius="md"
|
||||||
|
boxShadow="sm"
|
||||||
|
opacity={isReady ? 1 : 0}
|
||||||
|
transition="opacity 0.3s ease"
|
||||||
|
/>
|
||||||
|
</Box>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -121,6 +121,17 @@ const parseMarkdownWithCharts = (markdown) => {
|
|||||||
export const MarkdownWithCharts = ({ content, variant = 'auto' }) => {
|
export const MarkdownWithCharts = ({ content, variant = 'auto' }) => {
|
||||||
const parts = parseMarkdownWithCharts(content);
|
const parts = parseMarkdownWithCharts(content);
|
||||||
|
|
||||||
|
// 开发环境调试日志
|
||||||
|
if (process.env.NODE_ENV === 'development' && content?.includes('echarts')) {
|
||||||
|
logger.info('[MarkdownWithCharts] 解析结果', {
|
||||||
|
contentLength: content?.length,
|
||||||
|
contentPreview: content?.substring(0, 100),
|
||||||
|
partsCount: parts.length,
|
||||||
|
partTypes: parts.map(p => p.type),
|
||||||
|
hasChartPart: parts.some(p => p.type === 'chart'),
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
// 系统颜色模式
|
// 系统颜色模式
|
||||||
const systemTextColor = useColorModeValue('gray.700', 'gray.100');
|
const systemTextColor = useColorModeValue('gray.700', 'gray.100');
|
||||||
const systemHeadingColor = useColorModeValue('gray.800', 'gray.50');
|
const systemHeadingColor = useColorModeValue('gray.800', 'gray.50');
|
||||||
@@ -182,16 +193,42 @@ export const MarkdownWithCharts = ({ content, variant = 'auto' }) => {
|
|||||||
{children}
|
{children}
|
||||||
</Box>
|
</Box>
|
||||||
),
|
),
|
||||||
code: ({ inline, children }) =>
|
// 处理代码块和行内代码
|
||||||
inline ? (
|
code: ({ node, inline, className, children, ...props }) => {
|
||||||
<Code fontSize="sm" px={1} bg={codeBg}>
|
// 检查是否是代码块(通过父元素是否为 pre 或通过 className 判断)
|
||||||
|
const isCodeBlock = !inline && (className || (node?.position?.start?.line !== node?.position?.end?.line));
|
||||||
|
|
||||||
|
if (isCodeBlock) {
|
||||||
|
// 代码块样式
|
||||||
|
return (
|
||||||
|
<Code
|
||||||
|
display="block"
|
||||||
|
p={3}
|
||||||
|
borderRadius="md"
|
||||||
|
fontSize="sm"
|
||||||
|
whiteSpace="pre-wrap"
|
||||||
|
bg={codeBg}
|
||||||
|
overflowX="auto"
|
||||||
|
maxW="100%"
|
||||||
|
{...props}
|
||||||
|
>
|
||||||
|
{children}
|
||||||
|
</Code>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
// 行内代码样式
|
||||||
|
return (
|
||||||
|
<Code fontSize="sm" px={1} bg={codeBg} {...props}>
|
||||||
{children}
|
{children}
|
||||||
</Code>
|
</Code>
|
||||||
) : (
|
);
|
||||||
<Code display="block" p={3} borderRadius="md" fontSize="sm" whiteSpace="pre-wrap" bg={codeBg}>
|
},
|
||||||
{children}
|
// 处理 pre 元素,防止嵌套问题
|
||||||
</Code>
|
pre: ({ children }) => (
|
||||||
),
|
<Box as="pre" my={2} overflow="hidden" borderRadius="md">
|
||||||
|
{children}
|
||||||
|
</Box>
|
||||||
|
),
|
||||||
blockquote: ({ children }) => (
|
blockquote: ({ children }) => (
|
||||||
<Box
|
<Box
|
||||||
borderLeftWidth="4px"
|
borderLeftWidth="4px"
|
||||||
@@ -262,9 +299,12 @@ export const MarkdownWithCharts = ({ content, variant = 'auto' }) => {
|
|||||||
} else if (part.type === 'chart') {
|
} else if (part.type === 'chart') {
|
||||||
// 渲染 ECharts 图表
|
// 渲染 ECharts 图表
|
||||||
try {
|
try {
|
||||||
// 清理可能的 Markdown 残留符号
|
// 清理可能的 Markdown 残留符号和代码块标记
|
||||||
let cleanContent = part.content.trim();
|
let cleanContent = part.content.trim();
|
||||||
|
|
||||||
|
// 移除可能残留的代码块结束标记
|
||||||
|
cleanContent = cleanContent.replace(/```\s*$/g, '').trim();
|
||||||
|
|
||||||
// 移除可能的前后空白和不可见字符
|
// 移除可能的前后空白和不可见字符
|
||||||
cleanContent = cleanContent.replace(/^\s+|\s+$/g, '');
|
cleanContent = cleanContent.replace(/^\s+|\s+$/g, '');
|
||||||
|
|
||||||
@@ -427,7 +467,14 @@ export const MarkdownWithCharts = ({ content, variant = 'auto' }) => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Box key={index}>
|
<Box
|
||||||
|
key={index}
|
||||||
|
w="100%"
|
||||||
|
minW="300px"
|
||||||
|
my={3}
|
||||||
|
borderRadius="md"
|
||||||
|
overflow="hidden"
|
||||||
|
>
|
||||||
<EChartsRenderer option={chartOption} height={350} variant={variant} />
|
<EChartsRenderer option={chartOption} height={350} variant={variant} />
|
||||||
</Box>
|
</Box>
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -120,19 +120,21 @@ const MessageRenderer = ({ message, userAvatar }) => {
|
|||||||
|
|
||||||
case MessageTypes.AGENT_RESPONSE:
|
case MessageTypes.AGENT_RESPONSE:
|
||||||
return (
|
return (
|
||||||
<Flex justify="flex-start">
|
<Flex justify="flex-start" w="100%">
|
||||||
<HStack align="start" spacing={3} maxW="75%">
|
<HStack align="start" spacing={3} maxW={{ base: '95%', md: '85%', lg: '80%' }} w="100%">
|
||||||
<Avatar
|
<Avatar
|
||||||
src="/images/agent/基金经理.png"
|
src="/images/agent/基金经理.png"
|
||||||
icon={<Cpu className="w-4 h-4" />}
|
icon={<Cpu className="w-4 h-4" />}
|
||||||
size="sm"
|
size="sm"
|
||||||
bgGradient="linear(to-br, purple.500, pink.500)"
|
bgGradient="linear(to-br, purple.500, pink.500)"
|
||||||
boxShadow="0 0 12px rgba(236, 72, 153, 0.4)"
|
boxShadow="0 0 12px rgba(236, 72, 153, 0.4)"
|
||||||
|
flexShrink={0}
|
||||||
/>
|
/>
|
||||||
<motion.div
|
<motion.div
|
||||||
initial={{ opacity: 0, x: -20 }}
|
initial={{ opacity: 0, x: -20 }}
|
||||||
animate={{ opacity: 1, x: 0 }}
|
animate={{ opacity: 1, x: 0 }}
|
||||||
transition={{ type: 'spring', stiffness: 300, damping: 30 }}
|
transition={{ type: 'spring', stiffness: 300, damping: 30 }}
|
||||||
|
style={{ flex: 1, minWidth: 0 }}
|
||||||
>
|
>
|
||||||
<Card
|
<Card
|
||||||
bg="rgba(255, 255, 255, 0.05)"
|
bg="rgba(255, 255, 255, 0.05)"
|
||||||
@@ -140,12 +142,15 @@ const MessageRenderer = ({ message, userAvatar }) => {
|
|||||||
border="1px solid"
|
border="1px solid"
|
||||||
borderColor="rgba(255, 255, 255, 0.1)"
|
borderColor="rgba(255, 255, 255, 0.1)"
|
||||||
boxShadow="0 8px 32px 0 rgba(31, 38, 135, 0.37)"
|
boxShadow="0 8px 32px 0 rgba(31, 38, 135, 0.37)"
|
||||||
|
w="100%"
|
||||||
>
|
>
|
||||||
<CardBody px={5} py={3}>
|
<CardBody px={5} py={3}>
|
||||||
<Box
|
<Box
|
||||||
fontSize="sm"
|
fontSize="sm"
|
||||||
color="gray.100"
|
color="gray.100"
|
||||||
lineHeight="relaxed"
|
lineHeight="relaxed"
|
||||||
|
w="100%"
|
||||||
|
overflow="hidden"
|
||||||
sx={{
|
sx={{
|
||||||
'& p': { mb: 2 },
|
'& p': { mb: 2 },
|
||||||
'& h1, & h2, & h3': { color: 'gray.50' },
|
'& h1, & h2, & h3': { color: 'gray.50' },
|
||||||
|
|||||||
@@ -108,6 +108,23 @@ export const useAgentSessions = ({
|
|||||||
}
|
}
|
||||||
}, [user?.id]);
|
}, [user?.id]);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 安全解析 JSON 字符串,如果已经是对象则直接返回
|
||||||
|
*/
|
||||||
|
const safeJsonParse = (value: unknown): unknown => {
|
||||||
|
if (!value) return null;
|
||||||
|
if (typeof value === 'object') return value;
|
||||||
|
if (typeof value === 'string') {
|
||||||
|
try {
|
||||||
|
return JSON.parse(value);
|
||||||
|
} catch {
|
||||||
|
console.warn('JSON 解析失败:', value?.toString().substring(0, 100));
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 加载指定会话的历史消息
|
* 加载指定会话的历史消息
|
||||||
*/
|
*/
|
||||||
@@ -125,9 +142,9 @@ export const useAgentSessions = ({
|
|||||||
const formattedMessages: Message[] = history.map((msg: any, idx: number) => ({
|
const formattedMessages: Message[] = history.map((msg: any, idx: number) => ({
|
||||||
id: `${sessionId}-${idx}`,
|
id: `${sessionId}-${idx}`,
|
||||||
type: msg.message_type === 'user' ? MessageTypes.USER : MessageTypes.AGENT_RESPONSE,
|
type: msg.message_type === 'user' ? MessageTypes.USER : MessageTypes.AGENT_RESPONSE,
|
||||||
content: msg.message,
|
content: msg.message || '',
|
||||||
plan: msg.plan ? JSON.parse(msg.plan) : null,
|
plan: safeJsonParse(msg.plan),
|
||||||
stepResults: msg.steps ? JSON.parse(msg.steps) : null,
|
stepResults: safeJsonParse(msg.steps),
|
||||||
timestamp: msg.timestamp,
|
timestamp: msg.timestamp,
|
||||||
}));
|
}));
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user