feat: Update AddFlowVariableModal component to include parameter management (#1963)

This commit is contained in:
Dreammy23 2024-09-04 18:51:23 +08:00 committed by GitHub
commit af09d14d4a
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
5 changed files with 204 additions and 56 deletions

View File

@ -6,6 +6,7 @@ import {
IFlowRefreshParams, IFlowRefreshParams,
IFlowResponse, IFlowResponse,
IFlowUpdateParam, IFlowUpdateParam,
IFlowVariablesParams,
IUploadFileRequestParams, IUploadFileRequestParams,
IUploadFileResponse, IUploadFileResponse,
} from '@/types/flow'; } from '@/types/flow';
@ -63,7 +64,6 @@ export const downloadFile = (fileId: string) => {
return GET<null, any>(`/api/v2/serve/file/files/dbgpt/${fileId}`); return GET<null, any>(`/api/v2/serve/file/files/dbgpt/${fileId}`);
}; };
// TODOwait for interface update
export const getFlowTemplateList = () => { export const getFlowTemplateList = () => {
return GET<null, Array<any>>('/api/v2/serve/awel/flow/templates'); return GET<null, Array<any>>('/api/v2/serve/awel/flow/templates');
}; };
@ -71,6 +71,15 @@ export const getFlowTemplateList = () => {
export const getFlowTemplateById = (id: string) => { export const getFlowTemplateById = (id: string) => {
return GET<null, any>(`/api/v2/serve/awel/flow/templates/${id}`); return GET<null, any>(`/api/v2/serve/awel/flow/templates/${id}`);
}; };
export const getKeys = () => {
return GET<null, Array<any>>('/api/v2/serve/awel/variables/keys');
};
export const getVariablesByKey = ({ key, scope }: { key: string; scope: string }) => {
return GET<IFlowVariablesParams, any>('/api/v2/serve/awel/variables', { key, scope });
};
export const metadataBatch = (data: IUploadFileRequestParams) => { export const metadataBatch = (data: IUploadFileRequestParams) => {
return POST<IUploadFileRequestParams, Array<IUploadFileResponse>>('/api/v2/serve/file/files/metadata/batch', data); return POST<IUploadFileRequestParams, Array<IUploadFileResponse>>('/api/v2/serve/file/files/metadata/batch', data);
}; };

View File

@ -1,64 +1,128 @@
// import { IFlowNode } from '@/types/flow'; import { apiInterceptors, getKeys, getVariablesByKey } from '@/client/api';
import { IVariableInfo } from '@/types/flow';
import { MinusCircleOutlined, PlusOutlined } from '@ant-design/icons'; import { MinusCircleOutlined, PlusOutlined } from '@ant-design/icons';
import { Button, Form, Input, Modal, Select, Space } from 'antd'; import { Button, Cascader, Form, Input, Modal, Select, Space } from 'antd';
import React, { useState } from 'react'; import { uniqBy } from 'lodash';
import React, { useEffect, useState } from 'react';
import { useTranslation } from 'react-i18next'; import { useTranslation } from 'react-i18next';
// ype GroupType = { category: string; categoryLabel: string; nodes: IFlowNode[] };
type ValueType = 'str' | 'int' | 'float' | 'bool' | 'ref';
const { Option } = Select; const { Option } = Select;
type ValueType = 'str' | 'int' | 'float' | 'bool' | 'ref';
interface Option {
value?: string | number | null;
label: React.ReactNode;
children?: Option[];
isLeaf?: boolean;
}
interface VariableDict {
key: string;
name?: string;
scope?: string;
scope_key?: string;
sys_code?: string;
user_name?: string;
}
const DAG_PARAM_KEY = 'dbgpt.core.flow.params'; const DAG_PARAM_KEY = 'dbgpt.core.flow.params';
const DAG_PARAM_SCOPE = 'flow_priv'; const DAG_PARAM_SCOPE = 'flow_priv';
export const AddFlowVariableModal: React.FC = () => { function escapeVariable(value: string, enableEscape: boolean): string {
const { t } = useTranslation(); if (!enableEscape) {
// const [operators, setOperators] = useState<Array<IFlowNode>>([]); return value;
// const [resources, setResources] = useState<Array<IFlowNode>>([]); }
// const [operatorsGroup, setOperatorsGroup] = useState<GroupType[]>([]); return value.replace(/@/g, '\\@').replace(/#/g, '\\#').replace(/%/g, '\\%').replace(/:/g, '\\:');
// const [resourcesGroup, setResourcesGroup] = useState<GroupType[]>([]); }
const [isModalOpen, setIsModalOpen] = useState(false);
const [form] = Form.useForm(); // const [form] = Form.useForm<IFlowUpdateParam>();
const showModal = () => { function buildVariableString(variableDict) {
setIsModalOpen(true); const scopeSig = '@';
const sysCodeSig = '#';
const userSig = '%';
const kvSig = ':';
const enableEscape = true;
const specialChars = new Set([scopeSig, sysCodeSig, userSig, kvSig]);
// Replace undefined or null with ""
const newVariableDict: VariableDict = {
key: variableDict.key || '',
name: variableDict.name || '',
scope: variableDict.scope || '',
scope_key: variableDict.scope_key || '',
sys_code: variableDict.sys_code || '',
user_name: variableDict.user_name || '',
}; };
// TODO: get keys // Check for special characters in values
// useEffect(() => { for (const [key, value] of Object.entries(newVariableDict)) {
// getNodes(); if (value && [...specialChars].some(char => value.includes(char))) {
// }, []); if (enableEscape) {
newVariableDict[key as keyof VariableDict] = escapeVariable(value, enableEscape);
} else {
throw new Error(
`${key} contains special characters, error value: ${value}, special characters: ${[...specialChars].join(', ')}`,
);
}
}
}
// async function getNodes() { const { key, name, scope, scope_key, sys_code, user_name } = newVariableDict;
// const [_, data] = await apiInterceptors(getFlowNodes());
// if (data && data.length > 0) {
// localStorage.setItem(FLOW_NODES_KEY, JSON.stringify(data));
// const operatorNodes = data.filter(node => node.flow_type === 'operator');
// const resourceNodes = data.filter(node => node.flow_type === 'resource');
// setOperators(operatorNodes);
// setResources(resourceNodes);
// setOperatorsGroup(groupNodes(operatorNodes));
// setResourcesGroup(groupNodes(resourceNodes));
// }
// }
// function groupNodes(data: IFlowNode[]) { let variableStr = `${key}`;
// const groups: GroupType[] = [];
// const categoryMap: Record<string, { category: string; categoryLabel: string; nodes: IFlowNode[] }> = {}; if (name) {
// data.forEach(item => { variableStr += `${kvSig}${name}`;
// const { category, category_label } = item; }
// if (!categoryMap[category]) {
// categoryMap[category] = { category, categoryLabel: category_label, nodes: [] }; if (scope) {
// groups.push(categoryMap[category]); variableStr += `${scopeSig}${scope}`;
// } if (scope_key) {
// categoryMap[category].nodes.push(item); variableStr += `${kvSig}${scope_key}`;
// }); }
// return groups; }
// }
if (sys_code) {
variableStr += `${sysCodeSig}${sys_code}`;
}
if (user_name) {
variableStr += `${userSig}${user_name}`;
}
return `\${${variableStr}}`;
}
export const AddFlowVariableModal: React.FC = () => {
const { t } = useTranslation();
const [isModalOpen, setIsModalOpen] = useState(false);
const [form] = Form.useForm();
const [controlTypes, setControlTypes] = useState<ValueType[]>(['str']);
const [refVariableOptions, setRefVariableOptions] = useState<Option[]>([]);
useEffect(() => {
getKeysData();
}, []);
const getKeysData = async () => {
const [err, res] = await apiInterceptors(getKeys());
if (err) return;
const keyOptions = res?.map(({ key, label, scope }: IVariableInfo) => ({
value: key,
label,
scope,
isLeaf: false,
}));
setRefVariableOptions(keyOptions);
};
const onFinish = (values: any) => { const onFinish = (values: any) => {
console.log('Received values of form:', values); const variables = JSON.stringify(values.parameters);
localStorage.setItem('variables', variables);
setIsModalOpen(false);
}; };
function onNameChange(e: React.ChangeEvent<HTMLInputElement>, index: number) { function onNameChange(e: React.ChangeEvent<HTMLInputElement>, index: number) {
@ -95,13 +159,47 @@ export const AddFlowVariableModal: React.FC = () => {
} }
function onValueTypeChange(type: ValueType, index: number) { function onValueTypeChange(type: ValueType, index: number) {
if (type === 'ref') { const newControlTypes = [...controlTypes];
newControlTypes[index] = type;
setControlTypes(newControlTypes);
}
function loadData(selectedOptions: Option[]) {
const targetOption = selectedOptions[selectedOptions.length - 1];
const { value, scope } = targetOption as Option & { scope: string };
setTimeout(async () => {
const [err, res] = await apiInterceptors(getVariablesByKey({ key: value as string, scope }));
if (err) return;
if (res?.total_count === 0) {
targetOption.isLeaf = true;
return;
}
const uniqueItems = uniqBy(res?.items, 'name');
targetOption.children = uniqueItems?.map(item => ({
value: item?.name,
label: item.label,
data: item,
}));
setRefVariableOptions([...refVariableOptions]);
}, 1000);
}
function onRefTypeValueChange(value: string[], selectedOptions: Option[], index: number) {
// 选择两个select后获取到的value才能设置引用变量的值
if (value?.length === 2) {
const [selectRefKey, selectedRefVariable] = selectedOptions;
const selectedVariableData = selectRefKey?.children?.find(({ value }) => value === selectedRefVariable?.value);
const variableStr = buildVariableString(selectedVariableData?.data);
const parameters = form.getFieldValue('parameters'); const parameters = form.getFieldValue('parameters');
const param = parameters?.[index]; const param = parameters?.[index];
if (param) { if (param) {
const { name = '' } = param; param.value = variableStr;
param.value = `${DAG_PARAM_KEY}:${name}@scope:${DAG_PARAM_SCOPE}`; param.category = selectedVariableData?.data?.category;
param.value_type = selectedVariableData?.data?.value_type;
form.setFieldsValue({ form.setFieldsValue({
parameters: [...parameters], parameters: [...parameters],
@ -117,7 +215,7 @@ export const AddFlowVariableModal: React.FC = () => {
className='flex items-center justify-center rounded-full left-4 top-4' className='flex items-center justify-center rounded-full left-4 top-4'
style={{ zIndex: 1050 }} style={{ zIndex: 1050 }}
icon={<PlusOutlined />} icon={<PlusOutlined />}
onClick={showModal} onClick={() => setIsModalOpen(true)}
/> />
<Modal <Modal
@ -125,6 +223,7 @@ export const AddFlowVariableModal: React.FC = () => {
open={isModalOpen} open={isModalOpen}
footer={null} footer={null}
width={1000} width={1000}
onCancel={() => setIsModalOpen(false)}
styles={{ styles={{
body: { body: {
maxHeight: '70vh', maxHeight: '70vh',
@ -134,7 +233,6 @@ export const AddFlowVariableModal: React.FC = () => {
borderRadius: 4, borderRadius: 4,
}, },
}} }}
onClose={() => setIsModalOpen(false)}
> >
<Form <Form
name='dynamic_form_nest_item' name='dynamic_form_nest_item'
@ -199,13 +297,29 @@ export const AddFlowVariableModal: React.FC = () => {
style={{ width: 320 }} style={{ width: 320 }}
rules={[{ required: true, message: 'Missing parameter value' }]} rules={[{ required: true, message: 'Missing parameter value' }]}
> >
{controlTypes[index] === 'ref' ? (
<Cascader
placeholder='Select Value'
options={refVariableOptions}
loadData={loadData}
onChange={(value, selectedOptions) => onRefTypeValueChange(value, selectedOptions, index)}
// displayRender={displayRender}
// dropdownRender={dropdownRender}
changeOnSelect
/>
) : (
<Input placeholder='Parameter Value' /> <Input placeholder='Parameter Value' />
)}
</Form.Item> </Form.Item>
<Form.Item {...restField} name={[name, 'description']} label='描述' style={{ width: 170 }}> <Form.Item {...restField} name={[name, 'description']} label='描述' style={{ width: 170 }}>
<Input placeholder='Parameter Description' /> <Input placeholder='Parameter Description' />
</Form.Item> </Form.Item>
<Form.Item name={[name, 'key']} hidden initialValue='dbgpt.core.flow.params' />
<Form.Item name={[name, 'scope']} hidden initialValue='flow_priv' />
<Form.Item name={[name, 'category']} hidden initialValue='common' />
<MinusCircleOutlined onClick={() => remove(name)} /> <MinusCircleOutlined onClick={() => remove(name)} />
</Space> </Space>
))} ))}

View File

@ -92,7 +92,7 @@ export const ImportFlowModal: React.FC<Props> = ({ isImportModalOpen, setIsImpor
</Upload> </Upload>
</Form.Item> </Form.Item>
<Form.Item name='save_flow' label={t('Save_After_Import')}> <Form.Item name='save_flow' label={t('Save_After_Import')} hidden>
<Radio.Group> <Radio.Group>
<Radio value={true}>{t('Yes')}</Radio> <Radio value={true}>{t('Yes')}</Radio>
<Radio value={false}>{t('No')}</Radio> <Radio value={false}>{t('No')}</Radio>

View File

@ -47,6 +47,8 @@ export const SaveFlowModal: React.FC<Props> = ({
async function onSaveFlow() { async function onSaveFlow() {
const { name, label, description = '', editable = false, state = 'deployed' } = form.getFieldsValue(); const { name, label, description = '', editable = false, state = 'deployed' } = form.getFieldsValue();
const reactFlowObject = mapHumpToUnderline(reactFlow.toObject() as IFlowData); const reactFlowObject = mapHumpToUnderline(reactFlow.toObject() as IFlowData);
const variableValues = localStorage.getItem('variables');
const variables = JSON.parse(variableValues || '[]');
if (id) { if (id) {
const [, , res] = await apiInterceptors( const [, , res] = await apiInterceptors(
@ -58,6 +60,7 @@ export const SaveFlowModal: React.FC<Props> = ({
uid: id.toString(), uid: id.toString(),
flow_data: reactFlowObject, flow_data: reactFlowObject,
state, state,
variables,
}), }),
); );
@ -75,6 +78,7 @@ export const SaveFlowModal: React.FC<Props> = ({
editable, editable,
flow_data: reactFlowObject, flow_data: reactFlowObject,
state, state,
variables,
}), }),
); );

View File

@ -12,6 +12,7 @@ export type IFlowUpdateParam = {
uid?: string; uid?: string;
flow_data?: IFlowData; flow_data?: IFlowData;
state?: FlowState; state?: FlowState;
variables?: IVariableInfo[];
}; };
export type IFlowRefreshParams = { export type IFlowRefreshParams = {
@ -200,3 +201,23 @@ export type IUploadFileResponse = {
bucket: string; bucket: string;
uri?: string; uri?: string;
}; };
export type IFlowVariablesParams = {
key: string;
scope: string;
scope_key?: string;
user_name?: string;
sys_code?: string;
page?: number;
page_size?: number;
};
export type IVariableInfo = {
key: string;
label: string;
description: string;
value_type: string;
category: string;
scope: string;
scope_key: string | null;
};