diff --git a/web/app/components/workflow/header/index.tsx b/web/app/components/workflow/header/index.tsx
index e1f4a73d30..8cae9fff03 100644
--- a/web/app/components/workflow/header/index.tsx
+++ b/web/app/components/workflow/header/index.tsx
@@ -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 (
{
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}
>
{t('workflow.common.goBackToEdit')}
diff --git a/web/app/components/workflow/header/run-and-history.tsx b/web/app/components/workflow/header/run-and-history.tsx
index 2a242cf212..6a480cd3d0 100644
--- a/web/app/components/workflow/header/run-and-history.tsx
+++ b/web/app/components/workflow/header/run-and-history.tsx
@@ -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 (
diff --git a/web/app/components/workflow/hooks.ts b/web/app/components/workflow/hooks.ts
index 29976fda4d..5e2021c6b0 100644
--- a/web/app/components/workflow/hooks.ts
+++ b/web/app/components/workflow/hooks.ts
@@ -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 })
diff --git a/web/app/components/workflow/index.tsx b/web/app/components/workflow/index.tsx
index ee49428cd5..ca515e2fb1 100644
--- a/web/app/components/workflow/index.tsx
+++ b/web/app/components/workflow/index.tsx
@@ -63,6 +63,7 @@ const Workflow: FC
= memo(({
viewport,
}) => {
const showFeaturesPanel = useStore(state => state.showFeaturesPanel)
+ const runningStatus = useStore(s => s.runningStatus)
const {
handleSyncWorkflowDraft,
@@ -116,6 +117,11 @@ const Workflow: FC = memo(({
deleteKeyCode={null}
nodeDragThreshold={1}
defaultViewport={viewport}
+ panOnDrag={!runningStatus}
+ nodesDraggable={!runningStatus}
+ nodesConnectable={!runningStatus}
+ nodesFocusable={!runningStatus}
+ edgesFocusable={!runningStatus}
>
{
height: 80,
}}
className='!static !m-0 !w-[128px] !h-[80px] !border-[0.5px] !border-black/[0.08] !rounded-lg !shadow-lg'
- pannable
/>
diff --git a/web/app/components/workflow/panel/index.tsx b/web/app/components/workflow/panel/index.tsx
index 3c3b42ccd2..3bc9a93105 100644
--- a/web/app/components/workflow/panel/index.tsx
+++ b/web/app/components/workflow/panel/index.tsx
@@ -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 (
-
+
{
showInputsPanel && (
diff --git a/web/app/components/workflow/panel/inputs-panel.tsx b/web/app/components/workflow/panel/inputs-panel.tsx
index 06c528d9c7..42bc2c69d5 100644
--- a/web/app/components/workflow/panel/inputs-panel.tsx
+++ b/web/app/components/workflow/panel/inputs-panel.tsx
@@ -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
()
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 })
}
diff --git a/web/app/components/workflow/panel/run-history.tsx b/web/app/components/workflow/panel/run-history.tsx
index b21eade3ee..a5c924a28f 100644
--- a/web/app/components/workflow/panel/run-history.tsx
+++ b/web/app/components/workflow/panel/run-history.tsx
@@ -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 (
-
-
+
+
{t('workflow.common.runHistory')}
{
-
-
useStore.setState({ runTaskId: '1' })}
- >
- {
- appDetail?.mode === 'advanced-chat' && (
-
- )
- }
-
-
Test Run#6
-
- Evan · 30 min ago
-
+ {
+ isLoading && (
+
+
-
+ )
+ }
+
+ {
+ data?.data.map(item => (
+
useStore.setState({ workflowRunId: item.id })}
+ >
+ {
+ appDetail?.mode === 'workflow' && item.status === WorkflowRunningStatus.Failed && (
+
+ )
+ }
+ {
+ appDetail?.mode === 'workflow' && item.status === WorkflowRunningStatus.Succeeded && (
+
+ )
+ }
+
+
+ Test Run#{item.sequence_number}
+
+
+ {item.created_by_account.name} · {dayjs((item.finished_at || item.created_at) * 1000).fromNow()}
+
+
+
+ ))
+ }
)
diff --git a/web/app/components/workflow/types.ts b/web/app/components/workflow/types.ts
index f677307615..bdf957e3b6 100644
--- a/web/app/components/workflow/types.ts
+++ b/web/app/components/workflow/types.ts
@@ -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',
diff --git a/web/service/workflow.ts b/web/service/workflow.ts
index d60c7e940c..a862109483 100644
--- a/web/service/workflow.ts
+++ b/web/service/workflow.ts
@@ -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
= (url) => {
@@ -16,3 +17,7 @@ export const syncWorkflowDraft = ({ url, params }: { url: string; params: Pick = (url) => {
return get(url)
}
+
+export const fetchWorkflowRunHistory: Fetcher = (url) => {
+ return get(url)
+}
diff --git a/web/types/workflow.ts b/web/types/workflow.ts
index 2be7494488..2ae683e55f 100644
--- a/web/types/workflow.ts
+++ b/web/types/workflow.ts
@@ -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
+ status: string
+ outputs: Record
+ 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[]
+}