balibabu
commited on
Commit
·
6d4da5b
1
Parent(s):
189d1d0
Feat: Import & export the agents. #3851 (#3894)
Browse files### What problem does this PR solve?
Feat: Import & export the agents. #3851
### Type of change
- [x] New Feature (non-breaking change which adds functionality)
- web/package-lock.json +34 -0
- web/package.json +1 -0
- web/src/components/ui/tooltip.tsx +30 -0
- web/src/constants/common.ts +22 -21
- web/src/locales/en.ts +2 -0
- web/src/locales/zh-traditional.ts +2 -0
- web/src/locales/zh.ts +2 -0
- web/src/pages/flow/canvas/index.tsx +47 -1
- web/src/pages/flow/hooks.tsx +81 -13
- web/src/pages/flow/json-upload-modal/index.less +13 -0
- web/src/pages/flow/json-upload-modal/index.tsx +97 -0
- web/src/utils/file-util.ts +9 -0
web/package-lock.json
CHANGED
@@ -28,6 +28,7 @@
|
|
28 |
"@radix-ui/react-switch": "^1.1.1",
|
29 |
"@radix-ui/react-tabs": "^1.1.1",
|
30 |
"@radix-ui/react-toast": "^1.2.2",
|
|
|
31 |
"@tailwindcss/line-clamp": "^0.4.4",
|
32 |
"@tanstack/react-query": "^5.40.0",
|
33 |
"@tanstack/react-query-devtools": "^5.51.5",
|
@@ -4891,6 +4892,39 @@
|
|
4891 |
}
|
4892 |
}
|
4893 |
},
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
4894 |
"node_modules/@radix-ui/react-use-callback-ref": {
|
4895 |
"version": "1.1.0",
|
4896 |
"resolved": "https://registry.npmmirror.com/@radix-ui/react-use-callback-ref/-/react-use-callback-ref-1.1.0.tgz",
|
|
|
28 |
"@radix-ui/react-switch": "^1.1.1",
|
29 |
"@radix-ui/react-tabs": "^1.1.1",
|
30 |
"@radix-ui/react-toast": "^1.2.2",
|
31 |
+
"@radix-ui/react-tooltip": "^1.1.4",
|
32 |
"@tailwindcss/line-clamp": "^0.4.4",
|
33 |
"@tanstack/react-query": "^5.40.0",
|
34 |
"@tanstack/react-query-devtools": "^5.51.5",
|
|
|
4892 |
}
|
4893 |
}
|
4894 |
},
|
4895 |
+
"node_modules/@radix-ui/react-tooltip": {
|
4896 |
+
"version": "1.1.4",
|
4897 |
+
"resolved": "https://registry.npmmirror.com/@radix-ui/react-tooltip/-/react-tooltip-1.1.4.tgz",
|
4898 |
+
"integrity": "sha512-QpObUH/ZlpaO4YgHSaYzrLO2VuO+ZBFFgGzjMUPwtiYnAzzNNDPJeEGRrT7qNOrWm/Jr08M1vlp+vTHtnSQ0Uw==",
|
4899 |
+
"dependencies": {
|
4900 |
+
"@radix-ui/primitive": "1.1.0",
|
4901 |
+
"@radix-ui/react-compose-refs": "1.1.0",
|
4902 |
+
"@radix-ui/react-context": "1.1.1",
|
4903 |
+
"@radix-ui/react-dismissable-layer": "1.1.1",
|
4904 |
+
"@radix-ui/react-id": "1.1.0",
|
4905 |
+
"@radix-ui/react-popper": "1.2.0",
|
4906 |
+
"@radix-ui/react-portal": "1.1.2",
|
4907 |
+
"@radix-ui/react-presence": "1.1.1",
|
4908 |
+
"@radix-ui/react-primitive": "2.0.0",
|
4909 |
+
"@radix-ui/react-slot": "1.1.0",
|
4910 |
+
"@radix-ui/react-use-controllable-state": "1.1.0",
|
4911 |
+
"@radix-ui/react-visually-hidden": "1.1.0"
|
4912 |
+
},
|
4913 |
+
"peerDependencies": {
|
4914 |
+
"@types/react": "*",
|
4915 |
+
"@types/react-dom": "*",
|
4916 |
+
"react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc",
|
4917 |
+
"react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc"
|
4918 |
+
},
|
4919 |
+
"peerDependenciesMeta": {
|
4920 |
+
"@types/react": {
|
4921 |
+
"optional": true
|
4922 |
+
},
|
4923 |
+
"@types/react-dom": {
|
4924 |
+
"optional": true
|
4925 |
+
}
|
4926 |
+
}
|
4927 |
+
},
|
4928 |
"node_modules/@radix-ui/react-use-callback-ref": {
|
4929 |
"version": "1.1.0",
|
4930 |
"resolved": "https://registry.npmmirror.com/@radix-ui/react-use-callback-ref/-/react-use-callback-ref-1.1.0.tgz",
|
web/package.json
CHANGED
@@ -39,6 +39,7 @@
|
|
39 |
"@radix-ui/react-switch": "^1.1.1",
|
40 |
"@radix-ui/react-tabs": "^1.1.1",
|
41 |
"@radix-ui/react-toast": "^1.2.2",
|
|
|
42 |
"@tailwindcss/line-clamp": "^0.4.4",
|
43 |
"@tanstack/react-query": "^5.40.0",
|
44 |
"@tanstack/react-query-devtools": "^5.51.5",
|
|
|
39 |
"@radix-ui/react-switch": "^1.1.1",
|
40 |
"@radix-ui/react-tabs": "^1.1.1",
|
41 |
"@radix-ui/react-toast": "^1.2.2",
|
42 |
+
"@radix-ui/react-tooltip": "^1.1.4",
|
43 |
"@tailwindcss/line-clamp": "^0.4.4",
|
44 |
"@tanstack/react-query": "^5.40.0",
|
45 |
"@tanstack/react-query-devtools": "^5.51.5",
|
web/src/components/ui/tooltip.tsx
ADDED
@@ -0,0 +1,30 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
'use client';
|
2 |
+
|
3 |
+
import * as TooltipPrimitive from '@radix-ui/react-tooltip';
|
4 |
+
import * as React from 'react';
|
5 |
+
|
6 |
+
import { cn } from '@/lib/utils';
|
7 |
+
|
8 |
+
const TooltipProvider = TooltipPrimitive.Provider;
|
9 |
+
|
10 |
+
const Tooltip = TooltipPrimitive.Root;
|
11 |
+
|
12 |
+
const TooltipTrigger = TooltipPrimitive.Trigger;
|
13 |
+
|
14 |
+
const TooltipContent = React.forwardRef<
|
15 |
+
React.ElementRef<typeof TooltipPrimitive.Content>,
|
16 |
+
React.ComponentPropsWithoutRef<typeof TooltipPrimitive.Content>
|
17 |
+
>(({ className, sideOffset = 4, ...props }, ref) => (
|
18 |
+
<TooltipPrimitive.Content
|
19 |
+
ref={ref}
|
20 |
+
sideOffset={sideOffset}
|
21 |
+
className={cn(
|
22 |
+
'z-50 overflow-hidden rounded-md border bg-popover px-3 py-1.5 text-sm text-popover-foreground shadow-md animate-in fade-in-0 zoom-in-95 data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=closed]:zoom-out-95 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2',
|
23 |
+
className,
|
24 |
+
)}
|
25 |
+
{...props}
|
26 |
+
/>
|
27 |
+
));
|
28 |
+
TooltipContent.displayName = TooltipPrimitive.Content.displayName;
|
29 |
+
|
30 |
+
export { Tooltip, TooltipContent, TooltipProvider, TooltipTrigger };
|
web/src/constants/common.ts
CHANGED
@@ -66,27 +66,28 @@ export const LanguageTranslationMap = {
|
|
66 |
Vietnamese: 'vi',
|
67 |
};
|
68 |
|
69 |
-
export
|
70 |
-
|
71 |
-
|
72 |
-
|
73 |
-
|
74 |
-
|
75 |
-
|
76 |
-
|
77 |
-
|
78 |
-
|
79 |
-
|
80 |
-
|
81 |
-
|
82 |
-
|
83 |
-
|
84 |
-
|
85 |
-
|
86 |
-
|
87 |
-
|
88 |
-
|
89 |
-
|
|
|
90 |
|
91 |
export const Domain = 'demo.ragflow.io';
|
92 |
|
|
|
66 |
Vietnamese: 'vi',
|
67 |
};
|
68 |
|
69 |
+
export enum FileMimeType {
|
70 |
+
Bmp = 'image/bmp',
|
71 |
+
Csv = 'text/csv',
|
72 |
+
Odt = 'application/vnd.oasis.opendocument.text',
|
73 |
+
Doc = 'application/msword',
|
74 |
+
Docx = 'application/vnd.openxmlformats-officedocument.wordprocessingml.document',
|
75 |
+
Gif = 'image/gif',
|
76 |
+
Htm = 'text/htm',
|
77 |
+
Html = 'text/html',
|
78 |
+
Jpg = 'image/jpg',
|
79 |
+
Jpeg = 'image/jpeg',
|
80 |
+
Pdf = 'application/pdf',
|
81 |
+
Png = 'image/png',
|
82 |
+
Ppt = 'application/vnd.ms-powerpoint',
|
83 |
+
Pptx = 'application/vnd.openxmlformats-officedocument.presentationml.presentation',
|
84 |
+
Tiff = 'image/tiff',
|
85 |
+
Txt = 'text/plain',
|
86 |
+
Xls = 'application/vnd.ms-excel',
|
87 |
+
Xlsx = 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet',
|
88 |
+
Mp4 = 'video/mp4',
|
89 |
+
Json = 'application/json',
|
90 |
+
}
|
91 |
|
92 |
export const Domain = 'demo.ragflow.io';
|
93 |
|
web/src/locales/en.ts
CHANGED
@@ -1077,6 +1077,8 @@ When you want to search the given knowledge base at first place, set a higher pa
|
|
1077 |
ccEmailTip: 'cc_email: CC email (Optional)',
|
1078 |
subjectTip: 'subject: Email subject (Optional)',
|
1079 |
contentTip: 'content: Email content (Optional)',
|
|
|
|
|
1080 |
},
|
1081 |
footer: {
|
1082 |
profile: 'All rights reserved @ React',
|
|
|
1077 |
ccEmailTip: 'cc_email: CC email (Optional)',
|
1078 |
subjectTip: 'subject: Email subject (Optional)',
|
1079 |
contentTip: 'content: Email content (Optional)',
|
1080 |
+
jsonUploadTypeErrorMessage: 'Please upload json file',
|
1081 |
+
jsonUploadContentErrorMessage: 'json file error',
|
1082 |
},
|
1083 |
footer: {
|
1084 |
profile: 'All rights reserved @ React',
|
web/src/locales/zh-traditional.ts
CHANGED
@@ -1010,6 +1010,8 @@ export default {
|
|
1010 |
testRun: '試運行',
|
1011 |
template: '模板轉換',
|
1012 |
templateDescription: '此元件用於排版各種元件的輸出。 ',
|
|
|
|
|
1013 |
},
|
1014 |
footer: {
|
1015 |
profile: '“保留所有權利 @ react”',
|
|
|
1010 |
testRun: '試運行',
|
1011 |
template: '模板轉換',
|
1012 |
templateDescription: '此元件用於排版各種元件的輸出。 ',
|
1013 |
+
jsonUploadTypeErrorMessage: '請上傳json檔',
|
1014 |
+
jsonUploadContentErrorMessage: 'json 檔案錯誤',
|
1015 |
},
|
1016 |
footer: {
|
1017 |
profile: '“保留所有權利 @ react”',
|
web/src/locales/zh.ts
CHANGED
@@ -1055,6 +1055,8 @@ export default {
|
|
1055 |
ccEmailTip: 'cc_email: 抄送邮箱(可选)',
|
1056 |
subjectTip: 'subject: 邮件主题(可选)',
|
1057 |
contentTip: 'content: 邮件内容(可选)',
|
|
|
|
|
1058 |
},
|
1059 |
footer: {
|
1060 |
profile: 'All rights reserved @ React',
|
|
|
1055 |
ccEmailTip: 'cc_email: 抄送邮箱(可选)',
|
1056 |
subjectTip: 'subject: 邮件主题(可选)',
|
1057 |
contentTip: 'content: 邮件内容(可选)',
|
1058 |
+
jsonUploadTypeErrorMessage: '请上传json文件',
|
1059 |
+
jsonUploadContentErrorMessage: 'json 文件错误',
|
1060 |
},
|
1061 |
footer: {
|
1062 |
profile: 'All rights reserved @ React',
|
web/src/pages/flow/canvas/index.tsx
CHANGED
@@ -1,8 +1,16 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
import { useSetModalState } from '@/hooks/common-hooks';
|
|
|
2 |
import { useCallback, useEffect } from 'react';
|
3 |
import ReactFlow, {
|
4 |
Background,
|
5 |
ConnectionMode,
|
|
|
6 |
Controls,
|
7 |
NodeMouseHandler,
|
8 |
} from 'reactflow';
|
@@ -13,12 +21,14 @@ import FormDrawer from '../flow-drawer';
|
|
13 |
import {
|
14 |
useGetBeginNodeDataQuery,
|
15 |
useHandleDrop,
|
|
|
16 |
useSelectCanvasData,
|
17 |
useShowFormDrawer,
|
18 |
useValidateConnection,
|
19 |
useWatchNodeFormDataChange,
|
20 |
} from '../hooks';
|
21 |
import { BeginQuery } from '../interface';
|
|
|
22 |
import RunDrawer from '../run-drawer';
|
23 |
import { ButtonEdge } from './edge';
|
24 |
import styles from './index.less';
|
@@ -115,6 +125,14 @@ function FlowCanvas({ drawerVisible, hideDrawer }: IProps) {
|
|
115 |
|
116 |
const getBeginNodeDataQuery = useGetBeginNodeDataQuery();
|
117 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
118 |
useEffect(() => {
|
119 |
if (drawerVisible) {
|
120 |
const query: BeginQuery[] = getBeginNodeDataQuery();
|
@@ -192,7 +210,28 @@ function FlowCanvas({ drawerVisible, hideDrawer }: IProps) {
|
|
192 |
deleteKeyCode={['Delete', 'Backspace']}
|
193 |
>
|
194 |
<Background />
|
195 |
-
<Controls
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
196 |
</ReactFlow>
|
197 |
{formDrawerVisible && (
|
198 |
<FormDrawer
|
@@ -214,6 +253,13 @@ function FlowCanvas({ drawerVisible, hideDrawer }: IProps) {
|
|
214 |
showModal={showChatModal}
|
215 |
></RunDrawer>
|
216 |
)}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
217 |
</div>
|
218 |
);
|
219 |
}
|
|
|
1 |
+
import {
|
2 |
+
Tooltip,
|
3 |
+
TooltipContent,
|
4 |
+
TooltipProvider,
|
5 |
+
TooltipTrigger,
|
6 |
+
} from '@/components/ui/tooltip';
|
7 |
import { useSetModalState } from '@/hooks/common-hooks';
|
8 |
+
import { FolderInput, FolderOutput } from 'lucide-react';
|
9 |
import { useCallback, useEffect } from 'react';
|
10 |
import ReactFlow, {
|
11 |
Background,
|
12 |
ConnectionMode,
|
13 |
+
ControlButton,
|
14 |
Controls,
|
15 |
NodeMouseHandler,
|
16 |
} from 'reactflow';
|
|
|
21 |
import {
|
22 |
useGetBeginNodeDataQuery,
|
23 |
useHandleDrop,
|
24 |
+
useHandleExportOrImportJsonFile,
|
25 |
useSelectCanvasData,
|
26 |
useShowFormDrawer,
|
27 |
useValidateConnection,
|
28 |
useWatchNodeFormDataChange,
|
29 |
} from '../hooks';
|
30 |
import { BeginQuery } from '../interface';
|
31 |
+
import JsonUploadModal from '../json-upload-modal';
|
32 |
import RunDrawer from '../run-drawer';
|
33 |
import { ButtonEdge } from './edge';
|
34 |
import styles from './index.less';
|
|
|
125 |
|
126 |
const getBeginNodeDataQuery = useGetBeginNodeDataQuery();
|
127 |
|
128 |
+
const {
|
129 |
+
handleExportJson,
|
130 |
+
handleImportJson,
|
131 |
+
fileUploadVisible,
|
132 |
+
onFileUploadOk,
|
133 |
+
hideFileUploadModal,
|
134 |
+
} = useHandleExportOrImportJsonFile();
|
135 |
+
|
136 |
useEffect(() => {
|
137 |
if (drawerVisible) {
|
138 |
const query: BeginQuery[] = getBeginNodeDataQuery();
|
|
|
210 |
deleteKeyCode={['Delete', 'Backspace']}
|
211 |
>
|
212 |
<Background />
|
213 |
+
<Controls>
|
214 |
+
<ControlButton onClick={handleImportJson}>
|
215 |
+
<TooltipProvider>
|
216 |
+
<Tooltip>
|
217 |
+
<TooltipTrigger>
|
218 |
+
<FolderInput />
|
219 |
+
</TooltipTrigger>
|
220 |
+
<TooltipContent>Import</TooltipContent>
|
221 |
+
</Tooltip>
|
222 |
+
</TooltipProvider>
|
223 |
+
</ControlButton>
|
224 |
+
<ControlButton onClick={handleExportJson}>
|
225 |
+
<TooltipProvider>
|
226 |
+
<Tooltip>
|
227 |
+
<TooltipTrigger>
|
228 |
+
<FolderOutput />
|
229 |
+
</TooltipTrigger>
|
230 |
+
<TooltipContent>Export</TooltipContent>
|
231 |
+
</Tooltip>
|
232 |
+
</TooltipProvider>
|
233 |
+
</ControlButton>
|
234 |
+
</Controls>
|
235 |
</ReactFlow>
|
236 |
{formDrawerVisible && (
|
237 |
<FormDrawer
|
|
|
253 |
showModal={showChatModal}
|
254 |
></RunDrawer>
|
255 |
)}
|
256 |
+
{fileUploadVisible && (
|
257 |
+
<JsonUploadModal
|
258 |
+
onOk={onFileUploadOk}
|
259 |
+
visible={fileUploadVisible}
|
260 |
+
hideModal={hideFileUploadModal}
|
261 |
+
></JsonUploadModal>
|
262 |
+
)}
|
263 |
</div>
|
264 |
);
|
265 |
}
|
web/src/pages/flow/hooks.tsx
CHANGED
@@ -12,14 +12,16 @@ import React, {
|
|
12 |
import { Connection, Edge, Node, Position, ReactFlowInstance } from 'reactflow';
|
13 |
// import { shallow } from 'zustand/shallow';
|
14 |
import { variableEnabledFieldMap } from '@/constants/chat';
|
|
|
15 |
import {
|
16 |
ModelVariableType,
|
17 |
settledModelVariableMap,
|
18 |
} from '@/constants/knowledge';
|
19 |
import { useFetchModelId } from '@/hooks/logic-hooks';
|
20 |
import { Variable } from '@/interfaces/database/chat';
|
|
|
21 |
import { useDebounceEffect } from 'ahooks';
|
22 |
-
import { FormInstance, message } from 'antd';
|
23 |
import { DefaultOptionType } from 'antd/es/select';
|
24 |
import dayjs from 'dayjs';
|
25 |
import { humanId } from 'human-id';
|
@@ -261,30 +263,45 @@ export const useShowFormDrawer = () => {
|
|
261 |
};
|
262 |
};
|
263 |
|
264 |
-
export const
|
265 |
const { data } = useFetchFlow();
|
266 |
-
const { setFlow, loading } = useSetFlow();
|
267 |
-
const { id } = useParams();
|
268 |
const { nodes, edges } = useGraphStore((state) => state);
|
269 |
-
|
270 |
-
const
|
271 |
-
|
272 |
const dslComponents = buildDslComponentsByGraph(
|
273 |
currentNodes ?? nodes,
|
274 |
edges,
|
275 |
data.dsl.components,
|
276 |
);
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
277 |
return setFlow({
|
278 |
id,
|
279 |
title: data.title,
|
280 |
-
dsl:
|
281 |
-
...data.dsl,
|
282 |
-
graph: { nodes: currentNodes ?? nodes, edges },
|
283 |
-
components: dslComponents,
|
284 |
-
},
|
285 |
});
|
286 |
},
|
287 |
-
[
|
288 |
);
|
289 |
|
290 |
return { saveGraph, loading };
|
@@ -774,3 +791,54 @@ export const useWatchAgentChange = (chatDrawerVisible: boolean) => {
|
|
774 |
|
775 |
return time;
|
776 |
};
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
12 |
import { Connection, Edge, Node, Position, ReactFlowInstance } from 'reactflow';
|
13 |
// import { shallow } from 'zustand/shallow';
|
14 |
import { variableEnabledFieldMap } from '@/constants/chat';
|
15 |
+
import { FileMimeType } from '@/constants/common';
|
16 |
import {
|
17 |
ModelVariableType,
|
18 |
settledModelVariableMap,
|
19 |
} from '@/constants/knowledge';
|
20 |
import { useFetchModelId } from '@/hooks/logic-hooks';
|
21 |
import { Variable } from '@/interfaces/database/chat';
|
22 |
+
import { downloadJsonFile } from '@/utils/file-util';
|
23 |
import { useDebounceEffect } from 'ahooks';
|
24 |
+
import { FormInstance, UploadFile, message } from 'antd';
|
25 |
import { DefaultOptionType } from 'antd/es/select';
|
26 |
import dayjs from 'dayjs';
|
27 |
import { humanId } from 'human-id';
|
|
|
263 |
};
|
264 |
};
|
265 |
|
266 |
+
export const useBuildDslData = () => {
|
267 |
const { data } = useFetchFlow();
|
|
|
|
|
268 |
const { nodes, edges } = useGraphStore((state) => state);
|
269 |
+
|
270 |
+
const buildDslData = useCallback(
|
271 |
+
(currentNodes?: Node[]) => {
|
272 |
const dslComponents = buildDslComponentsByGraph(
|
273 |
currentNodes ?? nodes,
|
274 |
edges,
|
275 |
data.dsl.components,
|
276 |
);
|
277 |
+
|
278 |
+
return {
|
279 |
+
...data.dsl,
|
280 |
+
graph: { nodes: currentNodes ?? nodes, edges },
|
281 |
+
components: dslComponents,
|
282 |
+
};
|
283 |
+
},
|
284 |
+
[data.dsl, edges, nodes],
|
285 |
+
);
|
286 |
+
|
287 |
+
return { buildDslData };
|
288 |
+
};
|
289 |
+
|
290 |
+
export const useSaveGraph = () => {
|
291 |
+
const { data } = useFetchFlow();
|
292 |
+
const { setFlow, loading } = useSetFlow();
|
293 |
+
const { id } = useParams();
|
294 |
+
const { buildDslData } = useBuildDslData();
|
295 |
+
|
296 |
+
const saveGraph = useCallback(
|
297 |
+
async (currentNodes?: Node[]) => {
|
298 |
return setFlow({
|
299 |
id,
|
300 |
title: data.title,
|
301 |
+
dsl: buildDslData(currentNodes),
|
|
|
|
|
|
|
|
|
302 |
});
|
303 |
},
|
304 |
+
[setFlow, id, data.title, buildDslData],
|
305 |
);
|
306 |
|
307 |
return { saveGraph, loading };
|
|
|
791 |
|
792 |
return time;
|
793 |
};
|
794 |
+
|
795 |
+
export const useHandleExportOrImportJsonFile = () => {
|
796 |
+
const { buildDslData } = useBuildDslData();
|
797 |
+
const {
|
798 |
+
visible: fileUploadVisible,
|
799 |
+
hideModal: hideFileUploadModal,
|
800 |
+
showModal: showFileUploadModal,
|
801 |
+
} = useSetModalState();
|
802 |
+
const setGraphInfo = useSetGraphInfo();
|
803 |
+
const { data } = useFetchFlow();
|
804 |
+
const { t } = useTranslation();
|
805 |
+
|
806 |
+
const onFileUploadOk = useCallback(
|
807 |
+
async (fileList: UploadFile[]) => {
|
808 |
+
if (fileList.length > 0) {
|
809 |
+
const file: File = fileList[0] as unknown as File;
|
810 |
+
if (file.type !== FileMimeType.Json) {
|
811 |
+
message.error(t('flow.jsonUploadTypeErrorMessage'));
|
812 |
+
return;
|
813 |
+
}
|
814 |
+
|
815 |
+
const graphStr = await file.text();
|
816 |
+
const errorMessage = t('flow.jsonUploadContentErrorMessage');
|
817 |
+
try {
|
818 |
+
const graph = JSON.parse(graphStr);
|
819 |
+
if (graphStr && !isEmpty(graph) && Array.isArray(graph?.nodes)) {
|
820 |
+
setGraphInfo(graph ?? ({} as IGraph));
|
821 |
+
hideFileUploadModal();
|
822 |
+
} else {
|
823 |
+
message.error(errorMessage);
|
824 |
+
}
|
825 |
+
} catch (error) {
|
826 |
+
message.error(errorMessage);
|
827 |
+
}
|
828 |
+
}
|
829 |
+
},
|
830 |
+
[hideFileUploadModal, setGraphInfo, t],
|
831 |
+
);
|
832 |
+
|
833 |
+
const handleExportJson = useCallback(() => {
|
834 |
+
downloadJsonFile(buildDslData().graph, `${data.title}.json`);
|
835 |
+
}, [buildDslData, data.title]);
|
836 |
+
|
837 |
+
return {
|
838 |
+
fileUploadVisible,
|
839 |
+
handleExportJson,
|
840 |
+
handleImportJson: showFileUploadModal,
|
841 |
+
hideFileUploadModal,
|
842 |
+
onFileUploadOk,
|
843 |
+
};
|
844 |
+
};
|
web/src/pages/flow/json-upload-modal/index.less
ADDED
@@ -0,0 +1,13 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
.uploader {
|
2 |
+
:global {
|
3 |
+
.ant-upload-list {
|
4 |
+
max-height: 40vh;
|
5 |
+
overflow-y: auto;
|
6 |
+
}
|
7 |
+
}
|
8 |
+
}
|
9 |
+
|
10 |
+
.uploadLimit {
|
11 |
+
color: red;
|
12 |
+
font-size: 12px;
|
13 |
+
}
|
web/src/pages/flow/json-upload-modal/index.tsx
ADDED
@@ -0,0 +1,97 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
import { useTranslate } from '@/hooks/common-hooks';
|
2 |
+
import { IModalProps } from '@/interfaces/common';
|
3 |
+
import { InboxOutlined } from '@ant-design/icons';
|
4 |
+
import { Modal, Upload, UploadFile, UploadProps } from 'antd';
|
5 |
+
import { Dispatch, SetStateAction, useState } from 'react';
|
6 |
+
|
7 |
+
import { FileMimeType } from '@/constants/common';
|
8 |
+
|
9 |
+
import styles from './index.less';
|
10 |
+
|
11 |
+
const { Dragger } = Upload;
|
12 |
+
|
13 |
+
const FileUpload = ({
|
14 |
+
directory,
|
15 |
+
fileList,
|
16 |
+
setFileList,
|
17 |
+
}: {
|
18 |
+
directory: boolean;
|
19 |
+
fileList: UploadFile[];
|
20 |
+
setFileList: Dispatch<SetStateAction<UploadFile[]>>;
|
21 |
+
}) => {
|
22 |
+
const { t } = useTranslate('fileManager');
|
23 |
+
const props: UploadProps = {
|
24 |
+
multiple: false,
|
25 |
+
accept: FileMimeType.Json,
|
26 |
+
onRemove: (file) => {
|
27 |
+
const index = fileList.indexOf(file);
|
28 |
+
const newFileList = fileList.slice();
|
29 |
+
newFileList.splice(index, 1);
|
30 |
+
setFileList(newFileList);
|
31 |
+
},
|
32 |
+
beforeUpload: (file) => {
|
33 |
+
setFileList(() => {
|
34 |
+
return [file];
|
35 |
+
});
|
36 |
+
|
37 |
+
return false;
|
38 |
+
},
|
39 |
+
directory,
|
40 |
+
fileList,
|
41 |
+
};
|
42 |
+
|
43 |
+
return (
|
44 |
+
<Dragger {...props} className={styles.uploader}>
|
45 |
+
<p className="ant-upload-drag-icon">
|
46 |
+
<InboxOutlined />
|
47 |
+
</p>
|
48 |
+
<p className="ant-upload-text">{t('uploadTitle')}</p>
|
49 |
+
<p className="ant-upload-hint">{t('uploadDescription')}</p>
|
50 |
+
{false && <p className={styles.uploadLimit}>{t('uploadLimit')}</p>}
|
51 |
+
</Dragger>
|
52 |
+
);
|
53 |
+
};
|
54 |
+
|
55 |
+
const JsonUploadModal = ({
|
56 |
+
visible,
|
57 |
+
hideModal,
|
58 |
+
loading,
|
59 |
+
onOk: onFileUploadOk,
|
60 |
+
}: IModalProps<UploadFile[]>) => {
|
61 |
+
const { t } = useTranslate('fileManager');
|
62 |
+
const [fileList, setFileList] = useState<UploadFile[]>([]);
|
63 |
+
const [directoryFileList, setDirectoryFileList] = useState<UploadFile[]>([]);
|
64 |
+
|
65 |
+
const clearFileList = () => {
|
66 |
+
setFileList([]);
|
67 |
+
setDirectoryFileList([]);
|
68 |
+
};
|
69 |
+
|
70 |
+
const onOk = async () => {
|
71 |
+
const ret = await onFileUploadOk?.([...fileList, ...directoryFileList]);
|
72 |
+
return ret;
|
73 |
+
};
|
74 |
+
|
75 |
+
const afterClose = () => {
|
76 |
+
clearFileList();
|
77 |
+
};
|
78 |
+
|
79 |
+
return (
|
80 |
+
<Modal
|
81 |
+
title={t('uploadFile')}
|
82 |
+
open={visible}
|
83 |
+
onOk={onOk}
|
84 |
+
onCancel={hideModal}
|
85 |
+
confirmLoading={loading}
|
86 |
+
afterClose={afterClose}
|
87 |
+
>
|
88 |
+
<FileUpload
|
89 |
+
directory={false}
|
90 |
+
fileList={fileList}
|
91 |
+
setFileList={setFileList}
|
92 |
+
></FileUpload>
|
93 |
+
</Modal>
|
94 |
+
);
|
95 |
+
};
|
96 |
+
|
97 |
+
export default JsonUploadModal;
|
web/src/utils/file-util.ts
CHANGED
@@ -1,3 +1,4 @@
|
|
|
|
1 |
import fileManagerService from '@/services/file-manager-service';
|
2 |
import { UploadFile } from 'antd';
|
3 |
|
@@ -137,3 +138,11 @@ export const formatBytes = (x: string | number) => {
|
|
137 |
|
138 |
return n.toFixed(n < 10 && l > 0 ? 1 : 0) + ' ' + Units[l];
|
139 |
};
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
import { FileMimeType } from '@/constants/common';
|
2 |
import fileManagerService from '@/services/file-manager-service';
|
3 |
import { UploadFile } from 'antd';
|
4 |
|
|
|
138 |
|
139 |
return n.toFixed(n < 10 && l > 0 ? 1 : 0) + ' ' + Units[l];
|
140 |
};
|
141 |
+
|
142 |
+
export const downloadJsonFile = async (
|
143 |
+
data: Record<string, any>,
|
144 |
+
fileName: string,
|
145 |
+
) => {
|
146 |
+
const blob = new Blob([JSON.stringify(data)], { type: FileMimeType.Json });
|
147 |
+
downloadFileFromBlob(blob, fileName);
|
148 |
+
};
|