From e32490f54ec03af4e541ac1e167332a0a1a295b4 Mon Sep 17 00:00:00 2001 From: Wu Tianwei <30284043+WTW0313@users.noreply.github.com> Date: Wed, 11 Feb 2026 14:09:33 +0800 Subject: [PATCH] feat(workflow): enhance workflow run history management and UI updates (#32230) --- .../rag-pipeline/hooks/use-pipeline-run.ts | 9 ++++-- .../workflow-app/hooks/use-workflow-run.ts | 15 ++++++++-- .../workflow/header/view-history.tsx | 30 ++++++------------- .../workflow/panel/workflow-preview.tsx | 10 ++----- web/app/components/workflow/utils/common.ts | 6 ++-- web/service/use-workflow.ts | 14 ++++++++- web/types/workflow.ts | 16 ++++++++-- 7 files changed, 63 insertions(+), 37 deletions(-) diff --git a/web/app/components/rag-pipeline/hooks/use-pipeline-run.ts b/web/app/components/rag-pipeline/hooks/use-pipeline-run.ts index dc2a234d1e..b35441365b 100644 --- a/web/app/components/rag-pipeline/hooks/use-pipeline-run.ts +++ b/web/app/components/rag-pipeline/hooks/use-pipeline-run.ts @@ -12,7 +12,7 @@ import { useWorkflowRunEvent } from '@/app/components/workflow/hooks/use-workflo import { useStore, useWorkflowStore } from '@/app/components/workflow/store' import { WorkflowRunningStatus } from '@/app/components/workflow/types' import { ssePost } from '@/service/base' -import { useInvalidAllLastRun } from '@/service/use-workflow' +import { useInvalidAllLastRun, useInvalidateWorkflowRunHistory } from '@/service/use-workflow' import { stopWorkflowRun } from '@/service/workflow' import { FlowType } from '@/types/common' import { useNodesSyncDraft } from './use-nodes-sync-draft' @@ -93,6 +93,7 @@ export const usePipelineRun = () => { const pipelineId = useStore(s => s.pipelineId) const invalidAllLastRun = useInvalidAllLastRun(FlowType.ragPipeline, pipelineId) + const invalidateRunHistory = useInvalidateWorkflowRunHistory() const { fetchInspectVars } = useSetWorkflowVarsWithValue({ flowType: FlowType.ragPipeline, flowId: pipelineId!, @@ -132,6 +133,7 @@ export const usePipelineRun = () => { ...restCallback } = callback || {} const { pipelineId } = workflowStore.getState() + const runHistoryUrl = `/rag/pipelines/${pipelineId}/workflow-runs` workflowStore.setState({ historyWorkflowData: undefined }) const workflowContainer = document.getElementById('workflow-container') @@ -170,12 +172,14 @@ export const usePipelineRun = () => { }, onWorkflowStarted: (params) => { handleWorkflowStarted(params) + invalidateRunHistory(runHistoryUrl) if (onWorkflowStarted) onWorkflowStarted(params) }, onWorkflowFinished: (params) => { handleWorkflowFinished(params) + invalidateRunHistory(runHistoryUrl) fetchInspectVars({}) invalidAllLastRun() @@ -184,6 +188,7 @@ export const usePipelineRun = () => { }, onError: (params) => { handleWorkflowFailed() + invalidateRunHistory(runHistoryUrl) if (onError) onError(params) @@ -275,7 +280,7 @@ export const usePipelineRun = () => { ...restCallback, }, ) - }, [store, doSyncWorkflowDraft, workflowStore, handleWorkflowStarted, handleWorkflowFinished, fetchInspectVars, invalidAllLastRun, handleWorkflowFailed, handleWorkflowNodeStarted, handleWorkflowNodeFinished, handleWorkflowNodeIterationStarted, handleWorkflowNodeIterationNext, handleWorkflowNodeIterationFinished, handleWorkflowNodeLoopStarted, handleWorkflowNodeLoopNext, handleWorkflowNodeLoopFinished, handleWorkflowNodeRetry, handleWorkflowAgentLog, handleWorkflowTextChunk, handleWorkflowTextReplace]) + }, [store, doSyncWorkflowDraft, workflowStore, handleWorkflowStarted, handleWorkflowFinished, fetchInspectVars, invalidAllLastRun, invalidateRunHistory, handleWorkflowFailed, handleWorkflowNodeStarted, handleWorkflowNodeFinished, handleWorkflowNodeIterationStarted, handleWorkflowNodeIterationNext, handleWorkflowNodeIterationFinished, handleWorkflowNodeLoopStarted, handleWorkflowNodeLoopNext, handleWorkflowNodeLoopFinished, handleWorkflowNodeRetry, handleWorkflowAgentLog, handleWorkflowTextChunk, handleWorkflowTextReplace]) const handleStopRun = useCallback((taskId: string) => { const { pipelineId } = workflowStore.getState() diff --git a/web/app/components/workflow-app/hooks/use-workflow-run.ts b/web/app/components/workflow-app/hooks/use-workflow-run.ts index ef6d7731a4..ae4a21f5a0 100644 --- a/web/app/components/workflow-app/hooks/use-workflow-run.ts +++ b/web/app/components/workflow-app/hooks/use-workflow-run.ts @@ -23,7 +23,7 @@ import { useWorkflowStore } from '@/app/components/workflow/store' import { WorkflowRunningStatus } from '@/app/components/workflow/types' import { handleStream, post, sseGet, ssePost } from '@/service/base' import { ContentType } from '@/service/fetch' -import { useInvalidAllLastRun } from '@/service/use-workflow' +import { useInvalidAllLastRun, useInvalidateWorkflowRunHistory } from '@/service/use-workflow' import { stopWorkflowRun } from '@/service/workflow' import { AppModeEnum } from '@/types/app' import { useSetWorkflowVarsWithValue } from '../../workflow/hooks/use-fetch-workflow-inspect-vars' @@ -66,6 +66,7 @@ export const useWorkflowRun = () => { const configsMap = useConfigsMap() const { flowId, flowType } = configsMap const invalidAllLastRun = useInvalidAllLastRun(flowType, flowId) + const invalidateRunHistory = useInvalidateWorkflowRunHistory() const { fetchInspectVars } = useSetWorkflowVarsWithValue({ ...configsMap, @@ -189,6 +190,9 @@ export const useWorkflowRun = () => { } = callback || {} workflowStore.setState({ historyWorkflowData: undefined }) const appDetail = useAppStore.getState().appDetail + const runHistoryUrl = appDetail?.mode === AppModeEnum.ADVANCED_CHAT + ? `/apps/${appDetail.id}/advanced-chat/workflow-runs` + : `/apps/${appDetail?.id}/workflow-runs` const workflowContainer = document.getElementById('workflow-container') const { @@ -363,6 +367,7 @@ export const useWorkflowRun = () => { const wrappedOnError = (params: any) => { clearAbortController() handleWorkflowFailed() + invalidateRunHistory(runHistoryUrl) clearListeningState() if (onError) @@ -381,6 +386,7 @@ export const useWorkflowRun = () => { ...restCallback, onWorkflowStarted: (params) => { handleWorkflowStarted(params) + invalidateRunHistory(runHistoryUrl) if (onWorkflowStarted) onWorkflowStarted(params) @@ -388,6 +394,7 @@ export const useWorkflowRun = () => { onWorkflowFinished: (params) => { clearListeningState() handleWorkflowFinished(params) + invalidateRunHistory(runHistoryUrl) if (onWorkflowFinished) onWorkflowFinished(params) @@ -496,6 +503,7 @@ export const useWorkflowRun = () => { }, onWorkflowPaused: (params) => { handleWorkflowPaused() + invalidateRunHistory(runHistoryUrl) if (onWorkflowPaused) onWorkflowPaused(params) const url = `/workflow/${params.workflow_run_id}/events` @@ -694,6 +702,7 @@ export const useWorkflowRun = () => { }, onWorkflowFinished: (params) => { handleWorkflowFinished(params) + invalidateRunHistory(runHistoryUrl) if (onWorkflowFinished) onWorkflowFinished(params) @@ -704,6 +713,7 @@ export const useWorkflowRun = () => { }, onError: (params) => { handleWorkflowFailed() + invalidateRunHistory(runHistoryUrl) if (onError) onError(params) @@ -803,6 +813,7 @@ export const useWorkflowRun = () => { }, onWorkflowPaused: (params) => { handleWorkflowPaused() + invalidateRunHistory(runHistoryUrl) if (onWorkflowPaused) onWorkflowPaused(params) const url = `/workflow/${params.workflow_run_id}/events` @@ -837,7 +848,7 @@ export const useWorkflowRun = () => { }, finalCallbacks, ) - }, [store, doSyncWorkflowDraft, workflowStore, pathname, handleWorkflowFailed, flowId, handleWorkflowStarted, handleWorkflowFinished, fetchInspectVars, invalidAllLastRun, handleWorkflowNodeStarted, handleWorkflowNodeFinished, handleWorkflowNodeIterationStarted, handleWorkflowNodeIterationNext, handleWorkflowNodeIterationFinished, handleWorkflowNodeLoopStarted, handleWorkflowNodeLoopNext, handleWorkflowNodeLoopFinished, handleWorkflowNodeRetry, handleWorkflowAgentLog, handleWorkflowTextChunk, handleWorkflowTextReplace, handleWorkflowPaused, handleWorkflowNodeHumanInputRequired, handleWorkflowNodeHumanInputFormFilled, handleWorkflowNodeHumanInputFormTimeout]) + }, [store, doSyncWorkflowDraft, workflowStore, pathname, handleWorkflowFailed, flowId, handleWorkflowStarted, handleWorkflowFinished, fetchInspectVars, invalidAllLastRun, invalidateRunHistory, handleWorkflowNodeStarted, handleWorkflowNodeFinished, handleWorkflowNodeIterationStarted, handleWorkflowNodeIterationNext, handleWorkflowNodeIterationFinished, handleWorkflowNodeLoopStarted, handleWorkflowNodeLoopNext, handleWorkflowNodeLoopFinished, handleWorkflowNodeRetry, handleWorkflowAgentLog, handleWorkflowTextChunk, handleWorkflowTextReplace, handleWorkflowPaused, handleWorkflowNodeHumanInputRequired, handleWorkflowNodeHumanInputFormFilled, handleWorkflowNodeHumanInputFormTimeout]) const handleStopRun = useCallback((taskId: string) => { const setStoppedState = () => { diff --git a/web/app/components/workflow/header/view-history.tsx b/web/app/components/workflow/header/view-history.tsx index f9b446e930..94963e29fc 100644 --- a/web/app/components/workflow/header/view-history.tsx +++ b/web/app/components/workflow/header/view-history.tsx @@ -1,18 +1,8 @@ -import { - RiCheckboxCircleLine, - RiCloseLine, - RiErrorWarningLine, -} from '@remixicon/react' import { memo, useState, } from 'react' import { useTranslation } from 'react-i18next' -import { AlertTriangle } from '@/app/components/base/icons/src/vender/line/alertsAndFeedback' -import { - ClockPlay, - ClockPlaySlim, -} from '@/app/components/base/icons/src/vender/line/time' import Loading from '@/app/components/base/loading' import { PortalToFollowElem, @@ -89,9 +79,7 @@ const ViewHistory = ({ open && 'bg-components-button-secondary-bg-hover', )} > - + {t('common.showRunHistory', { ns: 'workflow' })} ) @@ -107,7 +95,7 @@ const ViewHistory = ({ onClearLogAndMessageModal?.() }} > - + ) @@ -129,7 +117,7 @@ const ViewHistory = ({ setOpen(false) }} > - + { @@ -145,7 +133,7 @@ const ViewHistory = ({ { !data?.data.length && (
- +
{t('common.notRunning', { ns: 'workflow' })}
@@ -175,18 +163,18 @@ const ViewHistory = ({ }} > { - !isChatMode && item.status === WorkflowRunningStatus.Stopped && ( - + !isChatMode && [WorkflowRunningStatus.Stopped, WorkflowRunningStatus.Paused].includes(item.status) && ( + ) } { !isChatMode && item.status === WorkflowRunningStatus.Failed && ( - + ) } { !isChatMode && item.status === WorkflowRunningStatus.Succeeded && ( - + ) }
@@ -196,7 +184,7 @@ const ViewHistory = ({ item.id === historyWorkflowData?.id && 'text-text-accent', )} > - {`Test ${isChatMode ? 'Chat' : 'Run'}${formatWorkflowRunIdentifier(item.finished_at)}`} + {`Test ${isChatMode ? 'Chat' : 'Run'}${formatWorkflowRunIdentifier(item.finished_at, item.status)}`}
{item.created_by_account?.name} diff --git a/web/app/components/workflow/panel/workflow-preview.tsx b/web/app/components/workflow/panel/workflow-preview.tsx index 7f23f5bc74..4fdc0b8376 100644 --- a/web/app/components/workflow/panel/workflow-preview.tsx +++ b/web/app/components/workflow/panel/workflow-preview.tsx @@ -1,7 +1,3 @@ -import { - RiClipboardLine, - RiCloseLine, -} from '@remixicon/react' import copy from 'copy-to-clipboard' import { memo, @@ -115,9 +111,9 @@ const WorkflowPreview = () => { onMouseDown={startResizing} />
- {`Test Run${formatWorkflowRunIdentifier(workflowRunningData?.result.finished_at)}`} + {`Test Run${formatWorkflowRunIdentifier(workflowRunningData?.result.finished_at, workflowRunningData?.result.status)}`}
handleCancelDebugAndPreviewPanel()}> - +
@@ -217,7 +213,7 @@ const WorkflowPreview = () => { Toast.notify({ type: 'success', message: t('actionMsg.copySuccessfully', { ns: 'common' }) }) }} > - +
{t('operation.copy', { ns: 'common' })}
)} diff --git a/web/app/components/workflow/utils/common.ts b/web/app/components/workflow/utils/common.ts index 8452161950..81bb22359c 100644 --- a/web/app/components/workflow/utils/common.ts +++ b/web/app/components/workflow/utils/common.ts @@ -41,8 +41,10 @@ export const isEventTargetInputArea = (target: HTMLElement) => { * @returns Formatted string like " (14:30:25)" or " (Running)" */ export const formatWorkflowRunIdentifier = (finishedAt?: number, fallbackText = 'Running'): string => { - if (!finishedAt) - return ` (${fallbackText})` + if (!finishedAt) { + const capitalized = fallbackText.charAt(0).toUpperCase() + fallbackText.slice(1) + return ` (${capitalized})` + } const date = new Date(finishedAt * 1000) const timeStr = date.toLocaleTimeString([], { diff --git a/web/service/use-workflow.ts b/web/service/use-workflow.ts index 2f9f5d2fb7..fe20b906fc 100644 --- a/web/service/use-workflow.ts +++ b/web/service/use-workflow.ts @@ -26,14 +26,26 @@ export const useAppWorkflow = (appID: string) => { }) } +const WorkflowRunHistoryKey = [NAME_SPACE, 'runHistory'] + export const useWorkflowRunHistory = (url?: string, enabled = true) => { return useQuery({ - queryKey: [NAME_SPACE, 'runHistory', url], + queryKey: [...WorkflowRunHistoryKey, url], queryFn: () => get(url as string), enabled: !!url && enabled, + staleTime: 0, }) } +export const useInvalidateWorkflowRunHistory = () => { + const queryClient = useQueryClient() + return (url: string) => { + queryClient.invalidateQueries({ + queryKey: [...WorkflowRunHistoryKey, url], + }) + } +} + export const useInvalidateAppWorkflow = () => { const queryClient = useQueryClient() return (appID: string) => { diff --git a/web/types/workflow.ts b/web/types/workflow.ts index 156a704b48..f8a53c8d7e 100644 --- a/web/types/workflow.ts +++ b/web/types/workflow.ts @@ -4,7 +4,19 @@ import type { BeforeRunFormProps } from '@/app/components/workflow/nodes/_base/c import type { ErrorHandleTypeEnum } from '@/app/components/workflow/nodes/_base/components/error-handle/types' import type { FormInputItem, UserAction } from '@/app/components/workflow/nodes/human-input/types' import type { SpecialResultPanelProps } from '@/app/components/workflow/run/special-result-panel' -import type { BlockEnum, CommonNodeType, ConversationVariable, Edge, EnvironmentVariable, InputVar, Node, ValueSelector, Variable, VarType } from '@/app/components/workflow/types' +import type { + BlockEnum, + CommonNodeType, + ConversationVariable, + Edge, + EnvironmentVariable, + InputVar, + Node, + ValueSelector, + Variable, + VarType, + WorkflowRunningStatus, +} from '@/app/components/workflow/types' import type { RAGPipelineVariables } from '@/models/pipeline' import type { TransferMethod } from '@/types/app' @@ -372,7 +384,7 @@ export type WorkflowRunHistory = { viewport?: Viewport } inputs: Record - status: string + status: WorkflowRunningStatus outputs: Record error?: string elapsed_time: number