Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: Update AddFlowVariableModal component to include parameter management #1963

Merged
merged 4 commits into from
Sep 4, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
13 changes: 11 additions & 2 deletions web/client/api/flow/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import {
IFlowRefreshParams,
IFlowResponse,
IFlowUpdateParam,
IFlowVariablesParams,
IUploadFileRequestParams,
IUploadFileResponse,
} from '@/types/flow';
Expand Down Expand Up @@ -63,14 +64,22 @@ export const downloadFile = (fileId: string) => {
return GET<null, any>(`/api/v2/serve/file/files/dbgpt/${fileId}`);
};

// TODO:wait for interface update
export const getFlowTemplateList = () => {
return GET<null, Array<any>>('/api/v2/serve/awel/flow/templates');
};

export const getFlowTemplateById = (id: string) => {
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) => {
return POST<IUploadFileRequestParams, Array<IUploadFileResponse>>('/api/v2/serve/file/files/metadata/batch', data);
};
};
218 changes: 166 additions & 52 deletions web/components/flow/canvas-modal/add-flow-variable-modal.tsx
Original file line number Diff line number Diff line change
@@ -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 { Button, Form, Input, Modal, Select, Space } from 'antd';
import React, { useState } from 'react';
import { Button, Cascader, Form, Input, Modal, Select, Space } from 'antd';
import { uniqBy } from 'lodash';
import React, { useEffect, useState } from 'react';
import { useTranslation } from 'react-i18next';

// ype GroupType = { category: string; categoryLabel: string; nodes: IFlowNode[] };
const { Option } = Select;

type ValueType = 'str' | 'int' | 'float' | 'bool' | 'ref';
interface Option {
value?: string | number | null;
label: React.ReactNode;
children?: Option[];
isLeaf?: boolean;
}

const { Option } = Select;
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_SCOPE = 'flow_priv';

function escapeVariable(value: string, enableEscape: boolean): string {
if (!enableEscape) {
return value;
}
return value.replace(/@/g, '\\@').replace(/#/g, '\\#').replace(/%/g, '\\%').replace(/:/g, '\\:');
}

function buildVariableString(variableDict) {
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 || '',
};

// Check for special characters in values
for (const [key, value] of Object.entries(newVariableDict)) {
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(', ')}`,
);
}
}
}

const { key, name, scope, scope_key, sys_code, user_name } = newVariableDict;

let variableStr = `${key}`;

if (name) {
variableStr += `${kvSig}${name}`;
}

if (scope) {
variableStr += `${scopeSig}${scope}`;
if (scope_key) {
variableStr += `${kvSig}${scope_key}`;
}
}

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 [operators, setOperators] = useState<Array<IFlowNode>>([]);
// const [resources, setResources] = useState<Array<IFlowNode>>([]);
// const [operatorsGroup, setOperatorsGroup] = useState<GroupType[]>([]);
// const [resourcesGroup, setResourcesGroup] = useState<GroupType[]>([]);
const [isModalOpen, setIsModalOpen] = useState(false);
const [form] = Form.useForm(); // const [form] = Form.useForm<IFlowUpdateParam>();
const [form] = Form.useForm();
const [controlTypes, setControlTypes] = useState<ValueType[]>(['str']);
const [refVariableOptions, setRefVariableOptions] = useState<Option[]>([]);

const showModal = () => {
setIsModalOpen(true);
};
useEffect(() => {
getKeysData();
}, []);

const getKeysData = async () => {
const [err, res] = await apiInterceptors(getKeys());

if (err) return;

// TODO: get keys
// useEffect(() => {
// getNodes();
// }, []);

// async function getNodes() {
// 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[]) {
// const groups: GroupType[] = [];
// const categoryMap: Record<string, { category: string; categoryLabel: string; nodes: IFlowNode[] }> = {};
// data.forEach(item => {
// const { category, category_label } = item;
// if (!categoryMap[category]) {
// categoryMap[category] = { category, categoryLabel: category_label, nodes: [] };
// groups.push(categoryMap[category]);
// }
// categoryMap[category].nodes.push(item);
// });
// return groups;
// }
const keyOptions = res?.map(({ key, label, scope }: IVariableInfo) => ({
value: key,
label,
scope,
isLeaf: false,
}));

setRefVariableOptions(keyOptions);
};

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) {
Expand Down Expand Up @@ -95,13 +159,47 @@ export const AddFlowVariableModal: React.FC = () => {
}

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 param = parameters?.[index];

if (param) {
const { name = '' } = param;
param.value = `${DAG_PARAM_KEY}:${name}@scope:${DAG_PARAM_SCOPE}`;
param.value = variableStr;
param.category = selectedVariableData?.data?.category;
param.value_type = selectedVariableData?.data?.value_type;

form.setFieldsValue({
parameters: [...parameters],
Expand All @@ -117,14 +215,15 @@ export const AddFlowVariableModal: React.FC = () => {
className='flex items-center justify-center rounded-full left-4 top-4'
style={{ zIndex: 1050 }}
icon={<PlusOutlined />}
onClick={showModal}
onClick={() => setIsModalOpen(true)}
/>

<Modal
title={t('Add_Global_Variable_of_Flow')}
open={isModalOpen}
footer={null}
width={1000}
onCancel={() => setIsModalOpen(false)}
styles={{
body: {
maxHeight: '70vh',
Expand All @@ -134,7 +233,6 @@ export const AddFlowVariableModal: React.FC = () => {
borderRadius: 4,
},
}}
onClose={() => setIsModalOpen(false)}
>
<Form
name='dynamic_form_nest_item'
Expand Down Expand Up @@ -199,13 +297,29 @@ export const AddFlowVariableModal: React.FC = () => {
style={{ width: 320 }}
rules={[{ required: true, message: 'Missing parameter value' }]}
>
<Input placeholder='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' />
)}
</Form.Item>

<Form.Item {...restField} name={[name, 'description']} label='描述' style={{ width: 170 }}>
<Input placeholder='Parameter Description' />
</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)} />
</Space>
))}
Expand Down
2 changes: 1 addition & 1 deletion web/components/flow/canvas-modal/import-flow-modal.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -92,7 +92,7 @@ export const ImportFlowModal: React.FC<Props> = ({ isImportModalOpen, setIsImpor
</Upload>
</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 value={true}>{t('Yes')}</Radio>
<Radio value={false}>{t('No')}</Radio>
Expand Down
4 changes: 4 additions & 0 deletions web/components/flow/canvas-modal/save-flow-modal.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,8 @@ export const SaveFlowModal: React.FC<Props> = ({
async function onSaveFlow() {
const { name, label, description = '', editable = false, state = 'deployed' } = form.getFieldsValue();
const reactFlowObject = mapHumpToUnderline(reactFlow.toObject() as IFlowData);
const variableValues = localStorage.getItem('variables');
const variables = JSON.parse(variableValues || '[]');

if (id) {
const [, , res] = await apiInterceptors(
Expand All @@ -58,6 +60,7 @@ export const SaveFlowModal: React.FC<Props> = ({
uid: id.toString(),
flow_data: reactFlowObject,
state,
variables,
}),
);

Expand All @@ -75,6 +78,7 @@ export const SaveFlowModal: React.FC<Props> = ({
editable,
flow_data: reactFlowObject,
state,
variables,
}),
);

Expand Down
21 changes: 21 additions & 0 deletions web/types/flow.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ export type IFlowUpdateParam = {
uid?: string;
flow_data?: IFlowData;
state?: FlowState;
variables?: IVariableInfo[];
};

export type IFlowRefreshParams = {
Expand Down Expand Up @@ -200,3 +201,23 @@ export type IUploadFileResponse = {
bucket: 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;
};