mirror of
https://github.com/csunny/DB-GPT.git
synced 2025-08-01 08:11:45 +00:00
feat: Update AddFlowVariableModal component to include parameter management (#1963)
This commit is contained in:
commit
af09d14d4a
@ -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}`);
|
||||||
};
|
};
|
||||||
|
|
||||||
// TODO:wait 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);
|
||||||
};
|
};
|
@ -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>
|
||||||
))}
|
))}
|
||||||
|
@ -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>
|
||||||
|
@ -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,
|
||||||
}),
|
}),
|
||||||
);
|
);
|
||||||
|
|
||||||
|
@ -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;
|
||||||
|
};
|
||||||
|
Loading…
Reference in New Issue
Block a user