diff --git a/.gitignore b/.gitignore index d4d03a8c7..a3714cf8e 100644 --- a/.gitignore +++ b/.gitignore @@ -158,6 +158,7 @@ thirdparty #web # dependencies /web/node_modules +/web/yarn.lock .idea # next.js @@ -184,7 +185,4 @@ thirdparty /examples/**/*.gv /examples/**/*.gv.pdf /i18n/locales/**/**/*_ai_translated.po -/i18n/locales/**/**/*~ - -/web_new/node_modules -/web_new/.next \ No newline at end of file +/i18n/locales/**/**/*~ \ No newline at end of file diff --git a/dbgpt/_private/config.py b/dbgpt/_private/config.py index cd7936a60..10b3f1684 100644 --- a/dbgpt/_private/config.py +++ b/dbgpt/_private/config.py @@ -332,6 +332,17 @@ def __init__(self) -> None: os.getenv("MULTI_INSTANCE", "False").lower() == "true" ) + # file server configuration + # The host of the current file server, if None, get the host automatically + self.FILE_SERVER_HOST = os.getenv("FILE_SERVER_HOST") + self.FILE_SERVER_LOCAL_STORAGE_PATH = os.getenv( + "FILE_SERVER_LOCAL_STORAGE_PATH" + ) + # multi-instance flag + self.WEBSERVER_MULTI_INSTANCE = ( + os.getenv("MULTI_INSTANCE", "False").lower() == "true" + ) + @property def local_db_manager(self) -> "ConnectorManager": from dbgpt.datasource.manages import ConnectorManager diff --git a/web/README.md b/web/README.md index b074b8120..f1db52c60 100644 --- a/web/README.md +++ b/web/README.md @@ -50,7 +50,7 @@ yarn install ### Usage ```sh -cp .env.example .env +cp .env.template .env ``` edit the `API_BASE_URL` to the real address diff --git a/web/client/api/app/index.ts b/web/client/api/app/index.ts index f7fc831d9..a5412c7e4 100644 --- a/web/client/api/app/index.ts +++ b/web/client/api/app/index.ts @@ -1,5 +1,12 @@ -import { AppListResponse, CreateAppParams, IAgent, IApp, NativeAppScenesResponse, StrategyResponse, TeamMode } from '@/types/app'; -import { IFlowResponse } from '@/types/flow'; +import { + AppListResponse, + CreateAppParams, + IAgent, + IApp, + NativeAppScenesResponse, + StrategyResponse, + TeamMode, +} from '@/types/app'; import { GET, POST } from '../index'; @@ -45,7 +52,9 @@ export const getAppStrategy = () => { * 获取资源参数 */ export const getResource = (data: Record) => { - return GET, Record[]>(`/api/v1/app/resources/list?type=${data.type}`); + return GET, Record[]>( + `/api/v1/app/resources/list?type=${data.type}` + ); }; /** * 创建native_app应用 @@ -61,13 +70,7 @@ export const getNativeAppScenes = () => { export const getAppStrategyValues = (type: string) => { return GET(`/api/v1/llm-strategy/value/list?type=${type}`); }; -/** - * 创建awel_layout应用 - * 获取工作流 - */ -export const getFlows = ({ page, page_size }: { page: number; page_size: number }) => { - return GET<{ page: number; page_size: number }, IFlowResponse>(`/api/v1/serve/awel/flows?page=${page}&page_size=${page_size}`); -}; + /** * 查询应用权限 */ @@ -77,6 +80,12 @@ export const getAppAdmins = (appCode: string) => { /** * 更新应用权限 */ -export const updateAppAdmins = (data: { app_code: string; admins: string[] }) => { - return POST<{ app_code: string; admins: string[] }, null>(`/api/v1/app/admins/update`, data); +export const updateAppAdmins = (data: { + app_code: string; + admins: string[]; +}) => { + return POST<{ app_code: string; admins: string[] }, null>( + `/api/v1/app/admins/update`, + data + ); }; diff --git a/web/client/api/flow/index.ts b/web/client/api/flow/index.ts index 9a6d89d0f..96bc73095 100644 --- a/web/client/api/flow/index.ts +++ b/web/client/api/flow/index.ts @@ -1,9 +1,82 @@ -import { IFlow, UpdateFLowAdminsParams } from '@/types/flow'; -import { POST } from '../index'; - -/** - * 更新管理员 - */ -export const updateFlowAdmins = (data: UpdateFLowAdminsParams) => { - return POST(`/api/v1/serve/awel/flow/admins`, data); +import { + IFlow, + IFlowNode, + IFlowResponse, + IFlowUpdateParam, + IFlowRefreshParams, + IFlowExportParams, + IFlowImportParams, + IUploadFileRequestParams, + IUploadFileResponse, +} from '@/types/flow'; +import { DELETE, GET, POST, PUT } from '../index'; + +/** AWEL Flow */ +export const addFlow = (data: IFlowUpdateParam) => { + return POST('/api/v2/serve/awel/flows', data); +}; + +export const getFlows = (page?: number, page_size?: number) => { + return GET('/api/v2/serve/awel/flows', { + page, + page_size, + }); +}; + +export const getFlowById = (id: string) => { + return GET(`/api/v2/serve/awel/flows/${id}`); +}; + +export const updateFlowById = (id: string, data: IFlowUpdateParam) => { + return PUT(`/api/v2/serve/awel/flows/${id}`, data); +}; + +export const deleteFlowById = (id: string) => { + return DELETE(`/api/v2/serve/awel/flows/${id}`); +}; + +export const getFlowNodes = () => { + return GET>(`/api/v2/serve/awel/nodes`); +}; + +export const refreshFlowNodeById = (data: IFlowRefreshParams) => { + return POST( + '/api/v2/serve/awel/nodes/refresh', + data + ); +}; + +export const debugFlow = (data: any) => { + return POST('/api/v2/serve/awel/flow/debug', data); +}; + +export const exportFlow = (data: IFlowExportParams) => { + return GET( + `/api/v2/serve/awel/flow/export/${data.uid}`, + data + ); +}; + +export const importFlow = (data: IFlowImportParams) => { + return POST('/api/v2/serve/awel/flow/import', data); +}; + +export const uploadFile = (data: IUploadFileRequestParams) => { + return POST>( + '/api/v2/serve/file/files/dbgpt', + data + ); +}; + +export const downloadFile = (fileId: string) => { + return GET(`/api/v2/serve/file/files/dbgpt/${fileId}`); +}; + +// TODO:wait for interface update +export const getFlowTemplateList = () => { + return GET>('/api/v2/serve/awel/flow/templates'); +}; + +export const getFlowTemplateById = (id: string) => { + return GET(`/api/v2/serve/awel/flow/templates/${id}`); }; diff --git a/web/client/api/request.ts b/web/client/api/request.ts index 706b9862b..ed0235a7b 100644 --- a/web/client/api/request.ts +++ b/web/client/api/request.ts @@ -5,7 +5,7 @@ import { PostAgentPluginResponse, PostAgentQueryParams, } from '@/types/agent'; -import { GetAppInfoParams, IApp, IAgent, IAppData } from '@/types/app'; +import { GetAppInfoParams, IApp } from '@/types/app'; import { ChatHistoryResponse, DialogueListResponse, @@ -17,7 +17,13 @@ import { UserParam, UserParamResponse, } from '@/types/chat'; -import { ChatFeedBackSchema, DbListResponse, DbSupportTypeResponse, PostDbParams, PostDbRefreshParams } from '@/types/db'; +import { + ChatFeedBackSchema, + DbListResponse, + DbSupportTypeResponse, + PostDbParams, + PostDbRefreshParams, +} from '@/types/db'; import { GetEditorSQLRoundRequest, GetEditorySqlParams, @@ -26,7 +32,6 @@ import { PostEditorSQLRunParams, PostSQLEditorSubmitParams, } from '@/types/editor'; -import { IFlow, IFlowNode, IFlowUpdateParam, IFlowResponse } from '@/types/flow'; import { AddKnowledgeParams, ArgumentsParams, @@ -42,17 +47,24 @@ import { ISyncBatchResponse, SpaceConfig, } from '@/types/knowledge'; -import { BaseModelParams, IModelData, StartModelParams, SupportModel } from '@/types/model'; +import { + BaseModelParams, + IModelData, + StartModelParams, + SupportModel, +} from '@/types/model'; import { AxiosRequestConfig } from 'axios'; -import { DELETE, GET, POST, PUT } from '.'; - +import { GET, POST } from '.'; /** App */ export const postScenes = () => { return POST>('/api/v1/chat/dialogue/scenes'); }; export const newDialogue = (data: NewDialogueParam) => { - return POST(`/api/v1/chat/dialogue/new?chat_mode=${data.chat_mode}&model_name=${data.model}`, data); + return POST( + `/api/v1/chat/dialogue/new?chat_mode=${data.chat_mode}&model_name=${data.model}`, + data + ); }; export const addUser = (data: UserParam) => { @@ -90,13 +102,19 @@ export const getUsableModels = () => { return GET>('/api/v1/model/types'); }; export const postChatModeParamsList = (chatMode: string) => { - return POST(`/api/v1/chat/mode/params/list?chat_mode=${chatMode}`); + return POST( + `/api/v1/chat/mode/params/list?chat_mode=${chatMode}` + ); }; export const postChatModeParamsInfoList = (chatMode: string) => { - return POST>(`/api/v1/chat/mode/params/info?chat_mode=${chatMode}`); + return POST>( + `/api/v1/chat/mode/params/info?chat_mode=${chatMode}` + ); }; export const getChatHistory = (convId: string) => { - return GET(`/api/v1/chat/dialogue/messages/history?con_uid=${convId}`); + return GET( + `/api/v1/chat/dialogue/messages/history?con_uid=${convId}` + ); }; export const postChatModeParamsFileLoad = ({ convUid, @@ -123,12 +141,14 @@ export const postChatModeParamsFileLoad = ({ 'Content-Type': 'multipart/form-data', }, ...config, - }, + } ); }; export const clearChatHistory = (conUid: string) => { - return POST>(`/api/v1/chat/dialogue/clear?con_uid=${conUid}`); + return POST>( + `/api/v1/chat/dialogue/clear?con_uid=${conUid}` + ); }; /** Menu */ @@ -138,19 +158,27 @@ export const delDialogue = (conv_uid: string) => { /** Editor */ export const getEditorSqlRounds = (id: string) => { - return GET(`/api/v1/editor/sql/rounds?con_uid=${id}`); + return GET( + `/api/v1/editor/sql/rounds?con_uid=${id}` + ); }; export const postEditorSqlRun = (data: PostEditorSQLRunParams) => { return POST(`/api/v1/editor/sql/run`, data); }; export const postEditorChartRun = (data: PostEditorChartRunParams) => { - return POST(`/api/v1/editor/chart/run`, data); + return POST( + `/api/v1/editor/chart/run`, + data + ); }; export const postSqlEditorSubmit = (data: PostSQLEditorSubmitParams) => { return POST(`/api/v1/sql/editor/submit`, data); }; export const getEditorSql = (id: string, round: string | number) => { - return POST>('/api/v1/editor/sql', { con_uid: id, round }); + return POST>('/api/v1/editor/sql', { + con_uid: id, + round, + }); }; /** knowledge */ @@ -158,21 +186,36 @@ export const getArguments = (knowledgeName: string) => { return POST(`/knowledge/${knowledgeName}/arguments`, {}); }; export const saveArguments = (knowledgeName: string, data: ArgumentsParams) => { - return POST(`/knowledge/${knowledgeName}/argument/save`, data); + return POST( + `/knowledge/${knowledgeName}/argument/save`, + data + ); }; export const getSpaceList = (data: any) => { return POST>('/knowledge/space/list', data); }; -export const getDocumentList = (spaceName: string, data: Record>) => { - return POST>, IDocumentResponse>(`/knowledge/${spaceName}/document/list`, data); +export const getDocumentList = ( + spaceName: string, + data: Record> +) => { + return POST>, IDocumentResponse>( + `/knowledge/${spaceName}/document/list`, + data + ); }; export const getGraphVis = (spaceName: string, data: { limit: number }) => { - return POST, GraphVisResult>(`/knowledge/${spaceName}/graphvis`, data); + return POST, GraphVisResult>( + `/knowledge/${spaceName}/graphvis`, + data + ); }; export const addDocument = (knowledgeName: string, data: DocumentParams) => { - return POST(`/knowledge/${knowledgeName}/document/add`, data); + return POST( + `/knowledge/${knowledgeName}/document/add`, + data + ); }; export const addSpace = (data: AddKnowledgeParams) => { @@ -180,27 +223,53 @@ export const addSpace = (data: AddKnowledgeParams) => { }; export const getChunkStrategies = () => { - return GET>('/knowledge/document/chunkstrategies'); + return GET>( + '/knowledge/document/chunkstrategies' + ); }; -export const syncDocument = (spaceName: string, data: Record>) => { - return POST>, string | null>(`/knowledge/${spaceName}/document/sync`, data); +export const syncDocument = ( + spaceName: string, + data: Record> +) => { + return POST>, string | null>( + `/knowledge/${spaceName}/document/sync`, + data + ); }; -export const syncBatchDocument = (spaceName: string, data: Array) => { - return POST, ISyncBatchResponse>(`/knowledge/${spaceName}/document/sync_batch`, data); +export const syncBatchDocument = ( + spaceName: string, + data: Array +) => { + return POST, ISyncBatchResponse>( + `/knowledge/${spaceName}/document/sync_batch`, + data + ); }; export const uploadDocument = (knowLedgeName: string, data: FormData) => { - return POST(`/knowledge/${knowLedgeName}/document/upload`, data); + return POST( + `/knowledge/${knowLedgeName}/document/upload`, + data + ); }; export const getChunkList = (spaceName: string, data: ChunkListParams) => { - return POST(`/knowledge/${spaceName}/chunk/list`, data); + return POST( + `/knowledge/${spaceName}/chunk/list`, + data + ); }; -export const delDocument = (spaceName: string, data: Record) => { - return POST, null>(`/knowledge/${spaceName}/document/delete`, data); +export const delDocument = ( + spaceName: string, + data: Record +) => { + return POST, null>( + `/knowledge/${spaceName}/document/delete`, + data + ); }; export const delSpace = (data: Record) => { @@ -226,21 +295,41 @@ export const getSupportModels = () => { /** Agent */ export const postAgentQuery = (data: PostAgentQueryParams) => { - return POST('/api/v1/agent/query', data); + return POST( + '/api/v1/agent/query', + data + ); }; export const postAgentHubUpdate = (data?: PostAgentHubUpdateParams) => { - return POST('/api/v1/agent/hub/update', data ?? { channel: '', url: '', branch: '', authorization: '' }); + return POST( + '/api/v1/agent/hub/update', + data ?? { channel: '', url: '', branch: '', authorization: '' } + ); }; export const postAgentMy = (user?: string) => { - return POST('/api/v1/agent/my', undefined, { params: { user } }); + return POST( + '/api/v1/agent/my', + undefined, + { params: { user } } + ); }; export const postAgentInstall = (pluginName: string, user?: string) => { - return POST('/api/v1/agent/install', undefined, { params: { plugin_name: pluginName, user }, timeout: 60000 }); + return POST('/api/v1/agent/install', undefined, { + params: { plugin_name: pluginName, user }, + timeout: 60000, + }); }; export const postAgentUninstall = (pluginName: string, user?: string) => { - return POST('/api/v1/agent/uninstall', undefined, { params: { plugin_name: pluginName, user }, timeout: 60000 }); + return POST('/api/v1/agent/uninstall', undefined, { + params: { plugin_name: pluginName, user }, + timeout: 60000, + }); }; -export const postAgentUpload = (user = '', data: FormData, config?: Omit) => { +export const postAgentUpload = ( + user = '', + data: FormData, + config?: Omit +) => { return POST('/api/v1/personal/agent/upload', data, { params: { user }, headers: { @@ -258,9 +347,18 @@ export const getChatFeedBackSelect = () => { return GET(`/api/v1/feedback/select`, undefined); }; export const getChatFeedBackItme = (conv_uid: string, conv_index: number) => { - return GET>(`/api/v1/feedback/find?conv_uid=${conv_uid}&conv_index=${conv_index}`, undefined); + return GET>( + `/api/v1/feedback/find?conv_uid=${conv_uid}&conv_index=${conv_index}`, + undefined + ); }; -export const postChatFeedBackForm = ({ data, config }: { data: ChatFeedBackSchema; config?: Omit }) => { +export const postChatFeedBackForm = ({ + data, + config, +}: { + data: ChatFeedBackSchema; + config?: Omit; +}) => { return POST(`/api/v1/feedback/commit`, data, { headers: { 'Content-Type': 'application/json', @@ -271,27 +369,6 @@ export const postChatFeedBackForm = ({ data, config }: { data: ChatFeedBackSchem /** prompt */ -/** AWEL Flow */ -export const addFlow = (data: IFlowUpdateParam) => { - return POST('/api/v1/serve/awel/flows', data); -}; - -export const getFlowById = (id: string) => { - return GET(`/api/v1/serve/awel/flows/${id}`); -}; - -export const updateFlowById = (id: string, data: IFlowUpdateParam) => { - return PUT(`/api/v1/serve/awel/flows/${id}`, data); -}; - -export const deleteFlowById = (id: string) => { - return DELETE(`/api/v1/serve/awel/flows/${id}`); -}; - -export const getFlowNodes = () => { - return GET>(`/api/v1/serve/awel/nodes`); -}; - /** app */ export const collectApp = (data: Record) => { @@ -322,7 +399,9 @@ export const getAppInfo = (data: GetAppInfoParams) => { }; export const getSupportDBList = (db_name = '') => { - return GET>(`/api/v1/permission/db/list?db_name=${db_name}`); + return GET>( + `/api/v1/permission/db/list?db_name=${db_name}` + ); }; export const recommendApps = (data: Record) => { @@ -336,7 +415,9 @@ export const modelSearch = (data: Record) => { }; export const getKnowledgeAdmins = (spaceId: string) => { - return GET>(`/knowledge/users/list?space_id=${spaceId}`); + return GET>( + `/knowledge/users/list?space_id=${spaceId}` + ); }; export const updateKnowledgeAdmins = (data: Record) => { return POST, any[]>(`/knowledge/users/update`, data); diff --git a/web/components/chat/completion.tsx b/web/components/chat/completion.tsx index 915625536..23ca57959 100644 --- a/web/components/chat/completion.tsx +++ b/web/components/chat/completion.tsx @@ -184,7 +184,7 @@ const Completion = ({ messages, onSubmit }: Props) => { question={showMessages?.filter((e) => e?.role === 'human' && e?.order === content.order)[0]?.context} knowledge_space={spaceNameOriginal || dbParam || ''} /> - + + /> ); }; diff --git a/web/components/flow/canvas-modal/export-flow-modal.tsx b/web/components/flow/canvas-modal/export-flow-modal.tsx new file mode 100644 index 000000000..8a14ad267 --- /dev/null +++ b/web/components/flow/canvas-modal/export-flow-modal.tsx @@ -0,0 +1,104 @@ +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; + flowInfo?: IFlowUpdateParam; + isExportFlowModalOpen: boolean; + setIsExportFlowModalOpen: (value: boolean) => void; +}; + +export const ExportFlowModal: React.FC = ({ + reactFlow, + flowInfo, + isExportFlowModalOpen, + setIsExportFlowModalOpen, +}) => { + const { t } = useTranslation(); + const [form] = Form.useForm(); + const [messageApi, contextHolder] = message.useMessage(); + + const onFlowExport = async (values: any) => { + + if (values.format === 'json') { + 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(); + }else{ + const linkUrl = `${process.env.API_BASE_URL}/api/v2/serve/awel/flow/export/${values.uid}?export_type=${values.export_type}&format=${values.format}` + window.open(linkUrl) + } + messageApi.success(t('Export_Flow_Success')); + + setIsExportFlowModalOpen(false); + }; + + return ( + <> + setIsExportFlowModalOpen(false)} + cancelButtonProps={{ className: 'hidden' }} + okButtonProps={{ className: 'hidden' }} + > +
+ + + JSON + DBGPTS + + + + + + File + JSON + + + + + + + + + + + +
+
+ + {contextHolder} + + ); +}; \ No newline at end of file diff --git a/web/components/flow/canvas-modal/import-flow-modal.tsx b/web/components/flow/canvas-modal/import-flow-modal.tsx new file mode 100644 index 000000000..8c771e374 --- /dev/null +++ b/web/components/flow/canvas-modal/import-flow-modal.tsx @@ -0,0 +1,113 @@ +import { + Modal, + Form, + Button, + message, + Upload, + UploadFile, + UploadProps, + GetProp, + Radio, + Space, +} from 'antd'; +import { apiInterceptors, importFlow } from '@/client/api'; +import { Node, Edge } from 'reactflow'; +import { UploadOutlined } from '@ant-design/icons'; +import { useTranslation } from 'react-i18next'; +import { useState } from 'react'; + +type Props = { + isImportModalOpen: boolean; + setNodes: React.Dispatch< + React.SetStateAction[]> + >; + setEdges: React.Dispatch[]>>; + setIsImportFlowModalOpen: (value: boolean) => void; +}; +type FileType = Parameters>[0]; + +export const ImportFlowModal: React.FC = ({ + setNodes, + setEdges, + isImportModalOpen, + setIsImportFlowModalOpen, +}) => { + const { t } = useTranslation(); + const [form] = Form.useForm(); + const [messageApi, contextHolder] = message.useMessage(); + const [fileList, setFileList] = useState([]); + + const onFlowImport = async (values: any) => { + values.file = values.file?.[0]; + + const formData: any = new FormData(); + fileList.forEach((file) => { + formData.append('file', file as FileType); + }); + const [, , res] = await apiInterceptors(importFlow(formData)); + + if (res?.success) { + messageApi.success(t('Export_Flow_Success')); + } else if (res?.err_msg) { + messageApi.error(res?.err_msg); + } + + setIsImportFlowModalOpen(false); + }; + + return ( + <> + setIsImportFlowModalOpen(false)} + cancelButtonProps={{ className: 'hidden' }} + okButtonProps={{ className: 'hidden' }} + > +
+ (Array.isArray(e) ? e : e && e.fileList)} + rules={[{ required: true, message: 'Please upload a file' }]} + > + false} maxCount={1}> + + + + + + + {t('Yes')} + {t('No')} + + + + + + + + + +
+
+ + {contextHolder} + + ); +}; diff --git a/web/components/flow/canvas-modal/index.ts b/web/components/flow/canvas-modal/index.ts new file mode 100644 index 000000000..e131e7b65 --- /dev/null +++ b/web/components/flow/canvas-modal/index.ts @@ -0,0 +1,3 @@ +export * from './save-flow-modal'; +export * from './export-flow-modal'; +export * from './import-flow-modal'; \ No newline at end of file diff --git a/web/components/flow/canvas-modal/save-flow-modal.tsx b/web/components/flow/canvas-modal/save-flow-modal.tsx new file mode 100644 index 000000000..871ddfaf1 --- /dev/null +++ b/web/components/flow/canvas-modal/save-flow-modal.tsx @@ -0,0 +1,203 @@ +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; + flowInfo?: IFlowUpdateParam; + isSaveFlowModalOpen: boolean; + setIsSaveFlowModalOpen: (value: boolean) => void; +}; + +export const SaveFlowModal: React.FC = ({ + reactFlow, + isSaveFlowModalOpen, + flowInfo, + setIsSaveFlowModalOpen, +}) => { + const [deploy, setDeploy] = useState(true); + const { t } = useTranslation(); + const searchParams = useSearchParams(); + const id = searchParams?.get('id') || ''; + const [form] = Form.useForm(); + const [messageApi, contextHolder] = message.useMessage(); + + function onLabelChange(e: React.ChangeEvent) { + 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 ( + <> + { + setIsSaveFlowModalOpen(false); + }} + cancelButtonProps={{ className: 'hidden' }} + okButtonProps={{ className: 'hidden' }} + > +
+ + + + + ({ + 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(); + }, + }), + ]} + > + + + + +