update pay function
This commit is contained in:
@@ -8,78 +8,81 @@ import remarkGfm from 'remark-gfm';
|
||||
import { EChartsRenderer } from './EChartsRenderer';
|
||||
import { logger } from '@utils/logger';
|
||||
|
||||
/**
|
||||
* 清理消息中可能存在的残缺 JSON 片段
|
||||
* 这种情况通常是模型生成时意外混入的工具返回数据
|
||||
* @param {string} text - 原始文本
|
||||
* @returns {string} - 清理后的文本
|
||||
*/
|
||||
const cleanBrokenJson = (text) => {
|
||||
if (!text) return text;
|
||||
|
||||
// 移除可能的残缺 JSON 对象片段(没有开头的 { 但有结尾的 })
|
||||
// 例如: "...一些文字itemStyle": {"color": "#ee6666"}, "smooth": true}]\n}"
|
||||
let cleaned = text;
|
||||
|
||||
// 模式1: 移除以 JSON 属性开始的残缺片段 (如 itemStyle": {...})
|
||||
cleaned = cleaned.replace(/[a-zA-Z_][a-zA-Z0-9_]*"\s*:\s*\{[^{}]*\}(\s*,\s*"[a-zA-Z_][a-zA-Z0-9_]*"\s*:\s*[^,}\]]+)*\s*\}\s*\]\s*\}/g, '');
|
||||
|
||||
// 模式2: 移除孤立的 JSON 数组/对象结尾 (如 ]\n})
|
||||
cleaned = cleaned.replace(/\s*\]\s*\}\s*```\s*$/g, '');
|
||||
|
||||
// 模式3: 移除不完整的 echarts 代码块残留
|
||||
// 匹配没有开始标记的残缺内容
|
||||
cleaned = cleaned.replace(/[a-zA-Z_][a-zA-Z0-9_]*"\s*:\s*\[[^\]]*\]\s*\}\s*```/g, '');
|
||||
|
||||
// 模式4: 清理残留的属性片段
|
||||
cleaned = cleaned.replace(/,?\s*"[a-zA-Z_][a-zA-Z0-9_]*"\s*:\s*(?:true|false|null|\d+|"[^"]*")\s*\}\s*\]\s*\}\s*$/g, '');
|
||||
|
||||
return cleaned.trim();
|
||||
};
|
||||
|
||||
/**
|
||||
* 解析 Markdown 内容,提取 ECharts 代码块
|
||||
* 支持处理不完整的代码块(LLM 输出被截断的情况)
|
||||
* 支持处理:
|
||||
* 1. 正常的换行符 \n
|
||||
* 2. 转义的换行符 \\n(后端 JSON 序列化产生)
|
||||
* 3. 不完整的代码块(LLM 输出被截断)
|
||||
*
|
||||
* @param {string} markdown - Markdown 文本
|
||||
* @returns {Array} - 包含文本和图表的数组
|
||||
*/
|
||||
const parseMarkdownWithCharts = (markdown) => {
|
||||
if (!markdown) return [];
|
||||
|
||||
// 先清理可能的残缺 JSON
|
||||
const cleanedMarkdown = cleanBrokenJson(markdown);
|
||||
let content = markdown;
|
||||
|
||||
// 处理转义的换行符(后端返回的 JSON 字符串可能包含 \\n)
|
||||
// 只处理代码块标记周围的换行符,不破坏 JSON 内部结构
|
||||
// 将 ```echarts\\n 转换为 ```echarts\n
|
||||
content = content.replace(/```echarts\\n/g, '```echarts\n');
|
||||
// 将 \\n``` 转换为 \n```
|
||||
content = content.replace(/\\n```/g, '\n```');
|
||||
|
||||
// 如果整个内容都是转义的换行符格式,进行全局替换
|
||||
// 检测:如果内容中没有真正的换行符但有 \\n,则进行全局替换
|
||||
if (!content.includes('\n') && content.includes('\\n')) {
|
||||
content = content.replace(/\\n/g, '\n');
|
||||
}
|
||||
|
||||
const parts = [];
|
||||
|
||||
// 方案1: 匹配完整的 echarts 代码块
|
||||
const completeEchartsRegex = /```echarts\s*\n([\s\S]*?)```/g;
|
||||
// 方案2: 匹配不完整的 echarts 代码块(没有结束的 ```)
|
||||
const incompleteEchartsRegex = /```echarts\s*\n([\s\S]*?)$/;
|
||||
// 匹配 echarts 代码块的正则表达式
|
||||
// 支持多种格式:
|
||||
// 1. ```echarts\n{...}\n```
|
||||
// 2. ```echarts\n{...}```(末尾无换行)
|
||||
// 3. ```echarts {...}```(同一行开始,虽不推荐但兼容)
|
||||
const echartsBlockRegex = /```echarts\s*\n?([\s\S]*?)```/g;
|
||||
|
||||
let lastIndex = 0;
|
||||
let match;
|
||||
|
||||
// 首先尝试匹配完整的代码块
|
||||
while ((match = completeEchartsRegex.exec(cleanedMarkdown)) !== null) {
|
||||
// 匹配所有 echarts 代码块
|
||||
while ((match = echartsBlockRegex.exec(content)) !== null) {
|
||||
// 添加代码块前的文本
|
||||
if (match.index > lastIndex) {
|
||||
const textBefore = cleanedMarkdown.substring(lastIndex, match.index).trim();
|
||||
const textBefore = content.substring(lastIndex, match.index).trim();
|
||||
if (textBefore) {
|
||||
parts.push({ type: 'text', content: textBefore });
|
||||
}
|
||||
}
|
||||
|
||||
// 添加 ECharts 配置
|
||||
const chartConfig = match[1].trim();
|
||||
// 提取 ECharts 配置内容
|
||||
let chartConfig = match[1].trim();
|
||||
|
||||
// 处理 JSON 内部的转义换行符(恢复为真正的换行,便于后续解析)
|
||||
// 注意:这里的 \\n 在 JSON 内部应该保持为 \n(换行符),不是字面量
|
||||
if (chartConfig.includes('\\n')) {
|
||||
chartConfig = chartConfig.replace(/\\n/g, '\n');
|
||||
}
|
||||
if (chartConfig.includes('\\t')) {
|
||||
chartConfig = chartConfig.replace(/\\t/g, '\t');
|
||||
}
|
||||
|
||||
if (chartConfig) {
|
||||
parts.push({ type: 'chart', content: chartConfig });
|
||||
}
|
||||
|
||||
lastIndex = match.index + match[0].length;
|
||||
}
|
||||
|
||||
// 检查剩余内容是否包含不完整的 echarts 代码块
|
||||
if (lastIndex < cleanedMarkdown.length) {
|
||||
const remainingText = cleanedMarkdown.substring(lastIndex);
|
||||
const incompleteMatch = remainingText.match(incompleteEchartsRegex);
|
||||
// 检查剩余内容
|
||||
if (lastIndex < content.length) {
|
||||
const remainingText = content.substring(lastIndex);
|
||||
|
||||
// 检查是否有不完整的 echarts 代码块(没有结束的 ```)
|
||||
const incompleteMatch = remainingText.match(/```echarts\s*\n?([\s\S]*?)$/);
|
||||
|
||||
if (incompleteMatch) {
|
||||
// 提取不完整代码块之前的文本
|
||||
@@ -89,9 +92,14 @@ const parseMarkdownWithCharts = (markdown) => {
|
||||
}
|
||||
|
||||
// 提取不完整的 echarts 内容
|
||||
const incompleteChartConfig = incompleteMatch[1].trim();
|
||||
let incompleteChartConfig = incompleteMatch[1].trim();
|
||||
// 同样处理转义换行符
|
||||
if (incompleteChartConfig.includes('\\n')) {
|
||||
incompleteChartConfig = incompleteChartConfig.replace(/\\n/g, '\n');
|
||||
}
|
||||
|
||||
if (incompleteChartConfig) {
|
||||
logger.warn('检测到不完整的 echarts 代码块(缺少结束符)', {
|
||||
logger.warn('[MarkdownWithCharts] 检测到不完整的 echarts 代码块', {
|
||||
contentPreview: incompleteChartConfig.substring(0, 100),
|
||||
});
|
||||
parts.push({ type: 'chart', content: incompleteChartConfig });
|
||||
@@ -105,9 +113,23 @@ const parseMarkdownWithCharts = (markdown) => {
|
||||
}
|
||||
}
|
||||
|
||||
// 如果没有找到任何图表,返回整个 markdown 作为文本
|
||||
// 如果没有找到任何部分,返回整个 markdown 作为文本
|
||||
if (parts.length === 0) {
|
||||
parts.push({ type: 'text', content: cleanedMarkdown });
|
||||
parts.push({ type: 'text', content: content });
|
||||
}
|
||||
|
||||
// 开发环境调试
|
||||
if (process.env.NODE_ENV === 'development') {
|
||||
const chartParts = parts.filter(p => p.type === 'chart');
|
||||
if (chartParts.length > 0 || content.includes('echarts')) {
|
||||
logger.info('[MarkdownWithCharts] 解析结果', {
|
||||
inputLength: markdown?.length,
|
||||
hasEchartsKeyword: content.includes('echarts'),
|
||||
hasCodeBlock: content.includes('```'),
|
||||
partsCount: parts.length,
|
||||
partTypes: parts.map(p => p.type),
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
return parts;
|
||||
@@ -121,17 +143,6 @@ 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');
|
||||
|
||||
Reference in New Issue
Block a user