Kaynağa Gözat

feat: 思维导图添加AI对话面板

liaojiaxing 2 ay önce
ebeveyn
işleme
65c8ed6c7a

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

@@ -9,7 +9,7 @@ export default defineConfig({
     '/favicon.ico'
   ],
   styles: [
-    '//at.alicdn.com/t/c/font_4676747_pixg9qt9e3r.css'
+    '//at.alicdn.com/t/c/font_4676747_ild1695qz8.css'
   ],
   metas: [
     { name: 'viewport', content: 'width=device-width, initial-scale=1, maximum-scale=1, user-scalable=no' }

+ 4 - 3
apps/designer/src/components/ai/AiCreator.tsx

@@ -68,12 +68,13 @@ function groupDataByDate(data: ChatHistoryItem[]): DateGroups {
   return groups;
 }
 
-export default function Chat(props: { onClose?: () => void }) {
+export default function AICreator(props: { 
+  type: 'mindmap' | 'flow',
+  onClose?: () => void 
+}) {
   const [focused, setFocused] = React.useState(false);
   const [chatStarted, setChatStarted] = React.useState(false);
-  const [scrollHeight, setScrollHeight] = React.useState(0);
   const scrollAreaRef = React.useRef<HTMLDivElement>(null);
-  const observer = useRef<ResizeObserver | null>(null);
   // 对话历史
   const [history, setHistory] = useLocalStorageState<ChatHistoryItem[]>(
     "chat-history",

+ 43 - 0
apps/designer/src/hooks/useDragResize.ts

@@ -0,0 +1,43 @@
+import { useState, useRef } from "react";
+
+export function useDragResize(setWidth: React.Dispatch<React.SetStateAction<number>>) {
+
+  const [dragging, setDragging] = useState(false);
+
+  // 拖拽改变右侧面板宽度
+  const handleMouseMove = (e: MouseEvent) => {
+    const offset = startRef.current - e.clientX;
+    requestAnimationFrame(() => {
+      setWidth((w) => {
+        const newW = w + offset;
+        if (newW < 280) return 280;
+        if (newW > window.innerWidth * 0.5) return window.innerWidth * 0.5;
+        return newW;
+      });
+      startRef.current = e.clientX;
+    });
+  };
+
+  const handleMouseUp = () => {
+    setDragging(false);
+    document.body.style.cursor = "default";
+    document.removeEventListener("mousemove", handleMouseMove);
+    document.removeEventListener("mouseup", handleMouseUp);
+  };
+
+  const startRef = useRef<number>(0);
+  // 开启左右拖拽
+  const handleMouseDown = (e: React.MouseEvent) => {
+    setDragging(true);
+    // 设置鼠标样式
+    document.body.style.cursor = "ew-resize";
+    startRef.current = e.clientX;
+    document.addEventListener("mousemove", handleMouseMove);
+    document.addEventListener("mouseup", handleMouseUp);
+  };
+
+  return {
+    dragging,
+    handleMouseDown,
+  };
+}

+ 8 - 4
apps/designer/src/models/appModel.ts

@@ -40,8 +40,10 @@ export default function appModel() {
   const [activeAI, setActiveAI] = useState<'chat' | 'creator'>();
   // 右侧面板tab activeKey
   const [rightPanelTabActiveKey, setRightPanelTabActiveKey] = useState("1");
-  // 右侧面板宽度
-  const [rightPanelWidth, setRightPanelWidth] = useState(280);
+  // 流程图右侧面板宽度
+  const [flowRightPanelWidth, setFlowRightPanelWidth] = useState(280);
+  // 思维导图右侧宽度
+  const [mindmapRightPanelWidth, setMindMapRightPanelWidth] = useState(280);
   const graphRef = useRef<Graph>();
   const [pageState, setPageState] = useState<PageSettings>({
     backgroundColor: "transparent",
@@ -184,7 +186,9 @@ export default function appModel() {
     setShowHistory,
     activeAI,
     setActiveAI,
-    rightPanelWidth,
-    setRightPanelWidth,
+    flowRightPanelWidth,
+    setFlowRightPanelWidth,
+    mindmapRightPanelWidth,
+    setMindMapRightPanelWidth
   }
 }

+ 5 - 48
apps/designer/src/pages/flow/components/Config/index.tsx

@@ -7,6 +7,7 @@ import { useModel } from "umi";
 import InsetCss from "insert-css";
 import AIChat from "@/components/ai/AIChat";
 import AICreator from "@/components/ai/AiCreator";
+import { useDragResize } from "@/hooks/useDragResize";
 
 InsetCss(`
   .shalu-tabs {
@@ -26,16 +27,15 @@ export default function Config() {
     setRightPanelTabActiveKey,
     activeAI,
     setActiveAI,
-    rightPanelWidth,
-    setRightPanelWidth,
+    setFlowRightPanelWidth
   } = useModel("appModel");
 
+  const { dragging, handleMouseDown } = useDragResize(setFlowRightPanelWidth);
+
   const firstNode = useMemo(() => {
     return selectedCell.find((cell) => cell.isNode());
   }, [selectedCell]);
 
-  const resizeRef = React.useRef<HTMLDivElement>(null);
-
   // 设置节点属性
   const handleSetNodeAttr = (attrs: any) => {
     firstNode?.setData({
@@ -94,54 +94,11 @@ export default function Config() {
     },
   ];
 
-  // useEffect(() => {
-  //   if (selectedCell?.length) {
-  //     setRightPanelTabActiveKey("2");
-  //   } else {
-  //     setRightPanelTabActiveKey("1");
-  //   }
-  // }, [selectedCell]);
-
-  const [dragging, setDragging] = useState(false);
-
-  // 拖拽改变右侧面板宽度
-  const handleMouseMove = (e: MouseEvent) => {
-    const offset = startRef.current - e.clientX;
-    requestAnimationFrame(() => {
-      setRightPanelWidth((w) => {
-        const newW = w + offset;
-        if (newW < 280) return 280;
-        if (newW > window.innerWidth * 0.5) return window.innerWidth * 0.5;
-        return newW;
-      });
-      startRef.current = e.clientX;
-    });
-  };
-
-  const handleMouseUp = () => {
-    setDragging(false);
-    document.body.style.cursor = "default";
-    document.removeEventListener("mousemove", handleMouseMove);
-    document.removeEventListener("mouseup", handleMouseUp);
-  };
-
-  const startRef = React.useRef<number>(0);
-  // 开启左右拖拽
-  const handleMouseDown = (e: React.MouseEvent) => {
-    setDragging(true);
-    // 设置鼠标样式
-    document.body.style.cursor = "ew-resize";
-    startRef.current = e.clientX;
-    document.addEventListener("mousemove", handleMouseMove);
-    document.addEventListener("mouseup", handleMouseUp);
-  };
-
   return (
     <div className="w-full h-full flex">
       <div
         className="h-full w-4px cursor-e-resize hover:bg-blue flex-shrink-0"
         style={{ backgroundColor: dragging ? "#60a5fa" : "" }}
-        ref={resizeRef}
         onMouseDown={handleMouseDown}
       ></div>
       <div className="flex-1 overflow-hidden">
@@ -149,7 +106,7 @@ export default function Config() {
           <AIChat onClose={() => setActiveAI(undefined)} />
         )}
         {activeAI === "creator" && (
-          <AICreator onClose={() => setActiveAI(undefined)} />
+          <AICreator type="flow" onClose={() => setActiveAI(undefined)} />
         )}
         {activeAI === undefined && (
           <Tabs

+ 2 - 2
apps/designer/src/pages/flow/index.tsx

@@ -9,7 +9,7 @@ import { useModel, useParams, useRequest } from "umi";
 import { useEffect } from "react";
 import { FlowchartMindMapInfo } from "@/api/systemDesigner";
 export default function HomePage() {
-  const { showRightPanel, pageState, setPageState, rightPanelWidth } = useModel("appModel");
+  const { showRightPanel, pageState, setPageState, flowRightPanelWidth } = useModel("appModel");
   const { setProjectInfo } = useModel("projectModel");
   const { initCells, updateKey } = useModel("graphModel");
 
@@ -118,7 +118,7 @@ export default function HomePage() {
               {/* 右侧配置表单 */}
               <Layout.Sider
                 className={styles.config}
-                width={showRightPanel ? rightPanelWidth : 0}
+                width={showRightPanel ? flowRightPanelWidth : 0}
               >
                 <Config />
               </Layout.Sider>

+ 29 - 4
apps/designer/src/pages/mindmap/components/Config/index.tsx

@@ -2,6 +2,7 @@ import React, { useEffect, useState, useMemo } from "react";
 import { useModel } from "umi";
 import { Tabs } from "antd";
 import InsertCss from "insert-css"
+import { useDragResize } from "@/hooks/useDragResize";
 
 import PageStyle from "./PageStyle";
 import NodeStyle from "./NodeStyle";
@@ -11,6 +12,8 @@ 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 "@/components/ai/AiCreator";
 
 InsertCss(`
   .shalu-tabs-nav {
@@ -19,7 +22,10 @@ InsertCss(`
 `)
 export default function index() {
   const { rightToobarActive, selectedCell, rightToolbarActive } = useModel("mindMapModel");
+  const { setMindMapRightPanelWidth } = useModel("appModel");
   const [activeKey, setActiveKey] = useState("1");
+  const { dragging, handleMouseDown } = useDragResize(setMindMapRightPanelWidth);
+
   useEffect(() => {
     if(selectedCell.find(cell => cell.isNode())) {
       setActiveKey("2");
@@ -39,11 +45,25 @@ export default function index() {
     });
   }
 
+  const handleCloseAI = () => {
+    rightToolbarActive("");
+  }
+
   return (
-    <div className="relative">
-      <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>
+    <div className="w-full h-full flex">
+      <div
+        className="h-full w-4px cursor-e-resize hover:bg-blue flex-shrink-0"
+        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" && (
         <>
@@ -93,6 +113,11 @@ export default function index() {
       {rightToobarActive === "tag" && <TagConfig />}
       {/* 备注 */}
       {rightToobarActive === "remark" && <Remark />}
+      {/* AI对话 */}
+      {rightToobarActive === "ai-chat" && <AIChat onClose={handleCloseAI}/>}
+      {/* AI创作 */}
+      {rightToobarActive === "ai-creator" && <AICreator type="mindmap" onClose={handleCloseAI}/>}
+    </div>
     </div>
   );
 }

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

@@ -15,70 +15,91 @@ export default function index() {
     useModel("mindMapModel");
 
   const handleAddImage = () => {
-    insertImage(selectedCell.find(node => node.isNode()));
-  }
+    insertImage(selectedCell.find((node) => node.isNode()));
+  };
 
   return (
-    <div className="absolute top-8px right-8px bg-white shadow-md rounded-4px flex flex-col p-8px">
-      <Tooltip placement="bottom" title="样式">
-        <Button
-          type="text"
-          icon={<i className="iconfont icon-yangshi" />}
-          className={rightToobarActive === "style" ? "active" : ""}
-          onClick={() => rightToolbarActive("style")}
-        />
-      </Tooltip>
-      <Tooltip placement="bottom" title="属性">
-        <Button
-          type="text"
-          icon={<i className="iconfont icon-shuxing-shouqi" />}
-          className={rightToobarActive === "attrs" ? "active" : ""}
-          onClick={() => rightToolbarActive("attrs")}
-        />
-      </Tooltip>
-      <Tooltip placement="bottom" title="结构">
-        <Button
-          type="text"
-          icon={<ApartmentOutlined />}
-          className={rightToobarActive === "structure" ? "active" : ""}
-          onClick={() => rightToolbarActive("structure")}
-        />
-      </Tooltip>
-      <Tooltip placement="bottom" title="风格">
-        <Button
-          type="text"
-          icon={<StarOutlined />}
-          className={rightToobarActive === "theme" ? "active" : ""}
-          onClick={() => rightToolbarActive("theme")}
-        />
-      </Tooltip>
-      <Divider className="my-8px" />
-      <Tooltip placement="bottom" title="图标">
-        <Button
-          type="text"
-          disabled={!selectedCell.length}
-          icon={<SmileOutlined />}
-          className={rightToobarActive === "icon" ? "active" : ""}
-          onClick={() => rightToolbarActive("icon")}
-        />
-      </Tooltip>
-      <Tooltip placement="bottom" title="图片">
-        <Button
-          type="text"
-          disabled={!selectedCell.length}
-          icon={<FileImageOutlined />}
-          onClick={handleAddImage}
-        />
-      </Tooltip>
-      <Tooltip placement="bottom" title="标签">
-        <Button
-          type="text"
-          disabled={!selectedCell.length}
-          icon={<TagOutlined />}
-          className={rightToobarActive === "tag" ? "active" : ""}
-          onClick={() => rightToolbarActive("tag")}
-        />
-      </Tooltip>
+    <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助手">
+          <Button
+            type="text"
+            icon={<i className="iconfont icon-AIduihua text-[#2984fd]" />}
+            className={rightToobarActive === "ai-chat" ? "active" : ""}
+            onClick={() => rightToolbarActive("ai-chat")}
+          />
+        </Tooltip>
+        <Tooltip placement="bottom" title="AI创作">
+          <Button
+            type="text"
+            icon={<i className="iconfont icon-AIchuangzuo text-[#aa6dff]" />}
+            className={rightToobarActive === "ai-creator" ? "active" : ""}
+            onClick={() => rightToolbarActive("ai-creator")}
+          />
+        </Tooltip>
+      </div>
+
+      <div className="bg-white shadow-md rounded-4px flex flex-col p-8px">
+        <Tooltip placement="bottom" title="样式">
+          <Button
+            type="text"
+            icon={<i className="iconfont icon-fenggeyangshi" />}
+            className={rightToobarActive === "style" ? "active" : ""}
+            onClick={() => rightToolbarActive("style")}
+          />
+        </Tooltip>
+        <Tooltip placement="bottom" title="属性">
+          <Button
+            type="text"
+            icon={<i className="iconfont icon-shuxing-shouqi" />}
+            className={rightToobarActive === "attrs" ? "active" : ""}
+            onClick={() => rightToolbarActive("attrs")}
+          />
+        </Tooltip>
+        <Tooltip placement="bottom" title="结构">
+          <Button
+            type="text"
+            icon={<ApartmentOutlined />}
+            className={rightToobarActive === "structure" ? "active" : ""}
+            onClick={() => rightToolbarActive("structure")}
+          />
+        </Tooltip>
+        <Tooltip placement="bottom" title="风格">
+          <Button
+            type="text"
+            icon={<StarOutlined />}
+            className={rightToobarActive === "theme" ? "active" : ""}
+            onClick={() => rightToolbarActive("theme")}
+          />
+        </Tooltip>
+        <Divider className="my-8px" />
+        <Tooltip placement="bottom" title="图标">
+          <Button
+            type="text"
+            disabled={!selectedCell.length}
+            icon={<SmileOutlined />}
+            className={rightToobarActive === "icon" ? "active" : ""}
+            onClick={() => rightToolbarActive("icon")}
+          />
+        </Tooltip>
+        <Tooltip placement="bottom" title="图片">
+          <Button
+            type="text"
+            disabled={!selectedCell.length}
+            icon={<FileImageOutlined />}
+            onClick={handleAddImage}
+          />
+        </Tooltip>
+        <Tooltip placement="bottom" title="标签">
+          <Button
+            type="text"
+            disabled={!selectedCell.length}
+            icon={<TagOutlined />}
+            className={rightToobarActive === "tag" ? "active" : ""}
+            onClick={() => rightToolbarActive("tag")}
+          />
+        </Tooltip>
+      </div>
     </div>
   );
 }

+ 2 - 2
apps/designer/src/pages/mindmap/index.tsx

@@ -19,7 +19,7 @@ export default function MindMap() {
   const { rightToobarActive, initMindMap, setMindProjectInfo } =
     useModel("mindMapModel");
   const graphRef = useRef<HTMLDivElement>(null);
-  const { showHistory } = useModel("appModel");
+  const { showHistory, mindmapRightPanelWidth } = useModel("appModel");
   const { setProjectInfo } = useModel("projectModel");
 
   useEffect(() => {
@@ -194,7 +194,7 @@ export default function MindMap() {
           </Layout.Content>
           <Layout.Sider
             className={styles.sider}
-            width={rightToobarActive ? 280 : 0}
+            width={rightToobarActive ? mindmapRightPanelWidth : 0}
           >
             <Config />
           </Layout.Sider>