refractor(web): 增加输入节点

This commit is contained in:
2025-07-20 17:17:22 +08:00
parent 77a09472aa
commit a5282762ed
14 changed files with 462 additions and 257 deletions

View File

@@ -20,8 +20,21 @@ export class CheckError extends Error {
const getNodeById = (id: string, nodes: Node[]) => find(nodes, (n: Node) => isEqual(n.id, id)) const getNodeById = (id: string, nodes: Node[]) => find(nodes, (n: Node) => isEqual(n.id, id))
export const typeNotFound = (type: string) => new CheckError(100, `类型 ${type} 不存在`)
export const addNodeError = (message?: string) => new CheckError(101, message ?? '无法添加节点')
// @ts-ignore // @ts-ignore
export const checkAddNode: (type: string, nodes: Node[], edges: Edge[]) => void = (type, nodes, edges) => { export const checkAddNode: (type: string, parentId: string | undefined, nodes: Node[], edges: Edge[]) => void = (type, parentId, nodes, edges) => {
let nodeDefine = NodeRegistryMap[type]
if (!nodeDefine) {
throw typeNotFound(type)
}
for (const checker of nodeDefine.checkers.add) {
let checkResult = checker(type, parentId, {}, nodes, edges, undefined)
if (checkResult.error) {
throw addNodeError(checkResult.message)
}
}
} }
export const sourceNodeNotFoundError = () => new CheckError(200, '连线起始节点未找到') export const sourceNodeNotFoundError = () => new CheckError(200, '连线起始节点未找到')
@@ -70,7 +83,7 @@ export const checkAddConnection: (connection: Connection, nodes: Node[], edges:
export const atLeastOneNode = () => new CheckError(300, '至少包含一个节点') export const atLeastOneNode = () => new CheckError(300, '至少包含一个节点')
export const hasUnfinishedNode = (nodeId: string) => new CheckError(301, `存在尚未配置完成的节点: ${nodeId}`) export const hasUnfinishedNode = (nodeId: string) => new CheckError(301, `存在尚未配置完成的节点: ${nodeId}`)
export const nodeTypeNotFound = () => new CheckError(302, '节点类型不存在') export const nodeTypeNotFound = () => new CheckError(302, '节点类型不存在')
export const nodeError = (nodeId: string, reason?: string) => new CheckError(303, reason ?? `节点配置存在错误:${nodeId}`) export const saveNodeError = (nodeId: string, reason?: string) => new CheckError(303, reason ?? `节点配置存在错误:${nodeId}`)
// @ts-ignore // @ts-ignore
export const checkSave: (inputSchema: Record<string, Record<string, any>>, nodes: Node[], edges: Edge[], data: any) => void = (inputSchema, nodes, edges, data) => { export const checkSave: (inputSchema: Record<string, Record<string, any>>, nodes: Node[], edges: Edge[], data: any) => void = (inputSchema, nodes, edges, data) => {
@@ -79,9 +92,8 @@ export const checkSave: (inputSchema: Record<string, Record<string, any>>, nodes
} }
for (let node of nodes) { for (let node of nodes) {
let nodeId = node.id if (!has(data, node.id) || !data[node.id]?.finished) {
if (!has(data, nodeId) || !data[nodeId]?.finished) { throw hasUnfinishedNode(node.id)
throw hasUnfinishedNode(nodeId)
} }
if (!has(node, 'type')) { if (!has(node, 'type')) {
@@ -89,10 +101,10 @@ export const checkSave: (inputSchema: Record<string, Record<string, any>>, nodes
} }
let nodeType = node.type! let nodeType = node.type!
let nodeDefine = NodeRegistryMap[nodeType] let nodeDefine = NodeRegistryMap[nodeType]
for (let checker of nodeDefine.checkers) { for (let checker of nodeDefine.checkers.save) {
let checkResult = checker(nodeId, inputSchema, nodes, edges, data) let checkResult = checker(node.id, node.parentId, inputSchema, nodes, edges, data)
if (checkResult.error) { if (checkResult.error) {
throw nodeError(nodeId, checkResult.message) throw saveNodeError(node.id, checkResult.message)
} }
} }
} }

View File

@@ -46,7 +46,6 @@ const FlowableDiv = styled.div`
function FlowEditor(props: FlowEditorProps) { function FlowEditor(props: FlowEditorProps) {
const navigate = useNavigate() const navigate = useNavigate()
const [messageApi, contextHolder] = message.useMessage()
const {data, setData} = useDataStore() const {data, setData} = useDataStore()
const { const {
@@ -84,7 +83,6 @@ function FlowEditor(props: FlowEditorProps) {
return ( return (
<FlowableDiv className="h-full w-full"> <FlowableDiv className="h-full w-full">
{contextHolder}
<ReactFlow <ReactFlow
className="rounded-xl" className="rounded-xl"
nodes={nodes} nodes={nodes}
@@ -133,7 +131,7 @@ function FlowEditor(props: FlowEditorProps) {
props.onGraphDataChange({nodes, edges, data}) props.onGraphDataChange({nodes, edges, data})
} catch (e) { } catch (e) {
// @ts-ignore // @ts-ignore
messageApi.error(e.toString()) message.error(e.toString())
} }
}}> }}>
<SaveFilled/> <SaveFilled/>

View File

@@ -1,4 +1,4 @@
import {has, isEmpty} from 'licia' import {has, isEmpty, isEqual} from 'licia'
import {getAllIncomerNodeOutputVariables} from './Helper.tsx' import {getAllIncomerNodeOutputVariables} from './Helper.tsx'
import CodeNode from './node/CodeNode.tsx' import CodeNode from './node/CodeNode.tsx'
import KnowledgeNode from './node/KnowledgeNode.tsx' import KnowledgeNode from './node/KnowledgeNode.tsx'
@@ -7,10 +7,11 @@ import LoopNode from './node/LoopNode.tsx'
import OutputNode from './node/OutputNode.tsx' import OutputNode from './node/OutputNode.tsx'
import SwitchNode from './node/SwitchNode.tsx' import SwitchNode from './node/SwitchNode.tsx'
import TemplateNode from './node/TemplateNode.tsx' import TemplateNode from './node/TemplateNode.tsx'
import type {NodeChecker, NodeDefine} from './types.ts' import type {AddNodeChecker, NodeDefine, SaveNodeChecker} from './types.ts'
import InputNode from './node/InputNode.tsx'
const inputSingleVariableChecker: (field: string) => NodeChecker = field => { const inputSingleVariableChecker: (field: string) => SaveNodeChecker = field => {
return (id, inputSchema, nodes, edges, data) => { return (id, _parentId, inputSchema, nodes, edges, data) => {
let nodeData = data[id] ?? {} let nodeData = data[id] ?? {}
if (has(nodeData, field)) { if (has(nodeData, field)) {
let expression = nodeData?.[field] ?? '' let expression = nodeData?.[field] ?? ''
@@ -28,7 +29,7 @@ const inputSingleVariableChecker: (field: string) => NodeChecker = field => {
} }
} }
const inputMultiVariableChecker: NodeChecker = (id, inputSchema, nodes, edges, data) => { const inputMultiVariableChecker: SaveNodeChecker = (id, _parentId, inputSchema, nodes, edges, data) => {
let nodeData = data[id] ?? {} let nodeData = data[id] ?? {}
if (has(nodeData, 'inputs')) { if (has(nodeData, 'inputs')) {
let inputs = nodeData?.inputs ?? {} let inputs = nodeData?.inputs ?? {}
@@ -48,6 +49,13 @@ const inputMultiVariableChecker: NodeChecker = (id, inputSchema, nodes, edges, d
return {error: false} return {error: false}
} }
const noMoreThanOneNodeType: AddNodeChecker = (type, parentId, _inputSchema, nodes) => {
return {
error: nodes.filter(n => isEqual(n.parentId, parentId) && isEqual(n.type, type)).length > 0,
message: `同一个流程(子流程)中类型为 ${type} 的节点至多有一个`
}
}
export const NodeRegistry: NodeDefine[] = [ export const NodeRegistry: NodeDefine[] = [
{ {
key: 'llm-node', key: 'llm-node',
@@ -56,7 +64,10 @@ export const NodeRegistry: NodeDefine[] = [
icon: <i className="fa fa-message"/>, icon: <i className="fa fa-message"/>,
description: '使用大模型对话', description: '使用大模型对话',
component: LlmNode, component: LlmNode,
checkers: [inputMultiVariableChecker], checkers: {
add: [],
save: [inputMultiVariableChecker]
},
}, },
{ {
key: 'knowledge-node', key: 'knowledge-node',
@@ -65,7 +76,10 @@ export const NodeRegistry: NodeDefine[] = [
icon: <i className="fa fa-book-bookmark"/>, icon: <i className="fa fa-book-bookmark"/>,
description: '', description: '',
component: KnowledgeNode, component: KnowledgeNode,
checkers: [inputMultiVariableChecker], checkers: {
add: [],
save: [inputMultiVariableChecker]
},
}, },
{ {
key: 'code-node', key: 'code-node',
@@ -74,7 +88,10 @@ export const NodeRegistry: NodeDefine[] = [
icon: <i className="fa fa-code"/>, icon: <i className="fa fa-code"/>,
description: '执行自定义的处理代码', description: '执行自定义的处理代码',
component: CodeNode, component: CodeNode,
checkers: [inputMultiVariableChecker], checkers: {
add: [],
save: [inputMultiVariableChecker]
},
}, },
{ {
key: 'template-node', key: 'template-node',
@@ -83,7 +100,10 @@ export const NodeRegistry: NodeDefine[] = [
icon: <i className="fa fa-pen-nib"/>, icon: <i className="fa fa-pen-nib"/>,
description: '使用模板聚合转换变量表示', description: '使用模板聚合转换变量表示',
component: TemplateNode, component: TemplateNode,
checkers: [inputMultiVariableChecker], checkers: {
add: [],
save: [inputMultiVariableChecker]
},
}, },
{ {
key: 'switch-node', key: 'switch-node',
@@ -92,7 +112,10 @@ export const NodeRegistry: NodeDefine[] = [
icon: <i className="fa fa-code-fork"/>, icon: <i className="fa fa-code-fork"/>,
description: '根据不同的情况前往不同的分支', description: '根据不同的情况前往不同的分支',
component: SwitchNode, component: SwitchNode,
checkers: [], checkers: {
add: [],
save: [],
},
}, },
{ {
key: 'loop-node', key: 'loop-node',
@@ -101,16 +124,35 @@ export const NodeRegistry: NodeDefine[] = [
icon: <i className="fa fa-repeat"/>, icon: <i className="fa fa-repeat"/>,
description: '实现循环执行流程', description: '实现循环执行流程',
component: LoopNode, component: LoopNode,
checkers: [], checkers: {
add: [],
save: [],
},
},
// 特殊节点特殊判断
{
key: 'input-node',
group: '数据节点',
name: '输入',
icon: <i className="fa fa-file"/>,
description: '定义流程输入变量',
component: InputNode,
checkers: {
add: [noMoreThanOneNodeType],
save: [],
},
}, },
{ {
key: 'output-node', key: 'output-node',
group: '输出节点', group: '数据节点',
name: '输出', name: '输出',
icon: <i className="fa fa-file"/>, icon: <i className="fa fa-file"/>,
description: '定义输出变量', description: '定义流程输出变量',
component: OutputNode, component: OutputNode,
checkers: [inputSingleVariableChecker('output')], checkers: {
add: [noMoreThanOneNodeType],
save: [inputSingleVariableChecker('output')]
},
}, },
] ]

View File

@@ -1,5 +1,5 @@
import {PlusCircleFilled} from '@ant-design/icons' import {PlusCircleFilled} from '@ant-design/icons'
import {Button, Dropdown} from 'antd' import {Button, Dropdown, message} from 'antd'
import type {ButtonProps} from 'antd/lib' import type {ButtonProps} from 'antd/lib'
import {isEqual, randomId, unique} from 'licia' import {isEqual, randomId, unique} from 'licia'
import {commonInfo} from '../../../util/amis.tsx' import {commonInfo} from '../../../util/amis.tsx'
@@ -34,7 +34,7 @@ const AddNodeButton = (props: AddNodeButtonProps) => {
if (commonInfo.debug) { if (commonInfo.debug) {
console.info('Add', key, JSON.stringify({nodes, edges, data})) console.info('Add', key, JSON.stringify({nodes, edges, data}))
} }
checkAddNode(key, nodes, edges) checkAddNode(key, props.parent, nodes, edges)
let nodeId = randomId(10, 'qwertyuiopasdfghjklzxcvbnmQWERTYUIOPASDFGHJKLZXCVBNM') let nodeId = randomId(10, 'qwertyuiopasdfghjklzxcvbnmQWERTYUIOPASDFGHJKLZXCVBNM')
let define = NodeRegistryMap[key] let define = NodeRegistryMap[key]
@@ -62,7 +62,7 @@ const AddNodeButton = (props: AddNodeButtonProps) => {
}) })
} catch (e) { } catch (e) {
// @ts-ignore // @ts-ignore
messageApi.error(e.toString()) message.error(e.toString())
} }
}, },
}} }}

View File

@@ -8,7 +8,7 @@ import {amisRender, commonInfo, horizontalFormOptions} from '../../../util/amis.
import {generateAllIncomerOutputVariablesFormOptions} from '../Helper.tsx' import {generateAllIncomerOutputVariablesFormOptions} from '../Helper.tsx'
import {useDataStore} from '../store/DataStore.ts' import {useDataStore} from '../store/DataStore.ts'
import {useFlowStore} from '../store/FlowStore.ts' import {useFlowStore} from '../store/FlowStore.ts'
import {OutputVariableTypeMap} from '../types.ts' import {type FormSchema, OutputVariableTypeMap} from '../types.ts'
export function inputsFormColumns( export function inputsFormColumns(
nodeId: string, nodeId: string,
@@ -88,7 +88,7 @@ type AmisNodeProps = {
nodeProps: NodeProps nodeProps: NodeProps
extraNodeDescription?: JSX.Element extraNodeDescription?: JSX.Element
handler: JSX.Element handler: JSX.Element
columnSchema?: () => Schema[] formSchema?: () => FormSchema,
resize?: { minWidth: number, minHeight: number } resize?: { minWidth: number, minHeight: number }
} }
@@ -122,7 +122,7 @@ const AmisNode: (props: AmisNodeProps) => JSX.Element = ({
nodeProps, nodeProps,
extraNodeDescription, extraNodeDescription,
handler, handler,
columnSchema, formSchema,
resize, resize,
}) => { }) => {
const {removeNode} = useFlowStore() const {removeNode} = useFlowStore()
@@ -136,6 +136,7 @@ const AmisNode: (props: AmisNodeProps) => JSX.Element = ({
const [editDrawerOpen, setEditDrawerOpen] = useState(false) const [editDrawerOpen, setEditDrawerOpen] = useState(false)
const [editDrawerForm, setEditDrawerForm] = useState<JSX.Element>(<></>) const [editDrawerForm, setEditDrawerForm] = useState<JSX.Element>(<></>)
const onOpenEditDrawerClick = useCallback(() => { const onOpenEditDrawerClick = useCallback(() => {
const schema = formSchema?.()
setEditDrawerForm( setEditDrawerForm(
amisRender( amisRender(
{ {
@@ -166,6 +167,7 @@ const AmisNode: (props: AmisNodeProps) => JSX.Element = ({
}, },
], ],
}, },
...(schema?.events ?? {})
}, },
body: [ body: [
{ {
@@ -183,7 +185,7 @@ const AmisNode: (props: AmisNodeProps) => JSX.Element = ({
{ {
type: 'divider', type: 'divider',
}, },
...(columnSchema?.() ?? []), ...(schema?.columns ?? []),
{ {
type: 'wrapper', type: 'wrapper',
size: 'none', size: 'none',

View File

@@ -5,6 +5,7 @@ import {useContextStore} from '../store/ContextStore.ts'
import {useDataStore} from '../store/DataStore.ts' import {useDataStore} from '../store/DataStore.ts'
import {useFlowStore} from '../store/FlowStore.ts' import {useFlowStore} from '../store/FlowStore.ts'
import AmisNode, {inputsFormColumns, nodeClassName, NormalNodeHandler, outputsFormColumns} from './AmisNode.tsx' import AmisNode, {inputsFormColumns, nodeClassName, NormalNodeHandler, outputsFormColumns} from './AmisNode.tsx'
import type {FormSchema} from '../types.ts'
const languageMap: Record<string, string> = { const languageMap: Record<string, string> = {
'javascript': 'Javascript', 'javascript': 'Javascript',
@@ -19,7 +20,8 @@ const CodeNode = (props: NodeProps) => {
const nodeData = getDataById(props.id) const nodeData = getDataById(props.id)
const columnsSchema = useCallback(() => [ const formSchema: () => FormSchema = useCallback(() => ({
columns: [
...inputsFormColumns(props.id, getInputSchema(), getNodes(), getEdges(), getData()), ...inputsFormColumns(props.id, getInputSchema(), getNodes(), getEdges(), getData()),
{ {
type: 'divider', type: 'divider',
@@ -46,7 +48,8 @@ const CodeNode = (props: NodeProps) => {
type: 'divider', type: 'divider',
}, },
...outputsFormColumns(true, false), ...outputsFormColumns(true, false),
], [props.id]) ]
}), [props.id])
const extraNodeDescription = useMemo(() => { const extraNodeDescription = useMemo(() => {
return nodeData?.type return nodeData?.type
@@ -62,7 +65,7 @@ const CodeNode = (props: NodeProps) => {
className={nodeClassName('code')} className={nodeClassName('code')}
nodeProps={props} nodeProps={props}
extraNodeDescription={extraNodeDescription} extraNodeDescription={extraNodeDescription}
columnSchema={columnsSchema} formSchema={formSchema}
handler={<NormalNodeHandler/>} handler={<NormalNodeHandler/>}
/> />
) )

View File

@@ -0,0 +1,123 @@
import type {NodeProps} from '@xyflow/react'
import React, {useCallback} from 'react'
import AmisNode, {nodeClassName, outputsFormColumns, StartNodeHandler} from './AmisNode.tsx'
import {horizontalFormOptions} from '../../../util/amis.tsx'
import {typeMap} from '../../../pages/ai/task/InputSchema.tsx'
import type {FormSchema, OutputVariableType} from '../types.ts'
import {isEmpty} from 'licia'
const originTypeMap: Record<string, OutputVariableType> = {
text: 'text',
textarea: 'text',
number: 'number',
files: 'array-text',
}
const InputNode = (props: NodeProps) => {
const formSchema: () => FormSchema = useCallback(() => ({
events: {
change: {
actions: [
{
actionType: 'validate',
},
{
actionType: 'custom',
// @ts-ignore
script: (context, doAction, event) => {
let data = event?.data
console.log(data)
if (data && isEmpty(data?.validateResult?.error ?? undefined)) {
let inputs = data.validateResult?.payload?.inputs ?? {}
if (inputs) {
let outputs: Record<string, { type: OutputVariableType }> = {}
for (let key of Object.keys(inputs)) {
outputs[key] = {
type: originTypeMap[inputs[key].type],
}
}
doAction({
actionType: 'setValue',
args: {
value: {
outputs
},
},
})
}
}
},
},
]
}
},
columns: [
{
type: 'input-kvs',
name: 'inputs',
label: '输入变量',
required: true,
addButtonText: '新增入参',
draggable: false,
keyItem: {
label: '参数名称',
...horizontalFormOptions(),
validations: {
isAlphanumeric: true,
},
},
valueItems: [
{
...horizontalFormOptions(),
type: 'input-text',
name: 'label',
required: true,
label: '中文名称',
clearValueOnEmpty: true,
clearable: true,
},
{
...horizontalFormOptions(),
type: 'input-text',
name: 'description',
label: '参数描述',
clearValueOnEmpty: true,
clearable: true,
},
{
...horizontalFormOptions(),
type: 'select',
name: 'type',
label: '参数类型',
required: true,
selectFirst: true,
options: Object.keys(typeMap).map(key => ({label: typeMap[key], value: key})),
},
{
...horizontalFormOptions(),
type: 'switch',
name: 'required',
label: '是否必填',
required: true,
value: true,
},
],
},
{
type: 'divider',
},
...outputsFormColumns(false, false),
]
}), [props.id])
return (
<AmisNode
className={nodeClassName('input')}
nodeProps={props}
formSchema={formSchema}
handler={<StartNodeHandler/>}
/>
)
}
export default React.memo(InputNode)

View File

@@ -5,6 +5,7 @@ import {useContextStore} from '../store/ContextStore.ts'
import {useDataStore} from '../store/DataStore.ts' import {useDataStore} from '../store/DataStore.ts'
import {useFlowStore} from '../store/FlowStore.ts' import {useFlowStore} from '../store/FlowStore.ts'
import AmisNode, {inputsFormColumns, nodeClassName, NormalNodeHandler, outputsFormColumns} from './AmisNode.tsx' import AmisNode, {inputsFormColumns, nodeClassName, NormalNodeHandler, outputsFormColumns} from './AmisNode.tsx'
import type {FormSchema} from '../types.ts'
const KnowledgeNode = (props: NodeProps) => { const KnowledgeNode = (props: NodeProps) => {
const {getNodes, getEdges} = useFlowStore() const {getNodes, getEdges} = useFlowStore()
@@ -24,7 +25,8 @@ const KnowledgeNode = (props: NodeProps) => {
) )
}, [props.id]) }, [props.id])
const columnsSchema = useCallback(() => [ const formSchema: () => FormSchema = useCallback(() => ({
columns: [
...inputsFormColumns(props.id, getInputSchema(), getNodes(), getEdges(), getData()), ...inputsFormColumns(props.id, getInputSchema(), getNodes(), getEdges(), getData()),
{ {
type: 'divider', type: 'divider',
@@ -76,12 +78,13 @@ const KnowledgeNode = (props: NodeProps) => {
type: 'divider', type: 'divider',
}, },
...outputsFormColumns(false, true), ...outputsFormColumns(false, true),
], [props.id]) ]
}), [props.id])
return ( return (
<AmisNode <AmisNode
className={nodeClassName('knowledge')} className={nodeClassName('knowledge')}
nodeProps={props} nodeProps={props}
columnSchema={columnsSchema} formSchema={formSchema}
handler={<NormalNodeHandler/>} handler={<NormalNodeHandler/>}
/> />
) )

View File

@@ -5,6 +5,7 @@ import {useContextStore} from '../store/ContextStore.ts'
import {useDataStore} from '../store/DataStore.ts' import {useDataStore} from '../store/DataStore.ts'
import {useFlowStore} from '../store/FlowStore.ts' import {useFlowStore} from '../store/FlowStore.ts'
import AmisNode, {inputsFormColumns, nodeClassName, NormalNodeHandler, outputsFormColumns} from './AmisNode.tsx' import AmisNode, {inputsFormColumns, nodeClassName, NormalNodeHandler, outputsFormColumns} from './AmisNode.tsx'
import type {FormSchema} from '../types.ts'
const modelMap: Record<string, string> = { const modelMap: Record<string, string> = {
qwen3: 'Qwen3', qwen3: 'Qwen3',
@@ -31,7 +32,8 @@ const LlmNode = (props: NodeProps) => {
) )
}, [props.id]) }, [props.id])
const columnsSchema = useCallback(() => [ const formSchema: () => FormSchema = useCallback(() => ({
columns: [
...inputsFormColumns(props.id, getInputSchema(), getNodes(), getEdges(), getData()), ...inputsFormColumns(props.id, getInputSchema(), getNodes(), getEdges(), getData()),
{ {
type: 'divider', type: 'divider',
@@ -54,7 +56,8 @@ const LlmNode = (props: NodeProps) => {
type: 'divider', type: 'divider',
}, },
...outputsFormColumns(false, true), ...outputsFormColumns(false, true),
], [props.id]) ]
}), [props.id])
const extraNodeDescription = useMemo(() => { const extraNodeDescription = useMemo(() => {
return nodeData?.model return nodeData?.model
@@ -70,7 +73,7 @@ const LlmNode = (props: NodeProps) => {
className={nodeClassName('llm')} className={nodeClassName('llm')}
nodeProps={props} nodeProps={props}
extraNodeDescription={extraNodeDescription} extraNodeDescription={extraNodeDescription}
columnSchema={columnsSchema} formSchema={formSchema}
handler={<NormalNodeHandler/>} handler={<NormalNodeHandler/>}
/> />
) )

View File

@@ -6,7 +6,7 @@ import {generateAllIncomerOutputVariablesFormOptions} from '../Helper.tsx'
import {useContextStore} from '../store/ContextStore.ts' import {useContextStore} from '../store/ContextStore.ts'
import {useDataStore} from '../store/DataStore.ts' import {useDataStore} from '../store/DataStore.ts'
import {useFlowStore} from '../store/FlowStore.ts' import {useFlowStore} from '../store/FlowStore.ts'
import {flowBackgroundColor, flowDotColor} from '../types.ts' import {flowBackgroundColor, flowDotColor, type FormSchema} from '../types.ts'
import AmisNode, {nodeClassName, NormalNodeHandler, outputsFormColumns} from './AmisNode.tsx' import AmisNode, {nodeClassName, NormalNodeHandler, outputsFormColumns} from './AmisNode.tsx'
const LoopNode = (props: NodeProps) => { const LoopNode = (props: NodeProps) => {
@@ -31,7 +31,8 @@ const LoopNode = (props: NodeProps) => {
) )
}, [props.id]) }, [props.id])
const columnsSchema = useCallback(() => [ const formSchema: () => FormSchema = useCallback(() => ({
columns: [
{ {
type: 'switch', type: 'switch',
name: 'failFast', name: 'failFast',
@@ -111,7 +112,8 @@ const LoopNode = (props: NodeProps) => {
type: 'divider', type: 'divider',
}, },
...outputsFormColumns(false, true), ...outputsFormColumns(false, true),
], [props.id]) ]
}), [props.id])
const extraNodeDescription = useMemo(() => { const extraNodeDescription = useMemo(() => {
return ( return (
@@ -142,7 +144,7 @@ const LoopNode = (props: NodeProps) => {
}} }}
nodeProps={props} nodeProps={props}
extraNodeDescription={extraNodeDescription} extraNodeDescription={extraNodeDescription}
columnSchema={columnsSchema} formSchema={formSchema}
handler={<NormalNodeHandler/>} handler={<NormalNodeHandler/>}
resize={{ resize={{
minWidth: 350, minWidth: 350,

View File

@@ -5,14 +5,15 @@ import {useContextStore} from '../store/ContextStore.ts'
import {useDataStore} from '../store/DataStore.ts' import {useDataStore} from '../store/DataStore.ts'
import {useFlowStore} from '../store/FlowStore.ts' import {useFlowStore} from '../store/FlowStore.ts'
import AmisNode, {EndNodeHandler, nodeClassName} from './AmisNode.tsx' import AmisNode, {EndNodeHandler, nodeClassName} from './AmisNode.tsx'
import type {FormSchema} from '../types.ts'
const OutputNode = (props: NodeProps) => { const OutputNode = (props: NodeProps) => {
const {getNodes, getEdges} = useFlowStore() const {getNodes, getEdges} = useFlowStore()
const {getData} = useDataStore() const {getData} = useDataStore()
const {getInputSchema} = useContextStore() const {getInputSchema} = useContextStore()
const columnsSchema = useCallback( const formSchema: () => FormSchema = useCallback(() => ({
() => [ columns: [
{ {
type: 'select', type: 'select',
name: 'output', name: 'output',
@@ -27,13 +28,14 @@ const OutputNode = (props: NodeProps) => {
getData(), getData(),
), ),
}, },
], [props.id]) ]
}), [props.id])
return ( return (
<AmisNode <AmisNode
className={nodeClassName('output')} className={nodeClassName('output')}
nodeProps={props} nodeProps={props}
columnSchema={columnsSchema} formSchema={formSchema}
handler={<EndNodeHandler/>} handler={<EndNodeHandler/>}
/> />
) )

View File

@@ -8,6 +8,7 @@ import {useContextStore} from '../store/ContextStore.ts'
import {useDataStore} from '../store/DataStore.ts' import {useDataStore} from '../store/DataStore.ts'
import {useFlowStore} from '../store/FlowStore.ts' import {useFlowStore} from '../store/FlowStore.ts'
import AmisNode, {nodeClassName} from './AmisNode.tsx' import AmisNode, {nodeClassName} from './AmisNode.tsx'
import type {FormSchema} from '../types.ts'
const SwitchNode = (props: NodeProps) => { const SwitchNode = (props: NodeProps) => {
const {getNodes, getEdges, removeEdges} = useFlowStore() const {getNodes, getEdges, removeEdges} = useFlowStore()
@@ -18,7 +19,8 @@ const SwitchNode = (props: NodeProps) => {
// @ts-ignore // @ts-ignore
const conditions: ConditionValue[] = nodeData?.conditions?.map(c => c.condition) ?? [] const conditions: ConditionValue[] = nodeData?.conditions?.map(c => c.condition) ?? []
const columnsSchema = useCallback(() => [ const formSchema: () => FormSchema = useCallback(() => ({
columns: [
{ {
type: 'combo', type: 'combo',
name: 'conditions', name: 'conditions',
@@ -43,7 +45,8 @@ const SwitchNode = (props: NodeProps) => {
}, },
], ],
}, },
], [props.id]) ]
}), [props.id])
const extraNodeDescription = useMemo(() => { const extraNodeDescription = useMemo(() => {
return ( return (
@@ -88,7 +91,7 @@ const SwitchNode = (props: NodeProps) => {
className={nodeClassName('switch')} className={nodeClassName('switch')}
nodeProps={props} nodeProps={props}
extraNodeDescription={extraNodeDescription} extraNodeDescription={extraNodeDescription}
columnSchema={columnsSchema} formSchema={formSchema}
handler={handler} handler={handler}
/> />
) )

View File

@@ -5,6 +5,7 @@ import {useContextStore} from '../store/ContextStore.ts'
import {useDataStore} from '../store/DataStore.ts' import {useDataStore} from '../store/DataStore.ts'
import {useFlowStore} from '../store/FlowStore.ts' import {useFlowStore} from '../store/FlowStore.ts'
import AmisNode, {inputsFormColumns, nodeClassName, NormalNodeHandler, outputsFormColumns} from './AmisNode.tsx' import AmisNode, {inputsFormColumns, nodeClassName, NormalNodeHandler, outputsFormColumns} from './AmisNode.tsx'
import type {FormSchema} from '../types.ts'
const typeMap: Record<string, string> = { const typeMap: Record<string, string> = {
default: '默认', default: '默认',
@@ -33,8 +34,8 @@ const TemplateNode = (props: NodeProps) => {
) )
}, [props.id]) }, [props.id])
const columnsSchema = useCallback( const formSchema: () => FormSchema = useCallback(() => ({
() => [ columns: [
...inputsFormColumns(props.id, getInputSchema(), getNodes(), getEdges(), getData()), ...inputsFormColumns(props.id, getInputSchema(), getNodes(), getEdges(), getData()),
{ {
type: 'divider', type: 'divider',
@@ -69,7 +70,8 @@ const TemplateNode = (props: NodeProps) => {
}, },
}, },
...outputsFormColumns(false, true), ...outputsFormColumns(false, true),
], [props.id]) ]
}), [props.id])
const extraNodeDescription = useMemo(() => { const extraNodeDescription = useMemo(() => {
return nodeData?.type return nodeData?.type
@@ -85,7 +87,7 @@ const TemplateNode = (props: NodeProps) => {
className={nodeClassName('template')} className={nodeClassName('template')}
nodeProps={props} nodeProps={props}
extraNodeDescription={extraNodeDescription} extraNodeDescription={extraNodeDescription}
columnSchema={columnsSchema} formSchema={formSchema}
handler={<NormalNodeHandler/>} handler={<NormalNodeHandler/>}
/> />
) )

View File

@@ -1,5 +1,6 @@
import type {Edge, Node} from '@xyflow/react' import type {Edge, Node} from '@xyflow/react'
import type {JSX} from 'react' import type {JSX} from 'react'
import type {ListenerAction, Schema} from 'amis'
export const flowBackgroundColor = '#fafafa' export const flowBackgroundColor = '#fafafa'
export const flowDotColor = '#dedede' export const flowDotColor = '#dedede'
@@ -19,7 +20,8 @@ export type NodeError = {
message?: string, message?: string,
} }
export type NodeChecker = (id: string, inputSchema: Record<string, Record<string, any>>, nodes: Node[], edges: Edge[], data: any) => NodeError export type AddNodeChecker = (type: string, parentId: string | undefined, inputSchema: Record<string, Record<string, any>>, nodes: Node[], edges: Edge[], data: any) => NodeError
export type SaveNodeChecker = (id: string, parentId: string | undefined, inputSchema: Record<string, Record<string, any>>, nodes: Node[], edges: Edge[], data: any) => NodeError
export type GraphData = { nodes: Node[], edges: Edge[], data: any } export type GraphData = { nodes: Node[], edges: Edge[], data: any }
@@ -47,7 +49,10 @@ export type NodeDefine = {
icon: JSX.Element, icon: JSX.Element,
description: string, description: string,
component: any, component: any,
checkers: NodeChecker[], checkers: {
add: AddNodeChecker[],
save: SaveNodeChecker[],
},
} }
export type OutputVariable = { export type OutputVariable = {
@@ -56,3 +61,8 @@ export type OutputVariable = {
type: OutputVariableType, type: OutputVariableType,
variable: string, variable: string,
} }
export type FormSchema = {
events?: Record<string, { actions: ListenerAction[] }>
columns: Schema[]
}