refractor(web): 增加输入节点
This commit is contained in:
@@ -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)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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/>
|
||||||
|
|||||||
@@ -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')]
|
||||||
|
},
|
||||||
},
|
},
|
||||||
]
|
]
|
||||||
|
|
||||||
|
|||||||
@@ -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())
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
}}
|
}}
|
||||||
|
|||||||
@@ -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',
|
||||||
|
|||||||
@@ -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/>}
|
||||||
/>
|
/>
|
||||||
)
|
)
|
||||||
|
|||||||
123
service-web/client/src/components/flow/node/InputNode.tsx
Normal file
123
service-web/client/src/components/flow/node/InputNode.tsx
Normal 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)
|
||||||
@@ -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/>}
|
||||||
/>
|
/>
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -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/>}
|
||||||
/>
|
/>
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -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,
|
||||||
|
|||||||
@@ -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/>}
|
||||||
/>
|
/>
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -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}
|
||||||
/>
|
/>
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -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/>}
|
||||||
/>
|
/>
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -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[]
|
||||||
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user