add-flow-variable-modal.tsx 9.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287
  1. import { apiInterceptors, getKeys, getVariablesByKey } from '@/client/api';
  2. import { IFlowUpdateParam, IGetKeysResponseData, IVariableItem } from '@/types/flow';
  3. import { buildVariableString } from '@/utils/flow';
  4. import { MinusCircleOutlined, PlusOutlined } from '@ant-design/icons';
  5. import { Button, Cascader, Form, Input, InputNumber, Modal, Select, Space } from 'antd';
  6. import { DefaultOptionType } from 'antd/es/cascader';
  7. import { uniqBy } from 'lodash';
  8. import React, { useEffect, useState } from 'react';
  9. import { useTranslation } from 'react-i18next';
  10. const { Option } = Select;
  11. const VALUE_TYPES = ['str', 'int', 'float', 'bool', 'ref'] as const;
  12. type ValueType = (typeof VALUE_TYPES)[number];
  13. type Props = {
  14. flowInfo?: IFlowUpdateParam;
  15. setFlowInfo: React.Dispatch<React.SetStateAction<IFlowUpdateParam | undefined>>;
  16. };
  17. export const AddFlowVariableModal: React.FC<Props> = ({ flowInfo, setFlowInfo }) => {
  18. const { t } = useTranslation();
  19. const [isModalOpen, setIsModalOpen] = useState(false);
  20. const [form] = Form.useForm();
  21. const [controlTypes, setControlTypes] = useState<ValueType[]>(['str']);
  22. const [refVariableOptions, setRefVariableOptions] = useState<DefaultOptionType[]>([]);
  23. useEffect(() => {
  24. getKeysData();
  25. }, []);
  26. const getKeysData = async () => {
  27. const [err, res] = await apiInterceptors(getKeys());
  28. if (err) return;
  29. const keyOptions = res?.map(({ key, label, scope }: IGetKeysResponseData) => ({
  30. value: key,
  31. label,
  32. scope,
  33. isLeaf: false,
  34. }));
  35. setRefVariableOptions(keyOptions);
  36. };
  37. const onFinish = (values: any) => {
  38. const newFlowInfo = { ...flowInfo, variables: values?.parameters || [] } as IFlowUpdateParam;
  39. setFlowInfo(newFlowInfo);
  40. setIsModalOpen(false);
  41. };
  42. const onNameChange = (e: React.ChangeEvent<HTMLInputElement>, index: number) => {
  43. const name = e.target.value;
  44. const newValue = name
  45. ?.split('_')
  46. ?.map(word => word.charAt(0).toUpperCase() + word.slice(1))
  47. ?.join(' ');
  48. form.setFields([
  49. {
  50. name: ['parameters', index, 'label'],
  51. value: newValue,
  52. },
  53. ]);
  54. };
  55. const onValueTypeChange = (type: ValueType, index: number) => {
  56. const newControlTypes = [...controlTypes];
  57. newControlTypes[index] = type;
  58. setControlTypes(newControlTypes);
  59. };
  60. const loadData = (selectedOptions: DefaultOptionType[]) => {
  61. const targetOption = selectedOptions[selectedOptions.length - 1];
  62. const { value, scope } = targetOption as DefaultOptionType & { scope: string };
  63. setTimeout(async () => {
  64. const [err, res] = await apiInterceptors(getVariablesByKey({ key: value as string, scope }));
  65. if (err) return;
  66. if (res?.total_count === 0) {
  67. targetOption.isLeaf = true;
  68. return;
  69. }
  70. const uniqueItems = uniqBy(res?.items, 'name');
  71. targetOption.children = uniqueItems?.map(item => ({
  72. value: item?.name,
  73. label: item.label,
  74. item: item,
  75. }));
  76. setRefVariableOptions([...refVariableOptions]);
  77. }, 1000);
  78. };
  79. const onRefTypeValueChange = (
  80. value: (string | number | null)[],
  81. selectedOptions: DefaultOptionType[],
  82. index: number,
  83. ) => {
  84. // when select ref variable, must be select two options(key and variable)
  85. if (value?.length !== 2) return;
  86. const [selectRefKey, selectedRefVariable] = selectedOptions as DefaultOptionType[];
  87. const selectedVariable = selectRefKey?.children?.find(
  88. ({ value }) => value === selectedRefVariable?.value,
  89. ) as DefaultOptionType & { item: IVariableItem };
  90. // build variable string by rule
  91. const variableStr = buildVariableString(selectedVariable?.item);
  92. const parameters = form.getFieldValue('parameters');
  93. const param = parameters?.[index];
  94. if (param) {
  95. param.value = variableStr;
  96. param.category = selectedVariable?.item?.category;
  97. param.value_type = selectedVariable?.item?.value_type;
  98. form.setFieldsValue({
  99. parameters: [...parameters],
  100. });
  101. }
  102. };
  103. // Helper function to render the appropriate control component
  104. const renderVariableValue = (type: string, index: number) => {
  105. switch (type) {
  106. case 'ref':
  107. return (
  108. <Cascader
  109. placeholder='Select Value'
  110. options={refVariableOptions}
  111. loadData={loadData}
  112. onChange={(value, selectedOptions) => onRefTypeValueChange(value, selectedOptions, index)}
  113. changeOnSelect
  114. />
  115. );
  116. case 'str':
  117. return <Input placeholder='Parameter Value' />;
  118. case 'int':
  119. return (
  120. <InputNumber
  121. step={1}
  122. placeholder='Parameter Value'
  123. parser={value => value?.replace(/[^\-?\d]/g, '') || 0}
  124. style={{ width: '100%' }}
  125. />
  126. );
  127. case 'float':
  128. return <InputNumber placeholder='Parameter Value' style={{ width: '100%' }} />;
  129. case 'bool':
  130. return (
  131. <Select placeholder='Select Value'>
  132. <Option value={true}>True</Option>
  133. <Option value={false}>False</Option>
  134. </Select>
  135. );
  136. default:
  137. return <Input placeholder='Parameter Value' />;
  138. }
  139. };
  140. return (
  141. <>
  142. <Button
  143. type='primary'
  144. className='flex items-center justify-center rounded-full left-4 top-4'
  145. style={{ zIndex: 1050 }}
  146. icon={<PlusOutlined />}
  147. onClick={() => setIsModalOpen(true)}
  148. />
  149. <Modal
  150. title={t('Add_Global_Variable_of_Flow')}
  151. width={1000}
  152. open={isModalOpen}
  153. styles={{
  154. body: {
  155. minHeight: '40vh',
  156. maxHeight: '65vh',
  157. overflow: 'scroll',
  158. backgroundColor: 'rgba(0,0,0,0.02)',
  159. padding: '0 8px',
  160. borderRadius: 4,
  161. },
  162. }}
  163. onCancel={() => setIsModalOpen(false)}
  164. footer={[
  165. <Button key='cancel' onClick={() => setIsModalOpen(false)}>
  166. {t('cancel')}
  167. </Button>,
  168. <Button key='submit' type='primary' onClick={() => form.submit()}>
  169. {t('verify')}
  170. </Button>,
  171. ]}
  172. >
  173. <Form
  174. name='dynamic_form_nest_item'
  175. onFinish={onFinish}
  176. form={form}
  177. autoComplete='off'
  178. layout='vertical'
  179. className='mt-8'
  180. initialValues={{ parameters: flowInfo?.variables || [{}] }}
  181. >
  182. <Form.List name='parameters'>
  183. {(fields, { add, remove }) => (
  184. <>
  185. {fields.map(({ key, name, ...restField }, index) => (
  186. <Space key={key} className='hover:bg-gray-100 pt-2 pl-2'>
  187. <Form.Item
  188. {...restField}
  189. name={[name, 'name']}
  190. label={`参数 ${index + 1} 名称`}
  191. style={{ width: 140 }}
  192. rules={[
  193. { required: true, message: 'Missing parameter name' },
  194. {
  195. pattern: /^[a-zA-Z0-9]+(_[a-zA-Z0-9]+)*$/,
  196. message: '名称必须是字母、数字或下划线,并使用下划线分隔多个单词',
  197. },
  198. ]}
  199. >
  200. <Input placeholder='Parameter Name' onChange={e => onNameChange(e, index)} />
  201. </Form.Item>
  202. <Form.Item
  203. {...restField}
  204. name={[name, 'label']}
  205. label='标题'
  206. style={{ width: 130 }}
  207. rules={[{ required: true, message: 'Missing parameter label' }]}
  208. >
  209. <Input placeholder='Parameter Label' />
  210. </Form.Item>
  211. <Form.Item
  212. {...restField}
  213. name={[name, 'value_type']}
  214. label='类型'
  215. style={{ width: 100 }}
  216. rules={[{ required: true, message: 'Missing parameter type' }]}
  217. >
  218. <Select placeholder='Select' onChange={value => onValueTypeChange(value, index)}>
  219. {VALUE_TYPES.map(type => (
  220. <Option key={type} value={type}>
  221. {type}
  222. </Option>
  223. ))}
  224. </Select>
  225. </Form.Item>
  226. <Form.Item
  227. {...restField}
  228. name={[name, 'value']}
  229. label='值'
  230. style={{ width: 320 }}
  231. rules={[{ required: true, message: 'Missing parameter value' }]}
  232. >
  233. {renderVariableValue(controlTypes[index], index)}
  234. </Form.Item>
  235. <Form.Item {...restField} name={[name, 'description']} label='描述' style={{ width: 170 }}>
  236. <Input placeholder='Parameter Description' />
  237. </Form.Item>
  238. <MinusCircleOutlined onClick={() => remove(name)} />
  239. <Form.Item name={[name, 'key']} hidden initialValue='dbgpt.core.flow.params' />
  240. <Form.Item name={[name, 'scope']} hidden initialValue='flow_priv' />
  241. <Form.Item name={[name, 'category']} hidden initialValue='common' />
  242. </Space>
  243. ))}
  244. <Form.Item>
  245. <Button type='dashed' onClick={() => add()} block icon={<PlusOutlined />}>
  246. {t('Add_Parameter')}
  247. </Button>
  248. </Form.Item>
  249. </>
  250. )}
  251. </Form.List>
  252. </Form>
  253. </Modal>
  254. </>
  255. );
  256. };