mirror of https://github.com/langgenius/dify.git
workflow run
This commit is contained in:
parent
e686d42262
commit
1f41521c21
|
|
@ -5,7 +5,9 @@ import Workflow from '@/app/components/workflow'
|
|||
|
||||
const Page = () => {
|
||||
return (
|
||||
<Workflow />
|
||||
<div className='w-full h-full overflow-x-auto'>
|
||||
<Workflow />
|
||||
</div>
|
||||
)
|
||||
}
|
||||
export default memo(Page)
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
}}
|
||||
/>
|
||||
|
|
|
|||
|
|
@ -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}
|
||||
>
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
}}
|
||||
>
|
||||
<PortalToFollowElemTrigger onClick={() => setOpen(v => !v)}>
|
||||
<PortalToFollowElemTrigger onClick={() => {
|
||||
if (runningStatus)
|
||||
return
|
||||
|
||||
setOpen(v => !v)
|
||||
}}>
|
||||
<Button
|
||||
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')}
|
||||
</Button>
|
||||
|
|
|
|||
|
|
@ -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<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) => {
|
||||
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,
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -100,7 +100,10 @@ const Workflow: FC<WorkflowProps> = memo(({
|
|||
useKeyPress('Backspace', handleEdgeDelete)
|
||||
|
||||
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 />
|
||||
<Panel />
|
||||
<Operator />
|
||||
|
|
|
|||
|
|
@ -35,7 +35,7 @@ const Operator = () => {
|
|||
<div
|
||||
className={`
|
||||
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}
|
||||
>
|
||||
|
|
|
|||
|
|
@ -100,7 +100,7 @@ const ZoomInOut: FC = () => {
|
|||
<div className={`
|
||||
flex items-center px-2 h-8 cursor-pointer text-[13px] hover:bg-gray-50 rounded-lg
|
||||
${open && 'bg-gray-50'}
|
||||
${runningStatus && '!cursor-not-allowed'}
|
||||
${runningStatus && '!cursor-not-allowed opacity-50'}
|
||||
`}>
|
||||
<SearchLg className='mr-1 w-4 h-4' />
|
||||
<div className='w-[34px]'>{parseFloat(`${zoom * 100}`).toFixed(0)}%</div>
|
||||
|
|
|
|||
|
|
@ -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 (
|
||||
<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'>
|
||||
{`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>
|
||||
<Run runID={workflowRunId} />
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -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<string, string>
|
||||
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<string, string>) => void
|
||||
setBackupDraft: (backupDraft?: State['backupDraft']) => void
|
||||
}
|
||||
|
||||
export const useStore = create<State & Action>(set => ({
|
||||
|
|
@ -78,4 +89,6 @@ export const useStore = create<State & Action>(set => ({
|
|||
setShowInputsPanel: showInputsPanel => set(() => ({ showInputsPanel })),
|
||||
inputs: {},
|
||||
setInputs: inputs => set(() => ({ inputs })),
|
||||
backupDraft: undefined,
|
||||
setBackupDraft: backupDraft => set(() => ({ backupDraft })),
|
||||
}))
|
||||
|
|
|
|||
|
|
@ -38,6 +38,7 @@ export type CommonNodeType<T = {}> = {
|
|||
export type CommonEdgeType = {
|
||||
_hovering: boolean
|
||||
_connectedNodeIsHovering: boolean
|
||||
_runned?: boolean
|
||||
}
|
||||
|
||||
export type Node<T = {}> = ReactFlowNode<CommonNodeType<T>>
|
||||
|
|
|
|||
Loading…
Reference in New Issue