mirror of
https://github.com/csunny/DB-GPT.git
synced 2025-09-11 05:49:22 +00:00
feat: add support for importing and exporting flows (#1869)
# Description - Add new components for importing and exporting flows: ImportFlowModal and ExportFlowModal. - Update the necessary API functions in request.ts to handle flow import and export. - Update i18n.ts to include success messages for flow export and import. - Improve error handling and display appropriate messages in case of failures.
This commit is contained in:
@@ -205,6 +205,8 @@ const en = {
|
||||
flow_name_required: 'Please enter the flow name',
|
||||
flow_description_required: 'Please enter the flow description',
|
||||
save_flow_success: 'Save flow success',
|
||||
export_flow_success: 'Export flow success',
|
||||
import_flow_success: 'Import flow success',
|
||||
delete_flow_confirm: 'Are you sure you want to delete this flow?',
|
||||
related_nodes: 'Related Nodes',
|
||||
add_resource: 'Add Resource',
|
||||
@@ -441,6 +443,8 @@ const zh: Resources['translation'] = {
|
||||
flow_name_required: '请输入工作流名称',
|
||||
flow_description_required: '请输入工作流描述',
|
||||
save_flow_success: '保存工作流成功',
|
||||
export_flow_success: '导出工作流成功',
|
||||
import_flow_success: '导入工作流成功',
|
||||
delete_flow_confirm: '确定删除该工作流吗?',
|
||||
related_nodes: '关联节点',
|
||||
language_select_tips: '请选择语言',
|
||||
|
@@ -307,7 +307,7 @@ export const debugFlow = (data: any) => {
|
||||
};
|
||||
|
||||
export const exportFlow = (data: IFlowExportParams) => {
|
||||
return GET<IFlowExportParams, any>('/api/v2/serve/awel/flow/export', data);
|
||||
return GET<IFlowExportParams, any>(`/api/v2/serve/awel/flow/export/${data.uid}`, data);
|
||||
};
|
||||
|
||||
export const importFlow = (data: IFlowImportParams) => {
|
||||
|
90
web/components/flow/canvas-modal/export-flow-modal.tsx
Normal file
90
web/components/flow/canvas-modal/export-flow-modal.tsx
Normal file
@@ -0,0 +1,90 @@
|
||||
import { Modal, Form, Input, Button, Space, Radio, message } from 'antd';
|
||||
import { IFlowData, IFlowUpdateParam } from '@/types/flow';
|
||||
import { apiInterceptors, exportFlow } from '@/client/api';
|
||||
import { ReactFlowInstance } from 'reactflow';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
|
||||
type Props = {
|
||||
reactFlow: ReactFlowInstance<any, any>;
|
||||
flowInfo?: IFlowUpdateParam;
|
||||
isExportFlowModalOpen: boolean;
|
||||
setIsExportFlowModalOpen: (value: boolean) => void;
|
||||
};
|
||||
|
||||
export const ExportFlowModal: React.FC<Props> = ({ reactFlow, flowInfo, isExportFlowModalOpen, setIsExportFlowModalOpen }) => {
|
||||
const { t } = useTranslation();
|
||||
const [form] = Form.useForm();
|
||||
const [messageApi, contextHolder] = message.useMessage();
|
||||
|
||||
const onFlowExport = async (values: any) => {
|
||||
const flowData = reactFlow.toObject() as IFlowData;
|
||||
const blob = new Blob([JSON.stringify(flowData)], { type: 'text/plain;charset=utf-8' });
|
||||
const url = URL.createObjectURL(blob);
|
||||
const a = document.createElement('a');
|
||||
a.href = url;
|
||||
a.download = values.file_name || 'flow.json';
|
||||
a.click();
|
||||
|
||||
const [, , res] = await apiInterceptors(exportFlow(values));
|
||||
|
||||
if (res?.success) {
|
||||
messageApi.success(t('export_flow_success'));
|
||||
} else if (res?.err_msg) {
|
||||
messageApi.error(res?.err_msg);
|
||||
}
|
||||
|
||||
setIsExportFlowModalOpen(false);
|
||||
};
|
||||
|
||||
return (
|
||||
<>
|
||||
<Modal title="Export Flow" open={isExportFlowModalOpen} onCancel={() => setIsExportFlowModalOpen(false)} footer={null}>
|
||||
<Form
|
||||
form={form}
|
||||
labelCol={{ span: 6 }}
|
||||
wrapperCol={{ span: 16 }}
|
||||
initialValues={{
|
||||
export_type: 'json',
|
||||
format: 'file',
|
||||
file_name: 'flow.json',
|
||||
uid: flowInfo?.uid,
|
||||
}}
|
||||
onFinish={onFlowExport}
|
||||
>
|
||||
<Form.Item label="File Name" name="file_name" rules={[{ required: true, message: 'Please input file name!' }]}>
|
||||
<Input placeholder="file.json" />
|
||||
</Form.Item>
|
||||
|
||||
<Form.Item label="Export Type" name="export_type">
|
||||
<Radio.Group>
|
||||
<Radio value="json">JSON</Radio>
|
||||
<Radio value="dbgpts">DBGPTS</Radio>
|
||||
</Radio.Group>
|
||||
</Form.Item>
|
||||
|
||||
<Form.Item label="Format" name="format">
|
||||
<Radio.Group>
|
||||
<Radio value="file">File</Radio>
|
||||
<Radio value="json">JSON</Radio>
|
||||
</Radio.Group>
|
||||
</Form.Item>
|
||||
|
||||
<Form.Item hidden name="uid">
|
||||
<Input />
|
||||
</Form.Item>
|
||||
|
||||
<Form.Item wrapperCol={{ offset: 14, span: 8 }}>
|
||||
<Space>
|
||||
<Button onClick={() => setIsExportFlowModalOpen(false)}>Cancel</Button>
|
||||
<Button type="primary" htmlType="submit">
|
||||
Export
|
||||
</Button>
|
||||
</Space>
|
||||
</Form.Item>
|
||||
</Form>
|
||||
</Modal>
|
||||
|
||||
{contextHolder}
|
||||
</>
|
||||
);
|
||||
};
|
82
web/components/flow/canvas-modal/import-flow-modal.tsx
Normal file
82
web/components/flow/canvas-modal/import-flow-modal.tsx
Normal file
@@ -0,0 +1,82 @@
|
||||
import { Modal, Form, Button, Space, message, Checkbox, Upload } from 'antd';
|
||||
import { apiInterceptors, importFlow } from '@/client/api';
|
||||
import { Node, Edge } from 'reactflow';
|
||||
import { UploadOutlined } from '@mui/icons-material';
|
||||
import { t } from 'i18next';
|
||||
|
||||
type Props = {
|
||||
isImportModalOpen: boolean;
|
||||
setNodes: React.Dispatch<React.SetStateAction<Node<any, string | undefined>[]>>;
|
||||
setEdges: React.Dispatch<React.SetStateAction<Edge<any>[]>>;
|
||||
setIsImportFlowModalOpen: (value: boolean) => void;
|
||||
};
|
||||
|
||||
export const ImportFlowModal: React.FC<Props> = ({ setNodes, setEdges, isImportModalOpen, setIsImportFlowModalOpen }) => {
|
||||
const [form] = Form.useForm();
|
||||
const [messageApi, contextHolder] = message.useMessage();
|
||||
|
||||
// TODO: Implement onFlowImport
|
||||
const onFlowImport = async (values: any) => {
|
||||
// const input = document.createElement('input');
|
||||
// input.type = 'file';
|
||||
// input.accept = '.json';
|
||||
// input.onchange = async (e: any) => {
|
||||
// const file = e.target.files[0];
|
||||
// const reader = new FileReader();
|
||||
// reader.onload = async (event) => {
|
||||
// const flowData = JSON.parse(event.target?.result as string) as IFlowData;
|
||||
// setNodes(flowData.nodes);
|
||||
// setEdges(flowData.edges);
|
||||
// };
|
||||
// reader.readAsText(file);
|
||||
// };
|
||||
// input.click;
|
||||
console.log(values);
|
||||
values.file = values.file?.[0];
|
||||
|
||||
const [, , res] = await apiInterceptors(importFlow(values));
|
||||
|
||||
if (res?.success) {
|
||||
messageApi.success(t('export_flow_success'));
|
||||
} else if (res?.err_msg) {
|
||||
messageApi.error(res?.err_msg);
|
||||
}
|
||||
|
||||
setIsImportFlowModalOpen(false);
|
||||
};
|
||||
|
||||
return (
|
||||
<>
|
||||
<Modal title="Import Flow" open={isImportModalOpen} onCancel={() => setIsImportFlowModalOpen(false)} footer={null}>
|
||||
<Form form={form} labelCol={{ span: 6 }} wrapperCol={{ span: 16 }} onFinish={onFlowImport}>
|
||||
<Form.Item
|
||||
name="file"
|
||||
label="File"
|
||||
valuePropName="fileList"
|
||||
getValueFromEvent={(e) => (Array.isArray(e) ? e : e && e.fileList)}
|
||||
rules={[{ required: true, message: 'Please upload a file' }]}
|
||||
>
|
||||
<Upload accept=".json,.zip" beforeUpload={() => false} maxCount={1}>
|
||||
<Button icon={<UploadOutlined />}>Click to Upload</Button>
|
||||
</Upload>
|
||||
</Form.Item>
|
||||
|
||||
<Form.Item label="save flow" name="save_flow" valuePropName="checked">
|
||||
<Checkbox />
|
||||
</Form.Item>
|
||||
|
||||
<Form.Item wrapperCol={{ offset: 14, span: 8 }}>
|
||||
<Space>
|
||||
<Button onClick={() => setIsImportFlowModalOpen(false)}>Cancel</Button>
|
||||
<Button type="primary" htmlType="submit">
|
||||
Import
|
||||
</Button>
|
||||
</Space>
|
||||
</Form.Item>
|
||||
</Form>
|
||||
</Modal>
|
||||
|
||||
{contextHolder}
|
||||
</>
|
||||
);
|
||||
};
|
3
web/components/flow/canvas-modal/index.ts
Normal file
3
web/components/flow/canvas-modal/index.ts
Normal file
@@ -0,0 +1,3 @@
|
||||
export * from './save-flow-modal';
|
||||
export * from './export-flow-modal';
|
||||
export * from './import-flow-modal';
|
152
web/components/flow/canvas-modal/save-flow-modal.tsx
Normal file
152
web/components/flow/canvas-modal/save-flow-modal.tsx
Normal file
@@ -0,0 +1,152 @@
|
||||
import { useState } from 'react';
|
||||
import { Modal, Form, Input, Button, Space, message, Checkbox } from 'antd';
|
||||
import { IFlowData, IFlowUpdateParam } from '@/types/flow';
|
||||
import { apiInterceptors, addFlow, updateFlowById } from '@/client/api';
|
||||
import { mapHumpToUnderline } from '@/utils/flow';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { ReactFlowInstance } from 'reactflow';
|
||||
import { useSearchParams } from 'next/navigation';
|
||||
|
||||
const { TextArea } = Input;
|
||||
|
||||
type Props = {
|
||||
reactFlow: ReactFlowInstance<any, any>;
|
||||
flowInfo?: IFlowUpdateParam;
|
||||
isSaveFlowModalOpen: boolean;
|
||||
setIsSaveFlowModalOpen: (value: boolean) => void;
|
||||
};
|
||||
|
||||
export const SaveFlowModal: React.FC<Props> = ({ reactFlow, isSaveFlowModalOpen, flowInfo, setIsSaveFlowModalOpen }) => {
|
||||
const [deploy, setDeploy] = useState(true);
|
||||
const { t } = useTranslation();
|
||||
const searchParams = useSearchParams();
|
||||
const id = searchParams?.get('id') || '';
|
||||
const [form] = Form.useForm<IFlowUpdateParam>();
|
||||
const [messageApi, contextHolder] = message.useMessage();
|
||||
|
||||
function onLabelChange(e: React.ChangeEvent<HTMLInputElement>) {
|
||||
const label = e.target.value;
|
||||
// replace spaces with underscores, convert uppercase letters to lowercase, remove characters other than digits, letters, _, and -.
|
||||
let result = label
|
||||
.replace(/\s+/g, '_')
|
||||
.replace(/[^a-z0-9_-]/g, '')
|
||||
.toLowerCase();
|
||||
result = result;
|
||||
form.setFieldsValue({ name: result });
|
||||
}
|
||||
|
||||
async function onSaveFlow() {
|
||||
const { name, label, description = '', editable = false, state = 'deployed' } = form.getFieldsValue();
|
||||
console.log(form.getFieldsValue());
|
||||
const reactFlowObject = mapHumpToUnderline(reactFlow.toObject() as IFlowData);
|
||||
|
||||
if (id) {
|
||||
const [, , res] = await apiInterceptors(updateFlowById(id, { name, label, description, editable, uid: id, flow_data: reactFlowObject, state }));
|
||||
|
||||
if (res?.success) {
|
||||
messageApi.success(t('save_flow_success'));
|
||||
} else if (res?.err_msg) {
|
||||
messageApi.error(res?.err_msg);
|
||||
}
|
||||
} else {
|
||||
const [_, res] = await apiInterceptors(addFlow({ name, label, description, editable, flow_data: reactFlowObject, state }));
|
||||
if (res?.uid) {
|
||||
messageApi.success(t('save_flow_success'));
|
||||
const history = window.history;
|
||||
history.pushState(null, '', `/flow/canvas?id=${res.uid}`);
|
||||
}
|
||||
}
|
||||
setIsSaveFlowModalOpen(false);
|
||||
}
|
||||
|
||||
return (
|
||||
<>
|
||||
<Modal
|
||||
title={t('flow_modal_title')}
|
||||
open={isSaveFlowModalOpen}
|
||||
onCancel={() => {
|
||||
setIsSaveFlowModalOpen(false);
|
||||
}}
|
||||
cancelButtonProps={{ className: 'hidden' }}
|
||||
okButtonProps={{ className: 'hidden' }}
|
||||
>
|
||||
<Form
|
||||
name="flow_form"
|
||||
form={form}
|
||||
labelCol={{ span: 6 }}
|
||||
wrapperCol={{ span: 16 }}
|
||||
style={{ maxWidth: 600 }}
|
||||
initialValues={{ remember: true }}
|
||||
onFinish={onSaveFlow}
|
||||
autoComplete="off"
|
||||
>
|
||||
<Form.Item label="Title" name="label" initialValue={flowInfo?.label} rules={[{ required: true, message: 'Please input flow title!' }]}>
|
||||
<Input onChange={onLabelChange} />
|
||||
</Form.Item>
|
||||
|
||||
<Form.Item
|
||||
label="Name"
|
||||
name="name"
|
||||
initialValue={flowInfo?.name}
|
||||
rules={[
|
||||
{ required: true, message: 'Please input flow name!' },
|
||||
() => ({
|
||||
validator(_, value) {
|
||||
const regex = /^[a-zA-Z0-9_\-]+$/;
|
||||
if (!regex.test(value)) {
|
||||
return Promise.reject('Can only contain numbers, letters, underscores, and dashes');
|
||||
}
|
||||
return Promise.resolve();
|
||||
},
|
||||
}),
|
||||
]}
|
||||
>
|
||||
<Input />
|
||||
</Form.Item>
|
||||
|
||||
<Form.Item label="Description" initialValue={flowInfo?.description} name="description">
|
||||
<TextArea rows={3} />
|
||||
</Form.Item>
|
||||
|
||||
<Form.Item label="Editable" name="editable" initialValue={flowInfo?.editable} valuePropName="checked">
|
||||
<Checkbox />
|
||||
</Form.Item>
|
||||
|
||||
<Form.Item hidden name="state">
|
||||
<Input />
|
||||
</Form.Item>
|
||||
|
||||
<Form.Item label="Deploy">
|
||||
<Checkbox
|
||||
defaultChecked={flowInfo?.state === 'deployed' || flowInfo?.state === 'running'}
|
||||
checked={deploy}
|
||||
onChange={(e) => {
|
||||
const val = e.target.checked;
|
||||
form.setFieldValue('state', val ? 'deployed' : 'developing');
|
||||
setDeploy(val);
|
||||
}}
|
||||
/>
|
||||
</Form.Item>
|
||||
|
||||
<Form.Item wrapperCol={{ offset: 14, span: 8 }}>
|
||||
<Space>
|
||||
<Button
|
||||
htmlType="button"
|
||||
onClick={() => {
|
||||
setIsSaveFlowModalOpen(false);
|
||||
}}
|
||||
>
|
||||
Cancel
|
||||
</Button>
|
||||
<Button type="primary" htmlType="submit">
|
||||
Submit
|
||||
</Button>
|
||||
</Space>
|
||||
</Form.Item>
|
||||
</Form>
|
||||
</Modal>
|
||||
|
||||
{contextHolder}
|
||||
</>
|
||||
);
|
||||
};
|
@@ -1,4 +1,4 @@
|
||||
import { IFlowNode, IFlowRefreshParams } from '@/types/flow';
|
||||
import { IFlowNode } from '@/types/flow';
|
||||
import Image from 'next/image';
|
||||
import NodeParamHandler from './node-param-handler';
|
||||
import classNames from 'classnames';
|
||||
|
@@ -1,19 +1,18 @@
|
||||
import { addFlow, apiInterceptors, getFlowById, updateFlowById } from '@/client/api';
|
||||
import { apiInterceptors, getFlowById, importFlow } from '@/client/api';
|
||||
import MuiLoading from '@/components/common/loading';
|
||||
import AddNodes from '@/components/flow/add-nodes';
|
||||
import ButtonEdge from '@/components/flow/button-edge';
|
||||
import CanvasNode from '@/components/flow/canvas-node';
|
||||
import { IFlowData, IFlowUpdateParam } from '@/types/flow';
|
||||
import { checkFlowDataRequied, getUniqueNodeId, mapHumpToUnderline, mapUnderlineToHump } from '@/utils/flow';
|
||||
import { checkFlowDataRequied, getUniqueNodeId, mapUnderlineToHump } from '@/utils/flow';
|
||||
import { ExportOutlined, FrownOutlined, ImportOutlined, SaveOutlined } from '@ant-design/icons';
|
||||
import { Button, Checkbox, Divider, Form, Input, Modal, Space, Tooltip, message, notification } from 'antd';
|
||||
import { Divider, Space, Tooltip, message, notification } from 'antd';
|
||||
import { useSearchParams } from 'next/navigation';
|
||||
import React, { DragEvent, useCallback, useEffect, useRef, useState } from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import ReactFlow, { Background, Connection, Controls, ReactFlowProvider, addEdge, useEdgesState, useNodesState, useReactFlow, Node } from 'reactflow';
|
||||
import 'reactflow/dist/style.css';
|
||||
|
||||
const { TextArea } = Input;
|
||||
import { SaveFlowModal, ExportFlowModal, ImportFlowModal } from '@/components/flow/canvas-modal';
|
||||
|
||||
interface Props {
|
||||
// Define your component props here
|
||||
@@ -23,8 +22,7 @@ const edgeTypes = { buttonedge: ButtonEdge };
|
||||
|
||||
const Canvas: React.FC<Props> = () => {
|
||||
const { t } = useTranslation();
|
||||
const [messageApi, contextHolder] = message.useMessage();
|
||||
const [form] = Form.useForm<IFlowUpdateParam>();
|
||||
|
||||
const searchParams = useSearchParams();
|
||||
const id = searchParams?.get('id') || '';
|
||||
const reactFlow = useReactFlow();
|
||||
@@ -33,9 +31,10 @@ const Canvas: React.FC<Props> = () => {
|
||||
const [nodes, setNodes, onNodesChange] = useNodesState([]);
|
||||
const [edges, setEdges, onEdgesChange] = useEdgesState([]);
|
||||
const reactFlowWrapper = useRef<HTMLDivElement>(null);
|
||||
const [isModalVisible, setIsModalVisible] = useState(false);
|
||||
const [flowInfo, setFlowInfo] = useState<IFlowUpdateParam>();
|
||||
const [deploy, setDeploy] = useState(true);
|
||||
const [isSaveFlowModalOpen, setIsSaveFlowModalOpen] = useState(false);
|
||||
const [isExportFlowModalOpen, setIsExportFlowModalOpen] = useState(false);
|
||||
const [isImportModalOpen, setIsImportFlowModalOpen] = useState(false);
|
||||
|
||||
async function getFlowData() {
|
||||
setLoading(true);
|
||||
@@ -139,17 +138,6 @@ const Canvas: React.FC<Props> = () => {
|
||||
event.dataTransfer.dropEffect = 'move';
|
||||
}, []);
|
||||
|
||||
function labelChange(e: React.ChangeEvent<HTMLInputElement>) {
|
||||
const label = e.target.value;
|
||||
// replace spaces with underscores, convert uppercase letters to lowercase, remove characters other than digits, letters, _, and -.
|
||||
let result = label
|
||||
.replace(/\s+/g, '_')
|
||||
.replace(/[^a-z0-9_-]/g, '')
|
||||
.toLowerCase();
|
||||
result = result;
|
||||
form.setFieldsValue({ name: result });
|
||||
}
|
||||
|
||||
function onSave() {
|
||||
const flowData = reactFlow.toObject() as IFlowData;
|
||||
const [check, node, message] = checkFlowDataRequied(flowData);
|
||||
@@ -172,60 +160,16 @@ const Canvas: React.FC<Props> = () => {
|
||||
);
|
||||
return notification.error({ message: 'Error', description: message, icon: <FrownOutlined className="text-red-600" /> });
|
||||
}
|
||||
setIsModalVisible(true);
|
||||
setIsSaveFlowModalOpen(true);
|
||||
}
|
||||
|
||||
// TODO: EXport flow data
|
||||
function onExport() {
|
||||
const flowData = reactFlow.toObject() as IFlowData;
|
||||
const blob = new Blob([JSON.stringify(flowData)], { type: 'text/plain;charset=utf-8' });
|
||||
const url = URL.createObjectURL(blob);
|
||||
const a = document.createElement('a');
|
||||
a.href = url;
|
||||
a.download = 'flow.json';
|
||||
a.click();
|
||||
setIsExportFlowModalOpen(true);
|
||||
}
|
||||
|
||||
// TODO: Import flow data
|
||||
function onImport() {
|
||||
const input = document.createElement('input');
|
||||
input.type = 'file';
|
||||
input.accept = '.json';
|
||||
input.onchange = async (e: any) => {
|
||||
const file = e.target.files[0];
|
||||
const reader = new FileReader();
|
||||
reader.onload = async (event) => {
|
||||
const flowData = JSON.parse(event.target?.result as string) as IFlowData;
|
||||
setNodes(flowData.nodes);
|
||||
setEdges(flowData.edges);
|
||||
};
|
||||
reader.readAsText(file);
|
||||
};
|
||||
input.click;
|
||||
}
|
||||
|
||||
async function handleSaveFlow() {
|
||||
const { name, label, description = '', editable = false, state = 'deployed' } = form.getFieldsValue();
|
||||
console.log(form.getFieldsValue());
|
||||
const reactFlowObject = mapHumpToUnderline(reactFlow.toObject() as IFlowData);
|
||||
|
||||
if (id) {
|
||||
const [, , res] = await apiInterceptors(updateFlowById(id, { name, label, description, editable, uid: id, flow_data: reactFlowObject, state }));
|
||||
setIsModalVisible(false);
|
||||
if (res?.success) {
|
||||
messageApi.success(t('save_flow_success'));
|
||||
} else if (res?.err_msg) {
|
||||
messageApi.error(res?.err_msg);
|
||||
}
|
||||
} else {
|
||||
const [_, res] = await apiInterceptors(addFlow({ name, label, description, editable, flow_data: reactFlowObject, state }));
|
||||
if (res?.uid) {
|
||||
messageApi.success(t('save_flow_success'));
|
||||
const history = window.history;
|
||||
history.pushState(null, '', `/flow/canvas?id=${res.uid}`);
|
||||
}
|
||||
setIsModalVisible(false);
|
||||
}
|
||||
setIsImportFlowModalOpen(true);
|
||||
}
|
||||
|
||||
return (
|
||||
@@ -269,86 +213,27 @@ const Canvas: React.FC<Props> = () => {
|
||||
<AddNodes />
|
||||
</ReactFlow>
|
||||
</div>
|
||||
|
||||
<Modal
|
||||
title={t('flow_modal_title')}
|
||||
open={isModalVisible}
|
||||
onCancel={() => {
|
||||
setIsModalVisible(false);
|
||||
}}
|
||||
cancelButtonProps={{ className: 'hidden' }}
|
||||
okButtonProps={{ className: 'hidden' }}
|
||||
>
|
||||
<Form
|
||||
name="flow_form"
|
||||
form={form}
|
||||
labelCol={{ span: 8 }}
|
||||
wrapperCol={{ span: 16 }}
|
||||
style={{ maxWidth: 600 }}
|
||||
initialValues={{ remember: true }}
|
||||
onFinish={handleSaveFlow}
|
||||
autoComplete="off"
|
||||
>
|
||||
<Form.Item label="Title" name="label" initialValue={flowInfo?.label} rules={[{ required: true, message: 'Please input flow title!' }]}>
|
||||
<Input onChange={labelChange} />
|
||||
</Form.Item>
|
||||
<Form.Item
|
||||
label="Name"
|
||||
name="name"
|
||||
initialValue={flowInfo?.name}
|
||||
rules={[
|
||||
{ required: true, message: 'Please input flow name!' },
|
||||
() => ({
|
||||
validator(_, value) {
|
||||
const regex = /^[a-zA-Z0-9_\-]+$/;
|
||||
if (!regex.test(value)) {
|
||||
return Promise.reject('Can only contain numbers, letters, underscores, and dashes');
|
||||
}
|
||||
return Promise.resolve();
|
||||
},
|
||||
}),
|
||||
]}
|
||||
>
|
||||
<Input />
|
||||
</Form.Item>
|
||||
<Form.Item label="Description" initialValue={flowInfo?.description} name="description">
|
||||
<TextArea rows={3} />
|
||||
</Form.Item>
|
||||
<Form.Item label="Editable" name="editable" initialValue={flowInfo?.editable} valuePropName="checked">
|
||||
<Checkbox />
|
||||
</Form.Item>
|
||||
<Form.Item hidden name="state">
|
||||
<Input />
|
||||
</Form.Item>
|
||||
<Form.Item label="Deploy">
|
||||
<Checkbox
|
||||
defaultChecked={flowInfo?.state === 'deployed' || flowInfo?.state === 'running'}
|
||||
value={deploy}
|
||||
onChange={(e) => {
|
||||
const val = e.target.checked;
|
||||
form.setFieldValue('state', val ? 'deployed' : 'developing');
|
||||
setDeploy(val);
|
||||
}}
|
||||
/>
|
||||
</Form.Item>
|
||||
<Form.Item wrapperCol={{ offset: 8, span: 16 }}>
|
||||
<Space>
|
||||
<Button
|
||||
htmlType="button"
|
||||
onClick={() => {
|
||||
setIsModalVisible(false);
|
||||
}}
|
||||
>
|
||||
Cancel
|
||||
</Button>
|
||||
<Button type="primary" htmlType="submit">
|
||||
Submit
|
||||
</Button>
|
||||
</Space>
|
||||
</Form.Item>
|
||||
</Form>
|
||||
</Modal>
|
||||
{contextHolder}
|
||||
|
||||
<SaveFlowModal
|
||||
reactFlow={reactFlow}
|
||||
flowInfo={flowInfo}
|
||||
isSaveFlowModalOpen={isSaveFlowModalOpen}
|
||||
setIsSaveFlowModalOpen={setIsSaveFlowModalOpen}
|
||||
/>
|
||||
|
||||
<ExportFlowModal
|
||||
reactFlow={reactFlow}
|
||||
flowInfo={flowInfo}
|
||||
isExportFlowModalOpen={isExportFlowModalOpen}
|
||||
setIsExportFlowModalOpen={setIsExportFlowModalOpen}
|
||||
/>
|
||||
|
||||
<ImportFlowModal
|
||||
setNodes={setNodes}
|
||||
setEdges={setEdges}
|
||||
isImportModalOpen={isImportModalOpen}
|
||||
setIsImportFlowModalOpen={setIsImportFlowModalOpen}
|
||||
/>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
@@ -172,6 +172,7 @@ export type IFlowData = {
|
||||
};
|
||||
|
||||
export type IFlowExportParams = {
|
||||
uid: string;
|
||||
export_type?: 'json' | 'dbgpts';
|
||||
format?: 'json' | 'file';
|
||||
file_name?: string;
|
||||
|
Reference in New Issue
Block a user