Sfoglia il codice sorgente

feat: 添加ai-server

liaojiaxing 2 mesi fa
parent
commit
c94d69dcc2

+ 10 - 0
apps/ai-server/app.js

@@ -0,0 +1,10 @@
+const express = require('express');
+require('dotenv').config();
+
+const port = 3000;
+
+const app = require('./src');
+
+app.listen(port, () => {
+  console.log(`Server is running on http://localhost:${port}`);
+});

+ 17 - 0
apps/ai-server/package.json

@@ -0,0 +1,17 @@
+{
+  "name": "ai-server",
+  "version": "1.0.0",
+  "description": "",
+  "main": "app.js",
+  "scripts": {
+    "start": "node app.js"
+  },
+  "keywords": [],
+  "author": "",
+  "license": "ISC",
+  "dependencies": {
+    "@ai-sdk/openai": "^1.1.9",
+    "dotenv": "^16.4.7",
+    "express": "^4.21.2"
+  }
+}

+ 8 - 0
apps/ai-server/src/controller/AIController.js

@@ -0,0 +1,8 @@
+const AIController = require('express').Router();
+const AIService = require('../service');
+
+AIController.post('/chat', (req, res) => {
+  AIService.chat(req, res);
+});
+
+module.exports = AIController;

+ 5 - 0
apps/ai-server/src/controller/index.js

@@ -0,0 +1,5 @@
+const router = require('express').Router();
+
+router.use('/ai', require('./AIController'));
+
+module.exports = router;

+ 9 - 0
apps/ai-server/src/index.js

@@ -0,0 +1,9 @@
+const app = require('express')();
+const express = require('express');
+
+app.use(express.json());
+app.use(express.urlencoded({ extended: true }));
+
+app.use('/', require('./controller'));
+
+module.exports = app;

+ 43 - 0
apps/ai-server/src/service/index.js

@@ -0,0 +1,43 @@
+const { streamText } = require('ai');
+const { createOpenAI } = require('@ai-sdk/openai');
+
+class AIService {
+  async chat(req, res) {
+    const { messages } = req.body;
+
+    try {
+      const deepseek = createOpenAI({
+        baseURL: 'https://api.deepseek.com',
+        apiKey: process.env.DEEPSEEK_API_KEY,
+      });
+
+      const result = await streamText({
+        "model": deepseek.chat("deepseek-chat"),
+        messages,
+        temperature: 0.5,
+        maxTokens: 1024
+      });
+
+      console.log(result.textStream);
+
+      // 设置流式响应头
+      res.setHeader('Content-Type', 'text/plain');
+      res.setHeader('Transfer-Encoding', 'chunked');
+
+      // 流式返回数据
+      for await (const chunk of result.textStream) {
+        console.log(chunk);
+        res.write(chunk);
+      }
+
+      res.end(); // 结束响应
+
+    } catch (error) {
+      console.error(error);
+      res.status(500).send(JSON.stringify({ error: error.message }));
+    }
+    
+  }
+}
+
+module.exports = new AIService();

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

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

+ 67 - 0
apps/designer/src/components/ai/Chat.tsx

@@ -0,0 +1,67 @@
+import React from "react";
+import {
+  PlusOutlined,
+  CloseOutlined,
+  FieldTimeOutlined,
+  UserOutlined,
+  SendOutlined,
+  LoadingOutlined,
+  PauseOutlined,
+} from "@ant-design/icons";
+import { Button, Tooltip, Input } from "antd";
+import { useModel } from "umi";
+
+export default function Chat() {
+  const { setActiveAIChat } = useModel("appModel");
+  const [focused, setFocused] = React.useState(false);
+
+  return (
+    <div className="flex-1 h-full flex flex-col">
+      <div className="chat-head w-full h-40px px-10px color-#333 flex items-center justify-between">
+        <i className="iconfont icon-duihua"></i>
+        <span>
+          <Tooltip title="新建会话">
+            <Button type="text" size="small" icon={<PlusOutlined />}></Button>
+          </Tooltip>
+          <Tooltip title="历史记录">
+            <Button
+              type="text"
+              size="small"
+              icon={<FieldTimeOutlined />}
+            ></Button>
+          </Tooltip>
+          <Tooltip title="关闭">
+            <Button
+              type="text"
+              size="small"
+              icon={<CloseOutlined />}
+              onClick={() => setActiveAIChat(false)}
+            ></Button>
+          </Tooltip>
+        </span>
+      </div>
+
+      <div className="chat-content flex-1 bg-#f5f5f5 px-10px">
+        <div className="text-center pt-40px"><i className="iconfont icon-robot-2-line text-#333 text-32px"></i></div>
+        <h2 className="text-center">询问AI助手</h2>
+        <p>您好,我是AI助手,有什么可以帮您的吗?</p>
+      </div>
+
+      <div style={{
+        borderColor: focused ? '#1890ff' : '#ddd'
+      }} className="chat-foot bg-#f3f4f6 rounded-10px border border-solid border-1px m-10px">
+        <Input.TextArea
+          rows={3}
+          autoSize={{ maxRows: 3, minRows: 3 }}
+          placeholder="输入询问内容..."
+          variant="borderless"
+          onFocus={() => setFocused(true)}
+          onBlur={() => setFocused(false)}
+        />
+        <div className="float-right p-10px">
+          <Button type="primary" icon={<SendOutlined />}>发送</Button>
+        </div>
+      </div>
+    </div>
+  );
+}

apps/designer/src/pages/flow/components/ToolBar/MermaidModal.tsx → apps/designer/src/components/ai/MermaidModal.tsx


apps/designer/src/pages/flow/components/ToolBar/mermaid.less → apps/designer/src/components/ai/mermaid.less


+ 5 - 1
apps/designer/src/models/appModel.ts

@@ -36,6 +36,8 @@ export default function appModel() {
   const [leftPanelActiveKey, setLeftPanelActiveKey] = useState("1");
   // 历史记录
   const [showHistory, setShowHistory] = useState(false);
+  // 激活AI对话
+  const [activeAIChat, setActiveAIChat] = useState(false);
   // 右侧面板tab activeKey
   const [rightPanelTabActiveKey, setRightPanelTabActiveKey] = useState("1");
   const graphRef = useRef<Graph>();
@@ -177,6 +179,8 @@ export default function appModel() {
     leftPanelActiveKey,
     setLeftPanelActiveKey,
     showHistory,
-    setShowHistory
+    setShowHistory,
+    activeAIChat,
+    setActiveAIChat
   }
 }

+ 35 - 20
apps/designer/src/pages/flow/components/Config/index.tsx

@@ -5,6 +5,7 @@ import GraphStyle from "./GraphStyle";
 import NodeAttrs from "@/components/NodeAttrs";
 import { useModel } from "umi";
 import InsetCss from "insert-css";
+import Chat from "@/components/ai/Chat";
 
 InsetCss(`
   .shalu-tabs {
@@ -19,19 +20,21 @@ InsetCss(`
   `);
 export default function Config() {
   const { selectedCell } = useModel("graphModel");
-  const { rightPanelTabActiveKey, setRightPanelTabActiveKey } =
+  const { rightPanelTabActiveKey, setRightPanelTabActiveKey, activeAIChat } =
     useModel("appModel");
 
   const firstNode = useMemo(() => {
-    return selectedCell.find(cell => cell.isNode());
+    return selectedCell.find((cell) => cell.isNode());
   }, [selectedCell]);
 
+  const resizeRef = React.useRef<HTMLDivElement>(null);
+
   // 设置节点属性
   const handleSetNodeAttr = (attrs: any) => {
     firstNode?.setData({
-      attrs
+      attrs,
     });
-  }
+  };
 
   const tabItems = [
     {
@@ -69,16 +72,18 @@ export default function Config() {
     {
       key: "3",
       label: `节点属性`,
-      children: <ConfigProvider
-      componentSize="small"
-      theme={{
-        token: {
-          colorPrimary: "#1890ff",
-        },
-      }}
-    >
-      <NodeAttrs cell={firstNode} onChange={handleSetNodeAttr}/>
-    </ConfigProvider>,
+      children: (
+        <ConfigProvider
+          componentSize="small"
+          theme={{
+            token: {
+              colorPrimary: "#1890ff",
+            },
+          }}
+        >
+          <NodeAttrs cell={firstNode} onChange={handleSetNodeAttr} />
+        </ConfigProvider>
+      ),
     },
   ];
 
@@ -91,11 +96,21 @@ export default function Config() {
   // }, [selectedCell]);
 
   return (
-    <Tabs
-      centered
-      items={tabItems}
-      activeKey={rightPanelTabActiveKey}
-      onChange={(key) => setRightPanelTabActiveKey(key)}
-    />
+    <div className="w-full h-full flex">
+      <div
+        className="h-full w-4px cursor-e-resize hover:bg-blue"
+        ref={resizeRef}
+      ></div>
+      {activeAIChat ? (
+        <Chat />
+      ) : (
+        <Tabs
+          centered
+          items={tabItems}
+          activeKey={rightPanelTabActiveKey}
+          onChange={(key) => setRightPanelTabActiveKey(key)}
+        />
+      )}
+    </div>
   );
 }

+ 18 - 21
apps/designer/src/pages/flow/components/ToolBar/index.tsx

@@ -31,7 +31,7 @@ import { ConnectorType } from "@/enum";
 import { set, cloneDeep } from "lodash-es";
 import FindReplaceModal from "@/components/FindReplaceModal";
 import { useFindReplace } from "@/hooks/useFindReplace";
-import MermaidModal, { MermaidResult } from "./MermaidModal";
+import MermaidModal, { MermaidResult } from "@/components/ai/MermaidModal";
 import { nodeMenu, edgeMenu } from "@/utils/contentMenu";
 import BaseNode from "@/components/base";
 import { Cell } from "@antv/x6";
@@ -43,6 +43,8 @@ export default function ToolBar() {
     toggleFormatBrush,
     enableFormatBrush,
     pageState,
+    activeAIChat,
+    setActiveAIChat,
   } = useModel("appModel");
   const { canRedo, canUndo, onRedo, onUndo, selectedCell, graph } =
     useModel("graphModel");
@@ -740,31 +742,15 @@ export default function ToolBar() {
             </Tooltip>
           </Dropdown>
 
-          <Dropdown
-            menu={{
-              items: [
-                {
-                  key: "mermaid",
-                  label: (
-                    <div>
-                      <i className="iconfont icon-Mermaid mr-8px" />
-                      Mermaid导入
-                    </div>
-                  ),
-                  onClick: () =>
-                    mermaidModelRef.current?.open()
-                },
-              ],
-            }}
-          >
+          <Tooltip title="Mermaid导入">
             <Button
               type="text"
               className="w-50px"
+              onClick={() => mermaidModelRef.current?.open()}
             >
-              <i className="iconfont icon-ai"></i>
-              <CaretDownOutlined className="text-12px" />
+              <i className="iconfont icon-Mermaid color-#ff356f"></i>
             </Button>
-          </Dropdown>
+          </Tooltip>
 
           {/* <Dropdown menu={{ items: [] }}>
             <Button type="text" className="w-50px">
@@ -788,6 +774,17 @@ export default function ToolBar() {
         />
         <MermaidModal ref={mermaidModelRef} onChange={handleInsertMermaid}/>
         <div>
+          <Tooltip placement="bottom" title="打开聊天">
+            <Button
+              type="text"
+              icon={<i className="iconfont icon-AI" />}
+              className={activeAIChat ? "active" : ""}
+              style={{marginRight: 16}}
+              onClick={() => {
+                setActiveAIChat(!activeAIChat);
+              }}
+            />
+          </Tooltip>
           <Tooltip placement="bottom" title="替换">
             <Button
               type="text"

+ 2 - 0
package.json

@@ -58,8 +58,10 @@
     "@uiw/react-codemirror": "^4.23.3",
     "@unocss/cli": "^0.62.3",
     "ahooks": "^3.8.1",
+    "ai": "^4.1.26",
     "antd": "^5.23.0",
     "axios": "^1.7.7",
+    "bytemd": "^1.21.0",
     "dagre": "^0.8.5",
     "dayjs": "^1.11.13",
     "insert-css": "^2.0.0",

File diff suppressed because it is too large
+ 564 - 41
pnpm-lock.yaml