refactor: 重构流程编排相关功能
- 新增 src/features/workflow/ 目录,按职责拆成:
- domain/:工作流类型、customDef 解析工具。
- adapters/:流程数据转换逻辑。
- registry/:节点注册和节点描述。
- operations/:连接、复制粘贴、删除、节点插入、循环节点等图操作。
- persistence/:流程保存、校验、事件同步、缓存更新。
- runtime/:运行、停止、暂停/恢复、重跑。
- 将原 src/utils/convertFlowData.ts、convertAppFlowData.ts、src/utils/flow/* 改成兼容 re-export,老路径仍可用。
- useFlowCallbacks.ts 大幅瘦身,从约 1617 行降到 867 行,把保存、运行、连接、复制粘贴、循环删除等逻辑迁出。
- FlowEditorMain.tsx 中循环节点成对删除逻辑迁到 deleteOperations。
- 节点注册开始走 nodeDescriptors,为后续新增节点统一入口打基础。
- nodeValidators.ts 开始使用统一的 parseCustomDef,减少散落的 JSON.parse。
refactor
parent
2a21018544
commit
3396cf3d62
File diff suppressed because it is too large
Load Diff
@ -0,0 +1,28 @@
|
|||||||
|
export type CustomDefValue = Record<string, any>;
|
||||||
|
|
||||||
|
export const parseCustomDef = (
|
||||||
|
value: unknown,
|
||||||
|
fallback: CustomDefValue | null = {}
|
||||||
|
): CustomDefValue | null => {
|
||||||
|
if (!value) return fallback;
|
||||||
|
if (typeof value === 'object') return value as CustomDefValue;
|
||||||
|
if (typeof value !== 'string') return fallback;
|
||||||
|
|
||||||
|
try {
|
||||||
|
const parsed = JSON.parse(value);
|
||||||
|
return parsed && typeof parsed === 'object' ? parsed : fallback;
|
||||||
|
} catch {
|
||||||
|
return fallback;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
export const stringifyCustomDef = (value: unknown): string => {
|
||||||
|
if (!value) return '{}';
|
||||||
|
if (typeof value === 'string') return value;
|
||||||
|
return JSON.stringify(value);
|
||||||
|
};
|
||||||
|
|
||||||
|
export const getNodeCustomDef = (
|
||||||
|
nodeData: { component?: { customDef?: unknown } } | undefined,
|
||||||
|
fallback: CustomDefValue = {}
|
||||||
|
) => parseCustomDef(nodeData?.component?.customDef, fallback);
|
||||||
@ -0,0 +1,66 @@
|
|||||||
|
import { Edge, Node } from '@xyflow/react';
|
||||||
|
|
||||||
|
export type WorkflowMode = 'component' | 'application';
|
||||||
|
|
||||||
|
export type WorkflowPortKind = 'apiIn' | 'apiOut' | 'dataIn' | 'dataOut';
|
||||||
|
|
||||||
|
export type WorkflowLineType = 'api' | 'data' | 'convert';
|
||||||
|
|
||||||
|
export interface WorkflowPort {
|
||||||
|
id?: string;
|
||||||
|
name?: string;
|
||||||
|
desc?: string;
|
||||||
|
dataType?: string;
|
||||||
|
defaultValue?: any;
|
||||||
|
arrayType?: string | null;
|
||||||
|
topic?: string;
|
||||||
|
eventId?: string;
|
||||||
|
eventName?: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface WorkflowPorts {
|
||||||
|
apiIns: WorkflowPort[];
|
||||||
|
apiOuts: WorkflowPort[];
|
||||||
|
dataIns: WorkflowPort[];
|
||||||
|
dataOuts: WorkflowPort[];
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface WorkflowComponentRef {
|
||||||
|
type?: string;
|
||||||
|
compId?: string;
|
||||||
|
compIdentifier?: string;
|
||||||
|
compInstanceIdentifier?: string;
|
||||||
|
customDef?: Record<string, any> | string;
|
||||||
|
[key: string]: any;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface WorkflowNodeData {
|
||||||
|
title: string;
|
||||||
|
type: string;
|
||||||
|
parameters: WorkflowPorts;
|
||||||
|
component?: WorkflowComponentRef;
|
||||||
|
compId?: string;
|
||||||
|
status?: string;
|
||||||
|
isStatusVisible?: boolean;
|
||||||
|
[key: string]: any;
|
||||||
|
}
|
||||||
|
|
||||||
|
export type WorkflowNode = Node<WorkflowNodeData>;
|
||||||
|
|
||||||
|
export type WorkflowEdge = Edge<{
|
||||||
|
lineType?: WorkflowLineType;
|
||||||
|
displayData?: Record<string, any>;
|
||||||
|
[key: string]: any;
|
||||||
|
}>;
|
||||||
|
|
||||||
|
export interface WorkflowGraph {
|
||||||
|
id?: string;
|
||||||
|
mode: WorkflowMode;
|
||||||
|
nodes: WorkflowNode[];
|
||||||
|
edges: WorkflowEdge[];
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface FlowConvertResult {
|
||||||
|
nodes: WorkflowNode[];
|
||||||
|
edges: WorkflowEdge[];
|
||||||
|
}
|
||||||
@ -0,0 +1,114 @@
|
|||||||
|
import { Edge, Node } from '@xyflow/react';
|
||||||
|
|
||||||
|
export interface CopiedFlowData {
|
||||||
|
nodes: Node[];
|
||||||
|
edges: Edge[];
|
||||||
|
appId?: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export const buildCopiedFlowData = (
|
||||||
|
activeNode: Node,
|
||||||
|
nodes: Node[],
|
||||||
|
edges: Edge[],
|
||||||
|
appId?: string
|
||||||
|
): CopiedFlowData | null => {
|
||||||
|
const selectedNodes = nodes.filter(
|
||||||
|
(node) => node.selected || node.id === activeNode.id
|
||||||
|
);
|
||||||
|
const nodesToCopy = selectedNodes.filter(
|
||||||
|
(node) => node.type !== 'start' && node.type !== 'end'
|
||||||
|
);
|
||||||
|
|
||||||
|
if (nodesToCopy.length === 0) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
const nodeIds = new Set(nodesToCopy.map((node) => node.id));
|
||||||
|
const edgesToCopy = edges.filter(
|
||||||
|
(edge) => nodeIds.has(edge.source) && nodeIds.has(edge.target)
|
||||||
|
);
|
||||||
|
|
||||||
|
return {
|
||||||
|
nodes: nodesToCopy.map((node) => ({
|
||||||
|
...node,
|
||||||
|
selected: false,
|
||||||
|
dragging: false,
|
||||||
|
})),
|
||||||
|
edges: edgesToCopy.map((edge) => ({
|
||||||
|
...edge,
|
||||||
|
selected: false,
|
||||||
|
})),
|
||||||
|
appId,
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
export const buildPastedFlowData = (
|
||||||
|
copiedData: CopiedFlowData,
|
||||||
|
position: { x: number; y: number },
|
||||||
|
timestamp = Date.now()
|
||||||
|
) => {
|
||||||
|
const copiedNodes = copiedData.nodes || [];
|
||||||
|
const copiedEdges = copiedData.edges || [];
|
||||||
|
|
||||||
|
if (copiedNodes.length === 0) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
const minX = Math.min(...copiedNodes.map((node) => node.position.x));
|
||||||
|
const minY = Math.min(...copiedNodes.map((node) => node.position.y));
|
||||||
|
const maxX = Math.max(...copiedNodes.map((node) => node.position.x));
|
||||||
|
const maxY = Math.max(...copiedNodes.map((node) => node.position.y));
|
||||||
|
const centerX = (minX + maxX) / 2;
|
||||||
|
const centerY = (minY + maxY) / 2;
|
||||||
|
const offsetX = position.x - centerX;
|
||||||
|
const offsetY = position.y - centerY;
|
||||||
|
const idMap = new Map<string, string>();
|
||||||
|
|
||||||
|
const nodes = copiedNodes.map((node, index) => {
|
||||||
|
const newId = `${node.type}-${timestamp}-${index}`;
|
||||||
|
idMap.set(node.id, newId);
|
||||||
|
|
||||||
|
return {
|
||||||
|
...node,
|
||||||
|
id: newId,
|
||||||
|
position: {
|
||||||
|
x: node.position.x + offsetX,
|
||||||
|
y: node.position.y + offsetY,
|
||||||
|
},
|
||||||
|
selected: false,
|
||||||
|
dragging: false,
|
||||||
|
appId: undefined,
|
||||||
|
};
|
||||||
|
});
|
||||||
|
|
||||||
|
const edges = copiedEdges
|
||||||
|
.map((edge, index) => {
|
||||||
|
const source = idMap.get(edge.source);
|
||||||
|
const target = idMap.get(edge.target);
|
||||||
|
if (!source || !target) return null;
|
||||||
|
|
||||||
|
return {
|
||||||
|
...edge,
|
||||||
|
id: `e${source}-${target}-${timestamp}-${index}`,
|
||||||
|
source,
|
||||||
|
target,
|
||||||
|
selected: false,
|
||||||
|
};
|
||||||
|
})
|
||||||
|
.filter(Boolean) as Edge[];
|
||||||
|
|
||||||
|
return { nodes, edges };
|
||||||
|
};
|
||||||
|
|
||||||
|
export const buildPastedSingleNode = (
|
||||||
|
copiedNode: Node,
|
||||||
|
position: { x: number; y: number },
|
||||||
|
timestamp = Date.now()
|
||||||
|
) => ({
|
||||||
|
...copiedNode,
|
||||||
|
id: `${copiedNode.type}-${timestamp}`,
|
||||||
|
position,
|
||||||
|
selected: false,
|
||||||
|
dragging: false,
|
||||||
|
appId: undefined,
|
||||||
|
});
|
||||||
@ -0,0 +1,139 @@
|
|||||||
|
import { Connection, Edge, Node } from '@xyflow/react';
|
||||||
|
import { getHandleType, validateDataType } from '@/utils/flowCommon';
|
||||||
|
|
||||||
|
export interface ConnectionValidationResult {
|
||||||
|
isValid: boolean;
|
||||||
|
lineType?: string;
|
||||||
|
message?: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export const validateWorkflowConnection = (
|
||||||
|
nodes: Node[],
|
||||||
|
connection: Connection
|
||||||
|
): ConnectionValidationResult => {
|
||||||
|
const sourceNode = nodes.find((node) => node.id === connection.source);
|
||||||
|
const targetNode = nodes.find((node) => node.id === connection.target);
|
||||||
|
|
||||||
|
if (!sourceNode || !targetNode) {
|
||||||
|
return { isValid: false };
|
||||||
|
}
|
||||||
|
|
||||||
|
if (sourceNode.id === targetNode.id) {
|
||||||
|
return { isValid: false, message: '不允许自旋链接' };
|
||||||
|
}
|
||||||
|
|
||||||
|
const sourceParams: any = sourceNode.data?.parameters || {};
|
||||||
|
const targetParams: any = targetNode.data?.parameters || {};
|
||||||
|
const sourceHandleType = getHandleType(connection.sourceHandle, sourceParams);
|
||||||
|
const targetHandleType = getHandleType(connection.targetHandle, targetParams);
|
||||||
|
|
||||||
|
if (sourceHandleType !== targetHandleType) {
|
||||||
|
return {
|
||||||
|
isValid: false,
|
||||||
|
message: `连接类型不匹配: ${sourceHandleType}, ${targetHandleType}`,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
if (
|
||||||
|
!validateDataType(
|
||||||
|
sourceNode,
|
||||||
|
targetNode,
|
||||||
|
connection.sourceHandle,
|
||||||
|
connection.targetHandle
|
||||||
|
)
|
||||||
|
) {
|
||||||
|
return { isValid: false, message: '数据类型不匹配' };
|
||||||
|
}
|
||||||
|
|
||||||
|
return { isValid: true, lineType: sourceHandleType };
|
||||||
|
};
|
||||||
|
|
||||||
|
export const buildWorkflowConnectionEdge = (
|
||||||
|
nodes: Node[],
|
||||||
|
connection: Connection,
|
||||||
|
lineType: string
|
||||||
|
) => {
|
||||||
|
const sourceNode = nodes.find((node) => node.id === connection.source);
|
||||||
|
const targetNode = nodes.find((node) => node.id === connection.target);
|
||||||
|
if (!sourceNode || !targetNode) return null;
|
||||||
|
|
||||||
|
const sourceParams: any = sourceNode.data?.parameters || {};
|
||||||
|
const targetParams: any = targetNode.data?.parameters || {};
|
||||||
|
|
||||||
|
if (lineType === 'data') {
|
||||||
|
const sourceDataOut = (sourceParams.dataOuts || []).find(
|
||||||
|
(dataOut: any) =>
|
||||||
|
dataOut.name === connection.sourceHandle ||
|
||||||
|
dataOut.id === connection.sourceHandle
|
||||||
|
);
|
||||||
|
const targetDataIn = (targetParams.dataIns || []).find(
|
||||||
|
(dataIn: any) =>
|
||||||
|
dataIn.name === connection.targetHandle ||
|
||||||
|
dataIn.id === connection.targetHandle
|
||||||
|
);
|
||||||
|
|
||||||
|
if (
|
||||||
|
sourceDataOut &&
|
||||||
|
targetDataIn &&
|
||||||
|
sourceDataOut.dataType !== targetDataIn.dataType
|
||||||
|
) {
|
||||||
|
return {
|
||||||
|
edge: null,
|
||||||
|
message: `数据类型不匹配,源节点数据类型: ${sourceDataOut.dataType},目标节点数据类型: ${targetDataIn.dataType}`,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const edgeParams: Edge = {
|
||||||
|
...connection,
|
||||||
|
id:
|
||||||
|
connection.source && connection.target
|
||||||
|
? `e${connection.source}-${connection.target}-${connection.sourceHandle}-${connection.targetHandle}`
|
||||||
|
: '',
|
||||||
|
type: 'custom',
|
||||||
|
data: {
|
||||||
|
...(connection as any).data,
|
||||||
|
lineType,
|
||||||
|
},
|
||||||
|
} as Edge;
|
||||||
|
|
||||||
|
const sourceApi = (sourceParams.apiOuts || []).find(
|
||||||
|
(api: any) =>
|
||||||
|
(api?.eventId || api.name || api.id) === connection.sourceHandle
|
||||||
|
);
|
||||||
|
const targetApi = (targetParams.apiIns || []).find(
|
||||||
|
(api: any) =>
|
||||||
|
(api?.eventId || api.name || api.id) === connection.targetHandle
|
||||||
|
);
|
||||||
|
|
||||||
|
if (sourceApi?.topic) {
|
||||||
|
if (
|
||||||
|
!targetApi ||
|
||||||
|
!targetApi.topic ||
|
||||||
|
targetApi.topic.includes('**empty**') ||
|
||||||
|
!sourceApi.topic.includes('**empty**')
|
||||||
|
) {
|
||||||
|
edgeParams.data = {
|
||||||
|
...edgeParams.data,
|
||||||
|
lineType: 'api',
|
||||||
|
displayData: {
|
||||||
|
name: sourceApi.eventName,
|
||||||
|
eventId: sourceApi.eventId,
|
||||||
|
topic: sourceApi.topic,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
}
|
||||||
|
} else if (targetApi?.topic && !targetApi.topic.includes('**empty**')) {
|
||||||
|
edgeParams.data = {
|
||||||
|
...edgeParams.data,
|
||||||
|
lineType: 'api',
|
||||||
|
displayData: {
|
||||||
|
name: targetApi.eventName,
|
||||||
|
eventId: targetApi.eventId,
|
||||||
|
topic: targetApi.topic,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
return { edge: edgeParams };
|
||||||
|
};
|
||||||
@ -0,0 +1,49 @@
|
|||||||
|
import { Edge, Node } from '@xyflow/react';
|
||||||
|
import { parseCustomDef } from '@/features/workflow/domain/customDef';
|
||||||
|
|
||||||
|
export const isLoopBoundaryNode = (node: Node) => {
|
||||||
|
return node.data?.type === 'LOOP_START' || node.data?.type === 'LOOP_END';
|
||||||
|
};
|
||||||
|
|
||||||
|
export const resolveDeletedNodesWithLoopPairs = (
|
||||||
|
deletedNodes: Node[],
|
||||||
|
allNodes: Node[]
|
||||||
|
) => {
|
||||||
|
const nodesToRemove = [...deletedNodes];
|
||||||
|
|
||||||
|
deletedNodes.filter(isLoopBoundaryNode).forEach((loopNode) => {
|
||||||
|
const component = loopNode.data?.component as { customDef?: unknown } | undefined;
|
||||||
|
const customDef = parseCustomDef(component?.customDef) || {};
|
||||||
|
const relatedNodeId =
|
||||||
|
loopNode.data?.type === 'LOOP_START'
|
||||||
|
? customDef.loopEndNodeId
|
||||||
|
: customDef.loopStartNodeId;
|
||||||
|
|
||||||
|
if (!relatedNodeId) return;
|
||||||
|
|
||||||
|
const relatedNode = allNodes.find((node) => node.id === relatedNodeId);
|
||||||
|
if (relatedNode) {
|
||||||
|
nodesToRemove.push(relatedNode);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
return nodesToRemove.filter(
|
||||||
|
(node, index, self) => index === self.findIndex((item) => item.id === node.id)
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export const removeNodesAndConnectedEdges = (
|
||||||
|
allNodes: Node[],
|
||||||
|
allEdges: Edge[],
|
||||||
|
nodesToRemove: Node[]
|
||||||
|
) => {
|
||||||
|
const nodeIdsToRemove = new Set(nodesToRemove.map((node) => node.id));
|
||||||
|
|
||||||
|
return {
|
||||||
|
nodes: allNodes.filter((node) => !nodeIdsToRemove.has(node.id)),
|
||||||
|
edges: allEdges.filter(
|
||||||
|
(edge) =>
|
||||||
|
!nodeIdsToRemove.has(edge.source) && !nodeIdsToRemove.has(edge.target)
|
||||||
|
),
|
||||||
|
};
|
||||||
|
};
|
||||||
@ -0,0 +1,54 @@
|
|||||||
|
import { Edge } from '@xyflow/react';
|
||||||
|
|
||||||
|
export const resolveInsertedNodeHandles = (node: any) => {
|
||||||
|
let sourceHandle = 'done';
|
||||||
|
let targetHandle = 'start';
|
||||||
|
|
||||||
|
if (node?.data?.parameters) {
|
||||||
|
const { apiOuts, apiIns } = node.data.parameters;
|
||||||
|
|
||||||
|
if (apiOuts && apiOuts.length > 0) {
|
||||||
|
sourceHandle = apiOuts[0].name || apiOuts[0].id || sourceHandle;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (apiIns && apiIns.length > 0) {
|
||||||
|
targetHandle = apiIns[0].name || apiIns[0].id || targetHandle;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return { sourceHandle, targetHandle };
|
||||||
|
};
|
||||||
|
|
||||||
|
export const buildInsertedNodeEdges = (params: {
|
||||||
|
baseEdges: Edge[];
|
||||||
|
removedEdgeId: string;
|
||||||
|
sourceId: string;
|
||||||
|
sourceHandle?: string;
|
||||||
|
targetId: string;
|
||||||
|
targetHandle?: string;
|
||||||
|
insertedNodeId: string;
|
||||||
|
insertedNodeSourceHandle: string;
|
||||||
|
insertedNodeTargetHandle: string;
|
||||||
|
}): Edge[] => [
|
||||||
|
...params.baseEdges.filter((e) => e.id !== params.removedEdgeId),
|
||||||
|
{
|
||||||
|
id: `e${params.sourceId}-${params.insertedNodeId}`,
|
||||||
|
source: params.sourceId,
|
||||||
|
target: params.insertedNodeId,
|
||||||
|
sourceHandle: params.sourceHandle,
|
||||||
|
targetHandle: params.insertedNodeTargetHandle,
|
||||||
|
type: 'custom',
|
||||||
|
lineType: 'api',
|
||||||
|
data: { lineType: 'api' },
|
||||||
|
} as Edge,
|
||||||
|
{
|
||||||
|
id: `e${params.insertedNodeId}-${params.targetId}`,
|
||||||
|
source: params.insertedNodeId,
|
||||||
|
target: params.targetId,
|
||||||
|
sourceHandle: params.insertedNodeSourceHandle,
|
||||||
|
targetHandle: params.targetHandle,
|
||||||
|
type: 'custom',
|
||||||
|
lineType: 'api',
|
||||||
|
data: { lineType: 'api' },
|
||||||
|
} as Edge,
|
||||||
|
];
|
||||||
@ -0,0 +1,104 @@
|
|||||||
|
import { Edge } from '@xyflow/react';
|
||||||
|
|
||||||
|
export const createLoopNodePair = (position: { x: number; y: number }) => {
|
||||||
|
const loopStartNode: any = {
|
||||||
|
id: `LOOP_START-${Date.now()}`,
|
||||||
|
type: 'LOOP',
|
||||||
|
position: { x: position.x, y: position.y },
|
||||||
|
data: {
|
||||||
|
title: '循环开始',
|
||||||
|
type: 'LOOP_START',
|
||||||
|
parameters: {
|
||||||
|
apiIns: [{ name: 'start', desc: '', dataType: '', defaultValue: '' }],
|
||||||
|
apiOuts: [{ name: 'done', desc: '', dataType: '', defaultValue: '' }],
|
||||||
|
dataIns: [],
|
||||||
|
dataOuts: [],
|
||||||
|
},
|
||||||
|
component: {},
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
const loopEndNode: any = {
|
||||||
|
id: `LOOP_END-${Date.now()}`,
|
||||||
|
type: 'LOOP',
|
||||||
|
position: { x: position.x + 400, y: position.y },
|
||||||
|
data: {
|
||||||
|
title: '循环结束',
|
||||||
|
type: 'LOOP_END',
|
||||||
|
parameters: {
|
||||||
|
apiIns: [
|
||||||
|
{ name: 'continue', desc: '', dataType: '', defaultValue: '' },
|
||||||
|
],
|
||||||
|
apiOuts: [{ name: 'break', desc: '', dataType: '', defaultValue: '' }],
|
||||||
|
dataIns: [
|
||||||
|
{
|
||||||
|
arrayType: null,
|
||||||
|
dataType: 'INTEGER',
|
||||||
|
defaultValue: 10,
|
||||||
|
desc: '最大循环次数',
|
||||||
|
id: 'maxTime',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
dataOuts: [],
|
||||||
|
},
|
||||||
|
component: {
|
||||||
|
type: 'LOOP_END',
|
||||||
|
customDef: JSON.stringify({
|
||||||
|
apiOutIds: ['continue', 'break'],
|
||||||
|
conditions: [],
|
||||||
|
loopStartNodeId: loopStartNode.id,
|
||||||
|
}),
|
||||||
|
loopStartNodeId: loopStartNode.id,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
loopStartNode.data.component = {
|
||||||
|
type: 'LOOP_START',
|
||||||
|
customDef: JSON.stringify({ loopEndNodeId: loopEndNode.id }),
|
||||||
|
};
|
||||||
|
|
||||||
|
return { loopStartNode, loopEndNode };
|
||||||
|
};
|
||||||
|
|
||||||
|
export const createLoopGroupEdge = (
|
||||||
|
loopStartId: string,
|
||||||
|
loopEndId: string
|
||||||
|
): Edge => ({
|
||||||
|
id: `${loopStartId}-${loopEndId}-group`,
|
||||||
|
source: loopStartId,
|
||||||
|
target: loopEndId,
|
||||||
|
sourceHandle: `${loopStartId}-group`,
|
||||||
|
targetHandle: `${loopEndId}-group`,
|
||||||
|
type: 'custom',
|
||||||
|
});
|
||||||
|
|
||||||
|
export const createLoopInsertConnectionEdges = (params: {
|
||||||
|
sourceId: string;
|
||||||
|
sourceHandle: string;
|
||||||
|
targetId: string;
|
||||||
|
targetHandle: string;
|
||||||
|
loopStartId: string;
|
||||||
|
loopEndId: string;
|
||||||
|
}): Edge[] => [
|
||||||
|
{
|
||||||
|
id: `e${params.sourceId}-${params.loopStartId}`,
|
||||||
|
source: params.sourceId,
|
||||||
|
target: params.loopStartId,
|
||||||
|
sourceHandle: params.sourceHandle,
|
||||||
|
targetHandle: 'start',
|
||||||
|
type: 'custom',
|
||||||
|
lineType: 'api',
|
||||||
|
data: { lineType: 'api' },
|
||||||
|
} as Edge,
|
||||||
|
{
|
||||||
|
id: `e${params.loopEndId}-${params.targetId}`,
|
||||||
|
source: params.loopEndId,
|
||||||
|
target: params.targetId,
|
||||||
|
sourceHandle: 'break',
|
||||||
|
targetHandle: params.targetHandle,
|
||||||
|
type: 'custom',
|
||||||
|
lineType: 'api',
|
||||||
|
data: { lineType: 'api' },
|
||||||
|
} as Edge,
|
||||||
|
];
|
||||||
@ -0,0 +1,79 @@
|
|||||||
|
import { Node } from '@xyflow/react';
|
||||||
|
|
||||||
|
interface FlowNodeDefinition {
|
||||||
|
nodeName: string;
|
||||||
|
data: any;
|
||||||
|
id?: string;
|
||||||
|
flowHousVO?: {
|
||||||
|
id?: string;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
type EventIdMode = 'id' | 'eventIdOptional';
|
||||||
|
|
||||||
|
export const createFlowNode = (
|
||||||
|
nodeType: string,
|
||||||
|
nodeDefinition: FlowNodeDefinition,
|
||||||
|
position: { x: number; y: number }
|
||||||
|
): Node => {
|
||||||
|
const node: any = {
|
||||||
|
id: `${nodeType}-${Date.now()}`,
|
||||||
|
type: nodeType,
|
||||||
|
position,
|
||||||
|
data: {
|
||||||
|
...nodeDefinition.data,
|
||||||
|
title: nodeDefinition.nodeName,
|
||||||
|
type: nodeType,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
if (nodeDefinition.id || nodeDefinition?.flowHousVO?.id) {
|
||||||
|
node.data.compId = nodeDefinition.id || nodeDefinition?.flowHousVO?.id;
|
||||||
|
}
|
||||||
|
|
||||||
|
return node;
|
||||||
|
};
|
||||||
|
|
||||||
|
export const attachFlowNodeComponent = (
|
||||||
|
node: any,
|
||||||
|
nodeType: string,
|
||||||
|
nodeDefinition: FlowNodeDefinition,
|
||||||
|
eventList: any[],
|
||||||
|
eventIdMode: EventIdMode
|
||||||
|
) => {
|
||||||
|
if (nodeType === 'SWITCH') {
|
||||||
|
node.data.component = {
|
||||||
|
customDef: JSON.stringify({
|
||||||
|
apiOutIds: ['default'],
|
||||||
|
conditions: [],
|
||||||
|
}),
|
||||||
|
};
|
||||||
|
} else if (nodeType === 'SUB') {
|
||||||
|
node.data.component = {
|
||||||
|
type: nodeType,
|
||||||
|
compId: node.data.compId,
|
||||||
|
};
|
||||||
|
} else if (nodeType === 'EVENTSEND' || nodeType === 'EVENTLISTENE') {
|
||||||
|
const emptyEvent = eventList.find((item) =>
|
||||||
|
item.topic.includes('**empty**')
|
||||||
|
);
|
||||||
|
node.data.component = {
|
||||||
|
type: nodeType,
|
||||||
|
customDef: {
|
||||||
|
eventId:
|
||||||
|
eventIdMode === 'eventIdOptional'
|
||||||
|
? emptyEvent?.eventId ?? null
|
||||||
|
: emptyEvent.id,
|
||||||
|
name: emptyEvent.name,
|
||||||
|
topic: emptyEvent.topic,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
} else {
|
||||||
|
node.data.component = {
|
||||||
|
type: nodeType,
|
||||||
|
compId: nodeDefinition.id,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
return node;
|
||||||
|
};
|
||||||
@ -0,0 +1,61 @@
|
|||||||
|
import {
|
||||||
|
createFlowNode,
|
||||||
|
attachFlowNodeComponent,
|
||||||
|
} from '@/features/workflow/operations/nodeFactory';
|
||||||
|
import {
|
||||||
|
ensureNodeTypeRegistered,
|
||||||
|
resolveNodeComponent,
|
||||||
|
} from '@/features/workflow/registry/nodeRegistry';
|
||||||
|
|
||||||
|
type EventIdMode = 'id' | 'eventIdOptional';
|
||||||
|
|
||||||
|
export const resolveNodeDefinition = (
|
||||||
|
nodeList: any[],
|
||||||
|
nodeType: string,
|
||||||
|
fallbackNode?: any
|
||||||
|
) => {
|
||||||
|
return nodeList.find((n) => n.nodeType === nodeType) || fallbackNode || null;
|
||||||
|
};
|
||||||
|
|
||||||
|
export const buildRuntimeNode = (params: {
|
||||||
|
nodeType: string;
|
||||||
|
nodeDefinition: any;
|
||||||
|
position: { x: number; y: number };
|
||||||
|
eventList: any[];
|
||||||
|
eventIdMode: EventIdMode;
|
||||||
|
}) => {
|
||||||
|
const { nodeType, nodeDefinition, position, eventList, eventIdMode } = params;
|
||||||
|
|
||||||
|
const newNode = attachFlowNodeComponent(
|
||||||
|
createFlowNode(nodeType, nodeDefinition, position),
|
||||||
|
nodeType,
|
||||||
|
nodeDefinition,
|
||||||
|
eventList,
|
||||||
|
eventIdMode
|
||||||
|
);
|
||||||
|
|
||||||
|
ensureNodeTypeRegistered(
|
||||||
|
nodeType,
|
||||||
|
nodeDefinition.nodeName,
|
||||||
|
resolveNodeComponent(nodeType)
|
||||||
|
);
|
||||||
|
|
||||||
|
return newNode;
|
||||||
|
};
|
||||||
|
|
||||||
|
export const buildDroppedNode = (
|
||||||
|
nodeData: any,
|
||||||
|
position: { x: number; y: number }
|
||||||
|
) => {
|
||||||
|
ensureNodeTypeRegistered(nodeData.nodeType, nodeData.nodeName);
|
||||||
|
return {
|
||||||
|
id: `${nodeData.nodeType}-${Date.now()}`,
|
||||||
|
type: nodeData.nodeType,
|
||||||
|
position,
|
||||||
|
data: {
|
||||||
|
...nodeData.data,
|
||||||
|
title: nodeData.nodeName,
|
||||||
|
type: nodeData.nodeType,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
};
|
||||||
@ -0,0 +1,22 @@
|
|||||||
|
import { Edge, Node } from '@xyflow/react';
|
||||||
|
|
||||||
|
interface FlowSnapshotDetail {
|
||||||
|
nodes: Node[];
|
||||||
|
edges: Edge[];
|
||||||
|
}
|
||||||
|
|
||||||
|
export const dispatchFlowSnapshot = (detail: FlowSnapshotDetail) => {
|
||||||
|
const event = new CustomEvent('takeSnapshot', {
|
||||||
|
detail,
|
||||||
|
});
|
||||||
|
document.dispatchEvent(event);
|
||||||
|
};
|
||||||
|
|
||||||
|
export const dispatchFlowSnapshotAsync = (
|
||||||
|
detail: FlowSnapshotDetail,
|
||||||
|
delay = 0
|
||||||
|
) => {
|
||||||
|
return setTimeout(() => {
|
||||||
|
dispatchFlowSnapshot(detail);
|
||||||
|
}, delay);
|
||||||
|
};
|
||||||
@ -0,0 +1,266 @@
|
|||||||
|
import React from 'react';
|
||||||
|
import { Edge, Node } from '@xyflow/react';
|
||||||
|
import { Message } from '@arco-design/web-react';
|
||||||
|
import { Dispatch } from 'redux';
|
||||||
|
import { getAppInfoNew, setMainFlowNew, setSubFlowNew } from '@/api/appRes';
|
||||||
|
import {
|
||||||
|
updateAppEventChannel,
|
||||||
|
updateAppFlowData,
|
||||||
|
} from '@/api/appEvent';
|
||||||
|
import {
|
||||||
|
deleteEventPub,
|
||||||
|
deleteEventSub,
|
||||||
|
queryEventItemBySceneIdOld,
|
||||||
|
} from '@/api/event';
|
||||||
|
import {
|
||||||
|
updateCanvasDataMap,
|
||||||
|
updateEventListOld,
|
||||||
|
updateFlowData,
|
||||||
|
} from '@/store/ideContainer';
|
||||||
|
import store from '@/store';
|
||||||
|
import {
|
||||||
|
convertFlowData,
|
||||||
|
reverseConvertFlowData,
|
||||||
|
revertFlowData,
|
||||||
|
} from '@/features/workflow/adapters/serverFlowAdapter';
|
||||||
|
import {
|
||||||
|
validateAllEdges,
|
||||||
|
validateAllNodes,
|
||||||
|
showValidationErrors,
|
||||||
|
} from '@/components/FlowEditor/nodeEditors/validators/nodeValidators';
|
||||||
|
import {
|
||||||
|
handelEventNodeList,
|
||||||
|
updateEvent,
|
||||||
|
upDatePublish,
|
||||||
|
} from '@/pages/flowEditor/utils/common';
|
||||||
|
import { sleep } from '@/utils/common';
|
||||||
|
|
||||||
|
interface SaveWorkflowDataParams {
|
||||||
|
nodes: Node[];
|
||||||
|
edges: Edge[];
|
||||||
|
useDefault: boolean;
|
||||||
|
initialData: any;
|
||||||
|
canvasDataMap: any;
|
||||||
|
dispatch: Dispatch<any>;
|
||||||
|
setNodes: React.Dispatch<React.SetStateAction<Node[]>>;
|
||||||
|
setEdges: React.Dispatch<React.SetStateAction<Edge[]>>;
|
||||||
|
}
|
||||||
|
|
||||||
|
export const saveWorkflowData = async ({
|
||||||
|
nodes,
|
||||||
|
edges,
|
||||||
|
useDefault,
|
||||||
|
initialData,
|
||||||
|
canvasDataMap,
|
||||||
|
dispatch,
|
||||||
|
setNodes,
|
||||||
|
setEdges,
|
||||||
|
}: SaveWorkflowDataParams) => {
|
||||||
|
if (useDefault) {
|
||||||
|
try {
|
||||||
|
const nodeValidation = validateAllNodes(nodes);
|
||||||
|
if (!nodeValidation.isValid) {
|
||||||
|
showValidationErrors(nodeValidation.errors);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const edgeValidation = validateAllEdges(edges, nodes);
|
||||||
|
if (!edgeValidation.isValid) {
|
||||||
|
showValidationErrors(edgeValidation.errors);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const revertedData = revertFlowData(nodes, edges);
|
||||||
|
const upDatePublishCB = await upDatePublish(revertedData.nodeConfigs);
|
||||||
|
const newRevertedData = reverseConvertFlowData(
|
||||||
|
nodes,
|
||||||
|
edges,
|
||||||
|
upDatePublishCB
|
||||||
|
);
|
||||||
|
const { flowData, currentAppData, info } =
|
||||||
|
store.getState().ideContainer;
|
||||||
|
const { deleteEventSendNodeList, deleteEventlisteneList } =
|
||||||
|
handelEventNodeList(newRevertedData);
|
||||||
|
|
||||||
|
if (currentAppData.key.includes('sub')) {
|
||||||
|
const appEventDefinition = updateEvent(
|
||||||
|
revertedData.nodeConfigs,
|
||||||
|
initialData.appId
|
||||||
|
);
|
||||||
|
const params = {
|
||||||
|
...(currentAppData?.compData || {}),
|
||||||
|
components: newRevertedData,
|
||||||
|
appEventDefinition,
|
||||||
|
sceneId: info.id,
|
||||||
|
};
|
||||||
|
const res: any = await setSubFlowNew(
|
||||||
|
params,
|
||||||
|
currentAppData.parentAppId
|
||||||
|
);
|
||||||
|
if (res.code === 200) {
|
||||||
|
Message.success('保存成功');
|
||||||
|
|
||||||
|
const res1: any = await queryEventItemBySceneIdOld(info.id);
|
||||||
|
if (res1.code === 200) dispatch(updateEventListOld(res1.data));
|
||||||
|
|
||||||
|
const appRes: any = await getAppInfoNew(currentAppData.parentAppId);
|
||||||
|
dispatch(
|
||||||
|
updateFlowData({ [currentAppData.parentAppId]: appRes.data })
|
||||||
|
);
|
||||||
|
|
||||||
|
if (appRes.data.main?.components) {
|
||||||
|
const { nodes: parentNodes, edges: parentEdges } =
|
||||||
|
convertFlowData(appRes.data.main.components, true);
|
||||||
|
dispatch(
|
||||||
|
updateCanvasDataMap({
|
||||||
|
...canvasDataMap,
|
||||||
|
[currentAppData.parentAppId]: {
|
||||||
|
nodes: parentNodes,
|
||||||
|
edges: parentEdges,
|
||||||
|
},
|
||||||
|
})
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
dispatch(
|
||||||
|
updateCanvasDataMap({
|
||||||
|
...canvasDataMap,
|
||||||
|
[currentAppData.key]: { nodes, edges },
|
||||||
|
})
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
Message.error(res.message);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
const appEventDefinition = updateEvent(
|
||||||
|
revertedData.nodeConfigs,
|
||||||
|
initialData.appId
|
||||||
|
);
|
||||||
|
const params = {
|
||||||
|
...(flowData[currentAppData.id]?.main || {}),
|
||||||
|
components: newRevertedData,
|
||||||
|
appEventDefinition,
|
||||||
|
sceneId: info.id,
|
||||||
|
};
|
||||||
|
|
||||||
|
const res: any = await setMainFlowNew(params, initialData.appId);
|
||||||
|
if (res.code === 200) {
|
||||||
|
Message.success('保存成功');
|
||||||
|
|
||||||
|
const res1: any = await queryEventItemBySceneIdOld(info.id);
|
||||||
|
if (res1.code === 200) dispatch(updateEventListOld(res1.data));
|
||||||
|
|
||||||
|
dispatch(
|
||||||
|
updateCanvasDataMap({
|
||||||
|
...canvasDataMap,
|
||||||
|
[currentAppData.id]: { nodes, edges },
|
||||||
|
})
|
||||||
|
);
|
||||||
|
|
||||||
|
const appRes: any = await getAppInfoNew(currentAppData.id);
|
||||||
|
dispatch(updateFlowData({ [currentAppData.id]: appRes.data }));
|
||||||
|
|
||||||
|
if (appRes.data.main?.components) {
|
||||||
|
const { nodes, edges } = convertFlowData(
|
||||||
|
appRes.data.main.components,
|
||||||
|
true
|
||||||
|
);
|
||||||
|
setNodes(nodes);
|
||||||
|
setEdges(edges);
|
||||||
|
dispatch(
|
||||||
|
updateCanvasDataMap({
|
||||||
|
...canvasDataMap,
|
||||||
|
[currentAppData.id]: { nodes, edges },
|
||||||
|
})
|
||||||
|
);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
Message.error(res.message);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (
|
||||||
|
deleteEventSendNodeList.length > 0 ||
|
||||||
|
deleteEventlisteneList.length > 0
|
||||||
|
) {
|
||||||
|
deleteEventSendNodeList.length > 0 &&
|
||||||
|
deleteEventPub({
|
||||||
|
appId: currentAppData.id,
|
||||||
|
topics: deleteEventSendNodeList,
|
||||||
|
});
|
||||||
|
deleteEventlisteneList.length > 0 &&
|
||||||
|
deleteEventSub({
|
||||||
|
appId: currentAppData.id,
|
||||||
|
topics: deleteEventlisteneList,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Error saving flow data:', error);
|
||||||
|
Message.error('保存失败');
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const appFlowParams: any = {
|
||||||
|
appEventList: {},
|
||||||
|
eventEdges: [],
|
||||||
|
};
|
||||||
|
|
||||||
|
nodes.forEach((node) => {
|
||||||
|
appFlowParams.appEventList[node.id] = {
|
||||||
|
x: node.position.x,
|
||||||
|
y: node.position.y,
|
||||||
|
};
|
||||||
|
});
|
||||||
|
|
||||||
|
const eventMap = new Map();
|
||||||
|
edges.forEach((edge: any) => {
|
||||||
|
appFlowParams.eventEdges.push({
|
||||||
|
id: edge.id,
|
||||||
|
source: edge.source,
|
||||||
|
target: edge.target,
|
||||||
|
lineType: 'data',
|
||||||
|
data: {
|
||||||
|
displayData: {
|
||||||
|
...edge.data.displayData,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
const sourceId = edge.sourceHandle;
|
||||||
|
const targetId = edge.targetHandle;
|
||||||
|
const topic = edge.data.displayData?.topic;
|
||||||
|
|
||||||
|
if (eventMap.has(topic)) {
|
||||||
|
eventMap.get(topic).eventId.push(sourceId);
|
||||||
|
eventMap.get(topic).eventId.push(targetId);
|
||||||
|
} else {
|
||||||
|
eventMap.set(topic, {
|
||||||
|
eventId: [sourceId, targetId],
|
||||||
|
topic,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
const appEventParams = Array.from(eventMap.values()).map((item) => ({
|
||||||
|
...item,
|
||||||
|
eventId: Array.from(new Set(item.eventId)),
|
||||||
|
}));
|
||||||
|
|
||||||
|
try {
|
||||||
|
updateAppFlowData(appFlowParams);
|
||||||
|
if (appEventParams.length > 0) {
|
||||||
|
for (const item of appEventParams) {
|
||||||
|
if (item.topic) {
|
||||||
|
await sleep(500);
|
||||||
|
await updateAppEventChannel(item);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Message.success('保存成功');
|
||||||
|
} catch (error: any) {
|
||||||
|
console.error('保存失败:', error);
|
||||||
|
Message.error('保存失败: ' + error.message);
|
||||||
|
}
|
||||||
|
};
|
||||||
@ -0,0 +1,34 @@
|
|||||||
|
import React from 'react';
|
||||||
|
import BasicNode from '@/components/FlowEditor/node/basicNode/BasicNode';
|
||||||
|
import SwitchNode from '@/components/FlowEditor/node/switchNode/SwitchNode';
|
||||||
|
import ImageNode from '@/components/FlowEditor/node/imageNode/ImageNode';
|
||||||
|
import CodeNode from '@/components/FlowEditor/node/codeNode/CodeNode';
|
||||||
|
import RestNode from '@/components/FlowEditor/node/restNode/RestNode';
|
||||||
|
import LocalNode from '@/components/FlowEditor/node/localNode/LocalNode';
|
||||||
|
import AppNode from '@/components/FlowEditor/node/appNode/AppNode';
|
||||||
|
import LoopNode from '@/components/FlowEditor/node/loopNode/LoopNode';
|
||||||
|
import MicrophoneNode from '@/components/FlowEditor/node/microphoneNode/MicrophoneNode';
|
||||||
|
|
||||||
|
export interface NodeDescriptor {
|
||||||
|
kind: string;
|
||||||
|
label: string;
|
||||||
|
render: React.ComponentType<any>;
|
||||||
|
}
|
||||||
|
|
||||||
|
export const nodeDescriptors: Record<string, NodeDescriptor> = {
|
||||||
|
BASIC: { kind: 'BASIC', label: '基础节点', render: BasicNode },
|
||||||
|
BASIC_LOOP: { kind: 'BASIC_LOOP', label: '基础节点', render: BasicNode },
|
||||||
|
SUB: { kind: 'SUB', label: '复合节点', render: BasicNode },
|
||||||
|
APP: { kind: 'APP', label: '应用节点', render: AppNode },
|
||||||
|
CODE: { kind: 'CODE', label: '代码节点', render: CodeNode },
|
||||||
|
IMAGE: { kind: 'IMAGE', label: '图片节点', render: ImageNode },
|
||||||
|
REST: { kind: 'REST', label: 'REST节点', render: RestNode },
|
||||||
|
SWITCH: { kind: 'SWITCH', label: '条件节点', render: SwitchNode },
|
||||||
|
LOOP: { kind: 'LOOP', label: '循环节点', render: LoopNode },
|
||||||
|
MICRO: { kind: 'MICRO', label: '语音节点', render: MicrophoneNode },
|
||||||
|
LOCAL: { kind: 'LOCAL', label: '本地节点', render: LocalNode },
|
||||||
|
};
|
||||||
|
|
||||||
|
export const getNodeDescriptor = (nodeType: string) => {
|
||||||
|
return nodeDescriptors[nodeType] || nodeDescriptors.LOCAL;
|
||||||
|
};
|
||||||
@ -0,0 +1,21 @@
|
|||||||
|
import { nodeTypeMap, registerNodeType } from '@/components/FlowEditor/node';
|
||||||
|
import { getNodeDescriptor } from '@/features/workflow/registry/nodeDescriptors';
|
||||||
|
|
||||||
|
export const resolveNodeComponent = (nodeType: string) => {
|
||||||
|
return getNodeDescriptor(nodeType).render;
|
||||||
|
};
|
||||||
|
|
||||||
|
export const ensureNodeTypeRegistered = (
|
||||||
|
nodeType: string,
|
||||||
|
nodeName: string,
|
||||||
|
component?: any
|
||||||
|
) => {
|
||||||
|
const nodeMap = Array.from(Object.values(nodeTypeMap).map((key) => key));
|
||||||
|
if (!nodeMap.includes(nodeType)) {
|
||||||
|
registerNodeType(
|
||||||
|
nodeType,
|
||||||
|
component || resolveNodeComponent(nodeType),
|
||||||
|
nodeName
|
||||||
|
);
|
||||||
|
}
|
||||||
|
};
|
||||||
@ -0,0 +1,34 @@
|
|||||||
|
export interface FlowCurrentAppData {
|
||||||
|
id?: string;
|
||||||
|
key?: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface AppRuntimeState {
|
||||||
|
nodeStatusMap: Record<string, string>;
|
||||||
|
isRunning: boolean;
|
||||||
|
isPaused: boolean;
|
||||||
|
logs: any[];
|
||||||
|
runId: string;
|
||||||
|
eventSendNodeList: any[];
|
||||||
|
eventlisteneList: any[];
|
||||||
|
}
|
||||||
|
|
||||||
|
export const getCurrentAppKey = (
|
||||||
|
currentAppData: FlowCurrentAppData | null | undefined
|
||||||
|
) => {
|
||||||
|
if (!currentAppData) return null;
|
||||||
|
if (currentAppData.key && currentAppData.key.includes('sub')) {
|
||||||
|
return currentAppData.key;
|
||||||
|
}
|
||||||
|
return currentAppData.id || null;
|
||||||
|
};
|
||||||
|
|
||||||
|
export const createDefaultAppRuntimeState = (): AppRuntimeState => ({
|
||||||
|
nodeStatusMap: {},
|
||||||
|
isRunning: false,
|
||||||
|
isPaused: false,
|
||||||
|
logs: [],
|
||||||
|
runId: '',
|
||||||
|
eventSendNodeList: [],
|
||||||
|
eventlisteneList: [],
|
||||||
|
});
|
||||||
@ -0,0 +1,183 @@
|
|||||||
|
import React from 'react';
|
||||||
|
import { Edge } from '@xyflow/react';
|
||||||
|
import { Message } from '@arco-design/web-react';
|
||||||
|
import { Dispatch } from 'redux';
|
||||||
|
import {
|
||||||
|
pauseApp,
|
||||||
|
reRunApp,
|
||||||
|
resumeApp,
|
||||||
|
runMainFlow,
|
||||||
|
runSubFlow,
|
||||||
|
stopApp,
|
||||||
|
} from '@/api/apps';
|
||||||
|
import {
|
||||||
|
clearRuntimeLogs,
|
||||||
|
resetNodeStatus,
|
||||||
|
updateIsPaused,
|
||||||
|
updateIsRunning,
|
||||||
|
updateRuntimeId,
|
||||||
|
} from '@/store/ideContainer';
|
||||||
|
import store from '@/store';
|
||||||
|
|
||||||
|
interface FlowRuntimeActionParams {
|
||||||
|
dispatch: Dispatch<any>;
|
||||||
|
getCurrentFlowAppKey: () => string | null;
|
||||||
|
}
|
||||||
|
|
||||||
|
interface RunWorkflowParams extends FlowRuntimeActionParams {
|
||||||
|
running: boolean;
|
||||||
|
setEdges: React.Dispatch<React.SetStateAction<Edge[]>>;
|
||||||
|
}
|
||||||
|
|
||||||
|
const setEdgesRunningState = (
|
||||||
|
setEdges: React.Dispatch<React.SetStateAction<Edge[]>>,
|
||||||
|
isRunning: boolean
|
||||||
|
) => {
|
||||||
|
setEdges((edges) =>
|
||||||
|
edges.map((edge) => ({
|
||||||
|
...edge,
|
||||||
|
data: {
|
||||||
|
...edge.data,
|
||||||
|
isRunning,
|
||||||
|
animationProgress: 0,
|
||||||
|
},
|
||||||
|
}))
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export const runWorkflow = async ({
|
||||||
|
running,
|
||||||
|
dispatch,
|
||||||
|
getCurrentFlowAppKey,
|
||||||
|
setEdges,
|
||||||
|
}: RunWorkflowParams) => {
|
||||||
|
const { currentAppData, socketId, appRuntimeData } =
|
||||||
|
store.getState().ideContainer;
|
||||||
|
const appKey = getCurrentFlowAppKey();
|
||||||
|
|
||||||
|
if (running) {
|
||||||
|
const isSubFlow = currentAppData.key.includes('sub');
|
||||||
|
const res: any = isSubFlow
|
||||||
|
? await runSubFlow({
|
||||||
|
appId: currentAppData.parentAppId,
|
||||||
|
socketId,
|
||||||
|
subflowId: currentAppData.key,
|
||||||
|
})
|
||||||
|
: await runMainFlow({
|
||||||
|
appId: currentAppData.id,
|
||||||
|
socketId,
|
||||||
|
});
|
||||||
|
|
||||||
|
if (res.code === 200) {
|
||||||
|
dispatch(updateIsRunning(true));
|
||||||
|
dispatch(resetNodeStatus());
|
||||||
|
dispatch(updateRuntimeId(res.data));
|
||||||
|
setEdgesRunningState(setEdges, true);
|
||||||
|
} else {
|
||||||
|
Message.error(res.message);
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
dispatch(updateIsRunning(false));
|
||||||
|
|
||||||
|
const runId =
|
||||||
|
appKey && appRuntimeData[appKey] ? appRuntimeData[appKey].runId : '';
|
||||||
|
if (runId) {
|
||||||
|
await stopApp(runId);
|
||||||
|
} else {
|
||||||
|
await stopApp(currentAppData.instanceId);
|
||||||
|
document.dispatchEvent(new CustomEvent('refreshAppList'));
|
||||||
|
}
|
||||||
|
|
||||||
|
dispatch(resetNodeStatus());
|
||||||
|
dispatch(updateRuntimeId(''));
|
||||||
|
setEdgesRunningState(setEdges, false);
|
||||||
|
|
||||||
|
if (appKey) {
|
||||||
|
dispatch(clearRuntimeLogs({ appId: appKey }));
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
export const pauseWorkflow = async ({
|
||||||
|
isPaused,
|
||||||
|
dispatch,
|
||||||
|
getCurrentFlowAppKey,
|
||||||
|
}: FlowRuntimeActionParams & { isPaused: boolean }) => {
|
||||||
|
const { currentAppData, appRuntimeData } = store.getState().ideContainer;
|
||||||
|
const appKey = getCurrentFlowAppKey();
|
||||||
|
|
||||||
|
if (!currentAppData) {
|
||||||
|
Message.warning('请先选择一个应用');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const runId =
|
||||||
|
appKey && appRuntimeData[appKey] ? appRuntimeData[appKey].runId : '';
|
||||||
|
|
||||||
|
if (!runId) {
|
||||||
|
Message.warning('应用未运行');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
const res: any = isPaused
|
||||||
|
? await resumeApp({ id: runId })
|
||||||
|
: await pauseApp({ id: runId });
|
||||||
|
|
||||||
|
if (res.code === 200) {
|
||||||
|
Message.success(isPaused ? '应用已恢复' : '应用已暂停');
|
||||||
|
dispatch(updateIsPaused(!isPaused));
|
||||||
|
} else {
|
||||||
|
Message.error(res.msg || (isPaused ? '恢复失败' : '暂停失败'));
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
console.error('暂停/恢复失败:', error);
|
||||||
|
Message.error('操作失败');
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
export const rerunWorkflow = async ({
|
||||||
|
dispatch,
|
||||||
|
getCurrentFlowAppKey,
|
||||||
|
}: FlowRuntimeActionParams) => {
|
||||||
|
const { currentAppData, appRuntimeData, socketId } =
|
||||||
|
store.getState().ideContainer;
|
||||||
|
const appKey = getCurrentFlowAppKey();
|
||||||
|
|
||||||
|
if (!currentAppData) {
|
||||||
|
Message.warning('请先选择一个应用');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const instanceId =
|
||||||
|
appKey && appRuntimeData[appKey] ? appRuntimeData[appKey].runId : '';
|
||||||
|
|
||||||
|
if (!instanceId) {
|
||||||
|
Message.warning('应用未运行');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
const appId =
|
||||||
|
currentAppData.key && currentAppData.key.includes('sub')
|
||||||
|
? currentAppData.parentAppId
|
||||||
|
: currentAppData.id;
|
||||||
|
|
||||||
|
const res: any = await reRunApp({
|
||||||
|
appId,
|
||||||
|
instanceId,
|
||||||
|
socketId,
|
||||||
|
});
|
||||||
|
|
||||||
|
if (res.code === 200) {
|
||||||
|
Message.success('应用重跑成功');
|
||||||
|
dispatch(resetNodeStatus());
|
||||||
|
} else {
|
||||||
|
Message.error(res.msg || '重跑失败');
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
console.error('重跑失败:', error);
|
||||||
|
Message.error('重跑失败');
|
||||||
|
}
|
||||||
|
};
|
||||||
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
@ -1,54 +1,4 @@
|
|||||||
import { Edge } from '@xyflow/react';
|
export {
|
||||||
|
buildInsertedNodeEdges,
|
||||||
export const resolveInsertedNodeHandles = (node: any) => {
|
resolveInsertedNodeHandles,
|
||||||
let sourceHandle = 'done';
|
} from '@/features/workflow/operations/edgeOperations';
|
||||||
let targetHandle = 'start';
|
|
||||||
|
|
||||||
if (node?.data?.parameters) {
|
|
||||||
const { apiOuts, apiIns } = node.data.parameters;
|
|
||||||
|
|
||||||
if (apiOuts && apiOuts.length > 0) {
|
|
||||||
sourceHandle = apiOuts[0].name || apiOuts[0].id || sourceHandle;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (apiIns && apiIns.length > 0) {
|
|
||||||
targetHandle = apiIns[0].name || apiIns[0].id || targetHandle;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return { sourceHandle, targetHandle };
|
|
||||||
};
|
|
||||||
|
|
||||||
export const buildInsertedNodeEdges = (params: {
|
|
||||||
baseEdges: Edge[];
|
|
||||||
removedEdgeId: string;
|
|
||||||
sourceId: string;
|
|
||||||
sourceHandle?: string;
|
|
||||||
targetId: string;
|
|
||||||
targetHandle?: string;
|
|
||||||
insertedNodeId: string;
|
|
||||||
insertedNodeSourceHandle: string;
|
|
||||||
insertedNodeTargetHandle: string;
|
|
||||||
}): Edge[] => [
|
|
||||||
...params.baseEdges.filter((e) => e.id !== params.removedEdgeId),
|
|
||||||
{
|
|
||||||
id: `e${params.sourceId}-${params.insertedNodeId}`,
|
|
||||||
source: params.sourceId,
|
|
||||||
target: params.insertedNodeId,
|
|
||||||
sourceHandle: params.sourceHandle,
|
|
||||||
targetHandle: params.insertedNodeTargetHandle,
|
|
||||||
type: 'custom',
|
|
||||||
lineType: 'api',
|
|
||||||
data: { lineType: 'api' },
|
|
||||||
} as Edge,
|
|
||||||
{
|
|
||||||
id: `e${params.insertedNodeId}-${params.targetId}`,
|
|
||||||
source: params.insertedNodeId,
|
|
||||||
target: params.targetId,
|
|
||||||
sourceHandle: params.insertedNodeSourceHandle,
|
|
||||||
targetHandle: params.targetHandle,
|
|
||||||
type: 'custom',
|
|
||||||
lineType: 'api',
|
|
||||||
data: { lineType: 'api' },
|
|
||||||
} as Edge,
|
|
||||||
];
|
|
||||||
|
|||||||
@ -1,104 +1,5 @@
|
|||||||
import { Edge } from '@xyflow/react';
|
export {
|
||||||
|
createLoopGroupEdge,
|
||||||
export const createLoopNodePair = (position: { x: number; y: number }) => {
|
createLoopInsertConnectionEdges,
|
||||||
const loopStartNode: any = {
|
createLoopNodePair,
|
||||||
id: `LOOP_START-${Date.now()}`,
|
} from '@/features/workflow/operations/loopOperations';
|
||||||
type: 'LOOP',
|
|
||||||
position: { x: position.x, y: position.y },
|
|
||||||
data: {
|
|
||||||
title: '循环开始',
|
|
||||||
type: 'LOOP_START',
|
|
||||||
parameters: {
|
|
||||||
apiIns: [{ name: 'start', desc: '', dataType: '', defaultValue: '' }],
|
|
||||||
apiOuts: [{ name: 'done', desc: '', dataType: '', defaultValue: '' }],
|
|
||||||
dataIns: [],
|
|
||||||
dataOuts: [],
|
|
||||||
},
|
|
||||||
component: {},
|
|
||||||
},
|
|
||||||
};
|
|
||||||
|
|
||||||
const loopEndNode: any = {
|
|
||||||
id: `LOOP_END-${Date.now()}`,
|
|
||||||
type: 'LOOP',
|
|
||||||
position: { x: position.x + 400, y: position.y },
|
|
||||||
data: {
|
|
||||||
title: '循环结束',
|
|
||||||
type: 'LOOP_END',
|
|
||||||
parameters: {
|
|
||||||
apiIns: [
|
|
||||||
{ name: 'continue', desc: '', dataType: '', defaultValue: '' },
|
|
||||||
],
|
|
||||||
apiOuts: [{ name: 'break', desc: '', dataType: '', defaultValue: '' }],
|
|
||||||
dataIns: [
|
|
||||||
{
|
|
||||||
arrayType: null,
|
|
||||||
dataType: 'INTEGER',
|
|
||||||
defaultValue: 10,
|
|
||||||
desc: '最大循环次数',
|
|
||||||
id: 'maxTime',
|
|
||||||
},
|
|
||||||
],
|
|
||||||
dataOuts: [],
|
|
||||||
},
|
|
||||||
component: {
|
|
||||||
type: 'LOOP_END',
|
|
||||||
customDef: JSON.stringify({
|
|
||||||
apiOutIds: ['continue', 'break'],
|
|
||||||
conditions: [],
|
|
||||||
loopStartNodeId: loopStartNode.id,
|
|
||||||
}),
|
|
||||||
loopStartNodeId: loopStartNode.id,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
};
|
|
||||||
|
|
||||||
loopStartNode.data.component = {
|
|
||||||
type: 'LOOP_START',
|
|
||||||
customDef: JSON.stringify({ loopEndNodeId: loopEndNode.id }),
|
|
||||||
};
|
|
||||||
|
|
||||||
return { loopStartNode, loopEndNode };
|
|
||||||
};
|
|
||||||
|
|
||||||
export const createLoopGroupEdge = (
|
|
||||||
loopStartId: string,
|
|
||||||
loopEndId: string
|
|
||||||
): Edge => ({
|
|
||||||
id: `${loopStartId}-${loopEndId}-group`,
|
|
||||||
source: loopStartId,
|
|
||||||
target: loopEndId,
|
|
||||||
sourceHandle: `${loopStartId}-group`,
|
|
||||||
targetHandle: `${loopEndId}-group`,
|
|
||||||
type: 'custom',
|
|
||||||
});
|
|
||||||
|
|
||||||
export const createLoopInsertConnectionEdges = (params: {
|
|
||||||
sourceId: string;
|
|
||||||
sourceHandle: string;
|
|
||||||
targetId: string;
|
|
||||||
targetHandle: string;
|
|
||||||
loopStartId: string;
|
|
||||||
loopEndId: string;
|
|
||||||
}): Edge[] => [
|
|
||||||
{
|
|
||||||
id: `e${params.sourceId}-${params.loopStartId}`,
|
|
||||||
source: params.sourceId,
|
|
||||||
target: params.loopStartId,
|
|
||||||
sourceHandle: params.sourceHandle,
|
|
||||||
targetHandle: 'start',
|
|
||||||
type: 'custom',
|
|
||||||
lineType: 'api',
|
|
||||||
data: { lineType: 'api' },
|
|
||||||
} as Edge,
|
|
||||||
{
|
|
||||||
id: `e${params.loopEndId}-${params.targetId}`,
|
|
||||||
source: params.loopEndId,
|
|
||||||
target: params.targetId,
|
|
||||||
sourceHandle: 'break',
|
|
||||||
targetHandle: params.targetHandle,
|
|
||||||
type: 'custom',
|
|
||||||
lineType: 'api',
|
|
||||||
data: { lineType: 'api' },
|
|
||||||
} as Edge,
|
|
||||||
];
|
|
||||||
|
|||||||
@ -1,79 +1,4 @@
|
|||||||
import { Node } from '@xyflow/react';
|
export {
|
||||||
|
attachFlowNodeComponent,
|
||||||
interface FlowNodeDefinition {
|
createFlowNode,
|
||||||
nodeName: string;
|
} from '@/features/workflow/operations/nodeFactory';
|
||||||
data: any;
|
|
||||||
id?: string;
|
|
||||||
flowHousVO?: {
|
|
||||||
id?: string;
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
type EventIdMode = 'id' | 'eventIdOptional';
|
|
||||||
|
|
||||||
export const createFlowNode = (
|
|
||||||
nodeType: string,
|
|
||||||
nodeDefinition: FlowNodeDefinition,
|
|
||||||
position: { x: number; y: number }
|
|
||||||
): Node => {
|
|
||||||
const node: any = {
|
|
||||||
id: `${nodeType}-${Date.now()}`,
|
|
||||||
type: nodeType,
|
|
||||||
position,
|
|
||||||
data: {
|
|
||||||
...nodeDefinition.data,
|
|
||||||
title: nodeDefinition.nodeName,
|
|
||||||
type: nodeType,
|
|
||||||
},
|
|
||||||
};
|
|
||||||
|
|
||||||
if (nodeDefinition.id || nodeDefinition?.flowHousVO?.id) {
|
|
||||||
node.data.compId = nodeDefinition.id || nodeDefinition?.flowHousVO?.id;
|
|
||||||
}
|
|
||||||
|
|
||||||
return node;
|
|
||||||
};
|
|
||||||
|
|
||||||
export const attachFlowNodeComponent = (
|
|
||||||
node: any,
|
|
||||||
nodeType: string,
|
|
||||||
nodeDefinition: FlowNodeDefinition,
|
|
||||||
eventList: any[],
|
|
||||||
eventIdMode: EventIdMode
|
|
||||||
) => {
|
|
||||||
if (nodeType === 'SWITCH') {
|
|
||||||
node.data.component = {
|
|
||||||
customDef: JSON.stringify({
|
|
||||||
apiOutIds: ['default'],
|
|
||||||
conditions: [],
|
|
||||||
}),
|
|
||||||
};
|
|
||||||
} else if (nodeType === 'SUB') {
|
|
||||||
node.data.component = {
|
|
||||||
type: nodeType,
|
|
||||||
compId: node.data.compId,
|
|
||||||
};
|
|
||||||
} else if (nodeType === 'EVENTSEND' || nodeType === 'EVENTLISTENE') {
|
|
||||||
const emptyEvent = eventList.find((item) =>
|
|
||||||
item.topic.includes('**empty**')
|
|
||||||
);
|
|
||||||
node.data.component = {
|
|
||||||
type: nodeType,
|
|
||||||
customDef: {
|
|
||||||
eventId:
|
|
||||||
eventIdMode === 'eventIdOptional'
|
|
||||||
? emptyEvent?.eventId ?? null
|
|
||||||
: emptyEvent.id,
|
|
||||||
name: emptyEvent.name,
|
|
||||||
topic: emptyEvent.topic,
|
|
||||||
},
|
|
||||||
};
|
|
||||||
} else {
|
|
||||||
node.data.component = {
|
|
||||||
type: nodeType,
|
|
||||||
compId: nodeDefinition.id,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
return node;
|
|
||||||
};
|
|
||||||
|
|||||||
@ -1,61 +1,5 @@
|
|||||||
import {
|
export {
|
||||||
createFlowNode,
|
buildDroppedNode,
|
||||||
attachFlowNodeComponent,
|
buildRuntimeNode,
|
||||||
} from '@/utils/flow/nodeFactory';
|
resolveNodeDefinition,
|
||||||
import {
|
} from '@/features/workflow/operations/nodeOnboarding';
|
||||||
ensureNodeTypeRegistered,
|
|
||||||
resolveNodeComponent,
|
|
||||||
} from '@/utils/flow/nodeRegistry';
|
|
||||||
|
|
||||||
type EventIdMode = 'id' | 'eventIdOptional';
|
|
||||||
|
|
||||||
export const resolveNodeDefinition = (
|
|
||||||
nodeList: any[],
|
|
||||||
nodeType: string,
|
|
||||||
fallbackNode?: any
|
|
||||||
) => {
|
|
||||||
return nodeList.find((n) => n.nodeType === nodeType) || fallbackNode || null;
|
|
||||||
};
|
|
||||||
|
|
||||||
export const buildRuntimeNode = (params: {
|
|
||||||
nodeType: string;
|
|
||||||
nodeDefinition: any;
|
|
||||||
position: { x: number; y: number };
|
|
||||||
eventList: any[];
|
|
||||||
eventIdMode: EventIdMode;
|
|
||||||
}) => {
|
|
||||||
const { nodeType, nodeDefinition, position, eventList, eventIdMode } = params;
|
|
||||||
|
|
||||||
const newNode = attachFlowNodeComponent(
|
|
||||||
createFlowNode(nodeType, nodeDefinition, position),
|
|
||||||
nodeType,
|
|
||||||
nodeDefinition,
|
|
||||||
eventList,
|
|
||||||
eventIdMode
|
|
||||||
);
|
|
||||||
|
|
||||||
ensureNodeTypeRegistered(
|
|
||||||
nodeType,
|
|
||||||
nodeDefinition.nodeName,
|
|
||||||
resolveNodeComponent(nodeType)
|
|
||||||
);
|
|
||||||
|
|
||||||
return newNode;
|
|
||||||
};
|
|
||||||
|
|
||||||
export const buildDroppedNode = (
|
|
||||||
nodeData: any,
|
|
||||||
position: { x: number; y: number }
|
|
||||||
) => {
|
|
||||||
ensureNodeTypeRegistered(nodeData.nodeType, nodeData.nodeName);
|
|
||||||
return {
|
|
||||||
id: `${nodeData.nodeType}-${Date.now()}`,
|
|
||||||
type: nodeData.nodeType,
|
|
||||||
position,
|
|
||||||
data: {
|
|
||||||
...nodeData.data,
|
|
||||||
title: nodeData.nodeName,
|
|
||||||
type: nodeData.nodeType,
|
|
||||||
},
|
|
||||||
};
|
|
||||||
};
|
|
||||||
|
|||||||
@ -1,40 +1,4 @@
|
|||||||
import BasicNode from '@/components/FlowEditor/node/basicNode/BasicNode';
|
export {
|
||||||
import SwitchNode from '@/components/FlowEditor/node/switchNode/SwitchNode';
|
ensureNodeTypeRegistered,
|
||||||
import ImageNode from '@/components/FlowEditor/node/imageNode/ImageNode';
|
resolveNodeComponent,
|
||||||
import CodeNode from '@/components/FlowEditor/node/codeNode/CodeNode';
|
} from '@/features/workflow/registry/nodeRegistry';
|
||||||
import RestNode from '@/components/FlowEditor/node/restNode/RestNode';
|
|
||||||
import LocalNode from '@/components/FlowEditor/node/localNode/LocalNode';
|
|
||||||
import { nodeTypeMap, registerNodeType } from '@/components/FlowEditor/node';
|
|
||||||
|
|
||||||
export const resolveNodeComponent = (nodeType: string) => {
|
|
||||||
switch (nodeType) {
|
|
||||||
case 'BASIC':
|
|
||||||
case 'SUB':
|
|
||||||
return BasicNode;
|
|
||||||
case 'SWITCH':
|
|
||||||
return SwitchNode;
|
|
||||||
case 'IMAGE':
|
|
||||||
return ImageNode;
|
|
||||||
case 'CODE':
|
|
||||||
return CodeNode;
|
|
||||||
case 'REST':
|
|
||||||
return RestNode;
|
|
||||||
default:
|
|
||||||
return LocalNode;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
export const ensureNodeTypeRegistered = (
|
|
||||||
nodeType: string,
|
|
||||||
nodeName: string,
|
|
||||||
component?: any
|
|
||||||
) => {
|
|
||||||
const nodeMap = Array.from(Object.values(nodeTypeMap).map((key) => key));
|
|
||||||
if (!nodeMap.includes(nodeType)) {
|
|
||||||
registerNodeType(
|
|
||||||
nodeType,
|
|
||||||
component || resolveNodeComponent(nodeType),
|
|
||||||
nodeName
|
|
||||||
);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|||||||
@ -1,34 +1,8 @@
|
|||||||
export interface FlowCurrentAppData {
|
export {
|
||||||
id?: string;
|
createDefaultAppRuntimeState,
|
||||||
key?: string;
|
getCurrentAppKey,
|
||||||
}
|
} from '@/features/workflow/runtime/flowRuntime';
|
||||||
|
export type {
|
||||||
export interface AppRuntimeState {
|
AppRuntimeState,
|
||||||
nodeStatusMap: Record<string, string>;
|
FlowCurrentAppData,
|
||||||
isRunning: boolean;
|
} from '@/features/workflow/runtime/flowRuntime';
|
||||||
isPaused: boolean;
|
|
||||||
logs: any[];
|
|
||||||
runId: string;
|
|
||||||
eventSendNodeList: any[];
|
|
||||||
eventlisteneList: any[];
|
|
||||||
}
|
|
||||||
|
|
||||||
export const getCurrentAppKey = (
|
|
||||||
currentAppData: FlowCurrentAppData | null | undefined
|
|
||||||
) => {
|
|
||||||
if (!currentAppData) return null;
|
|
||||||
if (currentAppData.key && currentAppData.key.includes('sub')) {
|
|
||||||
return currentAppData.key;
|
|
||||||
}
|
|
||||||
return currentAppData.id || null;
|
|
||||||
};
|
|
||||||
|
|
||||||
export const createDefaultAppRuntimeState = (): AppRuntimeState => ({
|
|
||||||
nodeStatusMap: {},
|
|
||||||
isRunning: false,
|
|
||||||
isPaused: false,
|
|
||||||
logs: [],
|
|
||||||
runId: '',
|
|
||||||
eventSendNodeList: [],
|
|
||||||
eventlisteneList: [],
|
|
||||||
});
|
|
||||||
|
|||||||
@ -1,22 +1,4 @@
|
|||||||
import { Edge, Node } from '@xyflow/react';
|
export {
|
||||||
|
dispatchFlowSnapshot,
|
||||||
interface FlowSnapshotDetail {
|
dispatchFlowSnapshotAsync,
|
||||||
nodes: Node[];
|
} from '@/features/workflow/operations/snapshot';
|
||||||
edges: Edge[];
|
|
||||||
}
|
|
||||||
|
|
||||||
export const dispatchFlowSnapshot = (detail: FlowSnapshotDetail) => {
|
|
||||||
const event = new CustomEvent('takeSnapshot', {
|
|
||||||
detail,
|
|
||||||
});
|
|
||||||
document.dispatchEvent(event);
|
|
||||||
};
|
|
||||||
|
|
||||||
export const dispatchFlowSnapshotAsync = (
|
|
||||||
detail: FlowSnapshotDetail,
|
|
||||||
delay = 0
|
|
||||||
) => {
|
|
||||||
return setTimeout(() => {
|
|
||||||
dispatchFlowSnapshot(detail);
|
|
||||||
}, delay);
|
|
||||||
};
|
|
||||||
|
|||||||
Loading…
Reference in New Issue