mirror of
https://github.com/langgenius/dify.git
synced 2026-01-08 07:14:14 +00:00
run
This commit is contained in:
@@ -5,7 +5,10 @@ import {
|
||||
} from 'react'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import { useStore } from '../store'
|
||||
import { useIsChatMode } from '../hooks'
|
||||
import {
|
||||
useIsChatMode,
|
||||
useWorkflow,
|
||||
} from '../hooks'
|
||||
import RunAndHistory from './run-and-history'
|
||||
import EditingTitle from './editing-title'
|
||||
import RunningTitle from './running-title'
|
||||
@@ -21,11 +24,16 @@ const Header: FC = () => {
|
||||
const appSidebarExpand = useAppStore(s => s.appSidebarExpand)
|
||||
const isChatMode = useIsChatMode()
|
||||
const runningStatus = useStore(s => s.runningStatus)
|
||||
const { handleRunInit } = useWorkflow()
|
||||
|
||||
const handleShowFeatures = useCallback(() => {
|
||||
useStore.setState({ showFeaturesPanel: true })
|
||||
}, [])
|
||||
|
||||
const handleGoBackToEdit = useCallback(() => {
|
||||
handleRunInit(true)
|
||||
}, [handleRunInit])
|
||||
|
||||
return (
|
||||
<div
|
||||
className='absolute top-0 left-0 flex items-center justify-between px-3 w-full h-14 z-10'
|
||||
@@ -54,7 +62,7 @@ const Header: FC = () => {
|
||||
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({ runningStatus: undefined })}
|
||||
onClick={handleGoBackToEdit}
|
||||
>
|
||||
<ArrowNarrowLeft className='mr-1 w-4 h-4' />
|
||||
{t('workflow.common.goBackToEdit')}
|
||||
|
||||
@@ -2,7 +2,10 @@ import type { FC } from 'react'
|
||||
import { memo } from 'react'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import { useStore } from '../store'
|
||||
import { useIsChatMode } from '../hooks'
|
||||
import {
|
||||
useIsChatMode,
|
||||
useWorkflow,
|
||||
} 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'
|
||||
@@ -51,11 +54,12 @@ RunMode.displayName = 'RunMode'
|
||||
|
||||
const PreviewMode = memo(() => {
|
||||
const { t } = useTranslation()
|
||||
const { handleRunInit } = useWorkflow()
|
||||
const runningStatus = useStore(s => s.runningStatus)
|
||||
const isRunning = runningStatus === WorkflowRunningStatus.Running
|
||||
|
||||
const handleClick = () => {
|
||||
useStore.setState({ runningStatus: WorkflowRunningStatus.Succeeded })
|
||||
handleRunInit()
|
||||
}
|
||||
|
||||
return (
|
||||
|
||||
@@ -79,11 +79,19 @@ export const useWorkflow = () => {
|
||||
|
||||
if (appId) {
|
||||
const features = featuresStore!.getState().features
|
||||
const nodes = produce(getNodes(), (draft) => {
|
||||
draft.forEach((node) => {
|
||||
Object.keys(node.data).forEach((key) => {
|
||||
if (key.startsWith('_'))
|
||||
delete node.data[key]
|
||||
})
|
||||
})
|
||||
})
|
||||
syncWorkflowDraft({
|
||||
url: `/apps/${appId}/workflows/draft`,
|
||||
params: {
|
||||
graph: {
|
||||
nodes: getNodes(),
|
||||
nodes,
|
||||
edges,
|
||||
viewport: getViewport(),
|
||||
},
|
||||
@@ -622,6 +630,17 @@ export const useWorkflow = () => {
|
||||
setEdges(newEdges)
|
||||
}, [store])
|
||||
|
||||
const handleRunInit = 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
|
||||
})
|
||||
})
|
||||
setNodes(newNodes)
|
||||
}, [store])
|
||||
|
||||
return {
|
||||
handleSyncWorkflowDraft,
|
||||
handleLayout,
|
||||
@@ -644,6 +663,8 @@ export const useWorkflow = () => {
|
||||
handleEdgeLeave,
|
||||
handleEdgeDelete,
|
||||
handleEdgesChange,
|
||||
|
||||
handleRunInit,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -674,6 +695,12 @@ export const useWorkflowRun = () => {
|
||||
useStore.setState({ runningStatus: WorkflowRunningStatus.Running })
|
||||
useStore.setState({ taskId: task_id })
|
||||
useStore.setState({ workflowRunId: workflow_run_id })
|
||||
const newNodes = produce(getNodes(), (draft) => {
|
||||
draft.forEach((node) => {
|
||||
node.data._runningStatus = NodeRunningStatus.Waiting
|
||||
})
|
||||
})
|
||||
setNodes(newNodes)
|
||||
},
|
||||
onWorkflowFinished: ({ data }) => {
|
||||
useStore.setState({ runningStatus: data.status as WorkflowRunningStatus })
|
||||
|
||||
@@ -63,6 +63,7 @@ const Workflow: FC<WorkflowProps> = memo(({
|
||||
viewport,
|
||||
}) => {
|
||||
const showFeaturesPanel = useStore(state => state.showFeaturesPanel)
|
||||
const runningStatus = useStore(s => s.runningStatus)
|
||||
|
||||
const {
|
||||
handleSyncWorkflowDraft,
|
||||
@@ -116,6 +117,11 @@ const Workflow: FC<WorkflowProps> = memo(({
|
||||
deleteKeyCode={null}
|
||||
nodeDragThreshold={1}
|
||||
defaultViewport={viewport}
|
||||
panOnDrag={!runningStatus}
|
||||
nodesDraggable={!runningStatus}
|
||||
nodesConnectable={!runningStatus}
|
||||
nodesFocusable={!runningStatus}
|
||||
edgesFocusable={!runningStatus}
|
||||
>
|
||||
<Background
|
||||
gap={[14, 14]}
|
||||
|
||||
@@ -18,7 +18,6 @@ const Operator = () => {
|
||||
height: 80,
|
||||
}}
|
||||
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'>
|
||||
<ZoomInOut />
|
||||
|
||||
@@ -30,12 +30,17 @@ const Panel: FC = () => {
|
||||
return {
|
||||
showWorkflowInfoPanel: !isChatMode && !selectedNode && !runningStatus,
|
||||
showNodePanel: !!selectedNode && !runningStatus,
|
||||
showDebugAndPreviewPanel: isChatMode && !selectedNode && !runningStatus,
|
||||
showDebugAndPreviewPanel: isChatMode && runningStatus,
|
||||
}
|
||||
}, [selectedNode, isChatMode, runningStatus])
|
||||
|
||||
return (
|
||||
<div className='absolute top-14 right-0 bottom-2 flex z-10'>
|
||||
<div
|
||||
className={`
|
||||
absolute top-14 right-0 bottom-2 flex pr-2 z-10
|
||||
${showRunHistory && '!pr-0'}
|
||||
`}
|
||||
>
|
||||
{
|
||||
showInputsPanel && (
|
||||
<InputsPanel />
|
||||
|
||||
@@ -7,7 +7,10 @@ import { useNodes } from 'reactflow'
|
||||
import FormItem from '../nodes/_base/components/before-run-form/form-item'
|
||||
import { BlockEnum } from '../types'
|
||||
import { useStore } from '../store'
|
||||
import { useWorkflowRun } from '../hooks'
|
||||
import {
|
||||
useWorkflow,
|
||||
useWorkflowRun,
|
||||
} from '../hooks'
|
||||
import type { StartNodeType } from '../nodes/start/types'
|
||||
import Button from '@/app/components/base/button'
|
||||
|
||||
@@ -16,6 +19,7 @@ const InputsPanel = () => {
|
||||
const nodes = useNodes<StartNodeType>()
|
||||
const inputs = useStore(s => s.inputs)
|
||||
const run = useWorkflowRun()
|
||||
const { handleRunInit } = useWorkflow()
|
||||
const startNode = nodes.find(node => node.data.type === BlockEnum.Start)
|
||||
const variables = startNode?.data.variables || []
|
||||
|
||||
@@ -32,6 +36,7 @@ const InputsPanel = () => {
|
||||
|
||||
const handleRun = () => {
|
||||
handleCancel()
|
||||
handleRunInit()
|
||||
run({ inputs })
|
||||
}
|
||||
|
||||
|
||||
@@ -1,17 +1,27 @@
|
||||
import { memo } from 'react'
|
||||
import dayjs from 'dayjs'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import useSWR from 'swr'
|
||||
import { WorkflowRunningStatus } from '../types'
|
||||
import { XClose } from '@/app/components/base/icons/src/vender/line/general'
|
||||
import { AlertCircle } from '@/app/components/base/icons/src/vender/line/alertsAndFeedback'
|
||||
import { CheckCircle } from '@/app/components/base/icons/src/vender/solid/general'
|
||||
import { useStore } from '@/app/components/workflow/store'
|
||||
import { useStore as useAppStore } from '@/app/components/app/store'
|
||||
import { fetchWorkflowRunHistory } from '@/service/workflow'
|
||||
import Loading from '@/app/components/base/loading'
|
||||
|
||||
const RunHistory = () => {
|
||||
const { t } = useTranslation()
|
||||
const appDetail = useAppStore(state => state.appDetail)
|
||||
const { data, isLoading } = useSWR(appDetail ? `/apps/${appDetail.id}/workflow-runs` : null, fetchWorkflowRunHistory)
|
||||
|
||||
if (!appDetail)
|
||||
return null
|
||||
|
||||
return (
|
||||
<div className='ml-2 w-[200px] h-full bg-white border-[0.5px] border-gray-200 shadow-xl rounded-l-2xl'>
|
||||
<div className='flex items-center justify-between px-4 pt-3 text-base font-semibold text-gray-900'>
|
||||
<div className='flex flex-col ml-2 w-[200px] h-full bg-white border-[0.5px] border-gray-200 shadow-xl rounded-l-2xl'>
|
||||
<div className='shrink-0 flex items-center justify-between px-4 pt-3 text-base font-semibold text-gray-900'>
|
||||
{t('workflow.common.runHistory')}
|
||||
<div
|
||||
className='flex items-center justify-center w-6 h-6 cursor-pointer'
|
||||
@@ -20,23 +30,42 @@ const RunHistory = () => {
|
||||
<XClose className='w-4 h-4 text-gray-500' />
|
||||
</div>
|
||||
</div>
|
||||
<div className='p-2'>
|
||||
<div
|
||||
className='flex mb-0.5 px-2 py-[7px] rounded-lg hover:bg-primary-50 cursor-pointer'
|
||||
onClick={() => useStore.setState({ runTaskId: '1' })}
|
||||
>
|
||||
{
|
||||
appDetail?.mode === 'advanced-chat' && (
|
||||
<AlertCircle className='mt-0.5 mr-1.5 w-3.5 h-3.5 text-[#F79009]' />
|
||||
)
|
||||
}
|
||||
<div>
|
||||
<div className='flex items-center text-[13px] font-medium text-primary-600 leading-[18px]'>Test Run#6</div>
|
||||
<div className='flex items-center text-xs text-gray-500 leading-[18px]'>
|
||||
Evan · 30 min ago
|
||||
</div>
|
||||
{
|
||||
isLoading && (
|
||||
<div className='grow flex items-center justify-center h-full'>
|
||||
<Loading />
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
<div className='grow p-2 overflow-y-auto'>
|
||||
{
|
||||
data?.data.map(item => (
|
||||
<div
|
||||
key={item.id}
|
||||
className='flex mb-0.5 px-2 py-[7px] rounded-lg hover:bg-primary-50 cursor-pointer'
|
||||
onClick={() => useStore.setState({ workflowRunId: item.id })}
|
||||
>
|
||||
{
|
||||
appDetail?.mode === 'workflow' && item.status === WorkflowRunningStatus.Failed && (
|
||||
<AlertCircle className='mt-0.5 mr-1.5 w-3.5 h-3.5 text-[#F79009]' />
|
||||
)
|
||||
}
|
||||
{
|
||||
appDetail?.mode === 'workflow' && item.status === WorkflowRunningStatus.Succeeded && (
|
||||
<CheckCircle className='mt-0.5 mr-1.5 w-3.5 h-3.5 text-[#12B76A]' />
|
||||
)
|
||||
}
|
||||
<div>
|
||||
<div className='flex items-center text-[13px] font-medium text-primary-600 leading-[18px]'>
|
||||
Test Run#{item.sequence_number}
|
||||
</div>
|
||||
<div className='flex items-center text-xs text-gray-500 leading-[18px]'>
|
||||
{item.created_by_account.name} · {dayjs((item.finished_at || item.created_at) * 1000).fromNow()}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
))
|
||||
}
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
|
||||
@@ -150,6 +150,7 @@ export enum Mode {
|
||||
}
|
||||
|
||||
export enum WorkflowRunningStatus {
|
||||
Waiting = 'waiting',
|
||||
Running = 'running',
|
||||
Succeeded = 'succeeded',
|
||||
Failed = 'failed',
|
||||
@@ -157,6 +158,7 @@ export enum WorkflowRunningStatus {
|
||||
}
|
||||
|
||||
export enum NodeRunningStatus {
|
||||
Waiting = 'waiting',
|
||||
Running = 'running',
|
||||
Succeeded = 'succeeded',
|
||||
Failed = 'failed',
|
||||
|
||||
@@ -3,6 +3,7 @@ import { get, post } from './base'
|
||||
import type { CommonResponse } from '@/models/common'
|
||||
import type {
|
||||
FetchWorkflowDraftResponse,
|
||||
WorkflowRunHistoryResponse,
|
||||
} from '@/types/workflow'
|
||||
|
||||
export const fetchWorkflowDraft: Fetcher<FetchWorkflowDraftResponse, string> = (url) => {
|
||||
@@ -16,3 +17,7 @@ export const syncWorkflowDraft = ({ url, params }: { url: string; params: Pick<F
|
||||
export const fetchNodesDefaultConfigs: Fetcher<any, string> = (url) => {
|
||||
return get<any>(url)
|
||||
}
|
||||
|
||||
export const fetchWorkflowRunHistory: Fetcher<WorkflowRunHistoryResponse, string> = (url) => {
|
||||
return get<WorkflowRunHistoryResponse>(url)
|
||||
}
|
||||
|
||||
@@ -131,3 +131,31 @@ export type TextReplaceResponse = {
|
||||
text: string
|
||||
}
|
||||
}
|
||||
|
||||
export type WorkflowRunHistory = {
|
||||
id: string
|
||||
sequence_number: number
|
||||
version: string
|
||||
graph: {
|
||||
nodes: Node[]
|
||||
edges: Edge[]
|
||||
viewport?: Viewport
|
||||
}
|
||||
inputs: Record<string, string>
|
||||
status: string
|
||||
outputs: Record<string, any>
|
||||
error?: string
|
||||
elapsed_time: number
|
||||
total_tokens: number
|
||||
total_steps: number
|
||||
created_at: number
|
||||
finished_at: number
|
||||
created_by_account: {
|
||||
id: string
|
||||
name: string
|
||||
email: string
|
||||
}
|
||||
}
|
||||
export type WorkflowRunHistoryResponse = {
|
||||
data: WorkflowRunHistory[]
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user