diff --git a/src/hooks/useFlowEditorState.ts b/src/hooks/useFlowEditorState.ts index 7fc31c5..ba33610 100644 --- a/src/hooks/useFlowEditorState.ts +++ b/src/hooks/useFlowEditorState.ts @@ -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([]); const [edges, setEdges] = useState([]); @@ -46,6 +93,11 @@ export const useFlowEditorState = (initialData?: any, readOnly?: boolean) => { // 在组件顶部添加历史记录相关状态 const [historyInitialized, setHistoryInitialized] = useState(false); const historyTimeoutRef = useRef(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( ( diff --git a/src/pages/ideContainer/index.tsx b/src/pages/ideContainer/index.tsx index 57186e3..a945de5 100644 --- a/src/pages/ideContainer/index.tsx +++ b/src/pages/ideContainer/index.tsx @@ -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({}); const [urlParams, setUrlParams] = useState({}); @@ -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) { diff --git a/src/utils/convertFlowData.ts b/src/utils/convertFlowData.ts index f650169..0e1a3bf 100644 --- a/src/utils/convertFlowData.ts +++ b/src/utils/convertFlowData.ts @@ -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 = { + SHOW_IMAGE: 'IMAGE', + SHOW_RESULT: 'RESULT', + JSON_CONVERT: 'JSONCONVERT', +}; + +const editorToRuntimeNodeTypeMap: Record = { + 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), }; }