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[] +}