Explorar o código

feat: 添加同步拉取功能

liaojiaxing hai 2 meses
pai
achega
985880d7f0

+ 1 - 1
apps/er-designer/src/api/dataModel.ts

@@ -24,7 +24,7 @@ export const GetDataModelList = (data: {
  * @param
  * @returns
  */
-export const SaveDataModel = (data: ProjectInfo) => {
+export const SaveDataModel = (data: any) => {
   return request("/api/erDiagram/dataModel/saveOrUpdate", {
     method: "POST",
     data,

+ 20 - 10
apps/er-designer/src/components/AddModel.tsx

@@ -94,20 +94,32 @@ export default React.forwardRef(function AddModel(
   const handleSubmit = () => {
     form.validateFields().then(async (values) => {
       if (editInfo.current) {
-        const params = {
+        const erDataModel = {
           ...editInfo.current,
           ...values,
+          table: undefined
+        }
+
+        const params = {
+          erDataModel
         };
         // 编辑
         await SaveDataModel(params);
         message.success("编辑成功");
-        props.onChange?.(params);
+        props.onChange?.(erDataModel);
         setOpen(false);
       } else {
         // 新增
+        const erDataModel = {
+          ...values,
+          directory: folderRef.current,
+          type: type.current,
+          setting: JSON.stringify(DEFAULT_SETTING),
+          table: undefined,
+        }
         const businessTables =
           data?.result
-            ?.filter((item: any) => values.table.includes(item.id))
+            ?.filter((item: any) => values?.table?.includes(item.id))
             ?.map((item: any) => {
               return pick(item, [
                 "aliasName",
@@ -117,13 +129,11 @@ export default React.forwardRef(function AddModel(
               ]);
             }) || [];
         const res = await SaveDataModel({
-          ...values,
-          directory: folderRef.current,
-          type: type.current,
-          setting: JSON.stringify(DEFAULT_SETTING),
-          table: undefined,
-          businessTables,
-          color,
+          erDataModel,
+          importFromBusinessTablesDto: {
+            businessTables,
+            color,
+          }
         });
         message.success("创建成功");
         props.onChange?.(res?.result);

+ 284 - 42
apps/er-designer/src/components/SyncModal.tsx

@@ -6,14 +6,32 @@ import {
   useMemo,
   useState,
 } from "react";
-import { Modal, Collapse, Empty, Steps, Result, Button, Spin } from "antd";
+import {
+  Modal,
+  Collapse,
+  Empty,
+  Steps,
+  Result,
+  Button,
+  Spin,
+  Form,
+  TreeSelect,
+  Tabs,
+  message,
+} from "antd";
 import { ProTable } from "@ant-design/pro-components";
 import DiffTable from "./DiffTable";
 import { useModel, useRequest } from "umi";
-import { PushDataModelTable } from "@/api/dataModel";
-import { GetAllDesignTables } from "@/api";
+import { PushDataModelTable, GetCanUseTableList } from "@/api/dataModel";
+import {
+  GetAllDesignTables,
+  SaveDataModel,
+  ImportFromBusinessTables,
+} from "@/api";
 import { TableItemType } from "@/type";
 import NoData from "@/assets/no-data.png";
+import CustomColorPicker from "./CustomColorPicker";
+import { pick, set } from "lodash-es";
 
 export default forwardRef(function SyncModal(
   props: { onPush: () => void },
@@ -21,11 +39,13 @@ export default forwardRef(function SyncModal(
 ) {
   const [open, setOpen] = useState(false);
   const { project } = useModel("erModel");
+  const [color, setColor] = useState<string>();
   const { data, loading, run } = useRequest(GetAllDesignTables, {
     manual: true,
   });
   const [tableList, setTableList] = useState<TableItemType[]>(project?.tables);
   const [step, setStep] = useState(0);
+  const [tabActiveKey, setTabActiveKey] = useState<string>("1");
 
   // 选中需要同步的表
   const [selectedRowKeys, setSelectedRowKeys] = useState<Key[]>([]);
@@ -34,6 +54,7 @@ export default forwardRef(function SyncModal(
   const [resultStatus, setResultStatus] = useState<"success" | "error">(
     "success"
   );
+  const [importTables, setImportTables] = useState<Key[]>([]);
 
   useEffect(() => {
     if (step === 0 && tableList.length) {
@@ -44,9 +65,12 @@ export default forwardRef(function SyncModal(
 
   useImperativeHandle(ref, () => ({
     open: () => {
+      setTabActiveKey("1");
+      setColor(undefined);
       setStep(0);
       setOpen(true);
       run();
+      run1({ type: project.type });
     },
     close: () => setOpen(false),
   }));
@@ -55,6 +79,75 @@ export default forwardRef(function SyncModal(
     setTableList(project.tables);
   }, [project.tables]);
 
+  useEffect(() => {
+    setStep(0);
+  }, [tabActiveKey]);
+
+  const {
+    data: data1,
+    loading: loading1,
+    run: run1,
+  } = useRequest(GetCanUseTableList, {
+    manual: true,
+  });
+
+  // 可引入表列表
+  const treeData = useMemo(() => {
+    const tableMap: Record<string, any> = {};
+    data1?.result
+      ?.filter(
+        (item: any) =>
+          // 过滤当前数据表类型及存在相同schemaName、aliasName的表
+          item?.type == project?.type
+        // && !project.tables.find(
+        //   (tableItem) =>
+        //     tableItem.table.schemaName === item.schemaName ||
+        //     tableItem.table.aliasName === item.aliasName
+        // )
+      )
+      ?.forEach((item: any) => {
+        // 判断是否存在已引入的表
+        const hasTable = project.tables.find(
+          (tableItem: TableItemType) =>
+            tableItem.table.schemaName === item.schemaName ||
+            tableItem.table.aliasName === item.aliasName
+        );
+
+        const option = {
+          key: item.id,
+          title: `${item?.schemaName}(${item.name})`,
+          value: item?.id,
+          disabled: hasTable,
+          selectable: !item.parentBusinessTableId,
+        };
+        // 子表
+        if (item.parentBusinessTableId) {
+          if (tableMap[item.parentBusinessTableId]) {
+            tableMap[item.parentBusinessTableId].children.push(option);
+          } else {
+            tableMap[item.parentBusinessTableId] = {
+              children: [option],
+            };
+          }
+        } else {
+          // 主表
+          if (tableMap[item.id]) {
+            tableMap[item.id] = {
+              ...tableMap[item.parentBusinessTableId],
+              ...option,
+            };
+          } else {
+            tableMap[item.id] = {
+              ...option,
+              children: [],
+            };
+          }
+        }
+        return;
+      });
+    return Object.keys(tableMap).map((key) => tableMap[key]);
+  }, [data1]);
+
   // 已存在的数据表
   const existTableList = useMemo(() => {
     const list: {
@@ -64,9 +157,9 @@ export default forwardRef(function SyncModal(
     const tables = data?.result?.appBusinessTables || [];
     selectedRows.forEach((tableItem) => {
       const dataTable = tables.find(
-        (item: any) =>
-          item.schemaName === tableItem.table.schemaName ||
-          item.aliasName === tableItem.table.aliasName
+        (item: any) => item.id === tableItem.table?.businessTableId
+        // item.schemaName === tableItem.table.schemaName ||
+        // item.aliasName === tableItem.table.aliasName
       );
       if (dataTable) {
         list.push({
@@ -92,7 +185,7 @@ export default forwardRef(function SyncModal(
     return (
       <>
         <ProTable
-          title={() => "请选择需要推送的表"}
+          title={() => "请选择模型表"}
           dataSource={tableList.map((item) => item.table)}
           rowKey="id"
           search={false}
@@ -172,7 +265,7 @@ export default forwardRef(function SyncModal(
             <>
               其中有
               <span className="color-#faad14">{existTableList.length}</span>
-              张表存在相同的编码或别名,可对比确认后提交
+              张表已存在,可对比调整差异后开始操作
             </>
           ) : null}
         </div>
@@ -237,15 +330,14 @@ export default forwardRef(function SyncModal(
     );
   };
 
-  const handleSubmit = async () => {
+  const handlePush = async () => {
     setStep(2);
     try {
       setOkLoading(true);
       await PushDataModelTable(selectedRows);
       // message.success("同步推送完成");
+
       setResultStatus("success");
-      setOpen(false);
-      props.onPush?.();
     } catch (err) {
       setResultStatus("error");
     } finally {
@@ -253,9 +345,72 @@ export default forwardRef(function SyncModal(
     }
   };
 
+  const handlePull = async () => {
+    setOkLoading(true);
+    try {
+      // 1、更新原有表的数据
+      const tableIds = selectedRows.map(({ table }) => table.id);
+      await SaveDataModel({
+        erDataModel: {
+          ...project,
+          tables: project.tables.map((item) => {
+            if (tableIds.includes(item.table.id)) {
+              return (
+                selectedRows.find((item) => item.table.id === item.table.id) ||
+                item
+              );
+            }
+            return item;
+          }),
+        },
+      });
+    } catch (err) {
+      console.log(err);
+      message.error("拉取失败");
+      setResultStatus("error");
+    }
+
+    // 2、添加引入数据
+    try {
+      const businessTables =
+        data1?.result
+          ?.filter((item: any) => importTables?.includes(item.id))
+          ?.map((item: any) => {
+            return pick(item, [
+              "aliasName",
+              "schemaName",
+              "id",
+              "parentBusinessTableId",
+            ]);
+          }) || [];
+      if (!businessTables.length) {
+        setResultStatus("success");
+        setStep(2);
+        return;
+      }
+      await ImportFromBusinessTables({
+        dataModelId: project.id,
+        color,
+        businessTables,
+      });
+    } catch (err) {
+      console.log(err);
+      message.error("引入失败");
+      setResultStatus("error");
+    } finally {
+      setOkLoading(false);
+      setStep(2);
+    }
+  };
+
+  const handleDone = () => {
+    setOpen(false);
+    props.onPush?.();
+  };
+
   return (
     <Modal
-      title="数据同步推送"
+      title="数据同步"
       width={"100%"}
       open={open}
       loading={loading}
@@ -290,42 +445,129 @@ export default forwardRef(function SyncModal(
             {step > 0 && (
               <Button onClick={() => setStep(step - 1)}>上一步</Button>
             )}
-            {step === 1 && (
-              <Button type="primary" onClick={handleSubmit}>
-                开始同步
+            {step === 2 && resultStatus === "success" && (
+              <Button type="primary" onClick={handleDone}>
+                完成
+              </Button>
+            )}
+            {step === 1 && tabActiveKey === "1" && (
+              <Button type="primary" onClick={handlePull}>
+                开始
+              </Button>
+            )}
+            {step === 1 && tabActiveKey === "2" && (
+              <Button type="primary" onClick={handlePush}>
+                开始
               </Button>
             )}
           </>
         );
       }}
     >
-      <div className="h-full flex flex-col overflow-hidden">
-        <div className="py-12px px-30px">
-          <Steps
-            current={step}
-            progressDot
-            items={[
-              {
-                title: "选择数据表",
-                description: "选择需要同步的表",
-              },
-              {
-                title: "差异对比",
-                description: "数据模型与数据表进行对比",
-              },
-              {
-                title: "推送",
-                description: "推送到数据表",
-              },
-            ]}
-          />
-        </div>
-        <div className="flex-1 overflow-auto">
-          {step === 0 && <Step1Comp />}
-          {step === 1 && <Step2Comp />}
-          {step === 2 && <Step3Comp />}
-        </div>
-      </div>
+      <Tabs
+        activeKey={tabActiveKey}
+        onChange={setTabActiveKey}
+        items={[
+          {
+            key: "1",
+            label: "拉取",
+            children: (
+              <div className="h-full flex flex-col overflow-hidden">
+                <div className="py-12px px-30px">
+                  <Steps
+                    current={step}
+                    // progressDot
+                    items={[
+                      {
+                        title: "选择数据表",
+                        description: "选择需要拉取的表",
+                      },
+                      {
+                        title: "差异对比",
+                        description: "数据模型与数据表进行对比",
+                      },
+                      {
+                        title: "结果",
+                        description: "拉取到模型",
+                      },
+                    ]}
+                  />
+                </div>
+                {step === 0 && (
+                  <>
+                    <Form>
+                      <Form.Item label="引入表" name="table">
+                        <TreeSelect
+                          multiple
+                          treeCheckable
+                          allowClear
+                          placeholder="请选择"
+                          loading={loading1}
+                          treeData={treeData}
+                          value={importTables}
+                          onChange={(value) => {
+                            setImportTables(value);
+                          }}
+                        />
+                      </Form.Item>
+                      <Form.Item label="颜色" name="color">
+                        <CustomColorPicker onChange={setColor}>
+                          {color ? (
+                            <div
+                              className="rounded-4px cus-btn w-32px h-32px bg-#eee flex-none cursor-pointer shadow-inner"
+                              style={{ background: color || "#eee" }}
+                            ></div>
+                          ) : (
+                            <span className="bg-#eee px-5px py-3px rounded-4px cursor-pointer text-12px text-#666">
+                              随机生成,点击可选择颜色
+                            </span>
+                          )}
+                        </CustomColorPicker>
+                      </Form.Item>
+                    </Form>
+                    <Step1Comp />
+                  </>
+                )}
+                {step === 1 && <Step2Comp />}
+                {step === 2 && <Step3Comp />}
+              </div>
+            ),
+          },
+          {
+            key: "2",
+            label: "推送",
+            children: (
+              <div className="h-full flex flex-col overflow-hidden">
+                <div className="py-12px px-30px">
+                  <Steps
+                    current={step}
+                    // progressDot
+                    items={[
+                      {
+                        title: "选择数据表",
+                        description: "选择需要同步的表",
+                      },
+                      {
+                        title: "差异对比",
+                        description: "数据模型与数据表进行对比",
+                      },
+                      {
+                        title: "结果",
+                        description: "推送到数据表",
+                      },
+                    ]}
+                  />
+                </div>
+                <div className="flex-1 overflow-auto">
+                  {step === 0 && <Step1Comp />}
+                  {step === 1 && <Step2Comp />}
+                  {step === 2 && <Step3Comp />}
+                </div>
+              </div>
+            ),
+          },
+        ]}
+      />
       {!tableList.length && (
         <Empty
           image={NoData}

+ 70 - 32
apps/er-designer/src/hooks/useChat.ts

@@ -2,6 +2,7 @@ import { useXAgent, XStream } from "@ant-design/x";
 import { useEffect, useRef, useState } from "react";
 import { useSessionStorageState } from "ahooks";
 import { GetSessionList, GetSessionMessageList } from "@/api/ai";
+// import { getDateGroupString } from "@/utils";
 
 import type { ConversationsProps } from "@ant-design/x";
 import type { ReactNode } from "react";
@@ -42,8 +43,6 @@ type ChatProps = {
   app_name: string;
   // 会话id 后续会话带入
   conversation_id?: string;
-  // 开始流式传输内容
-  onStart?: (data?: ResponseMessageItem) => void;
   // 成功获取会话内容
   onSuccess?: (data: ResponseMessageItem) => void;
   // 更新流式消息内容
@@ -56,9 +55,10 @@ const defaultConversation = {
   // 会话id
   key: "1",
   label: "新的对话",
+  group: '今日'
 };
 
-export function useChat({ app_name, onStart, onSuccess, onUpdate, onError }: ChatProps) {
+export function useChat({ app_name, onSuccess, onUpdate, onError }: ChatProps) {
   /**
    * 发送消息加载状态
    */
@@ -93,39 +93,73 @@ export function useChat({ app_name, onStart, onSuccess, onUpdate, onError }: Cha
   // 当前智能体对象
   const [currentAgent, setCurrentAgent] = useSessionStorageState("agent-map");
 
-  useEffect(() => {
+  // 有更多对话
+  const [hasMoreConversation, setHasMoreConversation] = useState(false);
+
+  // 会话分页
+  const [pageIndex, setPageIndex] = useState(1);
+
+  const getSession = (page: number) => {
     setLoadingSession(true);
     GetSessionList({
       app_name,
-      page_index: 1,
+      page_index: page,
     })
       .then((res) => {
-        setConversationList([
-          { ...defaultConversation },
-          ...(res?.result?.model || []).map((item: any) => ({
-            ...item,
-            key: item.sessionId,
-            label: item.name,
-          })),
-        ]);
+        if(page === 1)  {
+          setConversationList([
+            { ...defaultConversation },
+            ...(res?.result?.model || []).map((item: any) => ({
+              ...item,
+              key: item.sessionId,
+              label: item.name,
+              // group: getDateGroupString(item.updateTime)
+            })),
+          ]);
+        } else {
+          setConversationList([
+            ...(conversationList || []),
+            ...(res?.result?.model || []).map((item: any) => ({
+              ...item,
+              key: item.sessionId,
+              label: item.name,
+              // group: getDateGroupString(item.updateTime)
+            })),
+          ]);
+        }
+        setHasMoreConversation(res?.result.totalPages > page);
       })
       .finally(() => {
         setLoadingSession(false);
       });
+  }
+
+  // 切换app时获取会话记录
+  useEffect(() => {
+    setPageIndex(1);
+    getSession(1);
   }, [app_name]);
 
+  /**
+   * 加载更多会话
+   */
+  const loadMoreConversation = () => {
+    getSession(pageIndex + 1);
+    setPageIndex(pageIndex + 1);
+  };
+
   /**
    * 切换会话
    * @param key 会话id
    * @returns 
    */
   const changeConversation = async (key: string) => {
-    cancel();
     setActiveConversation(key);
     if (key === "1") {
       setMessages([]);
       return;
     }
+    cancel();
     setLoadingMessages(true);
     // 获取会话内容
     try {
@@ -158,22 +192,20 @@ export function useChat({ app_name, onStart, onSuccess, onUpdate, onError }: Cha
     }
   };
 
-  const baseUrl = process.env.NODE_ENV === "production" ? "" : "/api";
-
   /**
    * 封装智能体
    */
   const [agent] = useXAgent<ResponseMessageItem>({
     request: async (message, { onError, onSuccess, onUpdate }) => {
+      abortController.current = new AbortController();
+      const signal = abortController.current.signal;
       const enterpriseCode = sessionStorage.getItem("enterpriseCode");
       const token = localStorage.getItem("token_" + enterpriseCode) || '';
 
-      abortController.current = new AbortController();
-      const signal = abortController.current.signal;
       try {
         setLoading(true);
         const response = await fetch(
-          baseUrl + "/api/ai/chat-message",
+          "https://edesign.shalu.com/api/ai/chat-message",
           {
             method: "POST",
             body: JSON.stringify(message),
@@ -196,23 +228,21 @@ export function useChat({ app_name, onStart, onSuccess, onUpdate, onError }: Cha
                 onUpdate(data);
               } else if (data?.event === "message_end") {
                 onSuccess(data);
+              } else if (data?.event === "message_error") {
+                onError(data);
               } else if (data?.event === "ping") {
                 console.log(">>>> stream start <<<<");
-                onStart?.(data);
               } else {
                 console.log(">>>> stream error <<<<");
-                console.log(data);
-                onError(data?.message || "请求失败");
+                onError(Error(data?.message || '请求失败'));
               }
             }
           }
         } else {
           // 接口异常处理
           response.json().then(res => {
-            if(res.code === 0 ) {
               onError?.(Error(res?.error || '请求失败'));
               cancel();
-            }
           });
         }
       } catch (error) {
@@ -231,12 +261,8 @@ export function useChat({ app_name, onStart, onSuccess, onUpdate, onError }: Cha
    * 发起请求
    * @param chat_query 对话内容
    */
-  const onRequest = (chat_query: string, callbacks?: {
-    onUpdate: (message: ResponseMessageItem) => void;
-    onSuccess: (message: ResponseMessageItem) => void;
-    onError: (error: Error) => void;
-  }, name?: string) => {
-    setConversationList((list) => {
+  const onRequest = (chat_query: string, sessionName?: string) => {
+    activeConversation === '1' && setConversationList((list) => {
       return list?.map((item) => {
         return {
           ...item,
@@ -248,11 +274,11 @@ export function useChat({ app_name, onStart, onSuccess, onUpdate, onError }: Cha
       {
         app_name,
         chat_query,
-        chat_name: activeConversation === "1" ? name || chat_query : undefined,
+        chat_name: activeConversation === "1" ? sessionName || chat_query : undefined,
         conversation_id:
           activeConversation === "1" ? undefined : activeConversation,
       },
-      callbacks ?? {
+      {
         onSuccess: (data) => {
           onSuccess?.(data);
         },
@@ -285,6 +311,7 @@ export function useChat({ app_name, onStart, onSuccess, onUpdate, onError }: Cha
    */
   const cancel = () => {
     abortController.current?.abort();
+    setLoading(false);
   };
 
   /**
@@ -305,6 +332,14 @@ export function useChat({ app_name, onStart, onSuccess, onUpdate, onError }: Cha
     }
   };
 
+  /**
+   * 更新会话列表
+   */
+  const refreshConversationList = () => {
+    setPageIndex(1);
+    getSession(1);
+  };
+
   return {
     agent,
     loading,
@@ -320,5 +355,8 @@ export function useChat({ app_name, onStart, onSuccess, onUpdate, onError }: Cha
     onRequest,
     addConversation,
     changeConversation,
+    loadMoreConversation,
+    hasMoreConversation,
+    refreshConversationList
   };
 }

+ 5 - 3
apps/er-designer/src/models/erModel.tsx

@@ -85,7 +85,7 @@ export default function erModel() {
     // 清除定时器
     clearTimeout(timer.current);
     timer.current = setTimeout(() => {
-      SaveDataModel(info);
+      SaveDataModel({ erDataModel: info});
       // 格式化当前时间
       setSaveTime(dayjs().format("YYYY-MM-DD HH:mm:ss"));
     }, 500);
@@ -969,8 +969,10 @@ export default function erModel() {
           const res = await UploadFile(formData);
 
           await SaveDataModel({
-            ...state,
-            coverImage: res?.result?.[0]?.id,
+            erDataModel: {
+              ...state,
+              coverImage: res?.result?.[0]?.id,
+            }
           }).finally(() => {
             message.destroy();
           });

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

@@ -415,9 +415,9 @@ export default function index() {
                 </Button>
                 <AICreator
                   position={{
-                    bottom: 10,
+                    // bottom: 10,
                     right: 10,
-                    top: "auto"
+                    top: 20
                   }}
                   onChange={handleAiCreate}
                   trigger={

+ 130 - 116
apps/er-designer/src/pages/er/components/AICreator.tsx

@@ -1,12 +1,13 @@
 import React, { useMemo, useRef, useState } from "react";
-import { Modal, message } from "antd";
+import { Modal, Typography, message, Space, Spin } from "antd";
 import type { DraggableData, DraggableEvent } from "react-draggable";
 import Draggable from "react-draggable";
-import { Sender, Welcome, Prompts } from "@ant-design/x";
+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, FireOutlined, SmileOutlined } from "@ant-design/icons";
+import { CoffeeOutlined, SmileOutlined, UserOutlined } from "@ant-design/icons";
 import { useChat } from "@/hooks/useChat";
+import type { GetProp } from "antd/lib";
 
 type AICteatorProps = {
   trigger: JSX.Element;
@@ -33,14 +34,40 @@ const items: PromptsProps["items"] = [
     description: "创建一个订单表",
     disabled: false,
   },
-  // {
-  //   key: "8",
-  //   icon: <FireOutlined style={{ color: "#FF4D4F" }} />,
-  //   description: "创建一个商品表",
-  //   disabled: false,
-  // },
 ];
 
+// bubbles角色配置
+const roles: GetProp<typeof Bubble.List, "roles"> = {
+  assistant: {
+    placement: "start",
+    avatar: undefined,
+    loadingRender: () => (
+      <Space>
+        <Spin size="small" />
+        思考中...
+      </Space>
+    ),
+    messageRender: (content) => {
+      return typeof content === "string" ? (
+        <Typography className={content?.includes("```") ? "w-full" : ""}>
+          {/* <MarkdownViewer content={content} /> */}
+          {content}
+        </Typography>
+      ) : (
+        content
+      );
+    },
+    header: "数据模型助手",
+  },
+  user: {
+    placement: "start",
+    avatar: undefined,
+    messageRender: (content) => {
+      return <div style={{ whiteSpace: "pre-wrap" }}>{content}</div>;
+    },
+  },
+};
+
 export default (props: AICteatorProps) => {
   const [open, setOpen] = useState(false);
   const [disabled, setDisabled] = useState(true);
@@ -72,65 +99,56 @@ export default (props: AICteatorProps) => {
     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 handleParseJsonByMd = (str: string) => {
+    // 根据markdown格式取出json部分数据
+    const md = str;
+    let json: string;
+    if (md.includes("```json")) {
+      json = regexExtractJSON(msgContent.current)?.[0];
+    } else {
+      json = JSON.parse(msgContent.current);
     }
+
+    // console.log("解析结果:", json);
+    // props.onChange?.(json);
+    return json;
   };
 
-  const { loading, onRequest, cancel } = useChat({
-    app_name: "data_model",
-    onUpdate: (msg) => {
-      setInput("");
-      msgContent.current += msg.answer;
-    },
-    onSuccess: (msg) => {
-      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,
-        },
-      });
-    },
-  });
+  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].footer = <></>;
+          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;
+        });
+      },
+      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,
@@ -155,40 +173,23 @@ export default (props: AICteatorProps) => {
 
   const onSubmit = (value: string) => {
     if (value.trim()) {
-      const query = `设计一个数据模型内容,需求如下:${value.trim()}`;
-      onRequest(query, undefined, value);
-
-      messageApi.open({
-        key: messageKey,
-        type: "loading",
-        content: (
-          <span>
-            <svg className="icon mr-4px color-#666" aria-hidden="true">
-              <use xlinkHref="#icon-AI1"></use>
-            </svg>
-            <span>AI创作中...</span>
-          </span>
-        ),
-        duration: 0,
-        style: {
-          marginTop: 300,
+      setMessages((prev) => [
+        ...(prev || []),
+        {
+          id: Date.now() + "",
+          role: "user",
+          content: value.trim(),
+          status: "done",
         },
-      });
+      ]);
+
+      const query = `设计一个数据模型内容,需求如下:${value.trim()}`;
+      onRequest(query, value.trim());
     }
   };
 
   const onStop = () => {
     cancel();
-    msgContent.current = "";
-    messageApi.open({
-      key: messageKey,
-      type: "error",
-      content: "AI创作已取消",
-      duration: 2,
-      style: {
-        marginTop: 300,
-      },
-    });
   };
 
   const handlePromptClick = (item: any) => {
@@ -200,7 +201,11 @@ export default (props: AICteatorProps) => {
     () =>
       props.position
         ? { position: "absolute", ...props.position }
-        : { position: "absolute", top: 114, right: 18 },
+        : {
+            position: "absolute",
+            top: 20,
+            right: 18,
+          },
     [props.position]
   );
 
@@ -230,8 +235,12 @@ export default (props: AICteatorProps) => {
           content: {
             backgroundImage:
               "linear-gradient(137deg, #e5f4ff 0%, #efe7ff 100%)",
+            height: "88vh",
           },
           header: { background: "transparent" },
+          body: {
+            height: "calc(100% - 32px)",
+          },
         }}
         footer={null}
         destroyOnClose
@@ -248,30 +257,35 @@ export default (props: AICteatorProps) => {
           </Draggable>
         )}
       >
-        <div className="my-10">
-          <Welcome
-            variant="borderless"
-            icon={<img src={aiLogo} className="rounded-lg" alt="AI Logo" />}
-            title="你好,我是数据模型AI助手"
-            description="你需要创建什么的内容,我可以帮你快速生成~"
-          />
-        </div>
+        <div className="h-full flex flex-col overflow-hidden">
+          <div className="flex-1">
+            <div className="my-10">
+              <Welcome
+                variant="borderless"
+                icon={<img src={aiLogo} className="rounded-lg" alt="AI Logo" />}
+                title="你好,我是数据模型AI助手"
+                description="你需要创建什么的内容,我可以帮你快速生成~"
+              />
+            </div>
 
-        <Prompts
-          className="mb-10"
-          items={items}
-          vertical
-          onItemClick={handlePromptClick}
-        />
+            <Prompts
+              className="mb-10"
+              items={items}
+              vertical
+              onItemClick={handlePromptClick}
+            />
 
-        <Sender
-          placeholder="如:创建一个用户表"
-          loading={loading}
-          value={input}
-          onChange={setInput}
-          onSubmit={onSubmit}
-          onCancel={onStop}
-        />
+            <Bubble.List autoScroll roles={roles} items={messages} />
+          </div>
+          <Sender
+            placeholder="如:创建一个用户表"
+            loading={loading}
+            value={input}
+            onChange={setInput}
+            onSubmit={onSubmit}
+            onCancel={onStop}
+          />
+        </div>
       </Modal>
     </>
   );

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

@@ -334,21 +334,21 @@ export default function Menu() {
       type: "group",
       children: [
         { key: "1-1", label: "修改记录" },
-        {
-          key: "1-2",
-          label: (
-            <span className="flex items-center justify-between">
-              <span>自动保存</span>
-              <Switch
-                size="small"
-                checked={project.setting.autoUpdate}
-                onChange={(checked) =>
-                  handleChangeSetting("autoUpdate", checked)
-                }
-              />
-            </span>
-          ),
-        },
+        // {
+        //   key: "1-2",
+        //   label: (
+        //     <span className="flex items-center justify-between">
+        //       <span>自动保存</span>
+        //       <Switch
+        //         size="small"
+        //         checked={project.setting.autoUpdate}
+        //         onChange={(checked) =>
+        //           handleChangeSetting("autoUpdate", checked)
+        //         }
+        //       />
+        //     </span>
+        //   ),
+        // },
         // {
         //   key: "1-3",
         //   label: (

+ 1 - 1
apps/er-designer/src/pages/home/All.tsx

@@ -298,7 +298,7 @@ export default function All({
         ...moveSource.current.data,
         directory: targetFolder,
       };
-      SaveDataModel(newData).then((res) => {
+      SaveDataModel({ erDataModel: newData }).then((res) => {
         setDataSource(
           dataSource.map((item) => {
             return item.id === newData.id ? newData : item;

+ 4 - 2
apps/er-designer/src/pages/home/ProjectCard.tsx

@@ -76,8 +76,10 @@ export default function ProjectCard({
                 ),
                 onOk: async () => {
                   await SaveDataModel({
-                    ...record,
-                    name,
+                    erDataModel: {
+                      ...record,
+                      name,
+                    }
                   });
                   message.success("更新成功");
                   onFresh();