205 lines
6.6 KiB
TypeScript
205 lines
6.6 KiB
TypeScript
import {type Edge, getIncomers, type Node} from '@xyflow/react'
|
|
import type {Option} from 'amis/lib/Schema'
|
|
import {find, has, isEqual, max, min, unique} from 'licia'
|
|
import {type DependencyList, type MouseEvent as ReactMouseEvent, useCallback, useRef} from 'react'
|
|
import Queue from 'yocto-queue'
|
|
import {originTypeMap} from '../../pages/ai/task/InputSchema.tsx'
|
|
import {useFlowStore} from './store/FlowStore.ts'
|
|
import {type OutputVariable} from './types.ts'
|
|
|
|
export const getAllIncomerNodeById: (id: string, nodes: Node[], edges: Edge[]) => string[] = (id, nodes, edges) => {
|
|
let queue = new Queue<Node>()
|
|
queue.enqueue(find(nodes, node => isEqual(node.id, id))!)
|
|
let result: string[] = []
|
|
while (queue.size !== 0) {
|
|
let currentNode = queue.dequeue()!
|
|
for (const incomer of getIncomers(currentNode, nodes, edges)) {
|
|
result.push(incomer.id)
|
|
queue.enqueue(incomer)
|
|
}
|
|
}
|
|
return unique(result, (a, b) => isEqual(a, b))
|
|
}
|
|
|
|
export const getAllIncomerNodeOutputVariables: (id: string, inputSchema: Record<string, Record<string, any>>, nodes: Node[], edges: Edge[], data: any) => OutputVariable[] = (id, inputSchema, nodes, edges, data) => {
|
|
let inputSchemaVariables: OutputVariable[] = Object.keys(inputSchema).map(key => ({
|
|
group: '流程入参',
|
|
name: `${key}${inputSchema[key]?.label ? ` (${inputSchema[key].label})` : ''}`,
|
|
type: originTypeMap[inputSchema[key]?.type ?? ''],
|
|
variable: key,
|
|
}))
|
|
|
|
let currentNode = find(nodes, n => isEqual(id, n.id))
|
|
if (!currentNode) {
|
|
return inputSchemaVariables
|
|
}
|
|
|
|
let incomerIds = getAllIncomerNodeById(id, nodes, edges)
|
|
if (currentNode.parentId) {
|
|
incomerIds = [
|
|
...incomerIds,
|
|
...getAllIncomerNodeById(currentNode.parentId, nodes, edges),
|
|
]
|
|
}
|
|
|
|
let incomerVariables: OutputVariable[] = []
|
|
for (const incomerId of incomerIds) {
|
|
let nodeData = data[incomerId] ?? {}
|
|
let group = incomerId
|
|
if (has(nodeData, 'node') && has(nodeData.node, 'name')) {
|
|
group = `${nodeData.node.name} ${incomerId}`
|
|
}
|
|
if (has(nodeData, 'outputs')) {
|
|
let outputs = nodeData?.outputs ?? {}
|
|
for (const key of Object.keys(outputs)) {
|
|
incomerVariables.push({
|
|
group: group,
|
|
name: key,
|
|
type: outputs[key].type,
|
|
variable: `${incomerId}.${key}`,
|
|
})
|
|
}
|
|
}
|
|
}
|
|
|
|
return [
|
|
...inputSchemaVariables,
|
|
...(currentNode.parentId ? [
|
|
{
|
|
group: '循环入参',
|
|
name: 'loopIndex (当前迭代索引)',
|
|
type: 'number',
|
|
variable: 'loopIndex',
|
|
} as OutputVariable,
|
|
{
|
|
group: '循环入参',
|
|
name: 'loopItem (当前迭代对象)',
|
|
type: 'object',
|
|
variable: 'loopItem',
|
|
} as OutputVariable,
|
|
] : []),
|
|
...incomerVariables,
|
|
]
|
|
}
|
|
|
|
export const generateAllIncomerOutputVariablesFormOptions: (id: string, inputSchema: Record<string, Record<string, any>>, nodes: Node[], edges: Edge[], data: any) => Option[] = (id, inputSchema, nodes, edges, data) => {
|
|
let optionMap: Record<string, Option[]> = {}
|
|
for (const item of getAllIncomerNodeOutputVariables(id, inputSchema, nodes, edges, data)) {
|
|
if (!optionMap[item.group]) {
|
|
optionMap[item.group] = []
|
|
}
|
|
optionMap[item.group].push({
|
|
label: item.name,
|
|
value: item.variable,
|
|
})
|
|
}
|
|
return Object.keys(optionMap)
|
|
.map(key => ({
|
|
label: key,
|
|
children: optionMap[key],
|
|
}))
|
|
}
|
|
|
|
type ConditionOperator = string | { label: string, value: string }
|
|
const textOperators: ConditionOperator[] = ['equal', 'not_equal', 'is_empty', 'is_not_empty', 'like', 'not_like', 'starts_with', 'ends_with']
|
|
const textDefaultOperator: string = 'equal'
|
|
const booleanOperators: ConditionOperator[] = [
|
|
{label: '为真', value: 'is_true'},
|
|
{label: '为假', value: 'is_false'},
|
|
]
|
|
const booleanDefaultOperator: string = 'is_true'
|
|
const numberOperators: ConditionOperator[] = [
|
|
'equal',
|
|
'not_equal',
|
|
{label: '大于', value: 'greater'},
|
|
{label: '大于或等于', value: 'greater_equal'},
|
|
{label: '小于', value: 'less'},
|
|
{label: '小于或等于', value: 'less_equal'},
|
|
]
|
|
const numberDefaultOperator: string = 'equal'
|
|
|
|
export const generateAllIncomerOutputVariablesConditions: (id: string, inputSchema: Record<string, Record<string, any>>, nodes: Node[], edges: Edge[], data: any) => Option[] = (id, inputSchema, nodes, edges, data) => {
|
|
let optionMap: Record<string, Option[]> = {}
|
|
for (const item of getAllIncomerNodeOutputVariables(id, inputSchema, nodes, edges, data)) {
|
|
if (!optionMap[item.group]) {
|
|
optionMap[item.group] = []
|
|
}
|
|
optionMap[item.group].push({
|
|
label: item.name,
|
|
type: 'custom',
|
|
name: item.variable,
|
|
...(item.type === 'text' ? {
|
|
value: {
|
|
type: 'input-text',
|
|
required: true,
|
|
clearable: true,
|
|
},
|
|
defaultOp: textDefaultOperator,
|
|
operators: textOperators,
|
|
} : {}),
|
|
...(item.type === 'boolean' ? {
|
|
value: {
|
|
type: 'wrapper',
|
|
size: 'none',
|
|
},
|
|
defaultOp: booleanDefaultOperator,
|
|
operators: booleanOperators,
|
|
} : {}),
|
|
...(item.type === 'number' ? {
|
|
value: {
|
|
type: 'input-number',
|
|
required: true,
|
|
clearable: true,
|
|
},
|
|
defaultOp: numberDefaultOperator,
|
|
operators: numberOperators,
|
|
} : {}),
|
|
})
|
|
}
|
|
return Object.keys(optionMap)
|
|
.map(key => ({
|
|
label: key,
|
|
children: optionMap[key],
|
|
}))
|
|
}
|
|
|
|
// 处理循环节点的边界问题
|
|
export const useNodeDrag = (deps: DependencyList) => {
|
|
const currentPosition = useRef({x: 0, y: 0} as { x: number, y: number })
|
|
const {setNode, getNodeById} = useFlowStore()
|
|
|
|
const onNodeDragStart = useCallback(() => {
|
|
}, deps)
|
|
const onNodeDrag = useCallback((event: ReactMouseEvent, node: Node) => {
|
|
event.stopPropagation()
|
|
if (node.parentId) {
|
|
let parentNode = getNodeById(node.parentId)
|
|
if (parentNode) {
|
|
let newPosition = {
|
|
x: max(min(node.position.x, (parentNode.measured?.width ?? 0) - (node.measured?.width ?? 0) - 28), 28),
|
|
y: max(min(node.position.y, (parentNode.measured?.height ?? 0) - (node.measured?.height ?? 0) - 28), 130),
|
|
}
|
|
setNode({
|
|
...node,
|
|
position: newPosition,
|
|
})
|
|
currentPosition.current = newPosition
|
|
}
|
|
}
|
|
}, deps)
|
|
const onNodeDragEnd = useCallback((_event: ReactMouseEvent, node: Node) => {
|
|
if (node.parentId) {
|
|
setNode({
|
|
...node,
|
|
position: currentPosition.current,
|
|
})
|
|
}
|
|
}, deps)
|
|
|
|
return {
|
|
onNodeDragStart,
|
|
onNodeDrag,
|
|
onNodeDragEnd,
|
|
}
|
|
}
|