From a430284aa21e3ae1f0d5654e55b2ad2852519cc2 Mon Sep 17 00:00:00 2001 From: wwf <yearningwang@iqtogether.com> Date: 星期三, 04 六月 2025 15:17:49 +0800 Subject: [PATCH] 初始化 --- app/components/workflow/run/node.tsx | 180 +++++++++++++++++++++++++++++++---------------------------- 1 files changed, 94 insertions(+), 86 deletions(-) diff --git a/app/components/workflow/run/node.tsx b/app/components/workflow/run/node.tsx index 10f641c..d2da319 100644 --- a/app/components/workflow/run/node.tsx +++ b/app/components/workflow/run/node.tsx @@ -1,62 +1,51 @@ 'use client' import { useTranslation } from 'react-i18next' import type { FC } from 'react' -import { useCallback, useEffect, useMemo, useState } from 'react' +import { useCallback, useEffect, useState } from 'react' import { RiAlertFill, RiArrowRightSLine, RiCheckboxCircleFill, RiErrorWarningLine, RiLoader2Line, + RiRestartFill, } from '@remixicon/react' import BlockIcon from '../block-icon' import { BlockEnum } from '../types' -import { RetryLogTrigger } from './retry-log' -import { IterationLogTrigger } from './iteration-log' -import { LoopLogTrigger } from './loop-log' -import { AgentLogTrigger } from './agent-log' +import Split from '../nodes/_base/components/split' +import { Iteration } from '@/app/components/base/icons/src/vender/workflow' import cn from '@/utils/classnames' import StatusContainer from '@/app/components/workflow/run/status-container' import CodeEditor from '@/app/components/workflow/nodes/_base/components/editor/code-editor' +import Button from '@/app/components/base/button' import { CodeLanguage } from '@/app/components/workflow/nodes/code/types' -import type { - AgentLogItemWithChildren, - IterationDurationMap, - LoopDurationMap, - LoopVariableMap, - NodeTracing, -} from '@/types/workflow' +import type { IterationDurationMap, NodeTracing } from '@/types/workflow' import ErrorHandleTip from '@/app/components/workflow/nodes/_base/components/error-handle/error-handle-tip' import { hasRetryNode } from '@/app/components/workflow/utils' type Props = { className?: string nodeInfo: NodeTracing - allExecutions?: NodeTracing[] inMessage?: boolean hideInfo?: boolean hideProcessDetail?: boolean onShowIterationDetail?: (detail: NodeTracing[][], iterDurationMap: IterationDurationMap) => void - onShowLoopDetail?: (detail: NodeTracing[][], loopDurationMap: LoopDurationMap, loopVariableMap: LoopVariableMap) => void onShowRetryDetail?: (detail: NodeTracing[]) => void - onShowAgentOrToolLog?: (detail?: AgentLogItemWithChildren) => void notShowIterationNav?: boolean - notShowLoopNav?: boolean + justShowIterationNavArrow?: boolean + justShowRetryNavArrow?: boolean } const NodePanel: FC<Props> = ({ className, nodeInfo, - allExecutions, inMessage = false, hideInfo = false, hideProcessDetail, onShowIterationDetail, - onShowLoopDetail, onShowRetryDetail, - onShowAgentOrToolLog, notShowIterationNav, - notShowLoopNav, + justShowIterationNavArrow, }) => { const [collapseState, doSetCollapseState] = useState<boolean>(true) const setCollapseState = useCallback((state: boolean) => { @@ -70,7 +59,7 @@ if (time < 1) return `${(time * 1000).toFixed(3)} ms` if (time > 60) - return `${Number.parseInt(Math.round(time / 60).toString())} m ${(time % 60).toFixed(3)} s` + return `${parseInt(Math.round(time / 60).toString())} m ${(time % 60).toFixed(3)} s` return `${time.toFixed(3)} s` } @@ -78,42 +67,50 @@ if (tokens < 1000) return tokens if (tokens >= 1000 && tokens < 1000000) - return `${Number.parseFloat((tokens / 1000).toFixed(3))}K` + return `${parseFloat((tokens / 1000).toFixed(3))}K` if (tokens >= 1000000) - return `${Number.parseFloat((tokens / 1000000).toFixed(3))}M` + return `${parseFloat((tokens / 1000000).toFixed(3))}M` } + const getCount = (iteration_curr_length: number | undefined, iteration_length: number) => { + if ((iteration_curr_length && iteration_curr_length < iteration_length) || !iteration_length) + return iteration_curr_length + + return iteration_length + } + const getErrorCount = (details: NodeTracing[][] | undefined) => { + if (!details || details.length === 0) + return 0 + + return details.reduce((acc, iteration) => { + if (iteration.some(item => item.status === 'failed')) + acc++ + return acc + }, 0) + } useEffect(() => { setCollapseState(!nodeInfo.expand) }, [nodeInfo.expand, setCollapseState]) - const isIterationNode = nodeInfo.node_type === BlockEnum.Iteration && !!nodeInfo.details?.length - const isLoopNode = nodeInfo.node_type === BlockEnum.Loop && !!nodeInfo.details?.length - const isRetryNode = hasRetryNode(nodeInfo.node_type) && !!nodeInfo.retryDetail?.length - const isAgentNode = nodeInfo.node_type === BlockEnum.Agent && !!nodeInfo.agentLog?.length - const isToolNode = nodeInfo.node_type === BlockEnum.Tool && !!nodeInfo.agentLog?.length - - const inputsTitle = useMemo(() => { - let text = t('workflow.common.input') - if (nodeInfo.node_type === BlockEnum.Loop) - text = t('workflow.nodes.loop.initialLoopVariables') - return text.toLocaleUpperCase() - }, [nodeInfo.node_type, t]) - const processDataTitle = t('workflow.common.processData').toLocaleUpperCase() - const outputTitle = useMemo(() => { - let text = t('workflow.common.output') - if (nodeInfo.node_type === BlockEnum.Loop) - text = t('workflow.nodes.loop.finalLoopVariables') - return text.toLocaleUpperCase() - }, [nodeInfo.node_type, t]) - + const isIterationNode = nodeInfo.node_type === BlockEnum.Iteration + const isRetryNode = hasRetryNode(nodeInfo.node_type) && nodeInfo.retryDetail + const handleOnShowIterationDetail = (e: React.MouseEvent<HTMLButtonElement>) => { + e.stopPropagation() + e.nativeEvent.stopImmediatePropagation() + onShowIterationDetail?.(nodeInfo.details || [], nodeInfo?.iterDurationMap || nodeInfo.execution_metadata?.iteration_duration_map || {}) + } + const handleOnShowRetryDetail = (e: React.MouseEvent<HTMLButtonElement>) => { + e.stopPropagation() + e.nativeEvent.stopImmediatePropagation() + onShowRetryDetail?.(nodeInfo.retryDetail || []) + } return ( <div className={cn('px-2 py-1', className)}> - <div className='group rounded-[10px] border border-components-panel-border bg-background-default shadow-xs transition-all hover:shadow-md'> + <div className='group transition-all bg-background-default border border-components-panel-border rounded-[10px] shadow-xs hover:shadow-md'> <div className={cn( - 'flex cursor-pointer items-center pl-1 pr-3', - hideInfo ? 'py-2 pl-2' : 'py-1.5', + 'flex items-center pl-1 pr-3 cursor-pointer', + hideInfo ? 'py-2' : 'py-1.5', !collapseState && (hideInfo ? '!pb-1' : '!pb-1.5'), )} onClick={() => setCollapseState(!collapseState)} @@ -121,70 +118,81 @@ {!hideProcessDetail && ( <RiArrowRightSLine className={cn( - 'mr-1 h-4 w-4 shrink-0 text-text-quaternary transition-all group-hover:text-text-tertiary', + 'shrink-0 w-4 h-4 mr-1 text-text-quaternary transition-all group-hover:text-text-tertiary', !collapseState && 'rotate-90', )} /> )} - <BlockIcon size={inMessage ? 'xs' : 'sm'} className={cn('mr-2 shrink-0', inMessage && '!mr-1')} type={nodeInfo.node_type} toolIcon={nodeInfo.extras?.icon || nodeInfo.extras} /> + <BlockIcon size={inMessage ? 'xs' : 'sm'} className={cn('shrink-0 mr-2', inMessage && '!mr-1')} type={nodeInfo.node_type} toolIcon={nodeInfo.extras?.icon || nodeInfo.extras} /> <div className={cn( - 'system-xs-semibold-uppercase grow truncate text-text-secondary', + 'grow text-text-secondary system-xs-semibold-uppercase truncate', hideInfo && '!text-xs', )} title={nodeInfo.title}>{nodeInfo.title}</div> {nodeInfo.status !== 'running' && !hideInfo && ( - <div className='system-xs-regular shrink-0 text-text-tertiary'>{nodeInfo.execution_metadata?.total_tokens ? `${getTokenCount(nodeInfo.execution_metadata?.total_tokens || 0)} tokens 路 ` : ''}{`${getTime(nodeInfo.elapsed_time || 0)}`}</div> + <div className='shrink-0 text-text-tertiary system-xs-regular'>{nodeInfo.execution_metadata?.total_tokens ? `${getTokenCount(nodeInfo.execution_metadata?.total_tokens || 0)} tokens 路 ` : ''}{`${getTime(nodeInfo.elapsed_time || 0)}`}</div> )} {nodeInfo.status === 'succeeded' && ( - <RiCheckboxCircleFill className='ml-2 h-3.5 w-3.5 shrink-0 text-text-success' /> + <RiCheckboxCircleFill className='shrink-0 ml-2 w-3.5 h-3.5 text-text-success' /> )} {nodeInfo.status === 'failed' && ( - <RiErrorWarningLine className='ml-2 h-3.5 w-3.5 shrink-0 text-text-warning' /> + <RiErrorWarningLine className='shrink-0 ml-2 w-3.5 h-3.5 text-text-warning' /> )} {nodeInfo.status === 'stopped' && ( - <RiAlertFill className={cn('ml-2 h-4 w-4 shrink-0 text-text-warning-secondary', inMessage && 'h-3.5 w-3.5')} /> + <RiAlertFill className={cn('shrink-0 ml-2 w-4 h-4 text-text-warning-secondary', inMessage && 'w-3.5 h-3.5')} /> )} {nodeInfo.status === 'exception' && ( - <RiAlertFill className={cn('ml-2 h-4 w-4 shrink-0 text-text-warning-secondary', inMessage && 'h-3.5 w-3.5')} /> + <RiAlertFill className={cn('shrink-0 ml-2 w-4 h-4 text-text-warning-secondary', inMessage && 'w-3.5 h-3.5')} /> )} {nodeInfo.status === 'running' && ( - <div className='flex shrink-0 items-center text-[13px] font-medium leading-[16px] text-text-accent'> + <div className='shrink-0 flex items-center text-text-accent text-[13px] leading-[16px] font-medium'> <span className='mr-2 text-xs font-normal'>Running</span> - <RiLoader2Line className='h-3.5 w-3.5 animate-spin' /> + <RiLoader2Line className='w-3.5 h-3.5 animate-spin' /> </div> )} </div> {!collapseState && !hideProcessDetail && ( <div className='px-1 pb-1'> {/* The nav to the iteration detail */} - {isIterationNode && !notShowIterationNav && onShowIterationDetail && ( - <IterationLogTrigger - nodeInfo={nodeInfo} - allExecutions={allExecutions} - onShowIterationResultList={onShowIterationDetail} - /> + {isIterationNode && !notShowIterationNav && ( + <div className='mt-2 mb-1 !px-2'> + <Button + className='flex items-center w-full self-stretch gap-2 px-3 py-2 bg-components-button-tertiary-bg-hover hover:bg-components-button-tertiary-bg-hover rounded-lg cursor-pointer border-none' + onClick={handleOnShowIterationDetail} + > + <Iteration className='w-4 h-4 text-components-button-tertiary-text flex-shrink-0' /> + <div className='flex-1 text-left system-sm-medium text-components-button-tertiary-text'>{t('workflow.nodes.iteration.iteration', { count: getCount(nodeInfo.details?.length, nodeInfo.metadata?.iterator_length) })}{getErrorCount(nodeInfo.details) > 0 && ( + <> + {t('workflow.nodes.iteration.comma')} + {t('workflow.nodes.iteration.error', { count: getErrorCount(nodeInfo.details) })} + </> + )}</div> + {justShowIterationNavArrow + ? ( + <RiArrowRightSLine className='w-4 h-4 text-components-button-tertiary-text flex-shrink-0' /> + ) + : ( + <div className='flex items-center space-x-1 text-[#155EEF]'> + <div className='text-[13px] font-normal '>{t('workflow.common.viewDetailInTracingPanel')}</div> + <RiArrowRightSLine className='w-4 h-4 text-components-button-tertiary-text flex-shrink-0' /> + </div> + )} + </Button> + <Split className='mt-2' /> + </div> )} - {/* The nav to the Loop detail */} - {isLoopNode && !notShowLoopNav && onShowLoopDetail && ( - <LoopLogTrigger - nodeInfo={nodeInfo} - allExecutions={allExecutions} - onShowLoopResultList={onShowLoopDetail} - /> + {isRetryNode && ( + <Button + className='flex items-center justify-between mb-1 w-full' + variant='tertiary' + onClick={handleOnShowRetryDetail} + > + <div className='flex items-center'> + <RiRestartFill className='mr-0.5 w-4 h-4 text-components-button-tertiary-text flex-shrink-0' /> + {t('workflow.nodes.common.retry.retries', { num: nodeInfo.retryDetail?.length })} + </div> + <RiArrowRightSLine className='w-4 h-4 text-components-button-tertiary-text flex-shrink-0' /> + </Button> )} - {isRetryNode && onShowRetryDetail && ( - <RetryLogTrigger - nodeInfo={nodeInfo} - onShowRetryResultList={onShowRetryDetail} - /> - )} - { - (isAgentNode || isToolNode) && onShowAgentOrToolLog && ( - <AgentLogTrigger - nodeInfo={nodeInfo} - onShowAgentOrToolLog={onShowAgentOrToolLog} - /> - ) - } <div className={cn('mb-1', hideInfo && '!px-2 !py-0.5')}> {(nodeInfo.status === 'stopped') && ( <StatusContainer status='stopped'> @@ -218,7 +226,7 @@ <div className={cn('mb-1')}> <CodeEditor readOnly - title={<div>{inputsTitle}</div>} + title={<div>{t('workflow.common.input').toLocaleUpperCase()}</div>} language={CodeLanguage.json} value={nodeInfo.inputs} isJSONStringifyBeauty @@ -229,7 +237,7 @@ <div className={cn('mb-1')}> <CodeEditor readOnly - title={<div>{processDataTitle}</div>} + title={<div>{t('workflow.common.processData').toLocaleUpperCase()}</div>} language={CodeLanguage.json} value={nodeInfo.process_data} isJSONStringifyBeauty @@ -240,7 +248,7 @@ <div> <CodeEditor readOnly - title={<div>{outputTitle}</div>} + title={<div>{t('workflow.common.output').toLocaleUpperCase()}</div>} language={CodeLanguage.json} value={nodeInfo.outputs} isJSONStringifyBeauty -- Gitblit v1.8.0