config.tsx 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369
  1. import { AutoChart, BackEndChartType, getChartType } from '@/components/chart';
  2. import { formatSql } from '@/utils';
  3. import { LinkOutlined, ReadOutlined, SyncOutlined } from '@ant-design/icons';
  4. import { Datum } from '@antv/ava';
  5. import { GPTVis, withDefaultChartCode } from '@antv/gpt-vis';
  6. import { Image, Table, Tabs, TabsProps, Tag } from 'antd';
  7. import rehypeRaw from 'rehype-raw';
  8. import remarkGfm from 'remark-gfm';
  9. import ReferencesContent from './ReferencesContent';
  10. import VisAppLink from './VisAppLink';
  11. import VisChatLink from './VisChatLink';
  12. import VisResponse from './VisResponse';
  13. import AgentMessages from './agent-messages';
  14. import AgentPlans from './agent-plans';
  15. import { CodePreview } from './code-preview';
  16. import VisChart from './vis-chart';
  17. import VisCode from './vis-code';
  18. import VisConvertError from './vis-convert-error';
  19. import VisDashboard from './vis-dashboard';
  20. import VisPlugin from './vis-plugin';
  21. type MarkdownComponent = Parameters<typeof GPTVis>['0']['components'];
  22. const customeTags: (keyof JSX.IntrinsicElements)[] = ['custom-view', 'chart-view', 'references', 'summary'];
  23. function matchCustomeTagValues(context: string) {
  24. const matchValues = customeTags.reduce<string[]>((acc, tagName) => {
  25. // eslint-disable-next-line no-useless-escape
  26. const tagReg = new RegExp(`<${tagName}[^>]*\/?>`, 'gi');
  27. context = context.replace(tagReg, matchVal => {
  28. acc.push(matchVal);
  29. return '';
  30. });
  31. return acc;
  32. }, []);
  33. return { context, matchValues };
  34. }
  35. const codeComponents = {
  36. /**
  37. * @description
  38. * Custom code block rendering, which can be used to render custom components in the code block.
  39. * Is it defined in gpt-vis, and the default rendering contains `vis-chart`.
  40. */
  41. code: withDefaultChartCode({
  42. languageRenderers: {
  43. 'agent-plans': ({ className, children }) => {
  44. const content = String(children);
  45. /**
  46. * @description
  47. * In some cases, tags are nested within code syntax,
  48. * so it is necessary to extract the tags present in the code block and render them separately.
  49. */
  50. const lang = className?.replace('language-', '') || 'javascript';
  51. try {
  52. const data = JSON.parse(content) as Parameters<typeof AgentPlans>[0]['data'];
  53. return <AgentPlans data={data} />;
  54. } catch {
  55. return <CodePreview language={lang} code={content} />;
  56. }
  57. },
  58. 'agent-messages': ({ className, children }) => {
  59. const content = String(children);
  60. const lang = className?.replace('language-', '') || 'javascript';
  61. try {
  62. const data = JSON.parse(content) as Parameters<typeof AgentMessages>[0]['data'];
  63. return <AgentMessages data={data} />;
  64. } catch {
  65. return <CodePreview language={lang} code={content} />;
  66. }
  67. },
  68. 'vis-convert-error': ({ className, children }) => {
  69. const content = String(children);
  70. const lang = className?.replace('language-', '') || 'javascript';
  71. try {
  72. const data = JSON.parse(content) as Parameters<typeof VisConvertError>[0]['data'];
  73. return <VisConvertError data={data} />;
  74. } catch {
  75. return <CodePreview language={lang} code={content} />;
  76. }
  77. },
  78. 'vis-dashboard': ({ className, children }) => {
  79. const content = String(children);
  80. const lang = className?.replace('language-', '') || 'javascript';
  81. try {
  82. const data = JSON.parse(content) as Parameters<typeof VisDashboard>[0]['data'];
  83. return <VisDashboard data={data} />;
  84. } catch {
  85. return <CodePreview language={lang} code={content} />;
  86. }
  87. },
  88. 'vis-db-chart': ({ className, children }) => {
  89. const content = String(children);
  90. const lang = className?.replace('language-', '') || 'javascript';
  91. try {
  92. const data = JSON.parse(content) as Parameters<typeof VisChart>[0]['data'];
  93. return <VisChart data={data} />;
  94. } catch {
  95. return <CodePreview language={lang} code={content} />;
  96. }
  97. },
  98. 'vis-plugin': ({ className, children }) => {
  99. const content = String(children);
  100. const lang = className?.replace('language-', '') || 'javascript';
  101. try {
  102. const data = JSON.parse(content) as Parameters<typeof VisPlugin>[0]['data'];
  103. return <VisPlugin data={data} />;
  104. } catch {
  105. return <CodePreview language={lang} code={content} />;
  106. }
  107. },
  108. 'vis-code': ({ className, children }) => {
  109. const content = String(children);
  110. const lang = className?.replace('language-', '') || 'javascript';
  111. try {
  112. const data = JSON.parse(content) as Parameters<typeof VisCode>[0]['data'];
  113. return <VisCode data={data} />;
  114. } catch {
  115. return <CodePreview language={lang} code={content} />;
  116. }
  117. },
  118. 'vis-app-link': ({ className, children }) => {
  119. const content = String(children);
  120. const lang = className?.replace('language-', '') || 'javascript';
  121. try {
  122. const data = JSON.parse(content) as Parameters<typeof VisAppLink>[0]['data'];
  123. return <VisAppLink data={data} />;
  124. } catch {
  125. return <CodePreview language={lang} code={content} />;
  126. }
  127. },
  128. 'vis-api-response': ({ className, children }) => {
  129. const content = String(children);
  130. const lang = className?.replace('language-', '') || 'javascript';
  131. try {
  132. const data = JSON.parse(content) as Parameters<typeof VisResponse>[0]['data'];
  133. return <VisResponse data={data} />;
  134. } catch {
  135. return <CodePreview language={lang} code={content} />;
  136. }
  137. },
  138. },
  139. defaultRenderer({ node, className, children, style, ...props }) {
  140. const content = String(children);
  141. const lang = className?.replace('language-', '') || '';
  142. const { context, matchValues } = matchCustomeTagValues(content);
  143. return (
  144. <>
  145. {lang ? (
  146. <CodePreview code={context} language={lang || 'javascript'} />
  147. ) : (
  148. <code {...props} style={style} className='p-1 mx-1 rounded bg-theme-light dark:bg-theme-dark text-sm'>
  149. {children}
  150. </code>
  151. )}
  152. <GPTVis components={markdownComponents} rehypePlugins={[rehypeRaw]} remarkPlugins={[remarkGfm]}>
  153. {matchValues.join('\n')}
  154. </GPTVis>
  155. </>
  156. );
  157. },
  158. }),
  159. };
  160. const basicComponents: MarkdownComponent = {
  161. ...codeComponents,
  162. ul({ children }) {
  163. return <ul className='py-1'>{children}</ul>;
  164. },
  165. ol({ children }) {
  166. return <ol className='py-1'>{children}</ol>;
  167. },
  168. li({ children, ordered }) {
  169. return (
  170. <li
  171. className={`text-sm leading-7 ml-5 pl-2 text-gray-600 dark:text-gray-300 ${
  172. ordered ? 'list-decimal' : 'list-disc'
  173. }`}
  174. >
  175. {children}
  176. </li>
  177. );
  178. },
  179. table({ children }) {
  180. return (
  181. <table className='my-2 rounded-tl-md rounded-tr-md bg-white dark:bg-gray-800 text-sm rounded-lg overflow-hidden'>
  182. {children}
  183. </table>
  184. );
  185. },
  186. thead({ children }) {
  187. return <thead className='bg-[#fafafa] dark:bg-black font-semibold'>{children}</thead>;
  188. },
  189. th({ children }) {
  190. return <th className='!text-left p-4'>{children}</th>;
  191. },
  192. td({ children }) {
  193. return <td className='p-4 border-t border-[#f0f0f0] dark:border-gray-700'>{children}</td>;
  194. },
  195. h1({ children }) {
  196. return <h3 className='text-2xl font-bold my-4 border-b border-slate-300 pb-4'>{children}</h3>;
  197. },
  198. h2({ children }) {
  199. return <h3 className='text-xl font-bold my-3'>{children}</h3>;
  200. },
  201. h3({ children }) {
  202. return <h3 className='text-lg font-semibold my-2'>{children}</h3>;
  203. },
  204. h4({ children }) {
  205. return <h3 className='text-base font-semibold my-1'>{children}</h3>;
  206. },
  207. a({ children, href }) {
  208. return (
  209. <div className='inline-block text-blue-600 dark:text-blue-400'>
  210. <LinkOutlined className='mr-1' />
  211. <a href={href} target='_blank' rel='noreferrer'>
  212. {children}
  213. </a>
  214. </div>
  215. );
  216. },
  217. img({ src, alt }) {
  218. return (
  219. <div>
  220. <Image
  221. className='min-h-[1rem] max-w-full max-h-full border rounded'
  222. src={src}
  223. alt={alt}
  224. placeholder={
  225. <Tag icon={<SyncOutlined spin />} color='processing'>
  226. Image Loading...
  227. </Tag>
  228. }
  229. fallback='/pictures/fallback.png'
  230. />
  231. </div>
  232. );
  233. },
  234. blockquote({ children }) {
  235. return (
  236. <blockquote className='py-4 px-6 border-l-4 border-blue-600 rounded bg-white my-2 text-gray-500 dark:bg-slate-800 dark:text-gray-200 dark:border-white shadow-sm'>
  237. {children}
  238. </blockquote>
  239. );
  240. },
  241. button({ children, className, ...restProps }) {
  242. if (className === 'chat-link') {
  243. const msg = (restProps as any)?.['data-msg'];
  244. return <VisChatLink msg={msg}>{children}</VisChatLink>;
  245. }
  246. return (
  247. <button className={className} {...restProps}>
  248. {children}
  249. </button>
  250. );
  251. },
  252. };
  253. const returnSqlVal = (val: string) => {
  254. const punctuationMap: any = {
  255. ',': ',',
  256. '。': '.',
  257. '?': '?',
  258. '!': '!',
  259. ':': ':',
  260. ';': ';',
  261. '“': '"',
  262. '”': '"',
  263. '‘': "'",
  264. '’': "'",
  265. '(': '(',
  266. ')': ')',
  267. '【': '[',
  268. '】': ']',
  269. '《': '<',
  270. '》': '>',
  271. '—': '-',
  272. '、': ',',
  273. '…': '...',
  274. };
  275. const regex = new RegExp(Object.keys(punctuationMap).join('|'), 'g');
  276. return val.replace(regex, match => punctuationMap[match]);
  277. };
  278. const extraComponents: MarkdownComponent = {
  279. 'chart-view': function ({ content, children }) {
  280. let data: {
  281. data: Datum[];
  282. type: BackEndChartType;
  283. sql: string;
  284. };
  285. try {
  286. data = JSON.parse(content as string);
  287. } catch (e) {
  288. console.log(e, content);
  289. data = {
  290. type: 'response_table',
  291. sql: '',
  292. data: [],
  293. };
  294. }
  295. const columns = data?.data?.[0]
  296. ? Object.keys(data?.data?.[0])?.map(item => {
  297. return {
  298. title: item,
  299. dataIndex: item,
  300. key: item,
  301. };
  302. })
  303. : [];
  304. const ChartItem = {
  305. key: 'chart',
  306. label: 'Chart',
  307. children: <AutoChart data={data?.data} chartType={getChartType(data?.type)} />,
  308. };
  309. const SqlItem = {
  310. key: 'sql',
  311. label: 'SQL',
  312. children: <CodePreview code={formatSql(returnSqlVal(data?.sql), 'mysql') as string} language={'sql'} />,
  313. };
  314. const DataItem = {
  315. key: 'data',
  316. label: 'Data',
  317. children: <Table dataSource={data?.data} columns={columns} scroll={{ x: true }} virtual={true} />,
  318. };
  319. const TabItems: TabsProps['items'] =
  320. data?.type === 'response_table' ? [DataItem, SqlItem] : [ChartItem, SqlItem, DataItem];
  321. return (
  322. <div>
  323. <Tabs defaultActiveKey={data?.type === 'response_table' ? 'data' : 'chart'} items={TabItems} size='small' />
  324. {children}
  325. </div>
  326. );
  327. },
  328. references: function ({ children }) {
  329. if (children) {
  330. try {
  331. const referenceData = JSON.parse(children as string);
  332. const references = referenceData.references;
  333. return <ReferencesContent references={references} />;
  334. } catch {
  335. return null;
  336. }
  337. }
  338. },
  339. summary: function ({ children }) {
  340. return (
  341. <div>
  342. <p className='mb-2'>
  343. <ReadOutlined className='mr-2' />
  344. <span className='font-semibold'>Document Summary</span>
  345. </p>
  346. <div>{children}</div>
  347. </div>
  348. );
  349. },
  350. };
  351. const markdownComponents = {
  352. ...basicComponents,
  353. ...extraComponents,
  354. };
  355. export default markdownComponents;