feat(web): 尝试增加并行节点解决解析问题
This commit is contained in:
@@ -4,6 +4,7 @@ import {
|
||||
BackgroundVariant,
|
||||
type Connection,
|
||||
Controls,
|
||||
getIncomers,
|
||||
getOutgoers,
|
||||
MiniMap,
|
||||
type Node,
|
||||
@@ -17,11 +18,13 @@ import {arrToMap, find, findIdx, isEqual, isNil, randomId} from 'licia'
|
||||
import {type JSX, useState} from 'react'
|
||||
import styled from 'styled-components'
|
||||
import '@xyflow/react/dist/style.css'
|
||||
import Queue from 'yocto-queue'
|
||||
import {amisRender, commonInfo, horizontalFormOptions} from '../../../util/amis.tsx'
|
||||
import CodeNode from './node/CodeNode.tsx'
|
||||
import EndNode from './node/EndNode.tsx'
|
||||
import KnowledgeNode from './node/KnowledgeNode.tsx'
|
||||
import LlmNode from './node/LlmNode.tsx'
|
||||
import ParallelNode from './node/ParallelNode.tsx'
|
||||
import StartNode from './node/StartNode.tsx'
|
||||
import {useDataStore} from './store/DataStore.ts'
|
||||
import {useFlowStore} from './store/FlowStore.ts'
|
||||
@@ -71,27 +74,32 @@ function FlowEditor() {
|
||||
component: (props: NodeProps) => JSX.Element
|
||||
}[]>([
|
||||
{
|
||||
key: 'start-amis-node',
|
||||
key: 'start-node',
|
||||
name: '开始',
|
||||
component: StartNode,
|
||||
},
|
||||
{
|
||||
key: 'end-amis-node',
|
||||
key: 'end-node',
|
||||
name: '结束',
|
||||
component: EndNode,
|
||||
},
|
||||
{
|
||||
key: 'llm-amis-node',
|
||||
key: 'parallel-node',
|
||||
name: '并行',
|
||||
component: ParallelNode,
|
||||
},
|
||||
{
|
||||
key: 'llm-node',
|
||||
name: '大模型',
|
||||
component: LlmNode,
|
||||
},
|
||||
{
|
||||
key: 'knowledge-amis-node',
|
||||
key: 'knowledge-node',
|
||||
name: '知识库',
|
||||
component: KnowledgeNode,
|
||||
},
|
||||
{
|
||||
key: 'code-amis-node',
|
||||
key: 'code-node',
|
||||
name: '代码执行',
|
||||
component: CodeNode,
|
||||
},
|
||||
@@ -183,10 +191,10 @@ function FlowEditor() {
|
||||
}
|
||||
|
||||
const checkNode = (type: string) => {
|
||||
if (isEqual(type, 'start-amis-node') && findIdx(nodes, (node: Node) => isEqual(type, node.type)) > -1) {
|
||||
if (isEqual(type, 'start-node') && findIdx(nodes, (node: Node) => isEqual(type, node.type)) > -1) {
|
||||
throw new Error('只能存在1个开始节点')
|
||||
}
|
||||
if (isEqual(type, 'end-amis-node') && findIdx(nodes, (node: Node) => isEqual(type, node.type)) > -1) {
|
||||
if (isEqual(type, 'end-node') && findIdx(nodes, (node: Node) => isEqual(type, node.type)) > -1) {
|
||||
throw new Error('只能存在1个结束节点')
|
||||
}
|
||||
}
|
||||
@@ -201,12 +209,12 @@ function FlowEditor() {
|
||||
throw new Error('连线目标节点未找到')
|
||||
}
|
||||
// 禁止短路整个流程
|
||||
if (isEqual('start-amis-node', sourceNode.type) && isEqual('end-amis-node', targetNode.type)) {
|
||||
if (isEqual('start-node', sourceNode.type) && isEqual('end-node', targetNode.type)) {
|
||||
throw new Error('开始节点不能直连结束节点')
|
||||
}
|
||||
|
||||
// 禁止流程出现环,必须是有向无环图
|
||||
const hasCycle = (node: Node, visited = new Set()) => {
|
||||
const hasCycle = (node: Node, visited = new Set<string>()) => {
|
||||
if (visited.has(node.id)) return false
|
||||
visited.add(node.id)
|
||||
for (const outgoer of getOutgoers(node, nodes, edges)) {
|
||||
@@ -219,11 +227,35 @@ function FlowEditor() {
|
||||
} else if (hasCycle(targetNode)) {
|
||||
throw new Error('禁止流程循环')
|
||||
}
|
||||
|
||||
const hasRedundant = (source: Node, target: Node) => {
|
||||
const visited = new Set<string>()
|
||||
const queue = new Queue<Node>()
|
||||
queue.enqueue(source)
|
||||
visited.add(source.id)
|
||||
while (queue.size > 0) {
|
||||
const current = queue.dequeue()!
|
||||
console.log(current.id)
|
||||
for (const incomer of getIncomers(current, nodes, edges)) {
|
||||
if (isEqual(incomer.id, target.id)) {
|
||||
return true
|
||||
}
|
||||
if (!visited.has(incomer.id)) {
|
||||
visited.add(incomer.id)
|
||||
queue.enqueue(incomer)
|
||||
}
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
if (hasRedundant(sourceNode, targetNode)) {
|
||||
throw new Error('出现冗余边')
|
||||
}
|
||||
}
|
||||
|
||||
useMount(() => {
|
||||
// language=JSON
|
||||
let initialData = JSON.parse('{\n "nodes": [],\n "edges": [],\n "data": {}\n}')
|
||||
let initialData = JSON.parse('{\n "nodes": [\n {\n "id": "lurod0PM-J",\n "type": "parallel-node",\n "position": {\n "x": -156,\n "y": 77\n },\n "data": {},\n "measured": {\n "width": 256,\n "height": 75\n },\n "selected": false,\n "dragging": false\n },\n {\n "id": "ldoKAzHnKF",\n "type": "llm-node",\n "position": {\n "x": 207,\n "y": -38\n },\n "data": {},\n "measured": {\n "width": 256,\n "height": 75\n },\n "selected": false,\n "dragging": false\n },\n {\n "id": "1eJtMoJWs6",\n "type": "llm-node",\n "position": {\n "x": 207,\n "y": 172.5\n },\n "data": {},\n "measured": {\n "width": 256,\n "height": 75\n },\n "selected": false,\n "dragging": false\n }\n ],\n "edges": [\n {\n "source": "lurod0PM-J",\n "target": "1eJtMoJWs6",\n "id": "xy-edge__lurod0PM-J-1eJtMoJWs6"\n },\n {\n "source": "lurod0PM-J",\n "target": "ldoKAzHnKF",\n "id": "xy-edge__lurod0PM-J-ldoKAzHnKF"\n }\n ],\n "data": {}\n}')
|
||||
let initialNodes = initialData['nodes'] ?? []
|
||||
let initialEdges = initialData['edges'] ?? []
|
||||
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
import {DeleteFilled, EditFilled} from '@ant-design/icons'
|
||||
import {Handle, type NodeProps, Position} from '@xyflow/react'
|
||||
import {Handle, type HandleProps, type NodeProps, Position, useNodeConnections} from '@xyflow/react'
|
||||
import type {Schema} from 'amis'
|
||||
import {Card, Dropdown} from 'antd'
|
||||
import {isEmpty, isEqual} from 'licia'
|
||||
import {isEmpty, isEqual, isNil} from 'licia'
|
||||
import {type JSX} from 'react'
|
||||
import {horizontalFormOptions} from '../../../../util/amis.tsx'
|
||||
|
||||
@@ -55,13 +55,26 @@ export function outputsFormColumns(editable: boolean = false, required: boolean
|
||||
]
|
||||
}
|
||||
|
||||
interface AmisNodeProps {
|
||||
nodeProps: NodeProps,
|
||||
type: AmisNodeType,
|
||||
defaultNodeName: String,
|
||||
defaultNodeDescription?: String,
|
||||
extraNodeDescription?: (nodeData: any) => JSX.Element,
|
||||
columnSchema?: Schema[],
|
||||
export const LimitHandler = (props: HandleProps & { limit: number }) => {
|
||||
const connections = useNodeConnections({
|
||||
handleType: props.type,
|
||||
})
|
||||
return (
|
||||
<Handle
|
||||
{...props}
|
||||
isConnectable={connections.length < props.limit}
|
||||
/>
|
||||
)
|
||||
}
|
||||
|
||||
type AmisNodeProps = {
|
||||
nodeProps: NodeProps
|
||||
type: AmisNodeType
|
||||
defaultNodeName: String
|
||||
defaultNodeDescription?: String
|
||||
extraNodeDescription?: (nodeData: any) => JSX.Element
|
||||
handlers?: (nodeData: any) => JSX.Element
|
||||
columnSchema?: Schema[]
|
||||
}
|
||||
|
||||
const AmisNode: (props: AmisNodeProps) => JSX.Element = ({
|
||||
@@ -70,6 +83,7 @@ const AmisNode: (props: AmisNodeProps) => JSX.Element = ({
|
||||
defaultNodeName,
|
||||
defaultNodeDescription,
|
||||
extraNodeDescription,
|
||||
handlers,
|
||||
columnSchema,
|
||||
}) => {
|
||||
const {id, data} = nodeProps
|
||||
@@ -142,10 +156,14 @@ const AmisNode: (props: AmisNodeProps) => JSX.Element = ({
|
||||
</div>
|
||||
</Card>
|
||||
</Dropdown>
|
||||
{isEqual(type, 'start') || isEqual(type, 'normal')
|
||||
? <Handle type="source" position={Position.Right}/> : undefined}
|
||||
{isEqual(type, 'end') || isEqual(type, 'normal')
|
||||
? <Handle type="target" position={Position.Left}/> : undefined}
|
||||
{isNil(handlers)
|
||||
? <>
|
||||
{isEqual(type, 'start') || isEqual(type, 'normal')
|
||||
? <LimitHandler type="source" position={Position.Right} limit={1}/> : undefined}
|
||||
{isEqual(type, 'end') || isEqual(type, 'normal')
|
||||
? <Handle type="target" position={Position.Left}/> : undefined}
|
||||
</>
|
||||
: handlers?.(nodeData)}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
19
service-web/client/src/pages/ai/flow/node/ParallelNode.tsx
Normal file
19
service-web/client/src/pages/ai/flow/node/ParallelNode.tsx
Normal file
@@ -0,0 +1,19 @@
|
||||
import {Handle, type NodeProps, Position} from '@xyflow/react'
|
||||
import AmisNode, {LimitHandler} from './AmisNode.tsx'
|
||||
|
||||
const ParallelNode = (props: NodeProps) => AmisNode({
|
||||
nodeProps: props,
|
||||
type: 'normal',
|
||||
defaultNodeName: '并行节点',
|
||||
defaultNodeDescription: '允许开启并行流程',
|
||||
handlers: () => {
|
||||
return (
|
||||
<>
|
||||
<Handle type="source" position={Position.Right}/>
|
||||
<LimitHandler type="target" position={Position.Left} limit={1}/>
|
||||
</>
|
||||
)
|
||||
},
|
||||
})
|
||||
|
||||
export default ParallelNode
|
||||
Reference in New Issue
Block a user