فهرست منبع

feat: 思维导图添加AI组件

liaojiaxing 1 ماه پیش
والد
کامیت
ca12515d95

+ 1 - 1
apps/designer/.umirc.ts

@@ -17,7 +17,7 @@ export default defineConfig({
   scripts: [
     // 字体加载
     // '//ajax.googleapis.com/ajax/libs/webfont/1.6.26/webfont.js'
-    '//at.alicdn.com/t/c/font_4676747_dwa7i924a0c.js'
+    '//at.alicdn.com/t/c/font_4676747_ild1695qz8.js'
   ],
   plugins: [
     require.resolve('@umijs/plugins/dist/unocss'),

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

@@ -131,7 +131,13 @@ export const defaultData = {
 
 // 默认边数据
 export const BaseEdge: Edge.Properties = {
-  router: "manhattan",
+  router: {
+    name: 'manhattan',
+    args: {
+      excludeShapes: ['page-container-node'],
+      padding: 5
+    }
+  },
   attrs: {
     line: {
       stroke: "#323232",

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

@@ -173,14 +173,14 @@ export const handleGraphApiEvent = (graph: Graph) => {
       }
       timer2 = null;
       map2 = {};
-    }, 1000);
+    }, 500);
   });
 
   // 批量处理节点更新
   let timer1: any;
   let map: Record<string, any> = {};
   graph.on("cell:change:*", (args: EventArgs["cell:change:*"]) => {
-    if (args.cell?.data?.isPage || args.key === "tools" || timer2) return;
+    if (args.cell?.data?.isPage || args.key === "tools") return;
     const setData = () => {
       const id = args.cell.id;
       const data = args.cell.toJSON();

+ 10 - 6
apps/designer/src/models/flowchartModel.ts

@@ -138,7 +138,7 @@ export default function flowchartModel() {
           pointerEvents: "none",
           strict: true,
           filter: (cell: Cell) => {
-            const data = cell.getData<{ isPage: boolean; lock: boolean }>();
+            const data = cell?.getData<{ isPage: boolean; lock: boolean }>();
             return !data?.isPage && !data?.lock;
           },
         })
@@ -244,6 +244,8 @@ export default function flowchartModel() {
     (data) => {
       if(!graphRef.current || !data) return;
 
+      graphRef.current?.cleanSelection();
+
       // 查找对应主题的默认数据
       const defaultDataByTheme = getDefaultDataByTheme(pageState.theme || "1");
       // 数组
@@ -252,13 +254,14 @@ export default function flowchartModel() {
           getFlowNodeMetaByAi(item, defaultDataByTheme)
         );
         console.log('=====ai creator cells:=======\n', list);
-        list.forEach((item) => {
-          if(item.shape !== 'edge') {
-            graphRef.current?.addNode(item);
+        list.forEach((cell) => {
+          if(cell.shape !== 'edge') {
+            graphRef.current?.addNode(cell);
           }
-          if(item.shape === 'edge') {
-            graphRef.current?.addEdge(item);
+          if(cell.shape === 'edge') {
+            graphRef.current?.addEdge(cell);
           }
+          graphRef.current?.select(graphRef.current?.getCellById(cell.id));
         });
       }
       // 对象
@@ -270,6 +273,7 @@ export default function flowchartModel() {
         if(cell.shape === 'edge') {
           graphRef.current?.addEdge(cell);
         }
+        graphRef.current?.select(graphRef.current?.getCellById(cell.id));
       }
     },
     [pageState.theme, graphRef.current]

+ 104 - 136
apps/designer/src/pages/flow/components/Config/AiCreator.tsx

@@ -2,23 +2,12 @@ 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 { useChat } from "@/hooks/useChat";
-import {  } from "react-markdown"
-
-
-interface ChatHistoryItem {
-  id: string;
-  messages: any[];
-  createdAt: number;
-  updatedAt: number;
-  title: string;
-}
+import {} from "react-markdown";
 
 const items = [
   { key: "1", label: "流程图" },
@@ -27,135 +16,135 @@ const items = [
   { key: "4", label: "组织图" },
   { key: "5", label: "时序图" },
 ];
-export default function AICreator(props: { 
-  type: 'mindmap' | 'flow',
-  onClose?: () => void,
-  onChange?: (data: any) => void,
-  onError?: (err: Error) => 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 [input, setInput] = useState("");
   const [messageApi, contextHolder] = message.useMessage();
-  const messageKey = 'ailoading';
-  const msgContent = useRef<string>('');
+  const messageKey = "ailoading";
+  const msgContent = useRef<string>("");
 
-  const {
-    loading,
-    onRequest,
-    cancel
-  } = useChat({
-    app_name: "data_model",
+  const { loading, onRequest, cancel } = useChat({
+    app_name: "system_design",
     onUpdate: (msg) => {
       setInput("");
       msgContent.current += msg.answer;
     },
     onSuccess: (msg) => {
-      console.log('加载完毕!', msgContent.current);
+      console.log("加载完毕!", msgContent.current);
       messageApi.open({
         key: messageKey,
-        type: 'success',
+        type: "success",
         content: "AI创作完成",
         duration: 2,
         style: {
-          marginTop: 300
-        }
+          marginTop: 300,
+        },
       });
       handleParse();
     },
     onError: (err) => {
       messageApi.open({
         key: messageKey,
-        type: 'error',
-        content: err.message || 'AI创作失败',
+        type: "error",
+        content: err.message || "AI创作失败",
         duration: 2,
         style: {
-          marginTop: 300
-        }
+          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]);
-        }
+      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')) {
+      if (md.includes("```json")) {
         json = regexExtractJSON(msgContent.current)?.[0];
       } else {
         json = JSON.parse(msgContent.current);
       }
-      
-      console.log('解析结果:', json);
+
+      console.log("解析结果:", json);
       props.onChange?.(json);
     } catch (error) {
       messageApi.open({
         key: messageKey,
-        type: 'error',
+        type: "error",
         content: "AI创作失败",
         duration: 2,
         style: {
-          marginTop: 300
-        }
+          marginTop: 300,
+        },
       });
       console.error(error);
-      props.onError?.(new Error('AI创作失败'));
+      props.onError?.(new Error("AI创作失败"));
     }
   };
 
-  const [graphType, setGraphType] = useState<string>('流程图');
-  const dropDownMenu: DropDownProps['menu'] = {
+  const [graphType, setGraphType] = useState<string>("流程图");
+  const dropDownMenu: DropDownProps["menu"] = {
     items,
     onClick: (info) => {
       console.log(info);
-      const type = items.find(item => item.key === info.key);
-      setGraphType(type?.label || '流程图');
+      const type = items.find((item) => item.key === info.key);
+      setGraphType(type?.label || "流程图");
     },
-  }
+  };
 
   const handleStop = () => {
     cancel();
     messageApi.open({
       key: messageKey,
-      type: 'error',
+      type: "error",
       content: "AI创作已取消",
       duration: 2,
       style: {
-        marginTop: 300
-      }
+        marginTop: 300,
+      },
     });
-  }
+  };
 
   // 处理提交
   const onSubmit = () => {
     if (input.trim()) {
-      onRequest(`设计一个${graphType}, 返回图形json数据, 具体需求描述:${input}`, undefined, input);
+      onRequest(
+        `设计一个${graphType}, 返回图形json数据, 具体需求描述:${input}`,
+        undefined,
+        input
+      );
 
       messageApi.open({
         key: messageKey,
-        type: 'loading',
+        type: "loading",
         content: "AI创作中...",
         duration: 0,
         style: {
-          marginTop: 300
-        }
-      })
+          marginTop: 300,
+        },
+      });
     }
   };
 
@@ -167,50 +156,6 @@ export default function AICreator(props: {
     };
   }, []);
 
-  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: "style",
@@ -238,40 +183,65 @@ export default function AICreator(props: {
     },
   ];
 
-  const handleAiFeature = (feature: string) => {
-    // onRequest('', {
-    //   onUpdata: (data) => {
+  const getCurrentCells = () => {
+    
+  };
+
+  // 风格美化
+  const handleStyle = () => {
+    onRequest('生成一个美化风格的配置', {
+      onUpdate: (data) => {
+        console.log('style update:', data);
+      },
+      onSuccess: (data) => {
+        console.log(data);
+      },
+      onError: (err) => {
+        console.error(err);
+      }
+    })
+  };
 
-    //   },
-    //   onSuccess: (data) => {
+  // 语法修复
+  const handleGrammar = () => {
 
-    //   },
-    //   onError: (err) => {
+  };
 
-    //   }
-    // })
-  }
+  // 翻译
+  const handleTranslation = (lang: "en" | "zh") => {
+    
+  };
+
+  const handleAiFeature = (feature: string) => {
+    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">
+    <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
-            menu={{  }}
-            trigger={["click"]}
-            placement="bottomLeft"
-            arrow
-          >
-            <Tooltip title="历史记录">
-              <Button
-                type="text"
-                size="small"
-                icon={<FieldTimeOutlined />}
-              ></Button>
-            </Tooltip>
-          </Dropdown> */}
           <Button
             type="text"
             size="small"
@@ -282,9 +252,7 @@ export default function AICreator(props: {
       </div>
 
       <div className="text-14px pl-12px text-#333">绘制图形</div>
-      <div
-        className="chat-content bg-#f5f5f5 px-10px overflow-y-auto mt-12px"
-      >
+      <div className="chat-content px-10px overflow-y-auto mt-12px">
         <div
           style={{
             borderColor: focused ? "#1890ff" : "#ddd",

+ 10 - 0
apps/designer/src/pages/flow/components/Config/GraphStyle.tsx

@@ -301,6 +301,7 @@ export default function GraphStyle() {
             min={0}
             max={100}
             formatter={(val) => `${val}%`}
+            parser={(val) => Number(val?.replace('px', ''))}
             disabled={!selectedCell?.length}
             value={formModel.opacity}
             onChange={(val) => handleSetFormModel("opacity", val)}
@@ -319,6 +320,7 @@ export default function GraphStyle() {
               max={10000}
               suffix="W"
               formatter={(val) => `${val}px`}
+              parser={(val) => Number(val?.replace('px', ''))}
               disabled={!selectedCell?.length}
               value={formModel.width}
               onChange={(val) => handleSetFormModel("width", val)}
@@ -332,6 +334,7 @@ export default function GraphStyle() {
               max={10000}
               suffix="H"
               formatter={(val) => `${val}px`}
+              parser={(val) => Number(val?.replace('px', ''))}
               disabled={!selectedCell?.length}
               value={formModel.height}
               onChange={(val) => handleSetFormModel("height", val)}
@@ -348,6 +351,7 @@ export default function GraphStyle() {
               precision={0}
               suffix="X"
               formatter={(val) => `${val}px`}
+              parser={(val) => Number(val?.replace('px', ''))}
               disabled={!selectedCell?.length}
               value={formModel.x}
               onChange={(val) => handleSetFormModel("x", val)}
@@ -362,6 +366,7 @@ export default function GraphStyle() {
               precision={0}
               suffix="Y"
               formatter={(val) => `${val}px`}
+              parser={(val) => Number(val?.replace('px', ''))}
               disabled={!selectedCell?.length}
               value={formModel.y}
               onChange={(val) => handleSetFormModel("y", val)}
@@ -375,6 +380,7 @@ export default function GraphStyle() {
             min={0}
             max={360}
             formatter={(val) => `${val}°`}
+            parser={(val) => Number(val?.replace('°', ''))}
             suffix={<i className="iconfont icon-a-ziyuan126 text-12px" />}
             disabled={!selectedCell?.length}
             value={formModel.rotation}
@@ -453,6 +459,7 @@ export default function GraphStyle() {
             min={12}
             max={10000}
             formatter={(val) => `${val}px`}
+            parser={(val) => Number(val?.replace('px', ''))}
             disabled={!selectedCell?.length}
             value={formModel.text.fontSize}
             onChange={(val) => handleSetFormModel("text.fontSize", val)}
@@ -709,6 +716,7 @@ export default function GraphStyle() {
                     min={0}
                     max={360}
                     formatter={(val) => `${val}°`}
+                    parser={(val) => Number(val?.replace('°', ''))}
                     suffix={
                       <i className="iconfont icon-a-ziyuan126 text-12px" />
                     }
@@ -726,6 +734,7 @@ export default function GraphStyle() {
                     min={0}
                     max={100}
                     formatter={(val) => `${val}%`}
+                    parser={(val) => Number(val?.replace('%', ''))}
                     suffix={
                       <i className="iconfont icon-jingxiangjianbian text-12px" />
                     }
@@ -770,6 +779,7 @@ export default function GraphStyle() {
             min={1}
             max={10}
             formatter={(val) => `${val}px`}
+            parser={(val) => Number(val?.replace('px', ''))}
             disabled={!selectedCell?.length}
             value={formModel.stroke.width}
             onChange={(val) => handleSetFormModel("stroke.width", val)}

+ 3 - 0
apps/designer/src/pages/flow/components/Config/PageStyle.tsx

@@ -108,6 +108,7 @@ export default function PageStyle() {
             min={500}
             max={10000}
             formatter={(val) => `${val}px`}
+            parser={(val) => Number(val?.replace('px', ''))}
             onChange={(val) => onChangePageSettings('width', val)}
           />
           <Button icon={<SwapOutlined />} onClick={handleChangeWH}/>
@@ -118,6 +119,7 @@ export default function PageStyle() {
             min={500}
             max={10000}
             formatter={(val) => `${val}px`}
+            parser={(val) => Number(val?.replace('px', ''))}
             onChange={(val) => onChangePageSettings('height', val)}
           />
         </div>
@@ -165,6 +167,7 @@ export default function PageStyle() {
               max={100}
               formatter={(val) => `${val}px`}
               disabled={!pageState.grid}
+              parser={(val) => Number(val?.replace('px', ''))}
               onChange={(val) => onChangePageSettings('gridSize', val)}
             />
           </Form.Item>

+ 12 - 7
apps/designer/src/pages/flow/components/ToolBar/index.tsx

@@ -291,6 +291,7 @@ export default function ToolBar() {
               max={30}
               disabled={!selectedCell.length}
               formatter={(value) => `${value}px`}
+              parser={(val) => Number(val?.replace('px', ''))}
               value={formModel.fontSize}
               onChange={(value) =>
                 handleChange("all", "fontSize", "text.fontSize", value)
@@ -805,13 +806,17 @@ export default function ToolBar() {
                 ],
               }}
             > */}
-              <Button
-                type="text"
-                icon={<i className="iconfont icon-AI" />}
-                className={activeAI ? "active" : ""}
-                style={{ marginRight: 16 }}
-                onClick={() => setActiveAI("creator")}
-              />
+            <Button
+              type="text"
+              icon={
+                <svg className="icon h-24px w-24px" aria-hidden="true">
+                  <use xlinkHref="#icon-AI1"></use>
+                </svg>
+              }
+              className={activeAI ? "active" : ""}
+              style={{ marginRight: 16 }}
+              onClick={() => setActiveAI("creator")}
+            />
             {/* </Dropdown> */}
           </Tooltip>
           <Tooltip placement="bottom" title="替换">

+ 140 - 106
apps/designer/src/pages/mindmap/components/Config/AiCreator.tsx

@@ -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`}

+ 4 - 0
apps/designer/src/pages/mindmap/components/Config/NodeStyle.tsx

@@ -212,6 +212,7 @@ export default function GraphStyle() {
             min={12}
             max={10000}
             formatter={(val) => `${val}px`}
+            parser={(val) => Number(val?.replace('px', ''))}
             disabled={!formModel.fixedWidth}
             value={formModel.width}
             onChange={(val) => handleSetFormModel("width", val)}
@@ -244,6 +245,7 @@ export default function GraphStyle() {
             min={12}
             max={10000}
             formatter={(val) => `${val}px`}
+            parser={(val) => Number(val?.replace('px', ''))}
             disabled={!selectedCell?.length}
             value={formModel.text.fontSize}
             onChange={(val) => handleSetFormModel("text.fontSize", val)}
@@ -368,6 +370,7 @@ export default function GraphStyle() {
             min={1}
             max={10}
             formatter={(val) => `${val}px`}
+            parser={(val) => Number(val?.replace('px', ''))}
             disabled={!formModel.isStroke}
             value={formModel.stroke.width}
             onChange={(val) => handleSetFormModel("stroke.width", val)}
@@ -419,6 +422,7 @@ export default function GraphStyle() {
             min={1}
             max={10}
             formatter={(val) => `${val}px`}
+            parser={(val) => Number(val?.replace('px', ''))}
             disabled={!selectedCell?.length || formModel.type === TopicType.main}
             value={formModel.edge.width}
             onChange={(val) => handleSetFormModel("edge.width", val)}

+ 2 - 0
apps/designer/src/pages/mindmap/components/Config/PageStyle.tsx

@@ -66,6 +66,7 @@ export default function PageStyle() {
             min={10}
             max={200}
             formatter={(val) => `${val}px`}
+            parser={(val) => Number(val?.replace('px', ''))}
             value={pageSetting?.branchY}
             onChange={(val) => handleSetPageSetting("branchY", val)}
           />
@@ -76,6 +77,7 @@ export default function PageStyle() {
             min={20}
             max={200}
             formatter={(val) => `${val}px`}
+            parser={(val) => Number(val?.replace('px', ''))}
             value={pageSetting?.branchX}
             onChange={(val) => handleSetPageSetting("branchX", val)}
           />

+ 154 - 72
apps/designer/src/pages/mindmap/components/Config/index.tsx

@@ -1,7 +1,7 @@
-import React, { useEffect, useState, useMemo } from "react";
+import React, { useEffect, useState, useMemo, useRef } from "react";
 import { useModel } from "umi";
 import { Tabs } from "antd";
-import InsertCss from "insert-css"
+import InsertCss from "insert-css";
 import { useDragResize } from "@/hooks/useDragResize";
 
 import PageStyle from "./PageStyle";
@@ -12,22 +12,36 @@ import TagConfig from "./TagConfig";
 import IconConfig from "./IconConfig";
 import Remark from "./Remark";
 import NodeAttrs from "@/components/NodeAttrs";
-import AIChat from "@/components/ai/AIChat";
 import AICreator from "./AiCreator";
 
+import { buildTopic } from "../../mindMap";
+import { TopicType } from "@/enum";
+import { Node } from "@antv/x6";
+import { TopicItem } from "@/types";
+import { traverseNode } from "@/utils/mindmapHander";
+
 InsertCss(`
   .shalu-tabs-nav {
     padding: 0 16px
   }
-`)
+`);
 export default function index() {
-  const { rightToobarActive, selectedCell, rightToolbarActive } = useModel("mindMapModel");
+  const {
+    rightToobarActive,
+    selectedCell,
+    rightToolbarActive,
+    graph,
+    mindProjectInfo,
+    setMindProjectInfo,
+  } = useModel("mindMapModel");
   const { setMindMapRightPanelWidth } = useModel("appModel");
   const [activeKey, setActiveKey] = useState("1");
-  const { dragging, handleMouseDown } = useDragResize(setMindMapRightPanelWidth);
+  const { dragging, handleMouseDown } = useDragResize(
+    setMindMapRightPanelWidth
+  );
 
   useEffect(() => {
-    if(selectedCell.find(cell => cell.isNode())) {
+    if (selectedCell.find((cell) => cell.isNode())) {
       setActiveKey("2");
     } else {
       setActiveKey("1");
@@ -35,19 +49,78 @@ export default function index() {
   }, [selectedCell]);
 
   const firstNode = useMemo(() => {
-    return selectedCell.find(cell => cell.isNode());
+    return selectedCell.find((cell) => cell.isNode());
   }, [selectedCell]);
 
   // 设置节点属性
   const handleSetNodeAttr = (attrs: any) => {
     firstNode?.setData({
-      attrs
+      attrs,
     });
-  }
+  };
 
   const handleCloseAI = () => {
     rightToolbarActive("");
-  }
+  };
+
+  // 记录ai新增的节点id
+  const aiAddNodeIds = useRef<string[]>([]);
+  // 获取转换数据
+  const getConvertData = (data: any[], parentId: string, type: TopicType, parentTopic: TopicItem) => {
+    return data.map((item) => {
+      const topic = buildTopic(
+        type,
+        { id: item.id, label: item.label },
+        graph,
+        { id: parentId, data: parentTopic } as Node
+      );
+      topic.parentId = parentId;
+      aiAddNodeIds.current.push(topic.id);
+      if (item.children) {
+        topic.children = getConvertData(item.children, topic.id, TopicType.sub, topic);
+      }
+
+      return topic;
+    });
+  };
+
+  // 渲染AI创作
+  const handleAiCreated = (data: any) => {
+    aiAddNodeIds.current = [];
+    if (Array.isArray(data)) {
+      let currentTopic = graph?.getSelectedCells()?.[0]?.data;
+      if(!currentTopic){
+        currentTopic = mindProjectInfo?.topics?.find(
+          (topic) => topic.type === TopicType.main
+        );
+      }
+
+      const type = currentTopic?.type === TopicType.main ? TopicType.branch : TopicType.sub;
+      // 获取AI转换数据
+      const result = getConvertData(data, currentTopic?.id, type, currentTopic);
+
+      // 往节点下添加
+      mindProjectInfo && setMindProjectInfo({
+        ...mindProjectInfo,
+        topics: traverseNode(mindProjectInfo.topics, (topic) => {
+          if (topic.id === currentTopic?.id) {
+            topic.children = [
+              ...(topic.children || []),
+              ...result
+            ];
+          }
+          return topic
+        })
+      });
+
+      setTimeout(() => {
+        graph &&
+          aiAddNodeIds.current.forEach((id) => {
+            graph.select(graph.getCellById(id));
+          });
+      }, 200);
+    }
+  };
 
   return (
     <div className="w-full h-full flex">
@@ -56,68 +129,77 @@ export default function index() {
         style={{ backgroundColor: dragging ? "#60a5fa" : "" }}
         onMouseDown={handleMouseDown}
       ></div>
-      <div className="relative h-full flex-1" style={{ display: rightToobarActive ? "block" : "none" }}>
-      {
-        rightToobarActive && !['ai-chat', 'ai-creator'].includes(rightToobarActive) && (
-          <div className="absolute w-16px h-16px right-10px top-10px cursor-pointer z-99" onClick={() => rightToolbarActive("")}>
-          <i className="iconfont icon-cuowu-1 color-#666 hover:color-#333"/>
-        </div>
-        )
-      }
-      {/* 样式 */}
-      {rightToobarActive === "style" && (
-        <>
-          <Tabs
-            activeKey={activeKey}
-            onChange={(key) => setActiveKey(key)}
-            items={[
-              {
-                key: "1",
-                label: "页面样式",
-                children: <PageStyle />,
-              },
-              {
-                key: "2",
-                label: "主题样式",
-                children: <NodeStyle/>,
-              },
-            ]}
-          />
-        </>
-      )}
-      {/* 节点属性 */}
-      {rightToobarActive === "attrs" && (
-        <>
-          <Tabs
-            items={[
-              {
-                key: "1",
-                label: "节点属性",
-                children: <NodeAttrs cell={firstNode} onChange={handleSetNodeAttr} />,
-              },
-            ]}
+      <div
+        className="relative h-full flex-1"
+        style={{ display: rightToobarActive ? "block" : "none" }}
+      >
+        {rightToobarActive &&
+          !["ai-chat", "ai-creator"].includes(rightToobarActive) && (
+            <div
+              className="absolute w-16px h-16px right-10px top-10px cursor-pointer z-99"
+              onClick={() => rightToolbarActive("")}
+            >
+              <i className="iconfont icon-cuowu-1 color-#666 hover:color-#333" />
+            </div>
+          )}
+        {/* 样式 */}
+        {rightToobarActive === "style" && (
+          <>
+            <Tabs
+              activeKey={activeKey}
+              onChange={(key) => setActiveKey(key)}
+              items={[
+                {
+                  key: "1",
+                  label: "页面样式",
+                  children: <PageStyle />,
+                },
+                {
+                  key: "2",
+                  label: "主题样式",
+                  children: <NodeStyle />,
+                },
+              ]}
+            />
+          </>
+        )}
+        {/* 节点属性 */}
+        {rightToobarActive === "attrs" && (
+          <>
+            <Tabs
+              items={[
+                {
+                  key: "1",
+                  label: "节点属性",
+                  children: (
+                    <NodeAttrs cell={firstNode} onChange={handleSetNodeAttr} />
+                  ),
+                },
+              ]}
+            />
+          </>
+        )}
+        {/* 结构 */}
+        {rightToobarActive === "structure" && <Structure />}
+        {/* 风格 */}
+        {rightToobarActive === "theme" && <Theme />}
+        {/* 图标 */}
+        {rightToobarActive === "icon" && <IconConfig />}
+        {/* 标签 */}
+        {rightToobarActive === "tag" && <TagConfig />}
+        {/* 备注 */}
+        {rightToobarActive === "remark" && <Remark />}
+        {/* AI对话 */}
+        {/* {rightToobarActive === "ai-chat" && <AIChat onClose={handleCloseAI}/>} */}
+        {/* AI创作 */}
+        {rightToobarActive === "ai-creator" && (
+          <AICreator
+            type="mindmap"
+            onClose={handleCloseAI}
+            onChange={handleAiCreated}
           />
-        </>
-      )}
-      {/* 结构 */}
-      {
-        rightToobarActive === "structure" && (
-          <Structure />
-        )
-      }
-      {/* 风格 */}
-      {rightToobarActive === "theme" && <Theme />}
-      {/* 图标 */}
-      {rightToobarActive === "icon" && <IconConfig />}
-      {/* 标签 */}
-      {rightToobarActive === "tag" && <TagConfig />}
-      {/* 备注 */}
-      {rightToobarActive === "remark" && <Remark />}
-      {/* AI对话 */}
-      {rightToobarActive === "ai-chat" && <AIChat onClose={handleCloseAI}/>}
-      {/* AI创作 */}
-      {rightToobarActive === "ai-creator" && <AICreator type="mindmap" onClose={handleCloseAI}/>}
-    </div>
+        )}
+      </div>
     </div>
   );
 }

+ 5 - 5
apps/designer/src/pages/mindmap/components/RightToolbar/index.tsx

@@ -1,11 +1,9 @@
-import React from "react";
 import { Button, Divider, Tooltip } from "antd";
 import {
   ApartmentOutlined,
   FileImageOutlined,
   SmileOutlined,
   StarOutlined,
-  SwapOutlined,
   TagOutlined,
 } from "@ant-design/icons";
 import { useModel } from "umi";
@@ -21,18 +19,20 @@ export default function index() {
   return (
     <div className="absolute top-8px right-8px">
       <div className="bg-white shadow-md m-b-20px rounded-4px flex flex-col p-8px">
-        <Tooltip placement="bottom" title="AI助手">
+        {/* <Tooltip placement="bottom" title="AI助手">
           <Button
             type="text"
             icon={<i className="iconfont icon-AIduihua text-[#2984fd]" />}
             className={rightToobarActive === "ai-chat" ? "active" : ""}
             onClick={() => rightToolbarActive("ai-chat")}
           />
-        </Tooltip>
+        </Tooltip> */}
         <Tooltip placement="bottom" title="AI创作">
           <Button
             type="text"
-            icon={<i className="iconfont icon-AIchuangzuo text-[#aa6dff]" />}
+            icon={<svg className="icon h-32px w-32px" aria-hidden="true">
+              <use xlinkHref="#icon-AI1"></use>
+            </svg>}
             className={rightToobarActive === "ai-creator" ? "active" : ""}
             onClick={() => rightToolbarActive("ai-creator")}
           />

+ 3 - 1
apps/designer/src/pages/mindmap/mindMap.tsx

@@ -14,7 +14,6 @@ import {
   cacluculateExtremeValue,
   getBorderPositionAndSize,
 } from "@/utils/mindmapHander";
-import { AddMindMapElement } from "@/api/systemDesigner";
 
 /**
  * 渲染思维导图项目
@@ -299,6 +298,7 @@ export const addTopic = (
     node
   );
 
+  // 往父级节点添加子节点
   if (node) {
     const parentId = node.id;
     const traverse = (topics: TopicItem[]) => {
@@ -356,6 +356,8 @@ const topicMap = {
  * 构建一个主题数据
  * @param type 主题类型
  * @param options 配置项
+ * @param graph 图形实例
+ * @param parentNode 父节点
  * @returns
  */
 export const buildTopic = (

+ 28 - 4
apps/designer/src/utils/flow.ts

@@ -13,7 +13,8 @@ export const getFlowNodeMetaByBasic = (
   if (item.shape !== "edge") {
     const nodeMeta = getCompMap()[item.shape as string] || {};
     // ports 先取自身 再判断节点本身是否带有 最后取通用的ports
-    const ports: { items: any[]; groups: any[] } = nodeMeta?.ports || defaultDataByTheme.ports;
+    const ports: { items: any[]; groups: any[] } =
+      nodeMeta?.ports || defaultDataByTheme.ports;
     if (item.ports) {
       ports.items = JSON.parse(item.ports || "")?.items || [];
     }
@@ -41,7 +42,12 @@ export const getFlowNodeMetaByBasic = (
       newNode
     );
   } else {
-    const router = item?.router?.includes('{') ? JSON.parse(item?.router) : item?.router
+    const router = item?.router?.includes("{")
+      ? JSON.parse(item?.router)
+      : item?.router === "manhattan" // manhatan重置
+        ? undefined
+        : item?.router;
+
     const newEdge = {
       ...item,
       shape: "edge",
@@ -51,7 +57,14 @@ export const getFlowNodeMetaByBasic = (
       target: JSON.parse(item?.target),
       ...(item?.connector ? { connector: JSON.parse(item.connector) } : {}),
       ...(item?.labels ? { labels: JSON.parse(item.labels) } : {}),
-      router: router || "manhattan",
+      // 大节点可能导致连线问题,忽略这个图形
+      router: router || {
+        name: "manhattan",
+        args: {
+          excludeShapes: ["page-container-node"],
+          padding: 5,
+        },
+      },
       tools: [
         {
           name: "contextmenu",
@@ -75,7 +88,11 @@ export const getFlowNodeMetaByAi = (
   if (item.shape !== "edge") {
     const nodeMeta = getCompMap()[item.shape as string] || {};
     // ports 先取自身 再判断节点本身是否带有 最后取通用的ports
-    const ports: { items: any[]; groups: any[] } = nodeMeta?.ports || defaultDataByTheme.ports;
+    const ports: { items: any[]; groups: any[] } =
+      nodeMeta?.ports || defaultDataByTheme.ports;
+    if (item?.ports?.items) {
+      ports.items = item.ports.items;
+    }
     const newNode = {
       ...item,
       tools: [
@@ -107,6 +124,13 @@ export const getFlowNodeMetaByAi = (
           },
         },
       ],
+      router: {
+        name: "manhattan",
+        args: {
+          excludeShapes: ["page-container-node"],
+          padding: 5,
+        },
+      },
     };
     return merge(cloneDeep(defaultDataByTheme.edgeDefaultData), newEdge);
   }