Merge branch 'develop' of github.com:SuanmoSuanyangTechnology/MemoryBear into develop

This commit is contained in:
yujiangping
2026-01-14 12:07:28 +08:00
68 changed files with 1697 additions and 800 deletions

View File

@@ -107,7 +107,7 @@ const AddNode: ReactShapeConfig['component'] = ({ node, graph }) => {
<div style={{ maxHeight: '300px', overflowY: 'auto', minWidth: '240px' }}>
{nodeLibrary.map((category, categoryIndex) => {
const filteredNodes = category.nodes.filter(nodeType =>
nodeType.type !== 'start' && nodeType.type !== 'end' && nodeType.type !== 'loop' && nodeType.type !== 'cycle-start'
nodeType.type !== 'start' && nodeType.type !== 'end' && nodeType.type !== 'iteration' && nodeType.type !== 'loop' && nodeType.type !== 'cycle-start'
);
if (filteredNodes.length === 0) return null;

View File

@@ -33,7 +33,7 @@ const LoopNode: ReactShapeConfig['component'] = ({ node, graph }) => {
y: cycleStartBBox.y,
data: {
type: 'add-node',
label: '添加节点',
label: t('workflow.addNode'),
icon: '+',
parentId: node.id,
cycle: data.id,
@@ -61,7 +61,7 @@ const LoopNode: ReactShapeConfig['component'] = ({ node, graph }) => {
},
},
},
zIndex: 3
zIndex: 10
});
}
}
@@ -97,7 +97,7 @@ const LoopNode: ReactShapeConfig['component'] = ({ node, graph }) => {
y: centerY,
data: {
type: 'add-node',
label: '添加节点',
label: t('workflow.addNode'),
icon: '+',
parentId: node.id,
cycle: data.id,
@@ -128,7 +128,7 @@ const LoopNode: ReactShapeConfig['component'] = ({ node, graph }) => {
},
},
},
zIndex: 3
zIndex: 10
}
graph.addEdge(edgeConfig)

View File

@@ -151,11 +151,11 @@ const PortClickHandler: React.FC<PortClickHandlerProps> = ({ graph }) => {
let filteredNodes;
if (isChildOfLoop) {
// Use same filtering as AddNode for child nodes of loop
// Use same filtering as AddNode for child nodes of loop, but allow break
filteredNodes = category.nodes.filter(nodeType => !['start', 'end', 'loop', 'cycle-start', 'iteration'].includes(nodeType.type));
} else if (isChildOfIteration) {
// Filter out loop and iteration nodes for children of iteration nodes
filteredNodes = category.nodes.filter(nodeType => !['start', 'end', 'loop', 'break', 'cycle-start', 'iteration'].includes(nodeType.type));
// Filter out loop and iteration nodes for children of iteration nodes, but allow break
filteredNodes = category.nodes.filter(nodeType => !['start', 'end', 'loop', 'cycle-start', 'iteration'].includes(nodeType.type));
} else {
// Original filtering for non-loop child nodes
filteredNodes = category.nodes.filter(nodeType => !['start', 'end', 'break', 'cycle-start'].includes(nodeType.type));

View File

@@ -60,7 +60,7 @@ const AssignmentList: FC<AssignmentListProps> = ({
>
<VariableSelect
placeholder={t('common.pleaseSelect')}
options={options}
options={options.filter(vo => vo.nodeData.type === 'loop' || vo.value.includes('conv.'))}
popupMatchSelectWidth={false}
onChange={() => {
form.setFieldValue([parentName, name, 'operation'], undefined);

View File

@@ -1,17 +1,19 @@
import { type FC } from 'react';
import { useTranslation } from 'react-i18next';
import { Input, Button, Form, Space } from 'antd';
import { PlusOutlined, CopyOutlined, DeleteOutlined, ExpandOutlined } from '@ant-design/icons';
import { Button, Form, Space } from 'antd';
import { DeleteOutlined } from '@ant-design/icons';
import { Graph, Node } from '@antv/x6';
import type { PortMetadata } from '@antv/x6/lib/model/port';
import Editor from '../../Editor';
import type { Suggestion } from '../../Editor/plugin/AutocompletePlugin'
interface CategoryListProps {
parentName: string;
options: Suggestion[];
selectedNode?: Node | null;
graphRef?: React.MutableRefObject<Graph | undefined>;
}
const CategoryList: FC<CategoryListProps> = ({ parentName, selectedNode, graphRef }) => {
const CategoryList: FC<CategoryListProps> = ({ parentName, selectedNode, graphRef, options }) => {
const { t } = useTranslation();
const form = Form.useFormInstance();
const formValues = Form.useWatch([parentName], form);
@@ -167,9 +169,9 @@ const CategoryList: FC<CategoryListProps> = ({ parentName, selectedNode, graphRe
name={[name, 'class_name']}
noStyle
>
<Input.TextArea
<Editor
placeholder={t('common.pleaseEnter')}
rows={2}
options={options}
/>
</Form.Item>
</div>

View File

@@ -1,6 +1,6 @@
import { type FC } from 'react'
import { useTranslation } from 'react-i18next';
import { Form, Button, Select, Row, Col, InputNumber, Radio, type SelectProps } from 'antd'
import { Form, Button, Select, Row, Col, InputNumber, Radio, Input, type SelectProps } from 'antd'
import { DeleteOutlined } from '@ant-design/icons';
import type { Suggestion } from '../../Editor/plugin/AutocompletePlugin'
@@ -114,7 +114,7 @@ const ConditionList: FC<CaseListProps> = ({
<Col span={14}>
<Form.Item name={[field.name, 'left']} noStyle>
<VariableSelect
options={options}
options={options.filter(vo => vo.value.includes('sys.') || vo.value.includes('conv.') || vo.nodeData.type === 'loop')}
size="small"
allowClear={false}
popupMatchSelectWidth={false}
@@ -186,7 +186,7 @@ const ConditionList: FC<CaseListProps> = ({
<Radio.Button value={true}>True</Radio.Button>
<Radio.Button value={false}>False</Radio.Button>
</Radio.Group>
: <Editor options={options} />
: <Input placeholder={t('common.pleaseEnter')} />
}
</Form.Item>
</Col>

View File

@@ -1,6 +1,6 @@
import { type FC } from 'react'
import { useTranslation } from 'react-i18next';
import { Form, Button, Select, Row, Col, Input } from 'antd'
import { Form, Select, Row, Col, Input } from 'antd'
import { DeleteOutlined, PlusOutlined } from '@ant-design/icons';
import VariableSelect from '../VariableSelect'
@@ -36,7 +36,6 @@ const CycleVarsList: FC<CycleVarsListProps> = ({
value = [],
options,
parentName,
onChange,
selectedNode,
graphRef
}) => {
@@ -139,12 +138,17 @@ const CycleVarsList: FC<CycleVarsListProps> = ({
<Form.Item name={[name, 'value']} noStyle>
{currentInputType === 'variable' ? (
<VariableSelect
placeholder="选择变量"
options={availableOptions}
placeholder={t('common.pleaseSelect')}
options={availableOptions.filter(option => {
const currentType = value?.[index]?.type;
if (!currentType) return true;
return option.dataType === currentType
})}
/>
) : (
<Input.TextArea
placeholder="输入值"
placeholder={t('common.pleaseEnter')}
rows={3}
className="rb:w-full"
/>

View File

@@ -18,8 +18,22 @@ const GroupVariableList: FC<GroupVariableListProps> = ({
isCanAdd = false
}) => {
const { t } = useTranslation();
const form = Form.useFormInstance();
const value = form.getFieldValue(name) || [];
console.log('GroupVariableList', value)
if (!isCanAdd) {
// Filter options based on first variable's dataType if value exists
let filteredOptions = options;
if (value.length > 0) {
const firstVariableValue = value[0];
const firstVariable = options.find(opt => `{{${opt.value}}}` === firstVariableValue);
if (firstVariable) {
filteredOptions = options.filter(opt => opt.dataType === firstVariable.dataType);
}
}
return (
<div className="rb:mb-4">
<Row gutter={12} className="rb:mb-2!">
@@ -38,7 +52,7 @@ const GroupVariableList: FC<GroupVariableListProps> = ({
>
<VariableSelect
placeholder={t('common.pleaseSelect')}
options={options}
options={filteredOptions}
mode="multiple"
/>
</Form.Item>
@@ -77,7 +91,18 @@ const GroupVariableList: FC<GroupVariableListProps> = ({
>
<VariableSelect
placeholder={t('common.pleaseSelect')}
options={options}
options={(() => {
const currentGroupValue = value[name]?.value || [];
if (currentGroupValue.length > 0) {
const firstVariableValue = currentGroupValue[0];
const firstVariable = options.find(opt => `{{${opt.value}}}` === firstVariableValue);
if (firstVariable) {
return options.filter(opt => opt.dataType === firstVariable.dataType);
}
}
return options;
})()
}
mode="multiple"
/>
</Form.Item>

View File

@@ -90,7 +90,7 @@ const HttpRequest: FC<{ options: Suggestion[]; selectedNode?: any; graphRef?: an
</Col>
<Col span={16}>
<Form.Item name="url">
<Editor options={options} variant="outlined" />
<Editor options={options.filter(vo => vo.dataType === 'string' || vo.dataType === 'number')} variant="outlined" />
</Form.Item>
</Col>
</Row>
@@ -144,7 +144,7 @@ const HttpRequest: FC<{ options: Suggestion[]; selectedNode?: any; graphRef?: an
<Form.Item name={['body', 'data']} noStyle>
<EditableTable
parentName={['body', 'data']}
options={options}
options={options.filter(vo => vo.dataType === 'string' || vo.dataType === 'number')}
filterBooleanType={true}
/>
</Form.Item>
@@ -154,7 +154,7 @@ const HttpRequest: FC<{ options: Suggestion[]; selectedNode?: any; graphRef?: an
<MessageEditor
key="json"
parentName={['body', 'data']}
options={options}
options={options.filter(vo => vo.dataType === 'string' || vo.dataType === 'number')}
isArray={false}
title="JSON"
/>

View File

@@ -91,6 +91,7 @@ const VariableSelect: FC<VariableSelectProps> = ({
showSearch
allowClear={allowClear}
filterOption={(input, option) => {
if (input === '/') return true;
if (option?.options) {
return option.label?.toLowerCase().includes(input.toLowerCase()) ||
option.options.some((opt: any) =>

View File

@@ -22,6 +22,7 @@ import ConditionList from './ConditionList'
import CycleVarsList from './CycleVarsList'
import AssignmentList from './AssignmentList'
import ToolConfig from './ToolConfig'
// import { calculateVariableList } from './utils/variableListCalculator'
interface PropertiesProps {
selectedNode?: Node | null;
@@ -338,112 +339,35 @@ const Properties: FC<PropertiesProps> = ({
const parentLoopNode = getParentLoopNode(selectedNode.id);
console.log('childNodeIds', selectedNode, childNodeIds)
const allRelevantNodeIds = [...allPreviousNodeIds, ...childNodeIds];
let allRelevantNodeIds = [...allPreviousNodeIds, ...childNodeIds];
// Add parent loop/iteration node variables if current node is a child
// Add variables from nodes preceding the parent loop/iteration node if current node is a child
if (parentLoopNode) {
const parentData = parentLoopNode.getData();
const parentNodeId = parentLoopNode.getData().id;
if (parentData.type === 'loop') {
const cycleVars = parentData.cycle_vars || [];
cycleVars.forEach((cycleVar: any) => {
const key = `${parentNodeId}_cycle_${cycleVar.name}`;
if (!addedKeys.has(key)) {
addedKeys.add(key);
variableList.push({
key,
label: cycleVar.name,
type: 'variable',
dataType: cycleVar.type || 'String',
value: `${parentNodeId}.${cycleVar.name}`,
nodeData: parentData,
});
}
});
} else if (parentData.type === 'iteration') {
// Add item and index variables for iteration parent
const itemKey = `${parentNodeId}_item`;
const indexKey = `${parentNodeId}_index`;
if (!addedKeys.has(itemKey)) {
addedKeys.add(itemKey);
variableList.push({
key: itemKey,
label: 'item',
type: 'variable',
dataType: 'Object',
value: `${parentNodeId}.item`,
nodeData: parentData,
});
}
if (!addedKeys.has(indexKey)) {
addedKeys.add(indexKey);
variableList.push({
key: indexKey,
label: 'index',
type: 'variable',
dataType: 'Number',
value: `${parentNodeId}.index`,
nodeData: parentData,
});
}
}
// Check if parent loop/iteration is connected to http-request via ERROR connection
if (parentData.type === 'loop' || parentData.type === 'iteration') {
const parentPreviousNodeIds = getAllPreviousNodes(parentLoopNode.id);
parentPreviousNodeIds.forEach(prevNodeId => {
const prevNode = nodes.find(n => n.id === prevNodeId);
if (!prevNode) return;
const prevNodeData = prevNode.getData();
if (prevNodeData.type === 'http-request') {
// Check if connected via ERROR connection point
const errorEdges = edges.filter(edge => {
return edge.getTargetCellId() === parentLoopNode.id &&
edge.getSourceCellId() === prevNodeId &&
edge.getSourcePortId() === 'ERROR'
});
if (errorEdges.length > 0) {
const errorMessageKey = `${prevNodeData.id}_error_message`;
const errorTypeKey = `${prevNodeData.id}_error_type`;
if (!addedKeys.has(errorMessageKey)) {
addedKeys.add(errorMessageKey);
variableList.push({
key: errorMessageKey,
label: 'error_message',
type: 'variable',
dataType: 'string',
value: `${prevNodeData.id}.error_message`,
nodeData: prevNodeData,
});
}
if (!addedKeys.has(errorTypeKey)) {
addedKeys.add(errorTypeKey);
variableList.push({
key: errorTypeKey,
label: 'error_type',
type: 'variable',
dataType: 'string',
value: `${prevNodeData.id}.error_type`,
nodeData: prevNodeData,
});
}
}
}
});
}
// Add variables from nodes preceding the parent loop/iteration node
const parentPreviousNodeIds = getAllPreviousNodes(parentLoopNode.id);
allRelevantNodeIds.push(...parentPreviousNodeIds);
}
// Add conversation variables from global config
const conversationVariables = workflowConfig?.variables || [];
conversationVariables.forEach((variable: any) => {
const key = `CONVERSATION_${variable.name}`;
if (!addedKeys.has(key)) {
addedKeys.add(key);
variableList.push({
key,
label: variable.name,
type: 'variable',
dataType: variable.type,
value: `conv.${variable.name}`,
nodeData: { type: 'CONVERSATION', name: 'CONVERSATION', icon: '' },
group: 'CONVERSATION'
});
}
});
allRelevantNodeIds.forEach(nodeId => {
const node = nodes.find(n => n.id === nodeId);
if (!node) return;
@@ -496,7 +420,7 @@ const Properties: FC<PropertiesProps> = ({
key: llmKey,
label: 'output',
type: 'variable',
dataType: 'String',
dataType: 'string',
value: `${dataNodeId}.output`,
nodeData: nodeData,
});
@@ -565,6 +489,17 @@ const Properties: FC<PropertiesProps> = ({
const groupVariables = nodeData.config.group_variables.defaultValue || [];
groupVariables?.forEach((groupVar: any) => {
if (!groupVar || !groupVar.key) return;
// Determine dataType from first variable in the group
let groupDataType = 'string';
if (groupVar.value && Array.isArray(groupVar.value) && groupVar.value.length > 0) {
const firstVariableValue = groupVar.value[0];
const firstVariable = variableList.find(v => `{{${v.value}}}` === firstVariableValue);
if (firstVariable) {
groupDataType = firstVariable.dataType;
}
}
const groupVarKey = `${dataNodeId}_${groupVar.key}`;
if (!addedKeys.has(groupVarKey)) {
addedKeys.add(groupVarKey);
@@ -572,14 +507,26 @@ const Properties: FC<PropertiesProps> = ({
key: groupVarKey,
label: groupVar.key,
type: 'variable',
dataType: 'string',
dataType: groupDataType,
value: `${dataNodeId}.${groupVar.key}`,
nodeData: nodeData,
});
}
});
} else {
// If group=false, add output variable
// If group=false, add output variable with type from first group_variable
const groupVariables = nodeData.config.group_variables.defaultValue || [];
const firstVariable = groupVariables[0];
let outputDataType: string = 'any';
if (firstVariable) {
const filterVo = [...variableList].find(v => {
return `{{${v.value}}}` === firstVariable
})
if (filterVo) {
outputDataType = filterVo?.dataType
}
}
const varAggregatorKey = `${dataNodeId}_output`;
if (!addedKeys.has(varAggregatorKey)) {
addedKeys.add(varAggregatorKey);
@@ -587,7 +534,7 @@ const Properties: FC<PropertiesProps> = ({
key: varAggregatorKey,
label: 'output',
type: 'variable',
dataType: 'string',
dataType: outputDataType,
value: `${dataNodeId}.output`,
nodeData: nodeData,
});
@@ -684,21 +631,20 @@ const Properties: FC<PropertiesProps> = ({
nodeData: nodeData,
});
}
if (!addedKeys.has(outputKey)) {
addedKeys.add(outputKey);
variableList.push({
key: outputKey,
label: 'output',
type: 'variable',
dataType: 'string',
value: `${dataNodeId}.output`,
nodeData: nodeData,
});
}
// if (!addedKeys.has(outputKey)) {
// addedKeys.add(outputKey);
// variableList.push({
// key: outputKey,
// label: 'output',
// type: 'variable',
// dataType: 'string',
// value: `${dataNodeId}.output`,
// nodeData: nodeData,
// });
// }
break
case 'iteration':
const iterationOutputKey = `${dataNodeId}_output`;
const iterationItemKey = `${dataNodeId}_item`;
if (!addedKeys.has(iterationOutputKey)) {
addedKeys.add(iterationOutputKey);
// Get the data type from the output configuration, default to string
@@ -715,22 +661,11 @@ const Properties: FC<PropertiesProps> = ({
key: iterationOutputKey,
label: 'output',
type: 'variable',
dataType: outputDataType,
dataType: `array[${outputDataType}]`,
value: `${dataNodeId}.output`,
nodeData: nodeData,
});
}
if (!addedKeys.has(iterationItemKey)) {
addedKeys.add(iterationItemKey);
variableList.push({
key: iterationItemKey,
label: 'item',
type: 'variable',
dataType: 'string',
value: `${dataNodeId}.item`,
nodeData: nodeData,
});
}
break
case 'loop':
const cycleVars = nodeData.config.cycle_vars.defaultValue || [];
@@ -760,47 +695,337 @@ const Properties: FC<PropertiesProps> = ({
key: toolDataKey,
label: 'data',
type: 'variable',
dataType: 'object',
dataType: 'string',
value: `${dataNodeId}.data`,
nodeData: nodeData,
});
}
break
case 'memory-read':
const memoryReadAnswerKey = `${dataNodeId}_answer`;
const memoryReadIntermediateOutputs = `${dataNodeId}_intermediate_outputs`;
if (!addedKeys.has(memoryReadAnswerKey)) {
addedKeys.add(memoryReadAnswerKey);
variableList.push({
key: memoryReadAnswerKey,
label: 'answer',
type: 'variable',
dataType: 'string',
value: `${dataNodeId}.answer`,
nodeData: nodeData,
});
}
if (!addedKeys.has(memoryReadIntermediateOutputs)) {
addedKeys.add(memoryReadIntermediateOutputs);
variableList.push({
key: memoryReadIntermediateOutputs,
label: 'intermediate_outputs',
type: 'variable',
dataType: 'array[object]',
value: `${dataNodeId}.intermediate_outputs`,
nodeData: nodeData,
});
}
break
}
});
// Add conversation variables from global config
const conversationVariables = workflowConfig?.variables || [];
conversationVariables.forEach((variable: any) => {
const key = `CONVERSATION_${variable.name}`;
if (!addedKeys.has(key)) {
addedKeys.add(key);
variableList.push({
key,
label: variable.name,
type: 'variable',
dataType: variable.type,
value: `conv.${variable.name}`,
nodeData: { type: 'CONVERSATION', name: 'CONVERSATION', icon: '' },
group: 'CONVERSATION'
// Add parent loop/iteration node variables if current node is a child
if (parentLoopNode) {
const parentData = parentLoopNode.getData();
const parentNodeId = parentLoopNode.getData().id;
if (parentData.type === 'loop') {
const cycleVars = parentData.cycle_vars || [];
cycleVars.forEach((cycleVar: any) => {
const key = `${parentNodeId}_cycle_${cycleVar.name}`;
if (!addedKeys.has(key)) {
addedKeys.add(key);
variableList.push({
key,
label: cycleVar.name,
type: 'variable',
dataType: cycleVar.type || 'String',
value: `${parentNodeId}.${cycleVar.name}`,
nodeData: parentData,
});
}
});
} else if (parentData.type === 'iteration') {
// Add item and index variables for iteration parent only if input has value
if (parentData.config.input.defaultValue) {
const itemKey = `${parentNodeId}_item`;
const indexKey = `${parentNodeId}_index`;
// Determine item dataType from input variable
let itemDataType = 'object';
const inputVariable = variableList.find(v => `{{${v.value}}}` === parentData.config.input.defaultValue);
console.log('itemDataType defaultValue', parentData.config.input.defaultValue, variableList, inputVariable)
if (inputVariable && inputVariable.dataType.startsWith('array[')) {
itemDataType = inputVariable.dataType.replace(/^array\[(.+)\]$/, '$1');
console.log('itemDataType', itemDataType)
}
if (!addedKeys.has(itemKey)) {
addedKeys.add(itemKey);
variableList.push({
key: itemKey,
label: 'item',
type: 'variable',
dataType: itemDataType,
value: `${parentNodeId}.item`,
nodeData: parentData,
});
}
if (!addedKeys.has(indexKey)) {
addedKeys.add(indexKey);
variableList.push({
key: indexKey,
label: 'index',
type: 'variable',
dataType: 'number',
value: `${parentNodeId}.index`,
nodeData: parentData,
});
}
}
}
// Check if parent loop/iteration is connected to http-request via ERROR connection
if (parentData.type === 'loop' || parentData.type === 'iteration') {
const parentPreviousNodeIds = getAllPreviousNodes(parentLoopNode.id);
parentPreviousNodeIds.forEach(prevNodeId => {
const prevNode = nodes.find(n => n.id === prevNodeId);
if (!prevNode) return;
const prevNodeData = prevNode.getData();
if (prevNodeData.type === 'http-request') {
// Check if connected via ERROR connection point
const errorEdges = edges.filter(edge => {
return edge.getTargetCellId() === parentLoopNode.id &&
edge.getSourceCellId() === prevNodeId &&
edge.getSourcePortId() === 'ERROR'
});
if (errorEdges.length > 0) {
const errorMessageKey = `${prevNodeData.id}_error_message`;
const errorTypeKey = `${prevNodeData.id}_error_type`;
if (!addedKeys.has(errorMessageKey)) {
addedKeys.add(errorMessageKey);
variableList.push({
key: errorMessageKey,
label: 'error_message',
type: 'variable',
dataType: 'string',
value: `${prevNodeData.id}.error_message`,
nodeData: prevNodeData,
});
}
if (!addedKeys.has(errorTypeKey)) {
addedKeys.add(errorTypeKey);
variableList.push({
key: errorTypeKey,
label: 'error_type',
type: 'variable',
dataType: 'string',
value: `${prevNodeData.id}.error_type`,
nodeData: prevNodeData,
});
}
}
}
});
}
});
}
return variableList;
}, [selectedNode, graphRef, workflowConfig?.variables]);
// Filter out boolean type variables for loop and llm nodes
const getFilteredVariableList = (nodeType?: string) => {
if (nodeType === 'loop' || nodeType === 'llm') {
return variableList.filter(variable => variable.dataType !== 'boolean');
const getFilteredVariableList = (nodeType?: string, key?: string) => {
// Check if current node is a child of iteration node
const parentIterationNode = selectedNode ? (() => {
const nodes = graphRef.current?.getNodes() || [];
const nodeData = selectedNode.getData();
const cycle = nodeData?.cycle;
if (cycle) {
const parentNode = nodes.find(n => n.getData().id === cycle);
if (parentNode) {
const parentData = parentNode.getData();
if (parentData?.type === 'iteration') {
return parentNode;
}
}
}
return null;
})() : null;
// Helper function to add parent iteration variables
const addParentIterationVars = (filteredList: any[]) => {
if (parentIterationNode) {
const parentData = parentIterationNode.getData();
const parentNodeId = parentData.id;
if (parentData.config?.input?.defaultValue) {
const itemKey = `${parentNodeId}_item`;
const indexKey = `${parentNodeId}_index`;
const existingItemVar = filteredList.find(v => v.key === itemKey);
const existingIndexVar = filteredList.find(v => v.key === indexKey);
if (!existingItemVar) {
// Determine item dataType from input variable
let itemDataType = 'object';
const inputVariable = variableList.find(v => `{{${v.value}}}` === parentData.config.input.defaultValue);
if (inputVariable && inputVariable.dataType.startsWith('array[')) {
itemDataType = inputVariable.dataType.replace(/^array\[(.+)\]$/, '$1');
}
filteredList.push({
key: itemKey,
label: 'item',
type: 'variable',
dataType: itemDataType,
value: `${parentNodeId}.item`,
nodeData: parentData,
});
}
if (!existingIndexVar) {
filteredList.push({
key: indexKey,
label: 'index',
type: 'variable',
dataType: 'number',
value: `${parentNodeId}.index`,
nodeData: parentData,
});
}
}
}
return filteredList;
};
if (nodeType === 'llm') {
// For LLM nodes that are children of iteration or loop nodes, include parent variables
const parentLoopNode = selectedNode ? (() => {
const nodes = graphRef.current?.getNodes() || [];
const nodeData = selectedNode.getData();
const cycle = nodeData?.cycle;
if (cycle) {
const parentNode = nodes.find(n => n.getData().id === cycle);
if (parentNode) {
const parentData = parentNode.getData();
if (parentData?.type === 'loop' || parentData?.type === 'iteration') {
return parentNode;
}
}
}
return null;
})() : null;
let filteredList = variableList.filter(variable => variable.dataType !== 'boolean');
// If this LLM node is a child of iteration/loop, ensure parent variables are included
if (parentLoopNode) {
const parentData = parentLoopNode.getData();
const parentNodeId = parentData.id;
// Ensure parent loop/iteration variables are included
if (parentData.type === 'loop') {
const cycleVars = parentData.cycle_vars || [];
cycleVars.forEach((cycleVar: any) => {
const key = `${parentNodeId}_cycle_${cycleVar.name}`;
const existingVar = filteredList.find(v => v.key === key);
if (!existingVar && cycleVar.name && cycleVar.type !== 'boolean') {
filteredList.push({
key,
label: cycleVar.name,
type: 'variable',
dataType: cycleVar.type || 'String',
value: `${parentNodeId}.${cycleVar.name}`,
nodeData: parentData,
});
}
});
} else if (parentData.type === 'iteration') {
// Add item and index variables for iteration parent
if (parentData.config?.input?.defaultValue) {
const itemKey = `${parentNodeId}_item`;
const indexKey = `${parentNodeId}_index`;
const existingItemVar = filteredList.find(v => v.key === itemKey);
const existingIndexVar = filteredList.find(v => v.key === indexKey);
if (!existingItemVar) {
// Determine item dataType from input variable
let itemDataType = 'object';
const inputVariable = variableList.find(v => `{{${v.value}}}` === parentData.config.input.defaultValue);
if (inputVariable && inputVariable.dataType.startsWith('array[')) {
itemDataType = inputVariable.dataType.replace(/^array\[(.+)\]$/, '$1');
}
filteredList.push({
key: itemKey,
label: 'item',
type: 'variable',
dataType: itemDataType,
value: `${parentNodeId}.item`,
nodeData: parentData,
});
}
if (!existingIndexVar) {
filteredList.push({
key: indexKey,
label: 'index',
type: 'variable',
dataType: 'Number',
value: `${parentNodeId}.index`,
nodeData: parentData,
});
}
}
}
}
return filteredList;
}
return variableList;
if (nodeType === 'knowledge-retrieval' || nodeType === 'parameter-extractor' && key !== 'prompt' || nodeType === 'memory-read' || nodeType === 'memory-write' || nodeType === 'question-classifier') {
let filteredList = variableList.filter(variable => variable.dataType === 'string');
return addParentIterationVars(filteredList);
}
if (nodeType === 'parameter-extractor' && key === 'prompt') {
let filteredList = variableList.filter(variable => variable.dataType === 'string' || variable.dataType === 'number');
return addParentIterationVars(filteredList);
}
if (nodeType === 'iteration' && key === 'output') {
return variableList.filter(variable => variable.value.includes('sys.'));
}
if (nodeType === 'iteration') {
return variableList.filter(variable => variable.dataType.includes('array'));
}
if (nodeType === 'loop' && key === 'condition') {
let filteredList = variableList.filter(variable => variable.nodeData.type !== 'loop');
return addParentIterationVars(filteredList);
}
// For all other node types, add parent iteration variables if applicable
let baseList = variableList;
return addParentIterationVars(baseList);
};
// const defaultVariableList = calculateVariableList(selectedNode as Node, graphRef, workflowConfig )
console.log('values', values)
console.log('variableList', variableList, selectedNode?.data)
// console.log('variableList', variableList, defaultVariableList)
return (
<div className="rb:w-75 rb:fixed rb:right-0 rb:top-16 rb:bottom-0 rb:p-3">
@@ -901,11 +1126,10 @@ const Properties: FC<PropertiesProps> = ({
});
}
}
return (
<Form.Item key={key} name={key}>
<MessageEditor
key={key}
key={key}
options={contextVariableList.filter(variable => variable.nodeData?.type !== 'knowledge-retrieval')}
parentName={key}
/>
@@ -915,7 +1139,12 @@ const Properties: FC<PropertiesProps> = ({
if (selectedNode?.data?.type === 'end' && key === 'output') {
return (
<Form.Item key={key} name={key}>
<MessageEditor key={key} isArray={false} parentName={key} options={variableList} />
<MessageEditor
key={key}
isArray={false}
parentName={key}
options={variableList.filter(variable => variable.nodeData?.type !== 'knowledge-retrieval')}
/>
</Form.Item>
)
}
@@ -943,7 +1172,7 @@ const Properties: FC<PropertiesProps> = ({
isArray={!!config.isArray}
parentName={key}
enableJinja2={config.enableJinja2 as boolean}
options={getFilteredVariableList(selectedNode?.data?.type)}
options={getFilteredVariableList(selectedNode?.data?.type, key)}
/>
</Form.Item>
)
@@ -964,7 +1193,7 @@ const Properties: FC<PropertiesProps> = ({
<Form.Item key={key} name={key}>
<GroupVariableList
name={key}
options={getFilteredVariableList(selectedNode?.data?.type)}
options={getFilteredVariableList(selectedNode?.data?.type, key)}
isCanAdd={!!(values as any)?.group}
/>
</Form.Item>
@@ -976,7 +1205,7 @@ const Properties: FC<PropertiesProps> = ({
<Form.Item key={key} name={key}>
<CaseList
name={key}
options={getFilteredVariableList(selectedNode?.data?.type)}
options={getFilteredVariableList(selectedNode?.data?.type, key)}
selectedNode={selectedNode}
graphRef={graphRef}
/>
@@ -989,7 +1218,7 @@ const Properties: FC<PropertiesProps> = ({
<Form.Item key={key} name={key}
label={t(`workflow.config.${selectedNode?.data?.type}.${key}`)}
>
<MappingList name={key} options={getFilteredVariableList(selectedNode?.data?.type)} />
<MappingList name={key} options={getFilteredVariableList(selectedNode?.data?.type, key)} />
</Form.Item>
)
@@ -999,7 +1228,7 @@ const Properties: FC<PropertiesProps> = ({
<Form.Item key={key} name={key}>
<CycleVarsList
parentName={key}
options={getFilteredVariableList(selectedNode?.data?.type)}
options={getFilteredVariableList(selectedNode?.data?.type, key)}
/>
</Form.Item>
)
@@ -1013,9 +1242,9 @@ const Properties: FC<PropertiesProps> = ({
if (config.filterLoopIterationVars) {
const loopIterationVars: Suggestion[] = [];
return [...getFilteredVariableList(selectedNode?.data?.type), ...loopIterationVars];
return [...getFilteredVariableList(selectedNode?.data?.type, key), ...loopIterationVars];
}
return getFilteredVariableList(selectedNode?.data?.type);
return getFilteredVariableList(selectedNode?.data?.type, key);
})()
}
/>
@@ -1060,7 +1289,7 @@ const Properties: FC<PropertiesProps> = ({
? <VariableSelect
placeholder={t('common.pleaseSelect')}
options={(() => {
const baseVariableList = getFilteredVariableList(selectedNode?.data?.type);
const baseVariableList = getFilteredVariableList(selectedNode?.data?.type, key);
// Apply filtering if specified in config
if (config.filterNodeTypes || config.filterVariableNames) {
return baseVariableList.filter(variable => {
@@ -1068,7 +1297,7 @@ const Properties: FC<PropertiesProps> = ({
(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;
return nodeTypeMatch || variableNameMatch;
});
}
// Filter child nodes for iteration output
@@ -1085,7 +1314,7 @@ const Properties: FC<PropertiesProps> = ({
});
return baseVariableList.filter(variable =>
childNodes.some(node => node.id === variable.nodeData?.id)
childNodes.some(node => node.id === variable.nodeData?.id) || selectedNode?.data?.type === 'iteration' && key === 'output' && variable.value.includes('sys.')
);
}
return baseVariableList;
@@ -1095,7 +1324,12 @@ const Properties: FC<PropertiesProps> = ({
: config.type === 'switch'
? <Switch onChange={key === 'group' ? () => { form.setFieldValue('group_variables', []) } : undefined} />
: config.type === 'categoryList'
? <CategoryList parentName={key} selectedNode={selectedNode} graphRef={graphRef} />
? <CategoryList
parentName={key}
selectedNode={selectedNode}
graphRef={graphRef}
options={getFilteredVariableList(selectedNode?.data?.type, key)}
/>
: config.type === 'conditionList'
? <ConditionList
parentName={key}
@@ -1109,18 +1343,9 @@ const Properties: FC<PropertiesProps> = ({
value: `${selectedNode.getData().id}.${cycleVar.name}`,
nodeData: selectedNode.getData(),
}));
return [...variableList.filter(variable => {
// Keep conversation variables
if (variable.group === 'CONVERSATION') return true;
// Keep sys variables from start nodes
if (variable.nodeData?.type === 'start' && variable.value?.startsWith('sys.')) return true;
// Keep variables from non-start nodes
if (variable.nodeData?.type !== 'start' && variable.nodeData?.type !== 'http-request' && variable.dataType !== 'boolean') return true;
// Filter out custom variables from start nodes
return false;
}), ...cycleVarSuggestions];
})()
}
return [...getFilteredVariableList(selectedNode?.data?.type, key), ...cycleVarSuggestions];
})()}
selectedNode={selectedNode}
graphRef={graphRef}
addBtnText={t('workflow.config.addCase')}

View File

@@ -270,7 +270,7 @@ export const nodeLibrary: NodeLibrary[] = [
config: {
input: {
type: 'variableList',
filterNodeTypes: ['knowledge-retrieval'],
filterNodeTypes: ['knowledge-retrieval', 'iteration', 'loop'],
filterVariableNames: ['message']
},
parallel: {
@@ -334,8 +334,7 @@ export const nodeLibrary: NodeLibrary[] = [
}
}
},
{
type: "assigner", icon: assignerIcon,
{ type: "assigner", icon: assignerIcon,
config: {
assignments: {
type: 'assignmentList',
@@ -656,4 +655,114 @@ export const graphNodeLibrary: Record<string, NodeConfig> = {
items: [{ group: 'left' }],
},
}
}
export interface OutputVariable {
default?: Array<{
name: string;
type: string;
}>;
define?: string[];
sys?: Array<{
name: string;
type: string;
}>;
error?: Array<{
name: string;
type: string;
}>;
}
export const outputVariable: { [key: string]: OutputVariable } = {
start: {
sys: [
{ name: "message", type: "string" },
{ name: "conversation_id", type: "string" },
{ name: "execution_id", type: "string", },
{ name: "workspace_id", type: "string" },
{ name: "user_id", type: "string" },
],
define: ['variables']
},
end: {
},
llm: {
default: [
{ name: "output", type: "string" },
]
},
'knowledge-retrieval': {
default: [
{ name: "output", type: "array[object]" },
]
},
'parameter-extractor': {
default: [
{ name: "__is_success", type: "number" },
{ name: "__reason", type: "string" },
],
define: ['params']
},
'memory-read': {
default: [
{ name: "answer", type: "string" },
{ name: "intermediate_outputs", type: "array[object]" },
],
},
'memory-write': {
},
'if-else': {
},
'question-classifier': {
default: [
{ name: "class_name", type: "string" },
// { name: "output", type: "string" },
],
},
'iteration': {
default: [
// { name: "item", type: "string" }, // 仅内部使用
{ name: "output", type: "array[string]" },
],
},
'loop': {
define: ['cycle_vars']
},
'cycle-start': {
},
'break': {
},
'var-aggregator': {
// default: [
// { name: "output", type: "string" },
// ],
define: ['group_variables']
},
'assigner': {
},
'http-request': {
default: [
{ name: "body", type: "string" },
{ name: "status_code", type: "number" },
],
error: [
{ name: "error_message", type: "string" },
{ name: "error_type", type: "string" },
]
},
'tool': {
default: [
{ name: "data", type: "string" },
],
},
'jinja-render': {
default: [
{ name: "output", type: "string" },
],
},
}