|
@@ -1,23 +1,11 @@
|
|
|
import React, { useRef, useState } from "react";
|
|
|
-import {
|
|
|
- CloseOutlined,
|
|
|
- SendOutlined,
|
|
|
- EditOutlined,
|
|
|
- DeleteOutlined,
|
|
|
- CaretDownOutlined,
|
|
|
-} from "@ant-design/icons";
|
|
|
-import { Button, Tooltip, Input, Form, Dropdown, message } from "antd";
|
|
|
-import type { DropDownProps } from "antd";
|
|
|
+import { CloseOutlined, SendOutlined } from "@ant-design/icons";
|
|
|
+import { Button, Tooltip, Input, Form, message } from "antd";
|
|
|
import { useChat } from "@/hooks/useChat";
|
|
|
-import {} from "react-markdown";
|
|
|
-
|
|
|
-interface ChatHistoryItem {
|
|
|
- id: string;
|
|
|
- messages: any[];
|
|
|
- createdAt: number;
|
|
|
- updatedAt: number;
|
|
|
- title: string;
|
|
|
-}
|
|
|
+import { Cell } from "@antv/x6";
|
|
|
+import { handleParseAIData } from "@/utils";
|
|
|
+import { useModel } from "umi";
|
|
|
+import { traverseNode } from "@/utils/mindmapHander";
|
|
|
|
|
|
export default function AICreator(props: {
|
|
|
type: "mindmap" | "flow";
|
|
@@ -30,6 +18,7 @@ export default function AICreator(props: {
|
|
|
const [messageApi, contextHolder] = message.useMessage();
|
|
|
const messageKey = "ailoading";
|
|
|
const msgContent = useRef<string>("");
|
|
|
+ const { mindProjectInfo, setMindProjectInfo, graph } = useModel("mindMapModel");
|
|
|
|
|
|
const { loading, onRequest, cancel } = useChat({
|
|
|
app_name: "system_design",
|
|
@@ -48,7 +37,15 @@ export default function AICreator(props: {
|
|
|
marginTop: 300,
|
|
|
},
|
|
|
});
|
|
|
- handleParse();
|
|
|
+ handleParseAIData({
|
|
|
+ content: msgContent.current,
|
|
|
+ message: {
|
|
|
+ key: messageKey,
|
|
|
+ instance: messageApi,
|
|
|
+ },
|
|
|
+ onSuccess: props.onChange,
|
|
|
+ onError: props.onError,
|
|
|
+ });
|
|
|
},
|
|
|
onError: (err) => {
|
|
|
messageApi.open({
|
|
@@ -62,51 +59,6 @@ export default function AICreator(props: {
|
|
|
});
|
|
|
},
|
|
|
});
|
|
|
-
|
|
|
- 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({
|
|
@@ -123,11 +75,7 @@ export default function AICreator(props: {
|
|
|
// 处理提交
|
|
|
const onSubmit = () => {
|
|
|
if (input.trim()) {
|
|
|
- onRequest(
|
|
|
- `设计思维导图, 具体需求描述:${input}`,
|
|
|
- undefined,
|
|
|
- input
|
|
|
- );
|
|
|
+ onRequest(`设计思维导图, 具体需求描述:${input}`, undefined, input);
|
|
|
|
|
|
messageApi.open({
|
|
|
key: messageKey,
|
|
@@ -148,7 +96,7 @@ export default function AICreator(props: {
|
|
|
controller.abort();
|
|
|
};
|
|
|
}, []);
|
|
|
-
|
|
|
+
|
|
|
const handleList = [
|
|
|
{
|
|
|
key: "style",
|
|
@@ -176,19 +124,241 @@ export default function AICreator(props: {
|
|
|
},
|
|
|
];
|
|
|
|
|
|
+ type LabelMap = Record<
|
|
|
+ string,
|
|
|
+ { cell: string; key: string; cellType: string }[]
|
|
|
+ >;
|
|
|
+
|
|
|
+ // 从元素中提取文本 当前无选中元素时,提取所有元素
|
|
|
+ const getLabels = () => {
|
|
|
+ const labelMap: LabelMap = {};
|
|
|
+ let cells: Cell[] | undefined;
|
|
|
+
|
|
|
+ cells = graph?.getSelectedCells();
|
|
|
+ if (!cells || cells.length === 0) {
|
|
|
+ cells = graph?.getCells();
|
|
|
+ }
|
|
|
+
|
|
|
+ if (!cells || !cells.length) return;
|
|
|
+
|
|
|
+ cells.forEach((cell) => {
|
|
|
+ const data = cell.getData();
|
|
|
+ // 从label中提取文本
|
|
|
+ if (data?.label?.trim()) {
|
|
|
+ if (!labelMap[data.label.trim()]) {
|
|
|
+ labelMap[data.label.trim()] = [
|
|
|
+ { cell: cell.id, key: "label", cellType: cell.shape },
|
|
|
+ ];
|
|
|
+ } else {
|
|
|
+ labelMap[data.label.trim()].push({
|
|
|
+ cell: cell.id,
|
|
|
+ key: "label",
|
|
|
+ cellType: cell.shape,
|
|
|
+ });
|
|
|
+ }
|
|
|
+ }
|
|
|
+ });
|
|
|
+ return labelMap;
|
|
|
+ };
|
|
|
+
|
|
|
+ // 替换节点文本内容
|
|
|
+ const handleReplace = (labelMap: LabelMap, data: Record<string, string>) => {
|
|
|
+ mindProjectInfo && setMindProjectInfo({
|
|
|
+ ...mindProjectInfo,
|
|
|
+ topics: traverseNode(mindProjectInfo?.topics || [], (topic) => {
|
|
|
+ // 判断当前节点是否需要替换
|
|
|
+ const ids = labelMap[topic.label?.trim()]?.map(item => item.cell);
|
|
|
+ if (data[topic.label?.trim()] && ids?.includes(topic.id)) {
|
|
|
+ topic.label = data[topic.label?.trim()];
|
|
|
+ }
|
|
|
+ return topic
|
|
|
+ })
|
|
|
+ });
|
|
|
+ };
|
|
|
+
|
|
|
+ // 风格美化
|
|
|
+ const handleStyle = () => {
|
|
|
+ onRequest("生成一个美化风格的配置", {
|
|
|
+ onUpdate: (data) => {
|
|
|
+ console.log("style update:", data);
|
|
|
+ },
|
|
|
+ onSuccess: (data) => {
|
|
|
+ console.log(data);
|
|
|
+ },
|
|
|
+ onError: (err) => {
|
|
|
+ console.error(err);
|
|
|
+ },
|
|
|
+ });
|
|
|
+ };
|
|
|
+
|
|
|
+ // 语法修复
|
|
|
+ const handleGrammar = () => {
|
|
|
+ const labelMap = getLabels();
|
|
|
+ if (!labelMap) {
|
|
|
+ messageApi.open({
|
|
|
+ key: messageKey,
|
|
|
+ type: "info",
|
|
|
+ content: "无可修复的数据",
|
|
|
+ });
|
|
|
+ return;
|
|
|
+ }
|
|
|
+
|
|
|
+ messageApi.open({
|
|
|
+ key: messageKey,
|
|
|
+ type: "loading",
|
|
|
+ content: "AI正在修复语法...",
|
|
|
+ duration: 0,
|
|
|
+ style: {
|
|
|
+ marginTop: 300,
|
|
|
+ },
|
|
|
+ });
|
|
|
+
|
|
|
+ const data = JSON.stringify(Object.keys(labelMap));
|
|
|
+ let result = "";
|
|
|
+ onRequest(
|
|
|
+ `修复语法错误,并返回修复后的键对值,要求:
|
|
|
+1、仅返回修复后的JSON数据,不展示其他内容
|
|
|
+2、以键值对展示 如: {'大有做为': '大有作为'}
|
|
|
+ 需要修复的数据:${data}`,
|
|
|
+ {
|
|
|
+ onUpdate: (data) => {
|
|
|
+ result += data.answer;
|
|
|
+ },
|
|
|
+ onSuccess: (data) => {
|
|
|
+ messageApi.open({
|
|
|
+ key: messageKey,
|
|
|
+ type: "success",
|
|
|
+ content: "AI修复语法完成",
|
|
|
+ duration: 2,
|
|
|
+ style: {
|
|
|
+ marginTop: 300,
|
|
|
+ },
|
|
|
+ });
|
|
|
+ handleParseAIData({
|
|
|
+ content: result,
|
|
|
+ message: {
|
|
|
+ key: messageKey,
|
|
|
+ instance: messageApi,
|
|
|
+ },
|
|
|
+ onSuccess: (data) => {
|
|
|
+ handleReplace(labelMap, data);
|
|
|
+ },
|
|
|
+ });
|
|
|
+ },
|
|
|
+ onError: (err) => {
|
|
|
+ console.error(err);
|
|
|
+ messageApi.open({
|
|
|
+ key: messageKey,
|
|
|
+ type: "error",
|
|
|
+ content: err.message || "AI创作失败",
|
|
|
+ duration: 2,
|
|
|
+ style: {
|
|
|
+ marginTop: 300,
|
|
|
+ },
|
|
|
+ });
|
|
|
+ },
|
|
|
+ },
|
|
|
+ "语法修复"
|
|
|
+ );
|
|
|
+ };
|
|
|
+
|
|
|
+ // 翻译
|
|
|
+ const handleTranslation = (lang: "en" | "zh") => {
|
|
|
+ const labelMap = getLabels();
|
|
|
+ if (!labelMap) {
|
|
|
+ messageApi.open({
|
|
|
+ key: messageKey,
|
|
|
+ type: "info",
|
|
|
+ content: "无可翻译的数据",
|
|
|
+ });
|
|
|
+ return;
|
|
|
+ }
|
|
|
+
|
|
|
+ messageApi.open({
|
|
|
+ key: messageKey,
|
|
|
+ type: "loading",
|
|
|
+ content: "AI正在翻译...",
|
|
|
+ duration: 0,
|
|
|
+ style: {
|
|
|
+ marginTop: 300,
|
|
|
+ },
|
|
|
+ });
|
|
|
+
|
|
|
+ const data = JSON.stringify(Object.keys(labelMap));
|
|
|
+ let result = "";
|
|
|
+ onRequest(
|
|
|
+ `翻译成${lang === "en" ? "英文" : "中文"},并返回翻译后的键对值,要求:
|
|
|
+ 1、仅返回修复后的JSON数据,不展示其他内容
|
|
|
+ 2、以键值对展示 如: {'中文': 'Chinese'}
|
|
|
+ 需要翻译的数据:${data}`,
|
|
|
+ {
|
|
|
+ onUpdate: (data) => {
|
|
|
+ result += data.answer;
|
|
|
+ },
|
|
|
+ onSuccess: (data) => {
|
|
|
+ messageApi.open({
|
|
|
+ key: messageKey,
|
|
|
+ type: "success",
|
|
|
+ content: "AI翻译完成",
|
|
|
+ duration: 2,
|
|
|
+ style: {
|
|
|
+ marginTop: 300,
|
|
|
+ },
|
|
|
+ });
|
|
|
+ handleParseAIData({
|
|
|
+ content: result,
|
|
|
+ message: {
|
|
|
+ key: messageKey,
|
|
|
+ instance: messageApi,
|
|
|
+ },
|
|
|
+ onSuccess: (data) => {
|
|
|
+ handleReplace(labelMap, data);
|
|
|
+ },
|
|
|
+ });
|
|
|
+ },
|
|
|
+ onError: (err) => {
|
|
|
+ console.error(err);
|
|
|
+ messageApi.open({
|
|
|
+ key: messageKey,
|
|
|
+ type: "error",
|
|
|
+ content: err.message || "AI创作失败",
|
|
|
+ duration: 2,
|
|
|
+ style: {
|
|
|
+ marginTop: 300,
|
|
|
+ },
|
|
|
+ });
|
|
|
+ },
|
|
|
+ },
|
|
|
+ "翻译内容"
|
|
|
+ );
|
|
|
+ };
|
|
|
+
|
|
|
const handleAiFeature = (feature: string) => {
|
|
|
- // onRequest('', {
|
|
|
- // onUpdata: (data) => {
|
|
|
- // },
|
|
|
- // onSuccess: (data) => {
|
|
|
- // },
|
|
|
- // onError: (err) => {
|
|
|
- // }
|
|
|
- // })
|
|
|
+ switch (feature) {
|
|
|
+ case "style":
|
|
|
+ handleStyle();
|
|
|
+ break;
|
|
|
+ case "grammar":
|
|
|
+ handleGrammar();
|
|
|
+ break;
|
|
|
+ case "translation_en":
|
|
|
+ handleTranslation("en");
|
|
|
+ break;
|
|
|
+ case "translation_zh":
|
|
|
+ handleTranslation("zh");
|
|
|
+ break;
|
|
|
+ default:
|
|
|
+ break;
|
|
|
+ }
|
|
|
};
|
|
|
|
|
|
return (
|
|
|
- <div className="flex-1 h-full" style={{backgroundImage: 'linear-gradient(137deg, #e5f4ff 0%, #efe7ff 100%)'}}>
|
|
|
+ <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">
|
|
|
<span className="text-14px">
|
|
@@ -198,20 +368,6 @@ export default function AICreator(props: {
|
|
|
<span>AI创作</span>
|
|
|
</span>
|
|
|
<span>
|
|
|
- {/* <Dropdown
|
|
|
- menu={{ }}
|
|
|
- trigger={["click"]}
|
|
|
- placement="bottomLeft"
|
|
|
- arrow
|
|
|
- >
|
|
|
- <Tooltip title="历史记录">
|
|
|
- <Button
|
|
|
- type="text"
|
|
|
- size="small"
|
|
|
- icon={<FieldTimeOutlined />}
|
|
|
- ></Button>
|
|
|
- </Tooltip>
|
|
|
- </Dropdown> */}
|
|
|
<Button
|
|
|
type="text"
|
|
|
size="small"
|