From f23d0774f82661e2906405abaea77c4fd9315522 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E9=B1=BC=E6=98=9F?= <1772580802@qq.com> Date: Tue, 2 Jun 2026 14:01:22 +0800 Subject: [PATCH 1/2] =?UTF-8?q?feat:=20=E4=BF=AE=E5=A4=8D=E5=9B=BE?= =?UTF-8?q?=E7=89=87=E5=B1=95=E7=A4=BA=E7=BB=84=E4=BB=B6?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../sideBar/config/localNodeData.ts | 1 + src/utils/convertFlowData.ts | 44 +++++++++++++++---- 2 files changed, 36 insertions(+), 9 deletions(-) diff --git a/src/pages/flowEditor/sideBar/config/localNodeData.ts b/src/pages/flowEditor/sideBar/config/localNodeData.ts index 265c3e8..88bb984 100644 --- a/src/pages/flowEditor/sideBar/config/localNodeData.ts +++ b/src/pages/flowEditor/sideBar/config/localNodeData.ts @@ -84,6 +84,7 @@ const imageParameters = { defaultValue: '' }], dataIns: [{ + id: 'in', name: 'in', desc: 'url', dataType: 'STRING', 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), }; } From 52c55b5988ab2447c02e722e21e5885952c1e561 Mon Sep 17 00:00:00 2001 From: zly Date: Tue, 2 Jun 2026 15:03:15 +0800 Subject: [PATCH 2/2] =?UTF-8?q?pref:=20=E4=BC=98=E5=8C=96=E7=8A=B6?= =?UTF-8?q?=E6=80=81=E6=8C=87=E7=A4=BA=E5=99=A8=E7=9A=84=E6=B8=B2=E6=9F=93?= =?UTF-8?q?=E6=97=B6=E6=9C=BA?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/hooks/useFlowEditorState.ts | 118 ++++++++++++++++++++++++++++++- src/pages/ideContainer/index.tsx | 36 +++++----- 2 files changed, 136 insertions(+), 18 deletions(-) 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) {