Kaynağa Gözat

perf: 完善系统设计,数据模型ai历史对话修改,删除功能

liaojiaxing 6 gün önce
ebeveyn
işleme
f69dfb7433

+ 169 - 31
apps/designer/src/components/ai/AIChat.tsx

@@ -3,13 +3,37 @@ import {
   PlusOutlined,
   CloseOutlined,
   FieldTimeOutlined,
+  RedoOutlined,
+  EditOutlined,
+  DeleteOutlined,
 } from "@ant-design/icons";
-import { Button, Tooltip, Dropdown, Typography, Space, Spin } from "antd";
+import {
+  Button,
+  Tooltip,
+  Typography,
+  Space,
+  Spin,
+  Drawer,
+  Divider,
+  Modal,
+  Input,
+  message,
+} from "antd";
 import { GetProp } from "antd";
-import { Bubble, Sender, Suggestion, Prompts } from "@ant-design/x";
+import {
+  Bubble,
+  Sender,
+  Suggestion,
+  Prompts,
+  Conversations,
+} from "@ant-design/x";
 import MarkdownViewer from "./MarkdownViewer";
 import { useChat } from "@/hooks/useChat";
 import RenderGraph from "./RenderGraph";
+import InfiniteScroll from "react-infinite-scroll-component";
+import type { ConversationsProps } from "@ant-design/x";
+import { ChangeSessionName, DeleteSession } from "@/api/ai";
+import { Conversation } from "@ant-design/x/lib/conversations";
 
 export default function AIChat(props: {
   onClose?: () => void;
@@ -21,6 +45,7 @@ export default function AIChat(props: {
   const [inputVal, setInputVal] = useState("");
   const contentRef = useRef<HTMLDivElement>(null);
   const [contentHeight, setContentHeight] = useState(0);
+  const [openHistory, setOpenHistory] = useState(false);
 
   const setHeight = () => {
     setContentHeight(contentRef.current?.clientHeight || 0);
@@ -42,9 +67,15 @@ export default function AIChat(props: {
     cancel,
     messages,
     setMessages,
-    addConversation,
     conversationList,
     changeConversation,
+    loadingSession,
+    addConversation,
+    setConversationList,
+    loadMoreConversation,
+    hasMoreConversation,
+    refreshConversationList,
+    activeConversation,
   } = useChat({
     app_name: "system_design",
     onSuccess: (msg) => {
@@ -160,6 +191,91 @@ export default function AIChat(props: {
     },
   };
 
+  const handleChangeConversationName = (conversation: Conversation) => {
+    let new_name: string;
+    Modal.info({
+      title: "修改对话名称",
+      okText: "提交",
+      closable: true,
+      content: (
+        <div>
+          <Input
+            type="text"
+            defaultValue={conversation.label + ""}
+            onChange={(e) => {
+              new_name = e.target.value;
+            }}
+          />
+        </div>
+      ),
+      onOk: () => {
+        return ChangeSessionName({
+          app_name: "system_design",
+          session_id: conversation.key,
+          new_name,
+        }).then(() => {
+          message.success("修改成功");
+          setConversationList((list) =>
+            list?.map((item) =>
+              item.key === conversation.key
+                ? { ...item, label: new_name }
+                : item
+            )
+          );
+        });
+      },
+    });
+  };
+
+  const handleDeleteConversation = (conversation: Conversation) => {
+    Modal.confirm({
+      title: "删除对话",
+      content: "是否删除对话?",
+      okText: "删除",
+      cancelText: "取消",
+      onOk: () => {
+        return DeleteSession({
+          app_name: "system_design",
+          session_id: conversation.key,
+        }).then(() => {
+          message.success("删除成功");
+          refreshConversationList();
+
+          addConversation();
+        });
+      },
+    });
+  };
+
+  const menuConfig: ConversationsProps["menu"] = (conversation) => {
+    if (conversation.key === "1") return undefined;
+    return {
+      items: [
+        {
+          label: "修改对话名称",
+          key: "edit",
+          icon: <EditOutlined />,
+        },
+        {
+          label: "删除对话",
+          key: "del",
+          icon: <DeleteOutlined />,
+          danger: true,
+        },
+      ],
+      onClick: (menuInfo) => {
+        // 修改对话名称
+        if (menuInfo.key === "edit") {
+          handleChangeConversationName(conversation);
+        }
+        // 删除对话
+        if (menuInfo.key === "del") {
+          handleDeleteConversation(conversation);
+        }
+      },
+    };
+  };
+
   // 处理提交
   const onSubmit = (val: string) => {
     setInputVal("");
@@ -198,19 +314,6 @@ export default function AIChat(props: {
     cancel();
   };
 
-  const historyItems = useMemo(() => {
-    return (conversationList || []).map((item) => {
-      return {
-        key: item.id,
-        label: item.label,
-        onClick: () => {
-          console.log("changeConversation", item);
-          changeConversation(item.sessionId);
-        },
-      };
-    });
-  }, [conversationList]);
-
   const bubble = useMemo(() => {
     return (
       <Bubble.List
@@ -245,21 +348,14 @@ export default function AIChat(props: {
               onClick={onNewChat}
             ></Button>
           </Tooltip>
-          <Dropdown
-            menu={{ items: historyItems }}
-            trigger={["click"]}
-            placement="bottomLeft"
-            className="max-h-200px"
-            arrow
-          >
-            <Tooltip title="历史记录">
-              <Button
-                type="text"
-                size="small"
-                icon={<FieldTimeOutlined />}
-              ></Button>
-            </Tooltip>
-          </Dropdown>
+          <Tooltip title="历史记录">
+            <Button
+              type="text"
+              size="small"
+              icon={<FieldTimeOutlined />}
+              onClick={() => setOpenHistory(true)}
+            ></Button>
+          </Tooltip>
           <Button
             type="text"
             size="small"
@@ -299,6 +395,48 @@ export default function AIChat(props: {
           onCancel={onStop}
         />
       </div>
+      <Drawer
+        title="历史记录"
+        open={openHistory}
+        onClose={() => setOpenHistory(false)}
+        placement="right"
+        getContainer={false}
+        width={280}
+        styles={{ body: { padding: '0 12px' } }}
+      >
+        <div id="scrollableDiv" className="w-full h-full overflow-auto">
+          <InfiniteScroll
+            dataLength={conversationList?.length || 0}
+            next={loadMoreConversation}
+            hasMore={hasMoreConversation}
+            loader={
+              <div style={{ textAlign: "center" }}>
+                <Spin indicator={<RedoOutlined spin />} size="small" />
+              </div>
+            }
+            endMessage={
+              <Divider plain>
+                <span className="text-12px text-text-secondary">
+                  无更多会话~
+                </span>
+              </Divider>
+            }
+            scrollableTarget="scrollableDiv"
+            style={{ overflow: "hidden" }}
+          >
+            <Spin spinning={loadingSession} className="flex-col">
+              <Conversations
+                groupable
+                style={{ width: 200, padding: 0, margin: 0 }}
+                activeKey={activeConversation}
+                onActiveChange={changeConversation}
+                menu={menuConfig}
+                items={conversationList}
+              />
+            </Spin>
+          </InfiniteScroll>
+        </div>
+      </Drawer>
     </div>
   );
 }

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

@@ -2,7 +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 { getDateGroupString } from "@repo/utils";
 
 import type { ConversationsProps } from "@ant-design/x";
 import type { ReactNode } from "react";

+ 13 - 0
apps/designer/src/utils/contentMenu.tsx

@@ -153,6 +153,19 @@ const commonMenuData: MenuItem[] = [
     handler: menuHander.defaultStyle,
   },
   { type: "divider" },
+  {
+    key: "grammarFix",
+    label: "语法修复",
+    icon: "icon-AI",
+    handler: menuHander.defaultStyle,
+  },
+  {
+    key: "translation",
+    label: "翻译",
+    icon: "icon-AI",
+    handler: menuHander.defaultStyle,
+  },
+  { type: "divider" },
   {
     key: "top",
     label: "置于顶层",

+ 1 - 0
apps/designer/src/utils/hander.tsx

@@ -123,6 +123,7 @@ export const menuHander = {
   },
   // 替换图形
   replace(tool: ContextMenuTool, e: MouseEvent) {},
+  
 };
 
 export const selectAll = (graph: Graph) => {

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

@@ -202,35 +202,4 @@ export const handleParseFowlAIData = ({
     console.error(error);
     onError?.(new Error("AI创作失败"));
   }
-};
-
-/**
- * 获取日期格式化分组
- * @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 "更久~";
-  }
-}
+};

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

@@ -268,6 +268,7 @@ export default forwardRef(function SyncModal(
               张表已存在,可对比调整差异后开始操作。
             </>
           ) : null}
+          {tabActiveKey === '1' && importTables ? <span>引入<span className="color-green">{importTables.length}</span>张表。</span> : ''}
         </div>
 
         <Collapse

+ 142 - 25
apps/er-designer/src/components/ai/AICreator.tsx

@@ -7,20 +7,23 @@ import {
   Spin,
   Dropdown,
   Button,
-  Alert,
   Drawer,
-  List,
+  Divider,
+  Input,
 } from "antd";
 import type { DraggableData, DraggableEvent } from "react-draggable";
 import Draggable from "react-draggable";
-import { Sender, Welcome, Prompts, Bubble } from "@ant-design/x";
+import { Sender, Welcome, Prompts, Bubble, Conversations } from "@ant-design/x";
 import { PromptsProps } from "@ant-design/x";
 import aiLogo from "@/assets/icon-ai-3.png";
 import {
   CoffeeOutlined,
+  DeleteOutlined,
+  EditOutlined,
   FireOutlined,
   HistoryOutlined,
   PlusOutlined,
+  RedoOutlined,
   SmileOutlined,
   UserOutlined,
 } from "@ant-design/icons";
@@ -29,6 +32,10 @@ import type { GetProp } from "antd/lib";
 import MarkdownViewer from "@/components/ai/MarkdownViewer";
 // import AITable from "./AITable";
 import { useModel } from "umi";
+import InfiniteScroll from "react-infinite-scroll-component";
+import type { ConversationsProps } from "@ant-design/x";
+import { ChangeSessionName, DeleteSession } from "@/api/ai";
+import { Conversation } from "@ant-design/x/lib/conversations";
 
 type AICteatorProps = {
   trigger: JSX.Element;
@@ -111,12 +118,15 @@ export default (props: AICteatorProps) => {
                 };
                 return (
                   <Button
-                    type="text"
+                    variant="text"
+                    size="small"
+                    color="primary"
                     icon={<PlusOutlined />}
                     onClick={handleAdd}
                   >
-                    添加到画布
+                    加入画布
                   </Button>
+                          
                 );
               }}
             />
@@ -145,11 +155,17 @@ export default (props: AICteatorProps) => {
     onRequest,
     cancel,
     messages,
+    loadingMessages,
     setMessages,
-    addConversation,
     conversationList,
     changeConversation,
-    loadingMessages
+    loadingSession,
+    addConversation,
+    setConversationList,
+    loadMoreConversation,
+    hasMoreConversation,
+    refreshConversationList,
+    activeConversation,
   } = useChat({
     app_name: "data_model",
     onSuccess: (msg) => {
@@ -188,6 +204,91 @@ export default (props: AICteatorProps) => {
     },
   });
 
+  const handleChangeConversationName = (conversation: Conversation) => {
+    let new_name: string;
+    Modal.info({
+      title: "修改对话名称",
+      okText: "提交",
+      closable: true,
+      content: (
+        <div>
+          <Input
+            type="text"
+            defaultValue={conversation.label + ""}
+            onChange={(e) => {
+              new_name = e.target.value;
+            }}
+          />
+        </div>
+      ),
+      onOk: () => {
+        return ChangeSessionName({
+          app_name: "data_model",
+          session_id: conversation.key,
+          new_name,
+        }).then(() => {
+          message.success("修改成功");
+          setConversationList((list) =>
+            list?.map((item) =>
+              item.key === conversation.key
+                ? { ...item, label: new_name }
+                : item
+            )
+          );
+        });
+      },
+    });
+  };
+
+  const handleDeleteConversation = (conversation: Conversation) => {
+    Modal.confirm({
+      title: "删除对话",
+      content: "是否删除对话?",
+      okText: "删除",
+      cancelText: "取消",
+      onOk: () => {
+        return DeleteSession({
+          app_name: "data_model",
+          session_id: conversation.key,
+        }).then(() => {
+          message.success("删除成功");
+          refreshConversationList();
+
+          addConversation();
+        });
+      },
+    });
+  };
+
+  const menuConfig: ConversationsProps["menu"] = (conversation) => {
+    if (conversation.key === "1") return undefined;
+    return {
+      items: [
+        {
+          label: "修改对话名称",
+          key: "edit",
+          icon: <EditOutlined />,
+        },
+        {
+          label: "删除对话",
+          key: "del",
+          icon: <DeleteOutlined />,
+          danger: true,
+        },
+      ],
+      onClick: (menuInfo) => {
+        // 修改对话名称
+        if (menuInfo.key === "edit") {
+          handleChangeConversationName(conversation);
+        }
+        // 删除对话
+        if (menuInfo.key === "del") {
+          handleDeleteConversation(conversation);
+        }
+      },
+    };
+  };
+
   const triggerDom = React.cloneElement(props.trigger, {
     ...props.trigger.props,
     onClick: () => {
@@ -252,12 +353,6 @@ export default (props: AICteatorProps) => {
     onSubmit(msg);
   };
 
-  // 点击历史记录
-  const handleOpenHistory = (item: any) => {
-    setOpenHistory(false);
-    changeConversation(item.sessionId);
-  };
-
   // 获取样式
   const getStyle: React.CSSProperties = useMemo(
     () =>
@@ -401,19 +496,41 @@ export default (props: AICteatorProps) => {
           onClose={() => setOpenHistory(false)}
           placement="right"
           getContainer={false}
+          width={280}
+          styles={{ body: { padding: '0 12px' } }}
         >
-          <List
-            dataSource={conversationList}
-            renderItem={(item) => (
-              <List.Item
-                className="px-12px hover:bg-gray-50  cursor-pointer"
-                key={item.id}
-                onClick={() => handleOpenHistory(item)}
-              >
-                {item.label}
-              </List.Item>
-            )}
-          />
+          <div id="scrollableDiv" className="w-full h-full overflow-auto">
+            <InfiniteScroll
+              dataLength={conversationList?.length || 0}
+              next={loadMoreConversation}
+              hasMore={hasMoreConversation}
+              loader={
+                <div style={{ textAlign: "center" }}>
+                  <Spin indicator={<RedoOutlined spin />} size="small" />
+                </div>
+              }
+              endMessage={
+                <Divider plain>
+                  <span className="text-12px text-text-secondary">
+                    无更多会话~
+                  </span>
+                </Divider>
+              }
+              scrollableTarget="scrollableDiv"
+              style={{ overflow: "hidden" }}
+            >
+              <Spin spinning={loadingSession} className="flex-col">
+                <Conversations
+                  groupable
+                  style={{ width: 200, padding: 0, margin: 0 }}
+                  activeKey={activeConversation}
+                  onActiveChange={changeConversation}
+                  menu={menuConfig}
+                  items={conversationList}
+                />
+              </Spin>
+            </InfiniteScroll>
+          </div>
         </Drawer>
       </Modal>
     </>

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

@@ -2,7 +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 { getDateGroupString } from "@repo/utils";
 
 import type { ConversationsProps } from "@ant-design/x";
 import type { ReactNode } from "react";
@@ -113,7 +113,7 @@ export function useChat({ app_name, onSuccess, onUpdate, onError }: ChatProps) {
               ...item,
               key: item.sessionId,
               label: item.name,
-              // group: getDateGroupString(item.updateTime)
+              group: getDateGroupString(item.updateTime)
             })),
           ]);
         } else {
@@ -123,7 +123,7 @@ export function useChat({ app_name, onSuccess, onUpdate, onError }: ChatProps) {
               ...item,
               key: item.sessionId,
               label: item.name,
-              // group: getDateGroupString(item.updateTime)
+              group: getDateGroupString(item.updateTime)
             })),
           ]);
         }

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

@@ -1067,7 +1067,6 @@ export default function erModel() {
         };
       });
 
-      console.log("newTableList", generateTables, generateRelations);
       setProject((project) => {
         return {
           ...project,
@@ -1076,6 +1075,11 @@ export default function erModel() {
         };
       });
     }
+    setTimeout(() => {
+      const table1 = graphRef.current?.getCellById(data.tables[0].table.id);
+      table1 && graphRef.current?.centerCell(table1);
+    }, 300);
+    
     // await BatchAddAICreateResult({
     //   ...data,
     //   dataModelId: modelId || project.id

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

@@ -233,6 +233,7 @@ export default function TableItem({
             layout="horizontal"
             labelCol={{ span: 8 }}
             initialValues={table}
+            size="small"
           >
             <Form.Item
               label="类型"

+ 2 - 1
package.json

@@ -19,7 +19,7 @@
   },
   "dependencies": {
     "@ant-design/icons": "^5.6.1",
-    "@ant-design/pro-components": "^2.8.6",
+    "@ant-design/pro-components": "^2.8.7",
     "@ant-design/x": "^1.1.0",
     "@antv/hierarchy": "^0.6.14",
     "@antv/x6": "^2.18.1",
@@ -68,6 +68,7 @@
     "lodash-es": "^4.17.21",
     "mermaid": "^11.4.1",
     "react-draggable": "^4.4.6",
+    "react-infinite-scroll-component": "^6.1.0",
     "react-markdown": "^9.0.3",
     "react-syntax-highlighter": "^15.6.1",
     "rehype-raw": "^7.0.0",

+ 31 - 0
packages/utils/src/index.ts

@@ -79,3 +79,34 @@ export function uuid() {
     return v.toString(16);
   });
 }
+
+/**
+ * 获取日期格式化分组
+ * @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 "更久~";
+  }
+}

Dosya farkı çok büyük olduğundan ihmal edildi
+ 301 - 262
pnpm-lock.yaml