| | |
| | | import { useStore, useWorkflowStore } from '../store' |
| | | import { |
| | | CUSTOM_NODE, DSL_EXPORT_CHECK, |
| | | NODE_LAYOUT_HORIZONTAL_PADDING, |
| | | NODE_LAYOUT_VERTICAL_PADDING, |
| | | WORKFLOW_DATA_UPDATE, |
| | | } from '../constants' |
| | | import type { Node, WorkflowDataUpdater } from '../types' |
| | | import { BlockEnum, ControlMode } from '../types' |
| | | import { ControlMode } from '../types' |
| | | import { |
| | | getLayoutByDagre, |
| | | getLayoutForChildNodes, |
| | | initialEdges, |
| | | initialNodes, |
| | | } from '../utils' |
| | |
| | | useSelectionInteractions, |
| | | useWorkflowReadOnly, |
| | | } from '../hooks' |
| | | import { useEdgesInteractionsWithoutSync } from './use-edges-interactions-without-sync' |
| | | import { useNodesInteractionsWithoutSync } from './use-nodes-interactions-without-sync' |
| | | import { useEdgesInteractions } from './use-edges-interactions' |
| | | import { useNodesInteractions } from './use-nodes-interactions' |
| | | import { useNodesSyncDraft } from './use-nodes-sync-draft' |
| | | import { WorkflowHistoryEvent, useWorkflowHistory } from './use-workflow-history' |
| | | import { useEventEmitterContextContext } from '@/context/event-emitter' |
| | |
| | | |
| | | export const useWorkflowInteractions = () => { |
| | | const workflowStore = useWorkflowStore() |
| | | const { handleNodeCancelRunningStatus } = useNodesInteractionsWithoutSync() |
| | | const { handleEdgeCancelRunningStatus } = useEdgesInteractionsWithoutSync() |
| | | const { handleNodeCancelRunningStatus } = useNodesInteractions() |
| | | const { handleEdgeCancelRunningStatus } = useEdgesInteractions() |
| | | |
| | | const handleCancelDebugAndPreviewPanel = useCallback(() => { |
| | | workflowStore.setState({ |
| | |
| | | } = store.getState() |
| | | const { setViewport } = reactflow |
| | | const nodes = getNodes() |
| | | |
| | | const loopAndIterationNodes = nodes.filter( |
| | | node => (node.data.type === BlockEnum.Loop || node.data.type === BlockEnum.Iteration) |
| | | && !node.parentId |
| | | && node.type === CUSTOM_NODE, |
| | | ) |
| | | |
| | | const childLayoutsMap: Record<string, any> = {} |
| | | loopAndIterationNodes.forEach((node) => { |
| | | childLayoutsMap[node.id] = getLayoutForChildNodes(node.id, nodes, edges) |
| | | }) |
| | | |
| | | const containerSizeChanges: Record<string, { width: number, height: number }> = {} |
| | | |
| | | loopAndIterationNodes.forEach((parentNode) => { |
| | | const childLayout = childLayoutsMap[parentNode.id] |
| | | if (!childLayout) return |
| | | |
| | | let minX = Infinity |
| | | let minY = Infinity |
| | | let maxX = -Infinity |
| | | let maxY = -Infinity |
| | | let hasChildren = false |
| | | |
| | | const childNodes = nodes.filter(node => node.parentId === parentNode.id) |
| | | |
| | | childNodes.forEach((node) => { |
| | | if (childLayout.node(node.id)) { |
| | | hasChildren = true |
| | | const childNodeWithPosition = childLayout.node(node.id) |
| | | |
| | | const nodeX = childNodeWithPosition.x - node.width! / 2 |
| | | const nodeY = childNodeWithPosition.y - node.height! / 2 |
| | | |
| | | minX = Math.min(minX, nodeX) |
| | | minY = Math.min(minY, nodeY) |
| | | maxX = Math.max(maxX, nodeX + node.width!) |
| | | maxY = Math.max(maxY, nodeY + node.height!) |
| | | } |
| | | }) |
| | | |
| | | if (hasChildren) { |
| | | const requiredWidth = maxX - minX + NODE_LAYOUT_HORIZONTAL_PADDING * 2 |
| | | const requiredHeight = maxY - minY + NODE_LAYOUT_VERTICAL_PADDING * 2 |
| | | |
| | | containerSizeChanges[parentNode.id] = { |
| | | width: Math.max(parentNode.width || 0, requiredWidth), |
| | | height: Math.max(parentNode.height || 0, requiredHeight), |
| | | } |
| | | } |
| | | }) |
| | | |
| | | const nodesWithUpdatedSizes = produce(nodes, (draft) => { |
| | | draft.forEach((node) => { |
| | | if ((node.data.type === BlockEnum.Loop || node.data.type === BlockEnum.Iteration) |
| | | && containerSizeChanges[node.id]) { |
| | | node.width = containerSizeChanges[node.id].width |
| | | node.height = containerSizeChanges[node.id].height |
| | | |
| | | if (node.data.type === BlockEnum.Loop) { |
| | | node.data.width = containerSizeChanges[node.id].width |
| | | node.data.height = containerSizeChanges[node.id].height |
| | | } |
| | | else if (node.data.type === BlockEnum.Iteration) { |
| | | node.data.width = containerSizeChanges[node.id].width |
| | | node.data.height = containerSizeChanges[node.id].height |
| | | } |
| | | } |
| | | }) |
| | | }) |
| | | |
| | | const layout = getLayoutByDagre(nodesWithUpdatedSizes, edges) |
| | | |
| | | const layout = getLayoutByDagre(nodes, edges) |
| | | const rankMap = {} as Record<string, Node> |
| | | nodesWithUpdatedSizes.forEach((node) => { |
| | | |
| | | nodes.forEach((node) => { |
| | | if (!node.parentId && node.type === CUSTOM_NODE) { |
| | | const rank = layout.node(node.id).rank! |
| | | |
| | |
| | | } |
| | | }) |
| | | |
| | | const newNodes = produce(nodesWithUpdatedSizes, (draft) => { |
| | | const newNodes = produce(nodes, (draft) => { |
| | | draft.forEach((node) => { |
| | | if (!node.parentId && node.type === CUSTOM_NODE) { |
| | | const nodeWithPosition = layout.node(node.id) |
| | |
| | | } |
| | | } |
| | | }) |
| | | |
| | | loopAndIterationNodes.forEach((parentNode) => { |
| | | const childLayout = childLayoutsMap[parentNode.id] |
| | | if (!childLayout) return |
| | | |
| | | const childNodes = draft.filter(node => node.parentId === parentNode.id) |
| | | |
| | | let minX = Infinity |
| | | let minY = Infinity |
| | | |
| | | childNodes.forEach((node) => { |
| | | if (childLayout.node(node.id)) { |
| | | const childNodeWithPosition = childLayout.node(node.id) |
| | | const nodeX = childNodeWithPosition.x - node.width! / 2 |
| | | const nodeY = childNodeWithPosition.y - node.height! / 2 |
| | | |
| | | minX = Math.min(minX, nodeX) |
| | | minY = Math.min(minY, nodeY) |
| | | } |
| | | }) |
| | | |
| | | childNodes.forEach((node) => { |
| | | if (childLayout.node(node.id)) { |
| | | const childNodeWithPosition = childLayout.node(node.id) |
| | | |
| | | node.position = { |
| | | x: NODE_LAYOUT_HORIZONTAL_PADDING + (childNodeWithPosition.x - node.width! / 2 - minX), |
| | | y: NODE_LAYOUT_VERTICAL_PADDING + (childNodeWithPosition.y - node.height! / 2 - minY), |
| | | } |
| | | } |
| | | }) |
| | | }) |
| | | }) |
| | | |
| | | setNodes(newNodes) |
| | | const zoom = 0.7 |
| | | setViewport({ |
| | |
| | | handleSyncWorkflowDraft() |
| | | }) |
| | | }, [getNodesReadOnly, store, reactflow, workflowStore, handleSyncWorkflowDraft, saveStateToHistory]) |
| | | |
| | | return { |
| | | handleLayout, |
| | | } |
| | |
| | | |
| | | export const useWorkflowUpdate = () => { |
| | | const reactflow = useReactFlow() |
| | | const workflowStore = useWorkflowStore() |
| | | const { eventEmitter } = useEventEmitterContextContext() |
| | | |
| | | const handleUpdateWorkflowCanvas = useCallback((payload: WorkflowDataUpdater) => { |
| | |
| | | setViewport(viewport) |
| | | }, [eventEmitter, reactflow]) |
| | | |
| | | const handleRefreshWorkflowDraft = useCallback(() => { |
| | | const { |
| | | appId, |
| | | setSyncWorkflowDraftHash, |
| | | setIsSyncingWorkflowDraft, |
| | | setEnvironmentVariables, |
| | | setEnvSecrets, |
| | | setConversationVariables, |
| | | } = workflowStore.getState() |
| | | setIsSyncingWorkflowDraft(true) |
| | | fetchWorkflowDraft(`/apps/${appId}/workflows/draft`).then((response) => { |
| | | handleUpdateWorkflowCanvas(response.graph as WorkflowDataUpdater) |
| | | setSyncWorkflowDraftHash(response.hash) |
| | | setEnvSecrets((response.environment_variables || []).filter(env => env.value_type === 'secret').reduce((acc, env) => { |
| | | acc[env.id] = env.value |
| | | return acc |
| | | }, {} as Record<string, string>)) |
| | | setEnvironmentVariables(response.environment_variables?.map(env => env.value_type === 'secret' ? { ...env, value: '[__HIDDEN__]' } : env) || []) |
| | | // #TODO chatVar sync# |
| | | setConversationVariables(response.conversation_variables || []) |
| | | }).finally(() => setIsSyncingWorkflowDraft(false)) |
| | | }, [handleUpdateWorkflowCanvas, workflowStore]) |
| | | |
| | | return { |
| | | handleUpdateWorkflowCanvas, |
| | | handleRefreshWorkflowDraft, |
| | | } |
| | | } |
| | | |
| | |
| | | a.download = `${appDetail.name}.yml` |
| | | a.click() |
| | | } |
| | | catch { |
| | | catch (e) { |
| | | notify({ type: 'error', message: t('app.exportFailed') }) |
| | | } |
| | | finally { |
| | |
| | | }, |
| | | } as any) |
| | | } |
| | | catch { |
| | | catch (e) { |
| | | notify({ type: 'error', message: t('app.exportFailed') }) |
| | | } |
| | | }, [appDetail, eventEmitter, handleExportDSL, notify, t]) |