Merge pull request #148 from SuanmoSuanyangTechnology/feature/ui_zy

Feature/UI zy
This commit is contained in:
yingzhao
2026-01-19 15:54:17 +08:00
committed by GitHub
52 changed files with 1384 additions and 881 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

@@ -0,0 +1,12 @@
import clsx from "clsx";
import type { FC, ReactNode } from "react";
const DescWrapper: FC<{desc: string | ReactNode, className?: string}> = ({desc, className}) => {
return (
<div className={clsx(className, "rb:text-[12px] rb:text-[#5B6167] rb:font-regular rb:leading-4 ")}>
{desc}
</div>
)
}
export default DescWrapper

View File

@@ -0,0 +1,13 @@
import clsx from "clsx";
import type { FC, ReactNode } from "react";
const LabelWrapper: FC<{ title: string | ReactNode, className?: string; children?: ReactNode}> = ({title, className, children}) => {
return (
<div className={clsx(className)}>
<div className="rb:text-[14px] rb:font-medium rb:leading-5">{title}</div>
{children}
</div>
)
}
export default LabelWrapper

View File

@@ -0,0 +1,45 @@
import { Switch, Form, ConfigProvider } from "antd";
import useSize from 'antd/lib/config-provider/hooks/useSize'
import type { FC, ReactNode } from "react";
import { useContext } from "react";
import LabelWrapper from './LabelWrapper'
import DescWrapper from './DescWrapper'
interface SwitchFormItemProps {
title: string | ReactNode;
desc?: string | ReactNode;
name: string | string[];
size?: 'small' | 'default'
className?: string;
disabled?: boolean;
}
const SwitchFormItem: FC<SwitchFormItemProps> = ({
title,
desc,
name,
size = 'default',
className,
disabled
}) => {
const componentSize = useSize()
console.log('componentSize', componentSize)
return (
<div className={`${className} rb:flex rb:items-center rb:justify-between`}>
<LabelWrapper title={title}>
{desc && <DescWrapper desc={desc} className="rb:mt-2" />}
</LabelWrapper>
<Form.Item
name={name}
valuePropName="checked"
className="rb:mb-0!"
>
<Switch disabled={disabled} size={size} />
</Form.Item>
</div>
)
}
export default SwitchFormItem

View File

@@ -54,7 +54,7 @@ const AppHeader: FC<{source?: 'space' | 'manage';}> = ({source = 'manage'}) => {
key: '1', key: '1',
label: (<> label: (<>
<div>{user.username}</div> <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

@@ -150,9 +150,19 @@ const RbMarkdown: FC<RbMarkdownProps> = ({
) )
} }
// 处理键盘快捷键
const handleKeyDown = (e: React.KeyboardEvent) => {
if ((e.ctrlKey || e.metaKey) && e.key === 'c') {
const selection = window.getSelection()
if (selection && selection.toString()) {
navigator.clipboard.writeText(selection.toString())
}
}
}
// 预览模式 // 预览模式
return ( return (
<div className="rb:relative"> <div className="rb:relative" onKeyDown={handleKeyDown} tabIndex={0}>
<style>{` <style>{`
.html-comment { .html-comment {
color: #999; color: #999;

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -21,6 +21,8 @@ export const lightTheme: ThemeConfig = {
colorBorderSecondary: '#DFE4ED', colorBorderSecondary: '#DFE4ED',
// colorBgContainer: '#FBFDFF', // colorBgContainer: '#FBFDFF',
colorError: '#FF5D34', colorError: '#FF5D34',
sizeSM: 12,
fontSizeSM: 12,
}, },
components: { components: {
Layout: { Layout: {
@@ -86,6 +88,7 @@ export const lightTheme: ThemeConfig = {
rowSelectedBg: '#E9F1FF', rowSelectedBg: '#E9F1FF',
rowSelectedHoverBg: '#F0F3F8', rowSelectedHoverBg: '#F0F3F8',
cellPaddingBlock: 8, cellPaddingBlock: 8,
cellFontSizeSM: 12,
// cellPaddingInline: 24, // cellPaddingInline: 24,
selectionColumnWidth: 48, selectionColumnWidth: 48,
@@ -95,6 +98,13 @@ export const lightTheme: ThemeConfig = {
lastItemColor: '#212332', lastItemColor: '#212332',
linkColor: '#5B6167', linkColor: '#5B6167',
linkHoverColor: '#212332', 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 { 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 type { MenuProps } from 'antd';
import { useTranslation } from 'react-i18next'; import { useTranslation } from 'react-i18next';
import styles from '../index.module.css' import styles from '../index.module.css'
@@ -141,10 +141,12 @@ const ConfigHeader: FC<ConfigHeaderProps> = ({
{/* <Button type="primary">{t('workflow.export')}</Button> */} {/* <Button type="primary">{t('workflow.export')}</Button> */}
<img src={logoutIcon} className="rb:w-4 rb:h-4 rb:cursor-pointer" onClick={goToApplication} /> <img src={logoutIcon} className="rb:w-4 rb:h-4 rb:cursor-pointer" onClick={goToApplication} />
</div> </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}> : <Flex justify="flex-end">
<img src={logoutIcon} className="rb:mr-2 rb:w-4 rb:h-4" /> <div className="rb:h-8 rb:flex rb:items-center rb:text-[12px] rb:text-[#5B6167] rb:font-regular rb:cursor-pointer" onClick={goToApplication}>
{t('application.returnToApplicationList')} <img src={logoutIcon} className="rb:mr-2 rb:w-4 rb:h-4" />
</div> {t('application.returnToApplicationList')}
</div>
</Flex>
} }
</Header> </Header>
<ApplicationModal <ApplicationModal

View File

@@ -1,5 +1,5 @@
import React, { useState, useEffect } from 'react'; import React, { useState, useEffect } from 'react';
import { Row, Col, Form, Slider, Button, Alert, message, Switch, Space } from 'antd'; import { Row, Col, Form, Slider, Button, Alert, message, Space } from 'antd';
import { useParams } from 'react-router-dom'; import { useParams } from 'react-router-dom';
import { useTranslation } from 'react-i18next'; import { useTranslation } from 'react-i18next';
import RbCard from '@/components/RbCard/Card'; import RbCard from '@/components/RbCard/Card';
@@ -9,6 +9,7 @@ import type { ConfigForm } from './types'
import CustomSelect from '@/components/CustomSelect'; import CustomSelect from '@/components/CustomSelect';
import { getModelListUrl } from '@/api/models' import { getModelListUrl } from '@/api/models'
import Tag from '@/components/Tag' import Tag from '@/components/Tag'
import SwitchFormItem from '@/components/FormItem/SwitchFormItem'
const configList = [ const configList = [
{ {
@@ -158,23 +159,17 @@ const EmotionEngine: React.FC = () => {
</div> </div>
) )
} }
return ( return (
<div className="rb:flex rb:items-center rb:justify-between rb:mb-6"> <SwitchFormItem
<div> title={t(`emotionEngine.${config.key}`)}
<span className="rb:text-[14px] rb:font-medium rb:leading-5">{t(`emotionEngine.${config.key}`)}</span> name={config.key}
desc={<>
{config.hasSubTitle && <div className="rb:mt-1 rb:text-[12px] rb:text-[#5B6167] rb:font-regular rb:leading-4">{t(`emotionEngine.${config.key}_subTitle`)}</div>} {config.hasSubTitle && <div className="rb:mt-1 rb:text-[12px] rb:text-[#5B6167] rb:font-regular rb:leading-4">{t(`emotionEngine.${config.key}_subTitle`)}</div>}
<div className="rb:mt-1 rb:text-[12px] rb:text-[#5B6167] rb:font-regular rb:leading-4">{t(`emotionEngine.${config.key}_desc`)}</div> <div className="rb:mt-1 rb:text-[12px] rb:text-[#5B6167] rb:font-regular rb:leading-4">{t(`emotionEngine.${config.key}_desc`)}</div>
</div> </>}
<Form.Item className="rb:mb-6"
name={config.key} disabled={!values?.emotion_enabled && config.key !== 'emotion_enabled'}
valuePropName="checked" />
className="rb:ml-2 rb:mb-0!"
>
<Switch
disabled={!values?.emotion_enabled && config.key !== 'emotion_enabled'} />
</Form.Item>
</div>
) )
})} })}
<Row gutter={16} className="rb:mt-3"> <Row gutter={16} className="rb:mt-3">

View File

@@ -1,5 +1,5 @@
import React, { useState, useEffect } from 'react'; import React, { useState, useEffect } from 'react';
import { Row, Col, Form, Slider, Button, Space, message, Switch } from 'antd'; import { Row, Col, Form, Slider, Button, Space, message } from 'antd';
import { useParams } from 'react-router-dom'; import { useParams } from 'react-router-dom';
import { useTranslation } from 'react-i18next'; import { useTranslation } from 'react-i18next';
import RbCard from '@/components/RbCard/Card'; import RbCard from '@/components/RbCard/Card';
@@ -7,6 +7,7 @@ import strategyImpactSimulator from '@/assets/images/memory/strategyImpactSimula
import LineChart from './components/LineChart' import LineChart from './components/LineChart'
import { getMemoryForgetConfig, updateMemoryForgetConfig } from '@/api/memory' import { getMemoryForgetConfig, updateMemoryForgetConfig } from '@/api/memory'
import type { ConfigForm } from './types' import type { ConfigForm } from './types'
import SwitchFormItem from '@/components/FormItem/SwitchFormItem'
const configList = [ const configList = [
{ {
@@ -155,26 +156,12 @@ const ForgettingEngine: React.FC = () => {
{configList.map(config => { {configList.map(config => {
if (config.type === 'button') { if (config.type === 'button') {
return ( return (
<div key={config.key} className="rb:mb-2"> <SwitchFormItem
<div className="rb:flex rb:items-center rb:justify-between"> title={t(`forgettingEngine.${config.key}`)}
<div> name={config.name}
<span className="rb:text-[14px] rb:font-medium rb:leading-5">{t(`forgettingEngine.${config.key}`)}</span> desc={config.type && <span>{t(`forgettingEngine.type`)}: {config.type}</span>}
</div> className="rb:mb-2"
<Form.Item />
name={config.name}
valuePropName="checked"
className="rb:ml-2 rb:mb-0!"
>
<Switch />
</Form.Item>
</div>
<div className="rb:flex rb:text-[12px] rb:items-center rb:justify-between rb:text-[#5B6167] rb:leading-5">
<Space size={4}>
{config.range && <span>{t(`forgettingEngine.range`)}: {config.range?.join('-')}</span>}
{config.type && <span>{t(`forgettingEngine.type`)}: {config.type}</span>}
</Space>
</div>
</div>
) )
} }
return ( return (
@@ -191,8 +178,6 @@ const ForgettingEngine: React.FC = () => {
> >
{config.type === 'decimal' {config.type === 'decimal'
? <Slider tooltip={{ open: false }} max={config.range?.[1] || 1} min={config.range?.[0] || 0} step={config.step ?? 0.01} style={{ margin: '0' }} /> ? <Slider tooltip={{ open: false }} max={config.range?.[1] || 1} min={config.range?.[0] || 0} step={config.step ?? 0.01} style={{ margin: '0' }} />
: config.type === 'button'
? <Switch />
: null : null
} }
</Form.Item> </Form.Item>

View File

@@ -11,6 +11,7 @@ import { getModelList } from '@/api/models';
import type { Model } from '@/views/ModelManagement/types' import type { Model } from '@/views/ModelManagement/types'
import { configList } from './constant' import { configList } from './constant'
import Result from './components/Result' import Result from './components/Result'
import SwitchFormItem from '@/components/FormItem/SwitchFormItem'
const keys = [ const keys = [
// 'example', // 'example',
@@ -173,25 +174,18 @@ const MemoryExtractionEngine: FC = () => {
} }
)} )}
> >
<div className="rb:text-[16px] rb:font-medium rb:leading-[22px]">{t(`memoryExtractionEngine.${vo.title}`)}</div> <div className="rb:text-[16px] rb:font-medium rb:leading-5.5">{t(`memoryExtractionEngine.${vo.title}`)}</div>
<div className="rb:mt-1 rb:text-[12px] rb:text-[#5B6167] rb:font-regular rb:leading-4">{t(`memoryExtractionEngine.${vo.title}SubTitle`)}</div> <div className="rb:mt-1 rb:text-[12px] rb:text-[#5B6167] rb:font-regular rb:leading-4">{t(`memoryExtractionEngine.${vo.title}SubTitle`)}</div>
{vo.list.map(config => ( {vo.list.map(config => (
<div key={config.label}> <div key={config.label}>
{config.control === 'button' && {config.control === 'button' &&
<div className="rb:flex rb:items-center rb:justify-between rb:mt-6"> <SwitchFormItem
<div> title={<>-{t(`memoryExtractionEngine.${config.label}`)}</>}
<span className="rb:text-[14px] rb:font-medium rb:leading-5">-{t(`memoryExtractionEngine.${config.label}`)}</span> name={config.variableName}
<ConfigDesc config={config} className="rb:ml-2" /> desc={<ConfigDesc config={config} className="rb:ml-2" />}
</div> className="rb:mt-6"
<Form.Item />
name={config.variableName}
valuePropName="checked"
className="rb:ml-2 rb:mb-0!"
>
<Switch />
</Form.Item>
</div>
} }
{config.control === 'select' && {config.control === 'select' &&
<> <>

View File

@@ -1,5 +1,5 @@
import React, { useState, useEffect } from 'react'; import React, { useState, useEffect } from 'react';
import { Row, Col, Form, App, Button, Switch, Space, Select } from 'antd'; import { Row, Col, Form, App, Button, Space, Select } from 'antd';
import { useParams } from 'react-router-dom'; import { useParams } from 'react-router-dom';
import { useTranslation } from 'react-i18next'; import { useTranslation } from 'react-i18next';
@@ -11,6 +11,7 @@ import CustomSelect from '@/components/CustomSelect';
import { getModelListUrl } from '@/api/models' import { getModelListUrl } from '@/api/models'
import Tag from '@/components/Tag' import Tag from '@/components/Tag'
import { useI18n } from '@/store/locale'; import { useI18n } from '@/store/locale';
import SwitchFormItem from '@/components/FormItem/SwitchFormItem'
const configList = [ const configList = [
// 启用反思引擎 // 启用反思引擎
@@ -219,21 +220,16 @@ const SelfReflectionEngine: React.FC = () => {
} }
return ( return (
<div className="rb:flex rb:items-center rb:justify-between rb:mb-6"> <SwitchFormItem
<div> title={t(`reflectionEngine.${config.key}`)}
<span className="rb:text-[14px] rb:font-medium rb:leading-5">{t(`reflectionEngine.${config.key}`)}</span> name={config.key}
desc={<>
{(config as any).hasSubTitle && <div className="rb:mt-1 rb:text-[12px] rb:text-[#5B6167] rb:font-regular rb:leading-4">{t(`reflectionEngine.${config.key}_subTitle`)}</div>} {(config as any).hasSubTitle && <div className="rb:mt-1 rb:text-[12px] rb:text-[#5B6167] rb:font-regular rb:leading-4">{t(`reflectionEngine.${config.key}_subTitle`)}</div>}
<div className="rb:mt-1 rb:text-[12px] rb:text-[#5B6167] rb:font-regular rb:leading-4">{t(`reflectionEngine.${config.key}_desc`)}</div> <div className="rb:mt-1 rb:text-[12px] rb:text-[#5B6167] rb:font-regular rb:leading-4">{t(`reflectionEngine.${config.key}_desc`)}</div>
</div> </>}
<Form.Item className="rb:mb-6"
name={config.key} disabled={!values?.reflection_enabled && config.key !== 'reflection_enabled'}
valuePropName="checked" />
className="rb:ml-2 rb:mb-0!"
>
<Switch
disabled={!values?.reflection_enabled && config.key !== 'reflection_enabled'} />
</Form.Item>
</div>
) )
})} })}
<Row gutter={16} className="rb:mt-3"> <Row gutter={16} className="rb:mt-3">

View File

@@ -2,12 +2,13 @@ import { forwardRef, useImperativeHandle, useState } from 'react';
import { Form, Input, InputNumber, Checkbox } from 'antd'; import { Form, Input, InputNumber, Checkbox } from 'antd';
import { useTranslation } from 'react-i18next'; 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' import RbModal from '@/components/RbModal'
interface VariableEditModalProps { interface VariableEditModalProps {
refresh: (values: StartVariableItem[]) => void; refresh: (values: Variable[]) => void;
variables: StartVariableItem[] variables: Variable[]
} }
const VariableConfigModal = forwardRef<VariableConfigModalRef, VariableEditModalProps>(({ const VariableConfigModal = forwardRef<VariableConfigModalRef, VariableEditModalProps>(({
@@ -15,9 +16,9 @@ const VariableConfigModal = forwardRef<VariableConfigModalRef, VariableEditModal
}, ref) => { }, ref) => {
const { t } = useTranslation(); const { t } = useTranslation();
const [visible, setVisible] = useState(false); const [visible, setVisible] = useState(false);
const [form] = Form.useForm<{variables: StartVariableItem[]}>(); const [form] = Form.useForm<{variables: Variable[]}>();
const [loading, setLoading] = useState(false) const [loading, setLoading] = useState(false)
const [initialValues, setInitialValues] = useState<StartVariableItem[]>([]) const [initialValues, setInitialValues] = useState<Variable[]>([])
// 封装取消方法,添加关闭弹窗逻辑 // 封装取消方法,添加关闭弹窗逻辑
const handleClose = () => { const handleClose = () => {
@@ -26,7 +27,7 @@ const VariableConfigModal = forwardRef<VariableConfigModalRef, VariableEditModal
setLoading(false) setLoading(false)
}; };
const handleOpen = (values: StartVariableItem[]) => { const handleOpen = (values: Variable[]) => {
setVisible(true); setVisible(true);
form.setFieldsValue({variables: values}) form.setFieldsValue({variables: values})
setInitialValues([...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 { LexicalComposer } from '@lexical/react/LexicalComposer';
import { RichTextPlugin } from '@lexical/react/LexicalRichTextPlugin'; import { RichTextPlugin } from '@lexical/react/LexicalRichTextPlugin';
import { ContentEditable } from '@lexical/react/LexicalContentEditable'; import { ContentEditable } from '@lexical/react/LexicalContentEditable';
@@ -25,7 +25,11 @@ interface LexicalEditorProps {
options: Suggestion[]; options: Suggestion[];
variant?: 'outlined' | 'borderless'; variant?: 'outlined' | 'borderless';
height?: number; height?: number;
fontSize?: number;
lineHeight?: number;
enableJinja2?: boolean; enableJinja2?: boolean;
size?: 'default' | 'small';
type?: 'input' | 'textarea'
} }
const theme = { const theme = {
@@ -51,8 +55,9 @@ const Editor: FC<LexicalEditorProps> =({
onChange, onChange,
options, options,
variant = 'borderless', variant = 'borderless',
height = 60,
enableJinja2 = false, enableJinja2 = false,
size = 'default',
type = 'textarea'
}) => { }) => {
const [_count, setCount] = useState(0); const [_count, setCount] = useState(0);
@@ -94,12 +99,9 @@ const Editor: FC<LexicalEditorProps> =({
display: flex; display: flex;
} }
.line-numbers { .line-numbers {
background-color: #f8f9fa;
border-right: 1px solid #e1e4e8;
color: #656d76;
font-family: 'SFMono-Regular', Consolas, 'Liberation Mono', Menlo, monospace; font-family: 'SFMono-Regular', Consolas, 'Liberation Mono', Menlo, monospace;
font-size: 12px; font-size: 12px;
line-height: 20px; line-height: 16px;
padding: 4px 8px; padding: 4px 8px;
text-align: right; text-align: right;
user-select: none; user-select: none;
@@ -142,6 +144,21 @@ const Editor: FC<LexicalEditorProps> =({
console.error(error); 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 ( return (
<LexicalComposer initialConfig={initialConfig}> <LexicalComposer initialConfig={initialConfig}>
@@ -152,7 +169,7 @@ const Editor: FC<LexicalEditorProps> =({
<div className="editor-with-line-numbers" style={{ <div className="editor-with-line-numbers" style={{
border: variant === 'borderless' ? 'none' : '1px solid #DFE4ED', border: variant === 'borderless' ? 'none' : '1px solid #DFE4ED',
borderRadius: '6px', borderRadius: '6px',
minHeight: `${height}px`, minHeight: minheight,
}}> }}>
<div className="line-numbers"> <div className="line-numbers">
<div>1</div> <div>1</div>
@@ -160,12 +177,12 @@ const Editor: FC<LexicalEditorProps> =({
<ContentEditable <ContentEditable
className="editor-content-with-numbers" className="editor-content-with-numbers"
style={{ style={{
minHeight: `${height}px`, minHeight: minheight,
padding: '4px 11px', padding: '4px 0',
outline: 'none', outline: 'none',
resize: 'none', resize: 'none',
fontSize: '14px', fontSize: fontSize,
lineHeight: '20px', lineHeight: lineHeight,
border: 'none', border: 'none',
}} }}
/> />
@@ -173,14 +190,14 @@ const Editor: FC<LexicalEditorProps> =({
) : ( ) : (
<ContentEditable <ContentEditable
style={{ style={{
minHeight: `${height}px`, minHeight: minheight,
padding: variant === 'borderless' ? '0' : '4px 11px', padding: variant === 'borderless' ? '0' : '4px 11px',
border: variant === 'borderless' ? 'none' : '1px solid #DFE4ED', border: variant === 'borderless' ? 'none' : '1px solid #DFE4ED',
borderRadius: '6px', borderRadius: '6px',
outline: 'none', outline: 'none',
resize: 'none', resize: 'none',
fontSize: '14px', fontSize: fontSize,
lineHeight: '20px', lineHeight: lineHeight,
}} }}
/> />
) )
@@ -188,12 +205,13 @@ const Editor: FC<LexicalEditorProps> =({
placeholder={ placeholder={
<div <div
style={{ style={{
minHeight: placeHolderMinheight,
position: 'absolute', position: 'absolute',
top: variant === 'borderless' ? '0' : '6px', top: variant === 'borderless' ? '0' : '6px',
left: enableJinja2 ? '59px' : (variant === 'borderless' ? '0' : '11px'), left: enableJinja2 ? '59px' : (variant === 'borderless' ? '0' : '11px'),
color: '#5B6167', color: '#A8A9AA',
fontSize: '14px', fontSize: fontSize,
lineHeight: '20px', lineHeight: placeHolderMinheight,
pointerEvents: 'none', pointerEvents: 'none',
}} }}
> >

View File

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

View File

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

View File

@@ -1,8 +1,7 @@
import { type FC } from 'react' import { type FC } from 'react'
import clsx from 'clsx' import clsx from 'clsx'
import { useTranslation } from 'react-i18next'; import { useTranslation } from 'react-i18next';
import { Form, Button, Select, Space, Row, Col, Divider, InputNumber, Radio, type SelectProps } from 'antd' import { Form, Button, Select, Space, Divider, InputNumber, Radio, type SelectProps } from 'antd'
import { DeleteOutlined } from '@ant-design/icons';
import type { Suggestion } from '../../Editor/plugin/AutocompletePlugin' import type { Suggestion } from '../../Editor/plugin/AutocompletePlugin'
import VariableSelect from '../VariableSelect' import VariableSelect from '../VariableSelect'
@@ -247,37 +246,40 @@ const CaseList: FC<CaseListProps> = ({
{(conditionFields, { add: addCondition, remove: removeCondition }) => { {(conditionFields, { add: addCondition, remove: removeCondition }) => {
const logicalOperator = form.getFieldValue(name)?.[caseIndex]?.logical_operator || 'and' const logicalOperator = form.getFieldValue(name)?.[caseIndex]?.logical_operator || 'and'
return ( 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={clsx("rb:relative")}>
<div className="rb:flex rb:items-center rb:justify-between rb:mb-3"> <div className="rb:flex rb:items-center rb:justify-between rb:mb-2">
<span className="rb:font-medium"> <div className="rb:text-[12px] rb:leading-4.5">
{caseIndex === 0 ? 'IF' : 'ELIF'}<br/> <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>} {caseFields.length > 1 && <span className="rb:text-[10px] rb:text-[#5B6167]"> ({`CASE ${caseIndex + 1}`})</span>}
</span> </div>
<Space> <Space>
<Button <Button
type="dashed"
onClick={() => addCondition({})} onClick={() => addCondition({})}
className="rb:py-0! rb:px-1! rb:text-[12px]!"
size="small" size="small"
> >
+ {t('workflow.config.addCase')} + {t('workflow.config.addCase')}
</Button> </Button>
{caseFields.length > 1 && <DeleteOutlined {caseFields.length > 1 &&
className="rb:text-[12px]" <Button
onClick={() => handleRemoveCase(removeCase, caseField.name, caseIndex)} className="rb:py-0! rb:px-1! rb:text-[12px]!"
/>} onClick={() => handleRemoveCase(removeCase, caseField.name, caseIndex)}
>
{t('common.remove')}
</Button>
}
</Space> </Space>
</div> </div>
{conditionFields?.length > 1 && {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: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-[calc(50%-13px)]">
<div className="rb:absolute rb:z-10 rb:left-0 rb:top-[50%] rb:transform-[translateY(-50%)]]">
<Form.Item name={[caseField.name, 'logical_operator']} noStyle > <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> </Form.Item>
</div> </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) => { {conditionFields.map((conditionField, conditionIndex) => {
const cases = form.getFieldValue(name) || []; const cases = form.getFieldValue(name) || [];
const currentCase = cases[caseIndex] || {}; const currentCase = cases[caseIndex] || {};
@@ -290,91 +292,86 @@ const CaseList: FC<CaseListProps> = ({
const operatorList = operatorsObj[leftFieldType || 'default'] || operatorsObj.default || []; const operatorList = operatorsObj[leftFieldType || 'default'] || operatorsObj.default || [];
const inputType = leftFieldType === 'number' ? currentExpression.input_type : undefined; const inputType = leftFieldType === 'number' ? currentExpression.input_type : undefined;
return ( return (
<div key={conditionField.key} className={clsx({ <div key={conditionField.key} className="rb:flex rb:items-start rb:ml-9.5 rb:mb-4">
"rb:mb-3": conditionIndex !== conditionFields.length - 1 <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", {
<div className="rb:border rb:border-[#DFE4ED] rb:rounded-md rb:px-2 rb:py-1.5 rb:bg-white"> 'rb:border-b rb:border-b-[#DFE4ED]': !hideRightField
<Row gutter={12} className="rb:mb-1"> })}>
<Col span={14}> <Form.Item name={[conditionField.name, 'left']} noStyle>
<Form.Item name={[conditionField.name, 'left']} noStyle> <VariableSelect
<VariableSelect placeholder={t('common.pleaseSelect')}
placeholder={t('common.pleaseSelect')} options={options}
options={options} size="small"
size="small" allowClear={false}
allowClear={false} popupMatchSelectWidth={false}
popupMatchSelectWidth={false} onChange={(val) => handleLeftFieldChange(caseIndex, conditionIndex, val)}
onChange={(val) => handleLeftFieldChange(caseIndex, conditionIndex, val)} className="rb:bg-white! rb:w-29.5!"
/>
</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)}
/> />
</Col> </Form.Item>
</Row> <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' {leftFieldType === 'number'
? <Row> ? <div className="rb:flex rb:items-center">
<Col span={12}> <Form.Item name={[conditionField.name, 'input_type']} noStyle>
<Form.Item name={[conditionField.name, 'input_type']} noStyle> <Select
<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')} placeholder={t('common.pleaseSelect')}
options={[{ value: 'Variable', label: 'Variable' }, { value: 'Constant', label: 'Constant' }]} options={options.filter(vo => vo.dataType === 'number')}
allowClear={false}
popupMatchSelectWidth={false} popupMatchSelectWidth={false}
variant="borderless" variant="borderless"
onChange={() => handleInputTypeChange(caseIndex, conditionIndex)} size="small"
/> />
</Form.Item> : <InputNumber
</Col> placeholder={t('common.pleaseEnter')}
<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}
variant="borderless" variant="borderless"
className="rb:w-full!"
onChange={(value) => form.setFieldValue([name, caseIndex, 'expressions', conditionIndex, 'right'], value)}
/> />
: <InputNumber }
placeholder={t('common.pleaseEnter')} </Form.Item>
variant="borderless" </div>
className="rb:w-full!"
onChange={(value) => form.setFieldValue([name, caseIndex, 'expressions', conditionIndex, 'right'], value)}
/>
}
</Form.Item>
</Col>
</Row>
: <Form.Item name={[conditionField.name, 'right']} noStyle> : <Form.Item name={[conditionField.name, 'right']} noStyle>
{leftFieldType === 'boolean' {leftFieldType === 'boolean'
? <Radio.Group block> ? <Radio.Group block>
<Radio.Button value={true}>True</Radio.Button> <Radio.Button value={true}>True</Radio.Button>
<Radio.Button value={false}>False</Radio.Button> <Radio.Button value={false}>False</Radio.Button>
</Radio.Group> </Radio.Group>
: <Editor options={options} /> : <Editor options={options} size="small" type="input" />
} }
</Form.Item> </Form.Item>
} }
</>} </div>}
</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> </div>
) )
})} })}
@@ -388,6 +385,8 @@ const CaseList: FC<CaseListProps> = ({
<Button <Button
type="dashed" type="dashed"
block block
size="middle"
className="rb:text-[12px]!"
onClick={() => handleAddCase(addCase)} onClick={() => handleAddCase(addCase)}
> >
+ ELIF + ELIF
@@ -395,9 +394,9 @@ const CaseList: FC<CaseListProps> = ({
</> </>
)} )}
</Form.List> </Form.List>
<Divider />
<div className="rb:font-medium">ELSE</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] ">{t('workflow.config.if-else.else_desc')}</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 { type FC } from 'react';
import { useTranslation } from 'react-i18next'; import { useTranslation } from 'react-i18next';
import { Button, Form, Space } from 'antd'; import { Button, Form, Space } from 'antd';
import { DeleteOutlined } from '@ant-design/icons';
import { Graph, Node } from '@antv/x6'; import { Graph, Node } from '@antv/x6';
import Editor from '../../Editor'; import Editor from '../../Editor';
import type { Suggestion } from '../../Editor/plugin/AutocompletePlugin' import type { Suggestion } from '../../Editor/plugin/AutocompletePlugin'
@@ -151,17 +151,15 @@ const CategoryList: FC<CategoryListProps> = ({ parentName, selectedNode, graphRe
const contentLength = (currentItem.class_name || '').length; const contentLength = (currentItem.class_name || '').length;
return ( 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 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"> <div className="rb:flex rb:items-center rb:gap-1">
<span className="rb:text-xs rb:text-gray-500">{contentLength}</span> <span className="rb:text-xs rb:text-gray-500">{contentLength}</span>
<Button <div
type="text" 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')]"
size="small"
icon={<DeleteOutlined />}
onClick={() => handleRemoveCategory(remove, name, index)} onClick={() => handleRemoveCategory(remove, name, index)}
/> ></div>
</div> </div>
</div> </div>
<Form.Item <Form.Item
@@ -172,6 +170,7 @@ const CategoryList: FC<CategoryListProps> = ({ parentName, selectedNode, graphRe
<Editor <Editor
placeholder={t('common.pleaseEnter')} placeholder={t('common.pleaseEnter')}
options={options} options={options}
size="small"
/> />
</Form.Item> </Form.Item>
</div> </div>
@@ -179,8 +178,10 @@ const CategoryList: FC<CategoryListProps> = ({ parentName, selectedNode, graphRe
<Button <Button
type="dashed" type="dashed"
size="middle"
block
onClick={() => handleAddCategory(add)} onClick={() => handleAddCategory(add)}
className="rb:w-full" className="rb:text-[12px]!"
> >
+ {t('workflow.config.question-classifier.addClassName')} + {t('workflow.config.question-classifier.addClassName')}
</Button> </Button>

View File

@@ -1,11 +1,10 @@
import { type FC } from 'react' import { type FC } from 'react'
import clsx from 'clsx'
import { useTranslation } from 'react-i18next'; import { useTranslation } from 'react-i18next';
import { Form, Button, Select, Row, Col, InputNumber, Radio, Input, type SelectProps } from 'antd' import { Form, Button, Select, InputNumber, Radio, Input, Divider, type SelectProps } from 'antd'
import { DeleteOutlined } from '@ant-design/icons';
import type { Suggestion } from '../../Editor/plugin/AutocompletePlugin' import type { Suggestion } from '../../Editor/plugin/AutocompletePlugin'
import VariableSelect from '../VariableSelect' import VariableSelect from '../VariableSelect'
import Editor from '../../Editor'
interface Case { interface Case {
logical_operator: 'and' | 'or'; logical_operator: 'and' | 'or';
@@ -84,35 +83,49 @@ const ConditionList: FC<CaseListProps> = ({
return ( return (
<> <>
<Form.List name={[parentName, 'expressions']}> <Form.List name={[parentName, 'expressions']}>
{(fields, { add, remove }) => ( {(fields, { add, remove }) => {
<div> const logicalOperator = form.getFieldValue([parentName, 'logical_operator']);
return (
<div className="rb:relative"> <div className="rb:relative">
{fields.map((field, index) => { <div className="rb:flex rb:items-center rb:justify-between rb:mb-2">
const expressions = form.getFieldValue([parentName, 'expressions']) || []; <div className="rb:text-[12px] rb:font-medium rb:leading-4.5">
const currentExpression = expressions[index] || {}; {t('workflow.config.loop.condition')}
const currentOperator = currentExpression.operator; </div>
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 ( <Button
<div key={field.key} className="rb:mb-3"> onClick={() => add({})}
{index > 0 && (<> className="rb:py-0! rb:px-1! rb:text-[12px]!"
<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> size="small"
<div className="rb:absolute rb:z-10 rb:left-0 rb:top-[50%] rb:transform-[translateY(-50%)]]"> >
<Form.Item name={[parentName, 'logical_operator']} noStyle > + {t('workflow.config.loop.addCondition')}
<Button size="small" className="rb:cursor-pointer" onClick={handleChangeLogicalOperator}>{logicalOperator}</Button> </Button>
</Form.Item> </div>
</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;
<div className="rb:border rb:border-[#DFE4ED] rb:rounded-md rb:p-3 rb:bg-white rb:ml-6"> return (
<Row gutter={8} align="middle"> <div key={field.key} className="rb:flex rb:items-start rb:ml-9.5 rb:mb-4">
<Col span={14}> <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> <Form.Item name={[field.name, 'left']} noStyle>
<VariableSelect <VariableSelect
options={options.filter(vo => options={options.filter(vo =>
@@ -124,12 +137,10 @@ const ConditionList: FC<CaseListProps> = ({
size="small" size="small"
allowClear={false} allowClear={false}
popupMatchSelectWidth={false} popupMatchSelectWidth={false}
placeholder={t('common.pleaseSelect')}
onChange={(val) => handleLeftFieldChange(index, val)} onChange={(val) => handleLeftFieldChange(index, val)}
/> />
</Form.Item> </Form.Item>
</Col>
<Col span={8}>
<Form.Item name={[field.name, 'operator']} noStyle> <Form.Item name={[field.name, 'operator']} noStyle>
<Select <Select
options={operatorList.map(vo => ({ options={operatorList.map(vo => ({
@@ -138,84 +149,67 @@ const ConditionList: FC<CaseListProps> = ({
}))} }))}
size="small" size="small"
popupMatchSelectWidth={false} popupMatchSelectWidth={false}
placeholder={t('common.pleaseSelect')}
/> />
</Form.Item> </Form.Item>
</Col> </div>
<Col span={2}>
<DeleteOutlined
className="rb:text-gray-400 rb:cursor-pointer rb:hover:text-red-500"
onClick={() => remove(field.name)}
/>
</Col>
{!hideRightField && <> {!hideRightField && <div className="rb:p-1">
{leftFieldType === 'number' {leftFieldType === 'number'
? <Col span={24}><Row> ? <div className="rb:flex rb:items-center">
<Col span={12}> <Form.Item name={[field.name, 'input_type']} noStyle>
<Form.Item name={[field.name, 'input_type']} noStyle> <Select
<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')} placeholder={t('common.pleaseSelect')}
options={[{ value: 'Variable', label: 'Variable' }, { value: 'Constant', label: 'Constant' }]} options={options.filter(vo => vo.dataType === 'number')}
allowClear={false}
popupMatchSelectWidth={false} popupMatchSelectWidth={false}
variant="borderless" variant="borderless"
className="rb:w-full!" className="rb:w-full!"
onChange={() => handleInputTypeChange(index)}
/> />
</Form.Item> : <InputNumber
</Col> placeholder={t('common.pleaseEnter')}
<Col span={12}> variant="borderless"
<Form.Item name={[field.name, 'right']} noStyle> className="rb:w-full!"
{inputType === 'Variable' onChange={(value) => form.setFieldValue([parentName, 'expressions', index, 'right'], value)}
? />
<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')} />
} }
</Form.Item> </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>
} }
</>} </div>}
</div>
</Row> <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> )
) })}
})}
</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> </Form.List>
</> </>
) )

View File

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

View File

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

View File

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

View File

@@ -1,7 +1,6 @@
import { useMemo } from 'react';
import { useTranslation } from 'react-i18next' import { useTranslation } from 'react-i18next'
import { Button, Select, Table, Form, type TableProps } from 'antd'; 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 type { Suggestion } from '../../Editor/plugin/AutocompletePlugin';
import Empty from '@/components/Empty'; import Empty from '@/components/Empty';
import VariableSelect from '../VariableSelect'; import VariableSelect from '../VariableSelect';
@@ -19,6 +18,7 @@ interface EditableTableProps {
options?: Suggestion[]; options?: Suggestion[];
typeOptions?: { value: string, label: string }[] typeOptions?: { value: string, label: string }[]
filterBooleanType?: boolean; filterBooleanType?: boolean;
size?: "small"
} }
const EditableTable: React.FC<EditableTableProps> = ({ const EditableTable: React.FC<EditableTableProps> = ({
@@ -26,7 +26,8 @@ const EditableTable: React.FC<EditableTableProps> = ({
title, title,
options = [], options = [],
typeOptions = [], typeOptions = [],
filterBooleanType = false filterBooleanType = false,
size = 'small'
}) => { }) => {
const { t } = useTranslation(); const { t } = useTranslation();
@@ -38,21 +39,24 @@ const EditableTable: React.FC<EditableTableProps> = ({
const getColumns = (remove: (index: number) => void): TableProps<TableRow>['columns'] => { const getColumns = (remove: (index: number) => void): TableProps<TableRow>['columns'] => {
const hasType = typeOptions.length > 0; const hasType = typeOptions.length > 0;
const baseWidth = hasType ? '35%' : '45%'; const cellClassName="rb:p-1!"
const contentClassName ="rb:w-[108px]! rb:text-[12px]!"
return [ return [
{ {
title: t('workflow.config.name'), title: t('workflow.config.name'),
dataIndex: 'name', dataIndex: 'name',
width: baseWidth, className: cellClassName,
render: (_: any, __: TableRow, index: number) => ( render: (_: any, __: TableRow, index: number) => (
<Form.Item name={[index, 'name']} noStyle> <Form.Item name={[index, 'name']} noStyle>
<VariableSelect <VariableSelect
placeholder={t('common.pleaseSelect')} placeholder={t('common.pleaseSelect')}
size="small" // size="small"
options={options} options={options}
filterBooleanType={filterBooleanType} filterBooleanType={filterBooleanType}
popupMatchSelectWidth={false} popupMatchSelectWidth={false}
className={contentClassName}
size={size}
/> />
</Form.Item> </Form.Item>
) )
@@ -61,18 +65,20 @@ const EditableTable: React.FC<EditableTableProps> = ({
title: t('workflow.config.type'), title: t('workflow.config.type'),
dataIndex: 'type', dataIndex: 'type',
width: '20%', width: '20%',
className: cellClassName,
render: (_: any, __: TableRow, index: number) => ( render: (_: any, __: TableRow, index: number) => (
<Form.Item shouldUpdate noStyle> <Form.Item shouldUpdate noStyle>
{(form) => ( {(form) => (
<Form.Item name={[index, 'type']} noStyle> <Form.Item name={[index, 'type']} noStyle>
<Select <Select
placeholder={t('common.pleaseSelect')} placeholder={t('common.pleaseSelect')}
size="small" // size="small"
options={typeOptions} options={typeOptions}
popupMatchSelectWidth={false} popupMatchSelectWidth={false}
onChange={() => { onChange={() => {
form.setFieldValue([...Array.isArray(parentName) ? parentName : [parentName], index, 'value'], undefined); form.setFieldValue([...Array.isArray(parentName) ? parentName : [parentName], index, 'value'], undefined);
}} }}
size={size}
/> />
</Form.Item> </Form.Item>
)} )}
@@ -82,7 +88,7 @@ const EditableTable: React.FC<EditableTableProps> = ({
{ {
title: t('workflow.config.value'), title: t('workflow.config.value'),
dataIndex: 'value', dataIndex: 'value',
width: baseWidth, className: cellClassName,
render: (_: any, __: TableRow, index: number) => ( render: (_: any, __: TableRow, index: number) => (
<Form.Item <Form.Item
shouldUpdate={(prevValues, currentValues) => { shouldUpdate={(prevValues, currentValues) => {
@@ -102,10 +108,12 @@ const EditableTable: React.FC<EditableTableProps> = ({
<Form.Item name={[index, 'value']} noStyle> <Form.Item name={[index, 'value']} noStyle>
<VariableSelect <VariableSelect
placeholder={t('common.pleaseSelect')} placeholder={t('common.pleaseSelect')}
size="small" // size="small"
options={filteredOptions} options={filteredOptions}
filterBooleanType={filterBooleanType} filterBooleanType={filterBooleanType}
popupMatchSelectWidth={false} popupMatchSelectWidth={false}
className={contentClassName}
size={size}
/> />
</Form.Item> </Form.Item>
); );
@@ -116,9 +124,12 @@ const EditableTable: React.FC<EditableTableProps> = ({
{ {
title: '', title: '',
dataIndex: 'actions', dataIndex: 'actions',
width: '10%', className: cellClassName,
render: (_: any, __: TableRow, index: number) => ( 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>
) )
} }
]; ];
@@ -130,12 +141,10 @@ const EditableTable: React.FC<EditableTableProps> = ({
{(fields, { add, remove }) => { {(fields, { add, remove }) => {
const AddButton = ({ block = false }: { block?: boolean }) => ( const AddButton = ({ block = false }: { block?: boolean }) => (
<Button <Button
type={block ? "dashed" : "text"}
icon={block ? undefined : <PlusOutlined />} icon={block ? undefined : <PlusOutlined />}
onClick={() => add(createNewRow())} onClick={() => add(createNewRow())}
size="small" size="small"
block={block} className={block ? "rb:mt-1 rb:text-[12px]! rb:bg-transparent!" : "rb:text-[12px]!"}
className={block ? "rb:mt-1" : ""}
> >
{block && `+${t('common.add')}`} {block && `+${t('common.add')}`}
</Button> </Button>
@@ -145,8 +154,8 @@ const EditableTable: React.FC<EditableTableProps> = ({
<> <>
{title && ( {title && (
<div className="rb:flex rb:items-center rb:mb-2 rb:justify-between"> <div className="rb:flex rb:items-center rb:mb-2 rb:justify-between">
<div className="rb:font-medium">{title}</div> <div className="rb:font-medium rb:text-[12px] rb:leading-4.5">{title}</div>
<AddButton /> <AddButton block={true} />
</div> </div>
)} )}
@@ -161,8 +170,9 @@ const EditableTable: React.FC<EditableTableProps> = ({
columns={getColumns(remove)} columns={getColumns(remove)}
pagination={false} pagination={false}
size="small" size="small"
rowClassName="rb:p-0! rb:bg-[#F6F8FC]!"
locale={{ emptyText: <Empty size={88} /> }} locale={{ emptyText: <Empty size={88} /> }}
scroll={{ x: 'max-content' }} style={{ width: '274px' }}
/> />
{!title && <AddButton block />} {!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 { useTranslation } from 'react-i18next'
import { Form, Row, Col, Select, Button, Divider, InputNumber, Switch, Input } from 'antd' 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 Editor from '../../Editor'
import type { Suggestion } from '../../Editor/plugin/AutocompletePlugin' import type { Suggestion } from '../../Editor/plugin/AutocompletePlugin'
import AuthConfigModal from './AuthConfigModal' 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 ( return (
<> <>
<div className="rb:flex rb:items-center rb:justify-between rb:mb-4"> <div className="rb:flex rb:items-center rb:justify-between rb:mb-1">
<div>API</div> <div className="rb:font-medium rb:text-[12px] rb:leading-4.5">API</div>
<Button onClick={handleChangeAuth}>{t('workflow.config.http-request.auth')}</Button> <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> </div>
<Row gutter={16}> <Row gutter={4}>
<Col span={8}> <Col span={8}>
<Form.Item name="method"> <Form.Item name="method">
<Select <Select
@@ -85,35 +94,43 @@ const HttpRequest: FC<{ options: Suggestion[]; selectedNode?: any; graphRef?: an
{ label: 'PUT', value: 'PUT' }, { label: 'PUT', value: 'PUT' },
{ label: 'DELETE', value: 'DELETE' }, { label: 'DELETE', value: 'DELETE' },
]} ]}
className="rb:bg-transparent!"
/> />
</Form.Item> </Form.Item>
</Col> </Col>
<Col span={16}> <Col span={16}>
<Form.Item name="url"> <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> </Form.Item>
</Col> </Col>
</Row> </Row>
<Form.Item name="auth" hidden> <Form.Item name="auth" hidden>
</Form.Item> </Form.Item>
<Form.Item name="headers"> <Form.Item name="headers" noStyle>
<EditableTable <EditableTable
size="small"
parentName="headers" parentName="headers"
title="HEADERS" title="HEADERS"
options={options.filter(vo => vo.dataType === 'string' || vo.dataType === 'number')} options={options.filter(vo => vo.dataType === 'string' || vo.dataType === 'number')}
/> />
</Form.Item> </Form.Item>
<Form.Item name="params"> <Form.Item name="params" noStyle>
<EditableTable <EditableTable
size="small"
parentName="params" parentName="params"
title="PARAMS" title="PARAMS"
options={options.filter(vo => vo.dataType === 'string' || vo.dataType === 'number')} options={options.filter(vo => vo.dataType === 'string' || vo.dataType === 'number')}
/> />
</Form.Item> </Form.Item>
<Form.Item label="BODY"> <Form.Item label="BODY" className="rb:mb-0!">
<Form.Item name={['body', 'content_type']}> <Form.Item name={['body', 'content_type']}>
<Select <Select
placeholder={t('common.pleaseSelect')} placeholder={t('common.pleaseSelect')}
@@ -131,6 +148,7 @@ const HttpRequest: FC<{ options: Suggestion[]; selectedNode?: any; graphRef?: an
{values?.body?.content_type === 'form-data' && {values?.body?.content_type === 'form-data' &&
<Form.Item name={['body', 'data']} noStyle> <Form.Item name={['body', 'data']} noStyle>
<EditableTable <EditableTable
size="small"
parentName={['body', 'data']} parentName={['body', 'data']}
options={options.filter(vo => vo.dataType === 'string' || vo.dataType === 'number')} options={options.filter(vo => vo.dataType === 'string' || vo.dataType === 'number')}
typeOptions={[ typeOptions={[
@@ -143,6 +161,7 @@ const HttpRequest: FC<{ options: Suggestion[]; selectedNode?: any; graphRef?: an
{values?.body?.content_type === 'x-www-form-urlencoded' && {values?.body?.content_type === 'x-www-form-urlencoded' &&
<Form.Item name={['body', 'data']} noStyle> <Form.Item name={['body', 'data']} noStyle>
<EditableTable <EditableTable
size="small"
parentName={['body', 'data']} parentName={['body', 'data']}
options={options.filter(vo => vo.dataType === 'string' || vo.dataType === 'number')} options={options.filter(vo => vo.dataType === 'string' || vo.dataType === 'number')}
filterBooleanType={true} filterBooleanType={true}
@@ -150,7 +169,7 @@ const HttpRequest: FC<{ options: Suggestion[]; selectedNode?: any; graphRef?: an
</Form.Item> </Form.Item>
} }
{values?.body?.content_type === 'json' && {values?.body?.content_type === 'json' &&
<Form.Item name={['body', 'data']}> <Form.Item name={['body', 'data']} noStyle>
<MessageEditor <MessageEditor
key="json" key="json"
parentName={['body', 'data']} parentName={['body', 'data']}
@@ -161,7 +180,7 @@ const HttpRequest: FC<{ options: Suggestion[]; selectedNode?: any; graphRef?: an
</Form.Item> </Form.Item>
} }
{values?.body?.content_type === 'raw' && {values?.body?.content_type === 'raw' &&
<Form.Item name={['body', 'data']}> <Form.Item name={['body', 'data']} noStyle>
<MessageEditor <MessageEditor
key="raw" key="raw"
parentName={['body', 'data']} parentName={['body', 'data']}
@@ -172,7 +191,7 @@ const HttpRequest: FC<{ options: Suggestion[]; selectedNode?: any; graphRef?: an
</Form.Item> </Form.Item>
} }
{values?.body?.content_type === 'binary' && {values?.body?.content_type === 'binary' &&
<Form.Item name={['body', 'data']}> <Form.Item name={['body', 'data']} noStyle>
<VariableSelect <VariableSelect
placeholder={t('common.pleaseSelect')} placeholder={t('common.pleaseSelect')}
options={options.filter(vo => vo.dataType.includes('file'))} options={options.filter(vo => vo.dataType.includes('file'))}
@@ -182,15 +201,19 @@ const HttpRequest: FC<{ options: Suggestion[]; selectedNode?: any; graphRef?: an
} }
</Form.Item> </Form.Item>
<Divider /> <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 /> <Switch />
</Form.Item> </Form.Item>
<Divider /> <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 <Form.Item
name={['timeouts', 'connect_timeout']} name={['timeouts', 'connect_timeout']}
label={t('workflow.config.http-request.connect_timeout')} label={t('workflow.config.http-request.connect_timeout')}
hidden={collapsed}
> >
<InputNumber <InputNumber
placeholder={t('common.pleaseEnter')} placeholder={t('common.pleaseEnter')}
@@ -201,6 +224,7 @@ const HttpRequest: FC<{ options: Suggestion[]; selectedNode?: any; graphRef?: an
<Form.Item <Form.Item
name={['timeouts', 'read_timeout']} name={['timeouts', 'read_timeout']}
label={t('workflow.config.http-request.read_timeout')} label={t('workflow.config.http-request.read_timeout')}
hidden={collapsed}
> >
<InputNumber <InputNumber
placeholder={t('common.pleaseEnter')} placeholder={t('common.pleaseEnter')}
@@ -211,6 +235,7 @@ const HttpRequest: FC<{ options: Suggestion[]; selectedNode?: any; graphRef?: an
<Form.Item <Form.Item
name={['timeouts', 'write_timeout']} name={['timeouts', 'write_timeout']}
label={t('workflow.config.http-request.write_timeout')} label={t('workflow.config.http-request.write_timeout')}
hidden={collapsed}
> >
<InputNumber <InputNumber
placeholder={t('common.pleaseEnter')} placeholder={t('common.pleaseEnter')}

View File

@@ -1,6 +1,6 @@
import { type FC, useRef, useState, useEffect } from 'react' import { type FC, useRef, useState, useEffect } from 'react'
import { useTranslation } from 'react-i18next' 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 knowledgeEmpty from '@/assets/images/application/knowledgeEmpty.svg'
import type { import type {
KnowledgeConfigForm, KnowledgeConfigForm,
@@ -113,49 +113,65 @@ const Knowledge: FC<{value?: KnowledgeConfig; onChange?: (config: KnowledgeConfi
} }
return ( return (
<div> <div>
<div className="rb:flex rb:justify-between rb:items-center"> <div className="rb:flex rb:items-center rb:justify-between rb:mb-2">
<div>{t('application.knowledgeBaseAssociation')}</div> <div className="rb:text-[12px] rb:font-medium rb:leading-4.5">
{t('application.knowledgeBaseAssociation')}
</div>
<Space> <Button
<Button style={{ padding: '0 8px', height: '24px' }} onClick={handleKnowledgeConfig}>{t('workflow.config.knowledge-retrieval.recallConfig')}</Button> onClick={handleKnowledgeConfig}
<Button style={{ padding: '0 8px', height: '24px' }} onClick={handleAddKnowledge}>+</Button> className="rb:py-0! rb:px-1! rb:text-[12px]! rb:group rb:gap-0.5!"
</Space> 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> </div>
{knowledgeList.length === 0 <Space size={10} direction="vertical" className="rb:w-full!">
? <Empty url={knowledgeEmpty} size={88} subTitle={t('application.knowledgeEmpty')} /> <Button
: type="dashed"
<List block
grid={{ gutter: 12, column: 1 }} size="middle"
dataSource={knowledgeList} className="rb:text-[12px]!"
renderItem={(item) => { onClick={handleAddKnowledge}
if (!item.id) return null >
return ( + {t('workflow.config.knowledge-retrieval.addKnowledge')}
<List.Item> </Button>
<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"> {knowledgeList.length === 0
{item.name} ? <Empty url={knowledgeEmpty} size={88} subTitle={t('application.knowledgeEmpty')} />
<Tag color={item.status === 1 ? 'success' : item.status === 0 ? 'default' : 'error'} className="rb:ml-2"> : knowledgeList.map(item => {
{item.status === 1 ? t('common.enable') : item.status === 0 ? t('common.disabled') : t('common.deleted')} if (!item.id) return null
</Tag> return (
<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 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> <div className="">
<Space size={12}> <span className="rb:font-medium rb:leading-4">{item.name}</span>
<div <Tag
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')]" color={item.status === 1 ? 'success' : item.status === 0 ? 'default' : 'error'}
onClick={() => handleEditKnowledge(item)} className="rb:ml-1 rb:py-0! rb:px-1! rb:text-[12px] rb:leading-3.5!"
></div> >
<div {item.status === 1 ? t('common.enable') : item.status === 0 ? t('common.disabled') : t('common.deleted')}
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')]" </Tag>
onClick={() => handleDeleteKnowledge(item.id)} <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> </div>
</Space> <Space size={12}>
</div> <div
</List.Item> 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 <KnowledgeGlobalConfigModal
data={editConfig} data={editConfig}

View File

@@ -83,6 +83,7 @@ const KnowledgeConfigModal = forwardRef<KnowledgeConfigModalRef, KnowledgeConfig
<Form <Form
form={form} form={form}
layout="vertical" layout="vertical"
size="middle"
> >
{data && ( {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]"> <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={form} form={form}
layout="vertical" layout="vertical"
size="middle"
> >
<div className="rb:text-[#5B6167] rb:mb-6">{t('application.globalConfigDesc')}</div> <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')} placeholder={t('knowledgeBase.searchPlaceholder')}
onSearch={handleSearch} onSearch={handleSearch}
style={{ width: '100%' }} style={{ width: '100%' }}
size="middle"
/> />
{filterList.length === 0 {filterList.length === 0
? <Empty /> ? <Empty />

View File

@@ -1,7 +1,6 @@
import React from 'react'; import React from 'react';
import { useTranslation } from 'react-i18next' import { useTranslation } from 'react-i18next'
import { MinusCircleOutlined } from '@ant-design/icons'; import { Button, Form, Input, Divider } from 'antd';
import { Button, Form, Input, Space, Row, Col } from 'antd';
import type { Suggestion } from '../../Editor/plugin/AutocompletePlugin' import type { Suggestion } from '../../Editor/plugin/AutocompletePlugin'
import VariableSelect from '../VariableSelect' import VariableSelect from '../VariableSelect'
@@ -16,43 +15,55 @@ const MappingList: React.FC<MappingListProps> = ({ name, options }) => {
<Form.List name={name}> <Form.List name={name}>
{(fields, { add, remove }) => ( {(fields, { add, remove }) => (
<> <>
{fields.map(({ key, name, ...restField }) => ( <div className="rb:flex rb:items-center rb:justify-between rb:mb-2">
<Row key={key} gutter={12} className="rb:mb-2"> <div className="rb:text-[12px] rb:font-medium rb:leading-4.5">
<Col span={10}> {t('workflow.config.jinja-render.mapping')}
<Form.Item </div>
{...restField}
name={[name, 'name']} <Button
noStyle onClick={() => add()}
> className="rb:py-0! rb:px-1! rb:text-[12px]!"
<Input placeholder={t('common.pleaseEnter')} data-field-type="mapping-name" /> size="small"
</Form.Item> >
</Col> + {t('workflow.config.addVariable')}
<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')}
</Button> </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> </Form.List>
<Divider />
</> </>
) )
}; };

View File

@@ -30,7 +30,7 @@ const MemoryConfig: FC<{ options: Suggestion[]; parentName: string; }> = ({
return ( return (
<> <>
{values?.memory?.enable && <> {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')} {t('workflow.config.llm.memory')}
<span>{t('workflow.config.llm.inner')}</span> <span>{t('workflow.config.llm.inner')}</span>
</div> </div>
@@ -40,6 +40,7 @@ const MemoryConfig: FC<{ options: Suggestion[]; parentName: string; }> = ({
isArray={false} isArray={false}
parentName={[parentName, 'messages']} parentName={[parentName, 'messages']}
options={options} options={options}
size="small"
/> />
</Form.Item> </Form.Item>

View File

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

View File

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

View File

@@ -1,7 +1,7 @@
import { type FC, useRef } from 'react' import { type FC, useRef } from 'react'
import { useTranslation } from 'react-i18next' import { useTranslation } from 'react-i18next'
import { Button, Space, List } from 'antd' import { Button, Space } from 'antd'
import Empty from '@/components/Empty'
import type { ParamItem, ParamEditModalRef } from './types' import type { ParamItem, ParamEditModalRef } from './types'
import ParamEditModal from './ParamEditModal' import ParamEditModal from './ParamEditModal'
@@ -41,47 +41,37 @@ const ParamsList: FC<ParamsListProps> = ({
} }
return ( return (
<div> <div>
<div className="rb:flex rb:justify-between rb:items-center"> <div className="rb:leading-4.25 rb:text-[12px] rb:font-medium rb:mb-2">
<div>{label}</div> {label}
<Space>
<Button style={{ padding: '0 8px', height: '24px' }} onClick={handleAdd}>+</Button>
</Space>
</div> </div>
{value?.length === 0 <Space size={10} direction="vertical" className="rb:w-full!">
? <Empty size={88} /> <Button type="dashed" block size="middle" className="rb:text-[12px]!" onClick={handleAdd}>+ {t('workflow.config.parameter-extractor.addParams')}</Button>
:
<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>
</div> {value?.map((item, index) => (
<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> <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"> key={index}
<div 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"
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> <span className="rb:font-medium">{item.name}</span>
<div <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>
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')]" <div className="rb:text-[12px] rb:text-[#5B6167] rb:font-regular rb:leading-4.25 rb:mt-0.5">{item.desc}</div>
onClick={() => handleDelete(index)} </div>
></div>
</Space> <Space size={8}>
</div> <div
</List.Item> 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 <ParamEditModal
ref={paramEditModalRef} 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')} label={t('workflow.config.tool.tool_id')}
> >
<Cascader <Cascader
placeholder={t('common.pleaseSelect')}
options={optionList} options={optionList}
loadData={loadData} loadData={loadData}
onChange={handleChange} onChange={handleChange}
@@ -187,28 +188,30 @@ const ToolConfig: FC<{ options: Suggestion[]; }> = ({
label={parameter.name} label={parameter.name}
extra={parameter.type === 'boolean' ? undefined : parameter.description} extra={parameter.type === 'boolean' ? undefined : parameter.description}
rules={[ rules={[
{ required: parameter.required, message: t('workflow.config.tool.required') } { required: parameter.required, message: t('common.pleaseEnter') }
]} ]}
layout={parameter.type === 'boolean' ? 'horizontal' : 'vertical'} layout={parameter.type === 'boolean' ? 'horizontal' : 'vertical'}
className={parameter.type === 'boolean' ? 'rb:mb-0!' : ''} className={parameter.type === 'boolean' ? 'rb:mb-0!' : ''}
> >
{parameter.type === 'string' && parameter.enum && parameter.enum.length > 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' : parameter.type === 'boolean'
? <Switch /> ? <Switch size="small" />
: parameter.type === 'integer' || parameter.type === 'number' : parameter.type === 'integer' || parameter.type === 'number'
? <InputNumber ? <InputNumber
min={parameter.minimum} min={parameter.minimum}
max={parameter.maximum} max={parameter.maximum}
step={parameter.type === 'integer' ? 1 : 0.01} step={parameter.type === 'integer' ? 1 : 0.01}
placeholder={t('common.pleaseEnter')} placeholder={t('common.pleaseEnter')}
className="rb:w-full!" className="rb:w-full!"
size="small"
onChange={(value) => form.setFieldValue(['tool_parameters', parameter.name], value)} onChange={(value) => form.setFieldValue(['tool_parameters', parameter.name], value)}
/> />
: <Editor : <Editor
height={32} height={32}
variant="outlined" variant="outlined"
options={options} options={options}
size="small"
placeholder={t('common.pleaseEnter')} 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 { Form, Input, Select, InputNumber, Checkbox, Tag } from 'antd';
import { useTranslation } from 'react-i18next'; import { useTranslation } from 'react-i18next';
import type { StartVariableItem, VariableEditModalRef } from '../../types' import type { Variable, VariableEditModalRef } from './types'
import RbModal from '@/components/RbModal' import RbModal from '@/components/RbModal'
import SortableList from '@/components/SortableList' import SortableList from '@/components/SortableList'
const FormItem = Form.Item; const FormItem = Form.Item;
interface VariableEditModalProps { interface VariableEditModalProps {
refresh: (values: StartVariableItem) => void; refresh: (values: Variable) => void;
} }
const types = [ const types = [
@@ -36,9 +36,9 @@ const VariableEditModal = forwardRef<VariableEditModalRef, VariableEditModalProp
}, ref) => { }, ref) => {
const { t } = useTranslation(); const { t } = useTranslation();
const [visible, setVisible] = useState(false); const [visible, setVisible] = useState(false);
const [form] = Form.useForm<StartVariableItem>(); const [form] = Form.useForm<Variable>();
const [loading, setLoading] = useState(false) const [loading, setLoading] = useState(false)
const [editVo, setEditVo] = useState<StartVariableItem | null>(null) const [editVo, setEditVo] = useState<Variable | null>(null)
const values = Form.useWatch([], form); const values = Form.useWatch([], form);
@@ -50,7 +50,7 @@ const VariableEditModal = forwardRef<VariableEditModalRef, VariableEditModalProp
setEditVo(null) setEditVo(null)
}; };
const handleOpen = (variable?: StartVariableItem) => { const handleOpen = (variable?: Variable) => {
setVisible(true); setVisible(true);
if (variable) { if (variable) {
setEditVo(variable || null) setEditVo(variable || null)
@@ -85,7 +85,7 @@ const VariableEditModal = forwardRef<VariableEditModalRef, VariableEditModalProp
return ( return (
<RbModal <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} open={visible}
onCancel={handleClose} onCancel={handleClose}
okText={t('common.save')} okText={t('common.save')}
@@ -96,6 +96,7 @@ const VariableEditModal = forwardRef<VariableEditModalRef, VariableEditModalProp
form={form} form={form}
layout="vertical" layout="vertical"
initialValues={initialValues} initialValues={initialValues}
size="middle"
scrollToFirstError={{ behavior: 'instant', block: 'end', focus: true }} 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; onChange?: (value: string) => void;
allowClear?: boolean; allowClear?: boolean;
filterBooleanType?: boolean; filterBooleanType?: boolean;
size?: 'small' | 'middle' | 'large'
} }
const VariableSelect: FC<VariableSelectProps> = ({ const VariableSelect: FC<VariableSelectProps> = ({
@@ -18,7 +19,7 @@ const VariableSelect: FC<VariableSelectProps> = ({
value, value,
allowClear = true, allowClear = true,
onChange, onChange,
size, size = 'middle',
filterBooleanType = false, filterBooleanType = false,
...resetPorps ...resetPorps
}) => { }) => {

View File

@@ -1,13 +1,13 @@
import { type FC, useEffect, useState, useRef, useMemo } from "react"; import { type FC, useEffect, useState, useRef, useMemo } from "react";
import clsx from 'clsx'
import { useTranslation } from 'react-i18next' import { useTranslation } from 'react-i18next'
import { Graph, Node } from '@antv/x6'; 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 Empty from '@/components/Empty';
import emptyIcon from '@/assets/images/workflow/empty.png' import emptyIcon from '@/assets/images/workflow/empty.png'
import CustomSelect from "@/components/CustomSelect"; import CustomSelect from "@/components/CustomSelect";
import VariableEditModal from './VariableEditModal';
import MessageEditor from './MessageEditor' import MessageEditor from './MessageEditor'
import Knowledge from './Knowledge/Knowledge'; import Knowledge from './Knowledge/Knowledge';
import type { Suggestion } from '../Editor/plugin/AutocompletePlugin' import type { Suggestion } from '../Editor/plugin/AutocompletePlugin'
@@ -23,7 +23,11 @@ import CycleVarsList from './CycleVarsList'
import AssignmentList from './AssignmentList' import AssignmentList from './AssignmentList'
import ToolConfig from './ToolConfig' import ToolConfig from './ToolConfig'
import MemoryConfig from './MemoryConfig' import MemoryConfig from './MemoryConfig'
import VariableList from './VariableList'
// import { calculateVariableList } from './utils/variableListCalculator' // import { calculateVariableList } from './utils/variableListCalculator'
import styles from './properties.module.css'
import Editor from "../Editor";
import RbSlider from './RbSlider'
interface PropertiesProps { interface PropertiesProps {
selectedNode?: Node | null; selectedNode?: Node | null;
@@ -42,12 +46,9 @@ const Properties: FC<PropertiesProps> = ({
chatVariables chatVariables
}) => { }) => {
const { t } = useTranslation() const { t } = useTranslation()
const { modal } = App.useApp()
const [form] = Form.useForm<NodeConfig>(); const [form] = Form.useForm<NodeConfig>();
const [configs, setConfigs] = useState<Record<string,NodeConfig>>({} as Record<string,NodeConfig>) const [configs, setConfigs] = useState<Record<string,NodeConfig>>({} as Record<string,NodeConfig>)
const values = Form.useWatch([], form); const values = Form.useWatch([], form);
const variableModalRef = useRef<VariableEditModalRef>(null)
const [editIndex, setEditIndex] = useState<number | null>(null)
const [graphUpdateTrigger, setGraphUpdateTrigger] = useState(0) const [graphUpdateTrigger, setGraphUpdateTrigger] = useState(0)
const prevMappingNamesRef = useRef<string[]>([]) const prevMappingNamesRef = useRef<string[]>([])
const prevTemplateVarsRef = useRef<string[]>([]) const prevTemplateVarsRef = useRef<string[]>([])
@@ -243,49 +244,6 @@ const Properties: FC<PropertiesProps> = ({
} }
}, [values, selectedNode, form]) }, [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(() => { const variableList = useMemo(() => {
if (!selectedNode || !graphRef?.current) return []; if (!selectedNode || !graphRef?.current) return [];
@@ -586,7 +544,7 @@ const Properties: FC<PropertiesProps> = ({
break break
case 'question-classifier': case 'question-classifier':
const classNameKey = `${dataNodeId}_class_name`; const classNameKey = `${dataNodeId}_class_name`;
const outputKey = `${dataNodeId}_output`; // const outputKey = `${dataNodeId}_output`;
if (!addedKeys.has(classNameKey)) { if (!addedKeys.has(classNameKey)) {
addedKeys.add(classNameKey); addedKeys.add(classNameKey);
variableList.push({ variableList.push({
@@ -1039,11 +997,11 @@ const Properties: FC<PropertiesProps> = ({
console.log('variableList', variableList) console.log('variableList', variableList)
return ( return (
<div className="rb:w-75 rb:fixed rb:right-0 rb:top-16 rb:bottom-0 rb:p-3"> <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:mb-3">{t('workflow.nodeProperties')}</div> <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 {!selectedNode
? <Empty url={emptyIcon} size={140} className="rb:h-full rb:mx-15" title={t('workflow.empty')} /> ? <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')}> <Form.Item name="name" label={t('workflow.nodeName')}>
<Input <Input
placeholder={t('common.pleaseEnter')} placeholder={t('common.pleaseEnter')}
@@ -1073,46 +1031,13 @@ const Properties: FC<PropertiesProps> = ({
if (selectedNode?.data?.type === 'start' && key === 'variables' && config.type === 'define') { if (selectedNode?.data?.type === 'start' && key === 'variables' && config.type === 'define') {
return ( return (
<div key={key}> <Form.Item key={key} name={key}>
<div className="rb:flex rb:items-center rb:justify-between rb:mb-2.75"> <VariableList
<div className="rb:leading-5"> parentName={key}
{t(`workflow.config.${selectedNode?.data?.type}.${key}`)} selectedNode={selectedNode}
</div> config={config}
<Button style={{padding: '0 8px', height: '24px'}} onClick={handleAddVariable}>+{t('application.addVariables')}</Button> />
</div> </Form.Item>
<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>
) )
} }
@@ -1143,23 +1068,12 @@ const Properties: FC<PropertiesProps> = ({
key={key} key={key}
options={contextVariableList.filter(variable => variable.nodeData?.type !== 'knowledge-retrieval')} options={contextVariableList.filter(variable => variable.nodeData?.type !== 'knowledge-retrieval')}
parentName={key} parentName={key}
placeholder={t(config.placeholder || 'common.pleaseSelect')}
size="small"
/> />
</Form.Item> </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') { if (config.type === 'define') {
return null return null
} }
@@ -1184,6 +1098,8 @@ const Properties: FC<PropertiesProps> = ({
parentName={key} parentName={key}
enableJinja2={config.enableJinja2 as boolean} enableJinja2={config.enableJinja2 as boolean}
options={getFilteredVariableList(selectedNode?.data?.type, key)} options={getFilteredVariableList(selectedNode?.data?.type, key)}
titleVariant={config.titleVariant}
size="small"
/> />
</Form.Item> </Form.Item>
) )
@@ -1206,9 +1122,9 @@ const Properties: FC<PropertiesProps> = ({
name={key} name={key}
options={getFilteredVariableList(selectedNode?.data?.type, key)} options={getFilteredVariableList(selectedNode?.data?.type, key)}
isCanAdd={!!(values as any)?.group} isCanAdd={!!(values as any)?.group}
size="small"
/> />
</Form.Item> </Form.Item>
) )
} }
if (config.type === 'caseList') { if (config.type === 'caseList') {
@@ -1226,9 +1142,7 @@ const Properties: FC<PropertiesProps> = ({
if (config.type === 'mappingList') { if (config.type === 'mappingList') {
return ( return (
<Form.Item key={key} name={key} <Form.Item key={key} name={key} noStyle>
label={t(`workflow.config.${selectedNode?.data?.type}.${key}`)}
>
<MappingList name={key} options={getFilteredVariableList(selectedNode?.data?.type, key)} /> <MappingList name={key} options={getFilteredVariableList(selectedNode?.data?.type, key)} />
</Form.Item> </Form.Item>
@@ -1238,6 +1152,7 @@ const Properties: FC<PropertiesProps> = ({
return ( return (
<Form.Item key={key} name={key}> <Form.Item key={key} name={key}>
<CycleVarsList <CycleVarsList
size="small"
parentName={key} parentName={key}
options={getFilteredVariableList(selectedNode?.data?.type, key)} options={getFilteredVariableList(selectedNode?.data?.type, key)}
/> />
@@ -1276,87 +1191,14 @@ const Properties: FC<PropertiesProps> = ({
</Form.Item> </Form.Item>
) )
} }
if (config.type === 'conditionList') {
return ( return (
<Form.Item <Form.Item
key={key} key={key}
name={key} name={key}
label={t(`workflow.config.${selectedNode?.data?.type}.${key}`)} noStyle
layout={config.type === 'switch' ? 'horizontal' : 'vertical'} >
> <ConditionList
{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
parentName={key} parentName={key}
options={(() => { options={(() => {
const cycleVars = values?.cycle_vars || []; const cycleVars = values?.cycle_vars || [];
@@ -1375,6 +1217,93 @@ const Properties: FC<PropertiesProps> = ({
graphRef={graphRef} graphRef={graphRef}
addBtnText={t('workflow.config.addCase')} 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 : null
} }
</Form.Item> </Form.Item>
@@ -1383,11 +1312,6 @@ const Properties: FC<PropertiesProps> = ({
} }
</Form> </Form>
} }
<VariableEditModal
ref={variableModalRef}
refresh={handleRefreshVariable}
/>
</div> </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, type: "end", icon: endIcon,
config: { config: {
output: { output: {
type: 'define' type: 'editor'
} }
} }
}, },
@@ -125,6 +125,7 @@ export const nodeLibrary: NodeLibrary[] = [
}, },
context: { context: {
type: 'variableList', type: 'variableList',
placeholder: 'workflow.config.llm.contextPlaceholder'
}, },
messages: { messages: {
type: 'define', type: 'define',
@@ -134,7 +135,8 @@ export const nodeLibrary: NodeLibrary[] = [
content: undefined, content: undefined,
readonly: true readonly: true
}, },
] ],
placeholder: 'workflow.config.llm.messagesPlaceholder'
}, },
memory: { memory: {
type: 'memoryConfig', type: 'memoryConfig',
@@ -170,7 +172,8 @@ export const nodeLibrary: NodeLibrary[] = [
}, },
text: { text: {
type: 'variableList', type: 'variableList',
filterLoopIterationVars: true filterLoopIterationVars: true,
placeholder: 'workflow.config.parameter-extractor.textPlaceholder'
}, },
params: { params: {
type: 'paramList', type: 'paramList',
@@ -178,6 +181,8 @@ export const nodeLibrary: NodeLibrary[] = [
prompt: { prompt: {
type: 'messageEditor', type: 'messageEditor',
isArray: false, isArray: false,
titleVariant: 'borderless',
placeholder: 'workflow.config.parameter-extractor.promptPlaceholder'
}, },
} }
} }
@@ -189,7 +194,7 @@ export const nodeLibrary: NodeLibrary[] = [
{ type: "memory-read", icon: memoryReadIcon, { type: "memory-read", icon: memoryReadIcon,
config: { config: {
message: { message: {
type: 'messageEditor', type: 'editor',
isArray: false isArray: false
}, },
config_id: { config_id: {
@@ -212,7 +217,7 @@ export const nodeLibrary: NodeLibrary[] = [
{ type: "memory-write", icon: memoryWriteIcon, { type: "memory-write", icon: memoryWriteIcon,
config: { config: {
message: { message: {
type: 'messageEditor', type: 'editor',
isArray: false isArray: false
}, },
config_id: { config_id: {
@@ -270,7 +275,8 @@ export const nodeLibrary: NodeLibrary[] = [
}, },
user_supplement_prompt: { user_supplement_prompt: {
type: 'messageEditor', type: 'messageEditor',
isArray: false isArray: false,
titleVariant: 'borderless'
} }
} }
}, },
@@ -436,6 +442,7 @@ export const nodeLibrary: NodeLibrary[] = [
type: 'messageEditor', type: 'messageEditor',
isArray: false, isArray: false,
enableJinja2: true, enableJinja2: true,
titleVariant: 'borderless',
defaultValue: "{{arg1}}" defaultValue: "{{arg1}}"
}, },
} }

View File

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