feat(web): Add DAG variables to web flow (#1981)

Co-authored-by: Fangyin Cheng <staneyffer@gmail.com>
Co-authored-by: 谨欣 <echo.cmy@antgroup.com>
Co-authored-by: yanzhiyong <932374019@qq.com>
Co-authored-by: 严志勇 <yanzhiyong@tiansuixiansheng.com>
This commit is contained in:
Dreammy23
2024-09-10 09:39:40 +08:00
committed by GitHub
parent fe29f977f3
commit 746e4fda37
23 changed files with 767 additions and 156 deletions

View File

@@ -4,7 +4,8 @@ import { IFlowNode } from '@/types/flow';
import { FLOW_NODES_KEY } from '@/utils';
import { CaretLeftOutlined, CaretRightOutlined } from '@ant-design/icons';
import type { CollapseProps } from 'antd';
import { Badge, Collapse, Input, Layout, Space } from 'antd';
import { Badge, Collapse, Input, Layout, Space, Switch } from 'antd';
import classnames from 'classnames';
import React, { useContext, useEffect, useMemo, useState } from 'react';
import { useTranslation } from 'react-i18next';
import StaticNodes from './static-nodes';
@@ -12,6 +13,8 @@ import StaticNodes from './static-nodes';
const { Search } = Input;
const { Sider } = Layout;
const TAGS = JSON.stringify({ order: 'higher-order' });
type GroupType = {
category: string;
categoryLabel: string;
@@ -41,13 +44,16 @@ const AddNodesSider: React.FC = () => {
const [resources, setResources] = useState<Array<IFlowNode>>([]);
const [operatorsGroup, setOperatorsGroup] = useState<GroupType[]>([]);
const [resourcesGroup, setResourcesGroup] = useState<GroupType[]>([]);
const [isAllNodesVisible, setIsAllNodesVisible] = useState<boolean>(false);
useEffect(() => {
getNodes();
getNodes(TAGS);
}, []);
async function getNodes() {
const [_, data] = await apiInterceptors(getFlowNodes());
// tags is optional, if tags is not passed, it will get all nodes
async function getNodes(tags?: string) {
const [_, data] = await apiInterceptors(getFlowNodes(tags));
if (data && data.length > 0) {
localStorage.setItem(FLOW_NODES_KEY, JSON.stringify(data));
const operatorNodes = data.filter(node => node.flow_type === 'operator');
@@ -166,6 +172,16 @@ const AddNodesSider: React.FC = () => {
setSearchValue(val);
}
function onModeChange() {
if (isAllNodesVisible) {
getNodes(TAGS);
} else {
getNodes();
}
setIsAllNodesVisible(!isAllNodesVisible);
}
return (
<Sider
className='flex justify-center items-start nodrag bg-[#ffffff80] border-r border-[#d5e5f6] dark:bg-[#ffffff29] dark:border-[#ffffff66]'
@@ -179,9 +195,19 @@ const AddNodesSider: React.FC = () => {
onCollapse={collapsed => setCollapsed(collapsed)}
>
<Space direction='vertical' className='w-[280px] pt-4 px-4 overflow-hidden overflow-y-auto scrollbar-default'>
<p className='w-full text-base font-semibold text-[#1c2533] dark:text-[rgba(255,255,255,0.85)] line-clamp-1'>
{t('add_node')}
</p>
<div className='flex justify-between align-middle'>
<p className='w-full text-base font-semibold text-[#1c2533] dark:text-[rgba(255,255,255,0.85)] line-clamp-1'>
{t('add_node')}
</p>
<Switch
checkedChildren='高阶'
unCheckedChildren='全部'
onClick={onModeChange}
className={classnames('w-20', { 'bg-zinc-400': isAllNodesVisible })}
defaultChecked
/>
</div>
<Search placeholder='Search node' onSearch={searchNode} allowClear />

View File

@@ -0,0 +1,287 @@
import { apiInterceptors, getKeys, getVariablesByKey } from '@/client/api';
import { IFlowUpdateParam, IGetKeysResponseData, IVariableItem } from '@/types/flow';
import { buildVariableString } from '@/utils/flow';
import { MinusCircleOutlined, PlusOutlined } from '@ant-design/icons';
import { Button, Cascader, Form, Input, InputNumber, Modal, Select, Space } from 'antd';
import { DefaultOptionType } from 'antd/es/cascader';
import { uniqBy } from 'lodash';
import React, { useEffect, useState } from 'react';
import { useTranslation } from 'react-i18next';
const { Option } = Select;
const VALUE_TYPES = ['str', 'int', 'float', 'bool', 'ref'] as const;
type ValueType = (typeof VALUE_TYPES)[number];
type Props = {
flowInfo?: IFlowUpdateParam;
setFlowInfo: React.Dispatch<React.SetStateAction<IFlowUpdateParam | undefined>>;
};
export const AddFlowVariableModal: React.FC<Props> = ({ flowInfo, setFlowInfo }) => {
const { t } = useTranslation();
const [isModalOpen, setIsModalOpen] = useState(false);
const [form] = Form.useForm();
const [controlTypes, setControlTypes] = useState<ValueType[]>(['str']);
const [refVariableOptions, setRefVariableOptions] = useState<DefaultOptionType[]>([]);
useEffect(() => {
getKeysData();
}, []);
const getKeysData = async () => {
const [err, res] = await apiInterceptors(getKeys());
if (err) return;
const keyOptions = res?.map(({ key, label, scope }: IGetKeysResponseData) => ({
value: key,
label,
scope,
isLeaf: false,
}));
setRefVariableOptions(keyOptions);
};
const onFinish = (values: any) => {
const newFlowInfo = { ...flowInfo, variables: values?.parameters || [] } as IFlowUpdateParam;
setFlowInfo(newFlowInfo);
setIsModalOpen(false);
};
const onNameChange = (e: React.ChangeEvent<HTMLInputElement>, index: number) => {
const name = e.target.value;
const newValue = name
?.split('_')
?.map(word => word.charAt(0).toUpperCase() + word.slice(1))
?.join(' ');
form.setFields([
{
name: ['parameters', index, 'label'],
value: newValue,
},
]);
};
const onValueTypeChange = (type: ValueType, index: number) => {
const newControlTypes = [...controlTypes];
newControlTypes[index] = type;
setControlTypes(newControlTypes);
};
const loadData = (selectedOptions: DefaultOptionType[]) => {
const targetOption = selectedOptions[selectedOptions.length - 1];
const { value, scope } = targetOption as DefaultOptionType & { 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,
item: item,
}));
setRefVariableOptions([...refVariableOptions]);
}, 1000);
};
const onRefTypeValueChange = (
value: (string | number | null)[],
selectedOptions: DefaultOptionType[],
index: number,
) => {
// when select ref variable, must be select two options(key and variable)
if (value?.length !== 2) return;
const [selectRefKey, selectedRefVariable] = selectedOptions as DefaultOptionType[];
const selectedVariable = selectRefKey?.children?.find(
({ value }) => value === selectedRefVariable?.value,
) as DefaultOptionType & { item: IVariableItem };
// build variable string by rule
const variableStr = buildVariableString(selectedVariable?.item);
const parameters = form.getFieldValue('parameters');
const param = parameters?.[index];
if (param) {
param.value = variableStr;
param.category = selectedVariable?.item?.category;
param.value_type = selectedVariable?.item?.value_type;
form.setFieldsValue({
parameters: [...parameters],
});
}
};
// Helper function to render the appropriate control component
const renderVariableValue = (type: string, index: number) => {
switch (type) {
case 'ref':
return (
<Cascader
placeholder='Select Value'
options={refVariableOptions}
loadData={loadData}
onChange={(value, selectedOptions) => onRefTypeValueChange(value, selectedOptions, index)}
changeOnSelect
/>
);
case 'str':
return <Input placeholder='Parameter Value' />;
case 'int':
return (
<InputNumber
step={1}
placeholder='Parameter Value'
parser={value => value?.replace(/[^\-?\d]/g, '') || 0}
style={{ width: '100%' }}
/>
);
case 'float':
return <InputNumber placeholder='Parameter Value' style={{ width: '100%' }} />;
case 'bool':
return (
<Select placeholder='Select Value'>
<Option value={true}>True</Option>
<Option value={false}>False</Option>
</Select>
);
default:
return <Input placeholder='Parameter Value' />;
}
};
return (
<>
<Button
type='primary'
className='flex items-center justify-center rounded-full left-4 top-4'
style={{ zIndex: 1050 }}
icon={<PlusOutlined />}
onClick={() => setIsModalOpen(true)}
/>
<Modal
title={t('Add_Global_Variable_of_Flow')}
width={1000}
open={isModalOpen}
styles={{
body: {
minHeight: '40vh',
maxHeight: '65vh',
overflow: 'scroll',
backgroundColor: 'rgba(0,0,0,0.02)',
padding: '0 8px',
borderRadius: 4,
},
}}
onCancel={() => setIsModalOpen(false)}
footer={[
<Button key='cancel' onClick={() => setIsModalOpen(false)}>
{t('cancel')}
</Button>,
<Button key='submit' type='primary' onClick={() => form.submit()}>
{t('verify')}
</Button>,
]}
>
<Form
name='dynamic_form_nest_item'
onFinish={onFinish}
form={form}
autoComplete='off'
layout='vertical'
className='mt-8'
initialValues={{ parameters: flowInfo?.variables || [{}] }}
>
<Form.List name='parameters'>
{(fields, { add, remove }) => (
<>
{fields.map(({ key, name, ...restField }, index) => (
<Space key={key} className='hover:bg-gray-100 pt-2 pl-2'>
<Form.Item
{...restField}
name={[name, 'name']}
label={`参数 ${index + 1} 名称`}
style={{ width: 140 }}
rules={[
{ required: true, message: 'Missing parameter name' },
{
pattern: /^[a-zA-Z0-9]+(_[a-zA-Z0-9]+)*$/,
message: '名称必须是字母、数字或下划线,并使用下划线分隔多个单词',
},
]}
>
<Input placeholder='Parameter Name' onChange={e => onNameChange(e, index)} />
</Form.Item>
<Form.Item
{...restField}
name={[name, 'label']}
label='标题'
style={{ width: 130 }}
rules={[{ required: true, message: 'Missing parameter label' }]}
>
<Input placeholder='Parameter Label' />
</Form.Item>
<Form.Item
{...restField}
name={[name, 'value_type']}
label='类型'
style={{ width: 100 }}
rules={[{ required: true, message: 'Missing parameter type' }]}
>
<Select placeholder='Select' onChange={value => onValueTypeChange(value, index)}>
{VALUE_TYPES.map(type => (
<Option key={type} value={type}>
{type}
</Option>
))}
</Select>
</Form.Item>
<Form.Item
{...restField}
name={[name, 'value']}
label='值'
style={{ width: 320 }}
rules={[{ required: true, message: 'Missing parameter value' }]}
>
{renderVariableValue(controlTypes[index], index)}
</Form.Item>
<Form.Item {...restField} name={[name, 'description']} label='描述' style={{ width: 170 }}>
<Input placeholder='Parameter Description' />
</Form.Item>
<MinusCircleOutlined onClick={() => remove(name)} />
<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' />
</Space>
))}
<Form.Item>
<Button type='dashed' onClick={() => add()} block icon={<PlusOutlined />}>
{t('Add_Parameter')}
</Button>
</Form.Item>
</>
)}
</Form.List>
</Form>
</Modal>
</>
);
};

View File

@@ -1,5 +1,5 @@
import { IFlowData, IFlowUpdateParam } from '@/types/flow';
import { Button, Form, Input, Modal, Radio, Space, message } from 'antd';
import { Button, Form, Input, Modal, Radio, message } from 'antd';
import { useTranslation } from 'react-i18next';
import { ReactFlowInstance } from 'reactflow';
@@ -43,12 +43,17 @@ export const ExportFlowModal: React.FC<Props> = ({
return (
<>
<Modal
centered
title={t('Export_Flow')}
open={isExportFlowModalOpen}
onCancel={() => setIsExportFlowModalOpen(false)}
cancelButtonProps={{ className: 'hidden' }}
okButtonProps={{ className: 'hidden' }}
footer={[
<Button key='cancel' onClick={() => setIsExportFlowModalOpen(false)}>
{t('cancel')}
</Button>,
<Button key='submit' type='primary' onClick={() => form.submit()}>
{t('verify')}
</Button>,
]}
>
<Form
form={form}
@@ -79,17 +84,6 @@ export const ExportFlowModal: React.FC<Props> = ({
<Form.Item hidden name='uid'>
<Input />
</Form.Item>
<Form.Item wrapperCol={{ offset: 14, span: 8 }}>
<Space>
<Button htmlType='button' onClick={() => setIsExportFlowModalOpen(false)}>
{t('cancel')}
</Button>
<Button type='primary' htmlType='submit'>
{t('verify')}
</Button>
</Space>
</Form.Item>
</Form>
</Modal>

View File

@@ -0,0 +1,89 @@
import { getFlowTemplates } from '@/client/api';
import CanvasWrapper from '@/pages/construct/flow/canvas/index';
import type { TableProps } from 'antd';
import { Button, Modal, Space, Table } from 'antd';
import { useEffect, useState } from 'react';
import { useTranslation } from 'react-i18next';
type Props = {
isFlowTemplateModalOpen: boolean;
setIsFlowTemplateModalOpen: (value: boolean) => void;
};
interface DataType {
key: string;
name: string;
age: number;
address: string;
tags: string[];
}
export const FlowTemplateModal: React.FC<Props> = ({ isFlowTemplateModalOpen, setIsFlowTemplateModalOpen }) => {
const { t } = useTranslation();
const [dataSource, setDataSource] = useState([]);
const onTemplateImport = (record: DataType) => {
if (record?.name) {
localStorage.setItem('importFlowData', JSON.stringify(record));
CanvasWrapper();
setIsFlowTemplateModalOpen(false);
}
};
const columns: TableProps<DataType>['columns'] = [
{
title: t('Template_Name'),
dataIndex: 'name',
key: 'name',
},
{
title: t('Template_Label'),
dataIndex: 'label',
key: 'label',
},
{
title: t('Template_Description'),
dataIndex: 'description',
key: 'description',
},
{
title: t('Template_Action'),
key: 'action',
render: (_, record) => (
<Space size='middle'>
<Button
type='link'
onClick={() => {
onTemplateImport(record);
}}
block
>
{t('Import_From_Template')}
</Button>
</Space>
),
},
];
useEffect(() => {
getFlowTemplates().then(res => {
console.log(res);
setDataSource(res?.data?.data?.items);
});
}, []);
return (
<>
<Modal
className='w-[700px]'
title={t('Import_From_Template')}
open={isFlowTemplateModalOpen}
onCancel={() => setIsFlowTemplateModalOpen(false)}
cancelButtonProps={{ className: 'hidden' }}
okButtonProps={{ className: 'hidden' }}
>
<Table className='w-full' dataSource={dataSource} columns={columns} />;
</Modal>
</>
);
};

View File

@@ -1,6 +1,7 @@
import { apiInterceptors, importFlow } from '@/client/api';
import CanvasWrapper from '@/pages/construct/flow/canvas/index';
import { UploadOutlined } from '@ant-design/icons';
import { Button, Form, GetProp, Modal, Radio, Space, Upload, UploadFile, UploadProps, message } from 'antd';
import { Button, Form, GetProp, Modal, Radio, Upload, UploadFile, UploadProps, message } from 'antd';
import { useEffect, useState } from 'react';
import { useTranslation } from 'react-i18next';
import { Edge, Node } from 'reactflow';
@@ -37,7 +38,9 @@ export const ImportFlowModal: React.FC<Props> = ({ isImportModalOpen, setIsImpor
const [, , res] = await apiInterceptors(importFlow(formData));
if (res?.success) {
messageApi.success(t('Export_Flow_Success'));
messageApi.success(t('Import_Flow_Success'));
localStorage.setItem('importFlowData', JSON.stringify(res?.data));
CanvasWrapper();
} else if (res?.err_msg) {
messageApi.error(res?.err_msg);
}
@@ -61,12 +64,17 @@ export const ImportFlowModal: React.FC<Props> = ({ isImportModalOpen, setIsImpor
return (
<>
<Modal
centered
title={t('Import_Flow')}
open={isImportModalOpen}
onCancel={() => setIsImportFlowModalOpen(false)}
cancelButtonProps={{ className: 'hidden' }}
okButtonProps={{ className: 'hidden' }}
footer={[
<Button key='cancel' onClick={() => setIsImportFlowModalOpen(false)}>
{t('cancel')}
</Button>,
<Button key='submit' type='primary' onClick={() => form.submit()}>
{t('verify')}
</Button>,
]}
>
<Form
form={form}
@@ -90,21 +98,12 @@ 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>
</Radio.Group>
</Form.Item>
<Form.Item wrapperCol={{ offset: 14, span: 8 }}>
<Space>
<Button onClick={() => setIsImportFlowModalOpen(false)}>{t('cancel')}</Button>
<Button type='primary' htmlType='submit'>
{t('verify')}
</Button>
</Space>
</Form.Item>
</Form>
</Modal>

View File

@@ -1,3 +1,5 @@
export * from './add-flow-variable-modal';
export * from './export-flow-modal';
export * from './flow-template-modal';
export * from './import-flow-modal';
export * from './save-flow-modal';

View File

@@ -1,9 +1,9 @@
import { addFlow, apiInterceptors, updateFlowById } from '@/client/api';
import { IFlowData, IFlowUpdateParam } from '@/types/flow';
import { mapHumpToUnderline } from '@/utils/flow';
import { Button, Checkbox, Form, Input, Modal, Space, message } from 'antd';
import { useSearchParams } from 'next/navigation';
import { useState } from 'react';
import { Button, Checkbox, Form, Input, Modal, message } from 'antd';
import { useRouter } from 'next/router';
import { useEffect, useState } from 'react';
import { useTranslation } from 'react-i18next';
import { ReactFlowInstance } from 'reactflow';
@@ -22,13 +22,18 @@ export const SaveFlowModal: React.FC<Props> = ({
flowInfo,
setIsSaveFlowModalOpen,
}) => {
const [deploy, setDeploy] = useState(true);
const { t } = useTranslation();
const searchParams = useSearchParams();
const id = searchParams?.get('id') || '';
const router = useRouter();
const [form] = Form.useForm<IFlowUpdateParam>();
const [messageApi, contextHolder] = message.useMessage();
const [deploy, setDeploy] = useState(false);
const [id, setId] = useState(router.query.id || '');
useEffect(() => {
setId(router.query.id || '');
}, [router.query.id]);
function onLabelChange(e: React.ChangeEvent<HTMLInputElement>) {
const label = e.target.value;
// replace spaces with underscores, convert uppercase letters to lowercase, remove characters other than digits, letters, _, and -.
@@ -45,14 +50,15 @@ export const SaveFlowModal: React.FC<Props> = ({
if (id) {
const [, , res] = await apiInterceptors(
updateFlowById(id, {
updateFlowById(id.toString(), {
name,
label,
description,
editable,
uid: id,
uid: id.toString(),
flow_data: reactFlowObject,
state,
variables: flowInfo?.variables,
}),
);
@@ -70,12 +76,13 @@ export const SaveFlowModal: React.FC<Props> = ({
editable,
flow_data: reactFlowObject,
state,
variables: flowInfo?.variables,
}),
);
if (res?.uid) {
messageApi.success(t('save_flow_success'));
const history = window.history;
history.pushState(null, '', `/flow/canvas?id=${res.uid}`);
router.push(`/construct/flow/canvas?id=${res.uid}`, undefined, { shallow: true });
}
}
setIsSaveFlowModalOpen(false);
@@ -84,14 +91,17 @@ export const SaveFlowModal: React.FC<Props> = ({
return (
<>
<Modal
centered
title={t('flow_modal_title')}
open={isSaveFlowModalOpen}
onCancel={() => {
setIsSaveFlowModalOpen(false);
}}
cancelButtonProps={{ className: 'hidden' }}
okButtonProps={{ className: 'hidden' }}
onCancel={() => setIsSaveFlowModalOpen(false)}
footer={[
<Button key='cancel' onClick={() => setIsSaveFlowModalOpen(false)}>
{t('cancel')}
</Button>,
<Button key='submit' type='primary' onClick={() => form.submit()}>
{t('verify')}
</Button>,
]}
>
<Form
name='flow_form'
@@ -137,7 +147,7 @@ export const SaveFlowModal: React.FC<Props> = ({
<TextArea rows={3} />
</Form.Item>
<Form.Item label='Editable' name='editable' initialValue={flowInfo?.editable} valuePropName='checked'>
<Form.Item label='Editable' name='editable' initialValue={flowInfo?.editable || true} valuePropName='checked'>
<Checkbox />
</Form.Item>
@@ -156,22 +166,6 @@ export const SaveFlowModal: React.FC<Props> = ({
}}
/>
</Form.Item>
<Form.Item wrapperCol={{ offset: 14, span: 8 }}>
<Space>
<Button
htmlType='button'
onClick={() => {
setIsSaveFlowModalOpen(false);
}}
>
{t('cancel')}
</Button>
<Button type='primary' htmlType='submit'>
{t('verify')}
</Button>
</Space>
</Form.Item>
</Form>
</Modal>

View File

@@ -19,7 +19,6 @@ type CanvasNodeProps = {
function TypeLabel({ label }: { label: string }) {
return <div className='w-full h-8 align-middle font-semibold'>{label}</div>;
}
const forceTypeList = ['file', 'multiple_files', 'time'];
const CanvasNode: React.FC<CanvasNodeProps> = ({ data }) => {
const node = data;
@@ -125,12 +124,9 @@ const CanvasNode: React.FC<CanvasNodeProps> = ({ data }) => {
});
}
function onParameterValuesChange(changedValues: any, allValues: any) {
function onParameterValuesChange(changedValues: any) {
const [changedKey, changedVal] = Object.entries(changedValues)[0];
if (!allValues?.force && forceTypeList.includes(changedKey)) {
return;
}
updateCurrentNodeValue(changedKey, changedVal);
if (changedVal) {
updateDependsNodeValue(changedKey, changedVal);
@@ -142,7 +138,7 @@ const CanvasNode: React.FC<CanvasNodeProps> = ({ data }) => {
return (
<div className='bg-zinc-100 dark:bg-zinc-700 rounded p-2'>
<TypeLabel label='Outputs' />
{(outputs || []).map((output, index) => (
{outputs?.map((output, index) => (
<NodeHandler
key={`${data.id}_input_${index}`}
node={data}
@@ -197,8 +193,11 @@ const CanvasNode: React.FC<CanvasNodeProps> = ({ data }) => {
>
<div
className={classNames(
'w-80 h-auto rounded-xl shadow-md px-2 py-4 border bg-white dark:bg-zinc-800 cursor-grab flex flex-col space-y-2 text-sm',
'h-auto rounded-xl shadow-md px-2 py-4 border bg-white dark:bg-zinc-800 cursor-grab flex flex-col space-y-2 text-sm',
{
'w-80': node?.tags?.ui_size === 'middle' || !node?.tags?.ui_size,
'w-[256px]': node?.tags?.ui_size === 'small',
'w-[530px]': node?.tags?.ui_size === 'large',
'border-blue-500': node.selected || isHovered,
'border-stone-400 dark:border-white': !node.selected && !isHovered,
'border-dashed': flowType !== 'operator',

View File

@@ -131,6 +131,7 @@ const NodeParamHandler: React.FC<NodeParamHandlerProps> = ({ formValuesChange, n
if (ui_type === 'slider' && data.is_list) {
defaultValue = [0, 1];
}
return (
<Form.Item
className='mb-2'

View File

@@ -1,10 +1,11 @@
/* eslint-disable react-hooks/rules-of-hooks */
import { metadataBatch } from '@/client/api';
import { IFlowNodeParameter } from '@/types/flow';
import { convertKeysToCamelCase } from '@/utils/flow';
import { UploadOutlined } from '@ant-design/icons';
import type { UploadProps } from 'antd';
import type { UploadFile, UploadProps } from 'antd';
import { Button, Upload, message } from 'antd';
import { useRef, useState } from 'react';
import { useEffect, useRef, useState } from 'react';
import { useTranslation } from 'react-i18next';
type Props = {
@@ -16,6 +17,35 @@ export const renderUpload = (params: Props) => {
const { t } = useTranslation();
const urlList = useRef<string[]>([]);
const { data, formValuesChange } = params;
const [fileList, setFileList] = useState<UploadFile[]>([]);
// 获取上传文件元数据
useEffect(() => {
if (data.value) {
let uris: string[] = [];
typeof data.value === 'string' ? uris.push(data.value) : (uris = data.value);
const parameter: any = {
uris,
};
metadataBatch(parameter)
.then(res => {
const urlList: UploadFile[] = [];
for (let index = 0; index < res.data.data.length; index++) {
const element = res.data.data[index];
urlList.push({
uid: element.file_id,
name: element.file_name,
status: 'done',
url: element.uri,
});
}
setFileList(urlList);
})
.catch(error => {
console.log(error);
});
}
}, []);
const attr = convertKeysToCamelCase(data.ui?.attr || {});
const [uploading, setUploading] = useState(false);
@@ -51,6 +81,7 @@ export const renderUpload = (params: Props) => {
headers: {
authorization: 'authorization-text',
},
defaultFileList: fileList,
onChange(info) {
setUploading(true);
if (info.file.status !== 'uploading') {
@@ -73,19 +104,17 @@ export const renderUpload = (params: Props) => {
return (
<div className='p-2 text-sm text-center'>
{data.is_list ? (
<Upload onRemove={handleFileRemove} {...props} {...attr} multiple={true} accept={uploadType}>
<Button loading={uploading} icon={<UploadOutlined />}>
{t('Upload_Data')}
</Button>
</Upload>
) : (
<Upload onRemove={handleFileRemove} {...props} {...attr} multiple={false} accept={uploadType}>
<Button loading={uploading} icon={<UploadOutlined />}>
{t('Upload_Data')}
</Button>
</Upload>
)}
<Upload
onRemove={handleFileRemove}
{...props}
{...attr}
multiple={data.is_list ? true : false}
accept={uploadType}
>
<Button loading={uploading} icon={<UploadOutlined />}>
{t('Upload_Data')}
</Button>
</Upload>
</div>
);
};