Commit 1f41521c authored by StyleZhang's avatar StyleZhang

workflow run

parent e686d422
...@@ -5,7 +5,9 @@ import Workflow from '@/app/components/workflow' ...@@ -5,7 +5,9 @@ import Workflow from '@/app/components/workflow'
const Page = () => { const Page = () => {
return ( return (
<Workflow /> <div className='w-full h-full overflow-x-auto'>
<Workflow />
</div>
) )
} }
export default memo(Page) export default memo(Page)
...@@ -36,7 +36,7 @@ const CustomEdge = ({ ...@@ -36,7 +36,7 @@ const CustomEdge = ({
id={id} id={id}
path={edgePath} path={edgePath}
style={{ style={{
stroke: (selected || data?._connectedNodeIsHovering) ? '#2970FF' : '#D0D5DD', stroke: (selected || data?._connectedNodeIsHovering || data?._runned) ? '#2970FF' : '#D0D5DD',
strokeWidth: 2, strokeWidth: 2,
}} }}
/> />
......
...@@ -27,8 +27,11 @@ const Header: FC = () => { ...@@ -27,8 +27,11 @@ const Header: FC = () => {
const { handleRunSetting } = useWorkflowRun() const { handleRunSetting } = useWorkflowRun()
const handleShowFeatures = useCallback(() => { const handleShowFeatures = useCallback(() => {
if (runningStatus)
return
useStore.setState({ showFeaturesPanel: true }) useStore.setState({ showFeaturesPanel: true })
}, []) }, [runningStatus])
const handleGoBackToEdit = useCallback(() => { const handleGoBackToEdit = useCallback(() => {
handleRunSetting(true) handleRunSetting(true)
...@@ -77,6 +80,7 @@ const Header: FC = () => { ...@@ -77,6 +80,7 @@ const Header: FC = () => {
className={` className={`
mr-2 px-3 py-0 h-8 bg-white text-[13px] font-medium text-gray-700 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 border-[0.5px] border-gray-200 shadow-xs
${runningStatus && '!cursor-not-allowed opacity-50'}
`} `}
onClick={handleShowFeatures} onClick={handleShowFeatures}
> >
......
import { useState } from 'react' import { useState } from 'react'
import { useTranslation } from 'react-i18next' import { useTranslation } from 'react-i18next'
import { useStore } from '../store'
import Button from '@/app/components/base/button' import Button from '@/app/components/base/button'
import { import {
PortalToFollowElem, PortalToFollowElem,
...@@ -9,6 +10,7 @@ import { ...@@ -9,6 +10,7 @@ import {
const Publish = () => { const Publish = () => {
const { t } = useTranslation() const { t } = useTranslation()
const runningStatus = useStore(s => s.runningStatus)
const [open, setOpen] = useState(false) const [open, setOpen] = useState(false)
return ( return (
...@@ -21,10 +23,18 @@ const Publish = () => { ...@@ -21,10 +23,18 @@ const Publish = () => {
crossAxis: -5, crossAxis: -5,
}} }}
> >
<PortalToFollowElemTrigger onClick={() => setOpen(v => !v)}> <PortalToFollowElemTrigger onClick={() => {
if (runningStatus)
return
setOpen(v => !v)
}}>
<Button <Button
type='primary' type='primary'
className='px-3 py-0 h-8 text-[13px] font-medium' className={`
px-3 py-0 h-8 text-[13px] font-medium
${runningStatus && 'cursor-not-allowed opacity-50'}
`}
> >
{t('workflow.common.publish')} {t('workflow.common.publish')}
</Button> </Button>
......
import { useCallback } from 'react' import {
useCallback,
useRef,
} from 'react'
import { import {
useReactFlow, useReactFlow,
useStoreApi, useStoreApi,
...@@ -9,6 +12,7 @@ import { ...@@ -9,6 +12,7 @@ import {
NodeRunningStatus, NodeRunningStatus,
WorkflowRunningStatus, WorkflowRunningStatus,
} from '../types' } from '../types'
import { NODE_WIDTH } from '../constants'
import { useStore as useAppStore } from '@/app/components/app/store' import { useStore as useAppStore } from '@/app/components/app/store'
import type { IOtherOptions } from '@/service/base' import type { IOtherOptions } from '@/service/base'
import { ssePost } from '@/service/base' import { ssePost } from '@/service/base'
...@@ -16,24 +20,78 @@ import { ssePost } from '@/service/base' ...@@ -16,24 +20,78 @@ import { ssePost } from '@/service/base'
export const useWorkflowRun = () => { export const useWorkflowRun = () => {
const store = useStoreApi() const store = useStoreApi()
const reactflow = useReactFlow() const reactflow = useReactFlow()
const workflowContainerRef = useRef<HTMLDivElement>(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) => { const handleRunSetting = useCallback((shouldClear?: boolean) => {
useStore.setState({ runningStatus: shouldClear ? undefined : WorkflowRunningStatus.Waiting }) useStore.setState({ runningStatus: shouldClear ? undefined : WorkflowRunningStatus.Waiting })
const { setNodes, getNodes } = store.getState() const {
const newNodes = produce(getNodes(), (draft) => { setNodes,
draft.forEach((node) => { getNodes,
node.data._runningStatus = shouldClear ? undefined : NodeRunningStatus.Waiting 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)
setNodes(newNodes) const newEdges = produce(edges, (draft) => {
}, [store]) draft.forEach((edge) => {
edge.data = { ...edge.data, _runned: false }
})
})
setEdges(newEdges)
}
}, [store, handleLoadBackupDraft])
const handleRun = useCallback((params: any, callback?: IOtherOptions) => { const handleRun = useCallback((params: any, callback?: IOtherOptions) => {
const { const {
getNodes, getNodes,
setNodes, setNodes,
edges,
setEdges,
} = store.getState() } = store.getState()
const { getViewport } = reactflow
const { setBackupDraft } = useStore.getState()
const appDetail = useAppStore.getState().appDetail const appDetail = useAppStore.getState().appDetail
const workflowContainer = document.getElementById('workflow-container')
const {
clientWidth,
clientHeight,
} = workflowContainer!
setBackupDraft({
nodes: getNodes(),
edges,
viewport: getViewport(),
})
let url = '' let url = ''
if (appDetail?.mode === 'advanced-chat') if (appDetail?.mode === 'advanced-chat')
...@@ -69,19 +127,28 @@ export const useWorkflowRun = () => { ...@@ -69,19 +127,28 @@ export const useWorkflowRun = () => {
getViewport, getViewport,
setViewport, setViewport,
} = reactflow } = reactflow
const viewport = getViewport() const viewport = getViewport()
const currentNodeIndex = nodes.findIndex(node => node.id === data.node_id) const currentNodeIndex = nodes.findIndex(node => node.id === data.node_id)
const position = nodes[currentNodeIndex].position const currentNode = nodes[currentNodeIndex]
const zoom = 1 const position = currentNode.position
const zoom = 0.5
setViewport({ setViewport({
zoom, zoom,
x: 200 / viewport.zoom - position.x, x: (((clientWidth - 400) / 2 - NODE_WIDTH / 2) / viewport.zoom - position.x) * zoom,
y: 200 / viewport.zoom - position.y, y: ((clientHeight / 2 - currentNode.height! / 2) / viewport.zoom - position.y) * zoom,
}) })
const newNodes = produce(nodes, (draft) => { const newNodes = produce(nodes, (draft) => {
draft[currentNodeIndex].data._runningStatus = NodeRunningStatus.Running draft[currentNodeIndex].data._runningStatus = NodeRunningStatus.Running
}) })
setNodes(newNodes) 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 }) => { onNodeFinished: ({ data }) => {
const newNodes = produce(getNodes(), (draft) => { const newNodes = produce(getNodes(), (draft) => {
...@@ -99,5 +166,6 @@ export const useWorkflowRun = () => { ...@@ -99,5 +166,6 @@ export const useWorkflowRun = () => {
return { return {
handleRunSetting, handleRunSetting,
handleRun, handleRun,
workflowContainerRef,
} }
} }
...@@ -100,7 +100,10 @@ const Workflow: FC<WorkflowProps> = memo(({ ...@@ -100,7 +100,10 @@ const Workflow: FC<WorkflowProps> = memo(({
useKeyPress('Backspace', handleEdgeDelete) useKeyPress('Backspace', handleEdgeDelete)
return ( return (
<div className='relative w-full h-full bg-[#F0F2F7]'> <div
id='workflow-container'
className='relative w-full min-w-[960px] h-full bg-[#F0F2F7]'
>
<Header /> <Header />
<Panel /> <Panel />
<Operator /> <Operator />
......
...@@ -35,7 +35,7 @@ const Operator = () => { ...@@ -35,7 +35,7 @@ const Operator = () => {
<div <div
className={` className={`
ml-[1px] flex items-center justify-center w-8 h-8 cursor-pointer hover:bg-black/5 rounded-lg ml-[1px] flex items-center justify-center w-8 h-8 cursor-pointer hover:bg-black/5 rounded-lg
${runningStatus && '!cursor-not-allowed'} ${runningStatus && '!cursor-not-allowed opacity-50'}
`} `}
onClick={goLayout} onClick={goLayout}
> >
......
...@@ -100,7 +100,7 @@ const ZoomInOut: FC = () => { ...@@ -100,7 +100,7 @@ const ZoomInOut: FC = () => {
<div className={` <div className={`
flex items-center px-2 h-8 cursor-pointer text-[13px] hover:bg-gray-50 rounded-lg flex items-center px-2 h-8 cursor-pointer text-[13px] hover:bg-gray-50 rounded-lg
${open && 'bg-gray-50'} ${open && 'bg-gray-50'}
${runningStatus && '!cursor-not-allowed'} ${runningStatus && '!cursor-not-allowed opacity-50'}
`}> `}>
<SearchLg className='mr-1 w-4 h-4' /> <SearchLg className='mr-1 w-4 h-4' />
<div className='w-[34px]'>{parseFloat(`${zoom * 100}`).toFixed(0)}%</div> <div className='w-[34px]'>{parseFloat(`${zoom * 100}`).toFixed(0)}%</div>
......
import { memo } from 'react' import { memo } from 'react'
import Run from '../run' import Run from '../run'
import { useStore } from '../store' import { useStore } from '../store'
import { XClose } from '@/app/components/base/icons/src/vender/line/general'
const Record = () => { const Record = () => {
const { currentSequenceNumber, setCurrentSequenceNumber, workflowRunId, setWorkflowRunId } = useStore() const { currentSequenceNumber, workflowRunId } = useStore()
return ( return (
<div className='flex flex-col w-[400px] h-full rounded-2xl border-[0.5px] border-gray-200 shadow-xl bg-white'> <div className='flex flex-col w-[400px] h-full rounded-2xl border-[0.5px] border-gray-200 shadow-xl bg-white'>
<div className='flex items-center justify-between p-4 pb-1 text-base font-semibold text-gray-900'> <div className='flex items-center justify-between p-4 pb-1 text-base font-semibold text-gray-900'>
{`Test Run#${currentSequenceNumber}`} {`Test Run#${currentSequenceNumber}`}
<div
className='flex items-center justify-center w-6 h-6 cursor-pointer'
onClick={() => {
setWorkflowRunId('')
setCurrentSequenceNumber(0)
}}
>
<XClose className='w-4 h-4 text-gray-500' />
</div>
</div> </div>
<Run runID={workflowRunId} /> <Run runID={workflowRunId} />
</div> </div>
......
import { create } from 'zustand' import { create } from 'zustand'
import type { Viewport } from 'reactflow'
import type { import type {
HelpLineHorizontalPosition, HelpLineHorizontalPosition,
HelpLineVerticalPosition, HelpLineVerticalPosition,
...@@ -9,7 +10,11 @@ import type { ...@@ -9,7 +10,11 @@ import type {
ToolsMap, ToolsMap,
} from './block-selector/types' } from './block-selector/types'
import { Mode } from './types' import { Mode } from './types'
import type { WorkflowRunningStatus } from './types' import type {
Edge,
Node,
WorkflowRunningStatus,
} from './types'
type State = { type State = {
mode: Mode mode: Mode
...@@ -27,6 +32,11 @@ type State = { ...@@ -27,6 +32,11 @@ type State = {
runningStatus?: WorkflowRunningStatus runningStatus?: WorkflowRunningStatus
showInputsPanel: boolean showInputsPanel: boolean
inputs: Record<string, string> inputs: Record<string, string>
backupDraft?: {
nodes: Node[]
edges: Edge[]
viewport: Viewport
}
} }
type Action = { type Action = {
...@@ -45,6 +55,7 @@ type Action = { ...@@ -45,6 +55,7 @@ type Action = {
setRunningStatus: (runningStatus?: WorkflowRunningStatus) => void setRunningStatus: (runningStatus?: WorkflowRunningStatus) => void
setShowInputsPanel: (showInputsPanel: boolean) => void setShowInputsPanel: (showInputsPanel: boolean) => void
setInputs: (inputs: Record<string, string>) => void setInputs: (inputs: Record<string, string>) => void
setBackupDraft: (backupDraft?: State['backupDraft']) => void
} }
export const useStore = create<State & Action>(set => ({ export const useStore = create<State & Action>(set => ({
...@@ -78,4 +89,6 @@ export const useStore = create<State & Action>(set => ({ ...@@ -78,4 +89,6 @@ export const useStore = create<State & Action>(set => ({
setShowInputsPanel: showInputsPanel => set(() => ({ showInputsPanel })), setShowInputsPanel: showInputsPanel => set(() => ({ showInputsPanel })),
inputs: {}, inputs: {},
setInputs: inputs => set(() => ({ inputs })), setInputs: inputs => set(() => ({ inputs })),
backupDraft: undefined,
setBackupDraft: backupDraft => set(() => ({ backupDraft })),
})) }))
...@@ -38,6 +38,7 @@ export type CommonNodeType<T = {}> = { ...@@ -38,6 +38,7 @@ export type CommonNodeType<T = {}> = {
export type CommonEdgeType = { export type CommonEdgeType = {
_hovering: boolean _hovering: boolean
_connectedNodeIsHovering: boolean _connectedNodeIsHovering: boolean
_runned?: boolean
} }
export type Node<T = {}> = ReactFlowNode<CommonNodeType<T>> export type Node<T = {}> = ReactFlowNode<CommonNodeType<T>>
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment