feat(web): Unified frontend code style (#1923)

Co-authored-by: Fangyin Cheng <staneyffer@gmail.com>
Co-authored-by: 谨欣 <echo.cmy@antgroup.com>
Co-authored-by: 严志勇 <yanzhiyong@tiansuixiansheng.com>
Co-authored-by: yanzhiyong <932374019@qq.com>
This commit is contained in:
Dreammy23
2024-08-30 14:03:06 +08:00
committed by GitHub
parent f866580703
commit 471689ba20
247 changed files with 4960 additions and 4546 deletions

View File

@@ -30,10 +30,8 @@ const ReferencesContentView: React.FC<{ references: any }> = ({ references }) =>
),
key: reference.name,
children: (
<div className="h-full overflow-y-auto">
{reference?.chunks?.map((chunk: any) => (
<MarkDownContext key={chunk.id}>{chunk.content}</MarkDownContext>
))}
<div className='h-full overflow-y-auto'>
{reference?.chunks?.map((chunk: any) => <MarkDownContext key={chunk.id}>{chunk.content}</MarkDownContext>)}
</div>
),
};
@@ -42,21 +40,21 @@ const ReferencesContentView: React.FC<{ references: any }> = ({ references }) =>
return (
<div>
<Divider className="mb-1 mt-0" dashed />
<div className="flex text-sm gap-2 text-blue-400" onClick={() => setOpen(true)}>
<Divider className='mb-1 mt-0' dashed />
<div className='flex text-sm gap-2 text-blue-400' onClick={() => setOpen(true)}>
<LinkOutlined />
<span className="text-sm"></span>
<span className='text-sm'></span>
</div>
<Drawer
open={open}
title="回复引用"
title='回复引用'
placement={isMobile ? 'bottom' : 'right'}
onClose={() => setOpen(false)}
destroyOnClose={true}
className="p-0"
className='p-0'
{...(!isMobile && { width: '30%' })}
>
<Tabs items={items} size="small" />
<Tabs items={items} size='small' />
</Drawer>
</div>
);
@@ -66,7 +64,7 @@ const ReferencesContent: React.FC<{ references: any }> = ({ references }) => {
try {
const data = JSON.parse(references);
return <ReferencesContentView references={data} />;
} catch (e) {
} catch {
return null;
}
};

View File

@@ -1,6 +1,6 @@
import AppDefaultIcon from '@/new-components/common/AppDefaultIcon';
import { CheckCircleOutlined, ClockCircleOutlined, CloseCircleOutlined, LoadingOutlined } from '@ant-design/icons';
import { Spin, Tooltip, Typography } from 'antd';
import { Spin, Typography } from 'antd';
import React, { useMemo } from 'react';
interface VisAppLinkProps {
@@ -16,9 +16,9 @@ const VisAppLink: React.FC<{ data: VisAppLinkProps }> = ({ data }) => {
case 'todo':
return <ClockCircleOutlined />;
case 'failed':
return <CloseCircleOutlined className="text-[rgb(255,77,79)]" />;
return <CloseCircleOutlined className='text-[rgb(255,77,79)]' />;
case 'complete':
return <CheckCircleOutlined className="text-[rgb(82,196,26)]" />;
return <CheckCircleOutlined className='text-[rgb(82,196,26)]' />;
case 'running':
return <Spin indicator={<LoadingOutlined style={{ fontSize: 24 }} spin />} />;
default:
@@ -27,14 +27,14 @@ const VisAppLink: React.FC<{ data: VisAppLinkProps }> = ({ data }) => {
}, [data]);
if (!data) return null;
return (
<div className="flex flex-col p-2 border pr-4 rounded-md min-w-fit w-2/5">
<div className="flex items-center justify-between">
<div className="flex items-center">
<div className='flex flex-col p-2 border pr-4 rounded-md min-w-fit w-2/5'>
<div className='flex items-center justify-between'>
<div className='flex items-center'>
<AppDefaultIcon scene={'chat_agent'} width={8} height={8} />
<div className="flex flex-col flex-1 ml-2">
<div className="flex items-center text-sm dark:text-[rgba(255,255,255,0.85)] gap-2">{data?.app_name}</div>
<div className='flex flex-col flex-1 ml-2'>
<div className='flex items-center text-sm dark:text-[rgba(255,255,255,0.85)] gap-2'>{data?.app_name}</div>
<Typography.Text
className="text-sm text-[#525964] dark:text-[rgba(255,255,255,0.65)] leading-6"
className='text-sm text-[#525964] dark:text-[rgba(255,255,255,0.65)] leading-6'
ellipsis={{
tooltip: true,
}}
@@ -43,10 +43,10 @@ const VisAppLink: React.FC<{ data: VisAppLinkProps }> = ({ data }) => {
</Typography.Text>
</div>
</div>
<div className="text-2xl ml-1">{statusRender}</div>
<div className='text-2xl ml-1'>{statusRender}</div>
</div>
{data.status === 'failed' && data.msg && (
<Typography.Text type="danger" className="pl-12 text-xs mt-2">
<Typography.Text type='danger' className='pl-12 text-xs mt-2'>
{data.msg}
</Typography.Text>
)}

View File

@@ -12,13 +12,13 @@ const VisChatLink: React.FC<VisChatLinkProps> = ({ children, msg }) => {
const { handleChat: mobileHandleChat } = useContext(MobileChatContext);
return (
<Button
className="ml-1 inline text-xs"
className='ml-1 inline text-xs'
onClick={() => {
mobileHandleChat?.(msg);
webHandleChat?.(msg);
}}
type="dashed"
size="small"
type='dashed'
size='small'
>
{children || '点击分析当前异常'}
</Button>

View File

@@ -1,12 +1,12 @@
import { ChatContext } from '@/app/chat-context';
import { LoadingOutlined } from '@ant-design/icons';
import { GPTVis } from '@antv/gpt-vis';
import JsonView from '@uiw/react-json-view';
import { githubDarkTheme } from '@uiw/react-json-view/githubDark';
import { githubLightTheme } from '@uiw/react-json-view/githubLight';
import { Alert, Spin } from 'antd';
import classNames from 'classnames';
import React, { useContext, useMemo } from 'react';
import { GPTVis } from '@antv/gpt-vis';
import rehypeRaw from 'rehype-raw';
import remarkGfm from 'remark-gfm';
@@ -32,43 +32,39 @@ const VisResponse: React.FC<{ data: VisResponseProps }> = ({ data }) => {
case 'running':
return 'warning';
default:
undefined;
return undefined;
}
}, [data]);
if (!data) return null;
const theme = mode === 'dark' ? githubDarkTheme : githubLightTheme;
return (
<div className="flex flex-1 flex-col">
<div className='flex flex-1 flex-col'>
<Alert
className={classNames("mb-4", {
"bg-[#fafafa] border-[transparent]": !type,
className={classNames('mb-4', {
'bg-[#fafafa] border-[transparent]': !type,
})}
message={data.name}
type={type}
{...(type && { showIcon: true })}
{...(type === "warning" && {
{...(type === 'warning' && {
icon: <Spin indicator={<LoadingOutlined spin />} />,
})}
/>
{data.result && (
<JsonView
style={{ ...theme, width: "100%", padding: 10 }}
style={{ ...theme, width: '100%', padding: 10 }}
className={classNames({
"bg-[#fafafa]": mode === "light",
'bg-[#fafafa]': mode === 'light',
})}
value={JSON.parse(data.result || "{}")}
value={JSON.parse(data.result || '{}')}
enableClipboard={false}
displayDataTypes={false}
objectSortKeys={false}
/>
)}
{data.err_msg && (
<GPTVis
components={markdownComponents}
remarkPlugins={[remarkGfm]}
rehypePlugins={[rehypeRaw]}
>
<GPTVis components={markdownComponents} remarkPlugins={[remarkGfm]} rehypePlugins={[rehypeRaw]}>
{data.err_msg}
</GPTVis>
)}

View File

@@ -3,8 +3,8 @@ import { SwapRightOutlined } from '@ant-design/icons';
import { GPTVis } from '@antv/gpt-vis';
import rehypeRaw from 'rehype-raw';
import remarkGfm from 'remark-gfm';
import markdownComponents from './config';
import ReferencesContent from './ReferencesContent';
import markdownComponents from './config';
interface Props {
data: {
@@ -21,31 +21,21 @@ function AgentMessages({ data }: Props) {
return (
<>
{data.map((item, index) => (
<div key={index} className="rounded">
<div className="flex items-center mb-3 text-sm">
{item.model ? (
<ModelIcon model={item.model} />
) : (
<div className="rounded-full w-6 h-6 bg-gray-100" />
)}
<div className="ml-2 opacity-70">
<div key={index} className='rounded'>
<div className='flex items-center mb-3 text-sm'>
{item.model ? <ModelIcon model={item.model} /> : <div className='rounded-full w-6 h-6 bg-gray-100' />}
<div className='ml-2 opacity-70'>
{item.sender}
<SwapRightOutlined className="mx-2 text-base" />
<SwapRightOutlined className='mx-2 text-base' />
{item.receiver}
</div>
</div>
<div className="whitespace-normal text-sm mb-3">
<GPTVis
components={markdownComponents}
remarkPlugins={[remarkGfm]}
rehypePlugins={[rehypeRaw]}
>
<div className='whitespace-normal text-sm mb-3'>
<GPTVis components={markdownComponents} remarkPlugins={[remarkGfm]} rehypePlugins={[rehypeRaw]}>
{item.markdown}
</GPTVis>
</div>
{item.resource && item.resource !== "null" && (
<ReferencesContent references={item.resource} />
)}
{item.resource && item.resource !== 'null' && <ReferencesContent references={item.resource} />}
</div>
))}
</>

View File

@@ -1,6 +1,6 @@
import { CaretRightOutlined, CheckOutlined, ClockCircleOutlined } from '@ant-design/icons';
import { Collapse } from 'antd';
import { GPTVis } from '@antv/gpt-vis';
import { Collapse } from 'antd';
import rehypeRaw from 'rehype-raw';
import remarkGfm from 'remark-gfm';
@@ -22,7 +22,7 @@ function AgentPlans({ data }: Props) {
return (
<Collapse
bordered
className="my-3"
className='my-3'
expandIcon={({ isActive }) => <CaretRightOutlined rotate={isActive ? 90 : 0} />}
items={data.map((item, index) => {
return {
@@ -32,19 +32,15 @@ function AgentPlans({ data }: Props) {
<span>
{item.name} - {item.agent}
</span>
{item.status === "complete" ? (
<CheckOutlined className="!text-green-500 ml-2" />
{item.status === 'complete' ? (
<CheckOutlined className='!text-green-500 ml-2' />
) : (
<ClockCircleOutlined className="!text-gray-500 ml-2" />
<ClockCircleOutlined className='!text-gray-500 ml-2' />
)}
</div>
),
children: (
<GPTVis
components={markdownComponents}
rehypePlugins={[rehypeRaw]}
remarkPlugins={[remarkGfm]}
>
<GPTVis components={markdownComponents} rehypePlugins={[rehypeRaw]} remarkPlugins={[remarkGfm]}>
{item.markdown}
</GPTVis>
),

View File

@@ -1,13 +1,12 @@
import { AutoChart, BackEndChartType, getChartType } from '@/components/chart/autoChart';
import { formatSql } from '@/utils';
import { Datum } from '@antv/ava';
import { Table, Tabs, TabsProps } from 'antd';
import React from 'react';
import { AutoChart, BackEndChartType, getChartType } from '@/components/chart/autoChart';
import { CodePreview } from './code-preview';
import { formatSql } from '@/utils';
function ChartView({ data, type, sql }: { data: Datum[]; type: BackEndChartType; sql: string }) {
const columns = data?.[0]
? Object.keys(data?.[0])?.map((item) => {
? Object.keys(data?.[0])?.map(item => {
return {
title: item,
dataIndex: item,
@@ -23,7 +22,7 @@ function ChartView({ data, type, sql }: { data: Datum[]; type: BackEndChartType;
const SqlItem = {
key: 'sql',
label: 'SQL',
children: <CodePreview language="sql" code={formatSql(sql ?? '', 'mysql') as string} />,
children: <CodePreview language='sql' code={formatSql(sql ?? '', 'mysql') as string} />,
};
const DataItem = {
key: 'data',
@@ -32,7 +31,7 @@ function ChartView({ data, type, sql }: { data: Datum[]; type: BackEndChartType;
};
const TabItems: TabsProps['items'] = type === 'response_table' ? [DataItem, SqlItem] : [ChartItem, SqlItem, DataItem];
return <Tabs defaultActiveKey={type === 'response_table' ? 'data' : 'chart'} items={TabItems} size="small" />;
return <Tabs defaultActiveKey={type === 'response_table' ? 'data' : 'chart'} items={TabItems} size='small' />;
}
export default ChartView;

View File

@@ -1,10 +1,10 @@
import { Button, message } from 'antd';
import { ChatContext } from '@/app/chat-context';
import { CopyOutlined } from '@ant-design/icons';
import { oneDark, coldarkDark } from 'react-syntax-highlighter/dist/esm/styles/prism';
import { Prism as SyntaxHighlighter } from 'react-syntax-highlighter';
import { Button, message } from 'antd';
import copy from 'copy-to-clipboard';
import { CSSProperties, useContext } from 'react';
import { ChatContext } from '@/app/chat-context';
import { Prism as SyntaxHighlighter } from 'react-syntax-highlighter';
import { coldarkDark, oneDark } from 'react-syntax-highlighter/dist/esm/styles/prism';
interface Props {
code: string;
@@ -18,17 +18,21 @@ export function CodePreview({ code, light, dark, language, customStyle }: Props)
const { mode } = useContext(ChatContext);
return (
<div className="relative">
<div className='relative'>
<Button
className="absolute right-3 top-2 text-gray-300 hover:!text-gray-200 bg-gray-700"
type="text"
className='absolute right-3 top-2 text-gray-300 hover:!text-gray-200 bg-gray-700'
type='text'
icon={<CopyOutlined />}
onClick={() => {
const success = copy(code);
message[success ? 'success' : 'error'](success ? '复制成功' : '复制失败');
}}
/>
<SyntaxHighlighter customStyle={customStyle} language={language} style={mode === 'dark' ? dark ?? coldarkDark : light ?? oneDark}>
<SyntaxHighlighter
customStyle={customStyle}
language={language}
style={mode === 'dark' ? (dark ?? coldarkDark) : (light ?? oneDark)}
>
{code}
</SyntaxHighlighter>
</div>

View File

@@ -1,39 +1,35 @@
import { LinkOutlined, ReadOutlined, SyncOutlined } from "@ant-design/icons";
import { GPTVis, withDefaultChartCode } from "@antv/gpt-vis";
import { Table, Image, Tag, Tabs, TabsProps } from "antd";
import { AutoChart, BackEndChartType, getChartType } from "@/components/chart";
import { Datum } from "@antv/ava";
import rehypeRaw from "rehype-raw";
import remarkGfm from "remark-gfm";
import AgentMessages from "./agent-messages";
import AgentPlans from "./agent-plans";
import { CodePreview } from "./code-preview";
import ReferencesContent from "./ReferencesContent";
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 VisAppLink from "./VisAppLink";
import VisChatLink from "./VisChatLink";
import VisResponse from "./VisResponse";
import { formatSql } from "@/utils";
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 rehypeRaw from 'rehype-raw';
import remarkGfm from 'remark-gfm';
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 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';
type MarkdownComponent = Parameters<typeof GPTVis>["0"]["components"];
type MarkdownComponent = Parameters<typeof GPTVis>['0']['components'];
const customeTags: (keyof JSX.IntrinsicElements)[] = [
"custom-view",
"chart-view",
"references",
"summary",
];
const customeTags: (keyof JSX.IntrinsicElements)[] = ['custom-view', 'chart-view', 'references', 'summary'];
function matchCustomeTagValues(context: string) {
const matchValues = customeTags.reduce<string[]>((acc, tagName) => {
const tagReg = new RegExp(`<${tagName}[^>]*\/?>`, "gi");
context = context.replace(tagReg, (matchVal) => {
// eslint-disable-next-line no-useless-escape
const tagReg = new RegExp(`<${tagName}[^>]*\/?>`, 'gi');
context = context.replace(tagReg, matchVal => {
acc.push(matchVal);
return "";
return '';
});
return acc;
}, []);
@@ -48,146 +44,119 @@ const codeComponents = {
*/
code: withDefaultChartCode({
languageRenderers: {
"agent-plans": ({ node, className, children, style }) => {
'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";
const lang = className?.replace('language-', '') || 'javascript';
try {
const data = JSON.parse(content) as Parameters<
typeof AgentPlans
>[0]["data"];
const data = JSON.parse(content) as Parameters<typeof AgentPlans>[0]['data'];
return <AgentPlans data={data} />;
} catch (e) {
} catch {
return <CodePreview language={lang} code={content} />;
}
},
"agent-messages": ({ node, className, children, style }) => {
'agent-messages': ({ className, children }) => {
const content = String(children);
const lang = className?.replace("language-", "") || "javascript";
const lang = className?.replace('language-', '') || 'javascript';
try {
const data = JSON.parse(content) as Parameters<
typeof AgentMessages
>[0]["data"];
const data = JSON.parse(content) as Parameters<typeof AgentMessages>[0]['data'];
return <AgentMessages data={data} />;
} catch (e) {
} catch {
return <CodePreview language={lang} code={content} />;
}
},
"vis-convert-error": ({ node, className, children, style }) => {
'vis-convert-error': ({ className, children }) => {
const content = String(children);
const lang = className?.replace("language-", "") || "javascript";
const lang = className?.replace('language-', '') || 'javascript';
try {
const data = JSON.parse(content) as Parameters<
typeof VisConvertError
>[0]["data"];
const data = JSON.parse(content) as Parameters<typeof VisConvertError>[0]['data'];
return <VisConvertError data={data} />;
} catch (e) {
} catch {
return <CodePreview language={lang} code={content} />;
}
},
"vis-dashboard": ({ node, className, children, style }) => {
'vis-dashboard': ({ className, children }) => {
const content = String(children);
const lang = className?.replace("language-", "") || "javascript";
const lang = className?.replace('language-', '') || 'javascript';
try {
const data = JSON.parse(content) as Parameters<
typeof VisDashboard
>[0]["data"];
const data = JSON.parse(content) as Parameters<typeof VisDashboard>[0]['data'];
return <VisDashboard data={data} />;
} catch (e) {
} catch {
return <CodePreview language={lang} code={content} />;
}
},
"vis-db-chart": ({ node, className, children, style }) => {
'vis-chart': ({ className, children }) => {
const content = String(children);
const lang = className?.replace("language-", "") || "javascript";
const lang = className?.replace('language-', '') || 'javascript';
try {
const data = JSON.parse(content) as Parameters<
typeof VisChart
>[0]["data"];
const data = JSON.parse(content) as Parameters<typeof VisChart>[0]['data'];
return <VisChart data={data} />;
} catch (e) {
} catch {
return <CodePreview language={lang} code={content} />;
}
},
"vis-plugin": ({ node, className, children, style }) => {
'vis-plugin': ({ className, children }) => {
const content = String(children);
const lang = className?.replace("language-", "") || "javascript";
const lang = className?.replace('language-', '') || 'javascript';
try {
const data = JSON.parse(content) as Parameters<
typeof VisPlugin
>[0]["data"];
const data = JSON.parse(content) as Parameters<typeof VisPlugin>[0]['data'];
return <VisPlugin data={data} />;
} catch (e) {
} catch {
return <CodePreview language={lang} code={content} />;
}
},
"vis-code": ({ node, className, children, style, ...props }) => {
'vis-code': ({ className, children }) => {
const content = String(children);
const lang = className?.replace("language-", "") || "javascript";
const lang = className?.replace('language-', '') || 'javascript';
try {
const data = JSON.parse(content) as Parameters<
typeof VisCode
>[0]["data"];
const data = JSON.parse(content) as Parameters<typeof VisCode>[0]['data'];
return <VisCode data={data} />;
} catch (e) {
} catch {
return <CodePreview language={lang} code={content} />;
}
},
"vis-app-link": ({ node, className, children, style, ...props }) => {
'vis-app-link': ({ className, children }) => {
const content = String(children);
const lang = className?.replace("language-", "") || "javascript";
const lang = className?.replace('language-', '') || 'javascript';
try {
const data = JSON.parse(content) as Parameters<
typeof VisAppLink
>[0]["data"];
const data = JSON.parse(content) as Parameters<typeof VisAppLink>[0]['data'];
return <VisAppLink data={data} />;
} catch (e) {
} catch {
return <CodePreview language={lang} code={content} />;
}
},
"vis-api-response": ({ node, className, children, style, ...props }) => {
'vis-api-response': ({ className, children }) => {
const content = String(children);
const lang = className?.replace("language-", "") || "javascript";
const lang = className?.replace('language-', '') || 'javascript';
try {
const data = JSON.parse(content) as Parameters<
typeof VisResponse
>[0]["data"];
const data = JSON.parse(content) as Parameters<typeof VisResponse>[0]['data'];
return <VisResponse data={data} />;
} catch (e) {
} catch {
return <CodePreview language={lang} code={content} />;
}
},
},
defaultRenderer({ node, className, children, style, ...props }) {
const content = String(children);
const lang = className?.replace("language-", "") || "";
const lang = className?.replace('language-', '') || '';
const { context, matchValues } = matchCustomeTagValues(content);
console.log(111, { node, className, children, style, ...props }, lang);
return (
<>
{lang ? (
<CodePreview code={context} language={lang || "javascript"} />
<CodePreview code={context} language={lang || 'javascript'} />
) : (
<code
{...props}
style={style}
className="p-1 mx-1 rounded bg-theme-light dark:bg-theme-dark text-sm"
>
<code {...props} style={style} className='p-1 mx-1 rounded bg-theme-light dark:bg-theme-dark text-sm'>
{children}
</code>
)}
<GPTVis
components={markdownComponents}
rehypePlugins={[rehypeRaw]}
remarkPlugins={[remarkGfm]}
>
{matchValues.join("\n")}
<GPTVis components={markdownComponents} rehypePlugins={[rehypeRaw]} remarkPlugins={[remarkGfm]}>
{matchValues.join('\n')}
</GPTVis>
</>
);
@@ -198,16 +167,16 @@ const codeComponents = {
const basicComponents: MarkdownComponent = {
...codeComponents,
ul({ children }) {
return <ul className="py-1">{children}</ul>;
return <ul className='py-1'>{children}</ul>;
},
ol({ children }) {
return <ol className="py-1">{children}</ol>;
return <ol className='py-1'>{children}</ol>;
},
li({ children, ordered }) {
return (
<li
className={`text-sm leading-7 ml-5 pl-2 text-gray-600 dark:text-gray-300 ${
ordered ? "list-decimal" : "list-disc"
ordered ? 'list-decimal' : 'list-disc'
}`}
>
{children}
@@ -216,49 +185,37 @@ const basicComponents: MarkdownComponent = {
},
table({ children }) {
return (
<table className="my-2 rounded-tl-md rounded-tr-md bg-white dark:bg-gray-800 text-sm rounded-lg overflow-hidden">
<table className='my-2 rounded-tl-md rounded-tr-md bg-white dark:bg-gray-800 text-sm rounded-lg overflow-hidden'>
{children}
</table>
);
},
thead({ children }) {
return (
<thead className="bg-[#fafafa] dark:bg-black font-semibold">
{children}
</thead>
);
return <thead className='bg-[#fafafa] dark:bg-black font-semibold'>{children}</thead>;
},
th({ children }) {
return <th className="!text-left p-4">{children}</th>;
return <th className='!text-left p-4'>{children}</th>;
},
td({ children }) {
return (
<td className="p-4 border-t border-[#f0f0f0] dark:border-gray-700">
{children}
</td>
);
return <td className='p-4 border-t border-[#f0f0f0] dark:border-gray-700'>{children}</td>;
},
h1({ children }) {
return (
<h3 className="text-2xl font-bold my-4 border-b border-slate-300 pb-4">
{children}
</h3>
);
return <h3 className='text-2xl font-bold my-4 border-b border-slate-300 pb-4'>{children}</h3>;
},
h2({ children }) {
return <h3 className="text-xl font-bold my-3">{children}</h3>;
return <h3 className='text-xl font-bold my-3'>{children}</h3>;
},
h3({ children }) {
return <h3 className="text-lg font-semibold my-2">{children}</h3>;
return <h3 className='text-lg font-semibold my-2'>{children}</h3>;
},
h4({ children }) {
return <h3 className="text-base font-semibold my-1">{children}</h3>;
return <h3 className='text-base font-semibold my-1'>{children}</h3>;
},
a({ children, href }) {
return (
<div className="inline-block text-blue-600 dark:text-blue-400">
<LinkOutlined className="mr-1" />
<a href={href} target="_blank">
<div className='inline-block text-blue-600 dark:text-blue-400'>
<LinkOutlined className='mr-1' />
<a href={href} target='_blank' rel='noreferrer'>
{children}
</a>
</div>
@@ -268,29 +225,29 @@ const basicComponents: MarkdownComponent = {
return (
<div>
<Image
className="min-h-[1rem] max-w-full max-h-full border rounded"
className='min-h-[1rem] max-w-full max-h-full border rounded'
src={src}
alt={alt}
placeholder={
<Tag icon={<SyncOutlined spin />} color="processing">
<Tag icon={<SyncOutlined spin />} color='processing'>
Image Loading...
</Tag>
}
fallback="/pictures/fallback.png"
fallback='/pictures/fallback.png'
/>
</div>
);
},
blockquote({ children }) {
return (
<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">
<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'>
{children}
</blockquote>
);
},
button({ children, className, ...restProps }) {
if (className === "chat-link") {
const msg = (restProps as any)?.["data-msg"];
if (className === 'chat-link') {
const msg = (restProps as any)?.['data-msg'];
return <VisChatLink msg={msg}>{children}</VisChatLink>;
}
return (
@@ -303,32 +260,32 @@ const basicComponents: MarkdownComponent = {
const returnSqlVal = (val: string) => {
const punctuationMap: any = {
"": ",",
"。": ".",
"": "?",
"": "!",
"": ":",
"": ";",
"“": '"',
"”": '"',
"": "'",
"": "'",
"": "(",
"": ")",
"【": "[",
"】": "]",
"《": "<",
"》": ">",
"—": "-",
"、": ",",
"…": "...",
'': ',',
'。': '.',
'': '?',
'': '!',
'': ':',
'': ';',
'“': '"',
'”': '"',
'': "'",
'': "'",
'': '(',
'': ')',
'【': '[',
'】': ']',
'《': '<',
'》': '>',
'—': '-',
'、': ',',
'…': '...',
};
const regex = new RegExp(Object.keys(punctuationMap).join("|"), "g");
return val.replace(regex, (match) => punctuationMap[match]);
const regex = new RegExp(Object.keys(punctuationMap).join('|'), 'g');
return val.replace(regex, match => punctuationMap[match]);
};
const extraComponents: MarkdownComponent = {
"chart-view": function ({ content, children }) {
'chart-view': function ({ content, children }) {
let data: {
data: Datum[];
type: BackEndChartType;
@@ -339,15 +296,14 @@ const extraComponents: MarkdownComponent = {
} catch (e) {
console.log(e, content);
data = {
type: "response_table",
sql: "",
type: 'response_table',
sql: '',
data: [],
};
}
console.log(111, data);
const columns = data?.data?.[0]
? Object.keys(data?.data?.[0])?.map((item) => {
? Object.keys(data?.data?.[0])?.map(item => {
return {
title: item,
dataIndex: item,
@@ -357,57 +313,37 @@ const extraComponents: MarkdownComponent = {
: [];
const ChartItem = {
key: "chart",
label: "Chart",
children: (
<AutoChart data={data?.data} chartType={getChartType(data?.type)} />
),
key: 'chart',
label: 'Chart',
children: <AutoChart data={data?.data} chartType={getChartType(data?.type)} />,
};
const SqlItem = {
key: "sql",
label: "SQL",
children: (
<CodePreview
code={formatSql(returnSqlVal(data?.sql), "mysql") as string}
language={"sql"}
/>
),
key: 'sql',
label: 'SQL',
children: <CodePreview code={formatSql(returnSqlVal(data?.sql), 'mysql') as string} language={'sql'} />,
};
const DataItem = {
key: "data",
label: "Data",
children: (
<Table
dataSource={data?.data}
columns={columns}
scroll={{ x: true }}
virtual={true}
/>
),
key: 'data',
label: 'Data',
children: <Table dataSource={data?.data} columns={columns} scroll={{ x: true }} virtual={true} />,
};
const TabItems: TabsProps["items"] =
data?.type === "response_table"
? [DataItem, SqlItem]
: [ChartItem, SqlItem, DataItem];
const TabItems: TabsProps['items'] =
data?.type === 'response_table' ? [DataItem, SqlItem] : [ChartItem, SqlItem, DataItem];
return (
<div>
<Tabs
defaultActiveKey={data?.type === "response_table" ? "data" : "chart"}
items={TabItems}
size="small"
/>
<Tabs defaultActiveKey={data?.type === 'response_table' ? 'data' : 'chart'} items={TabItems} size='small' />
{children}
</div>
);
},
references: function ({ title, references, children }) {
references: function ({ children }) {
if (children) {
try {
const referenceData = JSON.parse(children as string);
const references = referenceData.references;
return <ReferencesContent references={references} />;
} catch (error) {
} catch {
return null;
}
}
@@ -415,9 +351,9 @@ const extraComponents: MarkdownComponent = {
summary: function ({ children }) {
return (
<div>
<p className="mb-2">
<ReadOutlined className="mr-2" />
<span className="font-semibold">Document Summary</span>
<p className='mb-2'>
<ReadOutlined className='mr-2' />
<span className='font-semibold'>Document Summary</span>
</p>
<div>{children}</div>
</div>

View File

@@ -1,12 +1,21 @@
import { PropsWithChildren, ReactNode, memo, useContext, useMemo } from 'react';
import { CheckOutlined, ClockCircleOutlined, CloseOutlined, CodeOutlined, LoadingOutlined, RobotOutlined, UserOutlined } from '@ant-design/icons';
import { GPTVis } from '@antv/gpt-vis';
import { IChatDialogueMessageSchema } from '@/types/chat';
import rehypeRaw from 'rehype-raw';
import classNames from 'classnames';
import { Tag } from 'antd';
import { renderModelIcon } from '../header/model-selector';
import { ChatContext } from '@/app/chat-context';
import { IChatDialogueMessageSchema } from '@/types/chat';
import {
CheckOutlined,
ClockCircleOutlined,
CloseOutlined,
CodeOutlined,
LoadingOutlined,
RobotOutlined,
UserOutlined,
} from '@ant-design/icons';
import { GPTVis } from '@antv/gpt-vis';
import { Tag } from 'antd';
import classNames from 'classnames';
import { PropsWithChildren, ReactNode, memo, useContext, useMemo } from 'react';
import ReactMarkdown from 'react-markdown';
import rehypeRaw from 'rehype-raw';
import { renderModelIcon } from '../header/model-selector';
import markdownComponents from './config';
interface Props {
@@ -22,7 +31,7 @@ interface Props {
onLinkClick?: () => void;
}
type MarkdownComponent = Parameters<typeof GPTVis>["0"]["components"];
type MarkdownComponent = Parameters<typeof GPTVis>['0']['components'];
type DBGPTView = {
name: string;
@@ -34,19 +43,19 @@ type DBGPTView = {
const pluginViewStatusMapper: Record<DBGPTView['status'], { bgClass: string; icon: ReactNode }> = {
todo: {
bgClass: 'bg-gray-500',
icon: <ClockCircleOutlined className="ml-2" />,
icon: <ClockCircleOutlined className='ml-2' />,
},
runing: {
bgClass: 'bg-blue-500',
icon: <LoadingOutlined className="ml-2" />,
icon: <LoadingOutlined className='ml-2' />,
},
failed: {
bgClass: 'bg-red-500',
icon: <CloseOutlined className="ml-2" />,
icon: <CloseOutlined className='ml-2' />,
},
completed: {
bgClass: 'bg-green-500',
icon: <CheckOutlined className="ml-2" />,
icon: <CheckOutlined className='ml-2' />,
},
};
@@ -63,7 +72,11 @@ function ChatContent({ children, content, isChartChat, onLinkClick }: PropsWithC
const { context, model_name, role } = content;
const isRobot = role === 'view';
const { relations, value, cachePluginContext } = useMemo<{ relations: string[]; value: string; cachePluginContext: DBGPTView[] }>(() => {
const { relations, value, cachePluginContext } = useMemo<{
relations: string[];
value: string;
cachePluginContext: DBGPTView[];
}>(() => {
if (typeof context !== 'string') {
return {
relations: [],
@@ -76,7 +89,7 @@ function ChatContent({ children, content, isChartChat, onLinkClick }: PropsWithC
const cachePluginContext: DBGPTView[] = [];
let cacheIndex = 0;
const result = value.replace(/<dbgpt-view[^>]*>[^<]*<\/dbgpt-view>/gi, (matchVal) => {
const result = value.replace(/<dbgpt-view[^>]*>[^<]*<\/dbgpt-view>/gi, matchVal => {
try {
const pluginVal = matchVal.replaceAll('\n', '\\n').replace(/<[^>]*>|<\/[^>]*>/gm, '');
const pluginContext = JSON.parse(pluginVal) as DBGPTView;
@@ -111,27 +124,19 @@ function ChatContent({ children, content, isChartChat, onLinkClick }: PropsWithC
const { name, status, err_msg, result } = cachePluginContext[index];
const { bgClass, icon } = pluginViewStatusMapper[status] ?? {};
return (
<div className="bg-white dark:bg-[#212121] rounded-lg overflow-hidden my-2 flex flex-col lg:max-w-[80%]">
<div
className={classNames(
"flex px-4 md:px-6 py-2 items-center text-white text-sm",
bgClass
)}
>
<div className='bg-white dark:bg-[#212121] rounded-lg overflow-hidden my-2 flex flex-col lg:max-w-[80%]'>
<div className={classNames('flex px-4 md:px-6 py-2 items-center text-white text-sm', bgClass)}>
{name}
{icon}
</div>
{result ? (
<div className="px-4 md:px-6 py-4 text-sm">
<GPTVis
components={markdownComponents}
rehypePlugins={[rehypeRaw]}
>
{result ?? ""}
</GPTVis>
<div className='px-4 md:px-6 py-4 text-sm'>
<ReactMarkdown components={markdownComponents} rehypePlugins={[rehypeRaw]}>
{result ?? ''}
</ReactMarkdown>
</div>
) : (
<div className="px-4 md:px-6 py-4 text-sm">{err_msg}</div>
<div className='px-4 md:px-6 py-4 text-sm'>{err_msg}</div>
)}
</div>
);
@@ -140,57 +145,41 @@ function ChatContent({ children, content, isChartChat, onLinkClick }: PropsWithC
[context, cachePluginContext],
);
if (!isRobot && !context) return <div className="h-12"></div>;
if (!isRobot && !context) return <div className='h-12'></div>;
return (
<div
className={classNames(
"relative flex flex-wrap w-full p-2 md:p-4 rounded-xl break-words",
{
"bg-white dark:bg-[#232734]": isRobot,
"lg:w-full xl:w-full pl-0": [
"chat_with_db_execute",
"chat_dashboard",
].includes(scene),
}
)}
className={classNames('relative flex flex-wrap w-full p-2 md:p-4 rounded-xl break-words', {
'bg-white dark:bg-[#232734]': isRobot,
'lg:w-full xl:w-full pl-0': ['chat_with_db_execute', 'chat_dashboard'].includes(scene),
})}
>
<div className="mr-2 flex flex-shrink-0 items-center justify-center h-7 w-7 rounded-full text-lg sm:mr-4">
{isRobot ? (
renderModelIcon(model_name) || <RobotOutlined />
) : (
<UserOutlined />
)}
<div className='mr-2 flex flex-shrink-0 items-center justify-center h-7 w-7 rounded-full text-lg sm:mr-4'>
{isRobot ? renderModelIcon(model_name) || <RobotOutlined /> : <UserOutlined />}
</div>
<div className="flex-1 overflow-hidden items-center text-md leading-8 pb-2">
<div className='flex-1 overflow-hidden items-center text-md leading-8 pb-2'>
{/* User Input */}
{!isRobot && typeof context === "string" && context}
{!isRobot && typeof context === 'string' && context}
{/* Render Report */}
{isRobot && isChartChat && typeof context === "object" && (
{isRobot && isChartChat && typeof context === 'object' && (
<div>
{`[${context.template_name}]: `}
<span
className="text-theme-primary cursor-pointer"
onClick={onLinkClick}
>
<CodeOutlined className="mr-1" />
{context.template_introduce || "More Details"}
<span className='text-theme-primary cursor-pointer' onClick={onLinkClick}>
<CodeOutlined className='mr-1' />
{context.template_introduce || 'More Details'}
</span>
</div>
)}
{/* Markdown */}
{isRobot && typeof context === "string" && (
<GPTVis
components={{ ...markdownComponents, ...extraMarkdownComponents }}
rehypePlugins={[rehypeRaw]}
>
{isRobot && typeof context === 'string' && (
<GPTVis components={{ ...markdownComponents, ...extraMarkdownComponents }} rehypePlugins={[rehypeRaw]}>
{formatMarkdownVal(value)}
</GPTVis>
)}
{!!relations?.length && (
<div className="flex flex-wrap mt-2">
<div className='flex flex-wrap mt-2'>
{relations?.map((value, index) => (
<Tag color="#108ee9" key={value + index}>
<Tag color='#108ee9' key={value + index}>
{value}
</Tag>
))}

View File

@@ -1,6 +1,6 @@
import { BackEndChartType } from '@/components/chart';
import ChartView from './chart-view';
import { Datum } from '@antv/ava';
import ChartView from './chart-view';
interface Props {
data: {

View File

@@ -1,12 +1,12 @@
import { GPTVis } from '@antv/gpt-vis';
import remarkGfm from 'remark-gfm';
import markdownComponents from './config';
import { CodePreview } from './code-preview';
import { CheckOutlined, CloseOutlined } from '@mui/icons-material';
import classNames from 'classnames';
import { useState } from 'react';
import { CheckOutlined, CloseOutlined } from '@ant-design/icons';
import { useTranslation } from 'react-i18next';
import { oneLight, oneDark } from 'react-syntax-highlighter/dist/esm/styles/prism';
import { oneDark, oneLight } from 'react-syntax-highlighter/dist/esm/styles/prism';
import remarkGfm from 'remark-gfm';
import { CodePreview } from './code-preview';
import markdownComponents from './config';
interface Props {
data: {
@@ -23,18 +23,15 @@ function VisCode({ data }: Props) {
const [show, setShow] = useState(0);
return (
<div className="bg-[#EAEAEB] rounded overflow-hidden border border-theme-primary dark:bg-theme-dark text-sm">
<div className='bg-[#EAEAEB] rounded overflow-hidden border border-theme-primary dark:bg-theme-dark text-sm'>
<div>
<div className="flex">
<div className='flex'>
{data.code.map((item, index) => (
<div
key={index}
className={classNames(
"px-4 py-2 text-[#121417] dark:text-white cursor-pointer",
{
"bg-white dark:bg-theme-dark-container": index === show,
}
)}
className={classNames('px-4 py-2 text-[#121417] dark:text-white cursor-pointer', {
'bg-white dark:bg-theme-dark-container': index === show,
})}
onClick={() => {
setShow(index);
}}
@@ -54,17 +51,17 @@ function VisCode({ data }: Props) {
)}
</div>
<div>
<div className="flex">
<div className="bg-white dark:bg-theme-dark-container px-4 py-2 text-[#121417] dark:text-white">
{t("Terminal")}{" "}
<div className='flex'>
<div className='bg-white dark:bg-theme-dark-container px-4 py-2 text-[#121417] dark:text-white'>
{t('Terminal')}{' '}
{data.exit_success ? (
<CheckOutlined className="text-green-600" />
<CheckOutlined className='text-green-600' />
) : (
<CloseOutlined className="text-red-600" />
<CloseOutlined className='text-red-600' />
)}
</div>
</div>
<div className="p-4 max-h-72 overflow-y-auto whitespace-normal bg-white dark:dark:bg-theme-dark">
<div className='p-4 max-h-72 overflow-y-auto whitespace-normal bg-white dark:dark:bg-theme-dark'>
<GPTVis components={markdownComponents} remarkPlugins={[remarkGfm]}>
{data.log}
</GPTVis>

View File

@@ -1,5 +1,5 @@
import { CodePreview } from './code-preview';
import { formatSql } from '@/utils';
import { CodePreview } from './code-preview';
interface Props {
data: {
@@ -11,11 +11,11 @@ interface Props {
function VisConvertError({ data }: Props) {
return (
<div className="rounded overflow-hidden">
<div className="p-3 text-white bg-red-500 whitespace-normal">{data.display_type}</div>
<div className="p-3 bg-red-50">
<div className="mb-2 whitespace-normal">{data.thought}</div>
<CodePreview code={formatSql(data.sql)} language="sql" />
<div className='rounded overflow-hidden'>
<div className='p-3 text-white bg-red-500 whitespace-normal'>{data.display_type}</div>
<div className='p-3 bg-red-50'>
<div className='mb-2 whitespace-normal'>{data.thought}</div>
<CodePreview code={formatSql(data.sql)} language='sql' />
</div>
</div>
);

View File

@@ -24,7 +24,7 @@ function VisDashboard({ data }: Props) {
if (data.chart_count > 1) {
const layout = chartLayout[data.chart_count - 2];
let prevIndex = 0;
return layout.map((item) => {
return layout.map(item => {
const items = data.data.slice(prevIndex, prevIndex + item);
prevIndex = item;
return items;
@@ -34,17 +34,17 @@ function VisDashboard({ data }: Props) {
}, [data.data, data.chart_count]);
return (
<div className="flex flex-col gap-3">
<div className='flex flex-col gap-3'>
{charts.map((row, index) => (
<div key={`row-${index}`} className="flex gap-3">
<div key={`row-${index}`} className='flex gap-3'>
{row.map((chart, subIndex) => (
<div
key={`chart-${subIndex}`}
className="flex flex-1 flex-col justify-between p-4 rounded border border-gray-200 dark:border-gray-500 whitespace-normal"
className='flex flex-1 flex-col justify-between p-4 rounded border border-gray-200 dark:border-gray-500 whitespace-normal'
>
<div>
{chart.title && <div className="mb-2 text-lg">{chart.title}</div>}
{chart.describe && <div className="mb-4 text-sm text-gray-500">{chart.describe}</div>}
{chart.title && <div className='mb-2 text-lg'>{chart.title}</div>}
{chart.describe && <div className='mb-4 text-sm text-gray-500'>{chart.describe}</div>}
</div>
<AutoChart data={chart.data} chartType={getChartType(chart.type)} />
</div>

View File

@@ -1,10 +1,9 @@
import { CheckOutlined, ClockCircleOutlined, CloseOutlined, LoadingOutlined } from '@ant-design/icons';
import { GPTVis } from '@antv/gpt-vis';
import classNames from 'classnames';
import { ReactNode } from 'react';
import { GPTVis } from '@antv/gpt-vis';
import rehypeRaw from 'rehype-raw';
import remarkGfm from 'remark-gfm';
import markdownComponents from './config';
interface IVisPlugin {
@@ -25,49 +24,39 @@ interface Props {
const pluginViewStatusMapper: Record<IVisPlugin['status'], { bgClass: string; icon: ReactNode }> = {
todo: {
bgClass: 'bg-gray-500',
icon: <ClockCircleOutlined className="ml-2" />,
icon: <ClockCircleOutlined className='ml-2' />,
},
runing: {
bgClass: 'bg-blue-500',
icon: <LoadingOutlined className="ml-2" />,
icon: <LoadingOutlined className='ml-2' />,
},
failed: {
bgClass: 'bg-red-500',
icon: <CloseOutlined className="ml-2" />,
icon: <CloseOutlined className='ml-2' />,
},
complete: {
bgClass: 'bg-green-500',
icon: <CheckOutlined className="ml-2" />,
icon: <CheckOutlined className='ml-2' />,
},
};
function VisPlugin({ data }: Props) {
const { bgClass, icon } = pluginViewStatusMapper[data.status] ?? {};
return (
<div className="bg-theme-light dark:bg-theme-dark-container rounded overflow-hidden my-2 flex flex-col">
<div
className={classNames(
"flex px-4 md:px-6 py-2 items-center text-white text-sm",
bgClass
)}
>
<div className='bg-theme-light dark:bg-theme-dark-container rounded overflow-hidden my-2 flex flex-col'>
<div className={classNames('flex px-4 md:px-6 py-2 items-center text-white text-sm', bgClass)}>
{data.name}
{icon}
</div>
{data.result ? (
<div className="px-4 md:px-6 py-4 text-sm whitespace-normal">
<GPTVis
components={markdownComponents}
rehypePlugins={[rehypeRaw]}
remarkPlugins={[remarkGfm]}
>
{data.result ?? ""}
<div className='px-4 md:px-6 py-4 text-sm whitespace-normal'>
<GPTVis components={markdownComponents} rehypePlugins={[rehypeRaw]} remarkPlugins={[remarkGfm]}>
{data.result ?? ''}
</GPTVis>
</div>
) : (
<div className="px-4 md:px-6 py-4 text-sm">{data.err_msg}</div>
<div className='px-4 md:px-6 py-4 text-sm'>{data.err_msg}</div>
)}
</div>
);