feat(web): add http-request、jinja-render node
This commit is contained in:
@@ -21,6 +21,8 @@ interface LexicalEditorProps {
|
|||||||
value?: string;
|
value?: string;
|
||||||
onChange?: (value: string) => void;
|
onChange?: (value: string) => void;
|
||||||
options: Suggestion[];
|
options: Suggestion[];
|
||||||
|
variant?: 'outlined' | 'borderless';
|
||||||
|
height?: number;
|
||||||
}
|
}
|
||||||
|
|
||||||
const theme = {
|
const theme = {
|
||||||
@@ -36,6 +38,8 @@ const Editor: FC<LexicalEditorProps> =({
|
|||||||
value = "",
|
value = "",
|
||||||
onChange,
|
onChange,
|
||||||
options,
|
options,
|
||||||
|
variant = 'borderless',
|
||||||
|
height = 60,
|
||||||
}) => {
|
}) => {
|
||||||
const [_count, setCount] = useState(0);
|
const [_count, setCount] = useState(0);
|
||||||
const initialConfig = {
|
const initialConfig = {
|
||||||
@@ -62,9 +66,10 @@ const Editor: FC<LexicalEditorProps> =({
|
|||||||
contentEditable={
|
contentEditable={
|
||||||
<ContentEditable
|
<ContentEditable
|
||||||
style={{
|
style={{
|
||||||
minHeight: '60px',
|
minHeight: `${height}px`,
|
||||||
padding: '0',
|
padding: variant === 'borderless' ? '0' : '4px 11px',
|
||||||
border: 'none',
|
border: variant === 'borderless' ? 'none' : '1px solid #DFE4ED',
|
||||||
|
borderRadius: '6px',
|
||||||
outline: 'none',
|
outline: 'none',
|
||||||
resize: 'none',
|
resize: 'none',
|
||||||
fontSize: '14px',
|
fontSize: '14px',
|
||||||
@@ -76,8 +81,8 @@ const Editor: FC<LexicalEditorProps> =({
|
|||||||
<div
|
<div
|
||||||
style={{
|
style={{
|
||||||
position: 'absolute',
|
position: 'absolute',
|
||||||
top: '0',
|
top: variant === 'borderless' ? '0' : '6px',
|
||||||
left: '0',
|
left: variant === 'borderless' ? '0' : '11px',
|
||||||
color: '#5B6167',
|
color: '#5B6167',
|
||||||
fontSize: '14px',
|
fontSize: '14px',
|
||||||
lineHeight: '20px',
|
lineHeight: '20px',
|
||||||
|
|||||||
@@ -47,9 +47,9 @@ const VariableComponent: React.FC<{ nodeKey: NodeKey; data: Suggestion }> = ({
|
|||||||
style={{ width: '12px', height: '12px', marginRight: '4px' }}
|
style={{ width: '12px', height: '12px', marginRight: '4px' }}
|
||||||
alt=""
|
alt=""
|
||||||
/>
|
/>
|
||||||
{data.nodeData?.name}
|
<span className="rb:wrap-break-word rb:line-clamp-1">{data.nodeData?.name}</span>
|
||||||
<span style={{ color: '#DFE4ED', margin: '0 2px' }}>/</span>
|
<span style={{ color: '#DFE4ED', margin: '0 2px' }}>/</span>
|
||||||
<span style={{ color: '#155EEF' }}>{data.label}</span>
|
<span className="rb:text-ellipsis rb:overflow-hidden rb:whitespace-nowrap rb:flex-1" style={{ color: '#155EEF' }}>{data.label}</span>
|
||||||
</span>
|
</span>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -10,7 +10,7 @@ const NodeLibrary: FC = () => {
|
|||||||
console.log('nodeLibrary', nodeLibrary)
|
console.log('nodeLibrary', nodeLibrary)
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="rb:w-80 rb:fixed rb:h-screen rb:left-0 rb:py-5 rb:px-5.5 rb:overflow-y-auto">
|
<div className="rb:w-80 rb:fixed rb:h-[calc(100%-64px)] rb:left-0 rb:py-5 rb:px-5.5 rb:overflow-y-auto">
|
||||||
<Space size={12} direction="vertical" className="rb:w-full">
|
<Space size={12} direction="vertical" className="rb:w-full">
|
||||||
{nodeLibrary.map(category => (
|
{nodeLibrary.map(category => (
|
||||||
<Card
|
<Card
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
import { useEffect } from 'react';
|
import { useEffect } from 'react';
|
||||||
|
import { useTranslation } from 'react-i18next'
|
||||||
import clsx from 'clsx';
|
import clsx from 'clsx';
|
||||||
import { Dropdown } from 'antd';
|
import { Dropdown } from 'antd';
|
||||||
import { SmallDashOutlined } from '@ant-design/icons';
|
import { SmallDashOutlined } from '@ant-design/icons';
|
||||||
@@ -15,7 +16,8 @@ interface NodeData {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const LoopNode: ReactShapeConfig['component'] = ({ node, graph }) => {
|
const LoopNode: ReactShapeConfig['component'] = ({ node, graph }) => {
|
||||||
const data = node.getData() as NodeData;
|
const data = node.getData() || {};
|
||||||
|
const { t } = useTranslation()
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
initNodes()
|
initNodes()
|
||||||
@@ -54,45 +56,28 @@ const LoopNode: ReactShapeConfig['component'] = ({ node, graph }) => {
|
|||||||
node.addChild(childNode2)
|
node.addChild(childNode2)
|
||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className={clsx('rb:group rb:border-2 rb:border-dashed rb:rounded-[12px] rb:relative rb:min-w-[300px] rb:min-h-[200px] rb:p-4', {
|
<div className={clsx('rb:cursor-pointer rb:group rb:relative rb:h-16 rb:w-60 rb:p-2.5 rb:border rb:rounded-xl rb:bg-white rb:hover:shadow-[0px_2px_6px_0px_rgba(33,35,50,0.12)]', {
|
||||||
'rb:border-orange-500 rb:border-[3px] rb:bg-white rb:text-gray-700': data?.isSelected,
|
'rb:border-[#155EEF]': data.isSelected,
|
||||||
'rb:border-[#d1d5db] rb:bg-white rb:text-[#374151]': !data?.isSelected
|
'rb:border-[#DFE4ED]': !data.isSelected
|
||||||
})}>
|
})}>
|
||||||
{/* 标题区域 */}
|
<div className="rb:flex rb:items-center rb:justify-between">
|
||||||
<div className="rb:absolute rb:-top-3 rb:left-4 rb:bg-[#10b981] rb:rounded-[20px] rb:p-[8px_16px] rb:flex rb:items-center rb:gap-2 rb:text-white rb:text-[14px] rb:font-bold rb:z-10">
|
<div className="rb:flex rb:items-center rb:gap-2 rb:flex-1">
|
||||||
<div className="rb:w-5 rb:h-5 rb:bg-[#FFFFFF] rb:rounded-sm rb:flex rb:items-center rb:justify-center rb:text-[12px] rb:text-[#10b981]">
|
<img src={data.icon} className="rb:w-5 rb:h-5" />
|
||||||
♻️
|
<div className="rb:wrap-break-word rb:line-clamp-1">{data.name ?? t(`workflow.${data.type}`)}</div>
|
||||||
</div>
|
</div>
|
||||||
循环
|
|
||||||
</div>
|
|
||||||
<Dropdown
|
|
||||||
menu={{items: [
|
|
||||||
{
|
|
||||||
key: '1',
|
|
||||||
label: '删除',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
key: '2',
|
|
||||||
label: '复制',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
key: '3',
|
|
||||||
label: '删除',
|
|
||||||
}
|
|
||||||
]}}
|
|
||||||
>
|
|
||||||
<SmallDashOutlined
|
|
||||||
className={clsx("rb:cursor-pointer rb:right-1 rb:top-1 rb:invisible rb:absolute rb:group-hover:visible", {
|
|
||||||
'rb:visible': data.isSelected
|
|
||||||
})}
|
|
||||||
/>
|
|
||||||
</Dropdown>
|
|
||||||
|
|
||||||
{/* 画布内容区域 */}
|
<div
|
||||||
<div className="rb:mt-6 rb:min-h-[150px] rb:w-full rb:bg-[radial-gradient(circle,#e5e7eb_1px,transparent_1px)] rb:bg-size-[12px_12px]"></div>
|
className="rb:w-5 rb:h-5 rb:cursor-pointer rb:bg-cover rb:bg-[url('@/assets/images/deleteBorder.svg')] rb:hover:bg-[url('@/assets/images/deleteBg.svg')]"
|
||||||
</div>
|
onClick={(e) => {
|
||||||
);
|
e.stopPropagation()
|
||||||
|
node.remove()
|
||||||
|
}}
|
||||||
|
></div>
|
||||||
|
</div>
|
||||||
|
<div className="rb:mt-6 rb:min-h-37.5 rb:w-full rb:bg-[radial-gradient(circle,#e5e7eb_1px,transparent_1px)] rb:bg-size-[12px_12px]"></div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
export default LoopNode;
|
export default LoopNode;
|
||||||
|
|||||||
@@ -0,0 +1,141 @@
|
|||||||
|
import { forwardRef, useEffect, useImperativeHandle, useState } from 'react';
|
||||||
|
import { Form, Select, Input } from 'antd';
|
||||||
|
import { useTranslation } from 'react-i18next';
|
||||||
|
|
||||||
|
import type { AuthConfigModalRef, HttpRequestConfigForm } from './types'
|
||||||
|
import RbModal from '@/components/RbModal'
|
||||||
|
|
||||||
|
const FormItem = Form.Item;
|
||||||
|
|
||||||
|
interface AuthConfigModalProps {
|
||||||
|
refresh: (values: HttpRequestConfigForm['auth']) => void;
|
||||||
|
}
|
||||||
|
|
||||||
|
const AuthConfigModal = forwardRef<AuthConfigModalRef, AuthConfigModalProps>(({
|
||||||
|
refresh,
|
||||||
|
}, ref) => {
|
||||||
|
const { t } = useTranslation();
|
||||||
|
const [visible, setVisible] = useState(false);
|
||||||
|
const [form] = Form.useForm<HttpRequestConfigForm['auth']>();
|
||||||
|
|
||||||
|
const values = Form.useWatch<HttpRequestConfigForm['auth']>([], form);
|
||||||
|
|
||||||
|
// 封装取消方法,添加关闭弹窗逻辑
|
||||||
|
const handleClose = () => {
|
||||||
|
setVisible(false);
|
||||||
|
form.resetFields();
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleOpen = (data?: HttpRequestConfigForm['auth']) => {
|
||||||
|
if (data) {
|
||||||
|
form.setFieldsValue({
|
||||||
|
auth: !data.auth_type || data.auth_type === 'none' ? 'none' : 'api_key',
|
||||||
|
auth_type: !data.auth_type || data.auth_type === 'none' ? undefined : data.auth_type,
|
||||||
|
header: data.header,
|
||||||
|
api_key: data.api_key
|
||||||
|
})
|
||||||
|
}
|
||||||
|
setVisible(true);
|
||||||
|
};
|
||||||
|
// 封装保存方法,添加提交逻辑
|
||||||
|
const handleSave = () => {
|
||||||
|
form
|
||||||
|
.validateFields()
|
||||||
|
.then(() => {
|
||||||
|
const { auth, auth_type, ...rest } = values ?? {}
|
||||||
|
refresh({
|
||||||
|
auth_type: auth === 'none' ? 'none' : auth_type,
|
||||||
|
...rest
|
||||||
|
})
|
||||||
|
handleClose()
|
||||||
|
})
|
||||||
|
.catch((err) => {
|
||||||
|
console.log('err', err)
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// 暴露给父组件的方法
|
||||||
|
useImperativeHandle(ref, () => ({
|
||||||
|
handleOpen,
|
||||||
|
handleClose
|
||||||
|
}));
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (values?.auth === 'api_key') {
|
||||||
|
form.setFieldValue('auth_type', 'basic')
|
||||||
|
} else {
|
||||||
|
form.setFieldsValue({
|
||||||
|
auth_type: undefined,
|
||||||
|
header: undefined,
|
||||||
|
api_key: undefined
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}, [values?.auth])
|
||||||
|
|
||||||
|
|
||||||
|
return (
|
||||||
|
<RbModal
|
||||||
|
title={t('workflow.config.http-request.auth')}
|
||||||
|
open={visible}
|
||||||
|
onCancel={handleClose}
|
||||||
|
okText={t('common.save')}
|
||||||
|
onOk={handleSave}
|
||||||
|
>
|
||||||
|
<Form
|
||||||
|
form={form}
|
||||||
|
layout="vertical"
|
||||||
|
initialValues={{
|
||||||
|
auth: 'none'
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<FormItem
|
||||||
|
name="auth"
|
||||||
|
label={t('workflow.config.http-request.authType')}
|
||||||
|
>
|
||||||
|
<Select
|
||||||
|
options={[
|
||||||
|
{ value: 'none', label: t('workflow.config.http-request.none') },
|
||||||
|
{ value: 'api_key', label: t('workflow.config.http-request.apiKey') },
|
||||||
|
]}
|
||||||
|
/>
|
||||||
|
</FormItem>
|
||||||
|
{values?.auth !== 'none' && <>
|
||||||
|
<FormItem
|
||||||
|
name="auth_type"
|
||||||
|
label={t('workflow.config.http-request.authType')}
|
||||||
|
>
|
||||||
|
<Select
|
||||||
|
options={[
|
||||||
|
{ value: 'basic', label: t('workflow.config.http-request.basic') },
|
||||||
|
{ value: 'bearer', label: t('workflow.config.http-request.bearer') },
|
||||||
|
{ value: 'custom', label: t('workflow.config.http-request.custom') },
|
||||||
|
]}
|
||||||
|
/>
|
||||||
|
</FormItem>
|
||||||
|
{values?.auth_type === 'custom' &&
|
||||||
|
<FormItem
|
||||||
|
name="header"
|
||||||
|
label={t('workflow.config.http-request.header')}
|
||||||
|
rules={[
|
||||||
|
{ required: true, message: t('common.pleaseEnter') }
|
||||||
|
]}
|
||||||
|
>
|
||||||
|
<Input placeholder={t('common.pleaseEnter')} />
|
||||||
|
</FormItem>
|
||||||
|
}
|
||||||
|
<FormItem
|
||||||
|
name="api_key"
|
||||||
|
label={t('workflow.config.http-request.api_key')}
|
||||||
|
rules={[
|
||||||
|
{ required: true, message: t('common.pleaseEnter') }
|
||||||
|
]}
|
||||||
|
>
|
||||||
|
<Input placeholder={t('common.pleaseEnter')} />
|
||||||
|
</FormItem>
|
||||||
|
</>}
|
||||||
|
</Form>
|
||||||
|
</RbModal>
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
export default AuthConfigModal;
|
||||||
@@ -0,0 +1,232 @@
|
|||||||
|
import { useState, useEffect } from 'react';
|
||||||
|
import { useTranslation } from 'react-i18next'
|
||||||
|
import { Button, Select, Table } from 'antd';
|
||||||
|
import { PlusOutlined, DeleteOutlined } from '@ant-design/icons';
|
||||||
|
import Editor from '../../Editor';
|
||||||
|
import type { Suggestion } from '../../Editor/plugin/AutocompletePlugin';
|
||||||
|
import Empty from '@/components/Empty';
|
||||||
|
import VariableSelect from '../VariableSelect';
|
||||||
|
|
||||||
|
export interface TableRow {
|
||||||
|
key: string;
|
||||||
|
name: string;
|
||||||
|
value: string;
|
||||||
|
type?: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
interface EditableTableProps {
|
||||||
|
title?: string;
|
||||||
|
value?: Record<string, string> | TableRow[];
|
||||||
|
onChange?: (value: TableRow[]) => void;
|
||||||
|
options?: Suggestion[];
|
||||||
|
typeOptions?: {value: string, label: string}[]
|
||||||
|
}
|
||||||
|
|
||||||
|
const EditableTable: React.FC<EditableTableProps> = ({
|
||||||
|
title,
|
||||||
|
value,
|
||||||
|
onChange,
|
||||||
|
options = [],
|
||||||
|
typeOptions = []
|
||||||
|
}) => {
|
||||||
|
const { t } = useTranslation()
|
||||||
|
const [rows, setRows] = useState<TableRow[]>([]);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
console.log('EditableTable value', value)
|
||||||
|
if (Array.isArray(value)) {
|
||||||
|
setRows([...value])
|
||||||
|
} else if (value && Object.keys(value).length > 0) {
|
||||||
|
// Only update if rows are empty or significantly different
|
||||||
|
const valueEntries = Object.entries(value)
|
||||||
|
if (rows.length === 0 || rows.length !== valueEntries.length) {
|
||||||
|
setRows(valueEntries.map(([key, val], index) => {
|
||||||
|
console.log('val', val)
|
||||||
|
return {
|
||||||
|
key: index.toString(),
|
||||||
|
name: key || '',
|
||||||
|
value: val || '',
|
||||||
|
type: typeOptions.length > 0 ? typeOptions[0].value : undefined
|
||||||
|
}
|
||||||
|
}))
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
setRows([])
|
||||||
|
}
|
||||||
|
}, [JSON.stringify(value), typeOptions.length])
|
||||||
|
|
||||||
|
const handleChange = (key: string, field: 'name' | 'value' | 'type', val: string) => {
|
||||||
|
const newRows = [...rows.map(row =>
|
||||||
|
row.key === key ? { ...row, [field]: val } : row
|
||||||
|
)];
|
||||||
|
|
||||||
|
setRows(newRows);
|
||||||
|
onChange?.(newRows);
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleAdd = () => {
|
||||||
|
const newKey = Date.now().toString();
|
||||||
|
if (typeOptions.length) {
|
||||||
|
setRows([...rows, { key: newKey, name: '', value: '', type: typeOptions[0].value }]);
|
||||||
|
} else {
|
||||||
|
setRows([...rows, { key: newKey, name: '', value: '' }]);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleDelete = (key: string, index: number) => {
|
||||||
|
console.log('index', index)
|
||||||
|
|
||||||
|
if (rows.length === 1) {
|
||||||
|
setRows([]);
|
||||||
|
onChange?.([]);
|
||||||
|
} else {
|
||||||
|
const newRows = rows.filter(row => row.key !== key);
|
||||||
|
setRows(newRows);
|
||||||
|
onChange?.(newRows);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const columns = typeOptions?.length > 0 ? [
|
||||||
|
{
|
||||||
|
title: t('workflow.config.name'),
|
||||||
|
dataIndex: 'name',
|
||||||
|
width: '45%',
|
||||||
|
render: (text: string, record: TableRow) => (
|
||||||
|
<Editor
|
||||||
|
options={options}
|
||||||
|
value={text}
|
||||||
|
height={32}
|
||||||
|
variant="outlined"
|
||||||
|
onChange={(value) => handleChange(record.key, 'name', value)}
|
||||||
|
/>
|
||||||
|
),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: t('workflow.config.type'),
|
||||||
|
dataIndex: 'type',
|
||||||
|
width: '20%',
|
||||||
|
render: (text: string, record: TableRow) => (
|
||||||
|
<Select
|
||||||
|
value={text}
|
||||||
|
options={typeOptions}
|
||||||
|
onChange={(value) => {
|
||||||
|
console.log('value record', value)
|
||||||
|
handleChange(record.key, 'type', value)
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: t('workflow.config.value'),
|
||||||
|
dataIndex: 'value',
|
||||||
|
width: '45%',
|
||||||
|
render: (text: string, record: TableRow) => {
|
||||||
|
if (record.type === 'file') {
|
||||||
|
|
||||||
|
return (
|
||||||
|
<VariableSelect
|
||||||
|
options={options}
|
||||||
|
value={text}
|
||||||
|
onChange={(value) => {
|
||||||
|
console.log('value record', value)
|
||||||
|
handleChange(record.key, 'value', value)
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
return (
|
||||||
|
<Editor
|
||||||
|
options={options}
|
||||||
|
value={text}
|
||||||
|
height={32}
|
||||||
|
variant="outlined"
|
||||||
|
onChange={(value) => {
|
||||||
|
console.log('value record', value)
|
||||||
|
handleChange(record.key, 'value', value)
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
)
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: '',
|
||||||
|
width: '10%',
|
||||||
|
render: (_: any, record: TableRow, index: number) => (
|
||||||
|
<Button
|
||||||
|
type="text"
|
||||||
|
icon={<DeleteOutlined />}
|
||||||
|
onClick={() => handleDelete(record.key, index)}
|
||||||
|
/>
|
||||||
|
),
|
||||||
|
},
|
||||||
|
] : [
|
||||||
|
{
|
||||||
|
title: '键',
|
||||||
|
dataIndex: 'name',
|
||||||
|
width: '45%',
|
||||||
|
render: (text: string, record: TableRow) => (
|
||||||
|
<Editor
|
||||||
|
options={options}
|
||||||
|
value={text}
|
||||||
|
height={32}
|
||||||
|
variant="outlined"
|
||||||
|
onChange={(value) => handleChange(record.key, 'name', value)}
|
||||||
|
/>
|
||||||
|
),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: '值',
|
||||||
|
dataIndex: 'value',
|
||||||
|
width: '45%',
|
||||||
|
render: (text: string, record: TableRow) => (
|
||||||
|
<Editor
|
||||||
|
options={options}
|
||||||
|
value={text}
|
||||||
|
height={32}
|
||||||
|
variant="outlined"
|
||||||
|
onChange={(value) => handleChange(record.key, 'value', value)}
|
||||||
|
/>
|
||||||
|
),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: '',
|
||||||
|
width: '10%',
|
||||||
|
render: (_: any, record: TableRow, index: number) => (
|
||||||
|
<Button
|
||||||
|
type="text"
|
||||||
|
icon={<DeleteOutlined />}
|
||||||
|
onClick={() => handleDelete(record.key, index)}
|
||||||
|
/>
|
||||||
|
),
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="rb:mb-4">
|
||||||
|
{title && <div className="rb:flex rb:items-center rb:mb-2 rb:justify-between">
|
||||||
|
<div className="rb:font-medium">{title}</div>
|
||||||
|
<Button
|
||||||
|
type="text"
|
||||||
|
icon={<PlusOutlined />}
|
||||||
|
onClick={handleAdd}
|
||||||
|
size="small"
|
||||||
|
/>
|
||||||
|
</div>}
|
||||||
|
<Table
|
||||||
|
columns={columns}
|
||||||
|
dataSource={rows}
|
||||||
|
pagination={false}
|
||||||
|
size="small"
|
||||||
|
locale={{ emptyText: <Empty size={88} /> }}
|
||||||
|
scroll={{ x: 'max-content' }}
|
||||||
|
/>
|
||||||
|
{!title &&
|
||||||
|
<Button type="dashed" onClick={handleAdd} block className='rb:mt-1'>
|
||||||
|
+{t('common.add')}
|
||||||
|
</Button>
|
||||||
|
}
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default EditableTable;
|
||||||
@@ -0,0 +1,272 @@
|
|||||||
|
import { type FC, useEffect, useRef } from "react";
|
||||||
|
import { useTranslation } from 'react-i18next'
|
||||||
|
import { Form, Row, Col, Select, Button, Divider, InputNumber, Switch, Input, Slider } from 'antd'
|
||||||
|
import Editor from '../../Editor'
|
||||||
|
import type { Suggestion } from '../../Editor/plugin/AutocompletePlugin'
|
||||||
|
import AuthConfigModal from './AuthConfigModal'
|
||||||
|
import type { AuthConfigModalRef, HttpRequestConfigForm } from './types'
|
||||||
|
import VariableSelect from "../VariableSelect";
|
||||||
|
import MessageEditor from '../MessageEditor'
|
||||||
|
import EditableTable, { type TableRow } from './EditableTable'
|
||||||
|
|
||||||
|
const HttpRequest: FC<{ options: Suggestion[]; }> = ({
|
||||||
|
options,
|
||||||
|
}) => {
|
||||||
|
const { t } = useTranslation()
|
||||||
|
const form = Form.useFormInstance();
|
||||||
|
const values = Form.useWatch([], form) || {}
|
||||||
|
const authConfigModalRef = useRef<AuthConfigModalRef>(null)
|
||||||
|
|
||||||
|
const handleChangeAuth = () => {
|
||||||
|
authConfigModalRef.current?.handleOpen(values?.auth)
|
||||||
|
}
|
||||||
|
const handleRefresh = (auth: HttpRequestConfigForm['auth']) => {
|
||||||
|
console.log('handleRefresh', auth)
|
||||||
|
form.setFieldsValue({ auth: {...auth} })
|
||||||
|
}
|
||||||
|
|
||||||
|
const handleChangeBodyContentType = (contentType: string) => {
|
||||||
|
const currentValues = form.getFieldsValue()
|
||||||
|
form.setFieldsValue({
|
||||||
|
body: {
|
||||||
|
...currentValues?.body,
|
||||||
|
content_type: contentType,
|
||||||
|
data: undefined
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
const updateObjectList = (data: TableRow[], key: string) => {
|
||||||
|
let obj: Record<string, string> = {}
|
||||||
|
if (data.length) {
|
||||||
|
data.forEach(vo => {
|
||||||
|
obj[vo.name] = vo.value
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
form.setFieldValue(key, obj)
|
||||||
|
}
|
||||||
|
|
||||||
|
console.log('HttpRequest', values)
|
||||||
|
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<div className="rb:flex rb:items-center rb:justify-between rb:mb-4">
|
||||||
|
<div>API</div>
|
||||||
|
<Button onClick={handleChangeAuth}>{t('workflow.config.http-request.auth')}</Button>
|
||||||
|
</div>
|
||||||
|
<Row gutter={16}>
|
||||||
|
<Col span={8}>
|
||||||
|
<Form.Item name="method">
|
||||||
|
<Select
|
||||||
|
options={[
|
||||||
|
{ label: 'GET', value: 'GET' },
|
||||||
|
{ label: 'POST', value: 'POST' },
|
||||||
|
{ label: 'HEAD', value: 'HEAD' },
|
||||||
|
{ label: 'PATCH', value: 'PATCH' },
|
||||||
|
{ label: 'PUT', value: 'PUT' },
|
||||||
|
{ label: 'DELETE', value: 'DELETE' },
|
||||||
|
]}
|
||||||
|
/>
|
||||||
|
</Form.Item>
|
||||||
|
</Col>
|
||||||
|
<Col span={16}>
|
||||||
|
<Form.Item name="url">
|
||||||
|
<Editor options={options} variant="outlined" />
|
||||||
|
</Form.Item>
|
||||||
|
</Col>
|
||||||
|
</Row>
|
||||||
|
<Form.Item name="auth" hidden>
|
||||||
|
</Form.Item>
|
||||||
|
|
||||||
|
<Form.Item name="headers">
|
||||||
|
<EditableTable
|
||||||
|
title="HEADERS"
|
||||||
|
options={options}
|
||||||
|
onChange={(headers) => updateObjectList(headers, 'headers')}
|
||||||
|
/>
|
||||||
|
</Form.Item>
|
||||||
|
|
||||||
|
<Form.Item name="params">
|
||||||
|
<EditableTable
|
||||||
|
title="PARAMS"
|
||||||
|
options={options}
|
||||||
|
onChange={(params) => updateObjectList(params, 'params')}
|
||||||
|
/>
|
||||||
|
</Form.Item>
|
||||||
|
|
||||||
|
<Form.Item label="BODY">
|
||||||
|
<Form.Item name={['body', 'content_type']}>
|
||||||
|
<Select
|
||||||
|
placeholder={t('common.pleaseSelect')}
|
||||||
|
onChange={handleChangeBodyContentType}
|
||||||
|
options={[
|
||||||
|
{ label: 'none', value: 'none' },
|
||||||
|
{ label: 'form-data', value: 'form-data' },
|
||||||
|
{ label: 'x-www-form-urlencoded', value: 'x-www-form-urlencoded' },
|
||||||
|
{ label: 'JSON', value: 'json' },
|
||||||
|
{ label: 'raw', value: 'raw' },
|
||||||
|
{ label: 'binary', value: 'binary' },
|
||||||
|
]}
|
||||||
|
/>
|
||||||
|
</Form.Item>
|
||||||
|
{values?.body?.content_type === 'form-data' &&
|
||||||
|
<Form.Item name={['body', 'data']} noStyle>
|
||||||
|
<EditableTable
|
||||||
|
options={options}
|
||||||
|
onChange={(data) => {
|
||||||
|
form.setFieldsValue({
|
||||||
|
body: {
|
||||||
|
...form.getFieldValue('body'),
|
||||||
|
data
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}}
|
||||||
|
typeOptions={[
|
||||||
|
{ label: 'text', value: 'text' },
|
||||||
|
{ label: 'file', value: 'file' }
|
||||||
|
]}
|
||||||
|
/>
|
||||||
|
</Form.Item>
|
||||||
|
}
|
||||||
|
{values?.body?.content_type === 'x-www-form-urlencoded' &&
|
||||||
|
<Form.Item name={['body', 'data']} noStyle>
|
||||||
|
<EditableTable
|
||||||
|
options={options}
|
||||||
|
onChange={(data) => {
|
||||||
|
const currentBody = form.getFieldValue('body') || {}
|
||||||
|
form.setFieldsValue({
|
||||||
|
body: { ...currentBody, data }
|
||||||
|
})
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</Form.Item>
|
||||||
|
}
|
||||||
|
{values?.body?.content_type === 'json' &&
|
||||||
|
<Form.Item name={['body', 'data']}>
|
||||||
|
<MessageEditor
|
||||||
|
options={options}
|
||||||
|
isArray={false}
|
||||||
|
title="JSON"
|
||||||
|
/>
|
||||||
|
</Form.Item>
|
||||||
|
}
|
||||||
|
{values?.body?.content_type === 'raw' &&
|
||||||
|
<Form.Item name={['body', 'data']}>
|
||||||
|
<MessageEditor
|
||||||
|
options={options}
|
||||||
|
isArray={false}
|
||||||
|
title="RAW TEXT"
|
||||||
|
/>
|
||||||
|
</Form.Item>
|
||||||
|
}
|
||||||
|
{values?.body?.content_type === 'binary' &&
|
||||||
|
<Form.Item name={['body', 'data']}>
|
||||||
|
<VariableSelect
|
||||||
|
options={options}
|
||||||
|
/>
|
||||||
|
</Form.Item>
|
||||||
|
}
|
||||||
|
</Form.Item>
|
||||||
|
<Divider />
|
||||||
|
<Form.Item layout="horizontal" name="verify_ssl" label={t('workflow.config.http-request.verify_ssl')}>
|
||||||
|
<Switch />
|
||||||
|
</Form.Item>
|
||||||
|
|
||||||
|
<Divider />
|
||||||
|
<div>{t('workflow.config.http-request.timeouts')}</div>
|
||||||
|
<Form.Item
|
||||||
|
name={['timeouts', 'connect_timeout']}
|
||||||
|
label={t('workflow.config.http-request.connect_timeout')}
|
||||||
|
>
|
||||||
|
<InputNumber placeholder={t('common.pleaseEnter')} className="rb:w-full!" />
|
||||||
|
</Form.Item>
|
||||||
|
<Form.Item
|
||||||
|
name={['timeouts', 'read_timeout']}
|
||||||
|
label={t('workflow.config.http-request.read_timeout')}
|
||||||
|
>
|
||||||
|
<InputNumber placeholder={t('common.pleaseEnter')} className="rb:w-full!" />
|
||||||
|
</Form.Item>
|
||||||
|
<Form.Item
|
||||||
|
name={['timeouts', 'write_timeout']}
|
||||||
|
label={t('workflow.config.http-request.write_timeout')}
|
||||||
|
>
|
||||||
|
<InputNumber placeholder={t('common.pleaseEnter')} className="rb:w-full!" />
|
||||||
|
</Form.Item>
|
||||||
|
|
||||||
|
<Divider />
|
||||||
|
<Form.Item name={['retry', 'enable']} valuePropName="checked" layout="horizontal" label={t('workflow.config.http-request.retry')}>
|
||||||
|
<Switch />
|
||||||
|
</Form.Item>
|
||||||
|
{(values?.retry?.enable || typeof values?.retry?.max_attempts === 'number' || typeof values?.retry?.retry_interval === 'number') &&
|
||||||
|
<>
|
||||||
|
<Form.Item
|
||||||
|
name={['retry', 'max_attempts']}
|
||||||
|
label={t('workflow.config.http-request.max_attempts')}
|
||||||
|
>
|
||||||
|
<InputNumber placeholder={t('common.pleaseEnter')} className="rb:w-full!" />
|
||||||
|
</Form.Item>
|
||||||
|
<Form.Item
|
||||||
|
name={['retry', 'retry_interval']}
|
||||||
|
label={t('workflow.config.http-request.retry_interval')}
|
||||||
|
>
|
||||||
|
<InputNumber placeholder={t('common.pleaseEnter')} className="rb:w-full!" />
|
||||||
|
</Form.Item>
|
||||||
|
</>
|
||||||
|
}
|
||||||
|
|
||||||
|
<Divider />
|
||||||
|
<Form.Item layout="horizontal" name={['error_handle', 'method']} label={t('workflow.config.http-request.error_handle')}>
|
||||||
|
<Select
|
||||||
|
placeholder={t('common.pleaseSelect')}
|
||||||
|
options={[
|
||||||
|
{ value: 'none', label: t('workflow.config.http-request.none') },
|
||||||
|
{ value: 'default', label: t('workflow.config.http-request.default') },
|
||||||
|
{ value: 'branch', label: t('workflow.config.http-request.branch') },
|
||||||
|
]}
|
||||||
|
/>
|
||||||
|
</Form.Item>
|
||||||
|
{values?.error_handle?.method === 'default' &&
|
||||||
|
<>
|
||||||
|
<Form.Item
|
||||||
|
name={['error_handle', 'body']}
|
||||||
|
label="body"
|
||||||
|
>
|
||||||
|
<Input placeholder={t('common.pleaseEnter')} />
|
||||||
|
</Form.Item>
|
||||||
|
<Form.Item
|
||||||
|
name={['error_handle', 'status_code']}
|
||||||
|
label="status_code"
|
||||||
|
>
|
||||||
|
<InputNumber placeholder={t('common.pleaseEnter')} className="rb:w-full!" />
|
||||||
|
</Form.Item>
|
||||||
|
<Form.Item
|
||||||
|
name={['error_handle', 'headers']}
|
||||||
|
label="headers"
|
||||||
|
rules={[
|
||||||
|
{
|
||||||
|
validator: (_, value) => {
|
||||||
|
if (!value) return Promise.resolve();
|
||||||
|
try {
|
||||||
|
JSON.parse(value);
|
||||||
|
return Promise.resolve();
|
||||||
|
} catch {
|
||||||
|
return Promise.reject(new Error('Please enter valid JSON format'));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]}
|
||||||
|
>
|
||||||
|
<Input.TextArea placeholder={t('common.pleaseEnter')} />
|
||||||
|
</Form.Item>
|
||||||
|
</>
|
||||||
|
}
|
||||||
|
|
||||||
|
<AuthConfigModal
|
||||||
|
ref={authConfigModalRef}
|
||||||
|
refresh={handleRefresh}
|
||||||
|
/>
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
export default HttpRequest;
|
||||||
@@ -0,0 +1,44 @@
|
|||||||
|
export interface HttpRequestConfigForm {
|
||||||
|
method?: string;
|
||||||
|
url?: string;
|
||||||
|
auth?: {
|
||||||
|
auth?: string;
|
||||||
|
auth_type?: string;
|
||||||
|
header?: string;
|
||||||
|
api_key?: string;
|
||||||
|
};
|
||||||
|
headers?: {
|
||||||
|
[key: string]: string;
|
||||||
|
};
|
||||||
|
params?: {
|
||||||
|
[key: string]: string;
|
||||||
|
};
|
||||||
|
body?: {
|
||||||
|
content_type?: string;
|
||||||
|
data: string | Record<string, string>;
|
||||||
|
};
|
||||||
|
verify_ssl?: boolean;
|
||||||
|
timeouts?: {
|
||||||
|
connect_timeout: number;
|
||||||
|
read_timeout: number;
|
||||||
|
write_timeout: number;
|
||||||
|
};
|
||||||
|
retry?: {
|
||||||
|
max_attempts: number;
|
||||||
|
retry_interval: number;
|
||||||
|
};
|
||||||
|
error_handle?: {
|
||||||
|
method: string;
|
||||||
|
default: {
|
||||||
|
body: string;
|
||||||
|
status_code: number;
|
||||||
|
headers: {
|
||||||
|
[key: string]: string;
|
||||||
|
};
|
||||||
|
};
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface AuthConfigModalRef {
|
||||||
|
handleOpen: (vo?: HttpRequestConfigForm['auth']) => void;
|
||||||
|
}
|
||||||
@@ -0,0 +1,47 @@
|
|||||||
|
import React from 'react';
|
||||||
|
import { useTranslation } from 'react-i18next'
|
||||||
|
import { MinusCircleOutlined, PlusOutlined } from '@ant-design/icons';
|
||||||
|
import { Button, Form, Input, Space } from 'antd';
|
||||||
|
|
||||||
|
interface MappingListProps {
|
||||||
|
name: string;
|
||||||
|
}
|
||||||
|
const MappingList: React.FC<MappingListProps> = ({ name }) => {
|
||||||
|
const { t } = useTranslation()
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<Form.List name={name}>
|
||||||
|
{(fields, { add, remove }) => (
|
||||||
|
<>
|
||||||
|
{fields.map(({ key, name, ...restField }) => (
|
||||||
|
<Space key={key} style={{ display: 'flex', marginBottom: 8 }} align="baseline">
|
||||||
|
<Form.Item
|
||||||
|
{...restField}
|
||||||
|
name={[name, 'name']}
|
||||||
|
noStyle
|
||||||
|
>
|
||||||
|
<Input placeholder={t('common.pleaseEnter')} />
|
||||||
|
</Form.Item>
|
||||||
|
<Form.Item
|
||||||
|
{...restField}
|
||||||
|
name={[name, 'value']}
|
||||||
|
noStyle
|
||||||
|
>
|
||||||
|
<Input placeholder={t('common.pleaseEnter')} />
|
||||||
|
</Form.Item>
|
||||||
|
<MinusCircleOutlined onClick={() => remove(name)} />
|
||||||
|
</Space>
|
||||||
|
))}
|
||||||
|
<Form.Item>
|
||||||
|
<Button type="dashed" onClick={() => add()} block icon={<PlusOutlined />}>
|
||||||
|
Add field
|
||||||
|
</Button>
|
||||||
|
</Form.Item>
|
||||||
|
</>
|
||||||
|
)}
|
||||||
|
</Form.List>
|
||||||
|
</>
|
||||||
|
)
|
||||||
|
};
|
||||||
|
|
||||||
|
export default MappingList;
|
||||||
@@ -15,6 +15,8 @@ import VariableSelect from './VariableSelect';
|
|||||||
import ParamsList from './ParamsList';
|
import ParamsList from './ParamsList';
|
||||||
import GroupVariableList from './GroupVariableList'
|
import GroupVariableList from './GroupVariableList'
|
||||||
import CaseList from './CaseList'
|
import CaseList from './CaseList'
|
||||||
|
import HttpRequest from './HttpRequest';
|
||||||
|
import MappingList from './MappingList'
|
||||||
|
|
||||||
interface PropertiesProps {
|
interface PropertiesProps {
|
||||||
selectedNode?: Node | null;
|
selectedNode?: Node | null;
|
||||||
@@ -38,10 +40,10 @@ const Properties: FC<PropertiesProps> = ({
|
|||||||
const [editIndex, setEditIndex] = useState<number | null>(null)
|
const [editIndex, setEditIndex] = useState<number | null>(null)
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (selectedNode?.getData().id) {
|
if (selectedNode?.getData()?.id) {
|
||||||
form.resetFields()
|
form.resetFields()
|
||||||
}
|
}
|
||||||
}, [selectedNode?.getData().id])
|
}, [selectedNode?.getData()?.id])
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (selectedNode && form) {
|
if (selectedNode && form) {
|
||||||
@@ -97,7 +99,7 @@ const Properties: FC<PropertiesProps> = ({
|
|||||||
}
|
}
|
||||||
|
|
||||||
Object.keys(values).forEach(key => {
|
Object.keys(values).forEach(key => {
|
||||||
if (selectedNode.data?.config[key]) {
|
if (selectedNode.data?.config?.[key]) {
|
||||||
selectedNode.data.config[key].defaultValue = values[key]
|
selectedNode.data.config[key].defaultValue = values[key]
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
@@ -190,6 +192,7 @@ const Properties: FC<PropertiesProps> = ({
|
|||||||
...(nodeData.config?.variables?.value ?? [])
|
...(nodeData.config?.variables?.value ?? [])
|
||||||
]
|
]
|
||||||
list.forEach((variable: any) => {
|
list.forEach((variable: any) => {
|
||||||
|
if (!variable || !variable?.name) return;
|
||||||
const key = `${nodeId}_${variable.name}`;
|
const key = `${nodeId}_${variable.name}`;
|
||||||
if (!addedKeys.has(key)) {
|
if (!addedKeys.has(key)) {
|
||||||
addedKeys.add(key);
|
addedKeys.add(key);
|
||||||
@@ -203,7 +206,8 @@ const Properties: FC<PropertiesProps> = ({
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
nodeData.config?.variables?.sys.forEach((variable: any) => {
|
nodeData.config?.variables?.sys?.forEach((variable: any) => {
|
||||||
|
if (!variable || !variable?.name) return;
|
||||||
const key = `${nodeId}_sys_${variable.name}`;
|
const key = `${nodeId}_sys_${variable.name}`;
|
||||||
if (!addedKeys.has(key)) {
|
if (!addedKeys.has(key)) {
|
||||||
addedKeys.add(key);
|
addedKeys.add(key);
|
||||||
@@ -238,6 +242,8 @@ const Properties: FC<PropertiesProps> = ({
|
|||||||
return variableList;
|
return variableList;
|
||||||
}, [selectedNode, graphRef]);
|
}, [selectedNode, graphRef]);
|
||||||
|
|
||||||
|
console.log('values', values)
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="rb:w-75 rb:fixed rb:right-0 rb:top-16 rb:bottom-0 rb:p-3">
|
<div className="rb:w-75 rb:fixed rb:right-0 rb:top-16 rb:bottom-0 rb:p-3">
|
||||||
<div className="rb:font-medium rb:leading-5 rb:mb-3">{t('workflow.nodeProperties')}</div>
|
<div className="rb:font-medium rb:leading-5 rb:mb-3">{t('workflow.nodeProperties')}</div>
|
||||||
@@ -251,178 +257,195 @@ const Properties: FC<PropertiesProps> = ({
|
|||||||
updateNodeLabel(e.target.value);
|
updateNodeLabel(e.target.value);
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
</Form.Item>
|
</Form.Item>
|
||||||
<Form.Item name="id" label="ID">
|
<Form.Item name="id" label="ID">
|
||||||
<Input disabled />
|
<Input disabled />
|
||||||
</Form.Item>
|
</Form.Item>
|
||||||
{configs && Object.keys(configs).length > 0 && Object.keys(configs).map((key) => {
|
|
||||||
const config = configs[key] || {}
|
|
||||||
|
|
||||||
if (selectedNode.data?.type === 'start' && key === 'variables' && config.type === 'define') {
|
{selectedNode?.data?.type === 'http-request'
|
||||||
return (
|
? <HttpRequest
|
||||||
<div key={key}>
|
options={variableList}
|
||||||
<div className="rb:flex rb:items-center rb:justify-between rb:mb-2.75">
|
/>
|
||||||
<div className="rb:leading-5">
|
: configs && Object.keys(configs).length > 0 && Object.keys(configs).map((key) => {
|
||||||
{t(`workflow.config.${selectedNode.data.type}.${key}`)}
|
const config = configs[key] || {}
|
||||||
|
|
||||||
|
if (selectedNode?.data?.type === 'start' && key === 'variables' && config.type === 'define') {
|
||||||
|
return (
|
||||||
|
<div key={key}>
|
||||||
|
<div className="rb:flex rb:items-center rb:justify-between rb:mb-2.75">
|
||||||
|
<div className="rb:leading-5">
|
||||||
|
{t(`workflow.config.${selectedNode?.data?.type}.${key}`)}
|
||||||
|
</div>
|
||||||
|
<Button style={{padding: '0 8px', height: '24px'}} onClick={handleAddVariable}>+{t('application.addVariables')}</Button>
|
||||||
</div>
|
</div>
|
||||||
<Button style={{padding: '0 8px', height: '24px'}} onClick={handleAddVariable}>+{t('application.addVariables')}</Button>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<Space size={4} direction="vertical" className="rb:w-full">
|
<Space size={4} direction="vertical" className="rb:w-full">
|
||||||
{Array.isArray(config.defaultValue) && config.defaultValue?.map((vo, index) =>
|
{Array.isArray(config.defaultValue) && config.defaultValue?.map((vo, index) =>
|
||||||
<div key={`${vo.name}}-${index}`} className="rb:p-[4px_8px] rb:text-[12px] rb:text-[#5B6167] rb:flex rb:items-center rb:justify-between rb:border rb:border-[#DFE4ED] rb:rounded-md rb:group rb:cursor-pointer">
|
<div key={`${vo.name}}-${index}`} className="rb:p-[4px_8px] rb:text-[12px] rb:text-[#5B6167] rb:flex rb:items-center rb:justify-between rb:border rb:border-[#DFE4ED] rb:rounded-md rb:group rb:cursor-pointer">
|
||||||
<span>{vo.name}·{vo.description}</span>
|
<span>{vo.name}·{vo.description}</span>
|
||||||
|
|
||||||
<div className="rb:group-hover:hidden rb:flex rb:items-center rb:gap-1">
|
<div className="rb:group-hover:hidden rb:flex rb:items-center rb:gap-1">
|
||||||
{vo.required && <span>{t('workflow.config.start.required')}</span>}
|
{vo.required && <span>{t('workflow.config.start.required')}</span>}
|
||||||
|
{vo.type}
|
||||||
|
</div>
|
||||||
|
<Space className="rb:hidden! rb:group-hover:flex!">
|
||||||
|
<div
|
||||||
|
className="rb:w-4.5 rb:h-4.5 rb:cursor-pointer rb:bg-cover rb:bg-[url('@/assets/images/editBorder.svg')] rb:hover:bg-[url('@/assets/images/editBg.svg')]"
|
||||||
|
onClick={() => handleEditVariable(index, vo)}
|
||||||
|
></div>
|
||||||
|
<div
|
||||||
|
className="rb:w-4.5 rb:h-4.5 rb:cursor-pointer rb:bg-cover rb:bg-[url('@/assets/images/deleteBorder.svg')] rb:hover:bg-[url('@/assets/images/deleteBg.svg')]"
|
||||||
|
onClick={() => handleDeleteVariable(index, vo)}
|
||||||
|
></div>
|
||||||
|
</Space>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
<Divider size="small" />
|
||||||
|
{config.sys?.map((vo, index) =>
|
||||||
|
<div key={index} className="rb:p-[4px_8px] rb:text-[12px] rb:text-[#5B6167] rb:flex rb:items-center rb:justify-between rb:border rb:border-[#DFE4ED] rb:rounded-md">
|
||||||
|
<div>
|
||||||
|
<span>sys.{vo.name}</span>
|
||||||
|
</div>
|
||||||
{vo.type}
|
{vo.type}
|
||||||
</div>
|
</div>
|
||||||
<Space className="rb:hidden! rb:group-hover:flex!">
|
)}
|
||||||
<div
|
</Space>
|
||||||
className="rb:w-4.5 rb:h-4.5 rb:cursor-pointer rb:bg-cover rb:bg-[url('@/assets/images/editBorder.svg')] rb:hover:bg-[url('@/assets/images/editBg.svg')]"
|
</div>
|
||||||
onClick={() => handleEditVariable(index, vo)}
|
)
|
||||||
></div>
|
}
|
||||||
<div
|
|
||||||
className="rb:w-4.5 rb:h-4.5 rb:cursor-pointer rb:bg-cover rb:bg-[url('@/assets/images/deleteBorder.svg')] rb:hover:bg-[url('@/assets/images/deleteBg.svg')]"
|
|
||||||
onClick={() => handleDeleteVariable(index, vo)}
|
|
||||||
></div>
|
|
||||||
</Space>
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
<Divider size="small" />
|
|
||||||
{config.sys?.map((vo, index) =>
|
|
||||||
<div key={index} className="rb:p-[4px_8px] rb:text-[12px] rb:text-[#5B6167] rb:flex rb:items-center rb:justify-between rb:border rb:border-[#DFE4ED] rb:rounded-md">
|
|
||||||
<div>
|
|
||||||
<span>sys.{vo.name}</span>
|
|
||||||
</div>
|
|
||||||
{vo.type}
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
</Space>
|
|
||||||
</div>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
if (selectedNode.data?.type === 'llm' && key === 'messages' && config.type === 'define') {
|
if (selectedNode?.data?.type === 'llm' && key === 'messages' && config.type === 'define') {
|
||||||
return (
|
return (
|
||||||
<Form.Item key={key} name={key}>
|
<Form.Item key={key} name={key}>
|
||||||
<MessageEditor options={variableList} />
|
<MessageEditor options={variableList} />
|
||||||
</Form.Item>
|
</Form.Item>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
if (selectedNode.data?.type === 'end' && key === 'output') {
|
if (selectedNode?.data?.type === 'end' && key === 'output') {
|
||||||
return (
|
return (
|
||||||
<Form.Item key={key} name={key}>
|
<Form.Item key={key} name={key}>
|
||||||
<MessageEditor isArray={false} parentName={key} options={variableList} />
|
<MessageEditor isArray={false} parentName={key} options={variableList} />
|
||||||
</Form.Item>
|
</Form.Item>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
if (config.type === 'define') {
|
if (config.type === 'define') {
|
||||||
return null
|
return null
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (config.type === 'knowledge') {
|
||||||
|
return (
|
||||||
|
<Form.Item
|
||||||
|
key={key}
|
||||||
|
name={key}
|
||||||
|
>
|
||||||
|
<Knowledge />
|
||||||
|
</Form.Item>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
if (config.type === 'messageEditor') {
|
||||||
|
return (
|
||||||
|
<Form.Item key={key} name={key}>
|
||||||
|
<MessageEditor
|
||||||
|
title={t(`workflow.config.${selectedNode?.data?.type}.${key}`)}
|
||||||
|
isArray={!!config.isArray}
|
||||||
|
parentName={key}
|
||||||
|
options={variableList}
|
||||||
|
/>
|
||||||
|
</Form.Item>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
if (config.type === 'paramList') {
|
||||||
|
return (
|
||||||
|
<Form.Item key={key} name={key}>
|
||||||
|
<ParamsList
|
||||||
|
label={t(`workflow.config.${selectedNode?.data?.type}.${key}`)}
|
||||||
|
/>
|
||||||
|
</Form.Item>
|
||||||
|
|
||||||
|
)
|
||||||
|
}
|
||||||
|
if (config.type === 'groupVariableList') {
|
||||||
|
return (
|
||||||
|
<Form.Item key={key} name={key}>
|
||||||
|
<GroupVariableList
|
||||||
|
name={key}
|
||||||
|
options={variableList}
|
||||||
|
isCanAdd={!!values?.group}
|
||||||
|
/>
|
||||||
|
</Form.Item>
|
||||||
|
|
||||||
|
)
|
||||||
|
}
|
||||||
|
if (config.type === 'caseList') {
|
||||||
|
console.log('key', key)
|
||||||
|
return (
|
||||||
|
<Form.Item key={key} name={key}>
|
||||||
|
<CaseList
|
||||||
|
name={key}
|
||||||
|
options={variableList}
|
||||||
|
selectedNode={selectedNode}
|
||||||
|
graphRef={graphRef}
|
||||||
|
/>
|
||||||
|
</Form.Item>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
if (config.type === 'mappingList') {
|
||||||
|
return (
|
||||||
|
<Form.Item key={key} name={key}
|
||||||
|
label={t(`workflow.config.${selectedNode?.data?.type}.${key}`)}
|
||||||
|
>
|
||||||
|
<MappingList name={key} />
|
||||||
|
</Form.Item>
|
||||||
|
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
if (config.type === 'knowledge') {
|
|
||||||
return (
|
return (
|
||||||
<Form.Item
|
<Form.Item
|
||||||
key={key}
|
key={key}
|
||||||
name={key}
|
name={key}
|
||||||
|
label={t(`workflow.config.${selectedNode?.data?.type}.${key}`)}
|
||||||
|
layout={config.type === 'switch' ? 'horizontal' : 'vertical'}
|
||||||
>
|
>
|
||||||
<Knowledge />
|
{config.type === 'input'
|
||||||
</Form.Item>
|
? <Input placeholder={t('common.pleaseEnter')} />
|
||||||
)
|
: config.type === 'textarea'
|
||||||
}
|
? <Input.TextArea placeholder={t('common.pleaseEnter')} />
|
||||||
|
: config.type === 'select'
|
||||||
if (config.type === 'messageEditor') {
|
? <Select
|
||||||
return (
|
options={config.options}
|
||||||
<Form.Item key={key} name={key}>
|
|
||||||
<MessageEditor
|
|
||||||
title={t(`workflow.config.${selectedNode.data.type}.${key}`)}
|
|
||||||
isArray={!!config.isArray}
|
|
||||||
parentName={key}
|
|
||||||
options={variableList}
|
|
||||||
/>
|
|
||||||
</Form.Item>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
if (config.type === 'paramList') {
|
|
||||||
return (
|
|
||||||
<Form.Item key={key} name={key}>
|
|
||||||
<ParamsList
|
|
||||||
label={t(`workflow.config.${selectedNode.data.type}.${key}`)}
|
|
||||||
/>
|
|
||||||
</Form.Item>
|
|
||||||
|
|
||||||
)
|
|
||||||
}
|
|
||||||
if (config.type === 'groupVariableList') {
|
|
||||||
return (
|
|
||||||
<Form.Item key={key} name={key}>
|
|
||||||
<GroupVariableList
|
|
||||||
name={key}
|
|
||||||
options={variableList}
|
|
||||||
isCanAdd={!!values?.group}
|
|
||||||
/>
|
|
||||||
</Form.Item>
|
|
||||||
|
|
||||||
)
|
|
||||||
}
|
|
||||||
if (config.type === 'caseList') {
|
|
||||||
console.log('key', key)
|
|
||||||
return (
|
|
||||||
<Form.Item key={key} name={key}>
|
|
||||||
<CaseList
|
|
||||||
name={key}
|
|
||||||
options={variableList}
|
|
||||||
selectedNode={selectedNode}
|
|
||||||
graphRef={graphRef}
|
|
||||||
/>
|
|
||||||
</Form.Item>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
return (
|
|
||||||
<Form.Item
|
|
||||||
key={key}
|
|
||||||
name={key}
|
|
||||||
label={t(`workflow.config.${selectedNode.data.type}.${key}`)}
|
|
||||||
layout={config.type === 'switch' ? 'horizontal' : 'vertical'}
|
|
||||||
>
|
|
||||||
{config.type === 'input'
|
|
||||||
? <Input placeholder={t('common.pleaseEnter')} />
|
|
||||||
: config.type === 'textarea'
|
|
||||||
? <Input.TextArea placeholder={t('common.pleaseEnter')} />
|
|
||||||
: config.type === 'select'
|
|
||||||
? <Select
|
|
||||||
options={config.options}
|
|
||||||
placeholder={t('common.pleaseSelect')}
|
|
||||||
/>
|
|
||||||
: config.type === 'inputNumber'
|
|
||||||
? <InputNumber />
|
|
||||||
: config.type === 'slider'
|
|
||||||
? <Slider min={config.min} max={config.max} step={config.step} />
|
|
||||||
: config.type === 'customSelect'
|
|
||||||
? <CustomSelect
|
|
||||||
placeholder={t('common.pleaseSelect')}
|
|
||||||
url={config.url as string}
|
|
||||||
params={config.params}
|
|
||||||
hasAll={false}
|
|
||||||
valueKey={config.valueKey}
|
|
||||||
labelKey={config.labelKey}
|
|
||||||
/>
|
|
||||||
: config.type === 'variableList'
|
|
||||||
? <VariableSelect
|
|
||||||
placeholder={t('common.pleaseSelect')}
|
placeholder={t('common.pleaseSelect')}
|
||||||
options={variableList}
|
|
||||||
/>
|
/>
|
||||||
: config.type === 'switch'
|
: config.type === 'inputNumber'
|
||||||
? <Switch />
|
? <InputNumber />
|
||||||
: null
|
: config.type === 'slider'
|
||||||
}
|
? <Slider min={config.min} max={config.max} step={config.step} />
|
||||||
</Form.Item>
|
: config.type === 'customSelect'
|
||||||
)
|
? <CustomSelect
|
||||||
})}
|
placeholder={t('common.pleaseSelect')}
|
||||||
|
url={config.url as string}
|
||||||
|
params={config.params}
|
||||||
|
hasAll={false}
|
||||||
|
valueKey={config.valueKey}
|
||||||
|
labelKey={config.labelKey}
|
||||||
|
/>
|
||||||
|
: config.type === 'variableList'
|
||||||
|
? <VariableSelect
|
||||||
|
placeholder={t('common.pleaseSelect')}
|
||||||
|
options={variableList}
|
||||||
|
/>
|
||||||
|
: config.type === 'switch'
|
||||||
|
? <Switch />
|
||||||
|
: null
|
||||||
|
}
|
||||||
|
</Form.Item>
|
||||||
|
)
|
||||||
|
})
|
||||||
|
}
|
||||||
</Form>
|
</Form>
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -215,15 +215,82 @@ export const nodeLibrary: NodeLibrary[] = [
|
|||||||
}
|
}
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
// {
|
{
|
||||||
// category: "externalInteraction",
|
category: "externalInteraction",
|
||||||
// nodes: [
|
nodes: [
|
||||||
// { type: "http_request", icon: httpRequestIcon },
|
{ type: "http-request", icon: httpRequestIcon,
|
||||||
// { type: "tools", icon: toolsIcon },
|
config: {
|
||||||
// { type: "code_execution", icon: codeExecutionIcon },
|
method: {
|
||||||
// { type: "template_rendering", icon: templateRenderingIcon }
|
type: 'select',
|
||||||
// ]
|
options: [
|
||||||
// },
|
{ label: 'GET', value: 'GET' },
|
||||||
|
{ label: 'POST', value: 'POST' },
|
||||||
|
{ label: 'HEAD', value: 'HEAD' },
|
||||||
|
{ label: 'PATCH', value: 'PATCH' },
|
||||||
|
{ label: 'PUT', value: 'PUT' },
|
||||||
|
{ label: 'DELETE', value: 'DELETE' },
|
||||||
|
],
|
||||||
|
defaultValue: 'GET'
|
||||||
|
},
|
||||||
|
url: {
|
||||||
|
type: 'messageEditor',
|
||||||
|
isArray: false,
|
||||||
|
},
|
||||||
|
auth: {
|
||||||
|
type: 'define',
|
||||||
|
defaultValue: {
|
||||||
|
auth_type: 'none'
|
||||||
|
}
|
||||||
|
},
|
||||||
|
headers: {
|
||||||
|
type: 'define',
|
||||||
|
defaultValue: {}
|
||||||
|
},
|
||||||
|
params: {
|
||||||
|
type: 'define',
|
||||||
|
defaultValue: {}
|
||||||
|
},
|
||||||
|
body: {
|
||||||
|
type: 'define',
|
||||||
|
defaultValue: {
|
||||||
|
'content_type': 'none'
|
||||||
|
}
|
||||||
|
},
|
||||||
|
verify_ssl: {
|
||||||
|
type: 'switch',
|
||||||
|
defaultValue: false
|
||||||
|
},
|
||||||
|
timeouts: {
|
||||||
|
type: 'define',
|
||||||
|
defaultValue: {}
|
||||||
|
},
|
||||||
|
retry: {
|
||||||
|
type: 'define',
|
||||||
|
},
|
||||||
|
error_handle: {
|
||||||
|
type: 'define',
|
||||||
|
defaultValue: {
|
||||||
|
method: 'default'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
// { type: "tools", icon: toolsIcon },
|
||||||
|
// { type: "code_execution", icon: codeExecutionIcon },
|
||||||
|
{ type: "jinja-render", icon: templateRenderingIcon,
|
||||||
|
config: {
|
||||||
|
mapping: {
|
||||||
|
type: 'mappingList',
|
||||||
|
defaultValue: []
|
||||||
|
},
|
||||||
|
template: {
|
||||||
|
type: 'messageEditor',
|
||||||
|
isArray: false,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
// {
|
// {
|
||||||
// category: "safetyAndCompliance",
|
// category: "safetyAndCompliance",
|
||||||
// nodes: [
|
// nodes: [
|
||||||
|
|||||||
@@ -171,9 +171,13 @@ export const useWorkflowGraph = ({
|
|||||||
|
|
||||||
let sourcePort = sourcePorts.find((port: any) => port.group === 'right')?.id || 'right';
|
let sourcePort = sourcePorts.find((port: any) => port.group === 'right')?.id || 'right';
|
||||||
|
|
||||||
// 如果是if-else节点且有label,使用label作为源端口
|
// 如果是if-else节点且有label,根据label匹配对应的端口
|
||||||
if (sourceCell.getData()?.type === 'if-else' && label) {
|
if (sourceCell.getData()?.type === 'if-else' && label) {
|
||||||
sourcePort = label;
|
// 查找匹配的端口ID
|
||||||
|
const matchingPort = sourcePorts.find((port: any) => port.id === label);
|
||||||
|
if (matchingPort) {
|
||||||
|
sourcePort = label;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const edgeConfig = {
|
const edgeConfig = {
|
||||||
|
|||||||
Reference in New Issue
Block a user