import { AutoChart, BackEndChartType, getChartType } from '@/components/chart'; import { formatSql } from '@/utils'; import { LinkOutlined, ReadOutlined, SyncOutlined } from '@ant-design/icons'; import { Datum } from '@antv/ava'; import { GPTVis, withDefaultChartCode } from '@antv/gpt-vis'; import { Image, Table, Tabs, TabsProps, Tag } from 'antd'; import 'katex/dist/katex.min.css'; import rehypeKatex from 'rehype-katex'; import rehypeRaw from 'rehype-raw'; import remarkGfm from 'remark-gfm'; import remarkMath from 'remark-math'; import ReferencesContent from './ReferencesContent'; import VisAppLink from './VisAppLink'; import VisChatLink from './VisChatLink'; import VisResponse from './VisResponse'; import AgentMessages from './agent-messages'; import AgentPlans from './agent-plans'; import { CodePreview } from './code-preview'; import HtmlPreview from './html-preview'; import SvgPreview from './svg-preview'; import VisChart from './vis-chart'; import VisCode from './vis-code'; import VisConvertError from './vis-convert-error'; import VisDashboard from './vis-dashboard'; import VisPlugin from './vis-plugin'; import { VisThinking } from './vis-thinking'; type MarkdownComponent = Parameters['0']['components']; const customeTags: (keyof JSX.IntrinsicElements)[] = ['custom-view', 'chart-view', 'references', 'summary']; function matchCustomeTagValues(context: string) { const matchValues = customeTags.reduce((acc, tagName) => { // eslint-disable-next-line no-useless-escape const tagReg = new RegExp(`<${tagName}[^>]*\/?>`, 'gi'); context = context.replace(tagReg, matchVal => { acc.push(matchVal); return ''; }); return acc; }, []); return { context, matchValues }; } /** * Preprocess LaTeX syntax, convert \[ \] and \( \) to $$ $$ and $ $ * Also handle some common edge cases * @param content */ export function preprocessLaTeX(content: any): string { if (typeof content !== 'string') { return content; } // Extract code blocks const codeBlocks: string[] = []; content = content.replace(/(```[\s\S]*?```|`[^`\n]+`)/g, match => { codeBlocks.push(match); return `<>`; }); // Replace common LaTeX delimiters with KaTeX supported format content = content .replace(/\\\\\[/g, '$$') // Replace \\[ with $$ .replace(/\\\\\]/g, '$$') // Replace \\] with $$ .replace(/\\\\\(/g, '$') // Replace \\( with $ .replace(/\\\\\)/g, '$') // Replace \\) with $ .replace(/\\\[/g, '$$') // Replace \[ with $$ .replace(/\\\]/g, '$$') // Replace \] with $$ .replace(/\\\(/g, '$') // Replace \( with $ .replace(/\\\)/g, '$'); // Replaces \( with $ // Make sure there is enough line breaks before and after the block formula content = content .replace(/([^\n])\$\$/g, '$1\n\n$$') // Add a blank line before $$ .replace(/\$\$([^\n])/g, '$$\n\n$1'); // Add a blank line after $$ // Handle currency symbols - escape $ that are obviously currency content = content.replace(/\$(?=\d)/g, '\\$'); // Recover code blocks content = content.replace(/<>/g, (_: any, index: string) => codeBlocks[parseInt(index)]); return content; } const codeComponents = { /** * @description * Custom code block rendering, which can be used to render custom components in the code block. * Is it defined in gpt-vis, and the default rendering contains `vis-chart`. */ code: withDefaultChartCode({ languageRenderers: { 'agent-plans': ({ className, children }) => { const content = String(children); /** * @description * In some cases, tags are nested within code syntax, * so it is necessary to extract the tags present in the code block and render them separately. */ const lang = className?.replace('language-', '') || 'javascript'; try { const data = JSON.parse(content) as Parameters[0]['data']; return ; } catch { return ; } }, 'agent-messages': ({ className, children }) => { const content = String(children); const lang = className?.replace('language-', '') || 'javascript'; try { const data = JSON.parse(content) as Parameters[0]['data']; return ; } catch { return ; } }, 'vis-convert-error': ({ className, children }) => { const content = String(children); const lang = className?.replace('language-', '') || 'javascript'; try { const data = JSON.parse(content) as Parameters[0]['data']; return ; } catch { return ; } }, 'vis-dashboard': ({ className, children }) => { const content = String(children); const lang = className?.replace('language-', '') || 'javascript'; try { const data = JSON.parse(content) as Parameters[0]['data']; return ; } catch { return ; } }, 'vis-db-chart': ({ className, children }) => { const content = String(children); const lang = className?.replace('language-', '') || 'javascript'; try { const data = JSON.parse(content) as Parameters[0]['data']; return ; } catch { return ; } }, 'vis-plugin': ({ className, children }) => { const content = String(children); const lang = className?.replace('language-', '') || 'javascript'; try { const data = JSON.parse(content) as Parameters[0]['data']; return ; } catch { return ; } }, 'vis-code': ({ className, children }) => { const content = String(children); const lang = className?.replace('language-', '') || 'javascript'; try { const data = JSON.parse(content) as Parameters[0]['data']; return ; } catch { return ; } }, 'vis-app-link': ({ className, children }) => { const content = String(children); const lang = className?.replace('language-', '') || 'javascript'; try { const data = JSON.parse(content) as Parameters[0]['data']; return ; } catch { return ; } }, 'vis-api-response': ({ className, children }) => { const content = String(children); const lang = className?.replace('language-', '') || 'javascript'; try { const data = JSON.parse(content) as Parameters[0]['data']; return ; } catch { return ; } }, 'vis-thinking': ({ className, children }) => { const content = String(children); const _lang = className?.replace('language-', '') || 'javascript'; return ; }, // Add HTML language processor html: ({ className, children }) => { const content = String(children); const _lang = className; return ; }, // Support for Web languages that mix HTML, CSS, and JS web: ({ className, children }) => { const content = String(children); const _lang = className; return ; }, svg: ({ className, children }) => { const content = String(children); const _lang = className; return ; }, xml: ({ className, children }) => { const content = String(children); const _lang = className; // Check if the content is SVG if (content.includes('')) { return ; } // If it is not SVG, use normal XML highlighting return ; }, }, defaultRenderer({ node, className, children, style, ...props }) { const content = String(children); const lang = className?.replace('language-', '') || ''; const { context, matchValues } = matchCustomeTagValues(content); return ( <> {lang ? ( ) : ( {children} )} {matchValues.join('\n')} ); }, }), }; const basicComponents: MarkdownComponent = { ...codeComponents, ul({ children }) { return
    {children}
; }, ol({ children }) { return
    {children}
; }, li({ children, ordered }) { return (
  • {children}
  • ); }, table({ children }) { return ( {children}
    ); }, thead({ children }) { return {children}; }, th({ children }) { return {children}; }, td({ children }) { return {children}; }, h1({ children }) { return

    {children}

    ; }, h2({ children }) { return

    {children}

    ; }, h3({ children }) { return

    {children}

    ; }, h4({ children }) { return

    {children}

    ; }, a({ children, href }) { return ( ); }, img({ src, alt }) { return (
    {alt}} color='processing'> Image Loading... } fallback='/pictures/fallback.png' />
    ); }, blockquote({ children }) { return (
    {children}
    ); }, button({ children, className, ...restProps }) { if (className === 'chat-link') { const msg = (restProps as any)?.['data-msg']; return {children}; } return ( ); }, }; const returnSqlVal = (val: string) => { const punctuationMap: any = { ',': ',', '。': '.', '?': '?', '!': '!', ':': ':', ';': ';', '“': '"', '”': '"', '‘': "'", '’': "'", '(': '(', ')': ')', '【': '[', '】': ']', '《': '<', '》': '>', '—': '-', '、': ',', '…': '...', }; const regex = new RegExp(Object.keys(punctuationMap).join('|'), 'g'); return val.replace(regex, match => punctuationMap[match]); }; const extraComponents: MarkdownComponent = { 'chart-view': function ({ content, children }) { let data: { data: Datum[]; type: BackEndChartType; sql: string; }; try { data = JSON.parse(content as string); } catch (e) { console.log(e, content); data = { type: 'response_table', sql: '', data: [], }; } const columns = data?.data?.[0] ? Object.keys(data?.data?.[0])?.map(item => { return { title: item, dataIndex: item, key: item, }; }) : []; const ChartItem = { key: 'chart', label: 'Chart', children: , }; const SqlItem = { key: 'sql', label: 'SQL', children: , }; const DataItem = { key: 'data', label: 'Data', children: , }; const TabItems: TabsProps['items'] = data?.type === 'response_table' ? [DataItem, SqlItem] : [ChartItem, SqlItem, DataItem]; return (
    {children}
    ); }, references: function ({ children }) { if (children) { try { const referenceData = JSON.parse(children as string); const references = referenceData.references; return ; } catch { return null; } } }, summary: function ({ children }) { return (

    Document Summary

    {children}
    ); }, }; const markdownComponents = { ...basicComponents, ...extraComponents, }; export const markdownPlugins = { remarkPlugins: [remarkGfm, [remarkMath, { singleDollarTextMath: true }]], rehypePlugins: [rehypeRaw, [rehypeKatex, { output: 'htmlAndMathml' }]], }; export default markdownComponents;