import { type FC, useEffect, useState, useRef, useMemo } from "react"; import { useTranslation } from 'react-i18next' import { Graph, Node } from '@antv/x6'; import { Form, Input, Button, Select, InputNumber, Slider, Space, Divider, App, Switch } from 'antd' import type { NodeConfig, NodeProperties, StartVariableItem, VariableEditModalRef } from '../../types' import Empty from '@/components/Empty'; import emptyIcon from '@/assets/images/workflow/empty.png' import CustomSelect from "@/components/CustomSelect"; import VariableEditModal from './VariableEditModal'; import MessageEditor from './MessageEditor' import Knowledge from './Knowledge/Knowledge'; import type { Suggestion } from '../Editor/plugin/AutocompletePlugin' import VariableSelect from './VariableSelect'; import ParamsList from './ParamsList'; import GroupVariableList from './GroupVariableList' import CaseList from './CaseList' import HttpRequest from './HttpRequest'; import MappingList from './MappingList' interface PropertiesProps { selectedNode?: Node | null; setSelectedNode: (node: Node | null) => void; graphRef: React.MutableRefObject; blankClick: () => void; deleteEvent: () => void; copyEvent: () => void; parseEvent: () => void; } const Properties: FC = ({ selectedNode, graphRef, }) => { const { t } = useTranslation() const { modal } = App.useApp() const [form] = Form.useForm(); const [configs, setConfigs] = useState>({} as Record) const values = Form.useWatch([], form); const variableModalRef = useRef(null) const [editIndex, setEditIndex] = useState(null) useEffect(() => { if (selectedNode?.getData()?.id) { form.resetFields() } }, [selectedNode?.getData()?.id]) useEffect(() => { if (selectedNode && form) { const { type = 'default', name = '', config } = selectedNode.getData() || {} const initialValue: Record = {} Object.keys(config || {}).forEach(key => { if (config && config[key] && 'defaultValue' in config[key]) { initialValue[key] = config[key].defaultValue } }) form.setFieldsValue({ type, id: selectedNode.id, name, ...initialValue, }) setConfigs(config || {}) } }, [selectedNode, form]) const updateNodeLabel = (newLabel: string) => { if (selectedNode && form) { const nodeData = selectedNode.data as NodeProperties; selectedNode.setAttrByPath('text/text', `${nodeData.icon} ${newLabel}`); selectedNode.setData({ ...selectedNode.data, name: newLabel }); } }; useEffect(() => { if (values && selectedNode) { const { id, knowledge_retrieval, group, group_names, ...rest } = values const { knowledge_bases = [], ...restKnowledgeConfig } = (knowledge_retrieval as any) || {} let groupNames: Record | string[] = {} if (group && group_names?.length) { group_names.forEach(vo => { (groupNames as Record)[vo.key] = vo.value }) } else if (!group) { groupNames = group_names?.[0]?.value || [] } let allRest = { ...rest, ...restKnowledgeConfig, } if (knowledge_bases?.length) { allRest.knowledge_bases = knowledge_bases?.map((vo: any) => ({ id: vo.id, ...vo.config })) } Object.keys(values).forEach(key => { if (selectedNode.data?.config?.[key]) { selectedNode.data.config[key].defaultValue = values[key] } }) selectedNode?.setData({ ...selectedNode.data, ...allRest, }) } }, [values, selectedNode]) const handleAddVariable = () => { variableModalRef.current?.handleOpen() } const handleEditVariable = (index: number, vo: StartVariableItem) => { variableModalRef.current?.handleOpen(vo) setEditIndex(index) } const handleRefreshVariable = (value: StartVariableItem) => { if (!selectedNode) return if (editIndex !== null) { const defaultValue = selectedNode.data.config.variables.defaultValue ?? [] defaultValue[editIndex] = value selectedNode.data.config.variables.defaultValue = [...defaultValue] } else { const defaultValue = selectedNode.data.config.variables.defaultValue ?? [] selectedNode.data.config.variables.defaultValue = [...defaultValue, value] } selectedNode?.setData({ ...selectedNode.data}) setConfigs({ ...selectedNode.data.config}) } const handleDeleteVariable = (index: number, vo: StartVariableItem) => { if (!selectedNode) return modal.confirm({ title: t('common.confirmDeleteDesc', { name: vo.name }), okText: t('common.delete'), cancelText: t('common.cancel'), okType: 'danger', onOk: () => { const defaultValue = selectedNode.data.config.variables.defaultValue ?? [] defaultValue.splice(index, 1) selectedNode.data.config.variables.defaultValue = [...defaultValue] selectedNode?.setData({ ...selectedNode.data }) setConfigs({ ...selectedNode.data.config }) } }) } const variableList = useMemo(() => { if (!selectedNode || !graphRef?.current) return []; const variableList: Suggestion[] = []; const graph = graphRef.current; const edges = graph.getEdges(); const nodes = graph.getNodes(); const addedKeys = new Set(); // Find all connected previous nodes (recursive) const getAllPreviousNodes = (nodeId: string, visited = new Set()): 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; }; const allPreviousNodeIds = getAllPreviousNodes(selectedNode.id); allPreviousNodeIds.forEach(nodeId => { const node = nodes.find(n => n.id === nodeId); if (!node) return; const nodeData = node.getData(); 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 = `${nodeId}_${variable.name}`; if (!addedKeys.has(key)) { addedKeys.add(key); variableList.push({ key, label: variable.name, type: 'variable', dataType: variable.type, value: `{{${nodeId}.${variable.name}}}`, nodeData: nodeData, }); } }); nodeData.config?.variables?.sys?.forEach((variable: any) => { if (!variable || !variable?.name) return; const key = `${nodeId}_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 = `${nodeId}_output`; if (!addedKeys.has(llmKey)) { addedKeys.add(llmKey); variableList.push({ key: llmKey, label: 'output', type: 'variable', dataType: 'String', value: `${nodeId}.output`, nodeData: nodeData, }); } break case 'knowledge-retrieval': const knowledgeKey = `${nodeId}_message`; if (!addedKeys.has(knowledgeKey)) { addedKeys.add(knowledgeKey); variableList.push({ key: knowledgeKey, label: 'message', type: 'variable', dataType: 'String', value: `${nodeId}.message`, nodeData: nodeData, }); } break } }); return variableList; }, [selectedNode, graphRef]); console.log('values', values) return (
{t('workflow.nodeProperties')}
{!selectedNode ? :
{ updateNodeLabel(e.target.value); }} /> {selectedNode?.data?.type === 'http-request' ? : configs && Object.keys(configs).length > 0 && Object.keys(configs).map((key) => { const config = configs[key] || {} if (selectedNode?.data?.type === 'start' && key === 'variables' && config.type === 'define') { return (
{t(`workflow.config.${selectedNode?.data?.type}.${key}`)}
{Array.isArray(config.defaultValue) && config.defaultValue?.map((vo, index) =>
{vo.name}ยท{vo.description}
{vo.required && {t('workflow.config.start.required')}} {vo.type}
handleEditVariable(index, vo)} >
handleDeleteVariable(index, vo)} >
)} {config.sys?.map((vo, index) =>
sys.{vo.name}
{vo.type}
)}
) } if (selectedNode?.data?.type === 'llm' && key === 'messages' && config.type === 'define') { return ( ) } if (selectedNode?.data?.type === 'end' && key === 'output') { return ( ) } if (config.type === 'define') { return null } if (config.type === 'knowledge') { return ( ) } if (config.type === 'messageEditor') { return ( ) } if (config.type === 'paramList') { return ( ) } if (config.type === 'groupVariableList') { return ( ) } if (config.type === 'caseList') { console.log('key', key) return ( ) } if (config.type === 'mappingList') { return ( ) } return ( {config.type === 'input' ? : config.type === 'textarea' ? : config.type === 'select' ?