From 8843c81d8ba8631581eee5243615ef79095c47fa Mon Sep 17 00:00:00 2001 From: zzlgreat Date: Sun, 30 Nov 2025 18:31:13 +0800 Subject: [PATCH] update pay function --- src/components/ChatBot/EChartsRenderer.js | 114 +++++++++++++++--- src/components/ChatBot/MarkdownWithCharts.js | 67 ++++++++-- .../components/ChatArea/MessageRenderer.js | 9 +- src/views/AgentChat/hooks/useAgentSessions.ts | 23 +++- 4 files changed, 178 insertions(+), 35 deletions(-) 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, }));