diff --git a/web/src/views/KnowledgeBase/[knowledgeBaseId]/CreateDataset.tsx b/web/src/views/KnowledgeBase/[knowledgeBaseId]/CreateDataset.tsx index 1ec66004..7d32796d 100644 --- a/web/src/views/KnowledgeBase/[knowledgeBaseId]/CreateDataset.tsx +++ b/web/src/views/KnowledgeBase/[knowledgeBaseId]/CreateDataset.tsx @@ -94,7 +94,7 @@ const CreateDataset = () => { const [processingMethod, setProcessingMethod] = useState('directBlock'); const [parameterSettings, setParameterSettings] = useState('defaultSettings'); const [pdfEnhancementEnabled, setPdfEnhancementEnabled] = useState(true); - const [pdfEnhancementMethod, setPdfEnhancementMethod] = useState('deepdoc'); + const [pdfEnhancementMethod, setPdfEnhancementMethod] = useState('mineru'); const fileType = ['pdf', 'doc', 'docx', 'xls', 'xlsx', 'csv', 'md', 'htm', 'html', 'json', 'ppt', 'pptx', 'txt','png','jpg','mp3','mp4','mov','wav'] const steps = useMemo( () => [ diff --git a/web/src/views/Workflow/components/Editor/plugin/CommandPlugin.tsx b/web/src/views/Workflow/components/Editor/plugin/CommandPlugin.tsx index d929f18c..6d31fc24 100644 --- a/web/src/views/Workflow/components/Editor/plugin/CommandPlugin.tsx +++ b/web/src/views/Workflow/components/Editor/plugin/CommandPlugin.tsx @@ -50,7 +50,7 @@ const CommandPlugin = () => { // Create and insert the variable node const tagNode = $createVariableNode(payload.data); - const spaceNode = $createTextNode(' '); + const spaceNode = $createTextNode(''); anchorNode.insertAfter(tagNode); tagNode.insertAfter(spaceNode); diff --git a/web/src/views/Workflow/components/PortClickHandler.tsx b/web/src/views/Workflow/components/PortClickHandler.tsx index 31693722..7e2350ee 100644 --- a/web/src/views/Workflow/components/PortClickHandler.tsx +++ b/web/src/views/Workflow/components/PortClickHandler.tsx @@ -34,9 +34,12 @@ const PortClickHandler: React.FC = ({ graph }) => { }; window.addEventListener('port:click', handlePortClick as EventListener); + const handleBlankClick = () => handlePopoverClose(); + window.addEventListener('blank:click', handleBlankClick); return () => { window.removeEventListener('port:click', handlePortClick as EventListener); + window.removeEventListener('blank:click', handleBlankClick); }; }, []); diff --git a/web/src/views/Workflow/hooks/useWorkflowGraph.ts b/web/src/views/Workflow/hooks/useWorkflowGraph.ts index 45400362..4c2ab44e 100644 --- a/web/src/views/Workflow/hooks/useWorkflowGraph.ts +++ b/web/src/views/Workflow/hooks/useWorkflowGraph.ts @@ -94,10 +94,10 @@ export const useWorkflowGraph = ({ const { message } = App.useApp(); const { t } = useTranslation() const { user } = useUser(); - + // Refs const graphRef = useRef(); - + // State const [selectedNode, setSelectedNode] = useState(null); const [zoomLevel, setZoomLevel] = useState(1); @@ -134,7 +134,7 @@ export const useWorkflowGraph = ({ useEffect(() => { initWorkflow() }, [config, graphRef.current]) - + /** * Initialize workflow graph with nodes and edges from configuration */ @@ -211,7 +211,7 @@ export const useWorkflowGraph = ({ id, type, name, - data: { ...node, ...nodeLibraryConfig}, + data: { ...node, ...nodeLibraryConfig }, ...position, } @@ -221,11 +221,11 @@ export const useWorkflowGraph = ({ if (w) nodeConfig.width = w as number; if (h) nodeConfig.height = h as number; } - + // Generate ports dynamically for if-else node based on cases if (type === 'if-else' && config.cases && Array.isArray(config.cases)) { const totalPorts = config.cases.length + 1; // IF/ELIF + ELSE - + const portItems: PortMetadata[] = [ defaultPortItems[0], ]; @@ -240,24 +240,24 @@ export const useWorkflowGraph = ({ }, }); } - + nodeConfig.ports = { groups: defaultAbsolutePortGroups, items: portItems }; - + nodeConfig.height = calcConditionNodeTotalHeight(config.cases); } - + // Generate ports dynamically for question-classifier node based on categories if (type === 'question-classifier' && config.categories && Array.isArray(config.categories)) { const categoryCount = config.categories.length; const newHeight = conditionNodeHeight + (categoryCount - 2) * conditionNodeItemHeight; - + const portItems: PortMetadata[] = [ defaultPortItems[0] ]; - + // Add category ports config.categories.forEach((_category: any, index: number) => { portItems.push({ @@ -269,15 +269,15 @@ export const useWorkflowGraph = ({ }, }); }); - + nodeConfig.ports = { groups: defaultAbsolutePortGroups, items: portItems }; - + nodeConfig.height = newHeight; } - + // Check error_handle.method config for http-request node if (type === 'http-request' && (nodeConfig as any).error_handle?.method === 'branch') { nodeConfig.ports = { @@ -294,21 +294,22 @@ export const useWorkflowGraph = ({ x: nodeWidth, y: portItemArgsY + portItemArgsY, }, - id: 'ERROR', attrs: { text: { text: t('workflow.config.http-request.errorBranch'), ...portTextAttrs }}} + id: 'ERROR', attrs: { text: { text: t('workflow.config.http-request.errorBranch'), ...portTextAttrs } } + } ] }; } - + return nodeConfig }) - + // Separate parent nodes and child nodes const parentNodes = nodeList.filter(node => !node.data.cycle) const childNodes = nodeList.filter(node => node.data.cycle) - + // Add parent nodes first graphRef.current?.addNodes(parentNodes) - + // Then process child nodes, use addChild to add to corresponding parent node childNodes.forEach(childNode => { const cycleId = childNode.data.cycle @@ -322,14 +323,14 @@ export const useWorkflowGraph = ({ } } }) - + // Adjust parent node size to fit child nodes setTimeout(() => { const parentNodesWithChildren = parentNodes.filter(parentNode => { const parentId = parentNode.data.id return childNodes.some(child => child.data.cycle === parentId) }) - + parentNodesWithChildren.forEach(parentNodeConfig => { const parentNode = graphRef.current?.getCellById(parentNodeConfig.data.id) if (parentNode) { @@ -340,18 +341,18 @@ export const useWorkflowGraph = ({ const minY = Math.min(...childBounds.map(b => b.y)) const maxX = Math.max(...childBounds.map(b => b.x + b.width)) const maxY = Math.max(...childBounds.map(b => b.y + b.height)) - + const padding = 24 const headerHeight = 50 const parentBBox = parentNode.getBBox() - + const newWidth = Math.max(parentBBox.width, maxX - minX + padding * 2) const newHeight = Math.max(parentBBox.height, maxY - minY + padding * 2 + headerHeight) console.log('newWidth', newHeight, newWidth) - + parentNode.prop('size', { width: newWidth, height: newHeight }) - + // Update x position of right group ports const ports = (parentNode as Node).getPorts() ports.forEach(port => { @@ -371,7 +372,7 @@ export const useWorkflowGraph = ({ const sourceCell = graphRef.current?.getCellById(e.source); const sourceType = sourceCell?.getData()?.type; const isMultiPortNode = sourceType === 'question-classifier' || sourceType === 'if-else'; - + if (isMultiPortNode) { // Multi-port nodes need to compare source, target and label return e.source === edge.source && e.target === edge.target && e.label === edge.label; @@ -381,18 +382,18 @@ export const useWorkflowGraph = ({ } }) === index; }); - + const edgeList = uniqueEdges.map(edge => { const { source, target, label } = edge const sourceCell = graphRef.current?.getCellById(source) const targetCell = graphRef.current?.getCellById(target) - + if (sourceCell && targetCell) { const sourcePorts = (sourceCell as Node).getPorts() const targetPorts = (targetCell as Node).getPorts() - + let sourcePort = sourcePorts.find((port: any) => port.group === 'right')?.id || 'right'; - + // If if-else node has label, match corresponding port by label if (sourceCell.getData()?.type === 'if-else' && label) { // Find matching port ID @@ -401,7 +402,7 @@ export const useWorkflowGraph = ({ sourcePort = label; } } - + // If question-classifier node has label, match corresponding port by label if (sourceCell.getData()?.type === 'question-classifier' && label) { const matchingPort = sourcePorts.find((port: any) => port.id === label); @@ -409,7 +410,7 @@ export const useWorkflowGraph = ({ sourcePort = label; } } - + // If http-request node has label, match corresponding port by label if (sourceCell.getData()?.type === 'http-request' && label) { const matchingPort = sourcePorts.find((port: any) => port.id === label); @@ -417,7 +418,7 @@ export const useWorkflowGraph = ({ sourcePort = label; } } - + const edgeConfig = { source: { cell: sourceCell.id, @@ -438,15 +439,23 @@ export const useWorkflowGraph = ({ }) graphRef.current.addEdges(edgeList.filter(vo => vo !== null)) } - + // Initialize after completion, display nodes in visible area if (nodes.length > 0 || edges.length > 0) { setTimeout(() => { if (graphRef.current) { graphRef.current.centerContent() - // graphRef.current.getNodes().forEach(node => node.toFront()); + graphRef.current.getNodes().forEach(node => { + if (!node.getData()?.cycle) node.toFront(); + }); // Bring edges to front first, then child nodes above edges; parent nodes stay behind - graphRef.current.getEdges().forEach(edge => edge.toFront()); + graphRef.current.getEdges().forEach(edge => { + const sourceCell = graphRef.current?.getCellById(edge.getSourceCellId()); + const targetCell = graphRef.current?.getCellById(edge.getTargetCellId()); + if (sourceCell?.getData()?.cycle || targetCell?.getData()?.cycle) { + edge.toFront(); + } + }); graphRef.current.getNodes().forEach(node => { if (node.getData()?.cycle) node.toFront(); }); @@ -575,6 +584,7 @@ export const useWorkflowGraph = ({ clearEdgeSelect(); graphRef.current?.cleanSelection(); setSelectedNode(null); + window.dispatchEvent(new CustomEvent('blank:click')); }; /** * Handle canvas scale/zoom event @@ -595,26 +605,26 @@ export const useWorkflowGraph = ({ // Get parent node and child node bounding boxes const parentBBox = parentNode.getBBox(); const childBBox = node.getBBox(); - + // Calculate parent node padding const padding = 24; const headerHeight = 50; - + // Calculate minimum and maximum positions allowed for child node const minX = parentBBox.x + padding; const minY = parentBBox.y + padding + headerHeight; const maxX = parentBBox.x + parentBBox.width - padding - childBBox.width; const maxY = parentBBox.y + parentBBox.height - padding - childBBox.height; - + // Restrict child node movement within parent node let newX = childBBox.x; let newY = childBBox.y; - + if (newX < minX) newX = minX; if (newY < minY) newY = minY; if (newX > maxX) newX = maxX; if (newY > maxY) newY = maxY; - + // If child node position is restricted, update its position if (newX !== childBBox.x || newY !== childBBox.y) { node.setPosition(newX, newY); @@ -706,7 +716,7 @@ export const useWorkflowGraph = ({ } // Add child node to deletion list cells.push(nodeToDelete); - } + } // Check if it's LoopNode, IterationNode or SubGraphNode else if (nodeToDelete.shape === 'loop-node' || nodeToDelete.shape === 'iteration-node' || nodeToDelete.shape === 'subgraph-node') { // Find all child nodes with cycle equal to current node id @@ -718,7 +728,7 @@ export const useWorkflowGraph = ({ }); // Add parent node to deletion list cells.push(nodeToDelete); - } + } // Normal node else { cells.push(nodeToDelete); @@ -726,7 +736,7 @@ export const useWorkflowGraph = ({ }); blankClick(); } - + // Delete all collected nodes and edges if (cells.length > 0) { graphRef.current?.removeCells(cells); @@ -873,19 +883,19 @@ export const useWorkflowGraph = ({ const targetGroup = targetMagnet ? getPortGroup(targetMagnet) : null; if (sourceGroup === 'left' || targetGroup === 'right') return false; - + // Node cannot connect to itself if (sourceCell?.id === targetCell?.id) return false; const targetType = targetCell?.getData()?.type; - + // Start node cannot be connection target if (targetType === 'start') return false; - + // Get source node and target node parent IDs const sourceParentId = sourceCell?.getData()?.cycle; const targetParentId = targetCell?.getData()?.cycle; - + // Validate parent-child relationship: // 1. If both nodes have parent IDs, they must be same to connect // 2. If both have no parent ID, can connect normally @@ -975,7 +985,6 @@ export const useWorkflowGraph = ({ if (!port) return; const portData = node.getPort(port); if (portData?.group !== 'right') return; - node.toFront(); node.setPortProp(port, 'attrs/body/opacity', 0); node.setPortProp(port, 'attrs/hoverBody/opacity', 1); node.setPortProp(port, 'attrs/label/opacity', 1); @@ -1028,33 +1037,28 @@ export const useWorkflowGraph = ({ graphRef.current.on('scale', scaleEvent); // Listen to node move event graphRef.current.on('node:moved', nodeMoved); - // When parent (isGroup) node position changes, move children with it - graphRef.current.on('node:change:position', ({ node, current, previous }: { node: Node; current: { x: number; y: number }; previous: { x: number; y: number } }) => { - - if (!(node.getData()?.type === 'iteration' && node.getData()?.type === 'loop') || !current || !previous) return; - const dx = current.x - previous.x; - const dy = current.y - previous.y; - const parentId = node.getData()?.id || node.id; - graphRef.current?.getNodes().forEach(child => { - if (child.getData()?.cycle === parentId) { - const cp = child.getPosition(); - child.setPosition(cp.x + dx, cp.y + dy, { silent: true }); - } - }); - }); graphRef.current.on('node:removed', blankClick) // When edge connected, bring connected nodes' ports to front - graphRef.current.on('edge:connected', ({ isNew, edge }) => { + graphRef.current.on('edge:connected', ({ isNew }) => { // Bring edge to front first, then bring child nodes above edges // Parent (loop/iteration) nodes stay behind to avoid covering edges - edge.toFront(); - graphRef.current?.getNodes().forEach(node => { - if (node.getData()?.cycle) node.toFront(); - }); // Reset any port hover state left from dragging if (isNew) { graphRef.current?.getNodes().forEach(node => { + if (!node.getData()?.cycle) node.toFront(); + }); + graphRef.current?.getEdges().forEach(edge => { + const sourceCell = graphRef.current?.getCellById(edge.getSourceCellId()); + const targetCell = graphRef.current?.getCellById(edge.getTargetCellId()); + if (sourceCell?.getData()?.cycle || targetCell?.getData()?.cycle) { + edge.toFront(); + } + }); + graphRef.current?.getNodes().forEach(node => { + graphRef.current?.getNodes().forEach(node => { + if (node.getData()?.cycle) node.toFront(); + }); node.getPorts().filter(p => p.group === 'right').forEach(p => { node.setPortProp(p.id!, 'attrs/body/opacity', 1); node.setPortProp(p.id!, 'attrs/hoverBody/opacity', 0); @@ -1100,7 +1104,6 @@ export const useWorkflowGraph = ({ // Enter new if (found) { const { node, portId } = found; - node.toFront(); node.setPortProp(portId, 'attrs/body/opacity', 0); node.setPortProp(portId, 'attrs/hoverBody/opacity', 1); node.setPortProp(portId, 'attrs/label/opacity', 1); @@ -1173,7 +1176,7 @@ export const useWorkflowGraph = ({ if (!graph) return; const point = graphRef.current.clientToLocal(event.clientX, event.clientY); - + // Get original config from node library to avoid config data chaining let nodeLibraryConfig = [...nodeLibrary] .flatMap(category => category.nodes) @@ -1326,11 +1329,11 @@ export const useWorkflowGraph = ({ const sourcePortId = edge.getSourcePortId(); // Filter invalid edges: source or target node doesn't exist, or is add-node type - if (!sourceCell?.getData()?.id || !targetCell?.getData()?.id || - sourceCell?.getData()?.type === 'add-node' || targetCell?.getData()?.type === 'add-node') { + if (!sourceCell?.getData()?.id || !targetCell?.getData()?.id || + sourceCell?.getData()?.type === 'add-node' || targetCell?.getData()?.type === 'add-node') { return null; } - + // If if-else node right port connection, add label if (sourceCell?.getData()?.type === 'if-else' && sourcePortId?.startsWith('CASE')) { return { @@ -1339,7 +1342,7 @@ export const useWorkflowGraph = ({ label: sourcePortId, }; } - + // If question-classifier node right port connection, add label if (sourceCell?.getData()?.type === 'question-classifier' && sourcePortId?.startsWith('CASE')) { return { @@ -1348,7 +1351,7 @@ export const useWorkflowGraph = ({ label: sourcePortId, }; } - + // If http-request node right port connection, add label if (sourceCell?.getData()?.type === 'http-request') { if (sourcePortId === 'ERROR') { @@ -1365,40 +1368,40 @@ export const useWorkflowGraph = ({ }; } } - + return { source: sourceCell?.getData().id, target: targetCell?.getData().id, }; }) - .filter(edge => edge !== null) - .filter((edge, index, arr) => { - // Deduplication: For if-else and question-classifier nodes, different ports can connect to same node - return arr.findIndex(e => { - if (!e || !edge) return false; - const sourceCell = graphRef.current?.getCellById(e.source); - const sourceType = sourceCell?.getData()?.type; - const isMultiPortNode = sourceType === 'question-classifier' || sourceType === 'if-else'; - - if (isMultiPortNode) { - // Multi-port nodes need to compare source, target and label - return e.source === edge.source && e.target === edge.target && e.label === edge.label; - } else { - // Other nodes only compare source and target - return e.source === edge.source && e.target === edge.target; - } - }) === index; - }), + .filter(edge => edge !== null) + .filter((edge, index, arr) => { + // Deduplication: For if-else and question-classifier nodes, different ports can connect to same node + return arr.findIndex(e => { + if (!e || !edge) return false; + const sourceCell = graphRef.current?.getCellById(e.source); + const sourceType = sourceCell?.getData()?.type; + const isMultiPortNode = sourceType === 'question-classifier' || sourceType === 'if-else'; + + if (isMultiPortNode) { + // Multi-port nodes need to compare source, target and label + return e.source === edge.source && e.target === edge.target && e.label === edge.label; + } else { + // Other nodes only compare source and target + return e.source === edge.source && e.target === edge.target; + } + }) === index; + }), } saveWorkflowConfig(config.app_id, params as WorkflowConfig) - .then((res) => { - if (flag) { - message.success({ content: t('common.saveSuccess'), duration: 1 }) - } - resolve(res) - }).catch(error => { - reject(error) - }) + .then((res) => { + if (flag) { + message.success({ content: t('common.saveSuccess'), duration: 1 }) + } + resolve(res) + }).catch(error => { + reject(error) + }) }) }