|
@@ -1,66 +1,22 @@
|
|
|
-import React, { useEffect, useMemo, useRef, useState } from "react";
|
|
|
+import { ReactNode, useEffect, useMemo, useRef, useState } from "react";
|
|
|
import {
|
|
|
PlusOutlined,
|
|
|
CloseOutlined,
|
|
|
FieldTimeOutlined,
|
|
|
- UserOutlined,
|
|
|
- SendOutlined,
|
|
|
- LoadingOutlined,
|
|
|
- EditOutlined,
|
|
|
- DeleteOutlined,
|
|
|
} from "@ant-design/icons";
|
|
|
-import {
|
|
|
- Button,
|
|
|
- Tooltip,
|
|
|
- Input,
|
|
|
- Form,
|
|
|
- Dropdown,
|
|
|
- Typography,
|
|
|
- Space,
|
|
|
- Spin,
|
|
|
-} from "antd";
|
|
|
+import { Button, Tooltip, Dropdown, Typography, Space, Spin } from "antd";
|
|
|
import { GetProp } from "antd";
|
|
|
-import { Bubble, Sender } from "@ant-design/x";
|
|
|
+import { Bubble, Sender, Suggestion, Prompts } from "@ant-design/x";
|
|
|
import MarkdownViewer from "./MarkdownViewer";
|
|
|
import { useChat } from "@/hooks/useChat";
|
|
|
+import RenderGraph from "./RenderGraph";
|
|
|
|
|
|
-// bubbles角色配置
|
|
|
-const roles: GetProp<typeof Bubble.List, "roles"> = {
|
|
|
- assistant: {
|
|
|
- placement: "start",
|
|
|
- avatar: undefined,
|
|
|
- loadingRender: () => (
|
|
|
- <Space>
|
|
|
- <Spin size="small" />
|
|
|
- 思考中...
|
|
|
- </Space>
|
|
|
- ),
|
|
|
- messageRender: (content) => {
|
|
|
- return typeof content === "string" ? (
|
|
|
- <Typography className={content?.includes("```") ? "w-full" : ""}>
|
|
|
- <MarkdownViewer content={content} />
|
|
|
- </Typography>
|
|
|
- ) : (
|
|
|
- content
|
|
|
- );
|
|
|
- },
|
|
|
- header: <span className="text-12px text-#666">数据模型助手</span>,
|
|
|
- },
|
|
|
- user: {
|
|
|
- placement: "start",
|
|
|
- avatar: undefined,
|
|
|
- messageRender: (content) => {
|
|
|
- return <div style={{ whiteSpace: "pre-wrap" }}>{content}</div>;
|
|
|
- },
|
|
|
- styles: {
|
|
|
- content: {
|
|
|
- backgroundColor: "#d2e1fb",
|
|
|
- },
|
|
|
- },
|
|
|
- },
|
|
|
-};
|
|
|
-
|
|
|
-export default function AIChat(props: { onClose?: () => void }) {
|
|
|
+export default function AIChat(props: {
|
|
|
+ onClose?: () => void;
|
|
|
+ onInsert?: (data: any) => void;
|
|
|
+ graphType?: "flow" | "mindmap";
|
|
|
+}) {
|
|
|
+ const { onClose, onInsert, graphType } = props;
|
|
|
const [chatStarted, setChatStarted] = useState(false);
|
|
|
const [inputVal, setInputVal] = useState("");
|
|
|
const contentRef = useRef<HTMLDivElement>(null);
|
|
@@ -80,41 +36,129 @@ export default function AIChat(props: { onClose?: () => void }) {
|
|
|
};
|
|
|
}, []);
|
|
|
|
|
|
- const { loading, onRequest, cancel, messages, setMessages, addConversation, conversationList } =
|
|
|
- useChat({
|
|
|
- app_name: "system_design",
|
|
|
- onSuccess: (msg) => {
|
|
|
- setMessages((messages) => {
|
|
|
- const arr = [...messages];
|
|
|
- // const query = arr[messages.length - 2].content as string;
|
|
|
- arr[messages.length - 1].status = "done";
|
|
|
- arr[messages.length - 1].footer = <></>;
|
|
|
- return arr;
|
|
|
- });
|
|
|
+ const {
|
|
|
+ loading,
|
|
|
+ onRequest,
|
|
|
+ cancel,
|
|
|
+ messages,
|
|
|
+ setMessages,
|
|
|
+ addConversation,
|
|
|
+ conversationList,
|
|
|
+ changeConversation,
|
|
|
+ } = useChat({
|
|
|
+ app_name: "system_design",
|
|
|
+ onSuccess: (msg) => {
|
|
|
+ setMessages((messages) => {
|
|
|
+ const arr = [...messages];
|
|
|
+ // const query = arr[messages.length - 2].content as string;
|
|
|
+ arr[messages.length - 1].status = "done";
|
|
|
+ arr[messages.length - 1].footer = <></>;
|
|
|
+ return arr;
|
|
|
+ });
|
|
|
+ },
|
|
|
+ onUpdate: (msg) => {
|
|
|
+ setMessages((messages) => {
|
|
|
+ const arr = [...messages];
|
|
|
+ arr[messages.length - 1].content += msg.answer;
|
|
|
+ arr[messages.length - 1].id = msg.message_id;
|
|
|
+ arr[messages.length - 1].loading = false;
|
|
|
+ arr[messages.length - 1].status = "loading";
|
|
|
+ return arr;
|
|
|
+ });
|
|
|
+ },
|
|
|
+ onError: (error) => {
|
|
|
+ // message.error(error.message);
|
|
|
+ setMessages((messages) => {
|
|
|
+ const arr = [...messages];
|
|
|
+ arr[messages.length - 1].content = (
|
|
|
+ <Typography.Text type="danger">{error.message}</Typography.Text>
|
|
|
+ );
|
|
|
+ arr[messages.length - 1].status = "error";
|
|
|
+ arr[messages.length - 1].loading = false;
|
|
|
+ return arr;
|
|
|
+ });
|
|
|
+ },
|
|
|
+ });
|
|
|
+
|
|
|
+ const CustomRender = (props: { language: string; code: string }) => {
|
|
|
+ const { language, code } = props;
|
|
|
+ const children = useMemo(
|
|
|
+ () => (
|
|
|
+ <RenderGraph
|
|
|
+ language={language}
|
|
|
+ json={code}
|
|
|
+ onInsertFlow={onInsert}
|
|
|
+ type={graphType}
|
|
|
+ ></RenderGraph>
|
|
|
+ ),
|
|
|
+ [language, code]
|
|
|
+ );
|
|
|
+
|
|
|
+ return <>{children}</>;
|
|
|
+ };
|
|
|
+
|
|
|
+ const CustomMessage = (props: { content: string | ReactNode }) => {
|
|
|
+ const { content } = props;
|
|
|
+ const children = useMemo(
|
|
|
+ () =>
|
|
|
+ typeof content === "string" ? (
|
|
|
+ <Typography className={content?.includes("```") ? "w-full" : ""}>
|
|
|
+ <MarkdownViewer
|
|
|
+ content={content}
|
|
|
+ renderCode={(language, code) => (
|
|
|
+ <CustomRender language={language} code={code} />
|
|
|
+ )}
|
|
|
+ />
|
|
|
+ </Typography>
|
|
|
+ ) : (
|
|
|
+ content
|
|
|
+ ),
|
|
|
+ [content]
|
|
|
+ );
|
|
|
+
|
|
|
+ return children;
|
|
|
+ };
|
|
|
+
|
|
|
+ // bubbles角色配置
|
|
|
+ const roles: GetProp<typeof Bubble.List, "roles"> = {
|
|
|
+ assistant: {
|
|
|
+ placement: "start",
|
|
|
+ avatar: undefined,
|
|
|
+ loadingRender: () => (
|
|
|
+ <Space>
|
|
|
+ <Spin size="small" />
|
|
|
+ 思考中...
|
|
|
+ </Space>
|
|
|
+ ),
|
|
|
+ messageRender: (content) => <CustomMessage content={content} />,
|
|
|
+ header: (
|
|
|
+ <span className="text-12px text-#666">
|
|
|
+ <svg className="icon h-16px! w-16px!" aria-hidden="true">
|
|
|
+ <use xlinkHref="#icon-AI1"></use>
|
|
|
+ </svg>
|
|
|
+ <span className="ml-4px">数据模型助手</span>
|
|
|
+ </span>
|
|
|
+ ),
|
|
|
+ styles: {
|
|
|
+ content: {
|
|
|
+ borderRadius: "0px 8px 8px 8px",
|
|
|
+ },
|
|
|
},
|
|
|
- onUpdate: (msg) => {
|
|
|
- setMessages((messages) => {
|
|
|
- const arr = [...messages];
|
|
|
- arr[messages.length - 1].content += msg.answer;
|
|
|
- arr[messages.length - 1].id = msg.message_id;
|
|
|
- arr[messages.length - 1].loading = false;
|
|
|
- arr[messages.length - 1].status = "loading";
|
|
|
- return arr;
|
|
|
- });
|
|
|
+ },
|
|
|
+ user: {
|
|
|
+ placement: "end",
|
|
|
+ avatar: undefined,
|
|
|
+ messageRender: (content) => {
|
|
|
+ return <div style={{ whiteSpace: "pre-wrap" }}>{content}</div>;
|
|
|
},
|
|
|
- onError: (error) => {
|
|
|
- // message.error(error.message);
|
|
|
- setMessages((messages) => {
|
|
|
- const arr = [...messages];
|
|
|
- arr[messages.length - 1].content = (
|
|
|
- <Typography.Text type="danger">{error.message}</Typography.Text>
|
|
|
- );
|
|
|
- arr[messages.length - 1].status = "error";
|
|
|
- arr[messages.length - 1].loading = false;
|
|
|
- return arr;
|
|
|
- });
|
|
|
+ styles: {
|
|
|
+ content: {
|
|
|
+ backgroundColor: "#d2e1fb",
|
|
|
+ borderRadius: "8px 0 8px 8px",
|
|
|
+ },
|
|
|
},
|
|
|
- });
|
|
|
+ },
|
|
|
+ };
|
|
|
|
|
|
// 处理提交
|
|
|
const onSubmit = (val: string) => {
|
|
@@ -155,14 +199,29 @@ export default function AIChat(props: { onClose?: () => void }) {
|
|
|
};
|
|
|
|
|
|
const historyItems = useMemo(() => {
|
|
|
- return (conversationList || []).map(item => {
|
|
|
+ return (conversationList || []).map((item) => {
|
|
|
return {
|
|
|
key: item.id,
|
|
|
- label: item.label
|
|
|
- }
|
|
|
- })
|
|
|
+ label: item.label,
|
|
|
+ onClick: () => {
|
|
|
+ console.log("changeConversation", item);
|
|
|
+ changeConversation(item.sessionId);
|
|
|
+ },
|
|
|
+ };
|
|
|
+ });
|
|
|
}, [conversationList]);
|
|
|
|
|
|
+ const bubble = useMemo(() => {
|
|
|
+ return (
|
|
|
+ <Bubble.List
|
|
|
+ style={{ maxHeight: contentHeight, padding: "0 10px" }}
|
|
|
+ autoScroll
|
|
|
+ items={messages}
|
|
|
+ roles={roles}
|
|
|
+ />
|
|
|
+ );
|
|
|
+ }, [messages]);
|
|
|
+
|
|
|
return (
|
|
|
<div
|
|
|
className="flex-1 h-full flex flex-col"
|
|
@@ -171,7 +230,7 @@ export default function AIChat(props: { onClose?: () => void }) {
|
|
|
}}
|
|
|
>
|
|
|
<div className="chat-head w-full h-40px px-10px color-#333 flex items-center justify-between">
|
|
|
- <span className="text-14px">
|
|
|
+ <span className="text-14px">
|
|
|
<svg className="icon h-32px w-32px mr-4px" aria-hidden="true">
|
|
|
<use xlinkHref="#icon-AI1"></use>
|
|
|
</svg>
|
|
@@ -205,13 +264,13 @@ export default function AIChat(props: { onClose?: () => void }) {
|
|
|
type="text"
|
|
|
size="small"
|
|
|
icon={<CloseOutlined />}
|
|
|
- onClick={() => props.onClose?.()}
|
|
|
+ onClick={() => onClose?.()}
|
|
|
></Button>
|
|
|
</span>
|
|
|
</div>
|
|
|
|
|
|
<div
|
|
|
- className="chat-content flex-1 px-10px overflow-hidden mt-12px"
|
|
|
+ className="chat-content flex-1 overflow-hidden mt-12px"
|
|
|
ref={contentRef}
|
|
|
style={{ height: contentHeight }}
|
|
|
>
|
|
@@ -226,20 +285,13 @@ export default function AIChat(props: { onClose?: () => void }) {
|
|
|
<p className="text-center">我是AI助手,有什么可以帮您的吗?</p>
|
|
|
</>
|
|
|
) : (
|
|
|
- <div className="overflow-y-auto h-full">
|
|
|
- <Bubble.List
|
|
|
- style={{ maxHeight: contentHeight }}
|
|
|
- autoScroll
|
|
|
- items={messages}
|
|
|
- roles={roles}
|
|
|
- />
|
|
|
- </div>
|
|
|
+ <div className="overflow-y-auto h-full">{bubble}</div>
|
|
|
)}
|
|
|
</div>
|
|
|
|
|
|
<div className="px-12px py-16px">
|
|
|
<Sender
|
|
|
- placeholder="你想咨询什么?"
|
|
|
+ placeholder="开始探索..."
|
|
|
loading={loading}
|
|
|
value={inputVal}
|
|
|
onChange={setInputVal}
|