refactor: extract useVariableList; properties add output variable
This commit is contained in:
@@ -1967,6 +1967,7 @@ Memory Bear: After the rebellion, regional warlordism intensified for several re
|
|||||||
value: 'Value',
|
value: 'Value',
|
||||||
addCase: 'Add Condition',
|
addCase: 'Add Condition',
|
||||||
addVariable: 'Add Variables',
|
addVariable: 'Add Variables',
|
||||||
|
output: 'Output Variable'
|
||||||
},
|
},
|
||||||
|
|
||||||
clear: 'Clear',
|
clear: 'Clear',
|
||||||
|
|||||||
@@ -2061,6 +2061,7 @@ export const zh = {
|
|||||||
value: '值',
|
value: '值',
|
||||||
addCase: '添加条件',
|
addCase: '添加条件',
|
||||||
addVariable: '添加变量',
|
addVariable: '添加变量',
|
||||||
|
output: '输出变量'
|
||||||
},
|
},
|
||||||
|
|
||||||
clear: '清空',
|
clear: '清空',
|
||||||
|
|||||||
@@ -33,6 +33,7 @@ const InitialValuePlugin: React.FC<InitialValuePluginProps> = ({ value, options
|
|||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (value !== prevValueRef.current && !isUserInputRef.current) {
|
if (value !== prevValueRef.current && !isUserInputRef.current) {
|
||||||
|
queueMicrotask(() => {
|
||||||
editor.update(() => {
|
editor.update(() => {
|
||||||
const root = $getRoot();
|
const root = $getRoot();
|
||||||
root.clear();
|
root.clear();
|
||||||
@@ -99,6 +100,7 @@ const InitialValuePlugin: React.FC<InitialValuePluginProps> = ({ value, options
|
|||||||
root.append(paragraph);
|
root.append(paragraph);
|
||||||
}
|
}
|
||||||
}, { discrete: true });
|
}, { discrete: true });
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
prevValueRef.current = value;
|
prevValueRef.current = value;
|
||||||
|
|||||||
@@ -17,7 +17,7 @@ const GroupVariableList: FC<GroupVariableListProps> = ({
|
|||||||
name,
|
name,
|
||||||
options = [],
|
options = [],
|
||||||
isCanAdd = false,
|
isCanAdd = false,
|
||||||
size = "middle"
|
size = "small"
|
||||||
}) => {
|
}) => {
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
const form = Form.useFormInstance();
|
const form = Form.useFormInstance();
|
||||||
@@ -37,16 +37,10 @@ const GroupVariableList: FC<GroupVariableListProps> = ({
|
|||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="rb:mb-4">
|
<div>
|
||||||
<Row gutter={12} className="rb:mb-2!">
|
<div className="rb:font-medium rb:text-[12px] rb:mb-1">
|
||||||
<Col span={12}>
|
|
||||||
<Form.Item
|
|
||||||
noStyle
|
|
||||||
>
|
|
||||||
{t('workflow.config.var-aggregator.variable')}
|
{t('workflow.config.var-aggregator.variable')}
|
||||||
</Form.Item>
|
</div>
|
||||||
</Col>
|
|
||||||
</Row>
|
|
||||||
|
|
||||||
<Form.Item
|
<Form.Item
|
||||||
name={name}
|
name={name}
|
||||||
|
|||||||
@@ -34,9 +34,9 @@ const VariableSelect: FC<VariableSelectProps> = ({
|
|||||||
if (filterOption) {
|
if (filterOption) {
|
||||||
return (
|
return (
|
||||||
<span
|
<span
|
||||||
className={clsx("rb:w-full rb:wrap-break-word rb:line-clamp-1 rb:border rb:border-[#DFE4ED] rb:rounded-md rb:bg-white rb:text-[12px] rb:inline-flex rb:items-center rb:px-1.5 rb:cursor-pointer", {
|
className={clsx("rb:max-w-full rb:wrap-break-word rb:line-clamp-1 rb:border rb:border-[#DFE4ED] rb:rounded-md rb:bg-white rb:text-[12px] rb:inline-flex rb:items-center rb:px-1.5 rb:cursor-pointer", {
|
||||||
'rb:leading-5.5!': size !== 'small',
|
'rb:leading-5.5!': size !== 'small',
|
||||||
'rb:leading-4!': size === 'small'
|
'rb:leading-4! rb:text-[10px]!': size === 'small'
|
||||||
})}
|
})}
|
||||||
contentEditable={false}
|
contentEditable={false}
|
||||||
>
|
>
|
||||||
|
|||||||
@@ -0,0 +1,209 @@
|
|||||||
|
import { useMemo, useEffect, useState } from 'react';
|
||||||
|
import { Graph, Node } from '@antv/x6';
|
||||||
|
import type { Suggestion } from '../../Editor/plugin/AutocompletePlugin';
|
||||||
|
import type { ChatVariable } from '../../../types';
|
||||||
|
|
||||||
|
const NODE_VARIABLES = {
|
||||||
|
llm: [{ label: 'output', dataType: 'string', field: 'output' }],
|
||||||
|
'jinja-render': [{ label: 'output', dataType: 'string', field: 'output' }],
|
||||||
|
tool: [{ label: 'data', dataType: 'string', field: 'data' }],
|
||||||
|
'knowledge-retrieval': [{ label: 'output', dataType: 'array[object]', field: 'output' }],
|
||||||
|
'parameter-extractor': [
|
||||||
|
{ label: '__is_success', dataType: 'number', field: '__is_success' },
|
||||||
|
{ label: '__reason', dataType: 'string', field: '__reason' }
|
||||||
|
],
|
||||||
|
'http-request': [
|
||||||
|
{ label: 'body', dataType: 'string', field: 'body' },
|
||||||
|
{ label: 'status_code', dataType: 'number', field: 'status_code' }
|
||||||
|
],
|
||||||
|
'question-classifier': [{ label: 'class_name', dataType: 'string', field: 'class_name' }],
|
||||||
|
'memory-read': [
|
||||||
|
{ label: 'answer', dataType: 'string', field: 'answer' },
|
||||||
|
{ label: 'intermediate_outputs', dataType: 'array[object]', field: 'intermediate_outputs' }
|
||||||
|
]
|
||||||
|
} as const;
|
||||||
|
|
||||||
|
const addVariable = (
|
||||||
|
list: Suggestion[],
|
||||||
|
keys: Set<string>,
|
||||||
|
key: string,
|
||||||
|
label: string,
|
||||||
|
dataType: string,
|
||||||
|
value: string,
|
||||||
|
nodeData: any,
|
||||||
|
extra?: Partial<Suggestion>
|
||||||
|
) => {
|
||||||
|
if (!keys.has(key)) {
|
||||||
|
keys.add(key);
|
||||||
|
list.push({ key, label, type: 'variable', dataType, value, nodeData, ...extra });
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const processNodeVariables = (
|
||||||
|
nodeData: any,
|
||||||
|
dataNodeId: string,
|
||||||
|
variableList: Suggestion[],
|
||||||
|
addedKeys: Set<string>
|
||||||
|
) => {
|
||||||
|
const { type, config } = nodeData;
|
||||||
|
|
||||||
|
if (type in NODE_VARIABLES) {
|
||||||
|
NODE_VARIABLES[type as keyof typeof NODE_VARIABLES].forEach(({ label, dataType, field }) => {
|
||||||
|
addVariable(variableList, addedKeys, `${dataNodeId}_${label}`, label, dataType, `${dataNodeId}.${field}`, nodeData);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
switch (type) {
|
||||||
|
case 'start':
|
||||||
|
[...(config?.variables?.defaultValue ?? []), ...(config?.variables?.value ?? [])].forEach((v: any) => {
|
||||||
|
if (v?.name) addVariable(variableList, addedKeys, `${dataNodeId}_${v.name}`, v.name, v.type, `${dataNodeId}.${v.name}`, nodeData);
|
||||||
|
});
|
||||||
|
config?.variables?.sys?.forEach((v: any) => {
|
||||||
|
if (v?.name) addVariable(variableList, addedKeys, `${dataNodeId}_sys_${v.name}`, `sys.${v.name}`, v.type, `sys.${v.name}`, nodeData);
|
||||||
|
});
|
||||||
|
break;
|
||||||
|
|
||||||
|
case 'parameter-extractor':
|
||||||
|
(config?.params?.defaultValue || []).forEach((p: any) => {
|
||||||
|
if (p?.name) addVariable(variableList, addedKeys, `${dataNodeId}_${p.name}`, p.name, p.type || 'string', `${dataNodeId}.${p.name}`, nodeData);
|
||||||
|
});
|
||||||
|
break;
|
||||||
|
|
||||||
|
case 'var-aggregator':
|
||||||
|
if (config.group.defaultValue) {
|
||||||
|
(config.group_variables.defaultValue || []).forEach((gv: any) => {
|
||||||
|
if (gv?.key) {
|
||||||
|
let dt = 'string';
|
||||||
|
if (gv.value?.[0]) {
|
||||||
|
const fv = variableList.find(v => `{{${v.value}}}` === gv.value[0]);
|
||||||
|
if (fv) dt = fv.dataType;
|
||||||
|
}
|
||||||
|
addVariable(variableList, addedKeys, `${dataNodeId}_${gv.key}`, gv.key, dt, `${dataNodeId}.${gv.key}`, nodeData);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
const fv = (config.group_variables.defaultValue || [])[0];
|
||||||
|
let dt = 'any';
|
||||||
|
if (fv) {
|
||||||
|
const found = variableList.find(v => `{{${v.value}}}` === fv);
|
||||||
|
if (found) dt = found.dataType;
|
||||||
|
}
|
||||||
|
addVariable(variableList, addedKeys, `${dataNodeId}_output`, 'output', dt, `${dataNodeId}.output`, nodeData);
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
|
||||||
|
case 'iteration':
|
||||||
|
let dt = 'string';
|
||||||
|
if (nodeData.output) {
|
||||||
|
const sv = variableList.find(v => v.value === nodeData.output);
|
||||||
|
if (sv) dt = sv.dataType;
|
||||||
|
}
|
||||||
|
addVariable(variableList, addedKeys, `${dataNodeId}_output`, 'output', `array[${dt}]`, `${dataNodeId}.output`, nodeData);
|
||||||
|
break;
|
||||||
|
|
||||||
|
case 'loop':
|
||||||
|
(config.cycle_vars.defaultValue || []).forEach((cv: any) => {
|
||||||
|
if (cv.name?.trim()) addVariable(variableList, addedKeys, `${dataNodeId}_cycle_${cv.name}`, cv.name, cv.type || 'string', `${dataNodeId}.${cv.name}`, nodeData);
|
||||||
|
});
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const hasOutputNodeTypes = [
|
||||||
|
'llm',
|
||||||
|
'knowledge-retrieval',
|
||||||
|
'memory-read',
|
||||||
|
'question-classifier',
|
||||||
|
'var-aggregator',
|
||||||
|
'http-request',
|
||||||
|
'tool',
|
||||||
|
'jinja-render'
|
||||||
|
]
|
||||||
|
export const getCurrentNodeVariables = (nodeData: any, values: any): Suggestion[] => {
|
||||||
|
if (!nodeData || !hasOutputNodeTypes.includes(nodeData.type)) return [];
|
||||||
|
const list: Suggestion[] = [];
|
||||||
|
const keys = new Set<string>();
|
||||||
|
const dataNodeId = nodeData.id;
|
||||||
|
|
||||||
|
processNodeVariables({
|
||||||
|
...nodeData,
|
||||||
|
config: {
|
||||||
|
...nodeData.config,
|
||||||
|
...values
|
||||||
|
}
|
||||||
|
}, dataNodeId, list, keys);
|
||||||
|
return nodeData.type === 'var-aggregator' && !nodeData.config.group.defaultValue ? [] : list;
|
||||||
|
};
|
||||||
|
|
||||||
|
export const useVariableList = (
|
||||||
|
selectedNode: Node | null | undefined,
|
||||||
|
graphRef: React.MutableRefObject<Graph | undefined>,
|
||||||
|
chatVariables: ChatVariable[]
|
||||||
|
) => {
|
||||||
|
const [trigger, setTrigger] = useState(0);
|
||||||
|
|
||||||
|
const variableList = useMemo(() => {
|
||||||
|
if (!selectedNode || !graphRef?.current) return [];
|
||||||
|
|
||||||
|
const list: Suggestion[] = [];
|
||||||
|
const graph = graphRef.current;
|
||||||
|
const edges = graph.getEdges();
|
||||||
|
const nodes = graph.getNodes();
|
||||||
|
const keys = new Set<string>();
|
||||||
|
|
||||||
|
const getPreviousNodes = (nodeId: string, visited = new Set<string>()): string[] => {
|
||||||
|
if (visited.has(nodeId)) return [];
|
||||||
|
visited.add(nodeId);
|
||||||
|
const prev = edges.filter(e => e.getTargetCellId() === nodeId).map(e => e.getSourceCellId());
|
||||||
|
return [...prev, ...prev.flatMap(id => getPreviousNodes(id, visited))];
|
||||||
|
};
|
||||||
|
|
||||||
|
const getParentLoop = (nodeId: string): Node | null => {
|
||||||
|
const node = nodes.find(n => n.id === nodeId);
|
||||||
|
const cycle = node?.getData()?.cycle;
|
||||||
|
if (cycle) {
|
||||||
|
const parent = nodes.find(n => n.getData().id === cycle);
|
||||||
|
if (parent?.getData()?.type === 'loop' || parent?.getData()?.type === 'iteration') return parent;
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
};
|
||||||
|
|
||||||
|
const childIds = nodes.filter(n => n.getData()?.cycle === selectedNode.id).map(n => n.id);
|
||||||
|
const parentLoop = getParentLoop(selectedNode.id);
|
||||||
|
const relevantIds = [...getPreviousNodes(selectedNode.id), ...childIds, ...(parentLoop ? getPreviousNodes(parentLoop.id) : [])];
|
||||||
|
|
||||||
|
chatVariables?.forEach(v => addVariable(list, keys, `CONVERSATION_${v.name}`, v.name, v.type, `conv.${v.name}`, { type: 'CONVERSATION', name: 'CONVERSATION', icon: '' }, { group: 'CONVERSATION' }));
|
||||||
|
|
||||||
|
relevantIds.forEach(id => {
|
||||||
|
const node = nodes.find(n => n.id === id);
|
||||||
|
if (node) processNodeVariables(node.getData(), node.getData().id, list, keys);
|
||||||
|
});
|
||||||
|
|
||||||
|
if (parentLoop) {
|
||||||
|
const pd = parentLoop.getData();
|
||||||
|
const pid = pd.id;
|
||||||
|
if (pd.type === 'loop') {
|
||||||
|
(pd.cycle_vars || []).forEach((cv: any) => addVariable(list, keys, `${pid}_cycle_${cv.name}`, cv.name, cv.type || 'String', `${pid}.${cv.name}`, pd));
|
||||||
|
} else if (pd.type === 'iteration' && pd.config.input.defaultValue) {
|
||||||
|
let itemType = 'object';
|
||||||
|
const iv = list.find(v => `{{${v.value}}}` === pd.config.input.defaultValue);
|
||||||
|
if (iv?.dataType.startsWith('array[')) itemType = iv.dataType.replace(/^array\[(.+)\]$/, '$1');
|
||||||
|
addVariable(list, keys, `${pid}_item`, 'item', itemType, `${pid}.item`, pd);
|
||||||
|
addVariable(list, keys, `${pid}_index`, 'index', 'number', `${pid}.index`, pd);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return list;
|
||||||
|
}, [selectedNode, graphRef, trigger, chatVariables]);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (!graphRef?.current) return;
|
||||||
|
const graph = graphRef.current;
|
||||||
|
const handler = () => setTrigger(p => p + 1);
|
||||||
|
const events = ['edge:added', 'edge:removed', 'edge:changed', 'edge:connected', 'node:added', 'node:removed', 'node:change:data'];
|
||||||
|
events.forEach(e => graph.on(e, handler));
|
||||||
|
return () => events.forEach(e => graph.off(e, handler));
|
||||||
|
}, [graphRef]);
|
||||||
|
|
||||||
|
return variableList;
|
||||||
|
};
|
||||||
@@ -2,7 +2,8 @@ import { type FC, useEffect, useState, useRef, useMemo } from "react";
|
|||||||
import clsx from 'clsx'
|
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, Select, InputNumber, Switch } from 'antd'
|
import { Form, Input, Select, InputNumber, Switch, Divider, Space } from 'antd'
|
||||||
|
import { CaretDownOutlined, CaretRightOutlined } from '@ant-design/icons';
|
||||||
|
|
||||||
import type { NodeConfig, NodeProperties, ChatVariable } from '../../types'
|
import type { NodeConfig, NodeProperties, ChatVariable } from '../../types'
|
||||||
import Empty from '@/components/Empty';
|
import Empty from '@/components/Empty';
|
||||||
@@ -24,7 +25,7 @@ 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 VariableList from './VariableList'
|
||||||
// import { calculateVariableList } from './utils/variableListCalculator'
|
import { useVariableList, getCurrentNodeVariables } from './hooks/useVariableList'
|
||||||
import styles from './properties.module.css'
|
import styles from './properties.module.css'
|
||||||
import Editor from "../Editor";
|
import Editor from "../Editor";
|
||||||
import RbSlider from './RbSlider'
|
import RbSlider from './RbSlider'
|
||||||
@@ -49,12 +50,12 @@ const Properties: FC<PropertiesProps> = ({
|
|||||||
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 [graphUpdateTrigger, setGraphUpdateTrigger] = useState(0)
|
|
||||||
const prevMappingNamesRef = useRef<string[]>([])
|
const prevMappingNamesRef = useRef<string[]>([])
|
||||||
const prevTemplateVarsRef = useRef<string[]>([])
|
const prevTemplateVarsRef = useRef<string[]>([])
|
||||||
const syncTimeoutRef = useRef<number | null>(null)
|
const syncTimeoutRef = useRef<number | null>(null)
|
||||||
const isSyncingRef = useRef(false)
|
const isSyncingRef = useRef(false)
|
||||||
const lastSyncSourceRef = useRef<'mapping' | 'template' | null>(null)
|
const lastSyncSourceRef = useRef<'mapping' | 'template' | null>(null)
|
||||||
|
const variableList = useVariableList(selectedNode, graphRef, chatVariables)
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (selectedNode?.getData()?.id) {
|
if (selectedNode?.getData()?.id) {
|
||||||
@@ -62,6 +63,7 @@ const Properties: FC<PropertiesProps> = ({
|
|||||||
prevMappingNamesRef.current = []
|
prevMappingNamesRef.current = []
|
||||||
prevTemplateVarsRef.current = []
|
prevTemplateVarsRef.current = []
|
||||||
lastSyncSourceRef.current = null
|
lastSyncSourceRef.current = null
|
||||||
|
setOutputCollapsed(true)
|
||||||
}
|
}
|
||||||
}, [selectedNode?.getData()?.id])
|
}, [selectedNode?.getData()?.id])
|
||||||
|
|
||||||
@@ -244,513 +246,7 @@ const Properties: FC<PropertiesProps> = ({
|
|||||||
}
|
}
|
||||||
}, [values, selectedNode, form])
|
}, [values, selectedNode, form])
|
||||||
|
|
||||||
const variableList = useMemo(() => {
|
|
||||||
if (!selectedNode || !graphRef?.current) return [];
|
|
||||||
|
|
||||||
const variableList: Suggestion[] = [];
|
|
||||||
const graph = graphRef.current;
|
|
||||||
const edges = graph.getEdges();
|
|
||||||
const nodes = graph.getNodes();
|
|
||||||
const addedKeys = new Set<string>();
|
|
||||||
|
|
||||||
// Find all connected previous nodes (recursive)
|
|
||||||
const getAllPreviousNodes = (nodeId: string, visited = new Set<string>()): string[] => {
|
|
||||||
if (visited.has(nodeId)) return [];
|
|
||||||
visited.add(nodeId);
|
|
||||||
|
|
||||||
const directPrevious = edges
|
|
||||||
.filter(edge => edge.getTargetCellId() === nodeId)
|
|
||||||
.map(edge => edge.getSourceCellId());
|
|
||||||
|
|
||||||
const allPrevious = [...directPrevious];
|
|
||||||
directPrevious.forEach(prevNodeId => {
|
|
||||||
allPrevious.push(...getAllPreviousNodes(prevNodeId, visited));
|
|
||||||
});
|
|
||||||
|
|
||||||
return allPrevious;
|
|
||||||
};
|
|
||||||
|
|
||||||
// Find child nodes (nodes whose cycle field equals current node's ID)
|
|
||||||
const getChildNodes = (nodeId: string): string[] => {
|
|
||||||
return nodes
|
|
||||||
.filter(node => node.getData()?.cycle === nodeId)
|
|
||||||
.map(node => node.id);
|
|
||||||
};
|
|
||||||
|
|
||||||
// Find parent loop/iteration node if current node is a child
|
|
||||||
const getParentLoopNode = (nodeId: string): Node | null => {
|
|
||||||
const node = nodes.find(n => n.id === nodeId);
|
|
||||||
if (!node) return null;
|
|
||||||
|
|
||||||
const nodeData = node.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;
|
|
||||||
};
|
|
||||||
|
|
||||||
const allPreviousNodeIds = getAllPreviousNodes(selectedNode.id);
|
|
||||||
const childNodeIds = getChildNodes(selectedNode.id);
|
|
||||||
const parentLoopNode = getParentLoopNode(selectedNode.id);
|
|
||||||
|
|
||||||
console.log('childNodeIds', selectedNode, childNodeIds)
|
|
||||||
let allRelevantNodeIds = [...allPreviousNodeIds, ...childNodeIds];
|
|
||||||
|
|
||||||
// Add variables from nodes preceding the parent loop/iteration node if current node is a child
|
|
||||||
if (parentLoopNode) {
|
|
||||||
const parentPreviousNodeIds = getAllPreviousNodes(parentLoopNode.id);
|
|
||||||
allRelevantNodeIds.push(...parentPreviousNodeIds);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Add conversation variables from global config
|
|
||||||
const conversationVariables = chatVariables || [];
|
|
||||||
|
|
||||||
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;
|
|
||||||
|
|
||||||
const nodeData = node.getData();
|
|
||||||
const dataNodeId = nodeData.id; // Use the data.id instead of node.id for consistency
|
|
||||||
|
|
||||||
switch(nodeData.type) {
|
|
||||||
case 'start':
|
|
||||||
const list = [
|
|
||||||
...(nodeData.config?.variables?.defaultValue ?? []),
|
|
||||||
...(nodeData.config?.variables?.value ?? [])
|
|
||||||
]
|
|
||||||
list.forEach((variable: any) => {
|
|
||||||
if (!variable || !variable?.name) return;
|
|
||||||
const key = `${dataNodeId}_${variable.name}`;
|
|
||||||
if (!addedKeys.has(key)) {
|
|
||||||
addedKeys.add(key);
|
|
||||||
variableList.push({
|
|
||||||
key,
|
|
||||||
label: variable.name,
|
|
||||||
type: 'variable',
|
|
||||||
dataType: variable.type,
|
|
||||||
value: `${dataNodeId}.${variable.name}`,
|
|
||||||
nodeData: nodeData,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
});
|
|
||||||
nodeData.config?.variables?.sys?.forEach((variable: any) => {
|
|
||||||
if (!variable || !variable?.name) return;
|
|
||||||
const key = `${dataNodeId}_sys_${variable.name}`;
|
|
||||||
if (!addedKeys.has(key)) {
|
|
||||||
addedKeys.add(key);
|
|
||||||
variableList.push({
|
|
||||||
key,
|
|
||||||
label: `sys.${variable.name}`,
|
|
||||||
type: 'variable',
|
|
||||||
dataType: variable.type,
|
|
||||||
value: `sys.${variable.name}`,
|
|
||||||
nodeData: nodeData,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
});
|
|
||||||
break
|
|
||||||
case 'llm':
|
|
||||||
const llmKey = `${dataNodeId}_output`;
|
|
||||||
if (!addedKeys.has(llmKey)) {
|
|
||||||
addedKeys.add(llmKey);
|
|
||||||
variableList.push({
|
|
||||||
key: llmKey,
|
|
||||||
label: 'output',
|
|
||||||
type: 'variable',
|
|
||||||
dataType: 'string',
|
|
||||||
value: `${dataNodeId}.output`,
|
|
||||||
nodeData: nodeData,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
break
|
|
||||||
case 'knowledge-retrieval':
|
|
||||||
const knowledgeKey = `${dataNodeId}_output`;
|
|
||||||
if (!addedKeys.has(knowledgeKey)) {
|
|
||||||
addedKeys.add(knowledgeKey);
|
|
||||||
variableList.push({
|
|
||||||
key: knowledgeKey,
|
|
||||||
label: 'output',
|
|
||||||
type: 'variable',
|
|
||||||
dataType: 'array[object]',
|
|
||||||
value: `${dataNodeId}.output`,
|
|
||||||
nodeData: nodeData,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
break
|
|
||||||
case 'parameter-extractor':
|
|
||||||
const successKey = `${dataNodeId}___is_success`;
|
|
||||||
const reasonKey = `${dataNodeId}___reason`;
|
|
||||||
if (!addedKeys.has(successKey)) {
|
|
||||||
addedKeys.add(successKey);
|
|
||||||
variableList.push({
|
|
||||||
key: successKey,
|
|
||||||
label: '__is_success',
|
|
||||||
type: 'variable',
|
|
||||||
dataType: 'number',
|
|
||||||
value: `${dataNodeId}.__is_success`,
|
|
||||||
nodeData: nodeData,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
if (!addedKeys.has(reasonKey)) {
|
|
||||||
addedKeys.add(reasonKey);
|
|
||||||
variableList.push({
|
|
||||||
key: reasonKey,
|
|
||||||
label: '__reason',
|
|
||||||
type: 'variable',
|
|
||||||
dataType: 'string',
|
|
||||||
value: `${dataNodeId}.__reason`,
|
|
||||||
nodeData: nodeData,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
// Add params variables
|
|
||||||
const paramsList = nodeData.config?.params?.defaultValue || [];
|
|
||||||
paramsList.forEach((param: any) => {
|
|
||||||
if (!param || !param?.name) return;
|
|
||||||
const paramKey = `${dataNodeId}_${param.name}`;
|
|
||||||
if (!addedKeys.has(paramKey)) {
|
|
||||||
addedKeys.add(paramKey);
|
|
||||||
variableList.push({
|
|
||||||
key: paramKey,
|
|
||||||
label: param.name,
|
|
||||||
type: 'variable',
|
|
||||||
dataType: param.type || 'string',
|
|
||||||
value: `${dataNodeId}.${param.name}`,
|
|
||||||
nodeData: nodeData,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
});
|
|
||||||
break
|
|
||||||
case 'var-aggregator':
|
|
||||||
if (nodeData.config.group.defaultValue) {
|
|
||||||
// If group=true, add variables from group_variables with key as variable name
|
|
||||||
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);
|
|
||||||
variableList.push({
|
|
||||||
key: groupVarKey,
|
|
||||||
label: groupVar.key,
|
|
||||||
type: 'variable',
|
|
||||||
dataType: groupDataType,
|
|
||||||
value: `${dataNodeId}.${groupVar.key}`,
|
|
||||||
nodeData: nodeData,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
});
|
|
||||||
} else {
|
|
||||||
// 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);
|
|
||||||
variableList.push({
|
|
||||||
key: varAggregatorKey,
|
|
||||||
label: 'output',
|
|
||||||
type: 'variable',
|
|
||||||
dataType: outputDataType,
|
|
||||||
value: `${dataNodeId}.output`,
|
|
||||||
nodeData: nodeData,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
break
|
|
||||||
case 'http-request':
|
|
||||||
const httpBodyKey = `${dataNodeId}_body`;
|
|
||||||
const httpStatusKey = `${dataNodeId}_status_code`;
|
|
||||||
if (!addedKeys.has(httpBodyKey)) {
|
|
||||||
addedKeys.add(httpBodyKey);
|
|
||||||
variableList.push({
|
|
||||||
key: httpBodyKey,
|
|
||||||
label: 'body',
|
|
||||||
type: 'variable',
|
|
||||||
dataType: 'string',
|
|
||||||
value: `${dataNodeId}.body`,
|
|
||||||
nodeData: nodeData,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
if (!addedKeys.has(httpStatusKey)) {
|
|
||||||
addedKeys.add(httpStatusKey);
|
|
||||||
variableList.push({
|
|
||||||
key: httpStatusKey,
|
|
||||||
label: 'status_code',
|
|
||||||
type: 'variable',
|
|
||||||
dataType: 'number',
|
|
||||||
value: `${dataNodeId}.status_code`,
|
|
||||||
nodeData: nodeData,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
break
|
|
||||||
case 'jinja-render':
|
|
||||||
const jinjaOutputKey = `${dataNodeId}_output`;
|
|
||||||
if (!addedKeys.has(jinjaOutputKey)) {
|
|
||||||
addedKeys.add(jinjaOutputKey);
|
|
||||||
variableList.push({
|
|
||||||
key: jinjaOutputKey,
|
|
||||||
label: 'output',
|
|
||||||
type: 'variable',
|
|
||||||
dataType: 'string',
|
|
||||||
value: `${dataNodeId}.output`,
|
|
||||||
nodeData: nodeData,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
break
|
|
||||||
case 'question-classifier':
|
|
||||||
const classNameKey = `${dataNodeId}_class_name`;
|
|
||||||
// const outputKey = `${dataNodeId}_output`;
|
|
||||||
if (!addedKeys.has(classNameKey)) {
|
|
||||||
addedKeys.add(classNameKey);
|
|
||||||
variableList.push({
|
|
||||||
key: classNameKey,
|
|
||||||
label: 'class_name',
|
|
||||||
type: 'variable',
|
|
||||||
dataType: 'string',
|
|
||||||
value: `${dataNodeId}.class_name`,
|
|
||||||
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`;
|
|
||||||
if (!addedKeys.has(iterationOutputKey)) {
|
|
||||||
addedKeys.add(iterationOutputKey);
|
|
||||||
// Get the data type from the output configuration, default to string
|
|
||||||
const outputConfig = nodeData.output;
|
|
||||||
let outputDataType = 'string';
|
|
||||||
if (outputConfig) {
|
|
||||||
// Find the selected variable from variableList to get its type
|
|
||||||
const selectedVariable = variableList.find(v => v.value === outputConfig);
|
|
||||||
if (selectedVariable) {
|
|
||||||
outputDataType = selectedVariable.dataType;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
variableList.push({
|
|
||||||
key: iterationOutputKey,
|
|
||||||
label: 'output',
|
|
||||||
type: 'variable',
|
|
||||||
dataType: `array[${outputDataType}]`,
|
|
||||||
value: `${dataNodeId}.output`,
|
|
||||||
nodeData: nodeData,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
break
|
|
||||||
case 'loop':
|
|
||||||
const cycleVars = nodeData.config.cycle_vars.defaultValue || [];
|
|
||||||
console.log('cycleVars', cycleVars)
|
|
||||||
cycleVars.forEach((cycleVar: any) => {
|
|
||||||
const cycleVarKey = `${dataNodeId}_cycle_${cycleVar.name}`;
|
|
||||||
if (!addedKeys.has(cycleVarKey)) {
|
|
||||||
addedKeys.add(cycleVarKey);
|
|
||||||
if (cycleVar.name && cycleVar.name.trim() !== '') {
|
|
||||||
variableList.push({
|
|
||||||
key: cycleVarKey,
|
|
||||||
label: cycleVar.name,
|
|
||||||
type: 'variable',
|
|
||||||
dataType: cycleVar.type || 'string',
|
|
||||||
value: `${dataNodeId}.${cycleVar.name}`,
|
|
||||||
nodeData: nodeData,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
break
|
|
||||||
case 'tool':
|
|
||||||
const toolDataKey = `${dataNodeId}_data`;
|
|
||||||
if (!addedKeys.has(toolDataKey)) {
|
|
||||||
addedKeys.add(toolDataKey);
|
|
||||||
variableList.push({
|
|
||||||
key: toolDataKey,
|
|
||||||
label: 'data',
|
|
||||||
type: 'variable',
|
|
||||||
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 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,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return variableList;
|
|
||||||
}, [selectedNode, graphRef, graphUpdateTrigger, chatVariables]);
|
|
||||||
|
|
||||||
// Trigger variableList update when graph edges or nodes change
|
|
||||||
useEffect(() => {
|
|
||||||
if (!graphRef?.current) return;
|
|
||||||
|
|
||||||
const graph = graphRef.current;
|
|
||||||
const handleGraphChange = () => {
|
|
||||||
console.log('handleGraphChange')
|
|
||||||
// Force variableList recalculation by updating trigger
|
|
||||||
setGraphUpdateTrigger(prev => prev + 1);
|
|
||||||
};
|
|
||||||
|
|
||||||
// Listen to graph changes
|
|
||||||
graph.on('edge:added', handleGraphChange);
|
|
||||||
graph.on('edge:removed', handleGraphChange);
|
|
||||||
graph.on('edge:changed', handleGraphChange);
|
|
||||||
graph.on('node:added', handleGraphChange);
|
|
||||||
graph.on('node:removed', handleGraphChange);
|
|
||||||
graph.on('node:change:data', handleGraphChange);
|
|
||||||
|
|
||||||
return () => {
|
|
||||||
graph.off('edge:added', handleGraphChange);
|
|
||||||
graph.off('edge:removed', handleGraphChange);
|
|
||||||
graph.off('edge:changed', handleGraphChange);
|
|
||||||
graph.off('node:added', handleGraphChange);
|
|
||||||
graph.off('node:removed', handleGraphChange);
|
|
||||||
graph.off('node:change:data', handleGraphChange);
|
|
||||||
};
|
|
||||||
}, [graphRef]);
|
|
||||||
|
|
||||||
// Filter out boolean type variables for loop and llm nodes
|
// Filter out boolean type variables for loop and llm nodes
|
||||||
const getFilteredVariableList = (nodeType?: string, key?: string) => {
|
const getFilteredVariableList = (nodeType?: string, key?: string) => {
|
||||||
@@ -994,14 +490,25 @@ const Properties: FC<PropertiesProps> = ({
|
|||||||
// const defaultVariableList = calculateVariableList(selectedNode as Node, graphRef, workflowConfig )
|
// const defaultVariableList = calculateVariableList(selectedNode as Node, graphRef, workflowConfig )
|
||||||
|
|
||||||
console.log('values', values)
|
console.log('values', values)
|
||||||
console.log('variableList', variableList)
|
|
||||||
|
const currentNodeVariables = useMemo(() => {
|
||||||
|
if (!selectedNode) return []
|
||||||
|
return getCurrentNodeVariables(selectedNode?.getData(), values)
|
||||||
|
}, [selectedNode?.getData(), values])
|
||||||
|
|
||||||
|
const [outputCollapsed, setOutputCollapsed] = useState(true)
|
||||||
|
const handleToggle = () => {
|
||||||
|
setOutputCollapsed((prev: boolean) => !prev)
|
||||||
|
}
|
||||||
|
console.log('variableList', variableList, currentNodeVariables)
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<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={clsx("rb:w-75 rb:fixed rb:right-0 rb:top-16 rb:bottom-0 rb:p-3 rb:pb-6", styles.properties)}>
|
||||||
<div className="rb:font-medium rb:leading-5 rb:pb-3 rb:mb-3 rb:border-b rb:border-b-[#DFE4ED]">{t('workflow.nodeProperties')}</div>
|
<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} size="small" layout="vertical" className="rb:h-[calc(100%-20px)] rb:overflow-x-hidden rb:overflow-y-auto">
|
: <div className="rb:h-[calc(100%-20px)] rb:overflow-x-hidden rb:overflow-y-auto">
|
||||||
|
<Form form={form} size="small" layout="vertical">
|
||||||
<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')}
|
||||||
@@ -1311,7 +818,25 @@ const Properties: FC<PropertiesProps> = ({
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
</Form>
|
</Form>
|
||||||
|
|
||||||
|
{currentNodeVariables.length > 0 && !(!values?.group && selectedNode.getData().type === 'var-aggregator') &&
|
||||||
|
<div className="rb:pb-3">
|
||||||
|
<Divider />
|
||||||
|
<Space size={8} direction="vertical" className="rb:max-w-full!">
|
||||||
|
<div className="rb:font-medium rb:text-[12px] rb:leading-4.5 rb:cursor-pointer rb:ml-4" onClick={handleToggle}>
|
||||||
|
{t('workflow.config.output')}
|
||||||
|
{outputCollapsed ? <CaretRightOutlined /> : <CaretDownOutlined />}
|
||||||
|
</div>
|
||||||
|
{!outputCollapsed && currentNodeVariables.map(vo => (
|
||||||
|
<div key={vo.value} className="rb:ml-4 rb:text-[12px] rb:flex rb:gap-2">
|
||||||
|
<span className="rb:font-medium">{vo.label}</span>
|
||||||
|
<span className="rb:text-[#5B6167]">{vo.dataType}</span>
|
||||||
|
</div>
|
||||||
|
))}
|
||||||
|
</Space>
|
||||||
|
</div>
|
||||||
}
|
}
|
||||||
|
</div>}
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -275,11 +275,6 @@ export const useWorkflowGraph = ({
|
|||||||
}, 100)
|
}, 100)
|
||||||
}
|
}
|
||||||
if (edges.length) {
|
if (edges.length) {
|
||||||
// 计算loop和iteration类型节点的数量
|
|
||||||
const loopIterationCount = nodes.filter(node =>
|
|
||||||
node.type === 'loop' || node.type === 'iteration'
|
|
||||||
).length;
|
|
||||||
|
|
||||||
// 去重处理:对于if-else和question-classifier节点,不同连接桩允许连接到相同节点
|
// 去重处理:对于if-else和question-classifier节点,不同连接桩允许连接到相同节点
|
||||||
const uniqueEdges = edges.filter((edge, index, arr) => {
|
const uniqueEdges = edges.filter((edge, index, arr) => {
|
||||||
return arr.findIndex(e => {
|
return arr.findIndex(e => {
|
||||||
|
|||||||
Reference in New Issue
Block a user