Jelajahi Sumber

perf: AI生成优化

liaojiaxing 6 hari lalu
induk
melakukan
3ffa3e9916

+ 1 - 1
apps/designer/src/components/ai/AIChat.tsx

@@ -87,7 +87,7 @@ export default function AIChat(props: {
         <RenderGraph
           language={language}
           json={code}
-          onInsertFlow={onInsert}
+          onInsert={onInsert}
           type={graphType}
         ></RenderGraph>
       ),

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

@@ -10,7 +10,7 @@ import type { DropDownProps } from "antd";
 import { useChat } from "@/hooks/useChat";
 import { useModel } from "umi";
 import { Cell } from "@antv/x6";
-import { handleParseAIData } from "@/utils";
+import { handleParseFowlAIData } from "@/utils";
 
 const items = [
   { key: "1", label: "流程图" },
@@ -49,7 +49,7 @@ export default function AICreator(props: {
           marginTop: 300,
         },
       });
-      handleParseAIData({
+      handleParseFowlAIData({
         content: msgContent.current,
         onSuccess: props.onChange,
         onError: props.onError,
@@ -317,7 +317,7 @@ export default function AICreator(props: {
               marginTop: 300,
             },
           });
-          handleParseAIData({
+          handleParseFowlAIData({
             content: result,
             message: {
               key: messageKey,
@@ -385,7 +385,7 @@ export default function AICreator(props: {
               marginTop: 300,
             },
           });
-          handleParseAIData({
+          handleParseFowlAIData({
             content: result,
             message: {
               key: messageKey,

+ 142 - 45
apps/designer/src/components/ai/RenderGraph.tsx

@@ -1,23 +1,32 @@
-import { useEffect, useRef, useMemo } from "react";
+import { useEffect, useMemo, useRef, useState } from "react";
 import { Button, message, Tooltip } from "antd";
 import { Graph } from "@antv/x6";
-import { handleParseAIData } from "@/utils";
+import { handleParseFowlAIData } from "@/utils";
 import { getFlowNodeMetaByAi } from "@/utils/flow";
 import { getDefaultDataByTheme } from "@/config/data";
 import { PlusOutlined } from "@ant-design/icons";
 
+import { defaultProject, topicData } from "@/config/data";
+import { getTopicsByAiData, renderMindMap, buildTopic } from "@/utils/mindmap";
+import { cloneDeep, throttle } from "lodash-es";
+import { TopicType } from "@/enum";
+import { traverseNode } from "@/utils/mindmapHander";
+
 export default function RenderGraph(props: {
   language: string;
   json: string;
   type?: "flow" | "mindmap";
-  onInsertFlow?: (data: any) => void;
+  onInsert?: (data: any) => void;
   showInsert?: boolean;
 }) {
-  const { language, json, onInsertFlow, showInsert = true, type = "flow" } = props;
+  const { language, json, onInsert, showInsert = true, type = "flow" } = props;
   const containerRef = useRef<HTMLDivElement>(null);
   const graphRef = useRef<Graph>();
   const [messageApi, contextHolder] = message.useMessage();
   const parseData = useRef();
+  const [graphType, setGraphType] = useState<"flowchart" | "mindmap">(
+    "flowchart"
+  );
 
   useEffect(() => {
     graphRef.current = new Graph({
@@ -39,59 +48,147 @@ export default function RenderGraph(props: {
     });
   }, []);
 
-  useEffect(() => {
-    handleParseAIData({
-      content: json,
-      message: {
-        key: "ai-creator",
-        instance: messageApi,
+  const loadMindmap = () => {
+    const mindProjectInfo = cloneDeep(defaultProject);
+    const mainTopic = buildTopic(TopicType.main, {
+      label: "中心主题",
+      fill: {
+        ...topicData.fill,
+        color1: "#30304D",
       },
-      onSuccess: (data) => {
-        parseData.current = data;
-        // graphRef.current?.fromJSON(data);
-        const defaultDataByTheme = getDefaultDataByTheme("1");
+      text: {
+        ...topicData.text,
+        fontSize: 30,
+        color: "#fff",
+      },
+    });
 
-        const list = (Array.isArray(data) ? data : [data]).map((item) =>
-          getFlowNodeMetaByAi(item, defaultDataByTheme)
-        );
+    const result = getTopicsByAiData(
+      JSON.parse(json),
+      mainTopic.id,
+      TopicType.branch,
+      mainTopic
+    );
 
-        list.forEach((cell) => {
-          if (cell.shape !== "edge") {
-            graphRef.current?.addNode(cell);
-          }
-          if (cell.shape === "edge") {
-            graphRef.current?.addEdge(cell);
-          }
-        });
+    const render = () => {
+      renderMindMap({
+        topics: result.topics,
+        pageSetting: mindProjectInfo.pageSetting,
+        structure: mindProjectInfo.structure,
+        theme: mindProjectInfo.theme,
+        graph: graphRef.current!,
+        showTool: false,
+      });
+      setTimeout(() => {
         graphRef.current?.zoomToFit({
-          padding: 20,
+          padding: 30,
         });
-      },
-      onError: (err) => {
-        messageApi.error(err.message);
-      },
+      }, 300);
+    };
+
+    render();
+
+    graphRef.current?.on("node:change:data", (args) => {
+      const { current, previous, cell } = args;
+      if (
+        current.width !== previous.width ||
+        current.collapsed !== previous.collapsed
+      ) {
+        result.topics = traverseNode(result.topics, (topic) => {
+          if (topic.id === cell.id) {
+            topic.width = current.width;
+            topic.collapsed = current.collapsed;
+          }
+          return topic;
+        });
+        throttle(render, 1000)();
+      }
     });
+  };
+
+  useEffect(() => {
+    if (json.includes("shape")) {
+      handleParseFowlAIData({
+        content: json,
+        message: {
+          key: "ai-creator",
+          instance: messageApi,
+        },
+        onSuccess: (data) => {
+          parseData.current = data;
+          // graphRef.current?.fromJSON(data);
+          const defaultDataByTheme = getDefaultDataByTheme("1");
+
+          const list = (Array.isArray(data) ? data : [data]).map((item) =>
+            getFlowNodeMetaByAi(item, defaultDataByTheme)
+          );
+
+          list.forEach((cell) => {
+            if (cell.shape !== "edge") {
+              graphRef.current?.addNode(cell);
+            }
+            if (cell.shape === "edge") {
+              graphRef.current?.addEdge(cell);
+            }
+          });
+          graphRef.current?.zoomToFit({
+            padding: 20,
+          });
+        },
+        onError: (err) => {
+          messageApi.error(err.message);
+        },
+      });
+    } else {
+      setGraphType("mindmap");
+      loadMindmap();
+    }
   }, [language, json]);
 
+  const addButton = useMemo(() => {
+    if (showInsert && type === "flow" && graphType === "flowchart") {
+      return (
+        <Tooltip title="添加到当前画布">
+          <Button
+            variant="text"
+            size="small"
+            color="primary"
+            icon={<PlusOutlined />}
+            onClick={() => onInsert?.(parseData.current)}
+          >
+            加入画布
+          </Button>
+        </Tooltip>
+      );
+    }
+    if (showInsert && type === "mindmap" && graphType === "mindmap") {
+      return (
+        <Tooltip title="添加到选择的主题">
+          <Button
+            variant="text"
+            size="small"
+            color="primary"
+            icon={<PlusOutlined />}
+            onClick={() => onInsert?.(JSON.parse(json))}
+          >
+            加入画布
+          </Button>
+        </Tooltip>
+      );
+    }
+    return <></>;
+  }, [graphType, showInsert]);
+
   return (
     <div className="w-full">
       {contextHolder}
-      <div className="text-14px">以为您生成以下内容:</div>
-      <div className="w-full h-300px max-h-300px rounded-4px" ref={containerRef}/>
+      <div className="text-14px">为您生成以下内容:</div>
+      <div
+        className="w-full h-300px max-h-300px rounded-4px"
+        ref={containerRef}
+      />
       <div className="flex mt-8px">
-        {showInsert && type === "flow" && (
-          <Tooltip title="添加到当前画布">
-            <Button
-              variant="text"
-              size="small"
-              color="primary"
-              icon={<PlusOutlined />}
-              onClick={() => onInsertFlow?.(parseData.current)}
-            >
-              加入画布
-            </Button>
-          </Tooltip>
-        )}
+        {addButton}
       </div>
     </div>
   );

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

@@ -3,7 +3,7 @@ import { CloseOutlined, LoadingOutlined, SendOutlined } from "@ant-design/icons"
 import { Button, Tooltip, Input, Form, message } from "antd";
 import { useChat } from "@/hooks/useChat";
 import { Cell } from "@antv/x6";
-import { handleParseAIData } from "@/utils";
+import { handleParseFowlAIData } from "@/utils";
 import { useModel } from "umi";
 import { traverseNode } from "@/utils/mindmapHander";
 
@@ -37,7 +37,7 @@ export default function AICreator(props: {
           marginTop: 300,
         },
       });
-      handleParseAIData({
+      handleParseFowlAIData({
         content: msgContent.current,
         message: {
           key: messageKey,
@@ -238,7 +238,7 @@ export default function AICreator(props: {
               marginTop: 300,
             },
           });
-          handleParseAIData({
+          handleParseFowlAIData({
             content: result,
             message: {
               key: messageKey,
@@ -306,7 +306,7 @@ export default function AICreator(props: {
               marginTop: 300,
             },
           });
-          handleParseAIData({
+          handleParseFowlAIData({
             content: result,
             message: {
               key: messageKey,

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

@@ -15,10 +15,8 @@ import NodeAttrs from "@/components/NodeAttrs";
 import AICreator from "./AiCreator";
 import AIChat from "@/components/ai/AIChat";
 
-import { buildTopic } from "@/utils/mindmap";
+import { getTopicsByAiData } from "@/utils/mindmap";
 import { TopicType } from "@/enum";
-import { Node } from "@antv/x6";
-import { TopicItem } from "@/types";
 import { traverseNode } from "@/utils/mindmapHander";
 
 InsertCss(`
@@ -66,40 +64,8 @@ export default function index() {
     setMindMapRightPanelWidth(rightPanelOriginalWidth);
   };
 
-  // 记录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) {
@@ -113,7 +79,13 @@ export default function index() {
           ? TopicType.branch
           : TopicType.sub;
       // 获取AI转换数据
-      const result = getConvertData(data, currentTopic?.id, type, currentTopic);
+      const result = getTopicsByAiData(
+        data,
+        currentTopic?.id,
+        type,
+        currentTopic,
+        graph
+      );
 
       // 往节点下添加
       mindProjectInfo &&
@@ -121,7 +93,7 @@ export default function index() {
           ...mindProjectInfo,
           topics: traverseNode(mindProjectInfo.topics, (topic) => {
             if (topic.id === currentTopic?.id) {
-              topic.children = [...(topic.children || []), ...result];
+              topic.children = [...(topic.children || []), ...result.topics];
             }
             return topic;
           }),
@@ -129,7 +101,7 @@ export default function index() {
 
       setTimeout(() => {
         graph &&
-          aiAddNodeIds.current.forEach((id) => {
+          result.ids.forEach((id) => {
             graph.select(graph.getCellById(id));
           });
       }, 200);
@@ -138,11 +110,11 @@ export default function index() {
 
   return (
     <div className="w-full h-full flex">
-      <div
+      { rightToobarActive && <div
         className="h-full w-4px cursor-e-resize hover:bg-blue flex-shrink-0"
         style={{ backgroundColor: dragging ? "#60a5fa" : "" }}
         onMouseDown={handleMouseDown}
-      ></div>
+      ></div>}
       <div
         className="relative h-full w-full"
         style={{ display: rightToobarActive ? "block" : "none" }}
@@ -158,40 +130,36 @@ export default function index() {
           )}
         {/* 样式 */}
         {rightToobarActive === "style" && (
-          <>
-            <Tabs
-              activeKey={activeKey}
-              onChange={(key) => setActiveKey(key)}
-              items={[
-                {
-                  key: "1",
-                  label: "页面样式",
-                  children: <PageStyle />,
-                },
-                {
-                  key: "2",
-                  label: "主题样式",
-                  children: <NodeStyle />,
-                },
-              ]}
-            />
-          </>
+          <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} />
-                  ),
-                },
-              ]}
-            />
-          </>
+          <Tabs
+            items={[
+              {
+                key: "1",
+                label: "节点属性",
+                children: (
+                  <NodeAttrs cell={firstNode} onChange={handleSetNodeAttr} />
+                ),
+              },
+            ]}
+          />
         )}
         {/* 结构 */}
         {rightToobarActive === "structure" && <Structure />}
@@ -205,7 +173,11 @@ export default function index() {
         {rightToobarActive === "remark" && <Remark />}
         {/* AI对话 */}
         {rightToobarActive === "ai-chat" && (
-          <AIChat onClose={handleCloseAI} onInsert={handleAiCreated} />
+          <AIChat
+            graphType="mindmap"
+            onClose={handleCloseAI}
+            onInsert={handleAiCreated}
+          />
         )}
         {/* AI创作 */}
         {rightToobarActive === "ai-creator" && (

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

@@ -27,7 +27,9 @@ export default function index() {
         <Tooltip placement="bottom" title="AI助手">
           <Button
             type="text"
-            icon={<i className="iconfont icon-AIduihua text-[#2984fd]" />}
+            icon={<svg className="icon h-32px w-32px" aria-hidden="true">
+              <use xlinkHref="#icon-AI1"></use>
+            </svg>}
             // className={rightToobarActive === "ai-chat" ? "active" : ""}
             onClick={() => {
               rightToolbarActive("ai-chat");

+ 1 - 1
apps/designer/src/utils/index.ts

@@ -163,7 +163,7 @@ const replaceId = (data: any) => {
  * @param onSuccess
  * @param onError
  */
-export const handleParseAIData = ({
+export const handleParseFowlAIData = ({
   content,
   message,
   onSuccess,

+ 52 - 5
apps/designer/src/utils/mindmap/index.tsx

@@ -21,7 +21,7 @@ type RenderParams = {
   structure: StructureType;
   theme: string;
   graph: Graph;
-  setMindProjectInfo: (info: MindMapProjectInfo) => void;
+  setMindProjectInfo?: (info: MindMapProjectInfo) => void;
   returnCells?: boolean;
   showTool?: boolean;
 }
@@ -99,9 +99,9 @@ export const renderMindMap = ({
             pageSetting,
             theme,
             graph,
-            setMindProjectInfo,
             offsetX,
-            offsetY
+            offsetY,
+            setMindProjectInfo,
           );
           cells.push(...(summaryCells || []));
         }
@@ -173,9 +173,9 @@ const createSummaryCells = (
   pageSetting: MindMapProjectInfo['pageSetting'],
   theme: string,
   graph: Graph,
-  setMindProjectInfo: (info: MindMapProjectInfo) => void,
   offsetX: number,
-  offsetY: number
+  offsetY: number,
+  setMindProjectInfo?: (info: MindMapProjectInfo) => void,
 ): Cell[] => {
   let cells: Cell[] = [];
   if (summary) {
@@ -450,3 +450,50 @@ export const updateTopic = (
   traverse(projectInfo?.topics || []);
   setMindProjectInfo(projectInfo);
 };
+
+/**
+ * 根据ai数据返回思维导图主题
+ * @param data 
+ * @param parentId 
+ * @param type 
+ * @param parentTopic 
+ * @param graph 
+ * @returns 
+ */
+export const getTopicsByAiData = (
+    data: any[],
+    parentId: string,
+    type: TopicType,
+    parentTopic: TopicItem,
+    graph?: Graph
+  ) => {
+    const ids: string[] = [];
+    const topics = data.map((item) => {
+      const topic = buildTopic(
+        type,
+        { id: item.id, label: item.label },
+        graph,
+        { id: parentId, data: parentTopic } as Node
+      );
+      topic.parentId = parentId;
+      ids.push(topic.id);
+      if (item.children) {
+        const child = getTopicsByAiData(
+          item.children,
+          topic.id,
+          TopicType.sub,
+          topic,
+          graph
+        );
+        topic.children = child.topics;
+        ids.push(...child.ids);
+      }
+
+      return topic;
+    });
+
+    return {
+      topics,
+      ids
+    }
+  };

+ 0 - 1
apps/er-designer/src/components/AddTable.tsx

@@ -12,7 +12,6 @@ import {
   message,
   Modal,
   Row,
-  Select,
   Tabs,
   TabsProps,
   TreeSelect,

+ 179 - 99
apps/er-designer/src/components/ai/AICreator.tsx

@@ -7,17 +7,28 @@ import {
   Spin,
   Dropdown,
   Button,
+  Alert,
+  Drawer,
+  List,
 } from "antd";
 import type { DraggableData, DraggableEvent } from "react-draggable";
 import Draggable from "react-draggable";
 import { Sender, Welcome, Prompts, Bubble } from "@ant-design/x";
 import { PromptsProps } from "@ant-design/x";
 import aiLogo from "@/assets/icon-ai-3.png";
-import { CoffeeOutlined, SmileOutlined, UserOutlined } from "@ant-design/icons";
+import {
+  CoffeeOutlined,
+  FireOutlined,
+  HistoryOutlined,
+  PlusOutlined,
+  SmileOutlined,
+  UserOutlined,
+} from "@ant-design/icons";
 import { useChat } from "@/hooks/useChat";
 import type { GetProp } from "antd/lib";
 import MarkdownViewer from "@/components/ai/MarkdownViewer";
-import AITable from "./AITable";
+// import AITable from "./AITable";
+import { useModel } from "umi";
 
 type AICteatorProps = {
   trigger: JSX.Element;
@@ -33,6 +44,7 @@ type AICteatorProps = {
 
 export default (props: AICteatorProps) => {
   const [open, setOpen] = useState(false);
+  const [openHistory, setOpenHistory] = useState(false);
   const [disabled, setDisabled] = useState(true);
   const [bounds, setBounds] = useState({
     left: 0,
@@ -47,6 +59,9 @@ export default (props: AICteatorProps) => {
   );
   const scrollRef = useRef<HTMLDivElement>(null);
 
+  const { onCreateByAi } = useModel("erModel");
+
+  // 提示词
   const items: PromptsProps["items"] = [
     {
       key: "6",
@@ -57,15 +72,17 @@ export default (props: AICteatorProps) => {
     {
       key: "7",
       icon: <SmileOutlined style={{ color: "#FAAD14" }} />,
-      description: "创建一个订单表",
+      description: "帮我创建一个订单表",
+      disabled: false,
+    },
+    {
+      key: "8",
+      icon: <FireOutlined style={{ color: "#FAAD14" }} />,
+      description: "客户管理系统需要的表",
       disabled: false,
     },
   ];
 
-  const handleTry = () => {
-    onSubmit(messages[messages.length - 2].content as string);
-  };
-
   // bubbles角色配置
   const roles: GetProp<typeof Bubble.List, "roles"> = {
     assistant: {
@@ -80,18 +97,29 @@ export default (props: AICteatorProps) => {
       messageRender: (content) => {
         return typeof content === "string" ? (
           <Typography className={content?.includes("```") ? "w-full" : ""}>
-            {/* <MarkdownViewer content={content} /> */}
-            {content?.includes("<generate>") ? (
-              <AITable
-                content={content}
-                onChange={props.onChange}
-                onTry={handleTry}
-              ></AITable>
-            ) : (
-              <MarkdownViewer
-                content={content.replace("<chat>", "").replace("</chat>", "")}
-              />
-            )}
+            <MarkdownViewer
+              content={content}
+              codeFooter={(language, code) => {
+                const handleAdd = () => {
+                  try {
+                    const data = JSON.parse(code);
+                    onCreateByAi(data);
+                    message.success("添加成功");
+                  } catch (error) {
+                    message.error("解析失败");
+                  }
+                };
+                return (
+                  <Button
+                    type="text"
+                    icon={<PlusOutlined />}
+                    onClick={handleAdd}
+                  >
+                    添加到画布
+                  </Button>
+                );
+              }}
+            />
           </Typography>
         ) : (
           content
@@ -112,45 +140,53 @@ export default (props: AICteatorProps) => {
     },
   };
 
-  const { loading, onRequest, cancel, messages, setMessages, addConversation } =
-    useChat({
-      app_name: "data_model",
-      onSuccess: (msg) => {
-        setMessages((messages) => {
-          const arr = [...messages];
-          const query = arr[messages.length - 2].content as string;
-          arr[messages.length - 1].status = "done";
-          arr[messages.length - 1].content += `</${assistantType}>`;
-          return arr;
-        });
-      },
-      onUpdate: (msg) => {
-        setMessages((messages) => {
-          const arr = [...messages];
-          arr[messages.length - 1].content += msg.answer;
-          arr[messages.length - 1].id = msg.message_id;
-          arr[messages.length - 1].loading = false;
-          arr[messages.length - 1].status = "loading";
-          return arr;
-        });
-        setTimeout(() => {
-          const scrollHeight = scrollRef.current?.scrollHeight;
-          scrollRef.current?.scrollTo(0, scrollHeight || 0);
-        }, 200);
-      },
-      onError: (error) => {
-        message.error(error.message);
-        setMessages((messages) => {
-          const arr = [...messages];
-          arr[messages.length - 1].content = (
-            <Typography.Text type="danger">{error.message}</Typography.Text>
-          );
-          arr[messages.length - 1].status = "error";
-          arr[messages.length - 1].loading = false;
-          return arr;
-        });
-      },
-    });
+  const {
+    loading,
+    onRequest,
+    cancel,
+    messages,
+    setMessages,
+    addConversation,
+    conversationList,
+    changeConversation,
+    loadingMessages
+  } = useChat({
+    app_name: "data_model",
+    onSuccess: (msg) => {
+      setMessages((messages) => {
+        const arr = [...messages];
+        // const query = arr[messages.length - 2].content as string;
+        arr[messages.length - 1].status = "done";
+        return arr;
+      });
+    },
+    onUpdate: (msg) => {
+      setMessages((messages) => {
+        const arr = [...messages];
+        arr[messages.length - 1].content += msg.answer;
+        arr[messages.length - 1].id = msg.message_id;
+        arr[messages.length - 1].loading = false;
+        arr[messages.length - 1].status = "loading";
+        return arr;
+      });
+      setTimeout(() => {
+        const scrollHeight = scrollRef.current?.scrollHeight;
+        scrollRef.current?.scrollTo(0, scrollHeight || 0);
+      }, 200);
+    },
+    onError: (error) => {
+      message.error(error.message);
+      setMessages((messages) => {
+        const arr = [...messages];
+        arr[messages.length - 1].content = (
+          <Typography.Text type="danger">{error.message}</Typography.Text>
+        );
+        arr[messages.length - 1].status = "error";
+        arr[messages.length - 1].loading = false;
+        return arr;
+      });
+    },
+  });
 
   const triggerDom = React.cloneElement(props.trigger, {
     ...props.trigger.props,
@@ -186,7 +222,7 @@ export default (props: AICteatorProps) => {
         },
         {
           id: Date.now() + "1",
-          content: `<${assistantType}>`,
+          content: "",
           status: "loading",
           role: "assistant",
           loading: true,
@@ -195,7 +231,7 @@ export default (props: AICteatorProps) => {
 
       const query =
         assistantType === "generate"
-          ? `#角色:你是一个数据模型助手,根据需求部分的描述用生成数据模型工具为用户生成数据模型的json数据,不需要返回其他内容
+          ? `#角色:你是一个数据模型助手,根据需求部分的描述用生成数据模型工具为用户生成数据模型的json数据。
 #需求:${value.trim()}`
           : value.trim();
       onRequest(query, value.trim());
@@ -210,11 +246,19 @@ export default (props: AICteatorProps) => {
     cancel();
   };
 
+  // 点击提示词
   const handlePromptClick = (item: any) => {
     const msg = item.data.description || item.data.label;
     onSubmit(msg);
   };
 
+  // 点击历史记录
+  const handleOpenHistory = (item: any) => {
+    setOpenHistory(false);
+    changeConversation(item.sessionId);
+  };
+
+  // 获取样式
   const getStyle: React.CSSProperties = useMemo(
     () =>
       props.position
@@ -287,54 +331,90 @@ export default (props: AICteatorProps) => {
 
             <Prompts
               className="mb-10"
+              title="为您推荐:"
               items={items}
               vertical
               onItemClick={handlePromptClick}
             />
-
-            <Bubble.List autoScroll roles={roles} items={messages} />
+            <Spin spinning={loadingMessages}>
+              <Bubble.List autoScroll roles={roles} items={messages} />
+            </Spin>
           </div>
-          <Sender
-            placeholder={
-              assistantType === "generate"
-                ? "如:创建一个用户表"
-                : "请输入咨询问题"
-            }
-            prefix={
-              <Dropdown
-                menu={{
-                  items: [
-                    {
-                      key: "1",
-                      label: "生成实体表",
-                      onClick: () => {
-                        setAssistantType("generate");
-                        addConversation();
+          <div>
+            <div className="flex justify-end">
+              <Button
+                type="text"
+                icon={<PlusOutlined />}
+                onClick={() => addConversation}
+              />
+              <Button
+                type="text"
+                icon={<HistoryOutlined />}
+                onClick={() => setOpenHistory(true)}
+              />
+            </div>
+            <Sender
+              placeholder={
+                assistantType === "generate"
+                  ? "如:创建一个用户表"
+                  : "请输入咨询问题"
+              }
+              prefix={
+                <Dropdown
+                  menu={{
+                    items: [
+                      {
+                        key: "1",
+                        label: "生成实体表",
+                        onClick: () => {
+                          setAssistantType("generate");
+                          addConversation();
+                        },
                       },
-                    },
-                    {
-                      key: "2",
-                      label: "咨询聊天",
-                      onClick: () => {
-                        setAssistantType("chat");
-                        addConversation();
+                      {
+                        key: "2",
+                        label: "咨询聊天",
+                        onClick: () => {
+                          setAssistantType("chat");
+                          addConversation();
+                        },
                       },
-                    },
-                  ],
-                }}
+                    ],
+                  }}
+                >
+                  <Button type="text" className="w-80px">
+                    {assistantType === "generate" ? "生成实体表" : "咨询聊天"}
+                  </Button>
+                </Dropdown>
+              }
+              loading={loading}
+              value={input}
+              onChange={setInput}
+              onSubmit={onSubmit}
+              onCancel={onStop}
+            />
+          </div>
+        </div>
+        <Drawer
+          title="历史记录"
+          open={openHistory}
+          onClose={() => setOpenHistory(false)}
+          placement="right"
+          getContainer={false}
+        >
+          <List
+            dataSource={conversationList}
+            renderItem={(item) => (
+              <List.Item
+                className="px-12px hover:bg-gray-50  cursor-pointer"
+                key={item.id}
+                onClick={() => handleOpenHistory(item)}
               >
-                <Button type="text" className="w-80px">
-                  {assistantType === "generate" ? "生成实体表" : "咨询聊天"}
-                </Button>
-              </Dropdown>
-            }
-            loading={loading}
-            value={input}
-            onChange={setInput}
-            onSubmit={onSubmit}
-            onCancel={onStop}
+                {item.label}
+              </List.Item>
+            )}
           />
-        </div>
+        </Drawer>
       </Modal>
     </>
   );

+ 61 - 44
apps/er-designer/src/components/ai/AITable.tsx

@@ -243,6 +243,13 @@ export default function AITable(props: {
     setTimeout(() => {}, 300);
   }, [props.content]);
 
+  useEffect(() => {
+    // 改成自动添加到页面
+    if (data?.tables?.length) {
+      onCreateByAi(data);
+    }
+  }, [data]);
+
   const handleChange = (data: TableItemType) => {
     if (selectedData.find((item) => item.table.id === data.table.id)) {
     }
@@ -267,52 +274,52 @@ export default function AITable(props: {
     return can;
   }, [selectedData]);
 
-  const handleSubmit = (status: "done" | "try" | "abort") => {
-    switch (status) {
-      case "done":
-        // 校验数据
-        const result = selectedData.filter(
-          (item) => item.tableColumnList.length
-        );
-        Promise.all(
-          result.map((item) => tableRef.current[item.table.id].validate())
-        )
-          .then(() => {
-            // 勾选后 过滤失效的关联关系
-            const relations = (data?.relations || []).filter((relation) => {
-              return !!result.find((tableItem) =>
-                tableItem.tableColumnList.find(
-                  (columnItem) =>
-                    columnItem.id === relation.primaryKey ||
-                    columnItem.id === relation.foreignKey
-                )
-              );
-            });
-            onCreateByAi({
-              relations,
-              tables: result,
-            });
-            setStatus("done");
-            props.onChange?.(result);
-          })
-          .catch((err) => {
-            message.warning("请检查数据");
-            console.log("校验失败:", err);
-          });
-        break;
-      case "try":
-        setStatus("try");
-        props.onTry?.();
-        break;
-      case "abort":
-        setStatus("abort");
-        break;
-    }
-  };
+  // const handleSubmit = (status: "done" | "try" | "abort") => {
+  //   switch (status) {
+  //     case "done":
+  //       // 校验数据
+  //       const result = selectedData.filter(
+  //         (item) => item.tableColumnList.length
+  //       );
+  //       Promise.all(
+  //         result.map((item) => tableRef.current[item.table.id].validate())
+  //       )
+  //         .then(() => {
+  //           // 勾选后 过滤失效的关联关系
+  //           const relations = (data?.relations || []).filter((relation) => {
+  //             return !!result.find((tableItem) =>
+  //               tableItem.tableColumnList.find(
+  //                 (columnItem) =>
+  //                   columnItem.id === relation.primaryKey ||
+  //                   columnItem.id === relation.foreignKey
+  //               )
+  //             );
+  //           });
+  //           onCreateByAi({
+  //             relations,
+  //             tables: result,
+  //           });
+  //           setStatus("done");
+  //           props.onChange?.(result);
+  //         })
+  //         .catch((err) => {
+  //           message.warning("请检查数据");
+  //           console.log("校验失败:", err);
+  //         });
+  //       break;
+  //     case "try":
+  //       setStatus("try");
+  //       props.onTry?.();
+  //       break;
+  //     case "abort":
+  //       setStatus("abort");
+  //       break;
+  //   }
+  // };
 
   return (
     <div>
-      {data ? (
+      {/* {data ? (
         <>
           <div className="mb-12px text-#333 font-bold">
             以为你生成以下内容:
@@ -352,7 +359,8 @@ export default function AITable(props: {
             <Alert className="mt-12px" message="已放弃该方案" type="error" />
           )}
         </>
-      ) : (
+      ) : ( */}
+      {!data && (
         <div className="flex">
           {/\<generate>[\s\S]*?<\/generate>/.test(props.content) ? (
             <span>易码工坊智能助手.</span>
@@ -364,6 +372,15 @@ export default function AITable(props: {
           )}
         </div>
       )}
+      {data && (
+        <Alert
+          className="mt-12px"
+          message="已完成生成"
+          type="success"
+          showIcon
+        />
+      )}
+      {/* )} */}
     </div>
   );
 }

+ 19 - 6
apps/er-designer/src/components/ai/MarkdownViewer.tsx

@@ -2,11 +2,14 @@ import ReactMarkdown from "react-markdown";
 import remarkGfm from "remark-gfm";
 import rehypeRaw from "rehype-raw";
 import { Prism as SyntaxHighlighter } from "react-syntax-highlighter";
-import { vscDarkPlus } from "react-syntax-highlighter/dist/esm/styles/prism";
-import { useState } from "react";
+import { materialDark } from "react-syntax-highlighter/dist/esm/styles/prism";
+import { useState, useMemo } from "react";
+import "./markdown.less";
 
 interface MarkdownViewerProps {
   content: string;
+  renderCode?: (language: string, code: string) => React.ReactNode | undefined;
+  codeFooter?: (language: string, code: string) => React.ReactNode | undefined;
 }
 
 const CodeHeader: React.FC<{
@@ -30,9 +33,15 @@ const CodeHeader: React.FC<{
   </div>
 );
 
-const MarkdownViewer: React.FC<MarkdownViewerProps> = ({ content }) => {
+const MarkdownViewer: React.FC<MarkdownViewerProps> = ({ content, renderCode, codeFooter }) => {
   const [copiedIndex, setCopiedIndex] = useState<number | null>(null);
 
+  const filterContent = useMemo(() => {
+    return content
+      .replaceAll("<think>", `<div class="think"><div class="think-title"><i class="iconfont icon-sikao" style="color: #df9e20;"></i> <span>思考过程</span></div>`)
+      .replaceAll("</think>", "</div>");
+  }, [content]);
+
   const handleCopy = (code: string, index: number) => {
     navigator.clipboard.writeText(code);
     setCopiedIndex(index);
@@ -49,7 +58,10 @@ const MarkdownViewer: React.FC<MarkdownViewerProps> = ({ content }) => {
           const match = /language-(\w+)/.exec(className || "");
           const code = String(children).replace(/\n$/, "");
           const language = match ? match[1] : "";
-
+          const customRender = renderCode?.(language, code);
+          if (customRender) {
+            return customRender;
+          }
           if (match) {
             return (
               <div className="rounded-md overflow-hidden mb-4">
@@ -62,7 +74,7 @@ const MarkdownViewer: React.FC<MarkdownViewerProps> = ({ content }) => {
                 />
                 <div className="max-w-full overflow-x-auto">
                   <SyntaxHighlighter
-                    style={vscDarkPlus}
+                    style={materialDark}
                     language={language}
                     PreTag="div"
                     {...props}
@@ -75,6 +87,7 @@ const MarkdownViewer: React.FC<MarkdownViewerProps> = ({ content }) => {
                     {code}
                   </SyntaxHighlighter>
                 </div>
+                { codeFooter?.(language, code)}
               </div>
             );
           }
@@ -86,7 +99,7 @@ const MarkdownViewer: React.FC<MarkdownViewerProps> = ({ content }) => {
         },
       }}
     >
-      {content}
+      {filterContent}
     </ReactMarkdown>
   );
 };

+ 59 - 0
apps/er-designer/src/components/ai/markdown.less

@@ -0,0 +1,59 @@
+.markdown-body {
+  table {
+    width: 100%;
+    border-collapse: collapse;
+    border-spacing: 0;
+    margin-bottom: 20px;
+    font-size: 14px;
+    line-height: 1.42857143;
+    color: #333;
+    border: solid 1px #ddd;
+    th {
+      padding: 8px;
+      line-height: 1.42857143;
+      vertical-align: top;
+      border-top: 1px solid #ddd;
+      border-right: 1px solid #ddd;
+      word-break: break-all;
+    }
+    td {
+      padding: 8px;
+      line-height: 1.42857143;
+      vertical-align: top;
+      border-top: 1px solid #ddd;
+      border-right: 1px solid #ddd;
+      word-break: break-all;
+    }
+    th {
+      text-align: left;
+    }
+    th {
+      background-color: #f9f9f9;
+    }
+    tr:nth-child(2n) {
+      // background-color: #f9f9f9;
+    }
+  }
+
+  .think {
+    padding-left: 20px;
+    border-left: solid 2px #999;
+    color: #666;
+    font-size: 12px;
+  }
+
+  .think-title {
+    font-size: 14px;
+    color: #333;
+  }
+
+  img, video {
+    max-width: 100%;
+  }
+
+  pre {
+    padding: 0;
+    background: none;
+    border: none;
+  }
+}

+ 3 - 1
apps/er-designer/src/hooks/useChat.ts

@@ -192,6 +192,8 @@ export function useChat({ app_name, onSuccess, onUpdate, onError }: ChatProps) {
     }
   };
 
+  const baseUrl = process.env.NODE_ENV === "production" ? "" : "/api";
+
   /**
    * 封装智能体
    */
@@ -205,7 +207,7 @@ export function useChat({ app_name, onSuccess, onUpdate, onError }: ChatProps) {
       try {
         setLoading(true);
         const response = await fetch(
-          "https://edesign.shalu.com/api/ai/chat-message",
+          baseUrl + "/api/ai/chat-message",
           {
             method: "POST",
             body: JSON.stringify(message),

+ 1 - 0
apps/er-designer/src/pages/detail/index.tsx

@@ -105,6 +105,7 @@ export default function index() {
       setSelectKey(result?.tables?.[0]?.table?.id || "");
       if (result) {
         setProject(result, false, true);
+        graph?.centerContent();
       }
     },
   });

+ 15 - 6
apps/er-designer/src/pages/er/components/TableItem.tsx

@@ -1,4 +1,4 @@
-import { DeleteOutlined, PlusOutlined } from "@ant-design/icons";
+import { AimOutlined, DeleteOutlined, PlusOutlined, SettingOutlined } from "@ant-design/icons";
 import {
   Col,
   Row,
@@ -42,7 +42,7 @@ export default function TableItem({
   setActive: (active: string) => void;
 }) {
   const { tableColumnList = [] } = data;
-  const { addTable, deleteTable } = useModel("erModel");
+  const { addTable, deleteTable, graph } = useModel("erModel");
   const [list, setList] = useState(tableColumnList);
   const [table, setTable] = React.useState(data?.table);
 
@@ -135,6 +135,12 @@ export default function TableItem({
     });
   };
 
+  const handleAimTable = (e: React.MouseEvent) => {
+    e.stopPropagation();
+    graph?.centerCell(graph.getCellById(table.id));
+    graph?.select(table.id);
+  }
+
   return (
     <div
       className="
@@ -170,6 +176,10 @@ export default function TableItem({
           {table?.langNameList?.find((item) => item.name === "zh-CN")?.value})
         </div>
         <div>
+          <Tooltip title="画布定位">
+            <AimOutlined onClick={handleAimTable}/>
+          </Tooltip>
+          
           <Popover
             trigger="click"
             placement="right"
@@ -196,10 +206,9 @@ export default function TableItem({
               </div>
             }
           >
-            <i
-              className="iconfont icon-shezhi mr-[10px] cursor-pointer"
-              onClick={(e) => e.stopPropagation()}
-            />
+            <Tooltip title="更多设置">
+              <SettingOutlined className="mx-4px" onClick={(e) => e.stopPropagation()} />
+            </Tooltip>
           </Popover>
           <i
             className="iconfont icon-open inline-block"

+ 0 - 1
apps/er-designer/src/pages/er/components/Toolbar.tsx

@@ -193,7 +193,6 @@ export default function Toolbar() {
             <Dropdown menu={layoutMenu} placement="bottomLeft">
               <span className="text-14px leading-18px">
                 <i className="iconfont icon-layout-5-line text-20px text-#666" />
-                <DownOutlined className="text-10px ml-4px" />
               </span>
             </Dropdown>
           </div>

+ 2 - 1
apps/er-designer/src/pages/er/index.tsx

@@ -22,7 +22,7 @@ const { Header, Content, Sider } = Layout;
 
 const App: React.FC = () => {
   const containerRef = React.useRef(null);
-  const { initGraph, project, playModeEnable, exitPlayMode, setProject } =
+  const { initGraph, project, playModeEnable, exitPlayMode, setProject, graph } =
     useModel("erModel");
   const [tabActiveKey, setTabActiveKey] = useSessionStorageState(
     "tabs-active-key",
@@ -46,6 +46,7 @@ const App: React.FC = () => {
       const result = res?.result;
       if (result) {
         setProject(result, false, true);
+        graph?.centerContent();
       }
     },
   });

+ 1 - 1
packages/mindmap-render/src/utils/index.ts

@@ -155,7 +155,7 @@ function regexExtractJSON(markdown: string) {
  * @param onSuccess
  * @param onError
  */
-export const handleParseAIData = ({
+export const handleParseFowlAIData = ({
   content,
   message,
   onSuccess,