Commit 84e2071a authored by StyleZhang's avatar StyleZhang

run

parent b3b9e1da
......@@ -14,15 +14,13 @@ import { Grid01 } from '@/app/components/base/icons/src/vender/line/layout'
import Button from '@/app/components/base/button'
import { ArrowNarrowLeft } from '@/app/components/base/icons/src/vender/line/arrows'
import { useStore as useAppStore } from '@/app/components/app/store'
import { Mode } from '@/app/components/workflow/types'
const Header: FC = () => {
const { t } = useTranslation()
const appDetail = useAppStore(s => s.appDetail)
const appSidebarExpand = useAppStore(s => s.appSidebarExpand)
const isChatMode = useIsChatMode()
const mode = useStore(state => state.mode)
const runTaskId = useStore(state => state.runTaskId)
const runningStatus = useStore(s => s.runningStatus)
const handleShowFeatures = useCallback(() => {
useStore.setState({ showFeaturesPanel: true })
......@@ -37,26 +35,26 @@ const Header: FC = () => {
>
<div>
{
appSidebarExpand && (
appSidebarExpand === 'collapse' && (
<div className='text-xs font-medium text-gray-700'>{appDetail?.name}</div>
)
}
{
mode === Mode.Editing && !runTaskId && <EditingTitle />
!runningStatus && <EditingTitle />
}
{
(mode === Mode.Running || runTaskId) && <RunningTitle />
runningStatus && <RunningTitle />
}
</div>
<div className='flex items-center'>
{
(mode === Mode.Running || runTaskId) && (
runningStatus && (
<Button
className={`
mr-2 px-3 py-0 h-8 bg-white text-[13px] font-medium text-primary-600
border-[0.5px] border-gray-200 shadow-xs
`}
onClick={() => useStore.setState({ mode: Mode.Editing, runTaskId: '' })}
onClick={() => useStore.setState({ runningStatus: undefined })}
>
<ArrowNarrowLeft className='mr-1 w-4 h-4' />
{t('workflow.common.goBackToEdit')}
......
......@@ -3,61 +3,102 @@ import { memo } from 'react'
import { useTranslation } from 'react-i18next'
import { useStore } from '../store'
import { useIsChatMode } from '../hooks'
import { WorkflowRunningStatus } from '../types'
import { Play } from '@/app/components/base/icons/src/vender/line/mediaAndDevices'
import { ClockPlay } from '@/app/components/base/icons/src/vender/line/time'
import TooltipPlus from '@/app/components/base/tooltip-plus'
import { Loading02 } from '@/app/components/base/icons/src/vender/line/general'
const RunAndHistory: FC = () => {
const RunMode = memo(() => {
const { t } = useTranslation()
const isChatMode = useIsChatMode()
const mode = useStore(state => state.mode)
const showRunHistory = useStore(state => state.showRunHistory)
const runningStatus = useStore(s => s.runningStatus)
const showInputsPanel = useStore(s => s.showInputsPanel)
const isRunning = runningStatus === WorkflowRunningStatus.Running
const handleClick = () => {
if (!isChatMode)
useStore.setState({ showInputsPanel: true })
}
return (
<div className='flex items-center px-0.5 h-8 rounded-lg border-[0.5px] border-gray-200 bg-white shadow-xs'>
<div
className={`
flex items-center px-1.5 h-7 rounded-md text-[13px] font-medium text-primary-600
hover:bg-primary-50 cursor-pointer
${mode === 'running' && 'bg-primary-50 !cursor-not-allowed'}
${mode === 'running' && isChatMode && 'opacity-50'}
${showInputsPanel && 'bg-primary-50'}
${isRunning && 'bg-primary-50 !cursor-not-allowed'}
`}
onClick={() => mode !== 'running' && handleClick()}
onClick={() => !isRunning && handleClick()}
>
{
mode === 'running'
isRunning
? (
<>
{
!isChatMode && (
<Loading02 className='mr-1 w-4 h-4 animate-spin' />
{t('workflow.common.running')}
</>
)
: (
<>
<Play className='mr-1 w-4 h-4' />
{t('workflow.common.run')}
</>
)
}
{
!isChatMode
? t('workflow.common.running')
: t('workflow.common.inPreview')
</div>
)
})
RunMode.displayName = 'RunMode'
const PreviewMode = memo(() => {
const { t } = useTranslation()
const runningStatus = useStore(s => s.runningStatus)
const isRunning = runningStatus === WorkflowRunningStatus.Running
const handleClick = () => {
useStore.setState({ runningStatus: WorkflowRunningStatus.Succeeded })
}
return (
<div
className={`
flex items-center px-1.5 h-7 rounded-md text-[13px] font-medium text-primary-600
hover:bg-primary-50 cursor-pointer
${isRunning && 'bg-primary-50 opacity-50 !cursor-not-allowed'}
`}
onClick={() => !isRunning && handleClick()}
>
{
isRunning
? (
<>
{t('workflow.common.inPreview')}
</>
)
: (
<>
<Play className='mr-1 w-4 h-4' />
{
!isChatMode
? t('workflow.common.run')
: t('workflow.common.preview')
}
{t('workflow.common.preview')}
</>
)
}
</div>
)
})
PreviewMode.displayName = 'PreviewMode'
const RunAndHistory: FC = () => {
const { t } = useTranslation()
const isChatMode = useIsChatMode()
const showRunHistory = useStore(state => state.showRunHistory)
return (
<div className='flex items-center px-0.5 h-8 rounded-lg border-[0.5px] border-gray-200 bg-white shadow-xs'>
{
!isChatMode && <RunMode />
}
{
isChatMode && <PreviewMode />
}
<div className='mx-0.5 w-[0.5px] h-8 bg-gray-200'></div>
<TooltipPlus
popupContent={t('workflow.common.viewRunHistory')}
......
......@@ -135,10 +135,21 @@ export const useWorkflow = () => {
}, [reactFlow])
const handleNodeDragStart = useCallback<NodeDragHandler>(() => {
useStore.getState().setIsDragging(true)
const {
runningStatus,
setIsDragging,
} = useStore.getState()
if (!runningStatus)
setIsDragging(true)
}, [])
const handleNodeDrag = useCallback<NodeDragHandler>((e, node: Node) => {
const { runningStatus } = useStore.getState()
if (runningStatus)
return
const {
getNodes,
setNodes,
......@@ -240,10 +251,15 @@ export const useWorkflow = () => {
const handleNodeDragStop = useCallback<NodeDragHandler>(() => {
const {
runningStatus,
setIsDragging,
setHelpLineHorizontal,
setHelpLineVertical,
} = useStore.getState()
if (runningStatus)
return
setIsDragging(false)
setHelpLineHorizontal()
setHelpLineVertical()
......@@ -251,6 +267,11 @@ export const useWorkflow = () => {
}, [handleSyncWorkflowDraft])
const handleNodeEnter = useCallback<NodeMouseHandler>((_, node) => {
const { runningStatus } = useStore.getState()
if (runningStatus)
return
const {
edges,
setEdges,
......@@ -268,6 +289,11 @@ export const useWorkflow = () => {
}, [store])
const handleNodeLeave = useCallback<NodeMouseHandler>(() => {
const { runningStatus } = useStore.getState()
if (runningStatus)
return
const {
edges,
setEdges,
......@@ -281,6 +307,11 @@ export const useWorkflow = () => {
}, [store])
const handleNodeSelect = useCallback((nodeId: string, cancelSelection?: boolean) => {
const { runningStatus } = useStore.getState()
if (runningStatus)
return
const {
getNodes,
setNodes,
......@@ -304,7 +335,12 @@ export const useWorkflow = () => {
}, [store, handleSyncWorkflowDraft])
const handleNodeClick = useCallback<NodeMouseHandler>((_, node) => {
if (useStore.getState().isDragging)
const {
runningStatus,
isDragging,
} = useStore.getState()
if (runningStatus || isDragging)
return
handleNodeSelect(node.id)
......@@ -316,6 +352,11 @@ export const useWorkflow = () => {
target,
targetHandle,
}) => {
const { runningStatus } = useStore.getState()
if (runningStatus)
return
const {
edges,
setEdges,
......@@ -340,6 +381,11 @@ export const useWorkflow = () => {
}, [store, handleSyncWorkflowDraft])
const handleNodeDelete = useCallback((nodeId: string) => {
const { runningStatus } = useStore.getState()
if (runningStatus)
return
const {
getNodes,
setNodes,
......@@ -363,6 +409,11 @@ export const useWorkflow = () => {
}, [store, handleSyncWorkflowDraft])
const handleNodeDataUpdate = useCallback(({ id, data }: { id: string; data: Record<string, any> }) => {
const { runningStatus } = useStore.getState()
if (runningStatus)
return
const {
getNodes,
setNodes,
......@@ -382,6 +433,11 @@ export const useWorkflow = () => {
sourceHandle: string,
toolDefaultValue?: ToolDefaultValue,
) => {
const { runningStatus } = useStore.getState()
if (runningStatus)
return
const {
getNodes,
setNodes,
......@@ -432,6 +488,11 @@ export const useWorkflow = () => {
sourceHandle: string,
toolDefaultValue?: ToolDefaultValue,
) => {
const { runningStatus } = useStore.getState()
if (runningStatus)
return
const {
getNodes,
setNodes,
......@@ -486,6 +547,11 @@ export const useWorkflow = () => {
}, [store, nodesInitialData, handleSyncWorkflowDraft])
const handleEdgeEnter = useCallback<EdgeMouseHandler>((_, edge) => {
const { runningStatus } = useStore.getState()
if (runningStatus)
return
const {
edges,
setEdges,
......@@ -499,6 +565,11 @@ export const useWorkflow = () => {
}, [store])
const handleEdgeLeave = useCallback<EdgeMouseHandler>((_, edge) => {
const { runningStatus } = useStore.getState()
if (runningStatus)
return
const {
edges,
setEdges,
......@@ -512,6 +583,11 @@ export const useWorkflow = () => {
}, [store])
const handleEdgeDelete = useCallback(() => {
const { runningStatus } = useStore.getState()
if (runningStatus)
return
const {
edges,
setEdges,
......@@ -527,6 +603,11 @@ export const useWorkflow = () => {
}, [store, handleSyncWorkflowDraft])
const handleEdgesChange = useCallback<OnEdgesChange>((changes) => {
const { runningStatus } = useStore.getState()
if (runningStatus)
return
const {
edges,
setEdges,
......@@ -585,10 +666,14 @@ export const useWorkflowRun = () => {
ssePost(
url,
params,
{
onWorkflowStarted: () => {
body: params,
},
{
onWorkflowStarted: ({ task_id, workflow_run_id }) => {
useStore.setState({ runningStatus: WorkflowRunningStatus.Running })
useStore.setState({ taskId: task_id })
useStore.setState({ workflowRunId: workflow_run_id })
},
onWorkflowFinished: ({ data }) => {
useStore.setState({ runningStatus: data.status as WorkflowRunningStatus })
......@@ -597,7 +682,7 @@ export const useWorkflowRun = () => {
const newNodes = produce(getNodes(), (draft) => {
const currentNode = draft.find(node => node.id === data.node_id)!
currentNode.data._running = NodeRunningStatus.Running
currentNode.data._runningStatus = NodeRunningStatus.Running
})
setNodes(newNodes)
},
......@@ -605,7 +690,7 @@ export const useWorkflowRun = () => {
const newNodes = produce(getNodes(), (draft) => {
const currentNode = draft.find(node => node.id === data.node_id)!
currentNode.data._running = data.status
currentNode.data._runningStatus = data.status
})
setNodes(newNodes)
},
......
......@@ -31,6 +31,10 @@ import Panel from './panel'
import Features from './features'
import HelpLine from './help-line'
import { useStore } from './store'
import {
initialEdges,
initialNodes,
} from './utils'
import {
fetchWorkflowDraft,
syncWorkflowDraft,
......@@ -130,7 +134,7 @@ const WorkflowWrap: FC<WorkflowProps> = ({
edges,
}) => {
const appDetail = useAppStore(state => state.appDetail)
const { data, isLoading, error } = useSWR(appDetail?.id ? `/apps/${appDetail.id}/workflows/draft` : null, fetchWorkflowDraft)
const { data, isLoading, error, mutate } = useSWR(appDetail?.id ? `/apps/${appDetail.id}/workflows/draft` : null, fetchWorkflowDraft)
const nodesInitialData = useNodesInitialData()
useEffect(() => {
......@@ -145,7 +149,7 @@ const WorkflowWrap: FC<WorkflowProps> = ({
data: nodesInitialData.start,
position: {
x: 100,
y: 100,
y: 200,
},
}
}, [nodesInitialData])
......@@ -155,7 +159,7 @@ const WorkflowWrap: FC<WorkflowProps> = ({
return nodes
if (data)
return data.graph.nodes
return initialNodes(data.graph.nodes)
return [startNode]
}, [data, nodes, startNode])
......@@ -164,7 +168,7 @@ const WorkflowWrap: FC<WorkflowProps> = ({
return edges
if (data)
return data.graph.edges
return initialEdges(data.graph.edges)
return []
}, [data, edges])
......@@ -199,6 +203,7 @@ const WorkflowWrap: FC<WorkflowProps> = ({
},
}).then((res) => {
useStore.setState({ draftUpdatedAt: res.updated_at })
mutate()
})
}
})
......
......@@ -44,13 +44,13 @@ const BaseNode: FC<BaseNodeProps> = ({
group relative w-[240px] bg-[#fcfdff] shadow-xs
border border-transparent rounded-[15px]
hover:shadow-lg
${data._runningStatus === NodeRunningStatus.Running && 'border-primary-500'}
${data._runningStatus === NodeRunningStatus.Succeeded && 'border-[#12B76A]'}
${data._runningStatus === NodeRunningStatus.Failed && 'border-[#F04438]'}
${data._runningStatus === NodeRunningStatus.Running && '!border-primary-500'}
${data._runningStatus === NodeRunningStatus.Succeeded && '!border-[#12B76A]'}
${data._runningStatus === NodeRunningStatus.Failed && '!border-[#F04438]'}
`}
>
{
data.type !== BlockEnum.VariableAssigner && (
data.type !== BlockEnum.VariableAssigner && !data._runningStatus && (
<NodeTargetHandle
id={id}
data={data}
......@@ -60,7 +60,7 @@ const BaseNode: FC<BaseNodeProps> = ({
)
}
{
data.type !== BlockEnum.IfElse && data.type !== BlockEnum.QuestionClassifier && (
data.type !== BlockEnum.IfElse && data.type !== BlockEnum.QuestionClassifier && !data._runningStatus && (
<NodeSourceHandle
id={id}
data={data}
......@@ -69,10 +69,14 @@ const BaseNode: FC<BaseNodeProps> = ({
/>
)
}
{
!data._runningStatus && (
<NodeControl
id={id}
data={data}
/>
)
}
<div className='flex items-center px-3 pt-3 pb-2'>
<BlockIcon
className='shrink-0 mr-2'
......
......@@ -17,7 +17,7 @@ const Operator = () => {
width: 128,
height: 80,
}}
className='!static !m-0 !w-[128px] !h-[80px] border-[0.5px] border-black/[0.08]'
className='!static !m-0 !w-[128px] !h-[80px] !border-[0.5px] !border-black/[0.08] !rounded-lg !shadow-lg'
pannable
/>
<div className='flex items-center mt-1 p-0.5 rounded-lg border-[0.5px] border-gray-100 bg-white shadow-lg text-gray-500'>
......
......@@ -15,9 +15,10 @@ import Record from './record'
import InputsPanel from './inputs-panel'
const Panel: FC = () => {
const isChatMode = useIsChatMode()
const runTaskId = useStore(state => state.runTaskId)
const nodes = useNodes<CommonNodeType>()
const isChatMode = useIsChatMode()
const runningStatus = useStore(s => s.runningStatus)
const workflowRunId = useStore(s => s.workflowRunId)
const selectedNode = nodes.find(node => node.data.selected)
const showRunHistory = useStore(state => state.showRunHistory)
const showInputsPanel = useStore(s => s.showInputsPanel)
......@@ -27,11 +28,11 @@ const Panel: FC = () => {
showDebugAndPreviewPanel,
} = useMemo(() => {
return {
showWorkflowInfoPanel: !isChatMode && !selectedNode && !runTaskId,
showNodePanel: !!selectedNode && !runTaskId,
showDebugAndPreviewPanel: isChatMode && !selectedNode && !runTaskId,
showWorkflowInfoPanel: !isChatMode && !selectedNode && !runningStatus,
showNodePanel: !!selectedNode && !runningStatus,
showDebugAndPreviewPanel: isChatMode && !selectedNode && !runningStatus,
}
}, [selectedNode, isChatMode, runTaskId])
}, [selectedNode, isChatMode, runningStatus])
return (
<div className='absolute top-14 right-0 bottom-2 flex z-10'>
......@@ -41,7 +42,7 @@ const Panel: FC = () => {
)
}
{
runTaskId && (
runningStatus && !isChatMode && workflowRunId && (
<Record />
)
}
......
import {
memo,
useCallback,
useState,
} from 'react'
import { useTranslation } from 'react-i18next'
import { useNodes } from 'reactflow'
......@@ -15,13 +14,13 @@ import Button from '@/app/components/base/button'
const InputsPanel = () => {
const { t } = useTranslation()
const nodes = useNodes<StartNodeType>()
const inputs = useStore(s => s.inputs)
const run = useWorkflowRun()
const [inputs, setInputs] = useState<Record<string, string>>({})
const startNode = nodes.find(node => node.data.type === BlockEnum.Start)
const variables = startNode?.data.variables || []
const handleValueChange = (variable: string, v: string) => {
setInputs({
useStore.getState().setInputs({
...inputs,
[variable]: v,
})
......@@ -32,7 +31,8 @@ const InputsPanel = () => {
}, [])
const handleRun = () => {
run(inputs)
handleCancel()
run({ inputs })
}
return (
......
......@@ -4,19 +4,19 @@ import { useStore } from '../store'
import { XClose } from '@/app/components/base/icons/src/vender/line/general'
const Record = () => {
const { runTaskId, setRunTaskId } = useStore()
const { workflowRunId, setWorkflowRunId } = 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#5
<div
className='flex items-center justify-center w-6 h-6 cursor-pointer'
onClick={() => setRunTaskId('')}
onClick={() => setWorkflowRunId('')}
>
<XClose className='w-4 h-4 text-gray-500' />
</div>
</div>
<Run runID={runTaskId} />
<Run runID={workflowRunId} />
</div>
)
}
......
......@@ -13,7 +13,8 @@ import type { WorkflowRunningStatus } from './types'
type State = {
mode: Mode
runTaskId: string
taskId: string
workflowRunId: string
showRunHistory: boolean
showFeaturesPanel: boolean
isDragging: boolean
......@@ -25,11 +26,13 @@ type State = {
publishedAt: number
runningStatus?: WorkflowRunningStatus
showInputsPanel: boolean
inputs: Record<string, string>
}
type Action = {
setMode: (mode: Mode) => void
setRunTaskId: (runTaskId: string) => void
setTaskId: (taskId: string) => void
setWorkflowRunId: (workflowRunId: string) => void
setShowRunHistory: (showRunHistory: boolean) => void
setShowFeaturesPanel: (showFeaturesPanel: boolean) => void
setIsDragging: (isDragging: boolean) => void
......@@ -41,12 +44,15 @@ type Action = {
setPublishedAt: (publishedAt: number) => void
setRunningStatus: (runningStatus?: WorkflowRunningStatus) => void
setShowInputsPanel: (showInputsPanel: boolean) => void
setInputs: (inputs: Record<string, string>) => void
}
export const useStore = create<State & Action>(set => ({
mode: Mode.Editing,
runTaskId: '',
setRunTaskId: runTaskId => set(() => ({ runTaskId })),
taskId: '',
setTaskId: taskId => set(() => ({ taskId })),
workflowRunId: '',
setWorkflowRunId: workflowRunId => set(() => ({ workflowRunId })),
setMode: mode => set(() => ({ mode })),
showRunHistory: false,
setShowRunHistory: showRunHistory => set(() => ({ showRunHistory })),
......@@ -70,4 +76,6 @@ export const useStore = create<State & Action>(set => ({
setRunningStatus: runningStatus => set(() => ({ runningStatus })),
showInputsPanel: false,
setShowInputsPanel: showInputsPanel => set(() => ({ showInputsPanel })),
inputs: {},
setInputs: inputs => set(() => ({ inputs })),
}))
......@@ -9,6 +9,7 @@ import type {
Node,
} from './types'
import { BlockEnum } from './types'
import type { QuestionClassifierNodeType } from './nodes/question-classifier/types'
export const nodesLevelOrderTraverse = (
firstNode: Node,
......@@ -80,19 +81,43 @@ export const nodesLevelOrderTraverse = (
}
}
export const initialNodesAndEdges = (nodes: Node[], edges: Edge[]) => {
export const initialNodes = (nodes: Node[]) => {
const newNodes = produce(nodes, (draft) => {
draft.forEach((node) => {
node.type = 'custom'
if (node.data.type === BlockEnum.IfElse) {
node.data._targetBranches = [
{
id: 'true',
name: 'IS TRUE',
},
{
id: 'false',
name: 'IS FALSE',
},
]
}
if (node.data.type === BlockEnum.QuestionClassifier) {
node.data._targetBranches = (node.data as QuestionClassifierNodeType).classes.map((topic) => {
return topic
})
}
})
})
return newNodes
}
export const initialEdges = (edges: Edge[]) => {
const newEdges = produce(edges, (draft) => {
draft.forEach((edge) => {
edge.type = 'custom'
})
})
return [newNodes, newEdges]
return newEdges
}
export type PositionMap = {
......
......@@ -421,7 +421,27 @@ export const upload = (options: any, isPublicAPI?: boolean, url?: string, search
})
}
export const ssePost = (url: string, fetchOptions: FetchOptionType, { isPublicAPI = false, onData, onCompleted, onThought, onFile, onMessageEnd, onMessageReplace, onError, getAbortController }: IOtherOptions) => {
export const ssePost = (
url: string,
fetchOptions: FetchOptionType,
{
isPublicAPI = false,
onData,
onCompleted,
onThought,
onFile,
onMessageEnd,
onMessageReplace,
onWorkflowStarted,
onWorkflowFinished,
onNodeStarted,
onNodeFinished,
onTextChunk,
onTextReplace,
onError,
getAbortController,
}: IOtherOptions,
) => {
const abortController = new AbortController()
const options = Object.assign({}, baseOptions, {
......@@ -459,7 +479,7 @@ export const ssePost = (url: string, fetchOptions: FetchOptionType, { isPublicAP
return
}
onData?.(str, isFirstMessage, moreInfo)
}, onCompleted, onThought, onMessageEnd, onMessageReplace, onFile)
}, onCompleted, onThought, onMessageEnd, onMessageReplace, onFile, onWorkflowStarted, onWorkflowFinished, onNodeStarted, onNodeFinished, onTextChunk, onTextReplace)
}).catch((e) => {
if (e.toString() !== 'AbortError: The user aborted a request.')
Toast.notify({ type: 'error', message: e })
......
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