From 1f41521c21fd474556ab61f27f9ddfaf726d6439 Mon Sep 17 00:00:00 2001 From: StyleZhang Date: Wed, 13 Mar 2024 15:38:35 +0800 Subject: [PATCH] workflow run --- .../[appId]/workflow/page.tsx | 4 +- web/app/components/workflow/custom-edge.tsx | 2 +- web/app/components/workflow/header/index.tsx | 6 +- .../components/workflow/header/publish.tsx | 14 ++- .../workflow/hooks/use-workflow-run.ts | 92 ++++++++++++++++--- web/app/components/workflow/index.tsx | 5 +- .../components/workflow/operator/index.tsx | 2 +- .../workflow/operator/zoom-in-out.tsx | 2 +- web/app/components/workflow/panel/record.tsx | 12 +-- web/app/components/workflow/store.ts | 15 ++- web/app/components/workflow/types.ts | 1 + 11 files changed, 123 insertions(+), 32 deletions(-) diff --git a/web/app/(commonLayout)/app/(appDetailLayout)/[appId]/workflow/page.tsx b/web/app/(commonLayout)/app/(appDetailLayout)/[appId]/workflow/page.tsx index 04ef37483f..bb57d526c9 100644 --- a/web/app/(commonLayout)/app/(appDetailLayout)/[appId]/workflow/page.tsx +++ b/web/app/(commonLayout)/app/(appDetailLayout)/[appId]/workflow/page.tsx @@ -5,7 +5,9 @@ import Workflow from '@/app/components/workflow' const Page = () => { return ( - +
+ +
) } export default memo(Page) diff --git a/web/app/components/workflow/custom-edge.tsx b/web/app/components/workflow/custom-edge.tsx index 8a31c98957..4a8e363aa6 100644 --- a/web/app/components/workflow/custom-edge.tsx +++ b/web/app/components/workflow/custom-edge.tsx @@ -36,7 +36,7 @@ const CustomEdge = ({ id={id} path={edgePath} style={{ - stroke: (selected || data?._connectedNodeIsHovering) ? '#2970FF' : '#D0D5DD', + stroke: (selected || data?._connectedNodeIsHovering || data?._runned) ? '#2970FF' : '#D0D5DD', strokeWidth: 2, }} /> diff --git a/web/app/components/workflow/header/index.tsx b/web/app/components/workflow/header/index.tsx index 6e428ef6e6..9ad43692be 100644 --- a/web/app/components/workflow/header/index.tsx +++ b/web/app/components/workflow/header/index.tsx @@ -27,8 +27,11 @@ const Header: FC = () => { const { handleRunSetting } = useWorkflowRun() const handleShowFeatures = useCallback(() => { + if (runningStatus) + return + useStore.setState({ showFeaturesPanel: true }) - }, []) + }, [runningStatus]) const handleGoBackToEdit = useCallback(() => { handleRunSetting(true) @@ -77,6 +80,7 @@ const Header: FC = () => { className={` mr-2 px-3 py-0 h-8 bg-white text-[13px] font-medium text-gray-700 border-[0.5px] border-gray-200 shadow-xs + ${runningStatus && '!cursor-not-allowed opacity-50'} `} onClick={handleShowFeatures} > diff --git a/web/app/components/workflow/header/publish.tsx b/web/app/components/workflow/header/publish.tsx index 884b3046a2..d5902d12ed 100644 --- a/web/app/components/workflow/header/publish.tsx +++ b/web/app/components/workflow/header/publish.tsx @@ -1,5 +1,6 @@ import { useState } from 'react' import { useTranslation } from 'react-i18next' +import { useStore } from '../store' import Button from '@/app/components/base/button' import { PortalToFollowElem, @@ -9,6 +10,7 @@ import { const Publish = () => { const { t } = useTranslation() + const runningStatus = useStore(s => s.runningStatus) const [open, setOpen] = useState(false) return ( @@ -21,10 +23,18 @@ const Publish = () => { crossAxis: -5, }} > - setOpen(v => !v)}> + { + if (runningStatus) + return + + setOpen(v => !v) + }}> diff --git a/web/app/components/workflow/hooks/use-workflow-run.ts b/web/app/components/workflow/hooks/use-workflow-run.ts index 6091432431..aa3cc5906e 100644 --- a/web/app/components/workflow/hooks/use-workflow-run.ts +++ b/web/app/components/workflow/hooks/use-workflow-run.ts @@ -1,4 +1,7 @@ -import { useCallback } from 'react' +import { + useCallback, + useRef, +} from 'react' import { useReactFlow, useStoreApi, @@ -9,6 +12,7 @@ import { NodeRunningStatus, WorkflowRunningStatus, } from '../types' +import { NODE_WIDTH } from '../constants' import { useStore as useAppStore } from '@/app/components/app/store' import type { IOtherOptions } from '@/service/base' import { ssePost } from '@/service/base' @@ -16,24 +20,78 @@ import { ssePost } from '@/service/base' export const useWorkflowRun = () => { const store = useStoreApi() const reactflow = useReactFlow() + const workflowContainerRef = useRef(null) + + const handleLoadBackupDraft = useCallback(() => { + const { + setNodes, + setEdges, + } = store.getState() + const { setViewport } = reactflow + const { backupDraft } = useStore.getState() + + if (backupDraft) { + const { + nodes, + edges, + viewport, + } = backupDraft + setNodes(nodes) + setEdges(edges) + setViewport(viewport) + } + }, [store, reactflow]) const handleRunSetting = useCallback((shouldClear?: boolean) => { useStore.setState({ runningStatus: shouldClear ? undefined : WorkflowRunningStatus.Waiting }) - const { setNodes, getNodes } = store.getState() - const newNodes = produce(getNodes(), (draft) => { - draft.forEach((node) => { - node.data._runningStatus = shouldClear ? undefined : NodeRunningStatus.Waiting + const { + setNodes, + getNodes, + edges, + setEdges, + } = store.getState() + + if (shouldClear) { + handleLoadBackupDraft() + } + else { + const newNodes = produce(getNodes(), (draft) => { + draft.forEach((node) => { + node.data._runningStatus = shouldClear ? undefined : NodeRunningStatus.Waiting + }) }) - }) - setNodes(newNodes) - }, [store]) + setNodes(newNodes) + const newEdges = produce(edges, (draft) => { + draft.forEach((edge) => { + edge.data = { ...edge.data, _runned: false } + }) + }) + setEdges(newEdges) + } + }, [store, handleLoadBackupDraft]) const handleRun = useCallback((params: any, callback?: IOtherOptions) => { const { getNodes, setNodes, + edges, + setEdges, } = store.getState() + const { getViewport } = reactflow + const { setBackupDraft } = useStore.getState() const appDetail = useAppStore.getState().appDetail + const workflowContainer = document.getElementById('workflow-container') + + const { + clientWidth, + clientHeight, + } = workflowContainer! + + setBackupDraft({ + nodes: getNodes(), + edges, + viewport: getViewport(), + }) let url = '' if (appDetail?.mode === 'advanced-chat') @@ -69,19 +127,28 @@ export const useWorkflowRun = () => { getViewport, setViewport, } = reactflow + const viewport = getViewport() const currentNodeIndex = nodes.findIndex(node => node.id === data.node_id) - const position = nodes[currentNodeIndex].position - const zoom = 1 + const currentNode = nodes[currentNodeIndex] + const position = currentNode.position + const zoom = 0.5 setViewport({ zoom, - x: 200 / viewport.zoom - position.x, - y: 200 / viewport.zoom - position.y, + x: (((clientWidth - 400) / 2 - NODE_WIDTH / 2) / viewport.zoom - position.x) * zoom, + y: ((clientHeight / 2 - currentNode.height! / 2) / viewport.zoom - position.y) * zoom, }) const newNodes = produce(nodes, (draft) => { draft[currentNodeIndex].data._runningStatus = NodeRunningStatus.Running }) setNodes(newNodes) + const newEdges = produce(edges, (draft) => { + const edge = draft.find(edge => edge.target === data.node_id) + + if (edge) + edge.data = { ...edge.data, _runned: true } + }) + setEdges(newEdges) }, onNodeFinished: ({ data }) => { const newNodes = produce(getNodes(), (draft) => { @@ -99,5 +166,6 @@ export const useWorkflowRun = () => { return { handleRunSetting, handleRun, + workflowContainerRef, } } diff --git a/web/app/components/workflow/index.tsx b/web/app/components/workflow/index.tsx index fe12a32ed3..d2cca2afd3 100644 --- a/web/app/components/workflow/index.tsx +++ b/web/app/components/workflow/index.tsx @@ -100,7 +100,10 @@ const Workflow: FC = memo(({ useKeyPress('Backspace', handleEdgeDelete) return ( -
+
diff --git a/web/app/components/workflow/operator/index.tsx b/web/app/components/workflow/operator/index.tsx index 166bf881c6..72cf5323a1 100644 --- a/web/app/components/workflow/operator/index.tsx +++ b/web/app/components/workflow/operator/index.tsx @@ -35,7 +35,7 @@ const Operator = () => {
diff --git a/web/app/components/workflow/operator/zoom-in-out.tsx b/web/app/components/workflow/operator/zoom-in-out.tsx index 5e1b9ef9d8..8d732df726 100644 --- a/web/app/components/workflow/operator/zoom-in-out.tsx +++ b/web/app/components/workflow/operator/zoom-in-out.tsx @@ -100,7 +100,7 @@ const ZoomInOut: FC = () => {
{parseFloat(`${zoom * 100}`).toFixed(0)}%
diff --git a/web/app/components/workflow/panel/record.tsx b/web/app/components/workflow/panel/record.tsx index 1a5deb4707..ad5be474e7 100644 --- a/web/app/components/workflow/panel/record.tsx +++ b/web/app/components/workflow/panel/record.tsx @@ -1,23 +1,13 @@ import { memo } from 'react' import Run from '../run' import { useStore } from '../store' -import { XClose } from '@/app/components/base/icons/src/vender/line/general' const Record = () => { - const { currentSequenceNumber, setCurrentSequenceNumber, workflowRunId, setWorkflowRunId } = useStore() + const { currentSequenceNumber, workflowRunId } = useStore() return (
{`Test Run#${currentSequenceNumber}`} -
{ - setWorkflowRunId('') - setCurrentSequenceNumber(0) - }} - > - -
diff --git a/web/app/components/workflow/store.ts b/web/app/components/workflow/store.ts index 524f135970..9c99ef812d 100644 --- a/web/app/components/workflow/store.ts +++ b/web/app/components/workflow/store.ts @@ -1,4 +1,5 @@ import { create } from 'zustand' +import type { Viewport } from 'reactflow' import type { HelpLineHorizontalPosition, HelpLineVerticalPosition, @@ -9,7 +10,11 @@ import type { ToolsMap, } from './block-selector/types' import { Mode } from './types' -import type { WorkflowRunningStatus } from './types' +import type { + Edge, + Node, + WorkflowRunningStatus, +} from './types' type State = { mode: Mode @@ -27,6 +32,11 @@ type State = { runningStatus?: WorkflowRunningStatus showInputsPanel: boolean inputs: Record + backupDraft?: { + nodes: Node[] + edges: Edge[] + viewport: Viewport + } } type Action = { @@ -45,6 +55,7 @@ type Action = { setRunningStatus: (runningStatus?: WorkflowRunningStatus) => void setShowInputsPanel: (showInputsPanel: boolean) => void setInputs: (inputs: Record) => void + setBackupDraft: (backupDraft?: State['backupDraft']) => void } export const useStore = create(set => ({ @@ -78,4 +89,6 @@ export const useStore = create(set => ({ setShowInputsPanel: showInputsPanel => set(() => ({ showInputsPanel })), inputs: {}, setInputs: inputs => set(() => ({ inputs })), + backupDraft: undefined, + setBackupDraft: backupDraft => set(() => ({ backupDraft })), })) diff --git a/web/app/components/workflow/types.ts b/web/app/components/workflow/types.ts index 634f023c2c..cc2d031547 100644 --- a/web/app/components/workflow/types.ts +++ b/web/app/components/workflow/types.ts @@ -38,6 +38,7 @@ export type CommonNodeType = { export type CommonEdgeType = { _hovering: boolean _connectedNodeIsHovering: boolean + _runned?: boolean } export type Node = ReactFlowNode>