Selaa lähdekoodia

feat: 流程图添加翻译,语法修复等功能

liaojiaxing 1 kuukausi sitten
vanhempi
commit
2d3a3dbb46

+ 1 - 1
apps/designer/src/config/data.ts

@@ -135,7 +135,7 @@ export const BaseEdge: Edge.Properties = {
     name: 'manhattan',
     args: {
       excludeShapes: ['page-container-node'],
-      padding: 5
+      padding: 10
     }
   },
   attrs: {

+ 8 - 2
apps/designer/src/events/flowEvent.ts

@@ -110,6 +110,7 @@ const getCellData = (cellData: Cell.Properties) => {
     source,
     target,
     ports,
+    labels
   } = cellData;
 
   const { attrs, data, router } = cellBasicData;
@@ -125,6 +126,7 @@ const getCellData = (cellData: Cell.Properties) => {
         attrs,
         router,
         shape,
+        labels,
       }
     : // 节点
       {
@@ -173,14 +175,18 @@ export const handleGraphApiEvent = (graph: Graph) => {
       }
       timer2 = null;
       map2 = {};
-    }, 500);
+    }, 1000);
   });
 
   // 批量处理节点更新
   let timer1: any;
   let map: Record<string, any> = {};
   graph.on("cell:change:*", (args: EventArgs["cell:change:*"]) => {
-    if (args.cell?.data?.isPage || args.key === "tools") return;
+    if (
+      args.cell?.data?.isPage 
+      || args.key === "tools" 
+      || (timer2 && args.cell.shape !== 'edge' // 线可能在创建的时候不需要处理
+    )) return;
     const setData = () => {
       const id = args.cell.id;
       const data = args.cell.toJSON();

+ 254 - 54
apps/designer/src/pages/flow/components/Config/AiCreator.tsx

@@ -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">

+ 0 - 1
apps/designer/src/utils/diffrence.ts

@@ -51,7 +51,6 @@ export const getBasicData = (cellMeta: Cell.Properties) => {
   // 先根据主题获取通用数据,然后返回差异部分
   const defaultData = getDefaultDataByTheme(themeKey);
   if(cellMeta.shape === "edge") {
-    console.log(cellMeta, defaultData.edgeDefaultData)
     return getDifferences(cellMeta, defaultData.edgeDefaultData);
   } else {
     return getDifferences(cellMeta.data || {}, defaultData.nodeDefaultData);

+ 2 - 2
apps/designer/src/utils/flow.ts

@@ -62,7 +62,7 @@ export const getFlowNodeMetaByBasic = (
         name: "manhattan",
         args: {
           excludeShapes: ["page-container-node"],
-          padding: 5,
+          padding: 10,
         },
       },
       tools: [
@@ -128,7 +128,7 @@ export const getFlowNodeMetaByAi = (
         name: "manhattan",
         args: {
           excludeShapes: ["page-container-node"],
-          padding: 5,
+          padding: 10,
         },
       },
     };

+ 63 - 0
apps/designer/src/utils/index.ts

@@ -1,6 +1,7 @@
 export * from "./hander";
 import { AddGraph } from "@/api/systemDesigner";
 import { GraphType } from "@/enum";
+import type { MessageInstance } from "antd/lib/message/interface";
 
 /**
  * 打印图片
@@ -100,3 +101,65 @@ export function isMermaidFlowchart(code: string): boolean {
   const nodeRegex = /(\w+)\[(.*?)\]|\{|\}/;
   return flowchartRegex.test(code) && nodeRegex.test(code);
 }
+
+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;
+}
+
+/**
+ * 转换解析出ai数据
+ * @param content
+ * @param message
+ * @param onSuccess
+ * @param onError
+ */
+export const handleParseAIData = ({
+  content,
+  message,
+  onSuccess,
+  onError,
+}: {
+  content: string;
+  message: {
+    key: string;
+    instance: MessageInstance;
+  };
+  onSuccess?: (data: any) => void;
+  onError?: (err: Error) => void;
+}) => {
+  try {
+    // 根据markdown格式取出json部分数据
+    let json: string;
+    if (content.includes("```json")) {
+      json = regexExtractJSON(content)?.[0];
+    } else {
+      json = JSON.parse(content);
+    }
+
+    onSuccess?.(json);
+  } catch (error) {
+    message.instance.open({
+      key: message.key,
+      type: "error",
+      content: "AI创作失败",
+      duration: 2,
+      style: {
+        marginTop: 300,
+      },
+    });
+    console.error(error);
+    onError?.(new Error("AI创作失败"));
+  }
+};