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))
|
||||
|
||||
export const typeNotFound = (type: string) => new CheckError(100, `类型 ${type} 不存在`)
|
||||
export const addNodeError = (message?: string) => new CheckError(101, message ?? '无法添加节点')
|
||||
|
||||
// @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, '连线起始节点未找到')
|
||||
@@ -70,7 +83,7 @@ export const checkAddConnection: (connection: Connection, nodes: Node[], edges:
|
||||
export const atLeastOneNode = () => new CheckError(300, '至少包含一个节点')
|
||||
export const hasUnfinishedNode = (nodeId: string) => new CheckError(301, `存在尚未配置完成的节点: ${nodeId}`)
|
||||
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
|
||||
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) {
|
||||
let nodeId = node.id
|
||||
if (!has(data, nodeId) || !data[nodeId]?.finished) {
|
||||
throw hasUnfinishedNode(nodeId)
|
||||
if (!has(data, node.id) || !data[node.id]?.finished) {
|
||||
throw hasUnfinishedNode(node.id)
|
||||
}
|
||||
|
||||
if (!has(node, 'type')) {
|
||||
@@ -89,10 +101,10 @@ export const checkSave: (inputSchema: Record<string, Record<string, any>>, nodes
|
||||
}
|
||||
let nodeType = node.type!
|
||||
let nodeDefine = NodeRegistryMap[nodeType]
|
||||
for (let checker of nodeDefine.checkers) {
|
||||
let checkResult = checker(nodeId, inputSchema, nodes, edges, data)
|
||||
for (let checker of nodeDefine.checkers.save) {
|
||||
let checkResult = checker(node.id, node.parentId, inputSchema, nodes, edges, data)
|
||||
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) {
|
||||
const navigate = useNavigate()
|
||||
const [messageApi, contextHolder] = message.useMessage()
|
||||
|
||||
const {data, setData} = useDataStore()
|
||||
const {
|
||||
@@ -84,7 +83,6 @@ function FlowEditor(props: FlowEditorProps) {
|
||||
|
||||
return (
|
||||
<FlowableDiv className="h-full w-full">
|
||||
{contextHolder}
|
||||
<ReactFlow
|
||||
className="rounded-xl"
|
||||
nodes={nodes}
|
||||
@@ -133,7 +131,7 @@ function FlowEditor(props: FlowEditorProps) {
|
||||
props.onGraphDataChange({nodes, edges, data})
|
||||
} catch (e) {
|
||||
// @ts-ignore
|
||||
messageApi.error(e.toString())
|
||||
message.error(e.toString())
|
||||
}
|
||||
}}>
|
||||
<SaveFilled/>
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import {has, isEmpty} from 'licia'
|
||||
import {has, isEmpty, isEqual} from 'licia'
|
||||
import {getAllIncomerNodeOutputVariables} from './Helper.tsx'
|
||||
import CodeNode from './node/CodeNode.tsx'
|
||||
import KnowledgeNode from './node/KnowledgeNode.tsx'
|
||||
@@ -7,10 +7,11 @@ import LoopNode from './node/LoopNode.tsx'
|
||||
import OutputNode from './node/OutputNode.tsx'
|
||||
import SwitchNode from './node/SwitchNode.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 => {
|
||||
return (id, inputSchema, nodes, edges, data) => {
|
||||
const inputSingleVariableChecker: (field: string) => SaveNodeChecker = field => {
|
||||
return (id, _parentId, inputSchema, nodes, edges, data) => {
|
||||
let nodeData = data[id] ?? {}
|
||||
if (has(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] ?? {}
|
||||
if (has(nodeData, 'inputs')) {
|
||||
let inputs = nodeData?.inputs ?? {}
|
||||
@@ -48,6 +49,13 @@ const inputMultiVariableChecker: NodeChecker = (id, inputSchema, nodes, edges, d
|
||||
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[] = [
|
||||
{
|
||||
key: 'llm-node',
|
||||
@@ -56,7 +64,10 @@ export const NodeRegistry: NodeDefine[] = [
|
||||
icon: <i className="fa fa-message"/>,
|
||||
description: '使用大模型对话',
|
||||
component: LlmNode,
|
||||
checkers: [inputMultiVariableChecker],
|
||||
checkers: {
|
||||
add: [],
|
||||
save: [inputMultiVariableChecker]
|
||||
},
|
||||
},
|
||||
{
|
||||
key: 'knowledge-node',
|
||||
@@ -65,7 +76,10 @@ export const NodeRegistry: NodeDefine[] = [
|
||||
icon: <i className="fa fa-book-bookmark"/>,
|
||||
description: '',
|
||||
component: KnowledgeNode,
|
||||
checkers: [inputMultiVariableChecker],
|
||||
checkers: {
|
||||
add: [],
|
||||
save: [inputMultiVariableChecker]
|
||||
},
|
||||
},
|
||||
{
|
||||
key: 'code-node',
|
||||
@@ -74,7 +88,10 @@ export const NodeRegistry: NodeDefine[] = [
|
||||
icon: <i className="fa fa-code"/>,
|
||||
description: '执行自定义的处理代码',
|
||||
component: CodeNode,
|
||||
checkers: [inputMultiVariableChecker],
|
||||
checkers: {
|
||||
add: [],
|
||||
save: [inputMultiVariableChecker]
|
||||
},
|
||||
},
|
||||
{
|
||||
key: 'template-node',
|
||||
@@ -83,7 +100,10 @@ export const NodeRegistry: NodeDefine[] = [
|
||||
icon: <i className="fa fa-pen-nib"/>,
|
||||
description: '使用模板聚合转换变量表示',
|
||||
component: TemplateNode,
|
||||
checkers: [inputMultiVariableChecker],
|
||||
checkers: {
|
||||
add: [],
|
||||
save: [inputMultiVariableChecker]
|
||||
},
|
||||
},
|
||||
{
|
||||
key: 'switch-node',
|
||||
@@ -92,7 +112,10 @@ export const NodeRegistry: NodeDefine[] = [
|
||||
icon: <i className="fa fa-code-fork"/>,
|
||||
description: '根据不同的情况前往不同的分支',
|
||||
component: SwitchNode,
|
||||
checkers: [],
|
||||
checkers: {
|
||||
add: [],
|
||||
save: [],
|
||||
},
|
||||
},
|
||||
{
|
||||
key: 'loop-node',
|
||||
@@ -101,16 +124,35 @@ export const NodeRegistry: NodeDefine[] = [
|
||||
icon: <i className="fa fa-repeat"/>,
|
||||
description: '实现循环执行流程',
|
||||
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',
|
||||
group: '输出节点',
|
||||
group: '数据节点',
|
||||
name: '输出',
|
||||
icon: <i className="fa fa-file"/>,
|
||||
description: '定义输出变量',
|
||||
description: '定义流程输出变量',
|
||||
component: OutputNode,
|
||||
checkers: [inputSingleVariableChecker('output')],
|
||||
checkers: {
|
||||
add: [noMoreThanOneNodeType],
|
||||
save: [inputSingleVariableChecker('output')]
|
||||
},
|
||||
},
|
||||
]
|
||||
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import {PlusCircleFilled} from '@ant-design/icons'
|
||||
import {Button, Dropdown} from 'antd'
|
||||
import {Button, Dropdown, message} from 'antd'
|
||||
import type {ButtonProps} from 'antd/lib'
|
||||
import {isEqual, randomId, unique} from 'licia'
|
||||
import {commonInfo} from '../../../util/amis.tsx'
|
||||
@@ -34,7 +34,7 @@ const AddNodeButton = (props: AddNodeButtonProps) => {
|
||||
if (commonInfo.debug) {
|
||||
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 define = NodeRegistryMap[key]
|
||||
@@ -62,7 +62,7 @@ const AddNodeButton = (props: AddNodeButtonProps) => {
|
||||
})
|
||||
} catch (e) {
|
||||
// @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 {useDataStore} from '../store/DataStore.ts'
|
||||
import {useFlowStore} from '../store/FlowStore.ts'
|
||||
import {OutputVariableTypeMap} from '../types.ts'
|
||||
import {type FormSchema, OutputVariableTypeMap} from '../types.ts'
|
||||
|
||||
export function inputsFormColumns(
|
||||
nodeId: string,
|
||||
@@ -88,7 +88,7 @@ type AmisNodeProps = {
|
||||
nodeProps: NodeProps
|
||||
extraNodeDescription?: JSX.Element
|
||||
handler: JSX.Element
|
||||
columnSchema?: () => Schema[]
|
||||
formSchema?: () => FormSchema,
|
||||
resize?: { minWidth: number, minHeight: number }
|
||||
}
|
||||
|
||||
@@ -122,7 +122,7 @@ const AmisNode: (props: AmisNodeProps) => JSX.Element = ({
|
||||
nodeProps,
|
||||
extraNodeDescription,
|
||||
handler,
|
||||
columnSchema,
|
||||
formSchema,
|
||||
resize,
|
||||
}) => {
|
||||
const {removeNode} = useFlowStore()
|
||||
@@ -136,6 +136,7 @@ const AmisNode: (props: AmisNodeProps) => JSX.Element = ({
|
||||
const [editDrawerOpen, setEditDrawerOpen] = useState(false)
|
||||
const [editDrawerForm, setEditDrawerForm] = useState<JSX.Element>(<></>)
|
||||
const onOpenEditDrawerClick = useCallback(() => {
|
||||
const schema = formSchema?.()
|
||||
setEditDrawerForm(
|
||||
amisRender(
|
||||
{
|
||||
@@ -166,6 +167,7 @@ const AmisNode: (props: AmisNodeProps) => JSX.Element = ({
|
||||
},
|
||||
],
|
||||
},
|
||||
...(schema?.events ?? {})
|
||||
},
|
||||
body: [
|
||||
{
|
||||
@@ -183,7 +185,7 @@ const AmisNode: (props: AmisNodeProps) => JSX.Element = ({
|
||||
{
|
||||
type: 'divider',
|
||||
},
|
||||
...(columnSchema?.() ?? []),
|
||||
...(schema?.columns ?? []),
|
||||
{
|
||||
type: 'wrapper',
|
||||
size: 'none',
|
||||
|
||||
@@ -5,6 +5,7 @@ import {useContextStore} from '../store/ContextStore.ts'
|
||||
import {useDataStore} from '../store/DataStore.ts'
|
||||
import {useFlowStore} from '../store/FlowStore.ts'
|
||||
import AmisNode, {inputsFormColumns, nodeClassName, NormalNodeHandler, outputsFormColumns} from './AmisNode.tsx'
|
||||
import type {FormSchema} from '../types.ts'
|
||||
|
||||
const languageMap: Record<string, string> = {
|
||||
'javascript': 'Javascript',
|
||||
@@ -19,34 +20,36 @@ const CodeNode = (props: NodeProps) => {
|
||||
|
||||
const nodeData = getDataById(props.id)
|
||||
|
||||
const columnsSchema = useCallback(() => [
|
||||
...inputsFormColumns(props.id, getInputSchema(), getNodes(), getEdges(), getData()),
|
||||
{
|
||||
type: 'divider',
|
||||
},
|
||||
{
|
||||
type: 'select',
|
||||
name: 'type',
|
||||
label: '代码类型',
|
||||
required: true,
|
||||
selectFirst: true,
|
||||
options: Object.keys(languageMap).map(key => ({label: languageMap[key], value: key})),
|
||||
},
|
||||
{
|
||||
type: 'editor',
|
||||
required: true,
|
||||
label: '代码内容',
|
||||
name: 'content',
|
||||
language: '${type}',
|
||||
options: {
|
||||
wordWrap: 'bounded',
|
||||
const formSchema: () => FormSchema = useCallback(() => ({
|
||||
columns: [
|
||||
...inputsFormColumns(props.id, getInputSchema(), getNodes(), getEdges(), getData()),
|
||||
{
|
||||
type: 'divider',
|
||||
},
|
||||
},
|
||||
{
|
||||
type: 'divider',
|
||||
},
|
||||
...outputsFormColumns(true, false),
|
||||
], [props.id])
|
||||
{
|
||||
type: 'select',
|
||||
name: 'type',
|
||||
label: '代码类型',
|
||||
required: true,
|
||||
selectFirst: true,
|
||||
options: Object.keys(languageMap).map(key => ({label: languageMap[key], value: key})),
|
||||
},
|
||||
{
|
||||
type: 'editor',
|
||||
required: true,
|
||||
label: '代码内容',
|
||||
name: 'content',
|
||||
language: '${type}',
|
||||
options: {
|
||||
wordWrap: 'bounded',
|
||||
},
|
||||
},
|
||||
{
|
||||
type: 'divider',
|
||||
},
|
||||
...outputsFormColumns(true, false),
|
||||
]
|
||||
}), [props.id])
|
||||
|
||||
const extraNodeDescription = useMemo(() => {
|
||||
return nodeData?.type
|
||||
@@ -62,7 +65,7 @@ const CodeNode = (props: NodeProps) => {
|
||||
className={nodeClassName('code')}
|
||||
nodeProps={props}
|
||||
extraNodeDescription={extraNodeDescription}
|
||||
columnSchema={columnsSchema}
|
||||
formSchema={formSchema}
|
||||
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 {useFlowStore} from '../store/FlowStore.ts'
|
||||
import AmisNode, {inputsFormColumns, nodeClassName, NormalNodeHandler, outputsFormColumns} from './AmisNode.tsx'
|
||||
import type {FormSchema} from '../types.ts'
|
||||
|
||||
const KnowledgeNode = (props: NodeProps) => {
|
||||
const {getNodes, getEdges} = useFlowStore()
|
||||
@@ -24,64 +25,66 @@ const KnowledgeNode = (props: NodeProps) => {
|
||||
)
|
||||
}, [props.id])
|
||||
|
||||
const columnsSchema = useCallback(() => [
|
||||
...inputsFormColumns(props.id, getInputSchema(), getNodes(), getEdges(), getData()),
|
||||
{
|
||||
type: 'divider',
|
||||
},
|
||||
{
|
||||
type: 'select',
|
||||
name: 'knowledgeId',
|
||||
label: '知识库',
|
||||
required: true,
|
||||
options: [],
|
||||
source: {
|
||||
method: 'get',
|
||||
url: `${commonInfo.baseAiUrl}/knowledge/list`,
|
||||
// @ts-ignore
|
||||
adaptor: (payload, response, api, context) => {
|
||||
return {
|
||||
...payload,
|
||||
data: {
|
||||
items: payload.data.items.map((item: any) => ({value: item['id'], label: item['name']})),
|
||||
},
|
||||
}
|
||||
const formSchema: () => FormSchema = useCallback(() => ({
|
||||
columns: [
|
||||
...inputsFormColumns(props.id, getInputSchema(), getNodes(), getEdges(), getData()),
|
||||
{
|
||||
type: 'divider',
|
||||
},
|
||||
{
|
||||
type: 'select',
|
||||
name: 'knowledgeId',
|
||||
label: '知识库',
|
||||
required: true,
|
||||
options: [],
|
||||
source: {
|
||||
method: 'get',
|
||||
url: `${commonInfo.baseAiUrl}/knowledge/list`,
|
||||
// @ts-ignore
|
||||
adaptor: (payload, response, api, context) => {
|
||||
return {
|
||||
...payload,
|
||||
data: {
|
||||
items: payload.data.items.map((item: any) => ({value: item['id'], label: item['name']})),
|
||||
},
|
||||
}
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
type: 'input-text',
|
||||
name: 'query',
|
||||
label: '查询文本',
|
||||
required: true,
|
||||
},
|
||||
{
|
||||
type: 'input-range',
|
||||
name: 'count',
|
||||
label: '返回数量',
|
||||
required: true,
|
||||
value: 3,
|
||||
max: 10,
|
||||
},
|
||||
{
|
||||
type: 'input-range',
|
||||
name: 'score',
|
||||
label: '匹配阀值',
|
||||
required: true,
|
||||
value: 0.6,
|
||||
max: 1,
|
||||
step: 0.05,
|
||||
},
|
||||
{
|
||||
type: 'divider',
|
||||
},
|
||||
...outputsFormColumns(false, true),
|
||||
], [props.id])
|
||||
{
|
||||
type: 'input-text',
|
||||
name: 'query',
|
||||
label: '查询文本',
|
||||
required: true,
|
||||
},
|
||||
{
|
||||
type: 'input-range',
|
||||
name: 'count',
|
||||
label: '返回数量',
|
||||
required: true,
|
||||
value: 3,
|
||||
max: 10,
|
||||
},
|
||||
{
|
||||
type: 'input-range',
|
||||
name: 'score',
|
||||
label: '匹配阀值',
|
||||
required: true,
|
||||
value: 0.6,
|
||||
max: 1,
|
||||
step: 0.05,
|
||||
},
|
||||
{
|
||||
type: 'divider',
|
||||
},
|
||||
...outputsFormColumns(false, true),
|
||||
]
|
||||
}), [props.id])
|
||||
return (
|
||||
<AmisNode
|
||||
className={nodeClassName('knowledge')}
|
||||
nodeProps={props}
|
||||
columnSchema={columnsSchema}
|
||||
formSchema={formSchema}
|
||||
handler={<NormalNodeHandler/>}
|
||||
/>
|
||||
)
|
||||
|
||||
@@ -5,6 +5,7 @@ import {useContextStore} from '../store/ContextStore.ts'
|
||||
import {useDataStore} from '../store/DataStore.ts'
|
||||
import {useFlowStore} from '../store/FlowStore.ts'
|
||||
import AmisNode, {inputsFormColumns, nodeClassName, NormalNodeHandler, outputsFormColumns} from './AmisNode.tsx'
|
||||
import type {FormSchema} from '../types.ts'
|
||||
|
||||
const modelMap: Record<string, string> = {
|
||||
qwen3: 'Qwen3',
|
||||
@@ -31,30 +32,32 @@ const LlmNode = (props: NodeProps) => {
|
||||
)
|
||||
}, [props.id])
|
||||
|
||||
const columnsSchema = useCallback(() => [
|
||||
...inputsFormColumns(props.id, getInputSchema(), getNodes(), getEdges(), getData()),
|
||||
{
|
||||
type: 'divider',
|
||||
},
|
||||
{
|
||||
type: 'select',
|
||||
name: 'model',
|
||||
label: '大模型',
|
||||
required: true,
|
||||
selectFirst: true,
|
||||
options: Object.keys(modelMap).map(key => ({label: modelMap[key], value: key})),
|
||||
},
|
||||
{
|
||||
type: 'textarea',
|
||||
name: 'systemPrompt',
|
||||
label: '系统提示词',
|
||||
required: true,
|
||||
},
|
||||
{
|
||||
type: 'divider',
|
||||
},
|
||||
...outputsFormColumns(false, true),
|
||||
], [props.id])
|
||||
const formSchema: () => FormSchema = useCallback(() => ({
|
||||
columns: [
|
||||
...inputsFormColumns(props.id, getInputSchema(), getNodes(), getEdges(), getData()),
|
||||
{
|
||||
type: 'divider',
|
||||
},
|
||||
{
|
||||
type: 'select',
|
||||
name: 'model',
|
||||
label: '大模型',
|
||||
required: true,
|
||||
selectFirst: true,
|
||||
options: Object.keys(modelMap).map(key => ({label: modelMap[key], value: key})),
|
||||
},
|
||||
{
|
||||
type: 'textarea',
|
||||
name: 'systemPrompt',
|
||||
label: '系统提示词',
|
||||
required: true,
|
||||
},
|
||||
{
|
||||
type: 'divider',
|
||||
},
|
||||
...outputsFormColumns(false, true),
|
||||
]
|
||||
}), [props.id])
|
||||
|
||||
const extraNodeDescription = useMemo(() => {
|
||||
return nodeData?.model
|
||||
@@ -70,7 +73,7 @@ const LlmNode = (props: NodeProps) => {
|
||||
className={nodeClassName('llm')}
|
||||
nodeProps={props}
|
||||
extraNodeDescription={extraNodeDescription}
|
||||
columnSchema={columnsSchema}
|
||||
formSchema={formSchema}
|
||||
handler={<NormalNodeHandler/>}
|
||||
/>
|
||||
)
|
||||
|
||||
@@ -6,7 +6,7 @@ import {generateAllIncomerOutputVariablesFormOptions} from '../Helper.tsx'
|
||||
import {useContextStore} from '../store/ContextStore.ts'
|
||||
import {useDataStore} from '../store/DataStore.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'
|
||||
|
||||
const LoopNode = (props: NodeProps) => {
|
||||
@@ -31,87 +31,89 @@ const LoopNode = (props: NodeProps) => {
|
||||
)
|
||||
}, [props.id])
|
||||
|
||||
const columnsSchema = useCallback(() => [
|
||||
{
|
||||
type: 'switch',
|
||||
name: 'failFast',
|
||||
label: '快速失败',
|
||||
required: true,
|
||||
description: '执行过程中一旦出现错误,及时中断循环任务的执行',
|
||||
},
|
||||
{
|
||||
disabled: true,
|
||||
type: 'switch',
|
||||
name: 'parallel',
|
||||
label: '并行执行',
|
||||
required: true,
|
||||
},
|
||||
{
|
||||
type: 'select',
|
||||
name: 'type',
|
||||
label: '循环模式',
|
||||
required: true,
|
||||
options: [
|
||||
{
|
||||
label: '次数循环',
|
||||
value: 'for',
|
||||
},
|
||||
{
|
||||
label: '次数循环 (引用变量)',
|
||||
value: 'for-variable',
|
||||
},
|
||||
{
|
||||
label: '对象循环',
|
||||
value: 'for-object',
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
visibleOn: '${type === \'for\'}',
|
||||
type: 'input-number',
|
||||
name: 'count',
|
||||
label: '循环次数',
|
||||
required: true,
|
||||
min: 1,
|
||||
precision: 0,
|
||||
},
|
||||
{
|
||||
visibleOn: '${type === \'for-variable\'}',
|
||||
type: 'select',
|
||||
name: 'countVariable',
|
||||
label: '循环次数',
|
||||
required: true,
|
||||
selectMode: 'group',
|
||||
options: generateAllIncomerOutputVariablesFormOptions(
|
||||
props.id,
|
||||
getInputSchema(),
|
||||
getNodes(),
|
||||
getEdges(),
|
||||
getData(),
|
||||
['number'],
|
||||
),
|
||||
},
|
||||
{
|
||||
visibleOn: '${type === \'for-object\'}',
|
||||
type: 'select',
|
||||
name: 'countObject',
|
||||
label: '循环对象',
|
||||
required: true,
|
||||
selectMode: 'group',
|
||||
options: generateAllIncomerOutputVariablesFormOptions(
|
||||
props.id,
|
||||
getInputSchema(),
|
||||
getNodes(),
|
||||
getEdges(),
|
||||
getData(),
|
||||
['array-text', 'array-object'],
|
||||
),
|
||||
},
|
||||
{
|
||||
type: 'divider',
|
||||
},
|
||||
...outputsFormColumns(false, true),
|
||||
], [props.id])
|
||||
const formSchema: () => FormSchema = useCallback(() => ({
|
||||
columns: [
|
||||
{
|
||||
type: 'switch',
|
||||
name: 'failFast',
|
||||
label: '快速失败',
|
||||
required: true,
|
||||
description: '执行过程中一旦出现错误,及时中断循环任务的执行',
|
||||
},
|
||||
{
|
||||
disabled: true,
|
||||
type: 'switch',
|
||||
name: 'parallel',
|
||||
label: '并行执行',
|
||||
required: true,
|
||||
},
|
||||
{
|
||||
type: 'select',
|
||||
name: 'type',
|
||||
label: '循环模式',
|
||||
required: true,
|
||||
options: [
|
||||
{
|
||||
label: '次数循环',
|
||||
value: 'for',
|
||||
},
|
||||
{
|
||||
label: '次数循环 (引用变量)',
|
||||
value: 'for-variable',
|
||||
},
|
||||
{
|
||||
label: '对象循环',
|
||||
value: 'for-object',
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
visibleOn: '${type === \'for\'}',
|
||||
type: 'input-number',
|
||||
name: 'count',
|
||||
label: '循环次数',
|
||||
required: true,
|
||||
min: 1,
|
||||
precision: 0,
|
||||
},
|
||||
{
|
||||
visibleOn: '${type === \'for-variable\'}',
|
||||
type: 'select',
|
||||
name: 'countVariable',
|
||||
label: '循环次数',
|
||||
required: true,
|
||||
selectMode: 'group',
|
||||
options: generateAllIncomerOutputVariablesFormOptions(
|
||||
props.id,
|
||||
getInputSchema(),
|
||||
getNodes(),
|
||||
getEdges(),
|
||||
getData(),
|
||||
['number'],
|
||||
),
|
||||
},
|
||||
{
|
||||
visibleOn: '${type === \'for-object\'}',
|
||||
type: 'select',
|
||||
name: 'countObject',
|
||||
label: '循环对象',
|
||||
required: true,
|
||||
selectMode: 'group',
|
||||
options: generateAllIncomerOutputVariablesFormOptions(
|
||||
props.id,
|
||||
getInputSchema(),
|
||||
getNodes(),
|
||||
getEdges(),
|
||||
getData(),
|
||||
['array-text', 'array-object'],
|
||||
),
|
||||
},
|
||||
{
|
||||
type: 'divider',
|
||||
},
|
||||
...outputsFormColumns(false, true),
|
||||
]
|
||||
}), [props.id])
|
||||
|
||||
const extraNodeDescription = useMemo(() => {
|
||||
return (
|
||||
@@ -142,7 +144,7 @@ const LoopNode = (props: NodeProps) => {
|
||||
}}
|
||||
nodeProps={props}
|
||||
extraNodeDescription={extraNodeDescription}
|
||||
columnSchema={columnsSchema}
|
||||
formSchema={formSchema}
|
||||
handler={<NormalNodeHandler/>}
|
||||
resize={{
|
||||
minWidth: 350,
|
||||
|
||||
@@ -5,14 +5,15 @@ import {useContextStore} from '../store/ContextStore.ts'
|
||||
import {useDataStore} from '../store/DataStore.ts'
|
||||
import {useFlowStore} from '../store/FlowStore.ts'
|
||||
import AmisNode, {EndNodeHandler, nodeClassName} from './AmisNode.tsx'
|
||||
import type {FormSchema} from '../types.ts'
|
||||
|
||||
const OutputNode = (props: NodeProps) => {
|
||||
const {getNodes, getEdges} = useFlowStore()
|
||||
const {getData} = useDataStore()
|
||||
const {getInputSchema} = useContextStore()
|
||||
|
||||
const columnsSchema = useCallback(
|
||||
() => [
|
||||
const formSchema: () => FormSchema = useCallback(() => ({
|
||||
columns: [
|
||||
{
|
||||
type: 'select',
|
||||
name: 'output',
|
||||
@@ -27,13 +28,14 @@ const OutputNode = (props: NodeProps) => {
|
||||
getData(),
|
||||
),
|
||||
},
|
||||
], [props.id])
|
||||
]
|
||||
}), [props.id])
|
||||
|
||||
return (
|
||||
<AmisNode
|
||||
className={nodeClassName('output')}
|
||||
nodeProps={props}
|
||||
columnSchema={columnsSchema}
|
||||
formSchema={formSchema}
|
||||
handler={<EndNodeHandler/>}
|
||||
/>
|
||||
)
|
||||
|
||||
@@ -8,6 +8,7 @@ import {useContextStore} from '../store/ContextStore.ts'
|
||||
import {useDataStore} from '../store/DataStore.ts'
|
||||
import {useFlowStore} from '../store/FlowStore.ts'
|
||||
import AmisNode, {nodeClassName} from './AmisNode.tsx'
|
||||
import type {FormSchema} from '../types.ts'
|
||||
|
||||
const SwitchNode = (props: NodeProps) => {
|
||||
const {getNodes, getEdges, removeEdges} = useFlowStore()
|
||||
@@ -18,32 +19,34 @@ const SwitchNode = (props: NodeProps) => {
|
||||
// @ts-ignore
|
||||
const conditions: ConditionValue[] = nodeData?.conditions?.map(c => c.condition) ?? []
|
||||
|
||||
const columnsSchema = useCallback(() => [
|
||||
{
|
||||
type: 'combo',
|
||||
name: 'conditions',
|
||||
label: '分支',
|
||||
multiple: true,
|
||||
required: true,
|
||||
items: [
|
||||
{
|
||||
type: 'condition-builder',
|
||||
name: 'condition',
|
||||
label: '条件',
|
||||
required: true,
|
||||
builderMode: 'simple',
|
||||
showANDOR: true,
|
||||
fields: generateAllIncomerOutputVariablesConditions(
|
||||
props.id,
|
||||
getInputSchema(),
|
||||
getNodes(),
|
||||
getEdges(),
|
||||
getData(),
|
||||
),
|
||||
},
|
||||
],
|
||||
},
|
||||
], [props.id])
|
||||
const formSchema: () => FormSchema = useCallback(() => ({
|
||||
columns: [
|
||||
{
|
||||
type: 'combo',
|
||||
name: 'conditions',
|
||||
label: '分支',
|
||||
multiple: true,
|
||||
required: true,
|
||||
items: [
|
||||
{
|
||||
type: 'condition-builder',
|
||||
name: 'condition',
|
||||
label: '条件',
|
||||
required: true,
|
||||
builderMode: 'simple',
|
||||
showANDOR: true,
|
||||
fields: generateAllIncomerOutputVariablesConditions(
|
||||
props.id,
|
||||
getInputSchema(),
|
||||
getNodes(),
|
||||
getEdges(),
|
||||
getData(),
|
||||
),
|
||||
},
|
||||
],
|
||||
},
|
||||
]
|
||||
}), [props.id])
|
||||
|
||||
const extraNodeDescription = useMemo(() => {
|
||||
return (
|
||||
@@ -88,7 +91,7 @@ const SwitchNode = (props: NodeProps) => {
|
||||
className={nodeClassName('switch')}
|
||||
nodeProps={props}
|
||||
extraNodeDescription={extraNodeDescription}
|
||||
columnSchema={columnsSchema}
|
||||
formSchema={formSchema}
|
||||
handler={handler}
|
||||
/>
|
||||
)
|
||||
|
||||
@@ -5,6 +5,7 @@ import {useContextStore} from '../store/ContextStore.ts'
|
||||
import {useDataStore} from '../store/DataStore.ts'
|
||||
import {useFlowStore} from '../store/FlowStore.ts'
|
||||
import AmisNode, {inputsFormColumns, nodeClassName, NormalNodeHandler, outputsFormColumns} from './AmisNode.tsx'
|
||||
import type {FormSchema} from '../types.ts'
|
||||
|
||||
const typeMap: Record<string, string> = {
|
||||
default: '默认',
|
||||
@@ -33,8 +34,8 @@ const TemplateNode = (props: NodeProps) => {
|
||||
)
|
||||
}, [props.id])
|
||||
|
||||
const columnsSchema = useCallback(
|
||||
() => [
|
||||
const formSchema: () => FormSchema = useCallback(() => ({
|
||||
columns: [
|
||||
...inputsFormColumns(props.id, getInputSchema(), getNodes(), getEdges(), getData()),
|
||||
{
|
||||
type: 'divider',
|
||||
@@ -69,7 +70,8 @@ const TemplateNode = (props: NodeProps) => {
|
||||
},
|
||||
},
|
||||
...outputsFormColumns(false, true),
|
||||
], [props.id])
|
||||
]
|
||||
}), [props.id])
|
||||
|
||||
const extraNodeDescription = useMemo(() => {
|
||||
return nodeData?.type
|
||||
@@ -85,7 +87,7 @@ const TemplateNode = (props: NodeProps) => {
|
||||
className={nodeClassName('template')}
|
||||
nodeProps={props}
|
||||
extraNodeDescription={extraNodeDescription}
|
||||
columnSchema={columnsSchema}
|
||||
formSchema={formSchema}
|
||||
handler={<NormalNodeHandler/>}
|
||||
/>
|
||||
)
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import type {Edge, Node} from '@xyflow/react'
|
||||
import type {JSX} from 'react'
|
||||
import type {ListenerAction, Schema} from 'amis'
|
||||
|
||||
export const flowBackgroundColor = '#fafafa'
|
||||
export const flowDotColor = '#dedede'
|
||||
@@ -19,7 +20,8 @@ export type NodeError = {
|
||||
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 }
|
||||
|
||||
@@ -47,7 +49,10 @@ export type NodeDefine = {
|
||||
icon: JSX.Element,
|
||||
description: string,
|
||||
component: any,
|
||||
checkers: NodeChecker[],
|
||||
checkers: {
|
||||
add: AddNodeChecker[],
|
||||
save: SaveNodeChecker[],
|
||||
},
|
||||
}
|
||||
|
||||
export type OutputVariable = {
|
||||
@@ -56,3 +61,8 @@ export type OutputVariable = {
|
||||
type: OutputVariableType,
|
||||
variable: string,
|
||||
}
|
||||
|
||||
export type FormSchema = {
|
||||
events?: Record<string, { actions: ListenerAction[] }>
|
||||
columns: Schema[]
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user