From 88c2483192ac496a2aac35692b8e9331a7a2f4c1 Mon Sep 17 00:00:00 2001 From: twwu Date: Thu, 15 Jan 2026 11:52:33 +0800 Subject: [PATCH] refactor: simplify dependencies in DetailPanel and enhance workflow run components with new props for better handling of paused states --- web/app/components/app/log/list.tsx | 4 +- web/app/components/workflow/run/index.tsx | 1 + .../components/workflow/run/result-panel.tsx | 3 + web/app/components/workflow/run/status.tsx | 86 +++++++++++++++---- web/i18n/en-US/workflow.json | 2 +- web/i18n/zh-Hans/workflow.json | 2 +- web/models/log.ts | 19 ++++ web/service/use-log.ts | 16 ++++ 8 files changed, 111 insertions(+), 22 deletions(-) diff --git a/web/app/components/app/log/list.tsx b/web/app/components/app/log/list.tsx index 0c355625fb..16f67de547 100644 --- a/web/app/components/app/log/list.tsx +++ b/web/app/components/app/log/list.tsx @@ -304,7 +304,7 @@ function DetailPanel({ detail, onFeedback }: IDetailPanel) { if (abortControllerRef.current === controller) abortControllerRef.current = null } - }, [detail.id, hasMore, timezone, t, appDetail, detail?.model_config?.configs?.introduction]) + }, [detail.id, hasMore, timezone, t, appDetail]) // Derive chatItemTree, threadChatItems, and oldestAnswerIdRef from allChatItems useEffect(() => { @@ -512,7 +512,7 @@ function DetailPanel({ detail, onFeedback }: IDetailPanel) { finally { setIsLoading(false) } - }, [detail.id, hasMore, isLoading, timezone, t, appDetail, detail?.model_config?.configs?.introduction]) + }, [detail.id, hasMore, isLoading, timezone, t, appDetail]) useEffect(() => { const scrollableDiv = document.getElementById('scrollableDiv') diff --git a/web/app/components/workflow/run/index.tsx b/web/app/components/workflow/run/index.tsx index 51f641f265..a501dd184b 100644 --- a/web/app/components/workflow/run/index.tsx +++ b/web/app/components/workflow/run/index.tsx @@ -182,6 +182,7 @@ const RunPanel: FC = ({ steps={runDetail.total_steps} exceptionCounts={runDetail.exceptions_count} isListening={isListening} + workflowRunId={runDetail.id} /> )} {!loading && currentTab === 'DETAIL' && !runDetail && isListening && ( diff --git a/web/app/components/workflow/run/result-panel.tsx b/web/app/components/workflow/run/result-panel.tsx index 873be71b5b..58f783e6c4 100644 --- a/web/app/components/workflow/run/result-panel.tsx +++ b/web/app/components/workflow/run/result-panel.tsx @@ -41,6 +41,7 @@ export type ResultPanelProps = { exceptionCounts?: number execution_metadata?: any isListening?: boolean + workflowRunId?: string handleShowIterationResultList?: (detail: NodeTracing[][], iterDurationMap: any) => void handleShowLoopResultList?: (detail: NodeTracing[][], loopDurationMap: any) => void onShowRetryDetail?: (detail: NodeTracing[]) => void @@ -67,6 +68,7 @@ const ResultPanel: FC = ({ exceptionCounts, execution_metadata, isListening = false, + workflowRunId, handleShowIterationResultList, handleShowLoopResultList, onShowRetryDetail, @@ -89,6 +91,7 @@ const ResultPanel: FC = ({ error={error} exceptionCounts={exceptionCounts} isListening={isListening} + workflowRunId={workflowRunId} />
diff --git a/web/app/components/workflow/run/status.tsx b/web/app/components/workflow/run/status.tsx index d018a3c0ea..3bdcb73960 100644 --- a/web/app/components/workflow/run/status.tsx +++ b/web/app/components/workflow/run/status.tsx @@ -1,9 +1,11 @@ 'use client' import type { FC } from 'react' +import { useMemo } from 'react' import { useTranslation } from 'react-i18next' import Indicator from '@/app/components/header/indicator' import StatusContainer from '@/app/components/workflow/run/status-container' import { useDocLink } from '@/context/i18n' +import { useWorkflowPausedDetails } from '@/service/use-log' import { cn } from '@/utils/classnames' type ResultProps = { @@ -12,8 +14,8 @@ type ResultProps = { tokens?: number error?: string exceptionCounts?: number - inputURL?: string isListening?: boolean + workflowRunId?: string } const StatusPanel: FC = ({ @@ -22,11 +24,46 @@ const StatusPanel: FC = ({ tokens, error, exceptionCounts, - inputURL, isListening = false, + workflowRunId, }) => { const { t } = useTranslation() const docLink = useDocLink() + const { data: pausedDetails } = useWorkflowPausedDetails({ + workflowRunId: workflowRunId || '', + enabled: status === 'paused', + }) + + const pausedReasons = useMemo(() => { + const reasons: string[] = [] + if (!pausedDetails) + return reasons + const hasHumanInputNode = pausedDetails.paused_nodes.some( + node => node.pause_type.type === 'human_input', + ) + if (hasHumanInputNode) { + reasons.push(t('nodes.humanInput.log.reasonContent', { ns: 'workflow' })) + } + return reasons + }, [pausedDetails, t]) + + const pausedInputURLs = useMemo(() => { + const inputURLs: string[] = [] + if (!pausedDetails) + return inputURLs + const { paused_nodes } = pausedDetails + const hasHumanInputNode = paused_nodes.some( + node => node.pause_type.type === 'human_input', + ) + if (hasHumanInputNode) { + paused_nodes.forEach((node) => { + if (node.pause_type.type === 'human_input') { + inputURLs.push(node.pause_type.backstage_input_url) + } + }) + } + return inputURLs + }, [pausedDetails]) return ( @@ -95,7 +132,7 @@ const StatusPanel: FC = ({
{t('resultPanel.time', { ns: 'runLog' })}
{(status === 'running' || status === 'paused') && ( -
+
)} {status !== 'running' && status !== 'paused' && ( {time ? `${time?.toFixed(3)}s` : '-'} @@ -106,7 +143,7 @@ const StatusPanel: FC = ({
{t('resultPanel.tokens', { ns: 'runLog' })}
{(status === 'running' || status === 'paused') && ( -
+
)} {status !== 'running' && status !== 'paused' && ( {`${tokens || 0} Tokens`} @@ -160,21 +197,34 @@ const StatusPanel: FC = ({ {status === 'paused' && ( <>
-
-
-
{t('nodes.humanInput.log.reason', { ns: 'workflow' })}
-
{t('nodes.humanInput.log.reasonContent', { ns: 'workflow' })}
-
-
-
{t('nodes.humanInput.log.inputURL', { ns: 'workflow' })}
- - {inputURL} - +
+
+
{t('nodes.humanInput.log.reason', { ns: 'workflow' })}
+ { + pausedReasons.length > 0 + ? pausedReasons.map(reason => ( +
{reason}
+ )) + : ( +
+ ) + }
+ {pausedInputURLs.length > 0 && ( +
+
{t('nodes.humanInput.log.backstageInputURL', { ns: 'workflow' })}
+ {pausedInputURLs.map(url => ( + + {url} + + ))} +
+ )}
)} diff --git a/web/i18n/en-US/workflow.json b/web/i18n/en-US/workflow.json index d20bf3b82b..80c7fa153b 100644 --- a/web/i18n/en-US/workflow.json +++ b/web/i18n/en-US/workflow.json @@ -577,7 +577,7 @@ "nodes.humanInput.insertInputField.useVarInstead": "Use Variable Instead", "nodes.humanInput.insertInputField.variable": "variable", "nodes.humanInput.insertInputField.variableNameInvalid": "Variable name can only contain letters, numbers, and underscores, and cannot start with a number", - "nodes.humanInput.log.inputURL": "Input URL:", + "nodes.humanInput.log.backstageInputURL": "Backstage input URL:", "nodes.humanInput.log.reason": "Reason:", "nodes.humanInput.log.reasonContent": "Human input required to proceed", "nodes.humanInput.singleRun.back": "Back", diff --git a/web/i18n/zh-Hans/workflow.json b/web/i18n/zh-Hans/workflow.json index 7301b5dfec..11651a7478 100644 --- a/web/i18n/zh-Hans/workflow.json +++ b/web/i18n/zh-Hans/workflow.json @@ -577,7 +577,7 @@ "nodes.humanInput.insertInputField.useVarInstead": "使用变量代替", "nodes.humanInput.insertInputField.variable": "变量", "nodes.humanInput.insertInputField.variableNameInvalid": "只能包含字母、数字和下划线,且不能以数字开头", - "nodes.humanInput.log.inputURL": "输入 URL :", + "nodes.humanInput.log.backstageInputURL": "表单输入 URL:", "nodes.humanInput.log.reason": "原因:", "nodes.humanInput.log.reasonContent": "需要人类输入才能继续", "nodes.humanInput.singleRun.back": "返回", diff --git a/web/models/log.ts b/web/models/log.ts index 4b3ed4f9de..ab1282b8af 100644 --- a/web/models/log.ts +++ b/web/models/log.ts @@ -367,3 +367,22 @@ export type AgentLogDetailResponse = { iterations: AgentIteration[] files: AgentLogFile[] } + +export type PauseType = { + type: 'human_input' + form_id: string + backstage_input_url: string +} | { + type: 'breakpoint' +} + +export type PauseDetail = { + node_id: string + node_title: string + pause_type: PauseType +} + +export type WorkflowPausedDetailsResponse = { + paused_at: string + paused_nodes: PauseDetail[] +} diff --git a/web/service/use-log.ts b/web/service/use-log.ts index b120adda2f..310c5000d6 100644 --- a/web/service/use-log.ts +++ b/web/service/use-log.ts @@ -7,6 +7,7 @@ import type { CompletionConversationsRequest, CompletionConversationsResponse, WorkflowLogsResponse, + WorkflowPausedDetailsResponse, } from '@/models/log' import { useQuery } from '@tanstack/react-query' import { get } from './base' @@ -87,3 +88,18 @@ export const useWorkflowLogs = ({ appId, params }: WorkflowLogsParams) => { enabled: !!appId, }) } + +// ============ Workflow Pause Details ============ + +type WorkflowPausedDetailsParams = { + workflowRunId: string + enabled?: boolean +} + +export const useWorkflowPausedDetails = ({ workflowRunId, enabled = true }: WorkflowPausedDetailsParams) => { + return useQuery({ + queryKey: [NAME_SPACE, 'workflow-paused-details', workflowRunId], + queryFn: () => get(`/workflow/${workflowRunId}/pause-details`), + enabled: enabled && !!workflowRunId, + }) +}