feat(web): 完成分支节点动态改变handle

This commit is contained in:
v-zhangjc9
2025-07-16 19:54:48 +08:00
parent 91e6f49342
commit a3c2250285
11 changed files with 138 additions and 105 deletions

View File

@@ -7,6 +7,7 @@ import {useNavigate} from 'react-router'
import styled from 'styled-components'
import '@xyflow/react/dist/style.css'
import {commonInfo} from '../../util/amis.tsx'
import AddNodeButton from './component/AddNodeButton.tsx'
import {checkAddConnection, checkSave} from './FlowChecker.tsx'
import {useNodeDrag} from './Helper.tsx'
import {NodeRegistryMap} from './NodeRegistry.tsx'
@@ -14,7 +15,6 @@ import {useContextStore} from './store/ContextStore.ts'
import {useDataStore} from './store/DataStore.ts'
import {useFlowStore} from './store/FlowStore.ts'
import {flowDotColor, type FlowEditorProps} from './types.ts'
import AddNodeButton from './component/AddNodeButton.tsx'
const FlowableDiv = styled.div`
.react-flow__node.selectable {
@@ -48,10 +48,9 @@ function FlowEditor(props: FlowEditorProps) {
const navigate = useNavigate()
const [messageApi, contextHolder] = message.useMessage()
const {data, setData, setDataById} = useDataStore()
const {data, setData} = useDataStore()
const {
nodes,
addNode,
setNodes,
onNodesChange,
edges,
@@ -109,6 +108,7 @@ function FlowEditor(props: FlowEditorProps) {
onNodeDragStart={onNodeDragStart}
onNodeDrag={onNodeDrag}
onNodeDragStop={onNodeDragEnd}
onEdgesDelete={() => console.info('delete')}
>
<Panel position="top-right">
<Space className="toolbar">

View File

@@ -88,7 +88,7 @@ type AmisNodeProps = {
nodeProps: NodeProps
extraNodeDescription?: JSX.Element
handler: JSX.Element
columnSchema?: () => Schema[]
columnSchema?: Schema[]
resize?: { minWidth: number, minHeight: number }
}
@@ -183,7 +183,7 @@ const AmisNode: (props: AmisNodeProps) => JSX.Element = ({
{
type: 'divider',
},
...(columnSchema?.() ?? []),
...(columnSchema ?? []),
{
type: 'wrapper',
size: 'none',
@@ -221,7 +221,7 @@ const AmisNode: (props: AmisNodeProps) => JSX.Element = ({
),
)
setEditDrawerOpen(true)
}, [nodeData])
}, [id])
const onRemoveClick = useCallback(() => {
removeNode(id)
removeDataById(id)

View File

@@ -1,6 +1,6 @@
import type {NodeProps} from '@xyflow/react'
import {Tag} from 'antd'
import React, {useCallback} from 'react'
import React, {useMemo} from 'react'
import {useContextStore} from '../store/ContextStore.ts'
import {useDataStore} from '../store/DataStore.ts'
import {useFlowStore} from '../store/FlowStore.ts'
@@ -19,7 +19,7 @@ const CodeNode = (props: NodeProps) => {
const nodeData = getDataById(props.id)
const columnsSchema = useCallback(() => [
const columnsSchema = useMemo(() => [
...inputsFormColumns(props.id, getInputSchema(), getNodes(), getEdges(), getData()),
{
type: 'divider',
@@ -47,18 +47,21 @@ const CodeNode = (props: NodeProps) => {
},
...outputsFormColumns(true, true),
], [props.id])
const extraNodeDescription = useMemo(() => {
return nodeData?.type
? <div className="mt-2 flex justify-between">
<span></span>
<Tag className="m-0" color="blue">{languageMap[nodeData.type]}</Tag>
</div>
: <></>
}, [nodeData])
return (
<AmisNode
className={nodeClassName('code')}
nodeProps={props}
extraNodeDescription={
nodeData?.type
? <div className="mt-2 flex justify-between">
<span></span>
<Tag className="m-0" color="blue">{languageMap[nodeData.type]}</Tag>
</div>
: <></>
}
extraNodeDescription={extraNodeDescription}
columnSchema={columnsSchema}
handler={<NormalNodeHandler/>}
/>

View File

@@ -1,5 +1,5 @@
import type {NodeProps} from '@xyflow/react'
import React, {useCallback, useEffect} from 'react'
import React, {useEffect, useMemo} from 'react'
import {commonInfo} from '../../../util/amis.tsx'
import {useContextStore} from '../store/ContextStore.ts'
import {useDataStore} from '../store/DataStore.ts'
@@ -24,7 +24,7 @@ const KnowledgeNode = (props: NodeProps) => {
)
}, [props.id])
const columnsSchema = useCallback(() => [
const columnsSchema = useMemo(() => [
...inputsFormColumns(props.id, getInputSchema(), getNodes(), getEdges(), getData()),
{
type: 'divider',

View File

@@ -1,6 +1,6 @@
import type {NodeProps} from '@xyflow/react'
import {Tag} from 'antd'
import React, {useCallback, useEffect} from 'react'
import React, {useEffect, useMemo} from 'react'
import {useContextStore} from '../store/ContextStore.ts'
import {useDataStore} from '../store/DataStore.ts'
import {useFlowStore} from '../store/FlowStore.ts'
@@ -31,7 +31,7 @@ const LlmNode = (props: NodeProps) => {
)
}, [props.id])
const columnsSchema = useCallback(() => [
const columnsSchema = useMemo(() => [
...inputsFormColumns(props.id, getInputSchema(), getNodes(), getEdges(), getData()),
{
type: 'divider',
@@ -55,18 +55,21 @@ const LlmNode = (props: NodeProps) => {
},
...outputsFormColumns(false, true),
], [props.id])
const extraNodeDescription = useMemo(() => {
return nodeData?.model
? <div className="mt-2 flex justify-between">
<span></span>
<Tag className="m-0" color="blue">{modelMap[nodeData.model]}</Tag>
</div>
: <></>
}, [nodeData])
return (
<AmisNode
className={nodeClassName('llm')}
nodeProps={props}
extraNodeDescription={
nodeData?.model
? <div className="mt-2 flex justify-between">
<span></span>
<Tag className="m-0" color="blue">{modelMap[nodeData.model]}</Tag>
</div>
: <></>
}
extraNodeDescription={extraNodeDescription}
columnSchema={columnsSchema}
handler={<NormalNodeHandler/>}
/>

View File

@@ -1,6 +1,6 @@
import {Background, BackgroundVariant, type NodeProps} from '@xyflow/react'
import {classnames} from 'amis'
import React, {useCallback, useEffect} from 'react'
import React, {useEffect, useMemo} from 'react'
import AddNodeButton from '../component/AddNodeButton.tsx'
import {useDataStore} from '../store/DataStore.ts'
import {flowBackgroundColor, flowDotColor} from '../types.ts'
@@ -22,7 +22,7 @@ const LoopNode = (props: NodeProps) => {
)
}, [props.id])
const columnsSchema = useCallback(() => [
const columnsSchema = useMemo(() => [
{
type: 'switch',
name: 'failFast',
@@ -41,6 +41,26 @@ const LoopNode = (props: NodeProps) => {
...outputsFormColumns(false, true),
], [props.id])
const extraNodeDescription = useMemo(() => {
return (
<div className="nodrag relative w-full h-full" style={{minHeight: '211px'}}>
<Background
id={`loop-background-${props.id}`}
className="rounded-xl"
variant={BackgroundVariant.Cross}
gap={20}
size={3}
style={{
zIndex: 0,
}}
color={flowDotColor}
bgColor={flowBackgroundColor}
/>
<AddNodeButton className="mt-2 ml-2" parent={props.id} onlyIcon/>
</div>
)
}, [props.id])
return (
<AmisNode
className={classnames('w-full', 'h-full', nodeClassName('loop'))}
@@ -49,23 +69,7 @@ const LoopNode = (props: NodeProps) => {
minHeight: '290px',
}}
nodeProps={props}
extraNodeDescription={
<div className="nodrag relative w-full h-full" style={{minHeight: '211px'}}>
<Background
id={`loop-background-${props.id}`}
className="rounded-xl"
variant={BackgroundVariant.Cross}
gap={20}
size={3}
style={{
zIndex: 0,
}}
color={flowDotColor}
bgColor={flowBackgroundColor}
/>
<AddNodeButton className="mt-2 ml-2" parent={props.id} onlyIcon/>
</div>
}
extraNodeDescription={extraNodeDescription}
columnSchema={columnsSchema}
handler={<NormalNodeHandler/>}
resize={{

View File

@@ -1,5 +1,5 @@
import type {NodeProps} from '@xyflow/react'
import React, {useCallback} from 'react'
import React, {useMemo} from 'react'
import {generateAllIncomerOutputVariablesFormOptions} from '../Helper.tsx'
import {useContextStore} from '../store/ContextStore.ts'
import {useDataStore} from '../store/DataStore.ts'
@@ -11,7 +11,7 @@ const OutputNode = (props: NodeProps) => {
const {getData} = useDataStore()
const {getInputSchema} = useContextStore()
const columnsSchema = useCallback(
const columnsSchema = useMemo(
() => [
{
type: 'select',

View File

@@ -1,30 +1,24 @@
import {Handle, type NodeProps, Position} from '@xyflow/react'
import type {ConditionValue} from 'amis'
import {Tag} from 'antd'
import React, {useCallback} from 'react'
import {contain, isEqual} from 'licia'
import React, {useMemo} from 'react'
import {generateAllIncomerOutputVariablesConditions} from '../Helper.tsx'
import {useContextStore} from '../store/ContextStore.ts'
import {useDataStore} from '../store/DataStore.ts'
import {useFlowStore} from '../store/FlowStore.ts'
import AmisNode, {nodeClassName} from './AmisNode.tsx'
const cases = [
{
index: 1,
},
{
index: 2,
},
{
index: 3,
},
]
const SwitchNode = (props: NodeProps) => {
const {getNodes, getEdges} = useFlowStore()
const {getData} = useDataStore()
const {getNodes, getEdges, removeEdges} = useFlowStore()
const {getData, getDataById} = useDataStore()
const {getInputSchema} = useContextStore()
const columnsSchema = useCallback(() => [
const nodeData = getDataById(props.id)
// @ts-ignore
const conditions: ConditionValue[] = nodeData?.conditions?.map(c => c.condition) ?? []
const columnsSchema = useMemo(() => [
{
type: 'combo',
name: 'conditions',
@@ -45,38 +39,55 @@ const SwitchNode = (props: NodeProps) => {
getData(),
),
},
]
}
],
},
], [props.id])
const extraNodeDescription = useMemo(() => {
return (
<div className="mt-2">
{conditions.map((item, index) => (
<div key={item.id} className="mt-1">
<Tag className="m-0" color="blue"> {index + 1}</Tag>
</div>
))}
</div>
)
}, [nodeData])
const handler = useMemo(() => {
// @ts-ignore
const conditions: ConditionValue[] = nodeData?.conditions?.map(c => c.condition) ?? []
// 移除不该存在的边
const conditionIds = conditions.map(c => c.id)
const removeEdgeIds = getEdges()
.filter(edge => isEqual(edge.source, props.id) && !contain(conditionIds, edge.sourceHandle))
.map(edge => edge.id)
removeEdges(removeEdgeIds)
return (
<>
<Handle type="target" position={Position.Left}/>
{conditions.map((item, index) => (
<Handle
type="source"
position={Position.Right}
key={item.id}
id={item.id}
style={{top: 91 + (26 * index)}}
/>
))}
</>
)
}, [nodeData])
return (
<AmisNode
className={nodeClassName('switch')}
nodeProps={props}
extraNodeDescription={
<div className="mt-2">
{cases.map(item => (
<div key={item.index} className="mt-1">
<Tag className="m-0" color="blue"> {item.index}</Tag>
</div>
))}
</div>
}
extraNodeDescription={extraNodeDescription}
columnSchema={columnsSchema}
handler={
<>
<Handle type="target" position={Position.Left}/>
{cases.map((item, index) => (
<Handle
type="source"
position={Position.Right}
key={item.index}
id={`${item.index}`}
style={{top: 85 + (25 * index)}}
/>
))}
</>
}
handler={handler}
/>
)
}

View File

@@ -1,6 +1,6 @@
import type {NodeProps} from '@xyflow/react'
import {Tag} from 'antd'
import React, {useCallback, useEffect} from 'react'
import React, {useEffect, useMemo} from 'react'
import {useContextStore} from '../store/ContextStore.ts'
import {useDataStore} from '../store/DataStore.ts'
import {useFlowStore} from '../store/FlowStore.ts'
@@ -33,7 +33,7 @@ const TemplateNode = (props: NodeProps) => {
)
}, [props.id])
const columnsSchema = useCallback(
const columnsSchema = useMemo(
() => [
...inputsFormColumns(props.id, getInputSchema(), getNodes(), getEdges(), getData()),
{
@@ -69,22 +69,22 @@ const TemplateNode = (props: NodeProps) => {
},
},
...outputsFormColumns(false, true),
],
[props.id],
)
], [props.id])
const extraNodeDescription = useMemo(() => {
return nodeData?.type
? <div className="mt-2 flex justify-between">
<span></span>
<Tag className="m-0" color="blue">{typeMap[nodeData.type]}</Tag>
</div>
: <></>
}, [nodeData])
return (
<AmisNode
className={nodeClassName('template')}
nodeProps={props}
extraNodeDescription={
nodeData?.type
? <div className="mt-2 flex justify-between">
<span></span>
<Tag className="m-0" color="blue">{typeMap[nodeData.type]}</Tag>
</div>
: <></>
}
extraNodeDescription={extraNodeDescription}
columnSchema={columnsSchema}
handler={<NormalNodeHandler/>}
/>

View File

@@ -8,7 +8,7 @@ import {
type OnEdgesChange,
type OnNodesChange,
} from '@xyflow/react'
import {filter, find, isEqual} from 'licia'
import {contain, filter, find, isEqual} from 'licia'
import {create} from 'zustand/react'
export const useFlowStore = create<{
@@ -24,6 +24,8 @@ export const useFlowStore = create<{
edges: Edge[],
getEdges: () => Edge[],
onEdgesChange: OnEdgesChange,
removeEdge: (id: string) => void,
removeEdges: (ids: string[]) => void,
setEdges: (edges: Edge[]) => void,
onConnect: OnConnect,
@@ -61,6 +63,16 @@ export const useFlowStore = create<{
edges: applyEdgeChanges(changes, get().edges),
})
},
removeEdge: id => {
set({
edges: filter(get().edges, edge => !isEqual(edge.id, id)),
})
},
removeEdges: ids => {
set({
edges: filter(get().edges, edge => !contain(ids, edge.id)),
})
},
setEdges: edges => set({edges}),
onConnect: connection => {