feat(workflow): enhance workflow run history management and UI updates (#32230)

This commit is contained in:
Wu Tianwei 2026-02-11 14:09:33 +08:00 committed by GitHub
parent e9db50f781
commit e32490f54e
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
7 changed files with 63 additions and 37 deletions

View File

@ -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()

View File

@ -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 = () => {

View File

@ -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',
)}
>
<ClockPlay
className="mr-1 h-4 w-4"
/>
<span className="i-custom-vender-line-time-clock-play mr-1 h-4 w-4" />
{t('common.showRunHistory', { ns: 'workflow' })}
</div>
)
@ -107,7 +95,7 @@ const ViewHistory = ({
onClearLogAndMessageModal?.()
}}
>
<ClockPlay className={cn('h-4 w-4 group-hover:text-components-button-secondary-accent-text', open ? 'text-components-button-secondary-accent-text' : 'text-components-button-ghost-text')} />
<span className={cn('i-custom-vender-line-time-clock-play', 'h-4 w-4 group-hover:text-components-button-secondary-accent-text', open ? 'text-components-button-secondary-accent-text' : 'text-components-button-ghost-text')} />
</div>
</Tooltip>
)
@ -129,7 +117,7 @@ const ViewHistory = ({
setOpen(false)
}}
>
<RiCloseLine className="h-4 w-4 text-text-tertiary" />
<span className="i-ri-close-line h-4 w-4 text-text-tertiary" />
</div>
</div>
{
@ -145,7 +133,7 @@ const ViewHistory = ({
{
!data?.data.length && (
<div className="py-12">
<ClockPlaySlim className="mx-auto mb-2 h-8 w-8 text-text-quaternary" />
<span className="i-custom-vender-line-time-clock-play-slim mx-auto mb-2 h-8 w-8 text-text-quaternary" />
<div className="text-center text-[13px] text-text-quaternary">
{t('common.notRunning', { ns: 'workflow' })}
</div>
@ -175,18 +163,18 @@ const ViewHistory = ({
}}
>
{
!isChatMode && item.status === WorkflowRunningStatus.Stopped && (
<AlertTriangle className="mr-1.5 mt-0.5 h-3.5 w-3.5 text-[#F79009]" />
!isChatMode && [WorkflowRunningStatus.Stopped, WorkflowRunningStatus.Paused].includes(item.status) && (
<span className="i-custom-vender-line-alertsAndFeedback-alert-triangle mr-1.5 mt-0.5 h-3.5 w-3.5 text-[#F79009]" />
)
}
{
!isChatMode && item.status === WorkflowRunningStatus.Failed && (
<RiErrorWarningLine className="mr-1.5 mt-0.5 h-3.5 w-3.5 text-[#F04438]" />
<span className="i-ri-error-warning-line mr-1.5 mt-0.5 h-3.5 w-3.5 text-[#F04438]" />
)
}
{
!isChatMode && item.status === WorkflowRunningStatus.Succeeded && (
<RiCheckboxCircleLine className="mr-1.5 mt-0.5 h-3.5 w-3.5 text-[#12B76A]" />
<span className="i-ri-checkbox-circle-line mr-1.5 mt-0.5 h-3.5 w-3.5 text-[#12B76A]" />
)
}
<div>
@ -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)}`}
</div>
<div className="flex items-center text-xs leading-[18px] text-text-tertiary">
{item.created_by_account?.name}

View File

@ -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}
/>
<div className="flex items-center justify-between p-4 pb-1 text-base font-semibold text-text-primary">
{`Test Run${formatWorkflowRunIdentifier(workflowRunningData?.result.finished_at)}`}
{`Test Run${formatWorkflowRunIdentifier(workflowRunningData?.result.finished_at, workflowRunningData?.result.status)}`}
<div className="cursor-pointer p-1" onClick={() => handleCancelDebugAndPreviewPanel()}>
<RiCloseLine className="h-4 w-4 text-text-tertiary" />
<span className="i-ri-close-line h-4 w-4 text-text-tertiary" />
</div>
</div>
<div className="relative flex grow flex-col">
@ -217,7 +213,7 @@ const WorkflowPreview = () => {
Toast.notify({ type: 'success', message: t('actionMsg.copySuccessfully', { ns: 'common' }) })
}}
>
<RiClipboardLine className="h-3.5 w-3.5" />
<span className="i-ri-clipboard-line h-3.5 w-3.5" />
<div>{t('operation.copy', { ns: 'common' })}</div>
</Button>
)}

View File

@ -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([], {

View File

@ -26,14 +26,26 @@ export const useAppWorkflow = (appID: string) => {
})
}
const WorkflowRunHistoryKey = [NAME_SPACE, 'runHistory']
export const useWorkflowRunHistory = (url?: string, enabled = true) => {
return useQuery<WorkflowRunHistoryResponse>({
queryKey: [NAME_SPACE, 'runHistory', url],
queryKey: [...WorkflowRunHistoryKey, url],
queryFn: () => get<WorkflowRunHistoryResponse>(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) => {

View File

@ -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<string, string>
status: string
status: WorkflowRunningStatus
outputs: Record<string, any>
error?: string
elapsed_time: number