瀏覽代碼

feat: 添加ai创建页面

liaojiaxing 3 周之前
父節點
當前提交
391dddebdd

+ 100 - 0
apps/ai-easy-builder/src/components/ai/MarkdownViewer.tsx

@@ -0,0 +1,100 @@
+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 { useMemo, useState } from "react";
+import "./index.less";
+
+interface MarkdownViewerProps {
+  content: string;
+}
+
+const CodeHeader: React.FC<{
+  language: string;
+  onCopy: () => void;
+  copied: boolean;
+}> = ({ language, onCopy, copied }) => (
+  <div
+    className="flex justify-between items-center text-white"
+    style={{ background: "#afadad", padding: "3px 4px" }}
+  >
+    <div className="flex items-center">
+      <span className="text-xs text-#fff">{language}</span>
+    </div>
+
+    <div>
+      <span className="cursor-pointer text-xs" onClick={onCopy}>
+        {copied ? "已复制!" : "复制代码"}
+      </span>
+    </div>
+  </div>
+);
+
+const MarkdownViewer: React.FC<MarkdownViewerProps> = ({ content }) => {
+  const [copiedIndex, setCopiedIndex] = useState<number | null>(null);
+  const filterContent = useMemo(() => {
+    return content
+      .replaceAll("<think>", '<div class="think">')
+      .replaceAll("</think>", "</div>");
+  }, [content]);
+
+  const handleCopy = (code: string, index: number) => {
+    navigator.clipboard.writeText(code);
+    setCopiedIndex(index);
+    setTimeout(() => setCopiedIndex(null), 2000);
+  };
+
+  return (
+    <div className="markdown-body">
+      <ReactMarkdown
+        remarkPlugins={[remarkGfm]}
+        rehypePlugins={[rehypeRaw]}
+        components={{
+          code({ node, className, children, ...props }) {
+            const match = /language-(\w+)/.exec(className || "");
+            const code = String(children).replace(/\n$/, "");
+            const language = match ? match[1] : "";
+            if (match) {
+              return (
+                <div className="rounded-md overflow-hidden mb-4">
+                  <CodeHeader
+                    language={language}
+                    onCopy={() =>
+                      handleCopy(code, node?.position?.start.line ?? 0)
+                    }
+                    copied={copiedIndex === node?.position?.start.line}
+                  />
+                  <div className="max-w-full overflow-x-auto">
+                    <SyntaxHighlighter
+                      style={vscDarkPlus}
+                      language={language}
+                      PreTag="div"
+                      {...props}
+                      customStyle={{
+                        margin: 0,
+                        borderTopLeftRadius: 0,
+                        borderTopRightRadius: 0,
+                      }}
+                    >
+                      {code}
+                    </SyntaxHighlighter>
+                  </div>
+                </div>
+              );
+            }
+            return (
+              <code className={className} {...props}>
+                {children}
+              </code>
+            );
+          },
+        }}
+      >
+        {filterContent}
+      </ReactMarkdown>
+    </div>
+  );
+};
+
+export default MarkdownViewer;

+ 46 - 0
apps/ai-easy-builder/src/components/ai/index.less

@@ -0,0 +1,46 @@
+.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 4px #ddd;
+  }
+
+  img, video {
+    max-width: 100%;
+  }
+}

+ 99 - 32
apps/ai-easy-builder/src/hooks/useChat.ts

@@ -42,8 +42,6 @@ type ChatProps = {
   app_name: string;
   // 会话id 后续会话带入
   conversation_id?: string;
-  // 开始流式传输内容
-  onStart?: (data?: ResponseMessageItem) => void;
   // 成功获取会话内容
   onSuccess?: (data: ResponseMessageItem) => void;
   // 更新流式消息内容
@@ -52,13 +50,45 @@ type ChatProps = {
   onError?: (error: Error) => void;
 };
 
+/**
+ * 获取日期格式化分组
+ * @param dateVal 日期
+ * @returns 
+ */
+export function getDateGroupString(dateVal: Date | string): string {
+  const normalizeDate = (date: Date | string): Date => {
+    const d = new Date(date);
+    d.setHours(0, 0, 0, 0);
+    return d;
+  };
+
+  const now = normalizeDate(new Date()); // 当前日期归一化
+
+  const itemDate = normalizeDate(new Date(dateVal));
+  const diffTime = now.getTime() - itemDate.getTime();
+  const diffDays = Math.floor(diffTime / (1000 * 3600 * 24));
+
+  if (diffDays === 0) {
+    return "今日";
+  } else if (diffDays === 1) {
+    return "昨日";
+  } else if (diffDays <= 6) {
+    return "7日内";
+  } else if (diffDays <= 29) {
+    return "30日内";
+  } else {
+    return "更久~";
+  }
+}
+
 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 +123,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,27 +222,22 @@ 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 }) => {
-      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://design.shalu.com/api/ai/chat-message",
           {
             method: "POST",
             body: JSON.stringify(message),
             headers: {
-              Authorization: token,
+              Authorization: localStorage.getItem("token_a") || "",
               "Content-Type": "application/json",
             },
             signal,
@@ -196,13 +255,13 @@ 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 || '请求失败'));
               }
             }
           }
@@ -231,12 +290,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) => {
+    activeConversation === '1' && setConversationList((list) => {
       return list?.map((item) => {
         return {
           ...item,
@@ -248,11 +303,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" ? chat_query : undefined,
         conversation_id:
           activeConversation === "1" ? undefined : activeConversation,
       },
-      callbacks ?? {
+      {
         onSuccess: (data) => {
           onSuccess?.(data);
         },
@@ -285,6 +340,7 @@ export function useChat({ app_name, onStart, onSuccess, onUpdate, onError }: Cha
    */
   const cancel = () => {
     abortController.current?.abort();
+    setLoading(false);
   };
 
   /**
@@ -305,6 +361,14 @@ export function useChat({ app_name, onStart, onSuccess, onUpdate, onError }: Cha
     }
   };
 
+  /**
+   * 更新会话列表
+   */
+  const refreshConversationList = () => {
+    setPageIndex(1);
+    getSession(1);
+  };
+
   return {
     agent,
     loading,
@@ -320,5 +384,8 @@ export function useChat({ app_name, onStart, onSuccess, onUpdate, onError }: Cha
     onRequest,
     addConversation,
     changeConversation,
+    loadMoreConversation,
+    hasMoreConversation,
+    refreshConversationList
   };
 }

+ 395 - 7
apps/ai-easy-builder/src/pages/home/index.tsx

@@ -1,9 +1,397 @@
-import React from 'react'
+import {
+  Bubble,
+  Prompts,
+  Sender,
+  Suggestion,
+  XProvider,
+  Welcome,
+  Attachments,
+  AttachmentsProps,
+} from "@ant-design/x";
+import { useChat } from "@/hooks/useChat";
+
+import {
+  Card,
+  Flex,
+  message,
+  Button,
+  Space,
+  Spin,
+  Typography,
+} from "antd";
+import { useEffect, useRef, useState } from "react";
+import {
+  BulbOutlined,
+  SmileOutlined,
+  UserOutlined,
+  CloudUploadOutlined,
+  LinkOutlined,
+  CopyOutlined,
+  RedoOutlined,
+  ReadOutlined,
+} from "@ant-design/icons";
+import type { GetProp, GetRef } from "antd";
+import MarkdownViewer from "@/components/ai/MarkdownViewer";
+
+// bubbles角色配置
+const roles: GetProp<typeof Bubble.List, "roles"> = {
+  assistant: {
+    placement: "start",
+    avatar: {
+      icon: <i className="iconfont icon-AI1" />,
+      style: { background: "#3450ec" },
+    },
+    // typing: { step: 5, interval: 20 },
+    loadingRender: () => (
+      <Space>
+        <Spin size="small" />
+        思考中...
+      </Space>
+    ),
+    messageRender: (content) => {
+      return typeof content === "string" ? (
+        <Typography className={content?.includes("```") ? "w-full" : ""}>
+          <MarkdownViewer content={content} />
+        </Typography>
+      ) : (
+        content
+      );
+    },
+    header: "易码工坊AI助手",
+  },
+  user: {
+    placement: "end",
+    avatar: { icon: <UserOutlined />, style: { background: "#87d068" } },
+    messageRender: (content) => {
+      return <div style={{ whiteSpace: "pre-wrap" }}>{content}</div>;
+    },
+  },
+};
+
+export default () => {
+  const [senderVal, setSenderVal] = useState("");
+  const {
+    messages,
+    setMessages,
+    activeConversation,
+    changeConversation,
+    conversationList,
+    onRequest,
+    cancel,
+    loading,
+    loadingSession,
+    addConversation,
+    setConversationList
+  } = useChat({
+    app_name: "normal_ask",
+    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 = (
+          <BubbleFooter
+            content={arr[messages.length - 1].content as string}
+            query={query}
+          />
+        );
+        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 = "error";
+        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 [openAttachment, setOpenAttachment] = useState(false);
+  const attachmentsRef = useRef<GetRef<typeof Attachments>>(null);
+  const senderRef = useRef<GetRef<typeof Sender>>(null);
+  const [attachmentItems, setAttachmentItems] = useState<
+    GetProp<AttachmentsProps, "items">
+  >([]);
+  const contentRef = useRef<HTMLDivElement>(null);
+  const [contentHeight, setContentHeight] = useState(0);
+
+  const setHeight = () => {
+    setContentHeight(contentRef.current?.clientHeight || 0);
+  };
+  useEffect(() => {
+    setHeight();
+    const resizeObserver = new ResizeObserver(() => {
+      setHeight();
+    });
+    resizeObserver.observe(contentRef.current!);
+    return () => {
+      resizeObserver.disconnect();
+    };
+  }, []);
+
+  // 附件组件
+  const senderHeader = (
+    <Sender.Header
+      title="附件"
+      styles={{
+        content: {
+          padding: 0,
+        },
+      }}
+      open={openAttachment}
+      onOpenChange={setOpenAttachment}
+      forceRender
+    >
+      <Attachments
+        ref={attachmentsRef}
+        beforeUpload={() => false}
+        items={attachmentItems}
+        onChange={({ fileList }) => setAttachmentItems(fileList)}
+        placeholder={(type) =>
+          type === "drop"
+            ? {
+                title: "拖拽文件到这里",
+              }
+            : {
+                icon: <CloudUploadOutlined />,
+                title: "文件列表",
+                description: "点击或者拖拽文件到这里上传",
+              }
+        }
+        getDropContainer={() => senderRef.current?.nativeElement}
+      />
+    </Sender.Header>
+  );
+
+  // 底部组件
+  const BubbleFooter = (props: { content: string; query: string }) => {
+    const handleCopy = () => {
+      navigator.clipboard.writeText(props.content);
+      message.success("复制成功");
+    };
+
+    const handleRedo = () => {
+      submitMessage(props.query);
+    };
+    return (
+      <Space>
+        <Button
+          type="text"
+          size="small"
+          icon={<CopyOutlined />}
+          onClick={handleCopy}
+        >
+          复制
+        </Button>
+        <Button
+          type="text"
+          size="small"
+          icon={<RedoOutlined />}
+          onClick={handleRedo}
+        >
+          重新生成
+        </Button>
+      </Space>
+    );
+  };
+
+  // 提交消息
+  const submitMessage = (msg: string) => {
+    setSenderVal("");
+
+    setMessages((arr) => {
+      const index = arr.length;
+      return [
+        ...arr,
+        { id: index + "", content: msg, status: "done", role: "user" },
+        {
+          id: index + 1 + "",
+          content: "",
+          status: "loading",
+          role: "assistant",
+          loading: true,
+        },
+      ];
+    });
+    onRequest(msg);
+  };
+
+  // 点击提示词
+  const handlePromptItem = (item: any) => {
+    const msg = item.data.description || item.data.label;
+    const index = messages.length;
+    setMessages([
+      ...messages,
+      { id: index + "", content: msg, status: "done", role: "user" },
+      {
+        id: index + 1 + "",
+        content: "",
+        status: "loading",
+        role: "assistant",
+        loading: true,
+      },
+    ]);
+    onRequest(msg);
+  };
+
+  // 停止对话
+  const handleStop = () => {
+    cancel();
+    setMessages((messages) => {
+      const arr = [...messages];
+      arr[messages.length - 1].status = "stop";
+      arr[messages.length - 1].loading = false;
+      arr[messages.length - 1].footer = (
+        <div>
+          <div className="text-12px text-text-secondary pl-12px">
+            (已停止思考)
+          </div>
+          <BubbleFooter
+            content={arr[messages.length - 1].content as string}
+            query={arr[messages.length - 2].content as string}
+          />
+        </div>
+      );
+      return arr;
+    });
+  };
 
-export default function index() {
   return (
-    <div>
-      
-    </div>
-  )
-}
+    <>
+      <Card
+        className="w-full h-full"
+        styles={{
+          body: {
+            height: "calc(100vh - 48px)",
+          },
+        }}
+      >
+        <XProvider direction="ltr">
+          <Flex style={{ height: "100%" }} gap={12}>
+            <Flex
+              vertical
+              style={{ flex: 1, overflow: "hidden", height: "100%" }}
+              gap={8}
+            >
+              <div
+                className="flex-1 overflow-hidden"
+                ref={contentRef}
+                style={{ height: contentHeight }}
+              >
+                {!messages.length ? (
+                  <>
+                    <div className="mt-20 mb-10">
+                      <Welcome
+                        icon={
+                          <img
+                            src={''}
+                            className="rounded-8px"
+                          />
+                        }
+                        title={`你好!我是AI易搭`}
+                        description={`我可以帮你一句话生成应用`}
+                      />
+                    </div>
+
+                    <Prompts
+                      title={
+                        "✨ 你可以这样问我:"
+                      }
+                      items={[]}
+                      wrap
+                      onItemClick={handlePromptItem}
+                    />
+                  </>
+                ) : (
+                  <Bubble.List
+                    style={{ maxHeight: contentHeight, padding: '0 20px' }}
+                    autoScroll
+                    roles={roles}
+                    items={messages}
+                  />
+                )}
+              </div>
+              <Prompts
+                items={[
+                  {
+                    key: "1",
+                    icon: <BulbOutlined style={{ color: "#FFD700" }} />,
+                    label: "写需求",
+                  },
+                  {
+                    key: "2",
+                    icon: <SmileOutlined style={{ color: "#52C41A" }} />,
+                    label: "生成代码",
+                  },
+                  {
+                    key: "3",
+                    icon: <ReadOutlined style={{ color: "#52C41A" }} />,
+                    label: "问题解答",
+                  },
+                ]}
+                onItemClick={handlePromptItem}
+              />
+
+              <Suggestion
+                items={[{ label: "写一个应用介绍", value: "report" }]}
+                onSelect={submitMessage}
+              >
+                {({ onTrigger, onKeyDown }) => {
+                  return (
+                    <Sender
+                      ref={senderRef}
+                      header={senderHeader}
+                      loading={loading}
+                      prefix={
+                        <Button
+                          type="text"
+                          icon={<LinkOutlined />}
+                          onClick={() => {
+                            setOpenAttachment(!openAttachment);
+                          }}
+                        />
+                      }
+                      value={senderVal}
+                      onPasteFile={(file) => {
+                        attachmentsRef.current?.upload(file);
+                        setOpenAttachment(true);
+                      }}
+                      onChange={(nextVal) => {
+                        if (nextVal === "/") {
+                          onTrigger();
+                        } else if (!nextVal) {
+                          onTrigger(false);
+                        }
+                        setSenderVal(nextVal);
+                      }}
+                      onKeyDown={onKeyDown}
+                      placeholder="输入/获取快捷提示"
+                      onSubmit={submitMessage}
+                      onCancel={handleStop}
+                    />
+                  );
+                }}
+              </Suggestion>
+            </Flex>
+          </Flex>
+        </XProvider>
+      </Card>
+    </>
+  );
+};

+ 0 - 208
apps/ai-easy-builder/src/type.d.ts

@@ -1,208 +0,0 @@
-/**
- * 字段结构
- */
-export interface ColumnItem {
-  langName?: string;
-  schemaName?: string;
-  type: number;
-  maxLength?: number;
-  // 必填
-  isRequired?: boolean;
-  // 唯一
-  isUnique?: boolean;
-  displayOrder?: number;
-  // 表id
-  tableId?: string;
-  // 描述
-  memo?: string;
-  alignment?: string;
-  isDisplayEnable?: boolean;
-  isLinkEnable?: boolean;
-  isWhereEnable?: boolean;
-  isOrderByEnable?: boolean;
-  isGroupByEnable?: boolean;
-  isAggregateEnable?: boolean;
-  whereInputType?: string;
-  // 内容
-  whereInputContent?: string;
-  precision?: number;
-  scale?: number;
-  isPreDefined?: boolean;
-  // 默认值
-  defaultValue?: string;
-  alterId?: string;
-  temp_rename?: string;
-  id: string;
-  charset?: string;
-  orderChar?: string;
-  langNameList: Record<string, string>[];
-  // 字符集
-  chartset?: string;
-  cnName?: string;
-  enName?: string;
-}
-
-/**
- * 表结构
- */
-export interface ViewTable {
-  aliasName: string;
-  creationTime: string;
-  creatorUserId: string;
-  displayOrder: number;
-  id: string;
-  isDeleted: boolean;
-  langDescription?: string;
-  langName?: string;
-  parentBusinessTableId: string;
-  schemaName: string;
-  type: number;
-  updateTime: string;
-  langNameList?: Record<string, string>[];
-  langDescriptionList?: Record<string, string>[];
-  cnName?: string;
-  enName?: string;
-  enDescription?: string;
-  cnDescription?: string;
-}
-
-/**
- * 字段关系
- */
-export interface ColumnRelation {
-  id: string;
-  // 名称
-  name: string;
-  // 主键
-  primaryKey: string;
-  // 主键表
-  primaryTable: string;
-  // 外键
-  foreignKey: string;
-  // 外键表
-  foreignTable: string;
-  // 关系类型 1 一对一 2 一对多 3 多对一
-  relationType: 1 | 2 | 3;
-  // 连线样式
-  style: Record<string, any>;
-  dataModelId?: string;
-}
-
-/**
- * 待办
- */
-export interface TodoItem {
-  id: string;
-  name: string;
-  text: string;
-  isDone: boolean;
-  level: 0 | 1 | 2 | 3; // 0 默认 1 一般 2 紧急 3 最高
-  dataModelId?: string;
-}
-
-/**
- * 主题区域信息
- */
-export interface TopicAreaInfo {
-  // 主题区域id
-  id: string;
-  // 主题区域名称
-  name: string;
-  // 主题区域样式
-  style: Record<string, any>;
-  // 主题区域
-  isTopicArea: boolean;
-  dataModelId?: string;
-}
-
-/**
- * 注释信息
- */
-export interface RemarkInfo {
-  id: string;
-  // 名称
-  name: string;
-  // 内容
-  text: string;
-  // 样式
-  style: {
-    x: number;
-    y: number;
-    width: number;
-    height: number;
-    background: string;
-  };
-  isRemark: boolean;
-  dataModelId?: string;
-}
-
-/**
- * 表数据
- */
-export type TableItemType = {
-  table: ViewTable & {openSync: boolean; style: Record<string, any>, dataModelId: string},
-  tableColumnList: ColumnItem[],
-  isTable: boolean;
-};
-
-/**
- * 数据模型项目信息
- */
-export interface ProjectInfo {
-  id: string;
-  // 名称
-  name: string;
-  // 文件夹
-  directory: string;
-  // 封面
-  coverImage?: string;
-  // 模型类型
-  type: number; // 2 流程 3 业务表
-  // 创建时间
-  createTime?: string;
-  // 创建者
-  createdBy?: string;
-  createdByName?: string;
-  // 更新者
-  updateBy?: string;
-  updateByName?: string;
-  // 更新时间
-  updateTime?: string;
-  // 模型说明
-  description?: string;
-  // 是否模版
-  isTemplate: boolean;
-  // 所属行业
-  industry?: string;
-  // 发布状态
-  publishStatus: string;
-  // 数据表 openSync 开启同步
-  tables: TableItemType[];
-  // 关系
-  relations: ColumnRelation[];
-  // 主题区域
-  topicAreas: TopicAreaInfo[];
-  // 注释节点
-  remarkInfos: RemarkInfo[];
-  // 操作记录
-  // history: any[];
-  // 待办事项
-  todos: TodoItem[];
-  // 画布设置
-  setting: {
-    // 展示菜单栏
-    showMenu: boolean;
-    // 展示侧边栏
-    showSidebar: boolean;
-    // 展示字段详情
-    showColumnDetail: boolean;
-    // 展示网格
-    showGrid: boolean;
-    // 展示关系
-    showRelation: boolean;
-    // 自动更新
-    autoUpdate: boolean;
-    // 表格宽度
-    tableWidth: number;
-  }
-}