diff --git a/web/src/views/Workflow/components/Properties/GroupVariableList/index.tsx b/web/src/views/Workflow/components/Properties/GroupVariableList/index.tsx index 81eac38e..a9b67dac 100644 --- a/web/src/views/Workflow/components/Properties/GroupVariableList/index.tsx +++ b/web/src/views/Workflow/components/Properties/GroupVariableList/index.tsx @@ -1,30 +1,87 @@ -import { type FC } from 'react' +/* + * @Author: ZhaoYing + * @Date: 2026-02-03 15:17:39 + * @Last Modified by: ZhaoYing + * @Last Modified time: 2026-02-03 15:17:39 + */ +import { useEffect, type FC } from 'react' import { useTranslation } from 'react-i18next'; import { Form, Input, Button, Row, Col } from 'antd' import type { Suggestion } from '../../Editor/plugin/AutocompletePlugin' import VariableSelect from '../VariableSelect' +/** + * Props for GroupVariableList component + */ interface GroupVariableListProps { + /** Current value - array of key-value pairs for grouped variables */ value?: Array<{ key: string; value: string[]; }>; + /** Form field name */ name: string; + /** Available variable options for selection */ options: Suggestion[]; + /** Whether user can add custom groups */ isCanAdd: boolean; + /** Size of form controls */ size: 'small' | 'middle' } +/** + * GroupVariableList component + * Manages grouped variable selection for var-aggregator node + * Supports two modes: + * 1. Simple mode (isCanAdd=false): Single variable list with type inference + * 2. Advanced mode (isCanAdd=true): Multiple named groups with type inference per group + * @param props - Component props + */ const GroupVariableList: FC = ({ name, options = [], isCanAdd = false, size = "small" }) => { + // Hooks const { t } = useTranslation(); const form = Form.useFormInstance(); + + // Get current form value const value = form.getFieldValue(name) || []; - console.log('GroupVariableList', value) + /** + * Reset group_type when mode changes + */ + useEffect(() => { + form.setFieldValue('group_type', {}) + }, [isCanAdd]) + /** + * Auto-infer and set data types based on selected variables + * In simple mode: Sets single output type + * In advanced mode: Sets type for each group + */ + useEffect(() => { + if (!isCanAdd && value[0]) { + const firstVariable = options.find(opt => `{{${opt.value}}}` === value[0]); + if (firstVariable) { + form.setFieldValue(['group_type', 'output'], firstVariable.dataType); + } + } else if (isCanAdd) { + value.forEach((item: any, index: number) => { + if (item?.value?.[0]) { + const firstVariable = options.find(opt => `{{${opt.value}}}` === item.value[0]); + if (firstVariable) { + form.setFieldValue(['group_type', index], firstVariable.dataType); + } + } + }); + } + }, [isCanAdd, options, value, form]) + + /** + * Simple mode rendering + * Single variable list with automatic type filtering + */ if (!isCanAdd) { // Filter options based on first variable's dataType if value exists let filteredOptions = options; @@ -53,77 +110,85 @@ const GroupVariableList: FC = ({ size={size} /> + ) } + /** + * Advanced mode rendering + * Multiple named groups with individual variable lists + */ return ( - - {(fields, { add, remove }) => ( - <> - {fields.map(({ key, name, ...restField }) => { - return ( -
- - - - {isCanAdd ? : t('workflow.config.var-aggregator.variable')} - - + <> + + {(fields, { add, remove }) => ( + <> + {fields.map(({ key, name, ...restField }) => { + return ( +
+ + + + {isCanAdd ? : t('workflow.config.var-aggregator.variable')} + + - {isCanAdd && -
remove(name)} - >
- } -
+ {isCanAdd && +
remove(name)} + >
+ } + - - { - 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); + + { + 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; + })() } - return options; - })() - } - mode="multiple" - size={size} - /> - -
- ) - })} + mode="multiple" + size={size} + /> + +
+ ) + })} - {isCanAdd && } - - )} -
+ {isCanAdd && } + + )} + + + ) } diff --git a/web/src/views/Workflow/components/Properties/VariableSelect.tsx b/web/src/views/Workflow/components/Properties/VariableSelect.tsx index f56180a0..7ae4eff5 100644 --- a/web/src/views/Workflow/components/Properties/VariableSelect.tsx +++ b/web/src/views/Workflow/components/Properties/VariableSelect.tsx @@ -1,18 +1,36 @@ +/* + * @Author: ZhaoYing + * @Date: 2026-02-03 15:40:13 + * @Last Modified by: ZhaoYing + * @Last Modified time: 2026-02-03 15:40:13 + */ import { type FC } from 'react' import clsx from 'clsx'; import { Select, type SelectProps } from 'antd' import type { Suggestion } from '../Editor/plugin/AutocompletePlugin' type LabelRender = SelectProps['labelRender']; +/** + * Props for VariableSelect component + */ interface VariableSelectProps extends SelectProps { + /** Available variable options */ options: Suggestion[]; + /** Current selected value */ value?: string; - onChange?: (value: string) => void; + /** Whether to show clear button */ allowClear?: boolean; + /** Filter out boolean type variables */ filterBooleanType?: boolean; + /** Size of the select component */ size?: 'small' | 'middle' | 'large' } +/** + * VariableSelect component + * Custom select component for workflow variables with grouped options and custom rendering + * @param props - Component props + */ const VariableSelect: FC = ({ placeholder, options, @@ -24,9 +42,19 @@ const VariableSelect: FC = ({ ...resetPorps }) => { - const handleChange = (value: string) => { - onChange?.(value); + /** + * Handle value change and pass selected option to parent + * @param value - Selected value + */ + const handleChange: SelectProps['onChange'] = (value: string) => { + const filterItem = options.find(option => `{{${option.value}}}` === value) + onChange?.(value, filterItem); } + /** + * Custom label renderer for selected value + * Displays node icon, name and variable label + * @param props - Label render props + */ const labelRender: LabelRender = (props) => { const { value } = props const filterOption = filteredOptions.find(vo => `{{${vo.value}}}` === value) @@ -57,10 +85,14 @@ const VariableSelect: FC = ({ } return null } + // Filter options based on boolean type if needed const filteredOptions = filterBooleanType ? options.filter(option => option.dataType !== 'boolean') : options; + /** + * Group suggestions by node ID + */ const groupedSuggestions = filteredOptions.reduce((groups: Record, suggestion) => { const { nodeData } = suggestion const nodeId = nodeData.id as string; @@ -71,6 +103,9 @@ const VariableSelect: FC = ({ return groups; }, {}); + /** + * Format grouped options for Select component + */ const groupedOptions = Object.entries(groupedSuggestions).map(([_nodeId, suggestions]) => ({ label: suggestions[0].nodeData.name, options: suggestions.map(s => ({ diff --git a/web/src/views/Workflow/components/Properties/index.tsx b/web/src/views/Workflow/components/Properties/index.tsx index aa757275..59860005 100644 --- a/web/src/views/Workflow/components/Properties/index.tsx +++ b/web/src/views/Workflow/components/Properties/index.tsx @@ -1,3 +1,9 @@ +/* + * @Author: ZhaoYing + * @Date: 2026-02-03 15:39:59 + * @Last Modified by: ZhaoYing + * @Last Modified time: 2026-02-03 15:39:59 + */ import { type FC, useEffect, useState, useMemo } from "react"; import clsx from 'clsx' import { useTranslation } from 'react-i18next' @@ -31,17 +37,35 @@ import RbSlider from './RbSlider' import JinjaRender from './JinjaRender' import CodeExecution from './CodeExecution' +/** + * Props for Properties component + */ interface PropertiesProps { + /** Currently selected node */ selectedNode?: Node | null; + /** Function to update selected node */ setSelectedNode: (node: Node | null) => void; + /** Reference to graph instance */ graphRef: React.MutableRefObject; + /** Handler for blank canvas click */ blankClick: () => void; + /** Handler for delete event */ deleteEvent: () => void; + /** Handler for copy event */ copyEvent: () => void; + /** Handler for paste event */ parseEvent: () => void; + /** Workflow configuration */ config?: any; + /** Chat variables */ chatVariables: ChatVariable[]; } + +/** + * Properties panel component + * Displays and manages configuration for selected workflow node + * @param props - Component props + */ const Properties: FC = ({ selectedNode, graphRef, @@ -83,6 +107,10 @@ const Properties: FC = ({ } }, [selectedNode, form]) + /** + * Update node label in graph + * @param newLabel - New label text + */ const updateNodeLabel = (newLabel: string) => { if (selectedNode && form) { const nodeData = selectedNode.data as NodeProperties; @@ -107,8 +135,6 @@ const Properties: FC = ({ })) } - - Object.keys(values).forEach(key => { if (selectedNode.data?.config?.[key]) { // Create a deep copy to avoid reference sharing between nodes @@ -131,7 +157,12 @@ const Properties: FC = ({ - // Filter out boolean type variables for loop and llm nodes + /** + * Get filtered variable list based on node type and config key + * @param nodeType - Type of the node + * @param key - Configuration key + * @returns Filtered variable list + */ const getFilteredVariableList = (nodeType?: string, key?: string) => { // Check if current node is a child of iteration node const parentIterationNode = selectedNode ? (() => { @@ -321,15 +352,33 @@ const Properties: FC = ({ console.log('values', values) + /** + * Get current node output variables + */ const currentNodeVariables = useMemo(() => { if (!selectedNode) return [] return getCurrentNodeVariables(selectedNode?.getData(), values) }, [selectedNode?.getData(), values]) const [outputCollapsed, setOutputCollapsed] = useState(true) + /** + * Toggle output section collapsed state + */ const handleToggle = () => { setOutputCollapsed((prev: boolean) => !prev) } + + /** + * Handle variable list change and update output type for iteration nodes + * @param _value - Selected value + * @param option - Selected option + * @param key - Configuration key + */ + const handleChangeVariableList = (_value: string, option: any, key: string) => { + if (selectedNode?.data?.type === 'iteration' && key === 'output') { + form.setFieldValue('output_type', option?.dataType) + } + } console.log('variableList', variableList, currentNodeVariables) return ( @@ -422,6 +471,9 @@ const Properties: FC = ({ ) } + if (selectedNode?.data?.type === 'iteration' && key === 'output_type') { + return (