diff --git a/src/components/ChatBot/EChartsRenderer.js b/src/components/ChatBot/EChartsRenderer.js
index 900b11e3..1d86496c 100644
--- a/src/components/ChatBot/EChartsRenderer.js
+++ b/src/components/ChatBot/EChartsRenderer.js
@@ -1,8 +1,8 @@
// src/components/ChatBot/EChartsRenderer.js
// ECharts 图表渲染组件
-import React, { useEffect, useRef } from 'react';
-import { Box, useColorModeValue } from '@chakra-ui/react';
+import React, { useEffect, useRef, useCallback, useState } from 'react';
+import { Box, useColorModeValue, Skeleton } from '@chakra-ui/react';
import * as echarts from 'echarts';
/**
@@ -67,6 +67,9 @@ const isValidOption = (option) => {
export const EChartsRenderer = ({ option, height = 400, variant = 'auto' }) => {
const chartRef = useRef(null);
const chartInstance = useRef(null);
+ const resizeObserverRef = useRef(null);
+ const [isReady, setIsReady] = useState(false);
+ const initTimeoutRef = useRef(null);
// 系统颜色模式
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 bgColor = variant === 'dark' ? 'transparent' : variant === 'light' ? 'white' : systemBgColor;
- useEffect(() => {
+ // 初始化或更新图表的函数
+ const initChart = useCallback(() => {
if (!chartRef.current || !option) return;
// 验证配置是否有效
@@ -85,9 +89,21 @@ export const EChartsRenderer = ({ option, height = 400, variant = 'auto' }) => {
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) {
- 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 {
chartInstance.current.setOption(defaultOption, true);
+ setIsReady(true);
} catch (error) {
console.error('EChartsRenderer: Failed to render chart', error);
// 销毁出错的图表实例
@@ -124,36 +141,93 @@ export const EChartsRenderer = ({ option, height = 400, variant = 'auto' }) => {
chartInstance.current = null;
return;
}
+ }, [option, isDarkMode]);
- // 响应式调整大小
- const handleResize = () => {
- chartInstance.current?.resize();
- };
+ // 处理容器尺寸变化
+ const handleResize = useCallback(() => {
+ 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);
return () => {
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(() => {
return () => {
- chartInstance.current?.dispose();
- chartInstance.current = null;
+ if (chartInstance.current) {
+ chartInstance.current.dispose();
+ chartInstance.current = null;
+ }
};
}, []);
return (
-
+
+ {!isReady && (
+
+ )}
+
+
);
};
diff --git a/src/components/ChatBot/MarkdownWithCharts.js b/src/components/ChatBot/MarkdownWithCharts.js
index c5c1bda0..11ad572b 100644
--- a/src/components/ChatBot/MarkdownWithCharts.js
+++ b/src/components/ChatBot/MarkdownWithCharts.js
@@ -121,6 +121,17 @@ const parseMarkdownWithCharts = (markdown) => {
export const MarkdownWithCharts = ({ content, variant = 'auto' }) => {
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 systemHeadingColor = useColorModeValue('gray.800', 'gray.50');
@@ -182,16 +193,42 @@ export const MarkdownWithCharts = ({ content, variant = 'auto' }) => {
{children}
),
- code: ({ inline, children }) =>
- inline ? (
-
+ // 处理代码块和行内代码
+ code: ({ node, inline, className, children, ...props }) => {
+ // 检查是否是代码块(通过父元素是否为 pre 或通过 className 判断)
+ const isCodeBlock = !inline && (className || (node?.position?.start?.line !== node?.position?.end?.line));
+
+ if (isCodeBlock) {
+ // 代码块样式
+ return (
+
+ {children}
+
+ );
+ }
+ // 行内代码样式
+ return (
+
{children}
- ) : (
-
- {children}
-
- ),
+ );
+ },
+ // 处理 pre 元素,防止嵌套问题
+ pre: ({ children }) => (
+
+ {children}
+
+ ),
blockquote: ({ children }) => (
{
} else if (part.type === 'chart') {
// 渲染 ECharts 图表
try {
- // 清理可能的 Markdown 残留符号
+ // 清理可能的 Markdown 残留符号和代码块标记
let cleanContent = part.content.trim();
+ // 移除可能残留的代码块结束标记
+ cleanContent = cleanContent.replace(/```\s*$/g, '').trim();
+
// 移除可能的前后空白和不可见字符
cleanContent = cleanContent.replace(/^\s+|\s+$/g, '');
@@ -427,7 +467,14 @@ export const MarkdownWithCharts = ({ content, variant = 'auto' }) => {
}
return (
-
+
);
diff --git a/src/views/AgentChat/components/ChatArea/MessageRenderer.js b/src/views/AgentChat/components/ChatArea/MessageRenderer.js
index 981107b6..004bc85c 100644
--- a/src/views/AgentChat/components/ChatArea/MessageRenderer.js
+++ b/src/views/AgentChat/components/ChatArea/MessageRenderer.js
@@ -120,19 +120,21 @@ const MessageRenderer = ({ message, userAvatar }) => {
case MessageTypes.AGENT_RESPONSE:
return (
-
-
+
+
}
size="sm"
bgGradient="linear(to-br, purple.500, pink.500)"
boxShadow="0 0 12px rgba(236, 72, 153, 0.4)"
+ flexShrink={0}
/>
{
border="1px solid"
borderColor="rgba(255, 255, 255, 0.1)"
boxShadow="0 8px 32px 0 rgba(31, 38, 135, 0.37)"
+ w="100%"
>
{
+ 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) => ({
id: `${sessionId}-${idx}`,
type: msg.message_type === 'user' ? MessageTypes.USER : MessageTypes.AGENT_RESPONSE,
- content: msg.message,
- plan: msg.plan ? JSON.parse(msg.plan) : null,
- stepResults: msg.steps ? JSON.parse(msg.steps) : null,
+ content: msg.message || '',
+ plan: safeJsonParse(msg.plan),
+ stepResults: safeJsonParse(msg.steps),
timestamp: msg.timestamp,
}));