refactor: upgrade gpt-vis (#1883)

This commit is contained in:
hustcc 2024-08-28 13:37:45 +08:00 committed by GitHub
parent 131bc7b89b
commit 1cb7e35295
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
12 changed files with 518 additions and 260 deletions

View File

@ -2,7 +2,7 @@ import { ChatContext } from '@/app/chat-context';
import { IChatDialogueMessageSchema } from '@/types/chat';
import classNames from 'classnames';
import { memo, useContext } from 'react';
import ReactMarkdown from 'react-markdown';
import { GPTVis } from '@antv/gpt-vis';
import markdownComponents from './chat-content/config';
import rehypeRaw from 'rehype-raw';
@ -21,15 +21,21 @@ function AgentContent({ content }: Props) {
return (
<div
className={classNames('relative w-full p-2 md:p-4 rounded-xl break-words', {
'bg-white dark:bg-[#232734]': isView,
'lg:w-full xl:w-full pl-0': ['chat_with_db_execute', 'chat_dashboard'].includes(scene),
})}
className={classNames(
"relative w-full p-2 md:p-4 rounded-xl break-words",
{
"bg-white dark:bg-[#232734]": isView,
"lg:w-full xl:w-full pl-0": [
"chat_with_db_execute",
"chat_dashboard",
].includes(scene),
}
)}
>
{isView ? (
<ReactMarkdown components={markdownComponents} rehypePlugins={[rehypeRaw]}>
<GPTVis components={markdownComponents} rehypePlugins={[rehypeRaw]}>
{formatMarkdownVal(content.context)}
</ReactMarkdown>
</GPTVis>
) : (
<div className="">{content.context}</div>
)}

View File

@ -6,7 +6,7 @@ import { githubLightTheme } from '@uiw/react-json-view/githubLight';
import { Alert, Spin } from 'antd';
import classNames from 'classnames';
import React, { useContext, useMemo } from 'react';
import ReactMarkdown from 'react-markdown';
import { GPTVis } from '@antv/gpt-vis';
import rehypeRaw from 'rehype-raw';
import remarkGfm from 'remark-gfm';
@ -41,30 +41,36 @@ const VisResponse: React.FC<{ data: VisResponseProps }> = ({ data }) => {
return (
<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' && { icon: <Spin indicator={<LoadingOutlined spin />} /> })}
{...(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 && (
<ReactMarkdown components={markdownComponents} remarkPlugins={[remarkGfm]} rehypePlugins={[rehypeRaw]}>
<GPTVis
components={markdownComponents}
remarkPlugins={[remarkGfm]}
rehypePlugins={[rehypeRaw]}
>
{data.err_msg}
</ReactMarkdown>
</GPTVis>
)}
</div>
);

View File

@ -1,7 +1,6 @@
import ModelIcon from '@/new-components/chat/content/ModelIcon';
import { LinkOutlined, SwapRightOutlined } from '@ant-design/icons';
import { Popover, Space } from 'antd';
import ReactMarkdown from 'react-markdown';
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';
@ -24,7 +23,11 @@ function AgentMessages({ data }: Props) {
{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" />}
{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" />
@ -32,11 +35,17 @@ function AgentMessages({ data }: Props) {
</div>
</div>
<div className="whitespace-normal text-sm mb-3">
<ReactMarkdown components={markdownComponents} remarkPlugins={[remarkGfm]} rehypePlugins={[rehypeRaw]}>
<GPTVis
components={markdownComponents}
remarkPlugins={[remarkGfm]}
rehypePlugins={[rehypeRaw]}
>
{item.markdown}
</ReactMarkdown>
</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 ReactMarkdown from 'react-markdown';
import { GPTVis } from '@antv/gpt-vis';
import rehypeRaw from 'rehype-raw';
import remarkGfm from 'remark-gfm';
@ -32,7 +32,7 @@ function AgentPlans({ data }: Props) {
<span>
{item.name} - {item.agent}
</span>
{item.status === 'complete' ? (
{item.status === "complete" ? (
<CheckOutlined className="!text-green-500 ml-2" />
) : (
<ClockCircleOutlined className="!text-gray-500 ml-2" />
@ -40,9 +40,13 @@ function AgentPlans({ data }: Props) {
</div>
),
children: (
<ReactMarkdown components={markdownComponents} rehypePlugins={[rehypeRaw]} remarkPlugins={[remarkGfm]}>
<GPTVis
components={markdownComponents}
rehypePlugins={[rehypeRaw]}
remarkPlugins={[remarkGfm]}
>
{item.markdown}
</ReactMarkdown>
</GPTVis>
),
};
})}

View File

@ -1,146 +1,241 @@
import { AutoChart, BackEndChartType, getChartType } from '@/components/chart';
import { LinkOutlined, ReadOutlined, SyncOutlined } from '@ant-design/icons';
import { Datum } from '@antv/ava';
import { Image, Table, Tabs, TabsProps, Tag } from 'antd';
import ReactMarkdown from 'react-markdown';
import rehypeRaw from 'rehype-raw';
import remarkGfm from 'remark-gfm';
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 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';
type MarkdownComponent = Parameters<typeof GPTVis>["0"]["components"];
type MarkdownComponent = Parameters<typeof ReactMarkdown>['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');
const tagReg = new RegExp(`<${tagName}[^>]*\/?>`, "gi");
context = context.replace(tagReg, (matchVal) => {
acc.push(matchVal);
return '';
return "";
});
return acc;
}, []);
return { context, matchValues };
}
const basicComponents: MarkdownComponent = {
code({ inline, node, className, children, style, ...props }) {
const codeComponents = {
/**
* @description
* Custom code block rendering, which can be used to render custom components in the code block.
* Is it defined in gpt-vis, and the default rendering contains `vis-chart`.
*/
code: withDefaultChartCode({
languageRenderers: {
"agent-plans": ({
node,
className,
children,
style,
}) => {
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 { context, matchValues } = matchCustomeTagValues(content);
const lang = className?.replace('language-', '') || 'javascript';
if (lang === 'agent-plans') {
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) {
return <CodePreview language={lang} code={content} />;
}
}
if (lang === 'agent-messages') {
},
"agent-messages": ({
node,
className,
children,
style,
}) => {
const content = String(children);
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) {
return <CodePreview language={lang} code={content} />;
}
}
if (lang === 'vis-convert-error') {
},
"vis-convert-error": ({
node,
className,
children,
style,
}) => {
const content = String(children);
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) {
return <CodePreview language={lang} code={content} />;
}
}
if (lang === 'vis-dashboard') {
},
"vis-dashboard": ({
node,
className,
children,
style,
}) => {
const content = String(children);
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) {
return <CodePreview language={lang} code={content} />;
}
}
if (lang === 'vis-chart') {
},
"vis-chart": ({ node, className, children, style }) => {
const content = String(children);
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) {
return <CodePreview language={lang} code={content} />;
}
}
if (lang === 'vis-plugin') {
},
"vis-plugin": ({
node,
className,
children,
style,
}) => {
const content = String(children);
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) {
return <CodePreview language={lang} code={content} />;
}
}
},
"vis-code": ({ node, className, children, style, ...props }) => {
const content = String(children);
const lang = className?.replace("language-", "") || "javascript";
if (lang === 'vis-code') {
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) {
return <CodePreview language={lang} code={content} />;
}
}
if (lang === 'vis-app-link') {
},
"vis-app-link": ({
node,
className,
children,
style,
...props
}) => {
const content = String(children);
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) {
return <CodePreview language={lang} code={content} />;
}
}
if (lang === 'vis-api-response') {
},
"vis-api-response": ({
node,
className,
children,
style,
...props
}) => {
const content = String(children);
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) {
return <CodePreview language={lang} code={content} />;
}
}
},
},
defaultRenderer({ node, className, children, style, ...props }) {
const content = String(children);
const lang = className?.replace("language-", "") || "";
const { context, matchValues } = matchCustomeTagValues(content);
console.log(111, { node, className, children, style, ...props }, lang);
return (
<>
{!inline ? (
<CodePreview code={context} language={lang} />
{lang ? (
<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>
)}
<ReactMarkdown components={markdownComponents} rehypePlugins={[rehypeRaw]} remarkPlugins={[remarkGfm]}>
{matchValues.join('\n')}
</ReactMarkdown>
<GPTVis
components={markdownComponents}
rehypePlugins={[rehypeRaw]}
remarkPlugins={[remarkGfm]}
>
{matchValues.join("\n")}
</GPTVis>
</>
);
},
}),
};
const basicComponents: MarkdownComponent = {
...codeComponents,
ul({ children }) {
return <ul className="py-1">{children}</ul>;
},
@ -148,22 +243,46 @@ const basicComponents: MarkdownComponent = {
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'}`}>{children}</li>;
return (
<li
className={`text-sm leading-7 ml-5 pl-2 text-gray-600 dark:text-gray-300 ${
ordered ? "list-decimal" : "list-disc"
}`}
>
{children}
</li>
);
},
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">{children}</table>;
return (
<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>;
},
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>;
@ -209,8 +328,8 @@ const basicComponents: MarkdownComponent = {
);
},
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 (
@ -223,32 +342,32 @@ const basicComponents: MarkdownComponent = {
const returnSqlVal = (val: string) => {
const punctuationMap: any = {
'': ',',
'。': '.',
'': '?',
'': '!',
'': ':',
'': ';',
'“': '"',
'”': '"',
'': "'",
'': "'",
'': '(',
'': ')',
'【': '[',
'】': ']',
'《': '<',
'》': '>',
'—': '-',
'、': ',',
'…': '...',
"": ",",
"。": ".",
"": "?",
"": "!",
"": ":",
"": ";",
"“": '"',
"”": '"',
"": "'",
"": "'",
"": "(",
"": ")",
"【": "[",
"】": "]",
"《": "<",
"》": ">",
"—": "-",
"、": ",",
"…": "...",
};
const regex = new RegExp(Object.keys(punctuationMap).join('|'), 'g');
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;
@ -259,11 +378,12 @@ 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) => {
@ -276,25 +396,46 @@ 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>
);

View File

@ -1,6 +1,6 @@
import { PropsWithChildren, ReactNode, memo, useContext, useMemo } from 'react';
import { CheckOutlined, ClockCircleOutlined, CloseOutlined, CodeOutlined, LoadingOutlined, RobotOutlined, UserOutlined } from '@ant-design/icons';
import ReactMarkdown from 'react-markdown';
import { GPTVis } from '@antv/gpt-vis';
import { IChatDialogueMessageSchema } from '@/types/chat';
import rehypeRaw from 'rehype-raw';
import classNames from 'classnames';
@ -22,7 +22,7 @@ interface Props {
onLinkClick?: () => void;
}
type MarkdownComponent = Parameters<typeof ReactMarkdown>['0']['components'];
type MarkdownComponent = Parameters<typeof GPTVis>["0"]["components"];
type DBGPTView = {
name: string;
@ -112,15 +112,23 @@ function ChatContent({ children, content, isChartChat, onLinkClick }: PropsWithC
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={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">
<ReactMarkdown components={markdownComponents} rehypePlugins={[rehypeRaw]}>
{result ?? ''}
</ReactMarkdown>
<GPTVis
components={markdownComponents}
rehypePlugins={[rehypeRaw]}
>
{result ?? ""}
</GPTVis>
</div>
) : (
<div className="px-4 md:px-6 py-4 text-sm">{err_msg}</div>
@ -136,32 +144,48 @@ function ChatContent({ children, content, isChartChat, onLinkClick }: PropsWithC
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 />}
{isRobot ? (
renderModelIcon(model_name) || <RobotOutlined />
) : (
<UserOutlined />
)}
</div>
<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}>
<span
className="text-theme-primary cursor-pointer"
onClick={onLinkClick}
>
<CodeOutlined className="mr-1" />
{context.template_introduce || 'More Details'}
{context.template_introduce || "More Details"}
</span>
</div>
)}
{/* Markdown */}
{isRobot && typeof context === 'string' && (
<ReactMarkdown components={{ ...markdownComponents, ...extraMarkdownComponents }} rehypePlugins={[rehypeRaw]}>
{isRobot && typeof context === "string" && (
<GPTVis
components={{ ...markdownComponents, ...extraMarkdownComponents }}
rehypePlugins={[rehypeRaw]}
>
{formatMarkdownVal(value)}
</ReactMarkdown>
</GPTVis>
)}
{!!relations?.length && (
<div className="flex flex-wrap mt-2">

View File

@ -1,4 +1,4 @@
import ReactMarkdown from 'react-markdown';
import { GPTVis } from '@antv/gpt-vis';
import remarkGfm from 'remark-gfm';
import markdownComponents from './config';
import { CodePreview } from './code-preview';
@ -29,9 +29,12 @@ function VisCode({ data }: Props) {
{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);
}}
@ -53,13 +56,18 @@ function VisCode({ data }: Props) {
<div>
<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" /> : <CloseOutlined className="text-red-600" />}
{t("Terminal")}{" "}
{data.exit_success ? (
<CheckOutlined className="text-green-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">
<ReactMarkdown components={markdownComponents} remarkPlugins={[remarkGfm]}>
<GPTVis components={markdownComponents} remarkPlugins={[remarkGfm]}>
{data.log}
</ReactMarkdown>
</GPTVis>
</div>
</div>
</div>

View File

@ -1,7 +1,7 @@
import { CheckOutlined, ClockCircleOutlined, CloseOutlined, LoadingOutlined } from '@ant-design/icons';
import classNames from 'classnames';
import { ReactNode } from 'react';
import ReactMarkdown from 'react-markdown';
import { GPTVis } from '@antv/gpt-vis';
import rehypeRaw from 'rehype-raw';
import remarkGfm from 'remark-gfm';
@ -47,15 +47,24 @@ function VisPlugin({ data }: Props) {
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={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">
<ReactMarkdown components={markdownComponents} rehypePlugins={[rehypeRaw]} remarkPlugins={[remarkGfm]}>
{data.result ?? ''}
</ReactMarkdown>
<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>

View File

@ -7,7 +7,7 @@ import Image from 'next/image';
import { useSearchParams } from 'next/navigation';
import React, { memo, useMemo } from 'react';
import { useTranslation } from 'react-i18next';
import ReactMarkdown from 'react-markdown';
import { GPTVis } from '@antv/gpt-vis';
import rehypeRaw from 'rehype-raw';
import remarkGfm from 'remark-gfm';
@ -42,7 +42,7 @@ type DBGPTView = {
err_msg?: string;
};
type MarkdownComponent = Parameters<typeof ReactMarkdown>['0']['components'];
type MarkdownComponent = Parameters<typeof GPTVis>["0"]["components"];
const pluginViewStatusMapper: Record<DBGPTView['status'], { bgClass: string; icon: React.ReactNode }> = {
todo: {
@ -143,15 +143,24 @@ const ChatContent: React.FC<{
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={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">
<ReactMarkdown components={markdownComponents} rehypePlugins={[rehypeRaw]} remarkPlugins={[remarkGfm]}>
{result ?? ''}
</ReactMarkdown>
<GPTVis
components={markdownComponents}
rehypePlugins={[rehypeRaw]}
remarkPlugins={[remarkGfm]}
>
{result ?? ""}
</GPTVis>
</div>
) : (
<div className="px-4 md:px-6 py-4 text-sm">{err_msg}</div>
@ -163,45 +172,68 @@ const ChatContent: React.FC<{
[cachePluginContext],
);
return (
<div className="flex flex-1 gap-3 mt-6">
{/* icon */}
<div className="flex flex-shrink-0 items-start">{isRobot ? <RobotIcon model={model_name} /> : <UserIcon />}</div>
<div className={`flex ${scene === 'chat_agent' && !thinking ? 'flex-1' : ''} overflow-hidden`}>
<div className="flex flex-shrink-0 items-start">
{isRobot ? <RobotIcon model={model_name} /> : <UserIcon />}
</div>
<div
className={`flex ${
scene === "chat_agent" && !thinking ? "flex-1" : ""
} overflow-hidden`}
>
{/* 用户提问 */}
{!isRobot && <div className="flex flex-1 items-center text-sm text-[#1c2533] dark:text-white">{typeof context === 'string' && context}</div>}
{!isRobot && (
<div className="flex flex-1 items-center text-sm text-[#1c2533] dark:text-white">
{typeof context === "string" && context}
</div>
)}
{/* ai回答 */}
{isRobot && (
<div className="flex flex-1 flex-col w-full">
<div className="bg-white dark:bg-[rgba(255,255,255,0.16)] p-4 rounded-2xl rounded-tl-none mb-2">
{typeof context === 'object' && (
{typeof context === "object" && (
<div>
{`[${context.template_name}]: `}
<span className="text-theme-primary cursor-pointer" onClick={onLinkClick}>
<span
className="text-theme-primary cursor-pointer"
onClick={onLinkClick}
>
<CodeOutlined className="mr-1" />
{context.template_introduce || 'More Details'}
{context.template_introduce || "More Details"}
</span>
</div>
)}
{typeof context === 'string' && scene === 'chat_agent' && (
<ReactMarkdown components={{ ...markdownComponents }} rehypePlugins={[rehypeRaw]} remarkPlugins={[remarkGfm]}>
{typeof context === "string" && scene === "chat_agent" && (
<GPTVis
components={{ ...markdownComponents }}
rehypePlugins={[rehypeRaw]}
remarkPlugins={[remarkGfm]}
>
{formatMarkdownValForAgent(value)}
</ReactMarkdown>
</GPTVis>
)}
{typeof context === 'string' && scene !== 'chat_agent' && (
<ReactMarkdown
components={{ ...markdownComponents, ...extraMarkdownComponents }}
{typeof context === "string" && scene !== "chat_agent" && (
<div>
<GPTVis
components={{
...markdownComponents,
...extraMarkdownComponents,
}}
rehypePlugins={[rehypeRaw]}
remarkPlugins={[remarkGfm]}
>
{formatMarkdownVal(value)}
</ReactMarkdown>
</GPTVis>
</div>
)}
{/* 正在思考 */}
{thinking && !context && (
<div className="flex items-center gap-2">
<span className="flex text-sm text-[#1c2533] dark:text-white">{t('thinking')}</span>
<span className="flex text-sm text-[#1c2533] dark:text-white">
{t("thinking")}
</span>
<div className="flex">
<div className="w-1 h-1 rounded-full mx-1 animate-pulse1"></div>
<div className="w-1 h-1 rounded-full mx-1 animate-pulse2"></div>

View File

@ -1,14 +1,18 @@
import markdownComponents from '@/components/chat/chat-content/config';
import React from 'react';
import ReactMarkdown from 'react-markdown';
import { GPTVis } from '@antv/gpt-vis';
import rehypeRaw from 'rehype-raw';
import remarkGfm from 'remark-gfm';
const MarkDownContext: React.FC<{ children: string }> = ({ children }) => {
return (
<ReactMarkdown components={{ ...markdownComponents }} rehypePlugins={[rehypeRaw]} remarkPlugins={[remarkGfm]}>
<GPTVis
components={{ ...markdownComponents }}
rehypePlugins={[rehypeRaw]}
remarkPlugins={[remarkGfm]}
>
{children}
</ReactMarkdown>
</GPTVis>
);
};

View File

@ -46,7 +46,7 @@ const nextConfig = {
}
};
const withTM = require('next-transpile-modules')(['@berryv/g2-react','@antv/g2','react-syntax-highlighter']);
const withTM = require('next-transpile-modules')(['@berryv/g2-react','@antv/g2','react-syntax-highlighter', '@antv/gpt-vis']);
module.exports = withTM({
...nextConfig,

View File

@ -16,6 +16,7 @@
"@ant-design/icons": "^5.2.5",
"@antv/ava": "3.5.0-alpha.4",
"@antv/g2": "^5.1.8",
"@antv/gpt-vis": "^0.0.5",
"@antv/s2": "^1.51.2",
"@berryv/g2-react": "^0.1.0",
"@emotion/react": "^11.11.4",
@ -55,13 +56,12 @@
"react": "^18.3.1",
"react-dom": "^18.3.1",
"react-i18next": "^13.2.0",
"react-markdown": "^8.0.7",
"react-markdown-editor-lite": "^1.3.4",
"react-syntax-highlighter": "^15.5.0",
"react-virtualized": "^9.22.5",
"reactflow": "^11.10.3",
"rehype-raw": "6.1.1",
"remark-gfm": "^3.0.1",
"rehype-raw": "^7.0.0",
"remark-gfm": "^4.0.0",
"sequelize": "^6.33.0",
"sql-formatter": "^12.2.4",
"tailwindcss": "3.3.2",
@ -69,8 +69,8 @@
"typescript": "5.1.3"
},
"devDependencies": {
"@types/cytoscape": "^3.21.0",
"@types/crypto-js": "^4.1.2",
"@types/cytoscape": "^3.21.0",
"@types/google-one-tap": "^1.2.4",
"@types/lodash": "^4.14.195",
"@types/markdown-it": "^14.1.1",
@ -94,5 +94,20 @@
},
"author": "",
"license": "ISC",
"repository": "https://github.com/eosphoros-ai/DB-GPT-Web.git"
"repository": "https://github.com/eosphoros-ai/DB-GPT-Web.git",
"resolutions": {
"d3-color": "2",
"d3-array": "2",
"d3-shape": "2",
"d3-path": "2",
"d3-dsv": "2",
"d3-hierarchy": "2",
"d3-scale-chromatic": "2",
"d3-format": "2",
"d3-timer": "2",
"d3-dispatch": "2",
"d3-quadtree": "2",
"d3-force": "2",
"d3-geo": "2"
}
}