diff --git a/web/client/api/flow/index.ts b/web/client/api/flow/index.ts index b7f6bd381..275aa83c2 100644 --- a/web/client/api/flow/index.ts +++ b/web/client/api/flow/index.ts @@ -6,6 +6,7 @@ import { IFlowRefreshParams, IFlowResponse, IFlowUpdateParam, + IFlowVariablesParams, IUploadFileRequestParams, IUploadFileResponse, } from '@/types/flow'; @@ -63,7 +64,6 @@ 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'); }; @@ -71,6 +71,15 @@ export const getFlowTemplateList = () => { export const getFlowTemplateById = (id: string) => { return GET(`/api/v2/serve/awel/flow/templates/${id}`); }; + +export const getKeys = () => { + return GET>('/api/v2/serve/awel/variables/keys'); +}; + +export const getVariablesByKey = ({ key, scope }: { key: string; scope: string }) => { + return GET('/api/v2/serve/awel/variables', { key, scope }); +}; + export const metadataBatch = (data: IUploadFileRequestParams) => { return POST>('/api/v2/serve/file/files/metadata/batch', data); -}; \ No newline at end of file +}; diff --git a/web/components/flow/canvas-modal/add-flow-variable-modal.tsx b/web/components/flow/canvas-modal/add-flow-variable-modal.tsx index 961ec76f7..de855d175 100644 --- a/web/components/flow/canvas-modal/add-flow-variable-modal.tsx +++ b/web/components/flow/canvas-modal/add-flow-variable-modal.tsx @@ -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>([]); - // const [resources, setResources] = useState>([]); - // const [operatorsGroup, setOperatorsGroup] = useState([]); - // const [resourcesGroup, setResourcesGroup] = useState([]); const [isModalOpen, setIsModalOpen] = useState(false); - const [form] = Form.useForm(); // const [form] = Form.useForm(); + const [form] = Form.useForm(); + const [controlTypes, setControlTypes] = useState(['str']); + const [refVariableOptions, setRefVariableOptions] = useState([]); - 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 = {}; - // 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, index: number) { @@ -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], @@ -117,7 +215,7 @@ export const AddFlowVariableModal: React.FC = () => { className='flex items-center justify-center rounded-full left-4 top-4' style={{ zIndex: 1050 }} icon={} - onClick={showModal} + onClick={() => setIsModalOpen(true)} /> { open={isModalOpen} footer={null} width={1000} + onCancel={() => setIsModalOpen(false)} styles={{ body: { maxHeight: '70vh', @@ -134,7 +233,6 @@ export const AddFlowVariableModal: React.FC = () => { borderRadius: 4, }, }} - onClose={() => setIsModalOpen(false)} >
{ style={{ width: 320 }} rules={[{ required: true, message: 'Missing parameter value' }]} > - + {controlTypes[index] === 'ref' ? ( + onRefTypeValueChange(value, selectedOptions, index)} + // displayRender={displayRender} + // dropdownRender={dropdownRender} + changeOnSelect + /> + ) : ( + + )} +