update pay function

This commit is contained in:
2025-11-30 18:55:35 +08:00
parent 7b65cac358
commit 455e1c1d32
2 changed files with 114 additions and 385 deletions

View File

@@ -1,13 +1,39 @@
// src/components/ChatBot/MarkdownWithCharts.js
// 支持 ECharts 图表的 Markdown 渲染组件
import React from 'react';
import React, { useMemo } from 'react';
import { Box, Alert, AlertIcon, Text, VStack, Code, useColorModeValue, Table, Thead, Tbody, Tr, Th, Td, TableContainer } from '@chakra-ui/react';
import ReactMarkdown from 'react-markdown';
import remarkGfm from 'remark-gfm';
import { EChartsRenderer } from './EChartsRenderer';
import { logger } from '@utils/logger';
/**
* 稳定的图表组件包装器
* 使用 useMemo 避免 option 对象引用变化导致的重复渲染
*/
const StableChart = React.memo(({ jsonString, height, variant }) => {
const chartOption = useMemo(() => {
try {
return JSON.parse(jsonString);
} catch (e) {
console.error('[StableChart] JSON parse error:', e);
return null;
}
}, [jsonString]);
if (!chartOption) {
return (
<Alert status="warning" borderRadius="md">
<AlertIcon />
<Text fontSize="sm">图表配置解析失败</Text>
</Alert>
);
}
return <EChartsRenderer option={chartOption} height={height} variant={variant} />;
});
/**
* 解析 Markdown 内容,提取 ECharts 代码块
* 支持处理:
@@ -309,194 +335,21 @@ export const MarkdownWithCharts = ({ content, variant = 'auto' }) => {
);
} else if (part.type === 'chart') {
// 渲染 ECharts 图表
// 清理可能的残留字符
let cleanContent = part.content.trim();
cleanContent = cleanContent.replace(/```\s*$/g, '').trim();
// 调试日志
console.log('[MarkdownWithCharts] Rendering chart, content length:', cleanContent.length);
console.log('[MarkdownWithCharts] Content preview:', cleanContent.substring(0, 100));
// 验证 JSON 是否可以解析
try {
// 清理可能的 Markdown 残留符号和代码块标记
let cleanContent = part.content.trim();
// 移除可能残留的代码块结束标记
cleanContent = cleanContent.replace(/```\s*$/g, '').trim();
// 移除可能的前后空白和不可见字符
cleanContent = cleanContent.replace(/^\s+|\s+$/g, '');
// ========== 增强的 JSON 修复逻辑 ==========
// 使用栈来跟踪括号和字符串状态
const stack = [];
let inString = false;
let escape = false;
let stringStartPos = -1;
for (let i = 0; i < cleanContent.length; i++) {
const char = cleanContent[i];
if (escape) {
escape = false;
continue;
}
if (char === '\\' && inString) {
escape = true;
continue;
}
if (char === '"') {
if (inString) {
inString = false;
stringStartPos = -1;
} else {
inString = true;
stringStartPos = i;
}
continue;
}
if (inString) continue;
if (char === '{' || char === '[') {
stack.push(char);
} else if (char === '}') {
if (stack.length > 0 && stack[stack.length - 1] === '{') {
stack.pop();
}
} else if (char === ']') {
if (stack.length > 0 && stack[stack.length - 1] === '[') {
stack.pop();
}
}
}
// 修复1: 如果字符串未闭合,需要关闭字符串
if (inString) {
logger.warn('检测到未闭合的字符串,尝试修复', {
position: stringStartPos,
});
// 找到最后一个有意义的位置(非空白)
let lastMeaningful = cleanContent.length - 1;
while (lastMeaningful > stringStartPos && /\s/.test(cleanContent[lastMeaningful])) {
lastMeaningful--;
}
// 截取到最后一个有意义的字符,然后闭合字符串
cleanContent = cleanContent.substring(0, lastMeaningful + 1) + '"';
logger.info('字符串已修复(添加闭合引号)');
}
// 修复2: 如果栈不为空,说明有未闭合的括号,需要补全
if (stack.length > 0) {
logger.warn('检测到不完整的 ECharts JSON尝试修复', {
unclosed: stack.join(''),
});
// 按照栈的逆序补全闭合括号
while (stack.length > 0) {
const open = stack.pop();
cleanContent += open === '{' ? '}' : ']';
}
logger.info('ECharts JSON 括号已修复');
}
// 修复3: 尝试清理可能的尾部垃圾字符(如截断导致的无效字符)
// 找到最后一个有效的 JSON 结束字符
const lastValidEnd = Math.max(
cleanContent.lastIndexOf('}'),
cleanContent.lastIndexOf(']'),
cleanContent.lastIndexOf('"')
);
if (lastValidEnd > 0 && lastValidEnd < cleanContent.length - 1) {
const tail = cleanContent.substring(lastValidEnd + 1).trim();
// 如果尾部不是有效的 JSON 字符,则截断
if (tail && !/^[,\}\]\s]*$/.test(tail)) {
logger.warn('检测到尾部垃圾字符,截断处理', { tail: tail.substring(0, 50) });
cleanContent = cleanContent.substring(0, lastValidEnd + 1);
}
}
// 尝试解析 JSON
let chartOption;
try {
chartOption = JSON.parse(cleanContent);
} catch (parseError) {
// 如果解析失败,尝试更激进的修复
logger.warn('首次 JSON 解析失败,尝试更激进的修复', { error: parseError.message });
// 尝试找到 JSON 的有效开始和结束
const jsonStart = cleanContent.indexOf('{');
if (jsonStart >= 0) {
let fixedContent = cleanContent.substring(jsonStart);
// 重新计算并补全括号
const fixStack = [];
let fixInString = false;
let fixEscape = false;
for (let i = 0; i < fixedContent.length; i++) {
const char = fixedContent[i];
if (fixEscape) { fixEscape = false; continue; }
if (char === '\\' && fixInString) { fixEscape = true; continue; }
if (char === '"') { fixInString = !fixInString; continue; }
if (fixInString) continue;
if (char === '{' || char === '[') fixStack.push(char);
else if (char === '}' && fixStack.length > 0 && fixStack[fixStack.length - 1] === '{') fixStack.pop();
else if (char === ']' && fixStack.length > 0 && fixStack[fixStack.length - 1] === '[') fixStack.pop();
}
// 如果仍在字符串中,关闭字符串
if (fixInString) fixedContent += '"';
// 补全括号
while (fixStack.length > 0) {
const open = fixStack.pop();
fixedContent += open === '{' ? '}' : ']';
}
chartOption = JSON.parse(fixedContent);
logger.info('激进修复成功');
} else {
throw parseError;
}
}
// 验证是否是有效的 ECharts 配置
if (!chartOption || typeof chartOption !== 'object') {
throw new Error('Invalid chart configuration: not an object');
}
// 验证 series 是否存在且有效
if (!chartOption.series) {
throw new Error('Invalid chart configuration: missing series');
}
const series = Array.isArray(chartOption.series) ? chartOption.series : [chartOption.series];
const hasValidSeries = series.some(s => s && s.data && Array.isArray(s.data) && s.data.length > 0);
if (!hasValidSeries) {
throw new Error('Invalid chart configuration: series has no valid data');
}
// 验证 xAxis如果存在
if (chartOption.xAxis) {
const xAxis = Array.isArray(chartOption.xAxis) ? chartOption.xAxis[0] : chartOption.xAxis;
if (xAxis && xAxis.type === 'category' && (!xAxis.data || !Array.isArray(xAxis.data) || xAxis.data.length === 0)) {
throw new Error('Invalid chart configuration: xAxis category type requires data');
}
}
return (
<Box
key={index}
w="100%"
minW="300px"
my={3}
borderRadius="md"
overflow="hidden"
>
<EChartsRenderer option={chartOption} height={350} variant={variant} />
</Box>
);
} catch (error) {
// 记录详细的错误信息
logger.error('解析 ECharts 配置失败', {
error: error.message,
contentLength: part.content.length,
contentPreview: part.content.substring(0, 200),
errorStack: error.stack
});
const testParse = JSON.parse(cleanContent);
console.log('[MarkdownWithCharts] JSON valid, has series:', !!testParse.series);
} catch (e) {
console.error('[MarkdownWithCharts] JSON parse error:', e.message);
console.log('[MarkdownWithCharts] Problematic content:', cleanContent.substring(0, 300));
return (
<Alert status="warning" key={index} borderRadius="md">
@@ -506,16 +359,29 @@ export const MarkdownWithCharts = ({ content, variant = 'auto' }) => {
图表配置解析失败
</Text>
<Text fontSize="xs" color="gray.600">
错误: {error.message}
错误: {e.message}
</Text>
<Code fontSize="xs" maxW="100%" overflow="auto" whiteSpace="pre-wrap">
{part.content.substring(0, 300)}
{part.content.length > 300 ? '...' : ''}
{cleanContent.substring(0, 300)}
{cleanContent.length > 300 ? '...' : ''}
</Code>
</VStack>
</Alert>
);
}
return (
<Box
key={index}
w="100%"
minW="300px"
my={3}
borderRadius="md"
overflow="hidden"
>
<StableChart jsonString={cleanContent} height={350} variant={variant} />
</Box>
);
}
return null;
})}