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 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 const FileMimeTypeMap = {
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
- };
 
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 useSaveGraph = () => {
265
  const { data } = useFetchFlow();
266
- const { setFlow, loading } = useSetFlow();
267
- const { id } = useParams();
268
  const { nodes, edges } = useGraphStore((state) => state);
269
- useEffect(() => {}, [nodes]);
270
- const saveGraph = useCallback(
271
- async (currentNodes?: Node[]) => {
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
- [nodes, edges, setFlow, id, data],
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
+ };