|
@@ -7,7 +7,9 @@ import {
|
|
|
import { Button, Tooltip, Input, Form, Dropdown, message } from "antd";
|
|
|
import type { DropDownProps } from "antd";
|
|
|
import { useChat } from "@/hooks/useChat";
|
|
|
-import {} from "react-markdown";
|
|
|
+import { useModel } from "umi";
|
|
|
+import { Cell } from "@antv/x6";
|
|
|
+import { handleParseAIData } from "@/utils";
|
|
|
|
|
|
const items = [
|
|
|
{ key: "1", label: "流程图" },
|
|
@@ -27,6 +29,7 @@ export default function AICreator(props: {
|
|
|
const [messageApi, contextHolder] = message.useMessage();
|
|
|
const messageKey = "ailoading";
|
|
|
const msgContent = useRef<string>("");
|
|
|
+ const { graph } = useModel("flowchartModel");
|
|
|
|
|
|
const { loading, onRequest, cancel } = useChat({
|
|
|
app_name: "system_design",
|
|
@@ -45,7 +48,15 @@ export default function AICreator(props: {
|
|
|
marginTop: 300,
|
|
|
},
|
|
|
});
|
|
|
- handleParse();
|
|
|
+ handleParseAIData({
|
|
|
+ content: msgContent.current,
|
|
|
+ onSuccess: props.onChange,
|
|
|
+ onError: props.onError,
|
|
|
+ message: {
|
|
|
+ key: messageKey,
|
|
|
+ instance: messageApi,
|
|
|
+ },
|
|
|
+ });
|
|
|
},
|
|
|
onError: (err) => {
|
|
|
messageApi.open({
|
|
@@ -60,50 +71,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 [graphType, setGraphType] = useState<string>("流程图");
|
|
|
const dropDownMenu: DropDownProps["menu"] = {
|
|
|
items,
|
|
@@ -183,33 +150,261 @@ export default function AICreator(props: {
|
|
|
},
|
|
|
];
|
|
|
|
|
|
- const getCurrentCells = () => {
|
|
|
-
|
|
|
+ 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,
|
|
|
+ });
|
|
|
+ }
|
|
|
+ }
|
|
|
+ // 从name中提取文本
|
|
|
+ if (data?.name?.trim()) {
|
|
|
+ if (!labelMap[data.name.trim()]) {
|
|
|
+ labelMap[data.name.trim()] = [
|
|
|
+ { cell: cell.id, key: "name", cellType: cell.shape },
|
|
|
+ ];
|
|
|
+ } else {
|
|
|
+ labelMap[data.name.trim()].push({
|
|
|
+ cell: cell.id,
|
|
|
+ key: "name",
|
|
|
+ cellType: cell.shape,
|
|
|
+ });
|
|
|
+ }
|
|
|
+ }
|
|
|
+ // 从边线中提取文本
|
|
|
+ if(cell.isEdge()) {
|
|
|
+ (cell.labels || []).forEach((label) => {
|
|
|
+ const labelText = (label?.attrs?.label?.text as string)?.trim();
|
|
|
+ if(labelText) {
|
|
|
+ if (!labelMap[labelText]) {
|
|
|
+ labelMap[labelText] = [
|
|
|
+ { cell: cell.id, key: "label", cellType: cell.shape },
|
|
|
+ ];
|
|
|
+ } else {
|
|
|
+ labelMap[labelText].push({
|
|
|
+ cell: cell.id,
|
|
|
+ key: "label",
|
|
|
+ cellType: cell.shape,
|
|
|
+ });
|
|
|
+ }
|
|
|
+ }
|
|
|
+ });
|
|
|
+ }
|
|
|
+ });
|
|
|
+ return labelMap;
|
|
|
+ };
|
|
|
+
|
|
|
+ // 替换节点文本内容
|
|
|
+ const handleReplace = (labelMap: LabelMap, data: Record<string, string>) => {
|
|
|
+ Object.keys(data).forEach((key) => {
|
|
|
+ if (labelMap[key]) {
|
|
|
+ labelMap[key].forEach((item) => {
|
|
|
+ const cell = graph?.getCellById(item.cell);
|
|
|
+ if (cell && cell.shape !== "edge") {
|
|
|
+ cell.setData({
|
|
|
+ [item.key]: data[key],
|
|
|
+ });
|
|
|
+ graph?.select(cell);
|
|
|
+ } else if (cell?.isEdge()) {
|
|
|
+ // 设置边线文本
|
|
|
+ const labels = cell.getLabels();
|
|
|
+ cell.setLabels(labels.map(item => {
|
|
|
+ return {
|
|
|
+ ...item,
|
|
|
+ attrs: {
|
|
|
+ label: {
|
|
|
+ text: data?.[(item.attrs?.label?.text as string)?.trim()] || item.attrs?.label?.text
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }))
|
|
|
+ }
|
|
|
+ });
|
|
|
+ }
|
|
|
+ });
|
|
|
};
|
|
|
|
|
|
// 风格美化
|
|
|
const handleStyle = () => {
|
|
|
- onRequest('生成一个美化风格的配置', {
|
|
|
+ onRequest("生成一个美化风格的配置", {
|
|
|
onUpdate: (data) => {
|
|
|
- console.log('style update:', 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) => {
|
|
@@ -232,7 +427,12 @@ export default function AICreator(props: {
|
|
|
};
|
|
|
|
|
|
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">
|