mirror of
https://github.com/csunny/DB-GPT.git
synced 2025-09-19 08:26:51 +00:00
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:
@@ -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;
|
||||
}
|
||||
};
|
||||
|
@@ -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>
|
||||
)}
|
||||
|
@@ -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>
|
||||
|
@@ -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>
|
||||
)}
|
||||
|
@@ -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>
|
||||
))}
|
||||
</>
|
||||
|
@@ -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>
|
||||
),
|
||||
|
@@ -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;
|
||||
|
@@ -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>
|
||||
|
@@ -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>
|
||||
|
@@ -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>
|
||||
))}
|
||||
|
@@ -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: {
|
||||
|
@@ -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>
|
||||
|
@@ -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>
|
||||
);
|
||||
|
@@ -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>
|
||||
|
@@ -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>
|
||||
);
|
||||
|
Reference in New Issue
Block a user