feat(web): nodeProperties's ui update

This commit is contained in:
zhaoying
2026-01-19 14:49:48 +08:00
parent 2891f2c068
commit ff6bdc1bed
44 changed files with 1267 additions and 814 deletions

View File

@@ -0,0 +1,21 @@
<?xml version="1.0" encoding="UTF-8"?>
<svg width="16px" height="16px" viewBox="0 0 16 16" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
<title>编组 33</title>
<g id="工作流" stroke="none" stroke-width="1" fill="none" fill-rule="evenodd">
<g id="工作流--流程控制-条件分支" transform="translate(-1752, -352)" stroke="#5B6167">
<g id="编组-37" transform="translate(1480, 63)">
<g id="编组-35" transform="translate(8, 195)">
<g id="编组-33" transform="translate(264, 94)">
<g id="编组-32" transform="translate(3, 3.5)">
<line x1="-1.63757896e-14" y1="2" x2="10" y2="2" id="路径-29"></line>
<polyline id="路径-30" stroke-linejoin="round" points="3 1.99990611 3 0 7 0 7 2"></polyline>
<path d="M1.5,2.01228712 L1.5,8 C1.5,8.55228475 1.94771525,9 2.5,9 L7.5,9 C8.05228475,9 8.5,8.55228475 8.5,8 L8.5,2 L8.5,2" id="路径-31" stroke-linejoin="round"></path>
<line x1="4" y1="4.00683364" x2="4" y2="7.00683364" id="路径-32"></line>
<line x1="6" y1="4.00683364" x2="6" y2="7.00683364" id="路径-32"></line>
</g>
</g>
</g>
</g>
</g>
</g>
</svg>

After

Width:  |  Height:  |  Size: 1.4 KiB

View File

@@ -0,0 +1,22 @@
<?xml version="1.0" encoding="UTF-8"?>
<svg width="16px" height="16px" viewBox="0 0 16 16" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
<title>编组 33</title>
<g id="工作流" stroke="none" stroke-width="1" fill="none" fill-rule="evenodd">
<g id="工作流--流程控制-条件分支" transform="translate(-1752, -420)">
<g id="编组-37" transform="translate(1480, 63)">
<g id="编组-35" transform="translate(8, 195)">
<g id="编组-33" transform="translate(264, 162)">
<rect id="矩形" fill="#FF5D34" opacity="0.116987" x="0" y="0" width="16" height="16" rx="4"></rect>
<g id="编组-32" transform="translate(3, 3.5)" stroke="#FF5D34">
<line x1="-1.63757896e-14" y1="2" x2="10" y2="2" id="路径-29"></line>
<polyline id="路径-30" stroke-linejoin="round" points="3 1.99990611 3 0 7 0 7 2"></polyline>
<path d="M1.5,2.01228712 L1.5,8 C1.5,8.55228475 1.94771525,9 2.5,9 L7.5,9 C8.05228475,9 8.5,8.55228475 8.5,8 L8.5,2 L8.5,2" id="路径-31" stroke-linejoin="round"></path>
<line x1="4" y1="4.00683364" x2="4" y2="7.00683364" id="路径-32"></line>
<line x1="6" y1="4.00683364" x2="6" y2="7.00683364" id="路径-32"></line>
</g>
</g>
</g>
</g>
</g>
</g>
</svg>

After

Width:  |  Height:  |  Size: 1.5 KiB

View File

@@ -0,0 +1,18 @@
<?xml version="1.0" encoding="UTF-8"?>
<svg width="16px" height="16px" viewBox="0 0 16 16" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
<title>编组 33</title>
<g id="工作流" stroke="none" stroke-width="1" fill="none" fill-rule="evenodd">
<g id="工作流--AI与认知处理-大语言模型" transform="translate(-1736, -662)" stroke="#212332" stroke-width="1.1">
<g id="编组-34" transform="translate(1472, 64)">
<g id="编组-3备份-10" transform="translate(12, 409)">
<g id="选择备份" transform="translate(0, 177)">
<g id="编组-33" transform="translate(252, 12)">
<circle id="椭圆形" cx="8" cy="8" r="6.45"></circle>
<line x1="6" y1="8" x2="10" y2="8" id="路径-10" stroke-linecap="round" stroke-linejoin="round"></line>
</g>
</g>
</g>
</g>
</g>
</g>
</svg>

After

Width:  |  Height:  |  Size: 1.0 KiB

View File

@@ -0,0 +1,18 @@
<?xml version="1.0" encoding="UTF-8"?>
<svg width="14px" height="14px" viewBox="0 0 14 14" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
<title>召回</title>
<g id="工作流" stroke="none" stroke-width="1" fill="none" fill-rule="evenodd">
<g id="工作流--AI与认知处理-知识检索" transform="translate(-1684, -330)" stroke="#212332">
<g id="节点属性" transform="translate(1472, 64)">
<g id="编组-30" transform="translate(12, 264)">
<g id="编组-28" transform="translate(196, 0)">
<g id="召回" transform="translate(4, 2)">
<path d="M3.00336574,8.78243131 C3.31276784,9.47513403 3.79697125,10.0726443 4.39990015,10.5188863 M5.34710411,11.051994 C5.85707009,11.2602342 6.41513255,11.375 7,11.375 C7.46865477,11.375 7.92009851,11.3013108 8.34337671,11.1648869 M9.11238544,10.8321699 C9.85595277,10.4214229 10.4672402,9.80055221 10.8661626,9.04964308 M11.1846924,8.28028469 C11.3084287,7.87534253 11.375,7.44544554 11.375,7 C11.375,4.58375422 9.41624578,2.625 7,2.625 C5.30981329,2.625 3.84348335,3.58344477 3.11486142,4.98648308" id="形状"></path>
<polyline id="路径-10" stroke-linejoin="round" points="2.48490579 2.81937431 2.86401413 5.38855621 5.4725833 4.82767632"></polyline>
</g>
</g>
</g>
</g>
</g>
</g>
</svg>

After

Width:  |  Height:  |  Size: 1.5 KiB

View File

@@ -0,0 +1,18 @@
<?xml version="1.0" encoding="UTF-8"?>
<svg width="14px" height="14px" viewBox="0 0 14 14" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
<title>召回</title>
<g id="工作流" stroke="none" stroke-width="1" fill="none" fill-rule="evenodd">
<g id="工作流--AI与认知处理-知识检索" transform="translate(-1684, -330)" stroke="#155EEF">
<g id="节点属性" transform="translate(1472, 64)">
<g id="编组-30" transform="translate(12, 264)">
<g id="编组-28" transform="translate(196, 0)">
<g id="召回" transform="translate(4, 2)">
<path d="M3.00336574,8.78243131 C3.31276784,9.47513403 3.79697125,10.0726443 4.39990015,10.5188863 M5.34710411,11.051994 C5.85707009,11.2602342 6.41513255,11.375 7,11.375 C7.46865477,11.375 7.92009851,11.3013108 8.34337671,11.1648869 M9.11238544,10.8321699 C9.85595277,10.4214229 10.4672402,9.80055221 10.8661626,9.04964308 M11.1846924,8.28028469 C11.3084287,7.87534253 11.375,7.44544554 11.375,7 C11.375,4.58375422 9.41624578,2.625 7,2.625 C5.30981329,2.625 3.84348335,3.58344477 3.11486142,4.98648308" id="形状"></path>
<polyline id="路径-10" stroke-linejoin="round" points="2.48490579 2.81937431 2.86401413 5.38855621 5.4725833 4.82767632"></polyline>
</g>
</g>
</g>
</g>
</g>
</g>
</svg>

After

Width:  |  Height:  |  Size: 1.5 KiB

View File

@@ -54,7 +54,7 @@ const AppHeader: FC<{source?: 'space' | 'manage';}> = ({source = 'manage'}) => {
key: '1',
label: (<>
<div>{user.username}</div>
<div className="rb:text-[12px] rb:text-[#5B6167] rb:mt-[8px]">{user.email}</div>
<div className="rb:text-[12px] rb:text-[#5B6167] rb:mt-2">{user.email}</div>
</>),
},
{

View File

@@ -1,3 +1,5 @@
.rb-modal .ant-modal-header {
margin-bottom: 24px;
.rb-modal .ant-modal-footer .ant-btn {
height: 32px !important;
padding: 0 15px !important;
font-size: 14px !important;
}

View File

@@ -9,6 +9,7 @@
import { type FC } from 'react'
import { Modal, type ModalProps } from 'antd'
import { useTranslation } from 'react-i18next'
import './index.css'
const RbModal: FC<ModalProps> = ({
onOk,
onCancel,

View File

@@ -1,5 +1,5 @@
import { useState, type FC, useCallback, useRef } from 'react';
import { Input } from 'antd';
import { Input, type InputProps } from 'antd';
import { useTranslation } from 'react-i18next';
import searchIcon from '@/assets/images/search.svg'
@@ -11,6 +11,7 @@ interface SearchInputProps {
defaultValue?: string;
style?: Record<string, string | number>;
className?: string;
size?: InputProps['size']
}
const SearchInput: FC<SearchInputProps> = ({
@@ -79,7 +80,7 @@ const SearchInput: FC<SearchInputProps> = ({
return (
<Input
allowClear
prefix={<img src={searchIcon} alt="search" className="rb:w-[16px] rb:h-[16px] rb:mr-[4px]" />}
prefix={<img src={searchIcon} alt="search" className="rb:w-4 rb:h-4 rb:mr-1" />}
placeholder={placeholder || t('user.searchPlaceholder')}
value={value}
onChange={handleChange}

View File

@@ -417,7 +417,8 @@ export const en = {
refresh: 'Refresh',
return: 'Return',
statusEnabled: 'Available',
statusDisabled: 'Unavailable'
statusDisabled: 'Unavailable',
remove: 'Remove',
},
model: {
searchPlaceholder: 'search model…',
@@ -1798,9 +1799,11 @@ Memory Bear: After the rebellion, regional warlordism intensified for several re
temperature: 'Temperature',
max_tokens: 'Max Tokens',
context: 'Context',
contextPlaceholder: '{x} Set Variable',
memory: 'Memory',
enable_window: 'Memory Window',
inner: 'Built-in',
messagesPlaceholder: 'Write prompts here, type "{" to insert variables, type "insert" to insert',
},
start: {
variables: 'Input Fields',
@@ -1811,7 +1814,6 @@ Memory Bear: After the rebellion, regional warlordism intensified for several re
array: 'Dropdown Options',
object: 'Object',
addVariable: 'Add Variable',
editVariable: 'Edit Variable',
variableType: 'Variable Type',
variableName: 'Variable Name',
@@ -1835,6 +1837,7 @@ Memory Bear: After the rebellion, regional warlordism intensified for several re
'parameter-extractor': {
model_id: 'Model',
text: 'Input Variable',
textPlaceholder: '{x} Set Variable',
params: 'Extract Parameters',
prompt: 'Instruction',
@@ -1855,6 +1858,8 @@ Memory Bear: After the rebellion, regional warlordism intensified for several re
'array[number]': 'Array[Number]',
'array[boolean]': 'Array[Boolean]',
'array[object]': 'Array[Object]',
addParams: 'Add Extract Variable',
promptPlaceholder: 'Write prompts here, type "{" to insert variables, type "insert" to insert',
},
'var-aggregator': {
group: 'Aggregation Group',
@@ -1924,6 +1929,7 @@ Memory Bear: After the rebellion, regional warlordism intensified for several re
loop: {
cycle_vars: 'Loop Variables',
condition: 'Loop Termination Condition',
addCondition: 'Add Condition',
max_loop: 'Maximum Loop Count',
},
assigner: {
@@ -1960,6 +1966,7 @@ Memory Bear: After the rebellion, regional warlordism intensified for several re
type: 'Type',
value: 'Value',
addCase: 'Add Condition',
addVariable: 'Add Variables',
},
clear: 'Clear',

View File

@@ -965,7 +965,8 @@ export const zh = {
refresh: '刷新',
return: '返回',
statusEnabled: '可用',
statusDisabled: '不可用'
statusDisabled: '不可用',
remove: '删除',
},
product: {
applicationManagement: '应用管理',
@@ -1891,9 +1892,11 @@ export const zh = {
temperature: '温度',
max_tokens: '最大令牌数',
context: '上下文',
contextPlaceholder: '{x} 设置变量',
memory: '记忆',
enable_window: '记忆窗口',
inner: '内置',
messagesPlaceholder: '在此处编写提示,输入“{”插入变量输入“insert”插入',
},
start: {
variables: '输入字段',
@@ -1904,7 +1907,6 @@ export const zh = {
array: '下拉选项',
object: '对象',
addVariable: '添加变量',
editVariable: '编辑变量',
variableType: '变量类型',
variableName: '变量名称',
@@ -1924,10 +1926,12 @@ export const zh = {
query: '查询变量',
knowledge_retrieval: '知识库',
recallConfig: '召回测试',
addKnowledge: '添加知识库'
},
'parameter-extractor': {
model_id: '模型',
text: '输入变量',
textPlaceholder: '{x} 设置变量',
params: '提取参数',
prompt: '指令',
@@ -1948,6 +1952,8 @@ export const zh = {
'array[number]': 'Array[Number]',
'array[boolean]': 'Array[Boolean]',
'array[object]': 'Array[Object]',
addParams: '添加提取变量',
promptPlaceholder: '在此处编写提示,输入“{”插入变量输入“insert”插入',
},
'var-aggregator': {
group: '聚合分组',
@@ -2017,6 +2023,7 @@ export const zh = {
loop: {
cycle_vars: '循环变量',
condition: '循环终止条件',
addCondition: '添加条件',
max_loop: '最大循环次数',
},
assigner: {
@@ -2053,6 +2060,7 @@ export const zh = {
type: '类型',
value: '值',
addCase: '添加条件',
addVariable: '添加变量',
},
clear: '清空',

View File

@@ -21,6 +21,8 @@ export const lightTheme: ThemeConfig = {
colorBorderSecondary: '#DFE4ED',
// colorBgContainer: '#FBFDFF',
colorError: '#FF5D34',
sizeSM: 12,
fontSizeSM: 12,
},
components: {
Layout: {
@@ -86,6 +88,7 @@ export const lightTheme: ThemeConfig = {
rowSelectedBg: '#E9F1FF',
rowSelectedHoverBg: '#F0F3F8',
cellPaddingBlock: 8,
cellFontSizeSM: 12,
// cellPaddingInline: 24,
selectionColumnWidth: 48,
@@ -95,6 +98,13 @@ export const lightTheme: ThemeConfig = {
lastItemColor: '#212332',
linkColor: '#5B6167',
linkHoverColor: '#212332',
},
Input: {
inputFontSizeSM: 12,
controlHeightSM: 26
},
Select: {
lineHeightSM: 26
}
}
};

View File

@@ -1,6 +1,6 @@
import { type FC, useEffect, useRef } from 'react';
import { type FC, useRef } from 'react';
import { useNavigate, useParams } from 'react-router-dom';
import { Layout, Tabs, Dropdown, Button } from 'antd';
import { Layout, Tabs, Dropdown, Button, Flex } from 'antd';
import type { MenuProps } from 'antd';
import { useTranslation } from 'react-i18next';
import styles from '../index.module.css'
@@ -141,10 +141,12 @@ const ConfigHeader: FC<ConfigHeaderProps> = ({
{/* <Button type="primary">{t('workflow.export')}</Button> */}
<img src={logoutIcon} className="rb:w-4 rb:h-4 rb:cursor-pointer" onClick={goToApplication} />
</div>
: <div className="rb:h-8 rb:flex rb:items-center rb:justify-end rb:text-[12px] rb:text-[#5B6167] rb:font-regular rb:cursor-pointer" onClick={goToApplication}>
<img src={logoutIcon} className="rb:mr-2 rb:w-4 rb:h-4" />
{t('application.returnToApplicationList')}
</div>
: <Flex justify="flex-end">
<div className="rb:h-8 rb:flex rb:items-center rb:text-[12px] rb:text-[#5B6167] rb:font-regular rb:cursor-pointer" onClick={goToApplication}>
<img src={logoutIcon} className="rb:mr-2 rb:w-4 rb:h-4" />
{t('application.returnToApplicationList')}
</div>
</Flex>
}
</Header>
<ApplicationModal

View File

@@ -2,12 +2,13 @@ import { forwardRef, useImperativeHandle, useState } from 'react';
import { Form, Input, InputNumber, Checkbox } from 'antd';
import { useTranslation } from 'react-i18next';
import type { StartVariableItem, VariableConfigModalRef } from '../../types'
import type { VariableConfigModalRef } from '../../types'
import type { Variable } from '../Properties/VariableList/types'
import RbModal from '@/components/RbModal'
interface VariableEditModalProps {
refresh: (values: StartVariableItem[]) => void;
variables: StartVariableItem[]
refresh: (values: Variable[]) => void;
variables: Variable[]
}
const VariableConfigModal = forwardRef<VariableConfigModalRef, VariableEditModalProps>(({
@@ -15,9 +16,9 @@ const VariableConfigModal = forwardRef<VariableConfigModalRef, VariableEditModal
}, ref) => {
const { t } = useTranslation();
const [visible, setVisible] = useState(false);
const [form] = Form.useForm<{variables: StartVariableItem[]}>();
const [form] = Form.useForm<{variables: Variable[]}>();
const [loading, setLoading] = useState(false)
const [initialValues, setInitialValues] = useState<StartVariableItem[]>([])
const [initialValues, setInitialValues] = useState<Variable[]>([])
// 封装取消方法,添加关闭弹窗逻辑
const handleClose = () => {
@@ -26,7 +27,7 @@ const VariableConfigModal = forwardRef<VariableConfigModalRef, VariableEditModal
setLoading(false)
};
const handleOpen = (values: StartVariableItem[]) => {
const handleOpen = (values: Variable[]) => {
setVisible(true);
form.setFieldsValue({variables: values})
setInitialValues([...values])

View File

@@ -1,4 +1,4 @@
import { type FC, useState, useEffect } from 'react';
import { type FC, useState, useEffect, useMemo } from 'react';
import { LexicalComposer } from '@lexical/react/LexicalComposer';
import { RichTextPlugin } from '@lexical/react/LexicalRichTextPlugin';
import { ContentEditable } from '@lexical/react/LexicalContentEditable';
@@ -25,7 +25,11 @@ interface LexicalEditorProps {
options: Suggestion[];
variant?: 'outlined' | 'borderless';
height?: number;
fontSize?: number;
lineHeight?: number;
enableJinja2?: boolean;
size?: 'default' | 'small';
type?: 'input' | 'textarea'
}
const theme = {
@@ -51,8 +55,9 @@ const Editor: FC<LexicalEditorProps> =({
onChange,
options,
variant = 'borderless',
height = 60,
enableJinja2 = false,
size = 'default',
type = 'textarea'
}) => {
const [_count, setCount] = useState(0);
@@ -94,12 +99,9 @@ const Editor: FC<LexicalEditorProps> =({
display: flex;
}
.line-numbers {
background-color: #f8f9fa;
border-right: 1px solid #e1e4e8;
color: #656d76;
font-family: 'SFMono-Regular', Consolas, 'Liberation Mono', Menlo, monospace;
font-size: 12px;
line-height: 20px;
line-height: 16px;
padding: 4px 8px;
text-align: right;
user-select: none;
@@ -142,6 +144,21 @@ const Editor: FC<LexicalEditorProps> =({
console.error(error);
},
};
const minheight = useMemo(() => {
if (type === 'input') {
return `${size === 'small' ? 26 : 30}px`
}
return `${size === 'small' ? 60 : 120}px`
}, [type, size])
const fontSize = useMemo(() => {
return `${size === 'small' ? 12 : 14}px`
}, [size])
const lineHeight = useMemo(() => {
return `${size === 'small' ? 16 : 20}px`
}, [size])
const placeHolderMinheight = useMemo(() => {
return `${size === 'small' ? 16 : 30}px`
}, [type, size])
return (
<LexicalComposer initialConfig={initialConfig}>
@@ -152,7 +169,7 @@ const Editor: FC<LexicalEditorProps> =({
<div className="editor-with-line-numbers" style={{
border: variant === 'borderless' ? 'none' : '1px solid #DFE4ED',
borderRadius: '6px',
minHeight: `${height}px`,
minHeight: minheight,
}}>
<div className="line-numbers">
<div>1</div>
@@ -160,12 +177,12 @@ const Editor: FC<LexicalEditorProps> =({
<ContentEditable
className="editor-content-with-numbers"
style={{
minHeight: `${height}px`,
padding: '4px 11px',
minHeight: minheight,
padding: '4px 0',
outline: 'none',
resize: 'none',
fontSize: '14px',
lineHeight: '20px',
fontSize: fontSize,
lineHeight: lineHeight,
border: 'none',
}}
/>
@@ -173,14 +190,14 @@ const Editor: FC<LexicalEditorProps> =({
) : (
<ContentEditable
style={{
minHeight: `${height}px`,
minHeight: minheight,
padding: variant === 'borderless' ? '0' : '4px 11px',
border: variant === 'borderless' ? 'none' : '1px solid #DFE4ED',
borderRadius: '6px',
outline: 'none',
resize: 'none',
fontSize: '14px',
lineHeight: '20px',
fontSize: fontSize,
lineHeight: lineHeight,
}}
/>
)
@@ -188,12 +205,13 @@ const Editor: FC<LexicalEditorProps> =({
placeholder={
<div
style={{
minHeight: placeHolderMinheight,
position: 'absolute',
top: variant === 'borderless' ? '0' : '6px',
left: enableJinja2 ? '59px' : (variant === 'borderless' ? '0' : '11px'),
color: '#5B6167',
fontSize: '14px',
lineHeight: '20px',
color: '#A8A9AA',
fontSize: fontSize,
lineHeight: placeHolderMinheight,
pointerEvents: 'none',
}}
>

View File

@@ -9,12 +9,21 @@ const Jinja2HighlightPlugin = () => {
return editor.registerNodeTransform(TextNode, (textNode: TextNode) => {
const text = textNode.getTextContent();
if (containsJinja2Patterns(text)) {
const parent = textNode.getParent();
if (!parent) return;
// Skip if node already has styling (prevent infinite recursion)
if (textNode.getStyle()) return;
// Skip if no Jinja2 patterns found
if (!containsJinja2Patterns(text)) return;
const parent = textNode.getParent();
if (!parent) return;
const tokens = tokenizeJinja2(text);
const newNodes = tokens.map(token => {
const tokens = tokenizeJinja2(text);
// Skip if no meaningful tokenization (only one text token)
if (tokens.length <= 1 || (tokens.length === 1 && tokens[0].type === 'text')) return;
const newNodes = tokens.map(token => {
const newNode = $createTextNode(token.text);
switch (token.type) {
@@ -30,16 +39,16 @@ const Jinja2HighlightPlugin = () => {
newNode.setStyle('color: #008000');
break;
case 'brace-0':
newNode.setStyle('color: #d73a49; font-family: monospace; font-weight: bold;');
newNode.setStyle('color: #155EEF; font-family: monospace; font-weight: bold;');
break;
case 'brace-1':
newNode.setStyle('color: #0366d6; font-family: monospace; font-weight: bold;');
newNode.setStyle('color: #369F21; font-family: monospace; font-weight: bold;');
break;
case 'brace-2':
newNode.setStyle('color: #28a745; font-family: monospace; font-weight: bold;');
newNode.setStyle('color: #FF5D34; font-family: monospace; font-weight: bold;');
break;
case 'brace-3':
newNode.setStyle('color: #6f42c1; font-family: monospace; font-weight: bold;');
newNode.setStyle('color: #5B6167; font-family: monospace; font-weight: bold;');
break;
case 'expression-0':
case 'expression-1':
@@ -77,7 +86,6 @@ const Jinja2HighlightPlugin = () => {
newNodes[i - 1].insertAfter(newNodes[i]);
}
}
}
});
}, [editor]);

View File

@@ -1,7 +1,6 @@
import { type FC } from 'react'
import { useTranslation } from 'react-i18next';
import { Form, Input, Row, Col, Select, InputNumber, Radio } from 'antd'
import { MinusCircleOutlined, PlusOutlined } from '@ant-design/icons';
import { Form, Input, Select, InputNumber, Radio, Button, Space } from 'antd'
import type { Suggestion } from '../../Editor/plugin/AutocompletePlugin'
import VariableSelect from '../VariableSelect'
@@ -9,6 +8,7 @@ interface AssignmentListProps {
value?: Array<{ variable_selector: string; operation: string[]; value: string;}>;
parentName: string;
options: Suggestion[];
size?: 'small' | 'middle'
}
const operationsObj = {
@@ -31,6 +31,7 @@ const operationsObj = {
const AssignmentList: FC<AssignmentListProps> = ({
parentName,
options = [],
size = 'small'
}) => {
const { t } = useTranslation();
const form = Form.useFormInstance();
@@ -39,109 +40,126 @@ const AssignmentList: FC<AssignmentListProps> = ({
<Form.List name={parentName}>
{(fields, { add, remove }) => (
<>
<div className="rb:flex rb:justify-between">
{t(`workflow.config.assigner.${parentName}`)}
<PlusOutlined onClick={() => add({ operation: 'cover'})} />
</div>
{fields.map(({ key, name, ...restField }) => {
const variableSelector = form.getFieldValue([parentName, name, 'variable_selector']);
const selectedOption = options.find(option => `{{${option.value}}}` === variableSelector);
const dataType = selectedOption?.dataType;
const operationOptions = dataType === 'number' ? operationsObj.number : operationsObj.default;
return (
<div key={key} className="rb:mb-4">
<Row gutter={12} className="rb:mb-2!">
<Col span={14}>
<Form.Item
{...restField}
name={[name, 'variable_selector']}
noStyle
>
<VariableSelect
placeholder={t('common.pleaseSelect')}
options={options.filter(vo => vo.nodeData.type === 'loop' || vo.value.includes('conv.') || (vo.nodeData.type === 'iteration' && (vo.label === 'item' || vo.label === 'index')))}
popupMatchSelectWidth={false}
onChange={() => {
form.setFieldValue([parentName, name, 'operation'], undefined);
form.setFieldValue([parentName, name, 'value'], undefined);
}}
/>
</Form.Item>
</Col>
<Col span={8}>
<Form.Item
{...restField}
name={[name, 'operation']}
noStyle
>
<Select
placeholder={t('common.pleaseSelect')}
options={operationOptions.map(op => ({
...op,
label: t(op.label)
}))}
popupMatchSelectWidth={false}
onChange={() => {
form.setFieldValue([parentName, name, 'value'], undefined);
}}
/>
</Form.Item>
</Col>
<Col span={2} className="rb:flex! rb:items-center rb:justify-end">
<MinusCircleOutlined onClick={() => remove(name)} />
</Col>
</Row>
<div className="rb:flex rb:items-center rb:justify-between rb:mb-2.5">
<div className="rb:text-[12px] rb:leading-4.5 rb:font-medium">
{t(`workflow.config.assigner.${parentName}`)}
</div>
<Form.Item shouldUpdate noStyle>
{(form) => {
const operation = form.getFieldValue([parentName, name, 'operation']);
if (operation === 'clear') return null;
return (
<Button
onClick={() => add({ operation: 'cover' })}
className="rb:py-0! rb:px-1! rb:text-[12px]!"
size="small"
>
+ {t('workflow.config.addVariable')}
</Button>
</div>
<Space size={10} direction="vertical" className="rb:w-full!">
{fields.map(({ key, name, ...restField }) => {
const variableSelector = form.getFieldValue([parentName, name, 'variable_selector']);
const selectedOption = options.find(option => `{{${option.value}}}` === variableSelector);
const dataType = selectedOption?.dataType;
const operationOptions = dataType === 'number' ? operationsObj.number : operationsObj.default;
return (
<div key={key} className="rb:flex rb:items-start">
<div className="rb:flex-1">
<div className="rb:flex rb:gap-1 rb:mb-1">
<Form.Item
{...restField}
name={[name, 'value']}
name={[name, 'variable_selector']}
noStyle
>
{dataType === 'number' && operation === 'cover'
? <VariableSelect
placeholder={t('common.pleaseSelect')}
options={dataType ? options.filter(vo => vo.dataType === dataType) : options}
popupMatchSelectWidth={false}
/>
: dataType === 'number'
? <InputNumber
placeholder={t('common.pleaseEnter')}
className="rb:w-full!"
onChange={(value) => form.setFieldValue([name, 'value'], value)}
/>
: operation === 'assign'
? <>
{dataType === 'boolean'
? <Radio.Group block>
<Radio.Button value={true}>True</Radio.Button>
<Radio.Button value={false}>False</Radio.Button>
</Radio.Group>
: <Input.TextArea
placeholder={t('common.pleaseEnter')}
rows={3}
/>
}
</>
: <VariableSelect
placeholder={t('common.pleaseSelect')}
options={dataType ? options.filter(vo => vo.dataType === dataType) : options}
popupMatchSelectWidth={false}
/>
}
<VariableSelect
placeholder={t('common.pleaseSelect')}
options={options.filter(vo => vo.nodeData.type === 'loop' || vo.value.includes('conv.') || (vo.nodeData.type === 'iteration' && (vo.label === 'item' || vo.label === 'index')))}
popupMatchSelectWidth={false}
onChange={() => {
form.setFieldValue([parentName, name, 'operation'], undefined);
form.setFieldValue([parentName, name, 'value'], undefined);
}}
size={size}
className="rb:w-39!"
/>
</Form.Item>
);
}}
</Form.Item>
</div>
)
})}
<Form.Item
{...restField}
name={[name, 'operation']}
noStyle
>
<Select
placeholder={t('common.pleaseSelect')}
options={operationOptions.map(op => ({
...op,
label: t(op.label)
}))}
popupMatchSelectWidth={false}
onChange={() => {
form.setFieldValue([parentName, name, 'value'], undefined);
}}
size={size}
className="rb:w-24!"
/>
</Form.Item>
</div>
<Form.Item shouldUpdate noStyle>
{(form) => {
const operation = form.getFieldValue([parentName, name, 'operation']);
if (operation === 'clear') return null;
return (
<Form.Item
{...restField}
name={[name, 'value']}
noStyle
>
{dataType === 'number' && operation === 'cover'
? <VariableSelect
placeholder={t('common.pleaseSelect')}
options={dataType ? options.filter(vo => vo.dataType === dataType) : options}
popupMatchSelectWidth={false}
size={size}
/>
: dataType === 'number'
? <InputNumber
placeholder={t('common.pleaseEnter')}
className="rb:w-full!"
onChange={(value) => form.setFieldValue([name, 'value'], value)}
size={size}
/>
: operation === 'assign'
? <>
{dataType === 'boolean'
? <Radio.Group block size={size}>
<Radio.Button value={true}>True</Radio.Button>
<Radio.Button value={false}>False</Radio.Button>
</Radio.Group>
: <Input.TextArea
placeholder={t('common.pleaseEnter')}
rows={3}
/>
}
</>
: <VariableSelect
placeholder={t('common.pleaseSelect')}
options={dataType ? options.filter(vo => vo.dataType === dataType) : options}
popupMatchSelectWidth={false}
size={size}
/>
}
</Form.Item>
);
}}
</Form.Item>
</div>
<div
className="rb:ml-1 rb:size-4 rb:cursor-pointer rb:bg-cover rb:bg-[url('@/assets/images/workflow/deleteBg.svg')] rb:hover:bg-[url('@/assets/images/workflow/deleteBg_hover.svg')]"
onClick={() => remove(name)}
></div>
</div>
)
})}
</Space>
</>
)}
</Form.List>

View File

@@ -1,8 +1,7 @@
import { type FC } from 'react'
import clsx from 'clsx'
import { useTranslation } from 'react-i18next';
import { Form, Button, Select, Space, Row, Col, Divider, InputNumber, Radio, type SelectProps } from 'antd'
import { DeleteOutlined } from '@ant-design/icons';
import { Form, Button, Select, Space, Divider, InputNumber, Radio, type SelectProps } from 'antd'
import type { Suggestion } from '../../Editor/plugin/AutocompletePlugin'
import VariableSelect from '../VariableSelect'
@@ -247,37 +246,40 @@ const CaseList: FC<CaseListProps> = ({
{(conditionFields, { add: addCondition, remove: removeCondition }) => {
const logicalOperator = form.getFieldValue(name)?.[caseIndex]?.logical_operator || 'and'
return (
<div className={clsx("rb:relative rb:mb-4 rb:border rb:border-gray-200 rb:rounded rb:p-3 rb:pl-5")}>
<div className="rb:flex rb:items-center rb:justify-between rb:mb-3">
<span className="rb:font-medium">
{caseIndex === 0 ? 'IF' : 'ELIF'}<br/>
{caseFields.length > 1 && <span className="rb:text-[10px] rb:text-[#5B6167]">{`CASE ${caseIndex + 1}`}</span>}
</span>
<div className={clsx("rb:relative")}>
<div className="rb:flex rb:items-center rb:justify-between rb:mb-2">
<div className="rb:text-[12px] rb:leading-4.5">
<span className="rb:font-medium ">{caseIndex === 0 ? 'IF' : 'ELIF'}</span>
{caseFields.length > 1 && <span className="rb:text-[10px] rb:text-[#5B6167]"> ({`CASE ${caseIndex + 1}`})</span>}
</div>
<Space>
<Button
type="dashed"
onClick={() => addCondition({})}
className="rb:py-0! rb:px-1! rb:text-[12px]!"
size="small"
>
+ {t('workflow.config.addCase')}
</Button>
{caseFields.length > 1 && <DeleteOutlined
className="rb:text-[12px]"
onClick={() => handleRemoveCase(removeCase, caseField.name, caseIndex)}
/>}
{caseFields.length > 1 &&
<Button
className="rb:py-0! rb:px-1! rb:text-[12px]!"
onClick={() => handleRemoveCase(removeCase, caseField.name, caseIndex)}
>
{t('common.remove')}
</Button>
}
</Space>
</div>
{conditionFields?.length > 1 &&
<>
<div className="rb:absolute rb:w-3 rb:left-2 rb:top-15 rb:bottom-6 rb:z-10 rb:border rb:border-[#DFE4ED] rb:rounded-l-md rb:border-r-0"></div>
<div className="rb:absolute rb:z-10 rb:left-0 rb:top-[50%] rb:transform-[translateY(-50%)]]">
{conditionFields?.length > 1 && <div className="rb:absolute rb:top-8 rb:bottom-4 rb:w-8.5 rb:h-[calc(100%-32px)]">
<div className="rb:absolute rb:w-2.5 rb:h-[calc(50%-30px)] rb:left-5 rb:top-4 rb:z-10 rb:border-l rb:border-t rb:border-[#DFE4ED] rb:rounded-tl-[10px] rb:border-r-0"></div>
<div className="rb:absolute rb:z-10 rb:left-0 rb:top-[calc(50%-13px)]">
<Form.Item name={[caseField.name, 'logical_operator']} noStyle >
<Button size="small" className="rb:cursor-pointer" onClick={() => handleChangeLogicalOperator(caseIndex)}>{logicalOperator}</Button>
<Button size="small" className="rb:text-[12px]! rb:py-px! rb:px-1! rb:w-8.5! rb:h-5!" onClick={() => handleChangeLogicalOperator(caseIndex)}>{logicalOperator}</Button>
</Form.Item>
</div>
</>
}
<div className="rb:absolute rb:w-2.5 rb:h-[calc(50%-30px)] rb:left-5 rb:bottom-4 rb:z-10 rb:border-l rb:border-b rb:border-[#DFE4ED] rb:rounded-bl-[10px] rb:border-r-0"></div>
</div>}
{conditionFields.map((conditionField, conditionIndex) => {
const cases = form.getFieldValue(name) || [];
const currentCase = cases[caseIndex] || {};
@@ -290,91 +292,86 @@ const CaseList: FC<CaseListProps> = ({
const operatorList = operatorsObj[leftFieldType || 'default'] || operatorsObj.default || [];
const inputType = leftFieldType === 'number' ? currentExpression.input_type : undefined;
return (
<div key={conditionField.key} className={clsx({
"rb:mb-3": conditionIndex !== conditionFields.length - 1
})}>
<div className="rb:border rb:border-[#DFE4ED] rb:rounded-md rb:px-2 rb:py-1.5 rb:bg-white">
<Row gutter={12} className="rb:mb-1">
<Col span={14}>
<Form.Item name={[conditionField.name, 'left']} noStyle>
<VariableSelect
placeholder={t('common.pleaseSelect')}
options={options}
size="small"
allowClear={false}
popupMatchSelectWidth={false}
onChange={(val) => handleLeftFieldChange(caseIndex, conditionIndex, val)}
/>
</Form.Item>
</Col>
<Col span={8}>
<Form.Item name={[conditionField.name, 'operator']} noStyle>
<Select
options={operatorList.map(vo => ({
...vo,
label: t(String(vo?.label || ''))
}))}
size="small"
popupMatchSelectWidth={false}
placeholder={t('common.pleaseSelect')}
/>
</Form.Item>
</Col>
<Col span={2}>
<DeleteOutlined
className="rb:text-[12px]"
onClick={() => removeCondition(conditionField.name)}
<div key={conditionField.key} className="rb:flex rb:items-start rb:ml-9.5 rb:mb-4">
<div className="rb:flex-1 rb:bg-[#F6F8FC] rb:border rb:border-[#DFE4ED] rb:rounded-md">
<div className={clsx("rb:flex rb:gap-1 rb:p-1", {
'rb:border-b rb:border-b-[#DFE4ED]': !hideRightField
})}>
<Form.Item name={[conditionField.name, 'left']} noStyle>
<VariableSelect
placeholder={t('common.pleaseSelect')}
options={options}
size="small"
allowClear={false}
popupMatchSelectWidth={false}
onChange={(val) => handleLeftFieldChange(caseIndex, conditionIndex, val)}
className="rb:bg-white! rb:w-29.5!"
/>
</Col>
</Row>
</Form.Item>
<Form.Item name={[conditionField.name, 'operator']} noStyle>
<Select
options={operatorList.map(vo => ({
...vo,
label: t(String(vo?.label || ''))
}))}
size="small"
popupMatchSelectWidth={false}
placeholder={t('common.pleaseSelect')}
className="rb:bg-white! rb:w-22!"
/>
</Form.Item>
</div>
{!hideRightField && <>
{!hideRightField && <div className="rb:p-1">
{leftFieldType === 'number'
? <Row>
<Col span={12}>
<Form.Item name={[conditionField.name, 'input_type']} noStyle>
<Select
? <div className="rb:flex rb:items-center">
<Form.Item name={[conditionField.name, 'input_type']} noStyle>
<Select
placeholder={t('common.pleaseSelect')}
options={[{ value: 'Variable', label: 'Variable' }, { value: 'Constant', label: 'Constant' }]}
popupMatchSelectWidth={false}
variant="borderless"
onChange={() => handleInputTypeChange(caseIndex, conditionIndex)}
className="rb:w-18!"
/>
</Form.Item>
<Divider type="vertical" />
<Form.Item name={[conditionField.name, 'right']} noStyle>
{inputType === 'Variable'
?
<VariableSelect
placeholder={t('common.pleaseSelect')}
options={[{ value: 'Variable', label: 'Variable' }, { value: 'Constant', label: 'Constant' }]}
options={options.filter(vo => vo.dataType === 'number')}
allowClear={false}
popupMatchSelectWidth={false}
variant="borderless"
onChange={() => handleInputTypeChange(caseIndex, conditionIndex)}
size="small"
/>
</Form.Item>
</Col>
<Col span={12}>
<Form.Item name={[conditionField.name, 'right']} noStyle>
{inputType === 'Variable'
?
<VariableSelect
placeholder={t('common.pleaseSelect')}
options={options.filter(vo => vo.dataType === 'number')}
allowClear={false}
popupMatchSelectWidth={false}
: <InputNumber
placeholder={t('common.pleaseEnter')}
variant="borderless"
className="rb:w-full!"
onChange={(value) => form.setFieldValue([name, caseIndex, 'expressions', conditionIndex, 'right'], value)}
/>
: <InputNumber
placeholder={t('common.pleaseEnter')}
variant="borderless"
className="rb:w-full!"
onChange={(value) => form.setFieldValue([name, caseIndex, 'expressions', conditionIndex, 'right'], value)}
/>
}
</Form.Item>
</Col>
</Row>
}
</Form.Item>
</div>
: <Form.Item name={[conditionField.name, 'right']} noStyle>
{leftFieldType === 'boolean'
? <Radio.Group block>
<Radio.Button value={true}>True</Radio.Button>
<Radio.Button value={false}>False</Radio.Button>
</Radio.Group>
: <Editor options={options} />
: <Editor options={options} size="small" type="input" />
}
</Form.Item>
}
</>}
</div>}
</div>
<div
className="rb:ml-1 rb:size-4 rb:cursor-pointer rb:bg-cover rb:bg-[url('@/assets/images/workflow/deleteBg.svg')] rb:hover:bg-[url('@/assets/images/workflow/deleteBg_hover.svg')]"
onClick={() => removeCondition(conditionField.name)}
></div>
</div>
)
})}
@@ -388,6 +385,8 @@ const CaseList: FC<CaseListProps> = ({
<Button
type="dashed"
block
size="middle"
className="rb:text-[12px]!"
onClick={() => handleAddCase(addCase)}
>
+ ELIF
@@ -395,9 +394,9 @@ const CaseList: FC<CaseListProps> = ({
</>
)}
</Form.List>
<Divider />
<div className="rb:font-medium">ELSE</div>
<div className="rb:text-[12px] rb:text-[#5B6167] ">{t('workflow.config.if-else.else_desc')}</div>
<div className="rb:font-medium rb:text-[12px] rb:mt-4 rb:leading-4.5">ELSE</div>
<div className="rb:text-[12px] rb:text-[#5B6167] rb:mt-2 rb:leading-4.5">{t('workflow.config.if-else.else_desc')}</div>
</>
)
}

View File

@@ -1,8 +1,8 @@
import { type FC } from 'react';
import { useTranslation } from 'react-i18next';
import { Button, Form, Space } from 'antd';
import { DeleteOutlined } from '@ant-design/icons';
import { Graph, Node } from '@antv/x6';
import Editor from '../../Editor';
import type { Suggestion } from '../../Editor/plugin/AutocompletePlugin'
@@ -151,17 +151,15 @@ const CategoryList: FC<CategoryListProps> = ({ parentName, selectedNode, graphRe
const contentLength = (currentItem.class_name || '').length;
return (
<div key={key} className="rb:border rb:border-[#DFE4ED] rb:rounded-md rb:p-3 rb:bg-[#F8F9FB]">
<div key={key} className="rb:border rb:border-[#DFE4ED] rb:rounded-md rb:p-2 rb:bg-[#F8F9FB]">
<div className="rb:flex rb:items-center rb:justify-between rb:mb-2">
<div>{t('workflow.config.question-classifier.class_name')} {index + 1}</div>
<div className="rb:text-[12px] rb:font-medium rb:py-1 rb:leading-2">{t('workflow.config.question-classifier.class_name')} {index + 1}</div>
<div className="rb:flex rb:items-center rb:gap-1">
<span className="rb:text-xs rb:text-gray-500">{contentLength}</span>
<Button
type="text"
size="small"
icon={<DeleteOutlined />}
<div
className="rb:ml-1 rb:size-4 rb:cursor-pointer rb:bg-cover rb:bg-[url('@/assets/images/workflow/deleteBg.svg')] rb:hover:bg-[url('@/assets/images/workflow/deleteBg_hover.svg')]"
onClick={() => handleRemoveCategory(remove, name, index)}
/>
></div>
</div>
</div>
<Form.Item
@@ -172,6 +170,7 @@ const CategoryList: FC<CategoryListProps> = ({ parentName, selectedNode, graphRe
<Editor
placeholder={t('common.pleaseEnter')}
options={options}
size="small"
/>
</Form.Item>
</div>
@@ -179,8 +178,10 @@ const CategoryList: FC<CategoryListProps> = ({ parentName, selectedNode, graphRe
<Button
type="dashed"
size="middle"
block
onClick={() => handleAddCategory(add)}
className="rb:w-full"
className="rb:text-[12px]!"
>
+ {t('workflow.config.question-classifier.addClassName')}
</Button>

View File

@@ -1,11 +1,10 @@
import { type FC } from 'react'
import clsx from 'clsx'
import { useTranslation } from 'react-i18next';
import { Form, Button, Select, Row, Col, InputNumber, Radio, Input, type SelectProps } from 'antd'
import { DeleteOutlined } from '@ant-design/icons';
import { Form, Button, Select, InputNumber, Radio, Input, Divider, type SelectProps } from 'antd'
import type { Suggestion } from '../../Editor/plugin/AutocompletePlugin'
import VariableSelect from '../VariableSelect'
import Editor from '../../Editor'
interface Case {
logical_operator: 'and' | 'or';
@@ -84,52 +83,64 @@ const ConditionList: FC<CaseListProps> = ({
return (
<>
<Form.List name={[parentName, 'expressions']}>
{(fields, { add, remove }) => (
<div>
{(fields, { add, remove }) => {
const logicalOperator = form.getFieldValue([parentName, 'logical_operator']);
return (
<div className="rb:relative">
{fields.map((field, index) => {
const expressions = form.getFieldValue([parentName, 'expressions']) || [];
const currentExpression = expressions[index] || {};
const currentOperator = currentExpression.operator;
const hideRightField = currentOperator === 'empty' || currentOperator === 'not_empty';
const leftFieldValue = currentExpression.left;
const leftFieldOption = options.find(option => `{{${option.value}}}` === leftFieldValue);
const leftFieldType = leftFieldOption?.dataType;
const operatorList = operatorsObj[leftFieldType || 'default'] || operatorsObj.default || [];
const inputType = leftFieldType === 'number' ? currentExpression.input_type : undefined;
const logicalOperator = form.getFieldValue([parentName, 'logical_operator']);
return (
<div key={field.key} className="rb:mb-3">
{index > 0 && (<>
<div className="rb:absolute rb:w-3 rb:left-2 rb:top-3.75 rb:bottom-3.75 rb:z-10 rb:border rb:border-[#DFE4ED] rb:rounded-l-md rb:border-r-0"></div>
<div className="rb:absolute rb:z-10 rb:left-0 rb:top-[50%] rb:transform-[translateY(-50%)]]">
<Form.Item name={[parentName, 'logical_operator']} noStyle >
<Button size="small" className="rb:cursor-pointer" onClick={handleChangeLogicalOperator}>{logicalOperator}</Button>
</Form.Item>
</div>
</>)}
<div className="rb:border rb:border-[#DFE4ED] rb:rounded-md rb:p-3 rb:bg-white rb:ml-6">
<Row gutter={8} align="middle">
<Col span={14}>
<div className="rb:flex rb:items-center rb:justify-between rb:mb-2">
<div className="rb:text-[12px] rb:font-medium rb:leading-4.5">
{t('workflow.config.loop.condition')}
</div>
<Button
onClick={() => add({})}
className="rb:py-0! rb:px-1! rb:text-[12px]!"
size="small"
>
+ {t('workflow.config.loop.addCondition')}
</Button>
</div>
{fields?.length > 1 && <div className="rb:absolute rb:top-8 rb:bottom-4 rb:w-8.5 rb:h-[calc(100%-32px)]">
<div className="rb:absolute rb:w-2.5 rb:h-[calc(50%-30px)] rb:left-5 rb:top-4 rb:z-10 rb:border-l rb:border-t rb:border-[#DFE4ED] rb:rounded-tl-[10px] rb:border-r-0"></div>
<div className="rb:absolute rb:z-10 rb:left-0 rb:top-[calc(50%-13px)]">
<Form.Item name={[parentName, 'logical_operator']} noStyle >
<Button size="small" className="rb:text-[12px]! rb:py-px! rb:px-1! rb:w-8.5! rb:h-5!" onClick={handleChangeLogicalOperator}>{logicalOperator}</Button>
</Form.Item>
</div>
<div className="rb:absolute rb:w-2.5 rb:h-[calc(50%-30px)] rb:left-5 rb:bottom-4 rb:z-10 rb:border-l rb:border-b rb:border-[#DFE4ED] rb:rounded-bl-[10px] rb:border-r-0"></div>
</div>}
{fields.map((field, index) => {
const expressions = form.getFieldValue([parentName, 'expressions']) || [];
const currentExpression = expressions[index] || {};
const currentOperator = currentExpression.operator;
const hideRightField = currentOperator === 'empty' || currentOperator === 'not_empty';
const leftFieldValue = currentExpression.left;
const leftFieldOption = options.find(option => `{{${option.value}}}` === leftFieldValue);
const leftFieldType = leftFieldOption?.dataType;
const operatorList = operatorsObj[leftFieldType || 'default'] || operatorsObj.default || [];
const inputType = leftFieldType === 'number' ? currentExpression.input_type : undefined;
return (
<div key={field.key} className="rb:flex rb:items-start rb:ml-9.5 rb:mb-4">
<div className="rb:flex-1 rb:bg-[#F6F8FC] rb:border rb:border-[#DFE4ED] rb:rounded-md">
<div className={clsx("rb:flex rb:gap-1 rb:p-1", {
'rb:border-b rb:border-b-[#DFE4ED]': !hideRightField
})}>
<Form.Item name={[field.name, 'left']} noStyle>
<VariableSelect
options={options.filter(vo =>
vo.value.includes('sys.') ||
vo.value.includes('conv.') ||
options={options.filter(vo =>
vo.value.includes('sys.') ||
vo.value.includes('conv.') ||
vo.nodeData.type === 'loop' ||
(vo.nodeData.cycle && vo.nodeData.cycle === selectedNode?.id)
)}
size="small"
allowClear={false}
popupMatchSelectWidth={false}
placeholder={t('common.pleaseSelect')}
onChange={(val) => handleLeftFieldChange(index, val)}
/>
</Form.Item>
</Col>
<Col span={8}>
<Form.Item name={[field.name, 'operator']} noStyle>
<Select
options={operatorList.map(vo => ({
@@ -138,84 +149,67 @@ const ConditionList: FC<CaseListProps> = ({
}))}
size="small"
popupMatchSelectWidth={false}
placeholder={t('common.pleaseSelect')}
/>
</Form.Item>
</Col>
<Col span={2}>
<DeleteOutlined
className="rb:text-gray-400 rb:cursor-pointer rb:hover:text-red-500"
onClick={() => remove(field.name)}
/>
</Col>
</div>
{!hideRightField && <>
{!hideRightField && <div className="rb:p-1">
{leftFieldType === 'number'
? <Col span={24}><Row>
<Col span={12}>
<Form.Item name={[field.name, 'input_type']} noStyle>
<Select
? <div className="rb:flex rb:items-center">
<Form.Item name={[field.name, 'input_type']} noStyle>
<Select
placeholder={t('common.pleaseSelect')}
options={[{ value: 'Variable', label: 'Variable' }, { value: 'Constant', label: 'Constant' }]}
popupMatchSelectWidth={false}
variant="borderless"
className="rb:w-full!"
onChange={() => handleInputTypeChange(index)}
/>
</Form.Item>
<Divider type="vertical" />
<Form.Item name={[field.name, 'right']} noStyle>
{inputType === 'Variable'
?
<VariableSelect
placeholder={t('common.pleaseSelect')}
options={[{ value: 'Variable', label: 'Variable' }, { value: 'Constant', label: 'Constant' }]}
options={options.filter(vo => vo.dataType === 'number')}
allowClear={false}
popupMatchSelectWidth={false}
variant="borderless"
className="rb:w-full!"
onChange={() => handleInputTypeChange(index)}
/>
</Form.Item>
</Col>
<Col span={12}>
<Form.Item name={[field.name, 'right']} noStyle>
{inputType === 'Variable'
?
<VariableSelect
placeholder={t('common.pleaseSelect')}
options={options.filter(vo => vo.dataType === 'number')}
allowClear={false}
popupMatchSelectWidth={false}
variant="borderless"
className="rb:w-full!"
/>
: <InputNumber
placeholder={t('common.pleaseEnter')}
variant="borderless"
className="rb:w-full!"
onChange={(value) => form.setFieldValue([parentName, 'expressions', index, 'right'], value)}
/>
}
</Form.Item>
</Col>
</Row></Col>
: <Col span={24}>
<Form.Item name={[field.name, 'right']} noStyle>
{leftFieldType === 'boolean'
? <Radio.Group block>
<Radio.Button value={true}>True</Radio.Button>
<Radio.Button value={false}>False</Radio.Button>
</Radio.Group>
: <Input placeholder={t('common.pleaseEnter')} />
: <InputNumber
placeholder={t('common.pleaseEnter')}
variant="borderless"
className="rb:w-full!"
onChange={(value) => form.setFieldValue([parentName, 'expressions', index, 'right'], value)}
/>
}
</Form.Item>
</Col>
</div>
: <Form.Item name={[field.name, 'right']} noStyle>
{leftFieldType === 'boolean'
? <Radio.Group block>
<Radio.Button value={true}>True</Radio.Button>
<Radio.Button value={false}>False</Radio.Button>
</Radio.Group>
: <Input placeholder={t('common.pleaseEnter')} />
}
</Form.Item>
}
</>}
</Row>
</div>}
</div>
<div
className="rb:ml-1 rb:size-4 rb:cursor-pointer rb:bg-cover rb:bg-[url('@/assets/images/workflow/deleteBg.svg')] rb:hover:bg-[url('@/assets/images/workflow/deleteBg_hover.svg')]"
onClick={() => remove(field.name)}
></div>
</div>
</div>
)
})}
)
})}
</div>
<Button
type="dashed"
onClick={() => add({ left: '', operator: '', right: '' })}
className="rb:w-full rb:ml-6 rb:mt-2"
icon={<span>+</span>}
>
</Button>
</div>
)}
)
}}
</Form.List>
</>
)

View File

@@ -1,7 +1,6 @@
import { type FC } from 'react'
import { useTranslation } from 'react-i18next';
import { Form, Select, Row, Col, Input } from 'antd'
import { DeleteOutlined, PlusOutlined } from '@ant-design/icons';
import { Form, Select, Input, Button } from 'antd'
import VariableSelect from '../VariableSelect'
import type { Suggestion } from '../../Editor/plugin/AutocompletePlugin'
@@ -20,6 +19,7 @@ interface CycleVarsListProps {
parentName: string;
selectedNode?: any;
graphRef?: any;
size?: 'small' | 'middle'
}
const types = [
@@ -37,7 +37,8 @@ const CycleVarsList: FC<CycleVarsListProps> = ({
options,
parentName,
selectedNode,
graphRef
graphRef,
size = 'middle'
}) => {
const { t } = useTranslation();
const form = Form.useFormInstance();
@@ -78,62 +79,56 @@ const CycleVarsList: FC<CycleVarsListProps> = ({
const availableOptions = getChildNodeVariables();
return (
<div>
<Form.List name={parentName}>
{(fields, { add, remove }) => (
<>
<div className="rb:flex rb:items-center rb:justify-between rb:mb-3">
<span className="rb:text-sm rb:font-medium"></span>
<PlusOutlined className="rb:text-gray-400 rb:cursor-pointer rb:hover:text-blue-500" onClick={() => add({ name: '', type: 'string', input_type: 'constant', value: '' })} />
</div>
{fields.map(({ key, name, ...field }, index) => {
const currentInputType = value?.[index]?.input_type;
return (
<div key={key} className="rb:mb-3 rb:border rb:border-[#DFE4ED] rb:rounded-md rb:p-3 rb:bg-white">
<Row gutter={8} align="middle" className="rb:mb-2">
<Col span={8}>
<Form.Item name={[name, 'name']} noStyle>
<Input size="small" />
</Form.Item>
</Col>
<Col span={6}>
<Form.Item name={[name, 'type']} noStyle>
<Select
options={types.map(key => ({
value: key,
label: t(`workflow.config.parameter-extractor.${key}`),
}))}
size="small"
popupMatchSelectWidth={false}
/>
</Form.Item>
</Col>
<Col span={8}>
<Form.Item name={[name, 'input_type']} noStyle>
<Select
placeholder="Constant"
options={[
{ label: 'Constant', value: 'constant' },
{ label: 'Variable', value: 'variable' }
]}
size="small"
popupMatchSelectWidth={false}
onChange={() => {
// 重置 value 字段
form.setFieldValue([parentName, index, 'value'], undefined);
}}
/>
</Form.Item>
</Col>
<Col span={2}>
<DeleteOutlined
className="rb:text-gray-400 rb:cursor-pointer rb:hover:text-red-500"
onClick={() => remove(name)}
<Form.List name={parentName}>
{(fields, { add, remove }) => (
<>
<div className="rb:flex rb:items-center rb:justify-between rb:mb-3">
<span className="rb:text-[12px] rb:font-medium">{t('workflow.config.loop.cycle_vars')}</span>
<Button
onClick={() => add({ name: '', type: 'string', input_type: 'constant', value: '' })}
className="rb:py-0! rb:px-1! rb:text-[12px]!"
size="small"
>
+ {t('workflow.config.addVariable')}
</Button>
</div>
{fields.map(({ key, name }, index) => {
const currentInputType = value?.[index]?.input_type;
return (
<div key={key} className="rb:flex rb:items-start rb:mb-2">
<div className="rb:flex-1 rb:bg-[#F6F8FC] rb:border rb:border-[#DFE4ED] rb:rounded-md">
<div className="rb:flex rb:gap-1 rb:p-1 rb:border-b rb:border-b-[#DFE4ED]">
<Form.Item name={[name, 'name']} noStyle>
<Input size={size} className="rb:w-23!" placeholder={t('common.pleaseEnter')} />
</Form.Item>
<Form.Item name={[name, 'type']} noStyle>
<Select
options={types.map(key => ({
value: key,
label: t(`workflow.config.parameter-extractor.${key}`),
}))}
size={size}
popupMatchSelectWidth={false}
className="rb:w-18.5!"
/>
</Col>
</Row>
</Form.Item>
<Form.Item name={[name, 'input_type']} noStyle>
<Select
placeholder="Constant"
options={[
{ label: 'Constant', value: 'constant' },
{ label: 'Variable', value: 'variable' }
]}
size={size}
popupMatchSelectWidth={false}
onChange={() => {
form.setFieldValue([parentName, index, 'value'], undefined);
}}
className="rb:w-18!"
/>
</Form.Item>
</div>
<Form.Item name={[name, 'value']} noStyle>
{currentInputType === 'variable' ? (
@@ -145,22 +140,29 @@ const CycleVarsList: FC<CycleVarsListProps> = ({
return option.dataType === currentType
})}
variant="borderless"
size="small"
/>
) : (
<Input.TextArea
placeholder={t('common.pleaseEnter')}
rows={3}
className="rb:w-full"
variant="borderless"
/>
)}
</Form.Item>
</div>
)
})}
</>
)}
</Form.List>
</div>
<div
className="rb:ml-1 rb:size-4 rb:cursor-pointer rb:bg-cover rb:bg-[url('@/assets/images/workflow/deleteBg.svg')] rb:hover:bg-[url('@/assets/images/workflow/deleteBg_hover.svg')]"
onClick={() => remove(name)}
></div>
</div>
)
})}
</>
)}
</Form.List>
)
}

View File

@@ -1,7 +1,7 @@
import { type FC } from 'react'
import { useTranslation } from 'react-i18next';
import { Form, Input, Button, Row, Col } from 'antd'
import { MinusCircleOutlined } from '@ant-design/icons';
import type { Suggestion } from '../../Editor/plugin/AutocompletePlugin'
import VariableSelect from '../VariableSelect'
@@ -9,13 +9,15 @@ interface GroupVariableListProps {
value?: Array<{ key: string; value: string[]; }>;
name: string;
options: Suggestion[];
isCanAdd: boolean
isCanAdd: boolean;
size: 'small' | 'middle'
}
const GroupVariableList: FC<GroupVariableListProps> = ({
name,
options = [],
isCanAdd = false
isCanAdd = false,
size = "middle"
}) => {
const { t } = useTranslation();
const form = Form.useFormInstance();
@@ -54,6 +56,7 @@ const GroupVariableList: FC<GroupVariableListProps> = ({
placeholder={t('common.pleaseSelect')}
options={filteredOptions}
mode="multiple"
size={size}
/>
</Form.Item>
</div>
@@ -76,11 +79,15 @@ const GroupVariableList: FC<GroupVariableListProps> = ({
]}
noStyle
>
{isCanAdd ? <Input placeholder={t('common.pleaseEnter')} /> : t('workflow.config.var-aggregator.variable')}
{isCanAdd ? <Input placeholder={t('common.pleaseEnter')} size={size} /> : t('workflow.config.var-aggregator.variable')}
</Form.Item>
</Col>
{isCanAdd && <Col span={12} className="rb:flex! rb:items-center rb:justify-end">
<MinusCircleOutlined onClick={() => remove(name)} />
<div
className="rb:ml-1 rb:size-4 rb:cursor-pointer rb:bg-cover rb:bg-[url('@/assets/images/workflow/deleteBg.svg')] rb:hover:bg-[url('@/assets/images/workflow/deleteBg_hover.svg')]"
onClick={() => remove(name)}
></div>
</Col>}
</Row>
@@ -104,16 +111,22 @@ const GroupVariableList: FC<GroupVariableListProps> = ({
})()
}
mode="multiple"
size={size}
/>
</Form.Item>
</div>
)
})}
{isCanAdd && <Form.Item noStyle>
<Button type="dashed" onClick={() => add({ key: `Group${fields.length + 1}` })} block>
+ {t('workflow.config.var-aggregator.addGroup')}
</Button>
</Form.Item>}
{isCanAdd && <Button
type="dashed"
block
size="middle"
className="rb:text-[12px]!"
onClick={() => add({ key: `Group${fields.length + 1}` })}
>
+ {t('workflow.config.var-aggregator.addGroup')}
</Button>}
</>
)}
</Form.List>

View File

@@ -93,6 +93,7 @@ const AuthConfigModal = forwardRef<AuthConfigModalRef, AuthConfigModalProps>(({
initialValues={{
auth: 'none'
}}
size="middle"
>
<FormItem
name="auth"
@@ -102,6 +103,7 @@ const AuthConfigModal = forwardRef<AuthConfigModalRef, AuthConfigModalProps>(({
]}
>
<Select
size="middle"
options={[
{ value: 'none', label: t('workflow.config.http-request.none') },
{ value: 'api_key', label: t('workflow.config.http-request.apiKey') },
@@ -117,6 +119,7 @@ const AuthConfigModal = forwardRef<AuthConfigModalRef, AuthConfigModalProps>(({
]}
>
<Select
size="middle"
options={[
{ value: 'basic', label: t('workflow.config.http-request.basic') },
{ value: 'bearer', label: t('workflow.config.http-request.bearer') },
@@ -132,7 +135,7 @@ const AuthConfigModal = forwardRef<AuthConfigModalRef, AuthConfigModalProps>(({
{ required: true, message: t('common.pleaseEnter') }
]}
>
<Input placeholder={t('common.pleaseEnter')} />
<Input size="middle" placeholder={t('common.pleaseEnter')} />
</FormItem>
}
<FormItem
@@ -142,7 +145,7 @@ const AuthConfigModal = forwardRef<AuthConfigModalRef, AuthConfigModalProps>(({
{ required: true, message: t('common.pleaseEnter') }
]}
>
<Input placeholder={t('common.pleaseEnter')} />
<Input size="middle" placeholder={t('common.pleaseEnter')} />
</FormItem>
</>}
</Form>

View File

@@ -1,7 +1,6 @@
import { useMemo } from 'react';
import { useTranslation } from 'react-i18next'
import { Button, Select, Table, Form, type TableProps } from 'antd';
import { PlusOutlined, DeleteOutlined } from '@ant-design/icons';
import { PlusOutlined } from '@ant-design/icons';
import type { Suggestion } from '../../Editor/plugin/AutocompletePlugin';
import Empty from '@/components/Empty';
import VariableSelect from '../VariableSelect';
@@ -19,6 +18,7 @@ interface EditableTableProps {
options?: Suggestion[];
typeOptions?: { value: string, label: string }[]
filterBooleanType?: boolean;
size?: "small"
}
const EditableTable: React.FC<EditableTableProps> = ({
@@ -26,7 +26,8 @@ const EditableTable: React.FC<EditableTableProps> = ({
title,
options = [],
typeOptions = [],
filterBooleanType = false
filterBooleanType = false,
size = 'small'
}) => {
const { t } = useTranslation();
@@ -38,21 +39,24 @@ const EditableTable: React.FC<EditableTableProps> = ({
const getColumns = (remove: (index: number) => void): TableProps<TableRow>['columns'] => {
const hasType = typeOptions.length > 0;
const baseWidth = hasType ? '35%' : '45%';
const cellClassName="rb:p-1!"
const contentClassName ="rb:w-[108px]! rb:text-[12px]!"
return [
{
title: t('workflow.config.name'),
dataIndex: 'name',
width: baseWidth,
className: cellClassName,
render: (_: any, __: TableRow, index: number) => (
<Form.Item name={[index, 'name']} noStyle>
<VariableSelect
placeholder={t('common.pleaseSelect')}
size="small"
// size="small"
options={options}
filterBooleanType={filterBooleanType}
popupMatchSelectWidth={false}
className={contentClassName}
size={size}
/>
</Form.Item>
)
@@ -61,18 +65,20 @@ const EditableTable: React.FC<EditableTableProps> = ({
title: t('workflow.config.type'),
dataIndex: 'type',
width: '20%',
className: cellClassName,
render: (_: any, __: TableRow, index: number) => (
<Form.Item shouldUpdate noStyle>
{(form) => (
<Form.Item name={[index, 'type']} noStyle>
<Select
placeholder={t('common.pleaseSelect')}
size="small"
// size="small"
options={typeOptions}
popupMatchSelectWidth={false}
onChange={() => {
form.setFieldValue([...Array.isArray(parentName) ? parentName : [parentName], index, 'value'], undefined);
}}
size={size}
/>
</Form.Item>
)}
@@ -82,7 +88,7 @@ const EditableTable: React.FC<EditableTableProps> = ({
{
title: t('workflow.config.value'),
dataIndex: 'value',
width: baseWidth,
className: cellClassName,
render: (_: any, __: TableRow, index: number) => (
<Form.Item
shouldUpdate={(prevValues, currentValues) => {
@@ -102,10 +108,12 @@ const EditableTable: React.FC<EditableTableProps> = ({
<Form.Item name={[index, 'value']} noStyle>
<VariableSelect
placeholder={t('common.pleaseSelect')}
size="small"
// size="small"
options={filteredOptions}
filterBooleanType={filterBooleanType}
popupMatchSelectWidth={false}
className={contentClassName}
size={size}
/>
</Form.Item>
);
@@ -116,9 +124,12 @@ const EditableTable: React.FC<EditableTableProps> = ({
{
title: '',
dataIndex: 'actions',
width: '10%',
className: cellClassName,
render: (_: any, __: TableRow, index: number) => (
<Button type="text" icon={<DeleteOutlined />} onClick={() => remove(index)} />
<div
className="rb:ml-1 rb:size-4 rb:cursor-pointer rb:bg-cover rb:bg-[url('@/assets/images/workflow/deleteBg.svg')] rb:hover:bg-[url('@/assets/images/workflow/deleteBg_hover.svg')]"
onClick={() => remove(index)}
></div>
)
}
];
@@ -129,13 +140,11 @@ const EditableTable: React.FC<EditableTableProps> = ({
<Form.List name={parentName}>
{(fields, { add, remove }) => {
const AddButton = ({ block = false }: { block?: boolean }) => (
<Button
type={block ? "dashed" : "text"}
<Button
icon={block ? undefined : <PlusOutlined />}
onClick={() => add(createNewRow())}
size="small"
block={block}
className={block ? "rb:mt-1" : ""}
className={block ? "rb:mt-1 rb:text-[12px]! rb:bg-transparent!" : "rb:text-[12px]!"}
>
{block && `+${t('common.add')}`}
</Button>
@@ -145,8 +154,8 @@ const EditableTable: React.FC<EditableTableProps> = ({
<>
{title && (
<div className="rb:flex rb:items-center rb:mb-2 rb:justify-between">
<div className="rb:font-medium">{title}</div>
<AddButton />
<div className="rb:font-medium rb:text-[12px] rb:leading-4.5">{title}</div>
<AddButton block={true} />
</div>
)}
@@ -161,8 +170,9 @@ const EditableTable: React.FC<EditableTableProps> = ({
columns={getColumns(remove)}
pagination={false}
size="small"
rowClassName="rb:p-0! rb:bg-[#F6F8FC]!"
locale={{ emptyText: <Empty size={88} /> }}
scroll={{ x: 'max-content' }}
style={{ width: '274px' }}
/>
{!title && <AddButton block />}

View File

@@ -1,6 +1,7 @@
import { type FC, useRef } from "react";
import { type FC, useRef, useState } from "react";
import { useTranslation } from 'react-i18next'
import { Form, Row, Col, Select, Button, Divider, InputNumber, Switch, Input } from 'antd'
import { CaretDownOutlined, CaretRightOutlined, SettingOutlined } from '@ant-design/icons';
import Editor from '../../Editor'
import type { Suggestion } from '../../Editor/plugin/AutocompletePlugin'
import AuthConfigModal from './AuthConfigModal'
@@ -65,15 +66,23 @@ const HttpRequest: FC<{ options: Suggestion[]; selectedNode?: any; graphRef?: an
}
}
console.log('HttpRequest', values)
const [collapsed, setCollapsed] = useState(true)
const handleToggle = () => {
setCollapsed((prev: boolean) => !prev)
}
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 className="rb:flex rb:items-center rb:justify-between rb:mb-1">
<div className="rb:font-medium rb:text-[12px] rb:leading-4.5">API</div>
<Button onClick={handleChangeAuth}
size="small"
type="text"
icon={<SettingOutlined />}
className="rb:mt-1 rb:text-[12px]!"
>{t('workflow.config.http-request.auth')}: {!values?.auth?.auth_type || values?.auth?.auth_type === 'none' ? t('workflow.config.http-request.none') : t('workflow.config.http-request.apiKey')}</Button>
</div>
<Row gutter={16}>
<Row gutter={4}>
<Col span={8}>
<Form.Item name="method">
<Select
@@ -85,35 +94,43 @@ const HttpRequest: FC<{ options: Suggestion[]; selectedNode?: any; graphRef?: an
{ label: 'PUT', value: 'PUT' },
{ label: 'DELETE', value: 'DELETE' },
]}
className="rb:bg-transparent!"
/>
</Form.Item>
</Col>
<Col span={16}>
<Form.Item name="url">
<Editor options={options.filter(vo => vo.dataType === 'string' || vo.dataType === 'number')} variant="outlined" />
<Editor
options={options.filter(vo => vo.dataType === 'string' || vo.dataType === 'number')}
variant="outlined"
type="input"
size="small"
/>
</Form.Item>
</Col>
</Row>
<Form.Item name="auth" hidden>
</Form.Item>
<Form.Item name="headers">
<Form.Item name="headers" noStyle>
<EditableTable
size="small"
parentName="headers"
title="HEADERS"
options={options.filter(vo => vo.dataType === 'string' || vo.dataType === 'number')}
/>
</Form.Item>
<Form.Item name="params">
<Form.Item name="params" noStyle>
<EditableTable
size="small"
parentName="params"
title="PARAMS"
options={options.filter(vo => vo.dataType === 'string' || vo.dataType === 'number')}
/>
</Form.Item>
<Form.Item label="BODY">
<Form.Item label="BODY" className="rb:mb-0!">
<Form.Item name={['body', 'content_type']}>
<Select
placeholder={t('common.pleaseSelect')}
@@ -131,6 +148,7 @@ const HttpRequest: FC<{ options: Suggestion[]; selectedNode?: any; graphRef?: an
{values?.body?.content_type === 'form-data' &&
<Form.Item name={['body', 'data']} noStyle>
<EditableTable
size="small"
parentName={['body', 'data']}
options={options.filter(vo => vo.dataType === 'string' || vo.dataType === 'number')}
typeOptions={[
@@ -143,6 +161,7 @@ const HttpRequest: FC<{ options: Suggestion[]; selectedNode?: any; graphRef?: an
{values?.body?.content_type === 'x-www-form-urlencoded' &&
<Form.Item name={['body', 'data']} noStyle>
<EditableTable
size="small"
parentName={['body', 'data']}
options={options.filter(vo => vo.dataType === 'string' || vo.dataType === 'number')}
filterBooleanType={true}
@@ -150,7 +169,7 @@ const HttpRequest: FC<{ options: Suggestion[]; selectedNode?: any; graphRef?: an
</Form.Item>
}
{values?.body?.content_type === 'json' &&
<Form.Item name={['body', 'data']}>
<Form.Item name={['body', 'data']} noStyle>
<MessageEditor
key="json"
parentName={['body', 'data']}
@@ -161,7 +180,7 @@ const HttpRequest: FC<{ options: Suggestion[]; selectedNode?: any; graphRef?: an
</Form.Item>
}
{values?.body?.content_type === 'raw' &&
<Form.Item name={['body', 'data']}>
<Form.Item name={['body', 'data']} noStyle>
<MessageEditor
key="raw"
parentName={['body', 'data']}
@@ -172,7 +191,7 @@ const HttpRequest: FC<{ options: Suggestion[]; selectedNode?: any; graphRef?: an
</Form.Item>
}
{values?.body?.content_type === 'binary' &&
<Form.Item name={['body', 'data']}>
<Form.Item name={['body', 'data']} noStyle>
<VariableSelect
placeholder={t('common.pleaseSelect')}
options={options.filter(vo => vo.dataType.includes('file'))}
@@ -182,15 +201,19 @@ const HttpRequest: FC<{ options: Suggestion[]; selectedNode?: any; graphRef?: an
}
</Form.Item>
<Divider />
<Form.Item layout="horizontal" name="verify_ssl" label={t('workflow.config.http-request.verify_ssl')}>
<Form.Item layout="horizontal" name="verify_ssl" label={t('workflow.config.http-request.verify_ssl')} className="rb:mb-0!">
<Switch />
</Form.Item>
<Divider />
<div>{t('workflow.config.http-request.timeouts')}</div>
<div className="rb:font-medium rb:text-[12px] rb:leading-4.5 rb:mb-2.5 rb:cursor-pointer" onClick={handleToggle}>
{t('workflow.config.http-request.timeouts')}
{collapsed ? <CaretRightOutlined /> : <CaretDownOutlined />}
</div>
<Form.Item
name={['timeouts', 'connect_timeout']}
label={t('workflow.config.http-request.connect_timeout')}
hidden={collapsed}
>
<InputNumber
placeholder={t('common.pleaseEnter')}
@@ -201,6 +224,7 @@ const HttpRequest: FC<{ options: Suggestion[]; selectedNode?: any; graphRef?: an
<Form.Item
name={['timeouts', 'read_timeout']}
label={t('workflow.config.http-request.read_timeout')}
hidden={collapsed}
>
<InputNumber
placeholder={t('common.pleaseEnter')}
@@ -211,6 +235,7 @@ const HttpRequest: FC<{ options: Suggestion[]; selectedNode?: any; graphRef?: an
<Form.Item
name={['timeouts', 'write_timeout']}
label={t('workflow.config.http-request.write_timeout')}
hidden={collapsed}
>
<InputNumber
placeholder={t('common.pleaseEnter')}

View File

@@ -1,6 +1,6 @@
import { type FC, useRef, useState, useEffect } from 'react'
import { useTranslation } from 'react-i18next'
import { Space, Button, List } from 'antd'
import { Space, Button } from 'antd'
import knowledgeEmpty from '@/assets/images/application/knowledgeEmpty.svg'
import type {
KnowledgeConfigForm,
@@ -113,49 +113,65 @@ const Knowledge: FC<{value?: KnowledgeConfig; onChange?: (config: KnowledgeConfi
}
return (
<div>
<div className="rb:flex rb:justify-between rb:items-center">
<div>{t('application.knowledgeBaseAssociation')}</div>
<div className="rb:flex rb:items-center rb:justify-between rb:mb-2">
<div className="rb:text-[12px] rb:font-medium rb:leading-4.5">
{t('application.knowledgeBaseAssociation')}
</div>
<Space>
<Button style={{ padding: '0 8px', height: '24px' }} onClick={handleKnowledgeConfig}>{t('workflow.config.knowledge-retrieval.recallConfig')}</Button>
<Button style={{ padding: '0 8px', height: '24px' }} onClick={handleAddKnowledge}>+</Button>
</Space>
<Button
onClick={handleKnowledgeConfig}
className="rb:py-0! rb:px-1! rb:text-[12px]! rb:group rb:gap-0.5!"
size="small"
>
<div
className="rb:size-3.5 rb:cursor-pointer rb:bg-cover rb:bg-[url('@/assets/images/workflow/recall.svg')] rb:group-hover:bg-[url('@/assets/images/workflow/recall_hover.svg')]"
></div>
{t('workflow.config.knowledge-retrieval.recallConfig')}
</Button>
</div>
{knowledgeList.length === 0
? <Empty url={knowledgeEmpty} size={88} subTitle={t('application.knowledgeEmpty')} />
:
<List
grid={{ gutter: 12, column: 1 }}
dataSource={knowledgeList}
renderItem={(item) => {
if (!item.id) return null
return (
<List.Item>
<div key={item.id} className="rb:flex rb:items-center rb:justify-between rb:p-[12px_16px] rb:bg-[#FBFDFF] rb:border rb:border-[#DFE4ED] rb:rounded-lg">
<div className="rb:font-medium rb:leading-4">
{item.name}
<Tag color={item.status === 1 ? 'success' : item.status === 0 ? 'default' : 'error'} className="rb:ml-2">
{item.status === 1 ? t('common.enable') : item.status === 0 ? t('common.disabled') : t('common.deleted')}
</Tag>
<div className="rb:mt-1 rb:text-[12px] rb:text-[#5B6167] rb:font-regular rb:leading-5">{t('application.contains', {include_count: item.doc_num})}</div>
</div>
<Space size={12}>
<div
className="rb:w-6 rb:h-6 rb:cursor-pointer rb:bg-[url('@/assets/images/editBorder.svg')] rb:hover:bg-[url('@/assets/images/editBg.svg')]"
onClick={() => handleEditKnowledge(item)}
></div>
<div
className="rb:w-6 rb:h-6 rb:cursor-pointer rb:bg-[url('@/assets/images/deleteBorder.svg')] rb:hover:bg-[url('@/assets/images/deleteBg.svg')]"
onClick={() => handleDeleteKnowledge(item.id)}
></div>
</Space>
</div>
</List.Item>
)
}}
/>
}
<Space size={10} direction="vertical" className="rb:w-full!">
<Button
type="dashed"
block
size="middle"
className="rb:text-[12px]!"
onClick={handleAddKnowledge}
>
+ {t('workflow.config.knowledge-retrieval.addKnowledge')}
</Button>
{knowledgeList.length === 0
? <Empty url={knowledgeEmpty} size={88} subTitle={t('application.knowledgeEmpty')} />
: knowledgeList.map(item => {
if (!item.id) return null
return (
<div key={item.id} className="rb:text-[12px] rb:flex rb:items-center rb:justify-between rb:py-2 rb:px-2.5 rb:bg-[#F6F8FC] rb:border rb:border-[#DFE4ED] rb:rounded-lg">
<div className="">
<span className="rb:font-medium rb:leading-4">{item.name}</span>
<Tag
color={item.status === 1 ? 'success' : item.status === 0 ? 'default' : 'error'}
className="rb:ml-1 rb:py-0! rb:px-1! rb:text-[12px] rb:leading-3.5!"
>
{item.status === 1 ? t('common.enable') : item.status === 0 ? t('common.disabled') : t('common.deleted')}
</Tag>
<div className="rb:mt-1 rb:text-[12px] rb:text-[#5B6167] rb:font-regular rb:leading-5">{t('application.contains', { include_count: item.doc_num })}</div>
</div>
<Space size={12}>
<div
className="rb:size-4 rb:cursor-pointer rb:bg-cover rb:bg-[url('@/assets/images/edit.svg')] rb:hover:bg-[url('@/assets/images/edit_hover.svg')]"
onClick={() => handleEditKnowledge(item)}
></div>
<div
className="rb:size-4 rb:cursor-pointer rb:bg-cover rb:bg-[url('@/assets/images/delete.svg')] rb:hover:bg-[url('@/assets/images/delete_hover.svg')]"
onClick={() => handleDeleteKnowledge(item.id)}
></div>
</Space>
</div>
)
})
}
</Space>
{/* 全局设置 */}
<KnowledgeGlobalConfigModal
data={editConfig}

View File

@@ -83,6 +83,7 @@ const KnowledgeConfigModal = forwardRef<KnowledgeConfigModalRef, KnowledgeConfig
<Form
form={form}
layout="vertical"
size="middle"
>
{data && (
<div className="rb:mb-6 rb:flex rb:items-center rb:justify-between rb:border rb:rounded-lg rb:p-[17px_16px] rb:cursor-pointer rb:bg-[#F0F3F8] rb:border-[#DFE4ED] rb:text-[#212332]">

View File

@@ -70,6 +70,7 @@ const KnowledgeGlobalConfigModal = forwardRef<KnowledgeGlobalConfigModalRef, Kno
<Form
form={form}
layout="vertical"
size="middle"
>
<div className="rb:text-[#5B6167] rb:mb-6">{t('application.globalConfigDesc')}</div>

View File

@@ -117,6 +117,7 @@ const KnowledgeListModal = forwardRef<KnowledgeModalRef, KnowledgeModalProps>(({
placeholder={t('knowledgeBase.searchPlaceholder')}
onSearch={handleSearch}
style={{ width: '100%' }}
size="middle"
/>
{filterList.length === 0
? <Empty />

View File

@@ -1,7 +1,6 @@
import React from 'react';
import { useTranslation } from 'react-i18next'
import { MinusCircleOutlined } from '@ant-design/icons';
import { Button, Form, Input, Space, Row, Col } from 'antd';
import { Button, Form, Input, Divider } from 'antd';
import type { Suggestion } from '../../Editor/plugin/AutocompletePlugin'
import VariableSelect from '../VariableSelect'
@@ -16,43 +15,55 @@ const MappingList: React.FC<MappingListProps> = ({ name, options }) => {
<Form.List name={name}>
{(fields, { add, remove }) => (
<>
{fields.map(({ key, name, ...restField }) => (
<Row key={key} gutter={12} className="rb:mb-2">
<Col span={10}>
<Form.Item
{...restField}
name={[name, 'name']}
noStyle
>
<Input placeholder={t('common.pleaseEnter')} data-field-type="mapping-name" />
</Form.Item>
</Col>
<Col span={12}>
<Form.Item
{...restField}
name={[name, 'value']}
noStyle
>
<VariableSelect
placeholder={t('common.pleaseSelect')}
options={options}
popupMatchSelectWidth={false}
/>
</Form.Item>
</Col>
<Col span={2}>
<MinusCircleOutlined onClick={() => remove(name)} />
</Col>
</Row>
))}
<Form.Item>
<Button type="dashed" onClick={() => add()} block>
+ {t('common.add')}
<div className="rb:flex rb:items-center rb:justify-between rb:mb-2">
<div className="rb:text-[12px] rb:font-medium rb:leading-4.5">
{t('workflow.config.jinja-render.mapping')}
</div>
<Button
onClick={() => add()}
className="rb:py-0! rb:px-1! rb:text-[12px]!"
size="small"
>
+ {t('workflow.config.addVariable')}
</Button>
</Form.Item>
</div>
{fields.map(({ key, name, ...restField }) => (
<div key={key} className="rb:flex rb:items-center rb:gap-1 rb:mb-2">
<Form.Item
{...restField}
name={[name, 'name']}
noStyle
>
<Input
placeholder={t('common.pleaseEnter')}
size="small"
className="rb:w-24!"
/>
</Form.Item>
<Form.Item
{...restField}
name={[name, 'value']}
noStyle
>
<VariableSelect
placeholder={t('common.pleaseSelect')}
options={options}
popupMatchSelectWidth={false}
size="small"
className="rb:w-39!"
/>
</Form.Item>
<div
className="rb:ml-1 rb:size-4 rb:cursor-pointer rb:bg-cover rb:bg-[url('@/assets/images/workflow/deleteBg.svg')] rb:hover:bg-[url('@/assets/images/workflow/deleteBg_hover.svg')]"
onClick={() => remove(name)}
></div>
</div>
))}
</>
)}
</Form.List>
<Divider />
</>
)
};

View File

@@ -30,7 +30,7 @@ const MemoryConfig: FC<{ options: Suggestion[]; parentName: string; }> = ({
return (
<>
{values?.memory?.enable && <>
<div className="rb:flex rb:items-center rb:justify-between rb:py-1.5 rb:px-2 rb:bg-[#F6F8FC] rb:rounded-md rb:mb-2">
<div className="rb:flex rb:items-center rb:justify-between rb:py-1.5 rb:px-2 rb:text-[12px] rb:bg-[#F6F8FC] rb:rounded-md rb:mb-2">
{t('workflow.config.llm.memory')}
<span>{t('workflow.config.llm.inner')}</span>
</div>
@@ -40,6 +40,7 @@ const MemoryConfig: FC<{ options: Suggestion[]; parentName: string; }> = ({
isArray={false}
parentName={[parentName, 'messages']}
options={options}
size="small"
/>
</Form.Item>

View File

@@ -1,13 +1,14 @@
import { type FC, useMemo } from 'react';
import clsx from 'clsx'
import { useTranslation } from 'react-i18next'
import { Input, Form, Space, Button, Row, Col, Select, type FormListOperation } from 'antd';
import { MinusCircleOutlined } from '@ant-design/icons';
import Editor from '../Editor'
import type { Suggestion } from '../Editor/plugin/AutocompletePlugin'
interface MessageEditor {
options: Suggestion[];
title?: string
title?: string;
titleVariant?: 'outlined' | 'borderless';
isArray?: boolean;
parentName?: string | string[];
label?: string;
@@ -15,6 +16,7 @@ interface MessageEditor {
value?: string;
enableJinja2?: boolean;
onChange?: (value?: string) => void;
size?: 'small' | 'default'
}
const roleOptions = [
// { label: 'SYSTEM', value: 'SYSTEM' },
@@ -23,11 +25,13 @@ const roleOptions = [
]
const MessageEditor: FC<MessageEditor> = ({
title,
titleVariant = 'outlined',
isArray = true,
parentName = 'messages',
placeholder,
options,
enableJinja2 = false,
size = 'default'
}) => {
const { t } = useTranslation()
const form = Form.useFormInstance();
@@ -74,14 +78,16 @@ const MessageEditor: FC<MessageEditor> = ({
if (!isArray) {
return (
<Space size={12} direction="vertical" className="rb:w-full rb:border rb:border-[#DFE4ED] rb:rounded-md rb:px-2 rb:py-1.5 rb:bg-white" data-editor-type={parentName === 'template' ? 'template' : undefined}>
<Space size={8} direction="vertical" className="rb:w-full rb:border rb:border-[#DFE4ED] rb:rounded-md rb:px-2 rb:py-1.5" data-editor-type={parentName === 'template' ? 'template' : undefined}>
<Row>
<Col span={12}>
{title ?? t('workflow.answerDesc')}
<div className={clsx("rb:text-[12px] rb:font-medium rb:py-1 rb:leading-2", {
'rb:bg-[#F6F8FC] rb:border rb:border-[#DFE4ED] rb:rounded-sm rb:px-2': titleVariant === 'outlined'
})}>{title ?? t('workflow.answerDesc')}</div>
</Col>
</Row>
<Form.Item name={parentName} noStyle>
<Editor enableJinja2={enableJinja2} placeholder={placeholder} options={processedOptions} />
<Editor size={size} enableJinja2={enableJinja2} placeholder={placeholder} options={processedOptions} />
</Form.Item>
</Space>
);
@@ -90,7 +96,7 @@ const MessageEditor: FC<MessageEditor> = ({
return (
<Form.List name={parentName}>
{(fields, { add, remove }) => (
<Space size={12} direction="vertical" className="rb:w-full">
<Space size={8} direction="vertical" className="rb:w-full">
{fields.map(({ key, name, ...restField }) => {
const fieldValue = Array.isArray(parentName)
? parentName.reduce((obj, key) => obj?.[key], values)
@@ -99,16 +105,17 @@ const MessageEditor: FC<MessageEditor> = ({
const currentRole = (fieldValue?.[name]?.role || 'USER').toUpperCase();
return (
<Space key={key} size={12} direction="vertical" className="rb:w-full rb:border rb:border-[#DFE4ED] rb:rounded-md rb:px-2 rb:py-1.5 rb:bg-white">
<Space key={key} size={12} direction="vertical" className="rb:w-full rb:border rb:border-[#DFE4ED] rb:rounded-md rb:p-2">
<Row>
<Col span={12}>
<Form.Item {...restField} name={[name, 'role']} noStyle>
{currentRole === 'SYSTEM' ? (
<Input disabled />
<Input disabled className="rb:font-medium!" />
) : (
<Select
options={roleOptions}
disabled={currentRole === 'SYSTEM'}
className="rb:font-medium!"
/>
)}
</Form.Item>
@@ -116,20 +123,23 @@ const MessageEditor: FC<MessageEditor> = ({
{currentRole !== 'SYSTEM' && (
<Col span={12}>
<div className="rb:h-full rb:flex rb:justify-end rb:items-center">
<MinusCircleOutlined onClick={() => remove(name)} />
<div
className="rb:size-4 rb:cursor-pointer rb:bg-cover rb:bg-[url('@/assets/images/workflow/delete_cycle.svg')]"
onClick={() => remove(name)}
></div>
</div>
</Col>
)}
</Row>
<Form.Item {...restField} name={[name, 'content']} noStyle>
<Editor enableJinja2={enableJinja2} placeholder={placeholder} options={processedOptions} />
<Editor size={size} enableJinja2={enableJinja2} placeholder={placeholder} options={processedOptions} />
</Form.Item>
</Space>
);
})}
<Form.Item noStyle>
<Button type="dashed" onClick={() => handleAdd(add)} block>
+{t('workflow.addMessage')}
<Button type="dashed" size="middle" className="rb:text-[12px]!" onClick={() => handleAdd(add)} block>
+ {t('workflow.addMessage')}
</Button>
</Form.Item>
</Space>

View File

@@ -74,6 +74,7 @@ const ParamEditModal = forwardRef<ParamEditModalRef, ParamEditModalProps>(({
<Form
form={form}
layout="vertical"
size="middle"
scrollToFirstError={{ behavior: 'instant', block: 'end', focus: true }}
>
<FormItem

View File

@@ -1,7 +1,7 @@
import { type FC, useRef } from 'react'
import { useTranslation } from 'react-i18next'
import { Button, Space, List } from 'antd'
import Empty from '@/components/Empty'
import { Button, Space } from 'antd'
import type { ParamItem, ParamEditModalRef } from './types'
import ParamEditModal from './ParamEditModal'
@@ -41,47 +41,37 @@ const ParamsList: FC<ParamsListProps> = ({
}
return (
<div>
<div className="rb:flex rb:justify-between rb:items-center">
<div>{label}</div>
<Space>
<Button style={{ padding: '0 8px', height: '24px' }} onClick={handleAdd}>+</Button>
</Space>
<div className="rb:leading-4.25 rb:text-[12px] rb:font-medium rb:mb-2">
{label}
</div>
{value?.length === 0
? <Empty size={88} />
:
<List
grid={{ gutter: 12, column: 1 }}
dataSource={value}
renderItem={(item, index) => (
<List.Item>
<div key={index} className="rb:group rb:relative rb:p-[12px_16px] rb:bg-[#FBFDFF] rb:cursor-pointer rb:border rb:border-[#DFE4ED] rb:rounded-lg">
<div className="rb:flex rb:items-center rb:justify-between">
<div className="rb:leading-4">
<span className="rb:font-medium">{item.name}</span>
<span className="rb:text-[12px] rb:text-[#5B6167] rb:font-regular"> ({t(`workflow.config.parameter-extractor.${item.type}`)})</span>
</div>
<span className="rb:block rb:group-hover:hidden rb:text-[12px] rb:text-[#5B6167] rb:font-regular">{item.required ? t('workflow.config.parameter-extractor.required') : ''}</span>
<Space size={10} direction="vertical" className="rb:w-full!">
<Button type="dashed" block size="middle" className="rb:text-[12px]!" onClick={handleAdd}>+ {t('workflow.config.parameter-extractor.addParams')}</Button>
</div>
<div className="rb:mt-1 rb:text-[12px] rb:text-[#5B6167] rb:font-regular rb:leading-5 rb:wrap-break-word rb:line-clamp-1">{item.desc}</div>
<Space size={12} className="rb:hidden! rb:group-hover:flex! rb:absolute rb:right-4 rb:top-[50%] rb:transform-[translateY(-50%)] rb:bg-white">
<div
className="rb:size-5 rb:cursor-pointer rb:bg-cover rb:bg-[url('@/assets/images/editBorder.svg')] rb:hover:bg-[url('@/assets/images/editBg.svg')]"
onClick={() => handleEdit(index)}
></div>
<div
className="rb:size-5 rb:cursor-pointer rb:bg-cover rb:bg-[url('@/assets/images/deleteBorder.svg')] rb:hover:bg-[url('@/assets/images/deleteBg.svg')]"
onClick={() => handleDelete(index)}
></div>
</Space>
</div>
</List.Item>
)}
/>
}
{value?.map((item, index) => (
<div
key={index}
className="rb:cursor-pointer rb:group rb:py-2 rb:pl-2.5 rb:pr-2 rb:text-[12px] rb:flex rb:items-center rb:justify-between rb:bg-[#F6F8FC] rb:border rb:border-[#DFE4ED] rb:rounded-md"
>
<div>
<span className="rb:font-medium">{item.name}</span>
<span className="rb:text-[12px] rb:text-[#5B6167] rb:font-regular"> ({t(`workflow.config.parameter-extractor.${item.type}`)}) {item.required ? t('workflow.config.parameter-extractor.required') : ''}</span>
<div className="rb:text-[12px] rb:text-[#5B6167] rb:font-regular rb:leading-4.25 rb:mt-0.5">{item.desc}</div>
</div>
<Space size={8}>
<div
className="rb:size-4 rb:cursor-pointer rb:bg-cover rb:bg-[url('@/assets/images/edit.svg')] rb:hover:bg-[url('@/assets/images/edit_hover.svg')]"
onClick={() => handleEdit(index)}
></div>
<div
className="rb:size-4 rb:cursor-pointer rb:bg-cover rb:bg-[url('@/assets/images/delete.svg')] rb:hover:bg-[url('@/assets/images/delete_hover.svg')]"
onClick={() => handleDelete(index)}
></div>
</Space>
</div>
))}
</Space>
<ParamEditModal
ref={paramEditModalRef}

View File

@@ -0,0 +1,55 @@
import React, { useState, useEffect } from 'react';
import type { InputNumberProps, SliderSingleProps } from 'antd';
import { Col, InputNumber, Row, Slider } from 'antd';
const RbSlider: React.FC<SliderSingleProps> = ({
value,
onChange,
min,
max,
step = 0.01,
...props
}) => {
const [curValue, setCurValue] = useState<number | undefined>(0)
useEffect(() => {
setCurValue(value)
}, [value])
const handleSliderChange = (newValue: number) => {
onChange && onChange(newValue);
};
const handleInputChange: InputNumberProps['onChange'] = (newValue) => {
onChange && onChange(newValue as number);
};
return (
<Row gutter={12}>
<Col span={16}>
<Slider
{...props}
min={min}
max={max}
step={step as number}
value={curValue}
className="rb:my-0! rb:ml-2.5!"
classNames={{
rail: 'rb:h-[6px]!',
track: 'rb:h-[6px]!'
}}
onChange={handleSliderChange}
/>
</Col>
<Col span={8}>
<InputNumber
min={min}
max={max}
step={step as number}
value={curValue}
onChange={handleInputChange}
className="rb:w-full!"
/>
</Col>
</Row>
);
};
export default RbSlider;

View File

@@ -171,6 +171,7 @@ const ToolConfig: FC<{ options: Suggestion[]; }> = ({
label={t('workflow.config.tool.tool_id')}
>
<Cascader
placeholder={t('common.pleaseSelect')}
options={optionList}
loadData={loadData}
onChange={handleChange}
@@ -187,28 +188,30 @@ const ToolConfig: FC<{ options: Suggestion[]; }> = ({
label={parameter.name}
extra={parameter.type === 'boolean' ? undefined : parameter.description}
rules={[
{ required: parameter.required, message: t('workflow.config.tool.required') }
{ required: parameter.required, message: t('common.pleaseEnter') }
]}
layout={parameter.type === 'boolean' ? 'horizontal' : 'vertical'}
className={parameter.type === 'boolean' ? 'rb:mb-0!' : ''}
>
{parameter.type === 'string' && parameter.enum && parameter.enum.length > 0
? <Select options={parameter.enum.map(vo => ({ value: vo, label: vo }))} placeholder={t('common.pleaseSelect')} />
? <Select size="small" options={parameter.enum.map(vo => ({ value: vo, label: vo }))} placeholder={t('common.pleaseSelect')} />
: parameter.type === 'boolean'
? <Switch />
? <Switch size="small" />
: parameter.type === 'integer' || parameter.type === 'number'
? <InputNumber
min={parameter.minimum}
max={parameter.maximum}
step={parameter.type === 'integer' ? 1 : 0.01}
placeholder={t('common.pleaseEnter')}
className="rb:w-full!"
className="rb:w-full!"
size="small"
onChange={(value) => form.setFieldValue(['tool_parameters', parameter.name], value)}
/>
: <Editor
height={32}
variant="outlined"
options={options}
size="small"
placeholder={t('common.pleaseEnter')}
/>
}

View File

@@ -2,14 +2,14 @@ import { forwardRef, useImperativeHandle, useState } from 'react';
import { Form, Input, Select, InputNumber, Checkbox, Tag } from 'antd';
import { useTranslation } from 'react-i18next';
import type { StartVariableItem, VariableEditModalRef } from '../../types'
import type { Variable, VariableEditModalRef } from './types'
import RbModal from '@/components/RbModal'
import SortableList from '@/components/SortableList'
const FormItem = Form.Item;
interface VariableEditModalProps {
refresh: (values: StartVariableItem) => void;
refresh: (values: Variable) => void;
}
const types = [
@@ -36,9 +36,9 @@ const VariableEditModal = forwardRef<VariableEditModalRef, VariableEditModalProp
}, ref) => {
const { t } = useTranslation();
const [visible, setVisible] = useState(false);
const [form] = Form.useForm<StartVariableItem>();
const [form] = Form.useForm<Variable>();
const [loading, setLoading] = useState(false)
const [editVo, setEditVo] = useState<StartVariableItem | null>(null)
const [editVo, setEditVo] = useState<Variable | null>(null)
const values = Form.useWatch([], form);
@@ -50,7 +50,7 @@ const VariableEditModal = forwardRef<VariableEditModalRef, VariableEditModalProp
setEditVo(null)
};
const handleOpen = (variable?: StartVariableItem) => {
const handleOpen = (variable?: Variable) => {
setVisible(true);
if (variable) {
setEditVo(variable || null)
@@ -85,7 +85,7 @@ const VariableEditModal = forwardRef<VariableEditModalRef, VariableEditModalProp
return (
<RbModal
title={editVo ? t('workflow.config.start.editVariable') : t('workflow.config.start.addVariable')}
title={editVo ? t('workflow.config.start.editVariable') : t('workflow.config.addVariable')}
open={visible}
onCancel={handleClose}
okText={t('common.save')}
@@ -96,6 +96,7 @@ const VariableEditModal = forwardRef<VariableEditModalRef, VariableEditModalProp
form={form}
layout="vertical"
initialValues={initialValues}
size="middle"
scrollToFirstError={{ behavior: 'instant', block: 'end', focus: true }}
>
{/* 变量类型 */}

View File

@@ -0,0 +1,108 @@
import { type FC, useRef, useState } from 'react'
import { useTranslation } from 'react-i18next'
import { Node } from '@antv/x6';
import { Space, Button, Divider, App } from 'antd'
import type { Variable, VariableEditModalRef } from './types'
import type { NodeConfig } from '../../../types'
import VariableEditModal from './VariableEditModal'
interface VariableListProps {
selectedNode?: Node | null;
config: NodeConfig;
value?: Variable[];
parentName: string;
onChange?: (value: Variable[]) => void;
}
const VariableList: FC<VariableListProps> = ({
value = [],
onChange,
selectedNode,
config,
parentName
}) => {
const { t } = useTranslation()
const { modal } = App.useApp()
const variableModalRef = useRef<VariableEditModalRef>(null)
const [editIndex, setEditIndex] = useState<number | null>(null)
const handleAddVariable = () => {
setEditIndex(null)
variableModalRef.current?.handleOpen()
}
const handleEditVariable = (index: number, vo: Variable) => {
variableModalRef.current?.handleOpen(vo)
setEditIndex(index)
}
const handleRefreshVariable = (variable: Variable) => {
if (!selectedNode) return
if (editIndex !== null) {
const list = [...value]
list[editIndex] = variable
onChange?.(list)
} else {
console.log('VariableList', value, variable)
onChange?.([...value, variable])
}
}
const handleDeleteVariable = (index: number, vo: Variable, e: React.MouseEvent) => {
e.preventDefault();
e.stopPropagation();
if (!selectedNode) return
modal.confirm({
title: t('common.confirmDeleteDesc', { name: vo.name }),
okText: t('common.delete'),
cancelText: t('common.cancel'),
okType: 'danger',
onOk: () => {
const list = [...value]
list.splice(index, 1)
onChange?.([...list])
}
})
}
return (
<div>
<Space size={10} direction="vertical" className="rb:w-full">
<div className="rb:leading-4.25 rb:text-[12px] rb:font-medium">
{t(`workflow.config.${selectedNode?.data?.type}.${parentName}`)}
</div>
<Button type="dashed" block size="middle" className="rb:text-[12px]!" onClick={handleAddVariable}>+ {t('workflow.config.addVariable')}</Button>
{Array.isArray(value) && value?.map((vo, index) =>
<div
key={`${vo.name}}-${index}`}
className="rb:cursor-pointer rb:group rb:py-2 rb:pl-2.5 rb:pr-2 rb:text-[12px] rb:flex rb:items-center rb:justify-between rb:bg-[#F6F8FC] rb:border rb:border-[#DFE4ED] rb:rounded-md"
onClick={() => handleEditVariable(index, vo)}
>
<span className="rb:font-medium">{vo.name}·{vo.description}</span>
<Space size={8}>
{vo.required && <span className="rb:py-px rb:px-2 rb:bg-[#FBFDFF] rb:border rb:border-[#DFE4ED] rb:rounded-sm">{t('workflow.config.start.required')}</span>}
<span className="rb:py-px rb:px-2 rb:bg-[#FBFDFF] rb:border rb:border-[#DFE4ED] rb:rounded-sm">{vo.type}</span>
<div
className="rb:size-3 rb:cursor-pointer rb:bg-cover rb:bg-[url('@/assets/images/close.svg')] rb:hover:bg-[url('@/assets/images/close_hover.svg')]"
onClick={(e) => handleDeleteVariable(index, vo, e)}
></div>
</Space>
</div>
)}
</Space>
<Divider size="small" />
<Space size={10} direction="vertical" className="rb:w-full">
{config.sys?.map((vo, index) =>
<div key={index} className="rb:py-2 rb:pl-2.5 rb:pr-2 rb:text-[12px] rb:flex rb:items-center rb:justify-between rb:bg-[#F6F8FC] rb:border rb:border-[#DFE4ED] rb:rounded-md">
<span className="rb:font-medium">sys.{vo.name}</span>
<span className="rb:py-px rb:px-2 rb:bg-[#FBFDFF] rb:border rb:border-[#DFE4ED] rb:rounded-sm">{vo.type}</span>
</div>
)}
</Space>
<VariableEditModal
ref={variableModalRef}
refresh={handleRefreshVariable}
/>
</div>
)
}
export default VariableList

View File

@@ -0,0 +1,23 @@
export interface Variable {
name: string;
type: string;
required: boolean;
description: string;
max_length?: number;
default?: string;
readonly?: boolean;
defaultValue?: any;
value?: any;
}
export interface VariableEditModalRef {
handleOpen: (values?: Variable) => void;
}
export interface ApiExtensionModalData {
name: string;
apiEndpoint: string;
apiKey: string;
}
export interface ApiExtensionModalRef {
handleOpen: () => void;
}

View File

@@ -10,6 +10,7 @@ interface VariableSelectProps extends SelectProps {
onChange?: (value: string) => void;
allowClear?: boolean;
filterBooleanType?: boolean;
size?: 'small' | 'middle' | 'large'
}
const VariableSelect: FC<VariableSelectProps> = ({
@@ -18,7 +19,7 @@ const VariableSelect: FC<VariableSelectProps> = ({
value,
allowClear = true,
onChange,
size,
size = 'middle',
filterBooleanType = false,
...resetPorps
}) => {

View File

@@ -1,13 +1,13 @@
import { type FC, useEffect, useState, useRef, useMemo } from "react";
import clsx from 'clsx'
import { useTranslation } from 'react-i18next'
import { Graph, Node } from '@antv/x6';
import { Form, Input, Button, Select, InputNumber, Slider, Space, Divider, App, Switch } from 'antd'
import { Form, Input, Select, InputNumber, Switch } from 'antd'
import type { NodeConfig, NodeProperties, StartVariableItem, VariableEditModalRef, ChatVariable } from '../../types'
import type { NodeConfig, NodeProperties, ChatVariable } from '../../types'
import Empty from '@/components/Empty';
import emptyIcon from '@/assets/images/workflow/empty.png'
import CustomSelect from "@/components/CustomSelect";
import VariableEditModal from './VariableEditModal';
import MessageEditor from './MessageEditor'
import Knowledge from './Knowledge/Knowledge';
import type { Suggestion } from '../Editor/plugin/AutocompletePlugin'
@@ -23,7 +23,11 @@ import CycleVarsList from './CycleVarsList'
import AssignmentList from './AssignmentList'
import ToolConfig from './ToolConfig'
import MemoryConfig from './MemoryConfig'
import VariableList from './VariableList'
// import { calculateVariableList } from './utils/variableListCalculator'
import styles from './properties.module.css'
import Editor from "../Editor";
import RbSlider from './RbSlider'
interface PropertiesProps {
selectedNode?: Node | null;
@@ -42,12 +46,9 @@ const Properties: FC<PropertiesProps> = ({
chatVariables
}) => {
const { t } = useTranslation()
const { modal } = App.useApp()
const [form] = Form.useForm<NodeConfig>();
const [configs, setConfigs] = useState<Record<string,NodeConfig>>({} as Record<string,NodeConfig>)
const values = Form.useWatch([], form);
const variableModalRef = useRef<VariableEditModalRef>(null)
const [editIndex, setEditIndex] = useState<number | null>(null)
const [graphUpdateTrigger, setGraphUpdateTrigger] = useState(0)
const prevMappingNamesRef = useRef<string[]>([])
const prevTemplateVarsRef = useRef<string[]>([])
@@ -243,49 +244,6 @@ const Properties: FC<PropertiesProps> = ({
}
}, [values, selectedNode, form])
const handleAddVariable = () => {
setEditIndex(null)
variableModalRef.current?.handleOpen()
}
const handleEditVariable = (index: number, vo: StartVariableItem) => {
variableModalRef.current?.handleOpen(vo)
setEditIndex(index)
}
const handleRefreshVariable = (value: StartVariableItem) => {
if (!selectedNode) return
if (editIndex !== null) {
const defaultValue = selectedNode.data.config.variables.defaultValue ?? []
defaultValue[editIndex] = value
selectedNode.data.config.variables.defaultValue = [...defaultValue]
} else {
const defaultValue = selectedNode.data.config.variables.defaultValue ?? []
selectedNode.data.config.variables.defaultValue = [...defaultValue, value]
}
selectedNode?.setData({ ...selectedNode.data})
setConfigs({ ...selectedNode.data.config })
}
const handleDeleteVariable = (index: number, vo: StartVariableItem) => {
if (!selectedNode) return
modal.confirm({
title: t('common.confirmDeleteDesc', { name: vo.name }),
okText: t('common.delete'),
cancelText: t('common.cancel'),
okType: 'danger',
onOk: () => {
const defaultValue = selectedNode.data.config.variables.defaultValue ?? []
defaultValue.splice(index, 1)
selectedNode.data.config.variables.defaultValue = [...defaultValue]
selectedNode?.setData({ ...selectedNode.data })
setConfigs({ ...selectedNode.data.config })
}
})
}
const variableList = useMemo(() => {
if (!selectedNode || !graphRef?.current) return [];
@@ -586,7 +544,7 @@ const Properties: FC<PropertiesProps> = ({
break
case 'question-classifier':
const classNameKey = `${dataNodeId}_class_name`;
const outputKey = `${dataNodeId}_output`;
// const outputKey = `${dataNodeId}_output`;
if (!addedKeys.has(classNameKey)) {
addedKeys.add(classNameKey);
variableList.push({
@@ -1039,11 +997,11 @@ const Properties: FC<PropertiesProps> = ({
console.log('variableList', variableList)
return (
<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={clsx("rb:w-75 rb:fixed rb:right-0 rb:top-16 rb:bottom-0 rb:p-3 rb:pb-6", styles.properties)}>
<div className="rb:font-medium rb:leading-5 rb:pb-3 rb:mb-3 rb:border-b rb:border-b-[#DFE4ED]">{t('workflow.nodeProperties')}</div>
{!selectedNode
? <Empty url={emptyIcon} size={140} className="rb:h-full rb:mx-15" title={t('workflow.empty')} />
: <Form form={form} layout="vertical" className="rb:h-[calc(100%-20px)] rb:overflow-y-auto">
: <Form form={form} size="small" layout="vertical" className="rb:h-[calc(100%-20px)] rb:overflow-x-hidden rb:overflow-y-auto">
<Form.Item name="name" label={t('workflow.nodeName')}>
<Input
placeholder={t('common.pleaseEnter')}
@@ -1073,46 +1031,13 @@ const Properties: FC<PropertiesProps> = ({
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>
<Space size={4} direction="vertical" className="rb:w-full">
{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">
<span>{vo.name}·{vo.description}</span>
<div className="rb:group-hover:hidden rb:flex rb:items-center rb:gap-1">
{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}
</div>
)}
</Space>
</div>
<Form.Item key={key} name={key}>
<VariableList
parentName={key}
selectedNode={selectedNode}
config={config}
/>
</Form.Item>
)
}
@@ -1143,23 +1068,12 @@ const Properties: FC<PropertiesProps> = ({
key={key}
options={contextVariableList.filter(variable => variable.nodeData?.type !== 'knowledge-retrieval')}
parentName={key}
placeholder={t(config.placeholder || 'common.pleaseSelect')}
size="small"
/>
</Form.Item>
)
}
if (selectedNode?.data?.type === 'end' && key === 'output') {
return (
<Form.Item key={key} name={key}>
<MessageEditor
key={key}
isArray={false}
parentName={key}
options={variableList.filter(variable => variable.nodeData?.type !== 'knowledge-retrieval')}
/>
</Form.Item>
)
}
if (config.type === 'define') {
return null
}
@@ -1184,6 +1098,8 @@ const Properties: FC<PropertiesProps> = ({
parentName={key}
enableJinja2={config.enableJinja2 as boolean}
options={getFilteredVariableList(selectedNode?.data?.type, key)}
titleVariant={config.titleVariant}
size="small"
/>
</Form.Item>
)
@@ -1206,9 +1122,9 @@ const Properties: FC<PropertiesProps> = ({
name={key}
options={getFilteredVariableList(selectedNode?.data?.type, key)}
isCanAdd={!!(values as any)?.group}
size="small"
/>
</Form.Item>
)
}
if (config.type === 'caseList') {
@@ -1226,9 +1142,7 @@ const Properties: FC<PropertiesProps> = ({
if (config.type === 'mappingList') {
return (
<Form.Item key={key} name={key}
label={t(`workflow.config.${selectedNode?.data?.type}.${key}`)}
>
<Form.Item key={key} name={key} noStyle>
<MappingList name={key} options={getFilteredVariableList(selectedNode?.data?.type, key)} />
</Form.Item>
@@ -1238,6 +1152,7 @@ const Properties: FC<PropertiesProps> = ({
return (
<Form.Item key={key} name={key}>
<CycleVarsList
size="small"
parentName={key}
options={getFilteredVariableList(selectedNode?.data?.type, key)}
/>
@@ -1276,87 +1191,14 @@ const Properties: FC<PropertiesProps> = ({
</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.needTranslation ? (config.options || []).map(vo => ({ ...vo, label: t(vo.label) })) : config.options}
placeholder={t('common.pleaseSelect')}
/>
: config.type === 'inputNumber'
? <InputNumber
placeholder={t('common.pleaseEnter')}
className="rb:w-full!"
onChange={(value) => form.setFieldValue(key, value)}
/>
: 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')}
options={(() => {
const baseVariableList = getFilteredVariableList(selectedNode?.data?.type, key);
// Apply filtering if specified in config
if (config.filterNodeTypes || config.filterVariableNames) {
return baseVariableList.filter(variable => {
const nodeTypeMatch = !config.filterNodeTypes ||
(Array.isArray(config.filterNodeTypes) && config.filterNodeTypes.includes(variable.nodeData?.type));
const variableNameMatch = !config.filterVariableNames ||
(Array.isArray(config.filterVariableNames) && config.filterVariableNames.includes(variable.label));
return nodeTypeMatch || variableNameMatch;
});
}
// Filter child nodes for iteration output
if (config.filterChildNodes && selectedNode) {
const graph = graphRef.current;
if (!graph) return [];
const nodes = graph.getNodes();
// Find child nodes whose cycle field equals parent node's ID
const childNodes = nodes.filter(node => {
const nodeData = node.getData();
return nodeData?.cycle === selectedNode.id;
});
return baseVariableList.filter(variable =>
childNodes.some(node => node.id === variable.nodeData?.id) || selectedNode?.data?.type === 'iteration' && key === 'output' && variable.value.includes('sys.')
);
}
return baseVariableList;
})()
}
/>
: config.type === 'switch'
? <Switch onChange={key === 'group' ? () => { form.setFieldValue('group_variables', []) } : undefined} />
: config.type === 'categoryList'
? <CategoryList
parentName={key}
selectedNode={selectedNode}
graphRef={graphRef}
options={getFilteredVariableList(selectedNode?.data?.type, key)}
/>
: config.type === 'conditionList'
? <ConditionList
if (config.type === 'conditionList') {
return (
<Form.Item
key={key}
name={key}
noStyle
>
<ConditionList
parentName={key}
options={(() => {
const cycleVars = values?.cycle_vars || [];
@@ -1375,6 +1217,93 @@ const Properties: FC<PropertiesProps> = ({
graphRef={graphRef}
addBtnText={t('workflow.config.addCase')}
/>
</Form.Item>
)
}
return (
<Form.Item
key={key}
name={key}
label={key === 'parallel_count' ? <span className="rb:text-[10px] rb:text-[#5B6167] rb:leading-3.5 rb:-mb-1!">{t(`workflow.config.${selectedNode?.data?.type}.${key}`)}</span> : t(`workflow.config.${selectedNode?.data?.type}.${key}`)}
layout={config.type === 'switch' ? 'horizontal' : 'vertical'}
className={key === 'parallel_count' ? 'rb:-mt-3! rb:leading-3.5!' : ''}
>
{config.type === 'input'
? <Input placeholder={t('common.pleaseEnter')} />
: config.type === 'textarea'
? <Input.TextArea placeholder={t('common.pleaseEnter')} />
: config.type === 'select'
? <Select
options={config.needTranslation ? (config.options || []).map(vo => ({ ...vo, label: t(vo.label) })) : config.options}
placeholder={t('common.pleaseSelect')}
/>
: config.type === 'inputNumber'
? <InputNumber
placeholder={t('common.pleaseEnter')}
className="rb:w-full!"
onChange={(value) => form.setFieldValue(key, value)}
/>
: config.type === 'slider'
? <RbSlider 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}
size="small"
/>
: config.type === 'variableList'
? <VariableSelect
placeholder={t(config.placeholder || 'common.pleaseSelect')}
options={(() => {
const baseVariableList = getFilteredVariableList(selectedNode?.data?.type, key);
// Apply filtering if specified in config
if (config.filterNodeTypes || config.filterVariableNames) {
return baseVariableList.filter(variable => {
const nodeTypeMatch = !config.filterNodeTypes ||
(Array.isArray(config.filterNodeTypes) && config.filterNodeTypes.includes(variable.nodeData?.type));
const variableNameMatch = !config.filterVariableNames ||
(Array.isArray(config.filterVariableNames) && config.filterVariableNames.includes(variable.label));
return nodeTypeMatch || variableNameMatch;
});
}
// Filter child nodes for iteration output
if (config.filterChildNodes && selectedNode) {
const graph = graphRef.current;
if (!graph) return [];
const nodes = graph.getNodes();
// Find child nodes whose cycle field equals parent node's ID
const childNodes = nodes.filter(node => {
const nodeData = node.getData();
return nodeData?.cycle === selectedNode.id;
});
return baseVariableList.filter(variable =>
childNodes.some(node => node.id === variable.nodeData?.id) || selectedNode?.data?.type === 'iteration' && key === 'output' && variable.value.includes('sys.')
);
}
return baseVariableList;
})()
}
size="small"
/>
: config.type === 'switch'
? <Switch onChange={key === 'group' ? () => { form.setFieldValue('group_variables', []) } : undefined} />
: config.type === 'categoryList'
? <CategoryList
parentName={key}
selectedNode={selectedNode}
graphRef={graphRef}
options={getFilteredVariableList(selectedNode?.data?.type, key)}
/>
: config.type === 'editor'
? <Editor options={variableList} variant="outlined" size="small" />
: null
}
</Form.Item>
@@ -1383,11 +1312,6 @@ const Properties: FC<PropertiesProps> = ({
}
</Form>
}
<VariableEditModal
ref={variableModalRef}
refresh={handleRefreshVariable}
/>
</div>
);
};

View File

@@ -0,0 +1,90 @@
.properties :global(.ant-input-outlined),
.properties :global(.ant-input-number-outlined),
.properties :global(.ant-select-outlined:not(.ant-select-customize-input) .ant-select-selector),
.properties :global(.ant-input-number-outlined),
.properties :global(.ant-input-number-outlined .ant-input-number-handler-wrap) {
background-color: transparent;
}
.properties :global(.ant-input-outlined.ant-input-disabled),
.properties :global(.ant-input-outlined[disabled]) {
background-color: #F6F8FC;
}
.properties :global(.ant-select-single.ant-select-sm){
height: 28px;
}
.properties :global(.ant-table-wrapper .ant-table-thead>tr>th),
.properties :global(.ant-table-wrapper .ant-table-thead>tr>td),
.properties :global(.ant-table-wrapper .ant-table) {
background-color: #F6F8FC;
}
.properties :global(.ant-table-wrapper .ant-table),
.properties :global(.ant-table-container),
.properties :global(.ant-table-wrapper table) {
border-radius: 6px;
}
.properties :global(.ant-table-wrapper .ant-table-container table>thead>tr:first-child>*:first-child) {
border-start-start-radius: 6px;
}
.properties :global(.ant-table-wrapper .ant-table-container table>thead>tr:first-child>*:last-child) {
border-start-end-radius: 6px;
}
.properties :global(.ant-table-row:last-child .ant-table-cell:first-child) {
border-bottom-left-radius: 6px;
}
.properties :global(.ant-table-row:last-child .ant-table-cell:last-child) {
border-bottom-right-radius: 6px;
}
.properties :global(.ant-table-wrapper .ant-table) {
background: transparent;
}
.properties :global(.ant-table-wrapper .ant-table-container) {
border-start-start-radius: 6px;
border-start-end-radius: 6px;
}
.properties :global(.ant-table-container) {
/* border-left: none;
border-top: none;
border-bottom: none; */
border: none;
}
.properties :global(.ant-table-wrapper .ant-table-tbody>tr.ant-table-placeholder:hover>th),
.properties :global(.ant-table-wrapper .ant-table-tbody>tr.ant-table-placeholder:hover>td),
.properties :global(.ant-table-wrapper .ant-table-tbody>tr.ant-table-placeholder),
.properties :global(.ant-table-wrapper .ant-table) {
background-color: #F6F8FC;
}
.properties :global(.ant-form-item-horizontal.ant-form-item .ant-form-item-control-input-content:has(> .ant-switch:only-child, > .ant-rate:only-child)) {
display: flex;
justify-content: end;
}
.properties :global(.ant-divider-horizontal.ant-divider-sm) {
margin-block: 16px;
}
.properties :global(.ant-form-item) {
margin-bottom: 16px;
}
.properties :global(.ant-form-item .ant-form-item-label>label) {
font-weight: 500;
font-size: 12px;
}
.properties :global(.ant-select-single.ant-select-sm .ant-select-selector),
.properties :global(.ant-select-dropdown .ant-select-item),.properties :global(.ant-input-number-sm) {
font-size: 12px;
}
.properties :global(.ant-input-number-out-of-range .ant-input-number-input-wrap input) {
color: #212332;
}
.properties :global(.ant-slider-horizontal .ant-slider-step) {
height: 6px;
}
.properties :global(.ant-select-single.ant-select-sm:not(.ant-select-customize-input) .ant-select-selector) {
padding: 0 4px 0 6px ;
}
.properties :global(.ant-select-single.ant-select-sm:not(.ant-select-customize-input).ant-select-show-arrow .ant-select-selection-item),
.properties :global(.ant-select-single.ant-select-sm:not(.ant-select-customize-input).ant-select-show-arrow .ant-select-selection-placeholder) {
padding-right: 10px;
}
.properties :global(.ant-select .ant-select-arrow) {
font-size: 10px;
inset-inline-end: 6px;
}

View File

@@ -90,7 +90,7 @@ export const nodeLibrary: NodeLibrary[] = [
type: "end", icon: endIcon,
config: {
output: {
type: 'define'
type: 'editor'
}
}
},
@@ -125,6 +125,7 @@ export const nodeLibrary: NodeLibrary[] = [
},
context: {
type: 'variableList',
placeholder: 'workflow.config.llm.contextPlaceholder'
},
messages: {
type: 'define',
@@ -134,7 +135,8 @@ export const nodeLibrary: NodeLibrary[] = [
content: undefined,
readonly: true
},
]
],
placeholder: 'workflow.config.llm.messagesPlaceholder'
},
memory: {
type: 'memoryConfig',
@@ -170,7 +172,8 @@ export const nodeLibrary: NodeLibrary[] = [
},
text: {
type: 'variableList',
filterLoopIterationVars: true
filterLoopIterationVars: true,
placeholder: 'workflow.config.parameter-extractor.textPlaceholder'
},
params: {
type: 'paramList',
@@ -178,6 +181,8 @@ export const nodeLibrary: NodeLibrary[] = [
prompt: {
type: 'messageEditor',
isArray: false,
titleVariant: 'borderless',
placeholder: 'workflow.config.parameter-extractor.promptPlaceholder'
},
}
}
@@ -189,7 +194,7 @@ export const nodeLibrary: NodeLibrary[] = [
{ type: "memory-read", icon: memoryReadIcon,
config: {
message: {
type: 'messageEditor',
type: 'editor',
isArray: false
},
config_id: {
@@ -212,7 +217,7 @@ export const nodeLibrary: NodeLibrary[] = [
{ type: "memory-write", icon: memoryWriteIcon,
config: {
message: {
type: 'messageEditor',
type: 'editor',
isArray: false
},
config_id: {
@@ -270,7 +275,8 @@ export const nodeLibrary: NodeLibrary[] = [
},
user_supplement_prompt: {
type: 'messageEditor',
isArray: false
isArray: false,
titleVariant: 'borderless'
}
}
},
@@ -436,6 +442,7 @@ export const nodeLibrary: NodeLibrary[] = [
type: 'messageEditor',
isArray: false,
enableJinja2: true,
titleVariant: 'borderless',
defaultValue: "{{arg1}}"
},
}

View File

@@ -1,8 +1,11 @@
import { Graph } from '@antv/x6';
import type { KnowledgeConfig } from './components/Properties/Knowledge/types'
import type { Variable } from './components/Properties/VariableList/types'
export interface NodeConfig {
type: 'input' | 'textarea' | 'select' | 'inputNumber' | 'slider' | 'customSelect' | 'define' | 'knowledge' | 'variableList' | string;
placeholder?: string;
titleVariant?: 'outlined' | 'borderless';
options?: { label: string; value: string }[];
max?: number;
@@ -14,7 +17,7 @@ export interface NodeConfig {
valueKey?: string;
labelKey?: string;
defaultValue?: any | StartVariableItem[];
defaultValue?: any;
sys?: Array<{
name: string;
@@ -37,6 +40,7 @@ export interface NodeProperties {
id?: string;
config?: Record<string, NodeConfig>;
hidden?: boolean;
cycle?: string;
}
export interface NodeLibrary {
@@ -87,27 +91,12 @@ export interface WorkflowConfig {
updated_at: number;
}
export interface VariableEditModalRef {
handleOpen: (values?: StartVariableItem) => void;
}
export interface StartVariableItem {
name: string;
type: string;
required: boolean;
description: string;
max_length?: number;
default?: string;
readonly?: boolean;
defaultValue?: any;
value?: any;
}
export interface ChatRef {
handleOpen: () => void;
}
export type GraphRef = React.MutableRefObject<Graph | undefined>
export interface VariableConfigModalRef {
handleOpen: (values: StartVariableItem[]) => void;
handleOpen: (values: Variable[]) => void;
}
export interface ChatVariable {