Merge remote-tracking branch 'origin/feature' into feature

feature
鱼星 3 weeks ago
commit 097fc169c7

@ -2,11 +2,58 @@ import { useState, useRef, useEffect, useMemo } from 'react';
import { Node, Edge } from '@xyflow/react';
import { debounce } from 'lodash';
import { useSelector, useDispatch, shallowEqual } from 'react-redux';
import { updateCanvasDataMap } from '@/store/ideContainer';
import { updateCanvasDataMap, updateNodeStatus } from '@/store/ideContainer';
import { getCurrentAppKey } from '@/utils/flow/runtime';
import { getNodeData } from '@/api/appIns';
import { Dispatch } from 'redux';
const getRuntimeNodeStatus = (state: any) => {
switch (state) {
case 0:
case '0':
return 'running';
case 1:
case '1':
return 'success';
case -1:
case '-1':
return 'failed';
default:
return '';
}
};
const collectRuntimeNodes = (runtimeData: any) => {
if (!runtimeData) {
return [];
}
if (Array.isArray(runtimeData)) {
return runtimeData.flatMap((item) => {
if (Array.isArray(item?.nodes)) {
return item.nodes;
}
return item?.nodeId || item?.id ? [item] : [];
});
}
if (Array.isArray(runtimeData?.main?.nodeLogs)) {
return runtimeData.main.nodeLogs;
}
if (Array.isArray(runtimeData?.nodes)) {
return runtimeData.nodes;
}
if (Array.isArray(runtimeData?.data)) {
return collectRuntimeNodes(runtimeData.data);
}
return [];
};
export const useFlowEditorState = (initialData?: any, readOnly?: boolean) => {
const [nodes, setNodes] = useState<Node[]>([]);
const [edges, setEdges] = useState<Edge[]>([]);
@ -46,6 +93,11 @@ export const useFlowEditorState = (initialData?: any, readOnly?: boolean) => {
// 在组件顶部添加历史记录相关状态
const [historyInitialized, setHistoryInitialized] = useState(false);
const historyTimeoutRef = useRef<NodeJS.Timeout | null>(null);
const syncedRuntimeKeyRef = useRef('');
const currentRunId =
currentAppKey && appRuntimeData[currentAppKey]
? appRuntimeData[currentAppKey].runId
: '';
// 更新节点状态将从store获取的状态应用到节点上
useEffect(() => {
@ -112,6 +164,70 @@ export const useFlowEditorState = (initialData?: any, readOnly?: boolean) => {
readOnly,
]);
useEffect(() => {
if (
readOnly ||
!currentAppKey ||
!currentAppIsRunning ||
!currentRunId ||
nodes.length === 0
) {
return;
}
const syncKey = `${currentAppKey}:${currentRunId}`;
if (syncedRuntimeKeyRef.current === syncKey) {
return;
}
let canceled = false;
syncedRuntimeKeyRef.current = syncKey;
const syncRuntimeNodeStatus = async () => {
try {
const nodeDataRes: any = await getNodeData(currentRunId);
const runtimeData = nodeDataRes?.data || nodeDataRes;
const runtimeNodes = collectRuntimeNodes(runtimeData);
if (canceled || runtimeNodes.length === 0) {
return;
}
runtimeNodes.forEach((node) => {
const nodeId = node?.nodeId || node?.id;
const status = getRuntimeNodeStatus(node?.state);
if (nodeId && status) {
dispatch(updateNodeStatus({
nodeId,
status,
appId: currentAppKey,
actionType: 'RUNTIME_RECONNECT_SYNC',
}));
}
});
} catch (error) {
if (!canceled) {
syncedRuntimeKeyRef.current = '';
console.error('同步运行实例节点状态失败:', error);
}
}
};
syncRuntimeNodeStatus();
return () => {
canceled = true;
};
}, [
currentAppKey,
currentAppIsRunning,
currentRunId,
dispatch,
nodes.length,
readOnly,
]);
const updateCanvasDataMapDebounced = useRef(
debounce(
(

@ -64,6 +64,22 @@ const ALL_PATHS = [
'systemResource', 'appGuide'
];
const getRuntimeNodeStatus = (state: any) => {
switch (state) {
case 0:
case '0':
return 'running';
case 1:
case '1':
return 'success';
case -1:
case '-1':
return 'failed';
default:
return '';
}
};
function IDEContainer() {
const [selected, setSelected] = useState<Selected>({});
const [urlParams, setUrlParams] = useState<UrlParamsOptions>({});
@ -94,25 +110,10 @@ function IDEContainer() {
// 处理节点状态更新
if (socketMessage?.nodeLog) {
const { nodeId, state, runLog, appId } = socketMessage.nodeLog;
// 将状态映射为前端使用的状态
let status = 'waiting';
switch (state) {
case 0: // 运行中
status = 'running';
break;
case 1: // 运行成功
status = 'success';
break;
case -1: // 运行失败
status = 'failed';
break;
default:// 等待运行
status = 'waiting';
break;
}
const status = getRuntimeNodeStatus(state);
// 更新节点状态使用特殊的actionType标记这是运行时状态更新
// 如果后端提供了 appId则传递给 action 以确保更新正确的应用状态
if (nodeId) {
if (nodeId && status) {
dispatch(updateNodeStatus({ nodeId, status, appId, actionType: 'RUNTIME_UPDATE' }));
}
@ -160,6 +161,7 @@ function IDEContainer() {
if (!canceled && reconnectResult?.success === false) {
lastReconnectKeyRef.current = '';
Message.error(reconnectResult.message || '运行实例重连失败');
return;
}
} catch (error) {
if (!canceled) {

@ -4,6 +4,28 @@ import LoopNode from '@/components/FlowEditor/node/loopNode/LoopNode';
import { updateEventNodeList } from '@/store/ideContainer';
import { resolveNodeComponent } from '@/utils/flow/nodeRegistry';
const runtimeToEditorNodeTypeMap: Record<string, string> = {
SHOW_IMAGE: 'IMAGE',
SHOW_RESULT: 'RESULT',
JSON_CONVERT: 'JSONCONVERT',
};
const editorToRuntimeNodeTypeMap: Record<string, string> = {
IMAGE: 'SHOW_IMAGE',
RESULT: 'SHOW_RESULT',
JSONCONVERT: 'JSON_CONVERT',
};
export const toEditorNodeType = (type?: string) => {
if (!type) return type;
return runtimeToEditorNodeTypeMap[type] || type;
};
export const toRuntimeComponentType = (type?: string) => {
if (!type) return type;
return editorToRuntimeNodeTypeMap[type] || type;
};
/**
* flow editor nodes edges
* @param flowData -
@ -117,17 +139,18 @@ export const convertFlowData = (flowData: any, useDefault = true) => {
// 确定节点类型
let nodeType = 'BASIC';
const componentType = toEditorNodeType(nodeConfig.component?.type);
if (nodeId.includes('start')) {
nodeType = 'start';
} else if (nodeId.includes('end')) {
nodeType = 'end';
} else if (
nodeConfig.component?.type === 'LOOP_START' ||
nodeConfig.component?.type === 'LOOP_END'
componentType === 'LOOP_START' ||
componentType === 'LOOP_END'
) {
nodeType = 'LOOP';
} else {
nodeType = nodeConfig.component?.type || 'BASIC';
nodeType = componentType || 'BASIC';
}
// 解析位置信息
const position = nodeConfig.position || { x: 0, y: 0 };
@ -145,13 +168,13 @@ export const convertFlowData = (flowData: any, useDefault = true) => {
dataIns: getNodeDataIns(nodeConfig),
dataOuts: nodeConfig.dataOuts || [],
},
type: nodeConfig.component?.type || nodeType,
type: componentType || nodeType,
},
};
// 添加组件标识信息
if (nodeConfig.component) {
node.data.component = { ...nodeConfig.component };
node.data.component = { ...nodeConfig.component, type: componentType };
node.data.compId = nodeConfig.component.compId;
}
@ -470,7 +493,7 @@ export const revertFlowData = (nodes: any[], edges: any[]) => {
// 处理 component 信息
if (node.data?.component) {
nodeConfig.component = {
type: nodeType,
type: toRuntimeComponentType(nodeType),
compIdentifier: node.data.component.compIdentifier || '',
compInstanceIdentifier:
node.data.component.compInstanceIdentifier || '',
@ -481,7 +504,7 @@ export const revertFlowData = (nodes: any[], edges: any[]) => {
} else if (nodeType !== 'start' && nodeType !== 'end') {
// 对于非 start/end 节点,添加基本的 component 信息
nodeConfig.component = {
type: nodeType,
type: toRuntimeComponentType(nodeType),
};
}
if (['BASIC', 'SUB'].includes(nodeType))
@ -624,10 +647,13 @@ export const reverseConvertFlowData = (
}),
};
} else if (node.data?.component) {
nodeConfig.component = { ...node.data.component };
nodeConfig.component = {
...node.data.component,
type: toRuntimeComponentType(node.data.component.type || node.type),
};
} else {
nodeConfig.component = {
type: node.type,
type: toRuntimeComponentType(node.type),
};
}

Loading…
Cancel
Save