diff --git a/web/src/assets/images/workflow/memory-read.png b/web/src/assets/images/workflow/memory-read.png new file mode 100644 index 00000000..4b0cdc1d Binary files /dev/null and b/web/src/assets/images/workflow/memory-read.png differ diff --git a/web/src/assets/images/workflow/memory-write.png b/web/src/assets/images/workflow/memory-write.png new file mode 100644 index 00000000..83a50fd4 Binary files /dev/null and b/web/src/assets/images/workflow/memory-write.png differ diff --git a/web/src/i18n/en.ts b/web/src/i18n/en.ts index e679bcee..fc729d98 100644 --- a/web/src/i18n/en.ts +++ b/web/src/i18n/en.ts @@ -1798,12 +1798,20 @@ Memory Bear: After the rebellion, regional warlordism intensified for several re "not_contains": 'Does Not Contain', "startwith": 'Starts With', "endwith": 'Ends With', - "eq": '==', - "ne": '!=', - "lt": '<', - "le": '<=', - "gt": '>', - "ge": '>=', + "eq": 'Equals', + "ne": 'Not Equals', + num: { + "eq": '=', + "ne": '≠', + "lt": '<', + "le": '≤', + "gt": '>', + "ge": '≥', + }, + boolean: { + "eq": 'Is', + "ne": 'Is Not', + }, else_desc: 'Used to define the logic that should be executed when the if condition is not met.' }, 'http-request': { @@ -1844,12 +1852,17 @@ Memory Bear: After the rebellion, regional warlordism intensified for several re loop: { cycle_vars: 'Loop Variables', condition: 'Loop Termination Condition', + max_loop: 'Maximum Loop Count', }, assigner: { assignments: 'Variables', - cover: 'Overwrite', + cover: 'Override', assign: 'Set', - clear: 'Clear' + clear: 'Clear', + add: '+=', + subtract: '-=', + multiply: '*=', + divide: '/=', }, iteration: { input: 'Input Variable', diff --git a/web/src/i18n/zh.ts b/web/src/i18n/zh.ts index abb95a79..b6972c1f 100644 --- a/web/src/i18n/zh.ts +++ b/web/src/i18n/zh.ts @@ -1898,12 +1898,20 @@ export const zh = { "not_contains": '不包含', "startwith": '开始是', "endwith": '结束是', - "eq": '==', - "ne": '!=', - "lt": '<', - "le": '<=', - "gt": '>', - "ge": '>=', + "eq": '是', + "ne": '不是', + num: { + "eq": '=', + "ne": '≠', + "lt": '<', + "le": '≤', + "gt": '>', + "ge": '≥', + }, + boolean: { + "eq": '是', + "ne": '不是', + }, else_desc: '用于定义当 if 条件不满足时应执行的逻辑。' }, 'http-request': { @@ -1944,12 +1952,17 @@ export const zh = { loop: { cycle_vars: '循环变量', condition: '循环终止条件', + max_loop: '最大循环次数', }, assigner: { assignments: '变量', cover: '覆盖', assign: '设置', - clear: '清空' + clear: '清空', + add: '+=', + subtract: '-=', + multiply: '*=', + divide: '/=', }, iteration: { input: '输入变量', diff --git a/web/src/views/ToolManagement/Inner.tsx b/web/src/views/ToolManagement/Inner.tsx index d256d6c7..6f85e1f7 100644 --- a/web/src/views/ToolManagement/Inner.tsx +++ b/web/src/views/ToolManagement/Inner.tsx @@ -4,10 +4,9 @@ import { Col, Tag, List, - Space + Flex } from 'antd'; import { EyeOutlined } from '@ant-design/icons'; -import clsx from 'clsx' import { useTranslation } from 'react-i18next'; import dayjs, { type Dayjs } from 'dayjs' @@ -103,9 +102,9 @@ const Inner: React.FC<{ getStatusTag: (status: string) => ReactNode }> = ({ getS
{t(`tool.${item.config_data.tool_class}_features`)}
- + {InnerConfigData[item.config_data.tool_class].features.map(vo => { t(`tool.${vo}`) }) } - + {item.config_data.tool_class === 'DateTimeTool' ?
diff --git a/web/src/views/Workflow/components/AddChatVariable/ChatVariableModal.tsx b/web/src/views/Workflow/components/AddChatVariable/ChatVariableModal.tsx index 571f1e4e..fabe45ba 100644 --- a/web/src/views/Workflow/components/AddChatVariable/ChatVariableModal.tsx +++ b/web/src/views/Workflow/components/AddChatVariable/ChatVariableModal.tsx @@ -26,7 +26,6 @@ const ChatVariableModal = forwardRef(); const [loading, setLoading] = useState(false) const [editIndex, setEditIndex] = useState(undefined) - const typeValue = Form.useWatch('type', form); // 封装取消方法,添加关闭弹窗逻辑 const handleClose = () => { diff --git a/web/src/views/Workflow/components/Editor/plugin/CharacterCountPlugin.tsx b/web/src/views/Workflow/components/Editor/plugin/CharacterCountPlugin.tsx index 963f824b..ed07392d 100644 --- a/web/src/views/Workflow/components/Editor/plugin/CharacterCountPlugin.tsx +++ b/web/src/views/Workflow/components/Editor/plugin/CharacterCountPlugin.tsx @@ -14,18 +14,23 @@ const CharacterCountPlugin = ({ setCount, onChange }: { setCount: (count: number let serializedContent = ''; // Traverse all nodes and serialize properly + const paragraphs: string[] = []; root.getChildren().forEach(child => { if ($isParagraphNode(child)) { + let paragraphContent = ''; child.getChildren().forEach(node => { if ($isVariableNode(node)) { - serializedContent += node.getTextContent(); + paragraphContent += node.getTextContent(); } else { - serializedContent += node.getTextContent(); + paragraphContent += node.getTextContent(); } }); + paragraphs.push(paragraphContent); } }); + serializedContent = paragraphs.join('\n'); + setCount(serializedContent.length); onChange?.(serializedContent); }); diff --git a/web/src/views/Workflow/components/Editor/plugin/InitialValuePlugin.tsx b/web/src/views/Workflow/components/Editor/plugin/InitialValuePlugin.tsx index 4059b300..93197150 100644 --- a/web/src/views/Workflow/components/Editor/plugin/InitialValuePlugin.tsx +++ b/web/src/views/Workflow/components/Editor/plugin/InitialValuePlugin.tsx @@ -26,6 +26,7 @@ const InitialValuePlugin: React.FC = ({ value, options parts.forEach(part => { const match = part.match(/^\{\{([^.]+)\.([^}]+)\}\}$/); const contextMatch = part.match(/^\{\{context\}\}$/); + const conversationMatch = part.match(/^\{\{conv\.([^}]+)\}\}$/); // 匹配{{context}}格式 if (contextMatch) { @@ -38,6 +39,20 @@ const InitialValuePlugin: React.FC = ({ value, options return } + // 匹配{{conv.xx}}格式 + if (conversationMatch) { + const [_, variableName] = conversationMatch; + const conversationSuggestion = options.find(s => + s.group === 'CONVERSATION' && s.label === variableName + ); + if (conversationSuggestion) { + paragraph.append($createVariableNode(conversationSuggestion)); + } else { + paragraph.append($createTextNode(part)); + } + return + } + // 匹配普通变量{{nodeId.label}}格式 if (match) { const [_, nodeId, label] = match; diff --git a/web/src/views/Workflow/components/Nodes/AddNode.tsx b/web/src/views/Workflow/components/Nodes/AddNode.tsx index a2f6d930..973a503c 100644 --- a/web/src/views/Workflow/components/Nodes/AddNode.tsx +++ b/web/src/views/Workflow/components/Nodes/AddNode.tsx @@ -13,13 +13,15 @@ const AddNode: ReactShapeConfig['component'] = ({ node, graph }) => { const handleNodeSelect = (selectedNodeType: any) => { const parentBBox = node.getBBox(); const cycleId = data.cycle; - + + const id = `${selectedNodeType.type.replace(/-/g, '_') }_${Date.now()}_${Math.random().toString(36).substr(2, 9)}` const newNode = graph.addNode({ ...(graphNodeLibrary[selectedNodeType.type] || graphNodeLibrary.default), x: parentBBox.x, y: parentBBox.y, + id, data: { - id: `${selectedNodeType.type}_${Date.now()}_${Math.random().toString(36).substr(2, 9)}`, + id, type: selectedNodeType.type, icon: selectedNodeType.icon, name: t(`workflow.${selectedNodeType.type}`), diff --git a/web/src/views/Workflow/components/Nodes/LoopNode.tsx b/web/src/views/Workflow/components/Nodes/LoopNode.tsx index b0b8d4ce..37feb2dc 100644 --- a/web/src/views/Workflow/components/Nodes/LoopNode.tsx +++ b/web/src/views/Workflow/components/Nodes/LoopNode.tsx @@ -75,12 +75,15 @@ const LoopNode: ReactShapeConfig['component'] = ({ node, graph }) => { const parentBBox = node.getBBox(); const centerX = parentBBox.x + 24; // 默认节点宽度的一半 const centerY = parentBBox.y + 50; // 默认节点高度的一半 - + + const cycleStartNodeId = `cycle_start_${Date.now()}_${Math.random().toString(36).substr(2, 9)}` const cycleStartNode = graph.addNode({ ...graphNodeLibrary.cycleStart, x: centerX, y: centerY, + id: cycleStartNodeId, data: { + id: cycleStartNodeId, type: 'cycle-start', parentId: node.id, isDefault: true, // 标记为默认节点,不可删除 diff --git a/web/src/views/Workflow/components/PortClickHandler.tsx b/web/src/views/Workflow/components/PortClickHandler.tsx index 0be6fba1..9a644438 100644 --- a/web/src/views/Workflow/components/PortClickHandler.tsx +++ b/web/src/views/Workflow/components/PortClickHandler.tsx @@ -43,12 +43,14 @@ const PortClickHandler: React.FC = ({ graph }) => { const newY = sourceBBox.y; // 创建新节点 + const id = `${selectedNodeType.type.replace(/-/g, '_')}_${Date.now()}_${Math.random().toString(36).substr(2, 9)}` const newNode = graph.addNode({ ...(graphNodeLibrary[selectedNodeType.type] || graphNodeLibrary.default), x: newX, y: newY, + id, data: { - id: `${selectedNodeType.type}_${Date.now()}_${Math.random().toString(36).substr(2, 9)}`, + id, type: selectedNodeType.type, icon: selectedNodeType.icon, name: t(`workflow.${selectedNodeType.type}`), diff --git a/web/src/views/Workflow/components/Properties/AssignmentList/index.tsx b/web/src/views/Workflow/components/Properties/AssignmentList/index.tsx index 34c133c7..eac3775f 100644 --- a/web/src/views/Workflow/components/Properties/AssignmentList/index.tsx +++ b/web/src/views/Workflow/components/Properties/AssignmentList/index.tsx @@ -1,6 +1,6 @@ import { type FC } from 'react' import { useTranslation } from 'react-i18next'; -import { Form, Input, Button, Row, Col, Select } from 'antd' +import { Form, Input, Row, Col, Select, InputNumber, Radio } from 'antd' import { MinusCircleOutlined, PlusOutlined } from '@ant-design/icons'; import type { Suggestion } from '../../Editor/plugin/AutocompletePlugin' import VariableSelect from '../VariableSelect' @@ -11,6 +11,23 @@ interface AssignmentListProps { options: Suggestion[]; } +const operationsObj = { + number: [ + { value: 'cover', label: 'workflow.config.assigner.cover' }, + { value: 'clear', label: 'workflow.config.assigner.clear' }, + { value: 'assign', label: 'workflow.config.assigner.assign' }, + { value: 'add', label: 'workflow.config.assigner.add' }, + { value: 'subtract', label: 'workflow.config.assigner.subtract' }, + { value: 'multiply', label: 'workflow.config.assigner.multiply' }, + { value: 'divide', label: 'workflow.config.assigner.divide' }, + ], + default: [ + { value: 'cover', label: 'workflow.config.assigner.cover' }, + { value: 'clear', label: 'workflow.config.assigner.clear' }, + { value: 'assign', label: 'workflow.config.assigner.assign' }, + ], +} + const AssignmentList: FC = ({ parentName, options = [], @@ -27,6 +44,11 @@ const AssignmentList: FC = ({ add({ operation: 'cover'})} />
{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 (
@@ -50,11 +72,10 @@ const AssignmentList: FC = ({ noStyle > ({ - value: key, - label: t(`workflow.config.if-else.${key}`) + options={operatorList.map(vo => ({ + ...vo, + label: t(String(vo?.label || '')) }))} size="small" popupMatchSelectWidth={false} + placeholder={t('common.pleaseSelect')} /> @@ -280,11 +321,48 @@ const CaseList: FC = ({ - {!hideRightField && ( - - - - )} + {!hideRightField && <> + {leftFieldType === 'number' + ? + + + ({ - value: key, - label: t(`workflow.config.if-else.${key}`) + options={operatorList.map(vo => ({ + ...vo, + label: t(String(vo?.label || '')) }))} size="small" popupMatchSelectWidth={false} @@ -104,14 +139,53 @@ const ConditionList: FC = ({ onClick={() => remove(field.name)} /> - - {!hideRightField && ( - - - - - - )} + + {!hideRightField && <> + {leftFieldType === 'number' + ? + + + { - console.log('value record', value) - handleChange(record.key, 'type', value) - }} - /> - ), - }, - { - title: t('workflow.config.value'), + const columns = useMemo(() => { + const baseColumns = [ + { + title: typeOptions.length > 0 ? t('workflow.config.name') : '键', + dataIndex: 'name', + width: typeOptions.length > 0 ? '35%' : '45%', + render: (text: string, record: TableRow) => ( + handleChange(record.key, 'name', value || '')} + /> + ), + } + ]; + + if (typeOptions.length > 0) { + baseColumns.push({ + title: t('workflow.config.type'), + dataIndex: 'type', + width: '20%', + render: (text: string, record: TableRow) => ( + - - - - - remove(name)} /> - + + + + + + + + + + + + + remove(name)} /> + + ))}