|
@@ -1,16 +1,15 @@
|
|
|
-import React, { useEffect, useMemo, useRef, useState } from "react";
|
|
|
+import React, { useRef, useState } from "react";
|
|
|
import {
|
|
|
CloseOutlined,
|
|
|
- FieldTimeOutlined,
|
|
|
SendOutlined,
|
|
|
EditOutlined,
|
|
|
DeleteOutlined,
|
|
|
CaretDownOutlined,
|
|
|
} from "@ant-design/icons";
|
|
|
-import { Button, Tooltip, Input, Form, Dropdown, MenuProps } from "antd";
|
|
|
+import { Button, Tooltip, Input, Form, Dropdown, message } from "antd";
|
|
|
import type { DropDownProps } from "antd";
|
|
|
import { useChat } from "@/hooks/useChat";
|
|
|
-
|
|
|
+import {} from "react-markdown";
|
|
|
|
|
|
interface ChatHistoryItem {
|
|
|
id: string;
|
|
@@ -20,54 +19,125 @@ interface ChatHistoryItem {
|
|
|
title: string;
|
|
|
}
|
|
|
|
|
|
-const dropDownMenu: DropDownProps['menu'] = {
|
|
|
- items: [
|
|
|
- { key: "1", label: "流程图" },
|
|
|
- { key: "2", label: "泳道图" },
|
|
|
- { key: "3", label: "ER实体图" },
|
|
|
- { key: "4", label: "组织图" },
|
|
|
- { key: "5", label: "时序图" },
|
|
|
- ]
|
|
|
-}
|
|
|
-
|
|
|
-export default function AICreator(props: {
|
|
|
- type: 'mindmap' | 'flow',
|
|
|
- onClose?: () => void,
|
|
|
- onChange?: (data: any) => void,
|
|
|
+export default function AICreator(props: {
|
|
|
+ type: "mindmap" | "flow";
|
|
|
+ onClose?: () => void;
|
|
|
+ onChange?: (data: any) => void;
|
|
|
+ onError?: (err: Error) => void;
|
|
|
}) {
|
|
|
const [focused, setFocused] = React.useState(false);
|
|
|
- const [chatStarted, setChatStarted] = React.useState(false);
|
|
|
- const scrollAreaRef = React.useRef<HTMLDivElement>(null);
|
|
|
const [input, setInput] = useState("");
|
|
|
+ const [messageApi, contextHolder] = message.useMessage();
|
|
|
+ const messageKey = "ailoading";
|
|
|
+ const msgContent = useRef<string>("");
|
|
|
|
|
|
- const {
|
|
|
- messages,
|
|
|
- setMessages,
|
|
|
- loading,
|
|
|
- onRequest
|
|
|
- } = useChat({
|
|
|
- app_name: "app1",
|
|
|
- onStart: () => {
|
|
|
-
|
|
|
- },
|
|
|
+ const { loading, onRequest, cancel } = useChat({
|
|
|
+ app_name: "system_design",
|
|
|
onUpdate: (msg) => {
|
|
|
- setMessages((messages) => {
|
|
|
- const arr = [...messages];
|
|
|
- arr[messages.length - 1].content += msg.answer;
|
|
|
- arr[messages.length - 1].id = msg.message_id;
|
|
|
- return arr;
|
|
|
- });
|
|
|
+ setInput("");
|
|
|
+ msgContent.current += msg.answer;
|
|
|
},
|
|
|
onSuccess: (msg) => {
|
|
|
- console.log('加载完毕!');
|
|
|
+ console.log("加载完毕!", msgContent.current);
|
|
|
+ messageApi.open({
|
|
|
+ key: messageKey,
|
|
|
+ type: "success",
|
|
|
+ content: "AI创作完成",
|
|
|
+ duration: 2,
|
|
|
+ style: {
|
|
|
+ marginTop: 300,
|
|
|
+ },
|
|
|
+ });
|
|
|
+ handleParse();
|
|
|
+ },
|
|
|
+ onError: (err) => {
|
|
|
+ messageApi.open({
|
|
|
+ key: messageKey,
|
|
|
+ type: "error",
|
|
|
+ content: err.message || "AI创作失败",
|
|
|
+ duration: 2,
|
|
|
+ style: {
|
|
|
+ marginTop: 300,
|
|
|
+ },
|
|
|
+ });
|
|
|
},
|
|
|
- })
|
|
|
+ });
|
|
|
+
|
|
|
+ function regexExtractJSON(markdown: string) {
|
|
|
+ const jsonRegex = /```(?:json)?\n([\s\S]*?)\n```/g;
|
|
|
+ const matches = [];
|
|
|
+ let match;
|
|
|
+
|
|
|
+ while ((match = jsonRegex.exec(markdown)) !== null) {
|
|
|
+ try {
|
|
|
+ const jsonObj = JSON.parse(match[1]);
|
|
|
+ matches.push(jsonObj);
|
|
|
+ } catch (e) {
|
|
|
+ console.warn("无效JSON:", match[0]);
|
|
|
+ }
|
|
|
+ }
|
|
|
+ return matches;
|
|
|
+ }
|
|
|
+
|
|
|
+ const handleParse = () => {
|
|
|
+ try {
|
|
|
+ // 根据markdown格式取出json部分数据
|
|
|
+ const md = msgContent.current;
|
|
|
+ let json: string;
|
|
|
+ if (md.includes("```json")) {
|
|
|
+ json = regexExtractJSON(msgContent.current)?.[0];
|
|
|
+ } else {
|
|
|
+ json = JSON.parse(msgContent.current);
|
|
|
+ }
|
|
|
+
|
|
|
+ console.log("解析结果:", json);
|
|
|
+ props.onChange?.(json);
|
|
|
+ } catch (error) {
|
|
|
+ messageApi.open({
|
|
|
+ key: messageKey,
|
|
|
+ type: "error",
|
|
|
+ content: "AI创作失败",
|
|
|
+ duration: 2,
|
|
|
+ style: {
|
|
|
+ marginTop: 300,
|
|
|
+ },
|
|
|
+ });
|
|
|
+ console.error(error);
|
|
|
+ props.onError?.(new Error("AI创作失败"));
|
|
|
+ }
|
|
|
+ };
|
|
|
+
|
|
|
+ const handleStop = () => {
|
|
|
+ cancel();
|
|
|
+ messageApi.open({
|
|
|
+ key: messageKey,
|
|
|
+ type: "error",
|
|
|
+ content: "AI创作已取消",
|
|
|
+ duration: 2,
|
|
|
+ style: {
|
|
|
+ marginTop: 300,
|
|
|
+ },
|
|
|
+ });
|
|
|
+ };
|
|
|
|
|
|
// 处理提交
|
|
|
const onSubmit = () => {
|
|
|
if (input.trim()) {
|
|
|
+ onRequest(
|
|
|
+ `设计思维导图, 具体需求描述:${input}`,
|
|
|
+ undefined,
|
|
|
+ input
|
|
|
+ );
|
|
|
|
|
|
- if (!chatStarted) setChatStarted(true);
|
|
|
+ messageApi.open({
|
|
|
+ key: messageKey,
|
|
|
+ type: "loading",
|
|
|
+ content: "AI创作中...",
|
|
|
+ duration: 0,
|
|
|
+ style: {
|
|
|
+ marginTop: 300,
|
|
|
+ },
|
|
|
+ });
|
|
|
}
|
|
|
};
|
|
|
|
|
@@ -78,84 +148,57 @@ export default function AICreator(props: {
|
|
|
controller.abort();
|
|
|
};
|
|
|
}, []);
|
|
|
-
|
|
|
- const [hoverId, setHoverId] = useState<string | null>(null);
|
|
|
-
|
|
|
- // 获取items
|
|
|
- const getItems = (list: ChatHistoryItem[]) => {
|
|
|
- return (list || []).map((item) => {
|
|
|
- return {
|
|
|
- key: item.id,
|
|
|
- label: (
|
|
|
- <div className="w-180px relative">
|
|
|
- <div className="w-full flex">
|
|
|
- <span
|
|
|
- className="truncate"
|
|
|
- style={{
|
|
|
- overflow: "hidden",
|
|
|
- whiteSpace: "nowrap",
|
|
|
- textOverflow: "ellipsis",
|
|
|
- }}
|
|
|
- >
|
|
|
- <Tooltip title={item.title}>{item.title}</Tooltip>
|
|
|
- </span>
|
|
|
- </div>
|
|
|
- {/* TODO: 添加编辑删除按钮 */}
|
|
|
- {hoverId === item.id && (
|
|
|
- <div className="h-full w-50px text-right absolute right-0 top-0 bg-#fff">
|
|
|
- <EditOutlined
|
|
|
- onClick={(e) => {
|
|
|
- e.stopPropagation();
|
|
|
- }}
|
|
|
- />
|
|
|
- <DeleteOutlined />
|
|
|
- </div>
|
|
|
- )}
|
|
|
- </div>
|
|
|
- ),
|
|
|
- onClick: () => {
|
|
|
-
|
|
|
- },
|
|
|
- onMouseOver: () => {
|
|
|
-
|
|
|
- },
|
|
|
- };
|
|
|
- });
|
|
|
- };
|
|
|
-
|
|
|
+
|
|
|
const handleList = [
|
|
|
{
|
|
|
- key: "1",
|
|
|
+ key: "style",
|
|
|
label: "风格美化",
|
|
|
icon: "icon-yijianmeihua",
|
|
|
color: "#a171f2",
|
|
|
},
|
|
|
{
|
|
|
- key: "2",
|
|
|
+ key: "grammar",
|
|
|
label: "语法修复",
|
|
|
icon: "icon-tubiao_yufajiucuo",
|
|
|
color: "#00c4ad",
|
|
|
},
|
|
|
{
|
|
|
- key: "3",
|
|
|
+ key: "translation_en",
|
|
|
label: "翻译为英文",
|
|
|
icon: "icon-fanyiweiyingwen",
|
|
|
color: "#8c4ff0",
|
|
|
},
|
|
|
{
|
|
|
- key: "4",
|
|
|
+ key: "translation_zh",
|
|
|
label: "翻译为中文",
|
|
|
icon: "icon-fanyiweizhongwen",
|
|
|
color: "#3d72fb",
|
|
|
},
|
|
|
];
|
|
|
|
|
|
+ const handleAiFeature = (feature: string) => {
|
|
|
+ // onRequest('', {
|
|
|
+ // onUpdata: (data) => {
|
|
|
+ // },
|
|
|
+ // onSuccess: (data) => {
|
|
|
+ // },
|
|
|
+ // onError: (err) => {
|
|
|
+ // }
|
|
|
+ // })
|
|
|
+ };
|
|
|
+
|
|
|
return (
|
|
|
- <div className="flex-1 h-full">
|
|
|
+ <div className="flex-1 h-full" style={{backgroundImage: 'linear-gradient(137deg, #e5f4ff 0%, #efe7ff 100%)'}}>
|
|
|
+ {contextHolder}
|
|
|
<div className="chat-head w-full h-40px px-10px color-#333 flex items-center justify-between">
|
|
|
- <i className="iconfont icon-AIchuangzuo"></i>
|
|
|
+ <span className="text-14px">
|
|
|
+ <svg className="icon h-32px w-32px" aria-hidden="true">
|
|
|
+ <use xlinkHref="#icon-AI1"></use>
|
|
|
+ </svg>
|
|
|
+ <span>AI创作</span>
|
|
|
+ </span>
|
|
|
<span>
|
|
|
- <Dropdown
|
|
|
+ {/* <Dropdown
|
|
|
menu={{ }}
|
|
|
trigger={["click"]}
|
|
|
placement="bottomLeft"
|
|
@@ -168,7 +211,7 @@ export default function AICreator(props: {
|
|
|
icon={<FieldTimeOutlined />}
|
|
|
></Button>
|
|
|
</Tooltip>
|
|
|
- </Dropdown>
|
|
|
+ </Dropdown> */}
|
|
|
<Button
|
|
|
type="text"
|
|
|
size="small"
|
|
@@ -178,28 +221,19 @@ export default function AICreator(props: {
|
|
|
</span>
|
|
|
</div>
|
|
|
|
|
|
- <div className="text-14px pl-12px text-#333">绘制图形</div>
|
|
|
- <div
|
|
|
- className="chat-content bg-#f5f5f5 px-10px overflow-y-auto mt-12px"
|
|
|
- ref={scrollAreaRef}
|
|
|
- >
|
|
|
+ <div className="text-14px pl-12px text-#333">一句话生成思维导图~</div>
|
|
|
+ <div className="chat-content px-10px overflow-y-auto mt-12px">
|
|
|
<div
|
|
|
style={{
|
|
|
borderColor: focused ? "#1890ff" : "#ddd",
|
|
|
}}
|
|
|
className="chat-foot bg-#fff rounded-10px border border-solid border-1px shadow-sm"
|
|
|
>
|
|
|
- <Dropdown menu={dropDownMenu} placement="bottomLeft">
|
|
|
- <div className="text-12px pl-10px pt-10px">
|
|
|
- 帮我绘制-流程图
|
|
|
- <CaretDownOutlined />
|
|
|
- </div>
|
|
|
- </Dropdown>
|
|
|
<Form onFinish={onSubmit}>
|
|
|
<Input.TextArea
|
|
|
rows={3}
|
|
|
autoSize={{ maxRows: 3, minRows: 3 }}
|
|
|
- placeholder="你可以这样问:用户登陆流程图"
|
|
|
+ placeholder="输入你的主题或问题"
|
|
|
variant="borderless"
|
|
|
onFocus={() => setFocused(true)}
|
|
|
onBlur={() => setFocused(false)}
|
|
@@ -215,7 +249,7 @@ export default function AICreator(props: {
|
|
|
type="primary"
|
|
|
shape="circle"
|
|
|
icon={<i className="iconfont icon-stopcircle" />}
|
|
|
- onClick={stop}
|
|
|
+ onClick={handleStop}
|
|
|
></Button>
|
|
|
</Tooltip>
|
|
|
) : (
|
|
@@ -237,7 +271,7 @@ export default function AICreator(props: {
|
|
|
<div
|
|
|
key={item.key}
|
|
|
className="flex-[40%] h-50px bg-#fff rounded-10px shadow-sm flex items-center pl-10px text-12px cursor-pointer"
|
|
|
- style={{}}
|
|
|
+ onClick={() => handleAiFeature(item.key)}
|
|
|
>
|
|
|
<i
|
|
|
className={`iconfont ${item.icon} text-16px`}
|