mirror of
https://git.mirrors.martin98.com/https://github.com/langgenius/dify.git
synced 2025-08-17 18:15:54 +08:00
Feat/workflow retry (#11885)
This commit is contained in:
parent
dacd457478
commit
0c0120ef27
@ -64,6 +64,12 @@ const WorkflowProcessItem = ({
|
|||||||
setShowMessageLogModal(true)
|
setShowMessageLogModal(true)
|
||||||
}, [item, setCurrentLogItem, setCurrentLogModalActiveTab, setShowMessageLogModal])
|
}, [item, setCurrentLogItem, setCurrentLogModalActiveTab, setShowMessageLogModal])
|
||||||
|
|
||||||
|
const showRetryDetail = useCallback(() => {
|
||||||
|
setCurrentLogItem(item)
|
||||||
|
setCurrentLogModalActiveTab('TRACING')
|
||||||
|
setShowMessageLogModal(true)
|
||||||
|
}, [item, setCurrentLogItem, setCurrentLogModalActiveTab, setShowMessageLogModal])
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div
|
<div
|
||||||
className={cn(
|
className={cn(
|
||||||
@ -105,6 +111,7 @@ const WorkflowProcessItem = ({
|
|||||||
<TracingPanel
|
<TracingPanel
|
||||||
list={data.tracing}
|
list={data.tracing}
|
||||||
onShowIterationDetail={showIterationDetail}
|
onShowIterationDetail={showIterationDetail}
|
||||||
|
onShowRetryDetail={showRetryDetail}
|
||||||
hideNodeInfo={hideInfo}
|
hideNodeInfo={hideInfo}
|
||||||
hideNodeProcessDetail={hideProcessDetail}
|
hideNodeProcessDetail={hideProcessDetail}
|
||||||
/>
|
/>
|
||||||
|
@ -28,6 +28,7 @@ export type InputProps = {
|
|||||||
destructive?: boolean
|
destructive?: boolean
|
||||||
wrapperClassName?: string
|
wrapperClassName?: string
|
||||||
styleCss?: CSSProperties
|
styleCss?: CSSProperties
|
||||||
|
unit?: string
|
||||||
} & React.InputHTMLAttributes<HTMLInputElement> & VariantProps<typeof inputVariants>
|
} & React.InputHTMLAttributes<HTMLInputElement> & VariantProps<typeof inputVariants>
|
||||||
|
|
||||||
const Input = ({
|
const Input = ({
|
||||||
@ -43,6 +44,7 @@ const Input = ({
|
|||||||
value,
|
value,
|
||||||
placeholder,
|
placeholder,
|
||||||
onChange,
|
onChange,
|
||||||
|
unit,
|
||||||
...props
|
...props
|
||||||
}: InputProps) => {
|
}: InputProps) => {
|
||||||
const { t } = useTranslation()
|
const { t } = useTranslation()
|
||||||
@ -80,6 +82,13 @@ const Input = ({
|
|||||||
{destructive && (
|
{destructive && (
|
||||||
<RiErrorWarningLine className='absolute right-2 top-1/2 -translate-y-1/2 w-4 h-4 text-text-destructive-secondary' />
|
<RiErrorWarningLine className='absolute right-2 top-1/2 -translate-y-1/2 w-4 h-4 text-text-destructive-secondary' />
|
||||||
)}
|
)}
|
||||||
|
{
|
||||||
|
unit && (
|
||||||
|
<div className='absolute right-2 top-1/2 -translate-y-1/2 system-sm-regular text-text-tertiary'>
|
||||||
|
{unit}
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
</div>
|
</div>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
@ -506,3 +506,5 @@ export const WORKFLOW_DATA_UPDATE = 'WORKFLOW_DATA_UPDATE'
|
|||||||
export const CUSTOM_NODE = 'custom'
|
export const CUSTOM_NODE = 'custom'
|
||||||
export const CUSTOM_EDGE = 'custom'
|
export const CUSTOM_EDGE = 'custom'
|
||||||
export const DSL_EXPORT_CHECK = 'DSL_EXPORT_CHECK'
|
export const DSL_EXPORT_CHECK = 'DSL_EXPORT_CHECK'
|
||||||
|
export const DEFAULT_RETRY_MAX = 3
|
||||||
|
export const DEFAULT_RETRY_INTERVAL = 100
|
||||||
|
@ -28,6 +28,7 @@ import {
|
|||||||
getFilesInLogs,
|
getFilesInLogs,
|
||||||
} from '@/app/components/base/file-uploader/utils'
|
} from '@/app/components/base/file-uploader/utils'
|
||||||
import { ErrorHandleTypeEnum } from '@/app/components/workflow/nodes/_base/components/error-handle/types'
|
import { ErrorHandleTypeEnum } from '@/app/components/workflow/nodes/_base/components/error-handle/types'
|
||||||
|
import type { NodeTracing } from '@/types/workflow'
|
||||||
|
|
||||||
export const useWorkflowRun = () => {
|
export const useWorkflowRun = () => {
|
||||||
const store = useStoreApi()
|
const store = useStoreApi()
|
||||||
@ -114,6 +115,7 @@ export const useWorkflowRun = () => {
|
|||||||
onIterationStart,
|
onIterationStart,
|
||||||
onIterationNext,
|
onIterationNext,
|
||||||
onIterationFinish,
|
onIterationFinish,
|
||||||
|
onNodeRetry,
|
||||||
onError,
|
onError,
|
||||||
...restCallback
|
...restCallback
|
||||||
} = callback || {}
|
} = callback || {}
|
||||||
@ -440,10 +442,13 @@ export const useWorkflowRun = () => {
|
|||||||
})
|
})
|
||||||
if (currentIndex > -1 && draft.tracing) {
|
if (currentIndex > -1 && draft.tracing) {
|
||||||
draft.tracing[currentIndex] = {
|
draft.tracing[currentIndex] = {
|
||||||
|
...data,
|
||||||
...(draft.tracing[currentIndex].extras
|
...(draft.tracing[currentIndex].extras
|
||||||
? { extras: draft.tracing[currentIndex].extras }
|
? { extras: draft.tracing[currentIndex].extras }
|
||||||
: {}),
|
: {}),
|
||||||
...data,
|
...(draft.tracing[currentIndex].retryDetail
|
||||||
|
? { retryDetail: draft.tracing[currentIndex].retryDetail }
|
||||||
|
: {}),
|
||||||
} as any
|
} as any
|
||||||
}
|
}
|
||||||
}))
|
}))
|
||||||
@ -616,6 +621,41 @@ export const useWorkflowRun = () => {
|
|||||||
if (onIterationFinish)
|
if (onIterationFinish)
|
||||||
onIterationFinish(params)
|
onIterationFinish(params)
|
||||||
},
|
},
|
||||||
|
onNodeRetry: (params) => {
|
||||||
|
const { data } = params
|
||||||
|
const {
|
||||||
|
workflowRunningData,
|
||||||
|
setWorkflowRunningData,
|
||||||
|
} = workflowStore.getState()
|
||||||
|
const {
|
||||||
|
getNodes,
|
||||||
|
setNodes,
|
||||||
|
} = store.getState()
|
||||||
|
|
||||||
|
const nodes = getNodes()
|
||||||
|
setWorkflowRunningData(produce(workflowRunningData!, (draft) => {
|
||||||
|
const tracing = draft.tracing!
|
||||||
|
const currentRetryNodeIndex = tracing.findIndex(trace => trace.node_id === data.node_id)
|
||||||
|
|
||||||
|
if (currentRetryNodeIndex > -1) {
|
||||||
|
const currentRetryNode = tracing[currentRetryNodeIndex]
|
||||||
|
if (currentRetryNode.retryDetail)
|
||||||
|
draft.tracing![currentRetryNodeIndex].retryDetail!.push(data as NodeTracing)
|
||||||
|
|
||||||
|
else
|
||||||
|
draft.tracing![currentRetryNodeIndex].retryDetail = [data as NodeTracing]
|
||||||
|
}
|
||||||
|
}))
|
||||||
|
const newNodes = produce(nodes, (draft) => {
|
||||||
|
const currentNode = draft.find(node => node.id === data.node_id)!
|
||||||
|
|
||||||
|
currentNode.data._retryIndex = data.retry_index
|
||||||
|
})
|
||||||
|
setNodes(newNodes)
|
||||||
|
|
||||||
|
if (onNodeRetry)
|
||||||
|
onNodeRetry(params)
|
||||||
|
},
|
||||||
onParallelBranchStarted: (params) => {
|
onParallelBranchStarted: (params) => {
|
||||||
// console.log(params, 'parallel start')
|
// console.log(params, 'parallel start')
|
||||||
},
|
},
|
||||||
|
@ -17,17 +17,25 @@ import ResultPanel from '@/app/components/workflow/run/result-panel'
|
|||||||
import Toast from '@/app/components/base/toast'
|
import Toast from '@/app/components/base/toast'
|
||||||
import { TransferMethod } from '@/types/app'
|
import { TransferMethod } from '@/types/app'
|
||||||
import { getProcessedFiles } from '@/app/components/base/file-uploader/utils'
|
import { getProcessedFiles } from '@/app/components/base/file-uploader/utils'
|
||||||
|
import type { NodeTracing } from '@/types/workflow'
|
||||||
|
import RetryResultPanel from '@/app/components/workflow/run/retry-result-panel'
|
||||||
|
import type { BlockEnum } from '@/app/components/workflow/types'
|
||||||
|
import type { Emoji } from '@/app/components/tools/types'
|
||||||
|
|
||||||
const i18nPrefix = 'workflow.singleRun'
|
const i18nPrefix = 'workflow.singleRun'
|
||||||
|
|
||||||
type BeforeRunFormProps = {
|
type BeforeRunFormProps = {
|
||||||
nodeName: string
|
nodeName: string
|
||||||
|
nodeType?: BlockEnum
|
||||||
|
toolIcon?: string | Emoji
|
||||||
onHide: () => void
|
onHide: () => void
|
||||||
onRun: (submitData: Record<string, any>) => void
|
onRun: (submitData: Record<string, any>) => void
|
||||||
onStop: () => void
|
onStop: () => void
|
||||||
runningStatus: NodeRunningStatus
|
runningStatus: NodeRunningStatus
|
||||||
result?: JSX.Element
|
result?: JSX.Element
|
||||||
forms: FormProps[]
|
forms: FormProps[]
|
||||||
|
retryDetails?: NodeTracing[]
|
||||||
|
onRetryDetailBack?: any
|
||||||
}
|
}
|
||||||
|
|
||||||
function formatValue(value: string | any, type: InputVarType) {
|
function formatValue(value: string | any, type: InputVarType) {
|
||||||
@ -50,12 +58,16 @@ function formatValue(value: string | any, type: InputVarType) {
|
|||||||
}
|
}
|
||||||
const BeforeRunForm: FC<BeforeRunFormProps> = ({
|
const BeforeRunForm: FC<BeforeRunFormProps> = ({
|
||||||
nodeName,
|
nodeName,
|
||||||
|
nodeType,
|
||||||
|
toolIcon,
|
||||||
onHide,
|
onHide,
|
||||||
onRun,
|
onRun,
|
||||||
onStop,
|
onStop,
|
||||||
runningStatus,
|
runningStatus,
|
||||||
result,
|
result,
|
||||||
forms,
|
forms,
|
||||||
|
retryDetails,
|
||||||
|
onRetryDetailBack = () => { },
|
||||||
}) => {
|
}) => {
|
||||||
const { t } = useTranslation()
|
const { t } = useTranslation()
|
||||||
|
|
||||||
@ -122,48 +134,69 @@ const BeforeRunForm: FC<BeforeRunFormProps> = ({
|
|||||||
<div className='text-base font-semibold text-gray-900 truncate'>
|
<div className='text-base font-semibold text-gray-900 truncate'>
|
||||||
{t(`${i18nPrefix}.testRun`)} {nodeName}
|
{t(`${i18nPrefix}.testRun`)} {nodeName}
|
||||||
</div>
|
</div>
|
||||||
<div className='ml-2 shrink-0 p-1 cursor-pointer' onClick={onHide}>
|
<div className='ml-2 shrink-0 p-1 cursor-pointer' onClick={() => {
|
||||||
|
onHide()
|
||||||
|
}}>
|
||||||
<RiCloseLine className='w-4 h-4 text-gray-500 ' />
|
<RiCloseLine className='w-4 h-4 text-gray-500 ' />
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
{
|
||||||
<div className='h-0 grow overflow-y-auto pb-4'>
|
retryDetails?.length && (
|
||||||
<div className='mt-3 px-4 space-y-4'>
|
<div className='h-0 grow overflow-y-auto pb-4'>
|
||||||
{forms.map((form, index) => (
|
<RetryResultPanel
|
||||||
<div key={index}>
|
list={retryDetails.map((item, index) => ({
|
||||||
<Form
|
...item,
|
||||||
key={index}
|
title: `${t('workflow.nodes.common.retry.retry')} ${index + 1}`,
|
||||||
className={cn(index < forms.length - 1 && 'mb-4')}
|
node_type: nodeType!,
|
||||||
{...form}
|
extras: {
|
||||||
/>
|
icon: toolIcon!,
|
||||||
{index < forms.length - 1 && <Split />}
|
},
|
||||||
|
}))}
|
||||||
|
onBack={onRetryDetailBack}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
{
|
||||||
|
!retryDetails?.length && (
|
||||||
|
<div className='h-0 grow overflow-y-auto pb-4'>
|
||||||
|
<div className='mt-3 px-4 space-y-4'>
|
||||||
|
{forms.map((form, index) => (
|
||||||
|
<div key={index}>
|
||||||
|
<Form
|
||||||
|
key={index}
|
||||||
|
className={cn(index < forms.length - 1 && 'mb-4')}
|
||||||
|
{...form}
|
||||||
|
/>
|
||||||
|
{index < forms.length - 1 && <Split />}
|
||||||
|
</div>
|
||||||
|
))}
|
||||||
</div>
|
</div>
|
||||||
))}
|
<div className='mt-4 flex justify-between space-x-2 px-4' >
|
||||||
</div>
|
{isRunning && (
|
||||||
|
<div
|
||||||
<div className='mt-4 flex justify-between space-x-2 px-4' >
|
className='p-2 rounded-lg border border-gray-200 bg-white shadow-xs cursor-pointer'
|
||||||
{isRunning && (
|
onClick={onStop}
|
||||||
<div
|
>
|
||||||
className='p-2 rounded-lg border border-gray-200 bg-white shadow-xs cursor-pointer'
|
<StopCircle className='w-4 h-4 text-gray-500' />
|
||||||
onClick={onStop}
|
</div>
|
||||||
>
|
)}
|
||||||
<StopCircle className='w-4 h-4 text-gray-500' />
|
<Button disabled={!isFileLoaded || isRunning} variant='primary' className='w-0 grow space-x-2' onClick={handleRun}>
|
||||||
|
{isRunning && <RiLoader2Line className='animate-spin w-4 h-4 text-white' />}
|
||||||
|
<div>{t(`${i18nPrefix}.${isRunning ? 'running' : 'startRun'}`)}</div>
|
||||||
|
</Button>
|
||||||
</div>
|
</div>
|
||||||
)}
|
{isRunning && (
|
||||||
<Button disabled={!isFileLoaded || isRunning} variant='primary' className='w-0 grow space-x-2' onClick={handleRun}>
|
<ResultPanel status='running' showSteps={false} />
|
||||||
{isRunning && <RiLoader2Line className='animate-spin w-4 h-4 text-white' />}
|
)}
|
||||||
<div>{t(`${i18nPrefix}.${isRunning ? 'running' : 'startRun'}`)}</div>
|
{isFinished && (
|
||||||
</Button>
|
<>
|
||||||
</div>
|
{result}
|
||||||
{isRunning && (
|
</>
|
||||||
<ResultPanel status='running' showSteps={false} />
|
)}
|
||||||
)}
|
</div>
|
||||||
{isFinished && (
|
)
|
||||||
<>
|
}
|
||||||
{result}
|
|
||||||
</>
|
|
||||||
)}
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
)
|
)
|
||||||
|
@ -14,7 +14,6 @@ import type {
|
|||||||
CommonNodeType,
|
CommonNodeType,
|
||||||
Node,
|
Node,
|
||||||
} from '@/app/components/workflow/types'
|
} from '@/app/components/workflow/types'
|
||||||
import Split from '@/app/components/workflow/nodes/_base/components/split'
|
|
||||||
import Tooltip from '@/app/components/base/tooltip'
|
import Tooltip from '@/app/components/base/tooltip'
|
||||||
|
|
||||||
type ErrorHandleProps = Pick<Node, 'id' | 'data'>
|
type ErrorHandleProps = Pick<Node, 'id' | 'data'>
|
||||||
@ -45,7 +44,6 @@ const ErrorHandle = ({
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<Split />
|
|
||||||
<div className='py-4'>
|
<div className='py-4'>
|
||||||
<Collapse
|
<Collapse
|
||||||
disabled={!error_strategy}
|
disabled={!error_strategy}
|
||||||
|
@ -0,0 +1,41 @@
|
|||||||
|
import {
|
||||||
|
useCallback,
|
||||||
|
useState,
|
||||||
|
} from 'react'
|
||||||
|
import type { WorkflowRetryConfig } from './types'
|
||||||
|
import {
|
||||||
|
useNodeDataUpdate,
|
||||||
|
} from '@/app/components/workflow/hooks'
|
||||||
|
import type { NodeTracing } from '@/types/workflow'
|
||||||
|
|
||||||
|
export const useRetryConfig = (
|
||||||
|
id: string,
|
||||||
|
) => {
|
||||||
|
const { handleNodeDataUpdateWithSyncDraft } = useNodeDataUpdate()
|
||||||
|
|
||||||
|
const handleRetryConfigChange = useCallback((value?: WorkflowRetryConfig) => {
|
||||||
|
handleNodeDataUpdateWithSyncDraft({
|
||||||
|
id,
|
||||||
|
data: {
|
||||||
|
retry_config: value,
|
||||||
|
},
|
||||||
|
})
|
||||||
|
}, [id, handleNodeDataUpdateWithSyncDraft])
|
||||||
|
|
||||||
|
return {
|
||||||
|
handleRetryConfigChange,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export const useRetryDetailShowInSingleRun = () => {
|
||||||
|
const [retryDetails, setRetryDetails] = useState<NodeTracing[] | undefined>()
|
||||||
|
|
||||||
|
const handleRetryDetailsChange = useCallback((details: NodeTracing[] | undefined) => {
|
||||||
|
setRetryDetails(details)
|
||||||
|
}, [])
|
||||||
|
|
||||||
|
return {
|
||||||
|
retryDetails,
|
||||||
|
handleRetryDetailsChange,
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,88 @@
|
|||||||
|
import { useMemo } from 'react'
|
||||||
|
import { useTranslation } from 'react-i18next'
|
||||||
|
import {
|
||||||
|
RiAlertFill,
|
||||||
|
RiCheckboxCircleFill,
|
||||||
|
RiLoader2Line,
|
||||||
|
} from '@remixicon/react'
|
||||||
|
import type { Node } from '@/app/components/workflow/types'
|
||||||
|
import { NodeRunningStatus } from '@/app/components/workflow/types'
|
||||||
|
import cn from '@/utils/classnames'
|
||||||
|
|
||||||
|
type RetryOnNodeProps = Pick<Node, 'id' | 'data'>
|
||||||
|
const RetryOnNode = ({
|
||||||
|
data,
|
||||||
|
}: RetryOnNodeProps) => {
|
||||||
|
const { t } = useTranslation()
|
||||||
|
const { retry_config } = data
|
||||||
|
const showSelectedBorder = data.selected || data._isBundled || data._isEntering
|
||||||
|
const {
|
||||||
|
isRunning,
|
||||||
|
isSuccessful,
|
||||||
|
isException,
|
||||||
|
isFailed,
|
||||||
|
} = useMemo(() => {
|
||||||
|
return {
|
||||||
|
isRunning: data._runningStatus === NodeRunningStatus.Running && !showSelectedBorder,
|
||||||
|
isSuccessful: data._runningStatus === NodeRunningStatus.Succeeded && !showSelectedBorder,
|
||||||
|
isFailed: data._runningStatus === NodeRunningStatus.Failed && !showSelectedBorder,
|
||||||
|
isException: data._runningStatus === NodeRunningStatus.Exception && !showSelectedBorder,
|
||||||
|
}
|
||||||
|
}, [data._runningStatus, showSelectedBorder])
|
||||||
|
const showDefault = !isRunning && !isSuccessful && !isException && !isFailed
|
||||||
|
|
||||||
|
if (!retry_config)
|
||||||
|
return null
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className='px-3'>
|
||||||
|
<div className={cn(
|
||||||
|
'flex items-center justify-between px-[5px] py-1 bg-workflow-block-parma-bg border-[0.5px] border-transparent rounded-md system-xs-medium-uppercase text-text-tertiary',
|
||||||
|
isRunning && 'bg-state-accent-hover border-state-accent-active text-text-accent',
|
||||||
|
isSuccessful && 'bg-state-success-hover border-state-success-active text-text-success',
|
||||||
|
(isException || isFailed) && 'bg-state-warning-hover border-state-warning-active text-text-warning',
|
||||||
|
)}>
|
||||||
|
<div className='flex items-center'>
|
||||||
|
{
|
||||||
|
showDefault && (
|
||||||
|
t('workflow.nodes.common.retry.retryTimes', { times: retry_config.max_retries })
|
||||||
|
)
|
||||||
|
}
|
||||||
|
{
|
||||||
|
isRunning && (
|
||||||
|
<>
|
||||||
|
<RiLoader2Line className='animate-spin mr-1 w-3.5 h-3.5' />
|
||||||
|
{t('workflow.nodes.common.retry.retrying')}
|
||||||
|
</>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
{
|
||||||
|
isSuccessful && (
|
||||||
|
<>
|
||||||
|
<RiCheckboxCircleFill className='mr-1 w-3.5 h-3.5' />
|
||||||
|
{t('workflow.nodes.common.retry.retrySuccessful')}
|
||||||
|
</>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
{
|
||||||
|
(isFailed || isException) && (
|
||||||
|
<>
|
||||||
|
<RiAlertFill className='mr-1 w-3.5 h-3.5' />
|
||||||
|
{t('workflow.nodes.common.retry.retryFailed')}
|
||||||
|
</>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
</div>
|
||||||
|
{
|
||||||
|
!showDefault && (
|
||||||
|
<div>
|
||||||
|
{data._retryIndex}/{data.retry_config?.max_retries}
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
export default RetryOnNode
|
@ -0,0 +1,117 @@
|
|||||||
|
import { useTranslation } from 'react-i18next'
|
||||||
|
import { useRetryConfig } from './hooks'
|
||||||
|
import s from './style.module.css'
|
||||||
|
import Switch from '@/app/components/base/switch'
|
||||||
|
import Slider from '@/app/components/base/slider'
|
||||||
|
import Input from '@/app/components/base/input'
|
||||||
|
import type {
|
||||||
|
Node,
|
||||||
|
} from '@/app/components/workflow/types'
|
||||||
|
import Split from '@/app/components/workflow/nodes/_base/components/split'
|
||||||
|
|
||||||
|
type RetryOnPanelProps = Pick<Node, 'id' | 'data'>
|
||||||
|
const RetryOnPanel = ({
|
||||||
|
id,
|
||||||
|
data,
|
||||||
|
}: RetryOnPanelProps) => {
|
||||||
|
const { t } = useTranslation()
|
||||||
|
const { handleRetryConfigChange } = useRetryConfig(id)
|
||||||
|
const { retry_config } = data
|
||||||
|
|
||||||
|
const handleRetryEnabledChange = (value: boolean) => {
|
||||||
|
handleRetryConfigChange({
|
||||||
|
retry_enabled: value,
|
||||||
|
max_retries: retry_config?.max_retries || 3,
|
||||||
|
retry_interval: retry_config?.retry_interval || 1000,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
const handleMaxRetriesChange = (value: number) => {
|
||||||
|
if (value > 10)
|
||||||
|
value = 10
|
||||||
|
else if (value < 1)
|
||||||
|
value = 1
|
||||||
|
handleRetryConfigChange({
|
||||||
|
retry_enabled: true,
|
||||||
|
max_retries: value,
|
||||||
|
retry_interval: retry_config?.retry_interval || 1000,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
const handleRetryIntervalChange = (value: number) => {
|
||||||
|
if (value > 5000)
|
||||||
|
value = 5000
|
||||||
|
else if (value < 100)
|
||||||
|
value = 100
|
||||||
|
handleRetryConfigChange({
|
||||||
|
retry_enabled: true,
|
||||||
|
max_retries: retry_config?.max_retries || 3,
|
||||||
|
retry_interval: value,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<div className='pt-2'>
|
||||||
|
<div className='flex items-center justify-between px-4 py-2 h-10'>
|
||||||
|
<div className='flex items-center'>
|
||||||
|
<div className='mr-0.5 system-sm-semibold-uppercase text-text-secondary'>{t('workflow.nodes.common.retry.retryOnFailure')}</div>
|
||||||
|
</div>
|
||||||
|
<Switch
|
||||||
|
defaultValue={retry_config?.retry_enabled}
|
||||||
|
onChange={v => handleRetryEnabledChange(v)}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
{
|
||||||
|
retry_config?.retry_enabled && (
|
||||||
|
<div className='px-4 pb-2'>
|
||||||
|
<div className='flex items-center mb-1 w-full'>
|
||||||
|
<div className='grow mr-2 system-xs-medium-uppercase'>{t('workflow.nodes.common.retry.maxRetries')}</div>
|
||||||
|
<Slider
|
||||||
|
className='mr-3 w-[108px]'
|
||||||
|
value={retry_config?.max_retries || 3}
|
||||||
|
onChange={handleMaxRetriesChange}
|
||||||
|
min={1}
|
||||||
|
max={10}
|
||||||
|
/>
|
||||||
|
<Input
|
||||||
|
type='number'
|
||||||
|
wrapperClassName='w-[80px]'
|
||||||
|
value={retry_config?.max_retries || 3}
|
||||||
|
onChange={e => handleMaxRetriesChange(e.target.value as any)}
|
||||||
|
min={1}
|
||||||
|
max={10}
|
||||||
|
unit={t('workflow.nodes.common.retry.times') || ''}
|
||||||
|
className={s.input}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<div className='flex items-center'>
|
||||||
|
<div className='grow mr-2 system-xs-medium-uppercase'>{t('workflow.nodes.common.retry.retryInterval')}</div>
|
||||||
|
<Slider
|
||||||
|
className='mr-3 w-[108px]'
|
||||||
|
value={retry_config?.retry_interval || 1000}
|
||||||
|
onChange={handleRetryIntervalChange}
|
||||||
|
min={100}
|
||||||
|
max={5000}
|
||||||
|
/>
|
||||||
|
<Input
|
||||||
|
type='number'
|
||||||
|
wrapperClassName='w-[80px]'
|
||||||
|
value={retry_config?.retry_interval || 1000}
|
||||||
|
onChange={e => handleRetryIntervalChange(e.target.value as any)}
|
||||||
|
min={100}
|
||||||
|
max={5000}
|
||||||
|
unit={t('workflow.nodes.common.retry.ms') || ''}
|
||||||
|
className={s.input}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
</div>
|
||||||
|
<Split className='mx-4 mt-2' />
|
||||||
|
</>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
export default RetryOnPanel
|
@ -0,0 +1,5 @@
|
|||||||
|
.input::-webkit-inner-spin-button,
|
||||||
|
.input::-webkit-outer-spin-button {
|
||||||
|
-webkit-appearance: none;
|
||||||
|
margin: 0;
|
||||||
|
}
|
@ -0,0 +1,5 @@
|
|||||||
|
export type WorkflowRetryConfig = {
|
||||||
|
max_retries: number
|
||||||
|
retry_interval: number
|
||||||
|
retry_enabled: boolean
|
||||||
|
}
|
@ -25,7 +25,10 @@ import {
|
|||||||
useNodesReadOnly,
|
useNodesReadOnly,
|
||||||
useToolIcon,
|
useToolIcon,
|
||||||
} from '../../hooks'
|
} from '../../hooks'
|
||||||
import { hasErrorHandleNode } from '../../utils'
|
import {
|
||||||
|
hasErrorHandleNode,
|
||||||
|
hasRetryNode,
|
||||||
|
} from '../../utils'
|
||||||
import { useNodeIterationInteractions } from '../iteration/use-interactions'
|
import { useNodeIterationInteractions } from '../iteration/use-interactions'
|
||||||
import type { IterationNodeType } from '../iteration/types'
|
import type { IterationNodeType } from '../iteration/types'
|
||||||
import {
|
import {
|
||||||
@ -35,6 +38,7 @@ import {
|
|||||||
import NodeResizer from './components/node-resizer'
|
import NodeResizer from './components/node-resizer'
|
||||||
import NodeControl from './components/node-control'
|
import NodeControl from './components/node-control'
|
||||||
import ErrorHandleOnNode from './components/error-handle/error-handle-on-node'
|
import ErrorHandleOnNode from './components/error-handle/error-handle-on-node'
|
||||||
|
import RetryOnNode from './components/retry/retry-on-node'
|
||||||
import AddVariablePopupWithPosition from './components/add-variable-popup-with-position'
|
import AddVariablePopupWithPosition from './components/add-variable-popup-with-position'
|
||||||
import cn from '@/utils/classnames'
|
import cn from '@/utils/classnames'
|
||||||
import BlockIcon from '@/app/components/workflow/block-icon'
|
import BlockIcon from '@/app/components/workflow/block-icon'
|
||||||
@ -237,6 +241,14 @@ const BaseNode: FC<BaseNodeProps> = ({
|
|||||||
</div>
|
</div>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
{
|
||||||
|
hasRetryNode(data.type) && (
|
||||||
|
<RetryOnNode
|
||||||
|
id={id}
|
||||||
|
data={data}
|
||||||
|
/>
|
||||||
|
)
|
||||||
|
}
|
||||||
{
|
{
|
||||||
hasErrorHandleNode(data.type) && (
|
hasErrorHandleNode(data.type) && (
|
||||||
<ErrorHandleOnNode
|
<ErrorHandleOnNode
|
||||||
|
@ -21,9 +21,11 @@ import {
|
|||||||
TitleInput,
|
TitleInput,
|
||||||
} from './components/title-description-input'
|
} from './components/title-description-input'
|
||||||
import ErrorHandleOnPanel from './components/error-handle/error-handle-on-panel'
|
import ErrorHandleOnPanel from './components/error-handle/error-handle-on-panel'
|
||||||
|
import RetryOnPanel from './components/retry/retry-on-panel'
|
||||||
import { useResizePanel } from './hooks/use-resize-panel'
|
import { useResizePanel } from './hooks/use-resize-panel'
|
||||||
import cn from '@/utils/classnames'
|
import cn from '@/utils/classnames'
|
||||||
import BlockIcon from '@/app/components/workflow/block-icon'
|
import BlockIcon from '@/app/components/workflow/block-icon'
|
||||||
|
import Split from '@/app/components/workflow/nodes/_base/components/split'
|
||||||
import {
|
import {
|
||||||
WorkflowHistoryEvent,
|
WorkflowHistoryEvent,
|
||||||
useAvailableBlocks,
|
useAvailableBlocks,
|
||||||
@ -38,6 +40,7 @@ import {
|
|||||||
import {
|
import {
|
||||||
canRunBySingle,
|
canRunBySingle,
|
||||||
hasErrorHandleNode,
|
hasErrorHandleNode,
|
||||||
|
hasRetryNode,
|
||||||
} from '@/app/components/workflow/utils'
|
} from '@/app/components/workflow/utils'
|
||||||
import Tooltip from '@/app/components/base/tooltip'
|
import Tooltip from '@/app/components/base/tooltip'
|
||||||
import type { Node } from '@/app/components/workflow/types'
|
import type { Node } from '@/app/components/workflow/types'
|
||||||
@ -168,6 +171,15 @@ const BasePanel: FC<BasePanelProps> = ({
|
|||||||
<div>
|
<div>
|
||||||
{cloneElement(children, { id, data })}
|
{cloneElement(children, { id, data })}
|
||||||
</div>
|
</div>
|
||||||
|
<Split />
|
||||||
|
{
|
||||||
|
hasRetryNode(data.type) && (
|
||||||
|
<RetryOnPanel
|
||||||
|
id={id}
|
||||||
|
data={data}
|
||||||
|
/>
|
||||||
|
)
|
||||||
|
}
|
||||||
{
|
{
|
||||||
hasErrorHandleNode(data.type) && (
|
hasErrorHandleNode(data.type) && (
|
||||||
<ErrorHandleOnPanel
|
<ErrorHandleOnPanel
|
||||||
|
@ -2,7 +2,10 @@ import { BlockEnum } from '../../types'
|
|||||||
import type { NodeDefault } from '../../types'
|
import type { NodeDefault } from '../../types'
|
||||||
import { AuthorizationType, BodyType, Method } from './types'
|
import { AuthorizationType, BodyType, Method } from './types'
|
||||||
import type { BodyPayload, HttpNodeType } from './types'
|
import type { BodyPayload, HttpNodeType } from './types'
|
||||||
import { ALL_CHAT_AVAILABLE_BLOCKS, ALL_COMPLETION_AVAILABLE_BLOCKS } from '@/app/components/workflow/constants'
|
import {
|
||||||
|
ALL_CHAT_AVAILABLE_BLOCKS,
|
||||||
|
ALL_COMPLETION_AVAILABLE_BLOCKS,
|
||||||
|
} from '@/app/components/workflow/constants'
|
||||||
|
|
||||||
const nodeDefault: NodeDefault<HttpNodeType> = {
|
const nodeDefault: NodeDefault<HttpNodeType> = {
|
||||||
defaultValue: {
|
defaultValue: {
|
||||||
@ -24,6 +27,11 @@ const nodeDefault: NodeDefault<HttpNodeType> = {
|
|||||||
max_read_timeout: 0,
|
max_read_timeout: 0,
|
||||||
max_write_timeout: 0,
|
max_write_timeout: 0,
|
||||||
},
|
},
|
||||||
|
retry_config: {
|
||||||
|
retry_enabled: true,
|
||||||
|
max_retries: 3,
|
||||||
|
retry_interval: 100,
|
||||||
|
},
|
||||||
},
|
},
|
||||||
getAvailablePrevNodes(isChatMode: boolean) {
|
getAvailablePrevNodes(isChatMode: boolean) {
|
||||||
const nodes = isChatMode
|
const nodes = isChatMode
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
import type { FC } from 'react'
|
import type { FC } from 'react'
|
||||||
import React from 'react'
|
import { memo } from 'react'
|
||||||
import { useTranslation } from 'react-i18next'
|
import { useTranslation } from 'react-i18next'
|
||||||
import useConfig from './use-config'
|
import useConfig from './use-config'
|
||||||
import ApiInput from './components/api-input'
|
import ApiInput from './components/api-input'
|
||||||
@ -18,6 +18,7 @@ import { FileArrow01 } from '@/app/components/base/icons/src/vender/line/files'
|
|||||||
import type { NodePanelProps } from '@/app/components/workflow/types'
|
import type { NodePanelProps } from '@/app/components/workflow/types'
|
||||||
import BeforeRunForm from '@/app/components/workflow/nodes/_base/components/before-run-form'
|
import BeforeRunForm from '@/app/components/workflow/nodes/_base/components/before-run-form'
|
||||||
import ResultPanel from '@/app/components/workflow/run/result-panel'
|
import ResultPanel from '@/app/components/workflow/run/result-panel'
|
||||||
|
import { useRetryDetailShowInSingleRun } from '@/app/components/workflow/nodes/_base/components/retry/hooks'
|
||||||
|
|
||||||
const i18nPrefix = 'workflow.nodes.http'
|
const i18nPrefix = 'workflow.nodes.http'
|
||||||
|
|
||||||
@ -60,6 +61,10 @@ const Panel: FC<NodePanelProps<HttpNodeType>> = ({
|
|||||||
hideCurlPanel,
|
hideCurlPanel,
|
||||||
handleCurlImport,
|
handleCurlImport,
|
||||||
} = useConfig(id, data)
|
} = useConfig(id, data)
|
||||||
|
const {
|
||||||
|
retryDetails,
|
||||||
|
handleRetryDetailsChange,
|
||||||
|
} = useRetryDetailShowInSingleRun()
|
||||||
// To prevent prompt editor in body not update data.
|
// To prevent prompt editor in body not update data.
|
||||||
if (!isDataReady)
|
if (!isDataReady)
|
||||||
return null
|
return null
|
||||||
@ -181,6 +186,7 @@ const Panel: FC<NodePanelProps<HttpNodeType>> = ({
|
|||||||
{isShowSingleRun && (
|
{isShowSingleRun && (
|
||||||
<BeforeRunForm
|
<BeforeRunForm
|
||||||
nodeName={inputs.title}
|
nodeName={inputs.title}
|
||||||
|
nodeType={inputs.type}
|
||||||
onHide={hideSingleRun}
|
onHide={hideSingleRun}
|
||||||
forms={[
|
forms={[
|
||||||
{
|
{
|
||||||
@ -192,7 +198,9 @@ const Panel: FC<NodePanelProps<HttpNodeType>> = ({
|
|||||||
runningStatus={runningStatus}
|
runningStatus={runningStatus}
|
||||||
onRun={handleRun}
|
onRun={handleRun}
|
||||||
onStop={handleStop}
|
onStop={handleStop}
|
||||||
result={<ResultPanel {...runResult} showSteps={false} />}
|
retryDetails={retryDetails}
|
||||||
|
onRetryDetailBack={handleRetryDetailsChange}
|
||||||
|
result={<ResultPanel {...runResult} showSteps={false} onShowRetryDetail={handleRetryDetailsChange} />}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
{(isShowCurlPanel && !readOnly) && (
|
{(isShowCurlPanel && !readOnly) && (
|
||||||
@ -207,4 +215,4 @@ const Panel: FC<NodePanelProps<HttpNodeType>> = ({
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
export default React.memo(Panel)
|
export default memo(Panel)
|
||||||
|
@ -19,6 +19,7 @@ import type { Props as FormProps } from '@/app/components/workflow/nodes/_base/c
|
|||||||
import ResultPanel from '@/app/components/workflow/run/result-panel'
|
import ResultPanel from '@/app/components/workflow/run/result-panel'
|
||||||
import Tooltip from '@/app/components/base/tooltip'
|
import Tooltip from '@/app/components/base/tooltip'
|
||||||
import Editor from '@/app/components/workflow/nodes/_base/components/prompt/editor'
|
import Editor from '@/app/components/workflow/nodes/_base/components/prompt/editor'
|
||||||
|
import { useRetryDetailShowInSingleRun } from '@/app/components/workflow/nodes/_base/components/retry/hooks'
|
||||||
|
|
||||||
const i18nPrefix = 'workflow.nodes.llm'
|
const i18nPrefix = 'workflow.nodes.llm'
|
||||||
|
|
||||||
@ -69,6 +70,10 @@ const Panel: FC<NodePanelProps<LLMNodeType>> = ({
|
|||||||
runResult,
|
runResult,
|
||||||
filterJinjia2InputVar,
|
filterJinjia2InputVar,
|
||||||
} = useConfig(id, data)
|
} = useConfig(id, data)
|
||||||
|
const {
|
||||||
|
retryDetails,
|
||||||
|
handleRetryDetailsChange,
|
||||||
|
} = useRetryDetailShowInSingleRun()
|
||||||
|
|
||||||
const model = inputs.model
|
const model = inputs.model
|
||||||
|
|
||||||
@ -282,12 +287,15 @@ const Panel: FC<NodePanelProps<LLMNodeType>> = ({
|
|||||||
{isShowSingleRun && (
|
{isShowSingleRun && (
|
||||||
<BeforeRunForm
|
<BeforeRunForm
|
||||||
nodeName={inputs.title}
|
nodeName={inputs.title}
|
||||||
|
nodeType={inputs.type}
|
||||||
onHide={hideSingleRun}
|
onHide={hideSingleRun}
|
||||||
forms={singleRunForms}
|
forms={singleRunForms}
|
||||||
runningStatus={runningStatus}
|
runningStatus={runningStatus}
|
||||||
onRun={handleRun}
|
onRun={handleRun}
|
||||||
onStop={handleStop}
|
onStop={handleStop}
|
||||||
result={<ResultPanel {...runResult} showSteps={false} />}
|
retryDetails={retryDetails}
|
||||||
|
onRetryDetailBack={handleRetryDetailsChange}
|
||||||
|
result={<ResultPanel {...runResult} showSteps={false} onShowRetryDetail={handleRetryDetailsChange} />}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
|
@ -14,6 +14,8 @@ import Loading from '@/app/components/base/loading'
|
|||||||
import BeforeRunForm from '@/app/components/workflow/nodes/_base/components/before-run-form'
|
import BeforeRunForm from '@/app/components/workflow/nodes/_base/components/before-run-form'
|
||||||
import OutputVars, { VarItem } from '@/app/components/workflow/nodes/_base/components/output-vars'
|
import OutputVars, { VarItem } from '@/app/components/workflow/nodes/_base/components/output-vars'
|
||||||
import ResultPanel from '@/app/components/workflow/run/result-panel'
|
import ResultPanel from '@/app/components/workflow/run/result-panel'
|
||||||
|
import { useRetryDetailShowInSingleRun } from '@/app/components/workflow/nodes/_base/components/retry/hooks'
|
||||||
|
import { useToolIcon } from '@/app/components/workflow/hooks'
|
||||||
|
|
||||||
const i18nPrefix = 'workflow.nodes.tool'
|
const i18nPrefix = 'workflow.nodes.tool'
|
||||||
|
|
||||||
@ -48,6 +50,11 @@ const Panel: FC<NodePanelProps<ToolNodeType>> = ({
|
|||||||
handleStop,
|
handleStop,
|
||||||
runResult,
|
runResult,
|
||||||
} = useConfig(id, data)
|
} = useConfig(id, data)
|
||||||
|
const toolIcon = useToolIcon(data)
|
||||||
|
const {
|
||||||
|
retryDetails,
|
||||||
|
handleRetryDetailsChange,
|
||||||
|
} = useRetryDetailShowInSingleRun()
|
||||||
|
|
||||||
if (isLoading) {
|
if (isLoading) {
|
||||||
return <div className='flex h-[200px] items-center justify-center'>
|
return <div className='flex h-[200px] items-center justify-center'>
|
||||||
@ -143,12 +150,16 @@ const Panel: FC<NodePanelProps<ToolNodeType>> = ({
|
|||||||
{isShowSingleRun && (
|
{isShowSingleRun && (
|
||||||
<BeforeRunForm
|
<BeforeRunForm
|
||||||
nodeName={inputs.title}
|
nodeName={inputs.title}
|
||||||
|
nodeType={inputs.type}
|
||||||
|
toolIcon={toolIcon}
|
||||||
onHide={hideSingleRun}
|
onHide={hideSingleRun}
|
||||||
forms={singleRunForms}
|
forms={singleRunForms}
|
||||||
runningStatus={runningStatus}
|
runningStatus={runningStatus}
|
||||||
onRun={handleRun}
|
onRun={handleRun}
|
||||||
onStop={handleStop}
|
onStop={handleStop}
|
||||||
result={<ResultPanel {...runResult} showSteps={false} />}
|
retryDetails={retryDetails}
|
||||||
|
onRetryDetailBack={handleRetryDetailsChange}
|
||||||
|
result={<ResultPanel {...runResult} showSteps={false} onShowRetryDetail={handleRetryDetailsChange} />}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
|
@ -27,6 +27,7 @@ import {
|
|||||||
getProcessedFilesFromResponse,
|
getProcessedFilesFromResponse,
|
||||||
} from '@/app/components/base/file-uploader/utils'
|
} from '@/app/components/base/file-uploader/utils'
|
||||||
import type { FileEntity } from '@/app/components/base/file-uploader/types'
|
import type { FileEntity } from '@/app/components/base/file-uploader/types'
|
||||||
|
import type { NodeTracing } from '@/types/workflow'
|
||||||
|
|
||||||
type GetAbortController = (abortController: AbortController) => void
|
type GetAbortController = (abortController: AbortController) => void
|
||||||
type SendCallback = {
|
type SendCallback = {
|
||||||
@ -381,6 +382,28 @@ export const useChat = (
|
|||||||
}
|
}
|
||||||
}))
|
}))
|
||||||
},
|
},
|
||||||
|
onNodeRetry: ({ data }) => {
|
||||||
|
if (data.iteration_id)
|
||||||
|
return
|
||||||
|
|
||||||
|
const currentIndex = responseItem.workflowProcess!.tracing!.findIndex((item) => {
|
||||||
|
if (!item.execution_metadata?.parallel_id)
|
||||||
|
return item.node_id === data.node_id
|
||||||
|
return item.node_id === data.node_id && (item.execution_metadata?.parallel_id === data.execution_metadata?.parallel_id || item.parallel_id === data.execution_metadata?.parallel_id)
|
||||||
|
})
|
||||||
|
if (responseItem.workflowProcess!.tracing[currentIndex].retryDetail)
|
||||||
|
responseItem.workflowProcess!.tracing[currentIndex].retryDetail?.push(data as NodeTracing)
|
||||||
|
else
|
||||||
|
responseItem.workflowProcess!.tracing[currentIndex].retryDetail = [data as NodeTracing]
|
||||||
|
|
||||||
|
handleUpdateChatList(produce(chatListRef.current, (draft) => {
|
||||||
|
const currentIndex = draft.findIndex(item => item.id === responseItem.id)
|
||||||
|
draft[currentIndex] = {
|
||||||
|
...draft[currentIndex],
|
||||||
|
...responseItem,
|
||||||
|
}
|
||||||
|
}))
|
||||||
|
},
|
||||||
onNodeFinished: ({ data }) => {
|
onNodeFinished: ({ data }) => {
|
||||||
if (data.iteration_id)
|
if (data.iteration_id)
|
||||||
return
|
return
|
||||||
@ -394,6 +417,9 @@ export const useChat = (
|
|||||||
...(responseItem.workflowProcess!.tracing[currentIndex]?.extras
|
...(responseItem.workflowProcess!.tracing[currentIndex]?.extras
|
||||||
? { extras: responseItem.workflowProcess!.tracing[currentIndex].extras }
|
? { extras: responseItem.workflowProcess!.tracing[currentIndex].extras }
|
||||||
: {}),
|
: {}),
|
||||||
|
...(responseItem.workflowProcess!.tracing[currentIndex]?.retryDetail
|
||||||
|
? { retryDetail: responseItem.workflowProcess!.tracing[currentIndex].retryDetail }
|
||||||
|
: {}),
|
||||||
...data,
|
...data,
|
||||||
} as any
|
} as any
|
||||||
handleUpdateChatList(produce(chatListRef.current, (draft) => {
|
handleUpdateChatList(produce(chatListRef.current, (draft) => {
|
||||||
|
@ -25,6 +25,7 @@ import {
|
|||||||
import { SimpleBtn } from '../../app/text-generate/item'
|
import { SimpleBtn } from '../../app/text-generate/item'
|
||||||
import Toast from '../../base/toast'
|
import Toast from '../../base/toast'
|
||||||
import IterationResultPanel from '../run/iteration-result-panel'
|
import IterationResultPanel from '../run/iteration-result-panel'
|
||||||
|
import RetryResultPanel from '../run/retry-result-panel'
|
||||||
import InputsPanel from './inputs-panel'
|
import InputsPanel from './inputs-panel'
|
||||||
import cn from '@/utils/classnames'
|
import cn from '@/utils/classnames'
|
||||||
import Loading from '@/app/components/base/loading'
|
import Loading from '@/app/components/base/loading'
|
||||||
@ -53,11 +54,16 @@ const WorkflowPreview = () => {
|
|||||||
}, [workflowRunningData])
|
}, [workflowRunningData])
|
||||||
|
|
||||||
const [iterationRunResult, setIterationRunResult] = useState<NodeTracing[][]>([])
|
const [iterationRunResult, setIterationRunResult] = useState<NodeTracing[][]>([])
|
||||||
|
const [retryRunResult, setRetryRunResult] = useState<NodeTracing[]>([])
|
||||||
const [iterDurationMap, setIterDurationMap] = useState<IterationDurationMap>({})
|
const [iterDurationMap, setIterDurationMap] = useState<IterationDurationMap>({})
|
||||||
const [isShowIterationDetail, {
|
const [isShowIterationDetail, {
|
||||||
setTrue: doShowIterationDetail,
|
setTrue: doShowIterationDetail,
|
||||||
setFalse: doHideIterationDetail,
|
setFalse: doHideIterationDetail,
|
||||||
}] = useBoolean(false)
|
}] = useBoolean(false)
|
||||||
|
const [isShowRetryDetail, {
|
||||||
|
setTrue: doShowRetryDetail,
|
||||||
|
setFalse: doHideRetryDetail,
|
||||||
|
}] = useBoolean(false)
|
||||||
|
|
||||||
const handleShowIterationDetail = useCallback((detail: NodeTracing[][], iterationDurationMap: IterationDurationMap) => {
|
const handleShowIterationDetail = useCallback((detail: NodeTracing[][], iterationDurationMap: IterationDurationMap) => {
|
||||||
setIterDurationMap(iterationDurationMap)
|
setIterDurationMap(iterationDurationMap)
|
||||||
@ -65,6 +71,11 @@ const WorkflowPreview = () => {
|
|||||||
doShowIterationDetail()
|
doShowIterationDetail()
|
||||||
}, [doShowIterationDetail])
|
}, [doShowIterationDetail])
|
||||||
|
|
||||||
|
const handleRetryDetail = useCallback((detail: NodeTracing[]) => {
|
||||||
|
setRetryRunResult(detail)
|
||||||
|
doShowRetryDetail()
|
||||||
|
}, [doShowRetryDetail])
|
||||||
|
|
||||||
if (isShowIterationDetail) {
|
if (isShowIterationDetail) {
|
||||||
return (
|
return (
|
||||||
<div className={`
|
<div className={`
|
||||||
@ -201,11 +212,12 @@ const WorkflowPreview = () => {
|
|||||||
<Loading />
|
<Loading />
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
{currentTab === 'TRACING' && (
|
{currentTab === 'TRACING' && !isShowRetryDetail && (
|
||||||
<TracingPanel
|
<TracingPanel
|
||||||
className='bg-background-section-burn'
|
className='bg-background-section-burn'
|
||||||
list={workflowRunningData?.tracing || []}
|
list={workflowRunningData?.tracing || []}
|
||||||
onShowIterationDetail={handleShowIterationDetail}
|
onShowIterationDetail={handleShowIterationDetail}
|
||||||
|
onShowRetryDetail={handleRetryDetail}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
{currentTab === 'TRACING' && !workflowRunningData?.tracing?.length && (
|
{currentTab === 'TRACING' && !workflowRunningData?.tracing?.length && (
|
||||||
@ -213,7 +225,14 @@ const WorkflowPreview = () => {
|
|||||||
<Loading />
|
<Loading />
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
|
{
|
||||||
|
currentTab === 'TRACING' && isShowRetryDetail && (
|
||||||
|
<RetryResultPanel
|
||||||
|
list={retryRunResult}
|
||||||
|
onBack={doHideRetryDetail}
|
||||||
|
/>
|
||||||
|
)
|
||||||
|
}
|
||||||
</div>
|
</div>
|
||||||
</>
|
</>
|
||||||
)}
|
)}
|
||||||
|
@ -9,6 +9,7 @@ import OutputPanel from './output-panel'
|
|||||||
import ResultPanel from './result-panel'
|
import ResultPanel from './result-panel'
|
||||||
import TracingPanel from './tracing-panel'
|
import TracingPanel from './tracing-panel'
|
||||||
import IterationResultPanel from './iteration-result-panel'
|
import IterationResultPanel from './iteration-result-panel'
|
||||||
|
import RetryResultPanel from './retry-result-panel'
|
||||||
import cn from '@/utils/classnames'
|
import cn from '@/utils/classnames'
|
||||||
import { ToastContext } from '@/app/components/base/toast'
|
import { ToastContext } from '@/app/components/base/toast'
|
||||||
import Loading from '@/app/components/base/loading'
|
import Loading from '@/app/components/base/loading'
|
||||||
@ -107,6 +108,18 @@ const RunPanel: FC<RunProps> = ({ hideResult, activeTab = 'RESULT', runID, getRe
|
|||||||
const processNonIterationNode = (item: NodeTracing) => {
|
const processNonIterationNode = (item: NodeTracing) => {
|
||||||
const { execution_metadata } = item
|
const { execution_metadata } = item
|
||||||
if (!execution_metadata?.iteration_id) {
|
if (!execution_metadata?.iteration_id) {
|
||||||
|
if (item.status === 'retry') {
|
||||||
|
const retryNode = result.find(node => node.node_id === item.node_id)
|
||||||
|
|
||||||
|
if (retryNode) {
|
||||||
|
if (retryNode?.retryDetail)
|
||||||
|
retryNode.retryDetail.push(item)
|
||||||
|
else
|
||||||
|
retryNode.retryDetail = [item]
|
||||||
|
}
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
||||||
result.push(item)
|
result.push(item)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@ -181,10 +194,15 @@ const RunPanel: FC<RunProps> = ({ hideResult, activeTab = 'RESULT', runID, getRe
|
|||||||
|
|
||||||
const [iterationRunResult, setIterationRunResult] = useState<NodeTracing[][]>([])
|
const [iterationRunResult, setIterationRunResult] = useState<NodeTracing[][]>([])
|
||||||
const [iterDurationMap, setIterDurationMap] = useState<IterationDurationMap>({})
|
const [iterDurationMap, setIterDurationMap] = useState<IterationDurationMap>({})
|
||||||
|
const [retryRunResult, setRetryRunResult] = useState<NodeTracing[]>([])
|
||||||
const [isShowIterationDetail, {
|
const [isShowIterationDetail, {
|
||||||
setTrue: doShowIterationDetail,
|
setTrue: doShowIterationDetail,
|
||||||
setFalse: doHideIterationDetail,
|
setFalse: doHideIterationDetail,
|
||||||
}] = useBoolean(false)
|
}] = useBoolean(false)
|
||||||
|
const [isShowRetryDetail, {
|
||||||
|
setTrue: doShowRetryDetail,
|
||||||
|
setFalse: doHideRetryDetail,
|
||||||
|
}] = useBoolean(false)
|
||||||
|
|
||||||
const handleShowIterationDetail = useCallback((detail: NodeTracing[][], iterDurationMap: IterationDurationMap) => {
|
const handleShowIterationDetail = useCallback((detail: NodeTracing[][], iterDurationMap: IterationDurationMap) => {
|
||||||
setIterationRunResult(detail)
|
setIterationRunResult(detail)
|
||||||
@ -192,6 +210,11 @@ const RunPanel: FC<RunProps> = ({ hideResult, activeTab = 'RESULT', runID, getRe
|
|||||||
setIterDurationMap(iterDurationMap)
|
setIterDurationMap(iterDurationMap)
|
||||||
}, [doShowIterationDetail, setIterationRunResult, setIterDurationMap])
|
}, [doShowIterationDetail, setIterationRunResult, setIterDurationMap])
|
||||||
|
|
||||||
|
const handleShowRetryDetail = useCallback((detail: NodeTracing[]) => {
|
||||||
|
setRetryRunResult(detail)
|
||||||
|
doShowRetryDetail()
|
||||||
|
}, [doShowRetryDetail, setRetryRunResult])
|
||||||
|
|
||||||
if (isShowIterationDetail) {
|
if (isShowIterationDetail) {
|
||||||
return (
|
return (
|
||||||
<div className='grow relative flex flex-col'>
|
<div className='grow relative flex flex-col'>
|
||||||
@ -261,13 +284,22 @@ const RunPanel: FC<RunProps> = ({ hideResult, activeTab = 'RESULT', runID, getRe
|
|||||||
exceptionCounts={runDetail.exceptions_count}
|
exceptionCounts={runDetail.exceptions_count}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
{!loading && currentTab === 'TRACING' && (
|
{!loading && currentTab === 'TRACING' && !isShowRetryDetail && (
|
||||||
<TracingPanel
|
<TracingPanel
|
||||||
className='bg-background-section-burn'
|
className='bg-background-section-burn'
|
||||||
list={list}
|
list={list}
|
||||||
onShowIterationDetail={handleShowIterationDetail}
|
onShowIterationDetail={handleShowIterationDetail}
|
||||||
|
onShowRetryDetail={handleShowRetryDetail}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
|
{
|
||||||
|
!loading && currentTab === 'TRACING' && isShowRetryDetail && (
|
||||||
|
<RetryResultPanel
|
||||||
|
list={retryRunResult}
|
||||||
|
onBack={doHideRetryDetail}
|
||||||
|
/>
|
||||||
|
)
|
||||||
|
}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
)
|
)
|
||||||
|
@ -8,6 +8,7 @@ import {
|
|||||||
RiCheckboxCircleFill,
|
RiCheckboxCircleFill,
|
||||||
RiErrorWarningLine,
|
RiErrorWarningLine,
|
||||||
RiLoader2Line,
|
RiLoader2Line,
|
||||||
|
RiRestartFill,
|
||||||
} from '@remixicon/react'
|
} from '@remixicon/react'
|
||||||
import BlockIcon from '../block-icon'
|
import BlockIcon from '../block-icon'
|
||||||
import { BlockEnum } from '../types'
|
import { BlockEnum } from '../types'
|
||||||
@ -20,6 +21,7 @@ import Button from '@/app/components/base/button'
|
|||||||
import { CodeLanguage } from '@/app/components/workflow/nodes/code/types'
|
import { CodeLanguage } from '@/app/components/workflow/nodes/code/types'
|
||||||
import type { IterationDurationMap, 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 ErrorHandleTip from '@/app/components/workflow/nodes/_base/components/error-handle/error-handle-tip'
|
||||||
|
import { hasRetryNode } from '@/app/components/workflow/utils'
|
||||||
|
|
||||||
type Props = {
|
type Props = {
|
||||||
className?: string
|
className?: string
|
||||||
@ -28,8 +30,10 @@ type Props = {
|
|||||||
hideInfo?: boolean
|
hideInfo?: boolean
|
||||||
hideProcessDetail?: boolean
|
hideProcessDetail?: boolean
|
||||||
onShowIterationDetail?: (detail: NodeTracing[][], iterDurationMap: IterationDurationMap) => void
|
onShowIterationDetail?: (detail: NodeTracing[][], iterDurationMap: IterationDurationMap) => void
|
||||||
|
onShowRetryDetail?: (detail: NodeTracing[]) => void
|
||||||
notShowIterationNav?: boolean
|
notShowIterationNav?: boolean
|
||||||
justShowIterationNavArrow?: boolean
|
justShowIterationNavArrow?: boolean
|
||||||
|
justShowRetryNavArrow?: boolean
|
||||||
}
|
}
|
||||||
|
|
||||||
const NodePanel: FC<Props> = ({
|
const NodePanel: FC<Props> = ({
|
||||||
@ -39,6 +43,7 @@ const NodePanel: FC<Props> = ({
|
|||||||
hideInfo = false,
|
hideInfo = false,
|
||||||
hideProcessDetail,
|
hideProcessDetail,
|
||||||
onShowIterationDetail,
|
onShowIterationDetail,
|
||||||
|
onShowRetryDetail,
|
||||||
notShowIterationNav,
|
notShowIterationNav,
|
||||||
justShowIterationNavArrow,
|
justShowIterationNavArrow,
|
||||||
}) => {
|
}) => {
|
||||||
@ -88,11 +93,17 @@ const NodePanel: FC<Props> = ({
|
|||||||
}, [nodeInfo.expand, setCollapseState])
|
}, [nodeInfo.expand, setCollapseState])
|
||||||
|
|
||||||
const isIterationNode = nodeInfo.node_type === BlockEnum.Iteration
|
const isIterationNode = nodeInfo.node_type === BlockEnum.Iteration
|
||||||
|
const isRetryNode = hasRetryNode(nodeInfo.node_type) && nodeInfo.retryDetail
|
||||||
const handleOnShowIterationDetail = (e: React.MouseEvent<HTMLButtonElement>) => {
|
const handleOnShowIterationDetail = (e: React.MouseEvent<HTMLButtonElement>) => {
|
||||||
e.stopPropagation()
|
e.stopPropagation()
|
||||||
e.nativeEvent.stopImmediatePropagation()
|
e.nativeEvent.stopImmediatePropagation()
|
||||||
onShowIterationDetail?.(nodeInfo.details || [], nodeInfo?.iterDurationMap || nodeInfo.execution_metadata?.iteration_duration_map || {})
|
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 (
|
return (
|
||||||
<div className={cn('px-2 py-1', className)}>
|
<div className={cn('px-2 py-1', className)}>
|
||||||
<div className='group transition-all bg-background-default border border-components-panel-border rounded-[10px] shadow-xs hover:shadow-md'>
|
<div className='group transition-all bg-background-default border border-components-panel-border rounded-[10px] shadow-xs hover:shadow-md'>
|
||||||
@ -169,6 +180,19 @@ const NodePanel: FC<Props> = ({
|
|||||||
<Split className='mt-2' />
|
<Split className='mt-2' />
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
|
{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>
|
||||||
|
)}
|
||||||
<div className={cn('mb-1', hideInfo && '!px-2 !py-0.5')}>
|
<div className={cn('mb-1', hideInfo && '!px-2 !py-0.5')}>
|
||||||
{(nodeInfo.status === 'stopped') && (
|
{(nodeInfo.status === 'stopped') && (
|
||||||
<StatusContainer status='stopped'>
|
<StatusContainer status='stopped'>
|
||||||
|
@ -1,11 +1,17 @@
|
|||||||
'use client'
|
'use client'
|
||||||
import type { FC } from 'react'
|
import type { FC } from 'react'
|
||||||
import { useTranslation } from 'react-i18next'
|
import { useTranslation } from 'react-i18next'
|
||||||
|
import {
|
||||||
|
RiArrowRightSLine,
|
||||||
|
RiRestartFill,
|
||||||
|
} from '@remixicon/react'
|
||||||
import StatusPanel from './status'
|
import StatusPanel from './status'
|
||||||
import MetaData from './meta'
|
import MetaData from './meta'
|
||||||
import CodeEditor from '@/app/components/workflow/nodes/_base/components/editor/code-editor'
|
import CodeEditor from '@/app/components/workflow/nodes/_base/components/editor/code-editor'
|
||||||
import { CodeLanguage } from '@/app/components/workflow/nodes/code/types'
|
import { CodeLanguage } from '@/app/components/workflow/nodes/code/types'
|
||||||
import ErrorHandleTip from '@/app/components/workflow/nodes/_base/components/error-handle/error-handle-tip'
|
import ErrorHandleTip from '@/app/components/workflow/nodes/_base/components/error-handle/error-handle-tip'
|
||||||
|
import type { NodeTracing } from '@/types/workflow'
|
||||||
|
import Button from '@/app/components/base/button'
|
||||||
|
|
||||||
type ResultPanelProps = {
|
type ResultPanelProps = {
|
||||||
inputs?: string
|
inputs?: string
|
||||||
@ -22,6 +28,8 @@ type ResultPanelProps = {
|
|||||||
showSteps?: boolean
|
showSteps?: boolean
|
||||||
exceptionCounts?: number
|
exceptionCounts?: number
|
||||||
execution_metadata?: any
|
execution_metadata?: any
|
||||||
|
retry_events?: NodeTracing[]
|
||||||
|
onShowRetryDetail?: (retries: NodeTracing[]) => void
|
||||||
}
|
}
|
||||||
|
|
||||||
const ResultPanel: FC<ResultPanelProps> = ({
|
const ResultPanel: FC<ResultPanelProps> = ({
|
||||||
@ -38,8 +46,11 @@ const ResultPanel: FC<ResultPanelProps> = ({
|
|||||||
showSteps,
|
showSteps,
|
||||||
exceptionCounts,
|
exceptionCounts,
|
||||||
execution_metadata,
|
execution_metadata,
|
||||||
|
retry_events,
|
||||||
|
onShowRetryDetail,
|
||||||
}) => {
|
}) => {
|
||||||
const { t } = useTranslation()
|
const { t } = useTranslation()
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className='bg-components-panel-bg py-2'>
|
<div className='bg-components-panel-bg py-2'>
|
||||||
<div className='px-4 py-2'>
|
<div className='px-4 py-2'>
|
||||||
@ -51,6 +62,23 @@ const ResultPanel: FC<ResultPanelProps> = ({
|
|||||||
exceptionCounts={exceptionCounts}
|
exceptionCounts={exceptionCounts}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
{
|
||||||
|
retry_events?.length && onShowRetryDetail && (
|
||||||
|
<div className='px-4'>
|
||||||
|
<Button
|
||||||
|
className='flex items-center justify-between w-full'
|
||||||
|
variant='tertiary'
|
||||||
|
onClick={() => onShowRetryDetail(retry_events)}
|
||||||
|
>
|
||||||
|
<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: retry_events?.length })}
|
||||||
|
</div>
|
||||||
|
<RiArrowRightSLine className='w-4 h-4 text-components-button-tertiary-text flex-shrink-0' />
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
<div className='px-4 py-2 flex flex-col gap-2'>
|
<div className='px-4 py-2 flex flex-col gap-2'>
|
||||||
<CodeEditor
|
<CodeEditor
|
||||||
readOnly
|
readOnly
|
||||||
|
46
web/app/components/workflow/run/retry-result-panel.tsx
Normal file
46
web/app/components/workflow/run/retry-result-panel.tsx
Normal file
@ -0,0 +1,46 @@
|
|||||||
|
'use client'
|
||||||
|
|
||||||
|
import type { FC } from 'react'
|
||||||
|
import { memo } from 'react'
|
||||||
|
import { useTranslation } from 'react-i18next'
|
||||||
|
import {
|
||||||
|
RiArrowLeftLine,
|
||||||
|
} from '@remixicon/react'
|
||||||
|
import TracingPanel from './tracing-panel'
|
||||||
|
import type { NodeTracing } from '@/types/workflow'
|
||||||
|
|
||||||
|
type Props = {
|
||||||
|
list: NodeTracing[]
|
||||||
|
onBack: () => void
|
||||||
|
}
|
||||||
|
|
||||||
|
const RetryResultPanel: FC<Props> = ({
|
||||||
|
list,
|
||||||
|
onBack,
|
||||||
|
}) => {
|
||||||
|
const { t } = useTranslation()
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div>
|
||||||
|
<div
|
||||||
|
className='flex items-center px-4 h-8 text-text-accent-secondary bg-components-panel-bg system-sm-medium cursor-pointer'
|
||||||
|
onClick={(e) => {
|
||||||
|
e.stopPropagation()
|
||||||
|
e.nativeEvent.stopImmediatePropagation()
|
||||||
|
onBack()
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<RiArrowLeftLine className='mr-1 w-4 h-4' />
|
||||||
|
{t('workflow.singleRun.back')}
|
||||||
|
</div>
|
||||||
|
<TracingPanel
|
||||||
|
list={list.map((item, index) => ({
|
||||||
|
...item,
|
||||||
|
title: `${t('workflow.nodes.common.retry.retry')} ${index + 1}`,
|
||||||
|
}))}
|
||||||
|
className='bg-background-section-burn'
|
||||||
|
/>
|
||||||
|
</div >
|
||||||
|
)
|
||||||
|
}
|
||||||
|
export default memo(RetryResultPanel)
|
@ -21,6 +21,7 @@ import type { IterationDurationMap, NodeTracing } from '@/types/workflow'
|
|||||||
type TracingPanelProps = {
|
type TracingPanelProps = {
|
||||||
list: NodeTracing[]
|
list: NodeTracing[]
|
||||||
onShowIterationDetail?: (detail: NodeTracing[][], iterDurationMap: IterationDurationMap) => void
|
onShowIterationDetail?: (detail: NodeTracing[][], iterDurationMap: IterationDurationMap) => void
|
||||||
|
onShowRetryDetail?: (detail: NodeTracing[]) => void
|
||||||
className?: string
|
className?: string
|
||||||
hideNodeInfo?: boolean
|
hideNodeInfo?: boolean
|
||||||
hideNodeProcessDetail?: boolean
|
hideNodeProcessDetail?: boolean
|
||||||
@ -160,6 +161,7 @@ function buildLogTree(nodes: NodeTracing[], t: (key: string) => string): Tracing
|
|||||||
const TracingPanel: FC<TracingPanelProps> = ({
|
const TracingPanel: FC<TracingPanelProps> = ({
|
||||||
list,
|
list,
|
||||||
onShowIterationDetail,
|
onShowIterationDetail,
|
||||||
|
onShowRetryDetail,
|
||||||
className,
|
className,
|
||||||
hideNodeInfo = false,
|
hideNodeInfo = false,
|
||||||
hideNodeProcessDetail = false,
|
hideNodeProcessDetail = false,
|
||||||
@ -251,7 +253,9 @@ const TracingPanel: FC<TracingPanelProps> = ({
|
|||||||
<NodePanel
|
<NodePanel
|
||||||
nodeInfo={node.data!}
|
nodeInfo={node.data!}
|
||||||
onShowIterationDetail={onShowIterationDetail}
|
onShowIterationDetail={onShowIterationDetail}
|
||||||
|
onShowRetryDetail={onShowRetryDetail}
|
||||||
justShowIterationNavArrow={true}
|
justShowIterationNavArrow={true}
|
||||||
|
justShowRetryNavArrow={true}
|
||||||
hideInfo={hideNodeInfo}
|
hideInfo={hideNodeInfo}
|
||||||
hideProcessDetail={hideNodeProcessDetail}
|
hideProcessDetail={hideNodeProcessDetail}
|
||||||
/>
|
/>
|
||||||
|
@ -13,6 +13,7 @@ import type {
|
|||||||
DefaultValueForm,
|
DefaultValueForm,
|
||||||
ErrorHandleTypeEnum,
|
ErrorHandleTypeEnum,
|
||||||
} from '@/app/components/workflow/nodes/_base/components/error-handle/types'
|
} from '@/app/components/workflow/nodes/_base/components/error-handle/types'
|
||||||
|
import type { WorkflowRetryConfig } from '@/app/components/workflow/nodes/_base/components/retry/types'
|
||||||
|
|
||||||
export enum BlockEnum {
|
export enum BlockEnum {
|
||||||
Start = 'start',
|
Start = 'start',
|
||||||
@ -68,6 +69,7 @@ export type CommonNodeType<T = {}> = {
|
|||||||
_iterationIndex?: number
|
_iterationIndex?: number
|
||||||
_inParallelHovering?: boolean
|
_inParallelHovering?: boolean
|
||||||
_waitingRun?: boolean
|
_waitingRun?: boolean
|
||||||
|
_retryIndex?: number
|
||||||
isInIteration?: boolean
|
isInIteration?: boolean
|
||||||
iteration_id?: string
|
iteration_id?: string
|
||||||
selected?: boolean
|
selected?: boolean
|
||||||
@ -77,6 +79,7 @@ export type CommonNodeType<T = {}> = {
|
|||||||
width?: number
|
width?: number
|
||||||
height?: number
|
height?: number
|
||||||
error_strategy?: ErrorHandleTypeEnum
|
error_strategy?: ErrorHandleTypeEnum
|
||||||
|
retry_config?: WorkflowRetryConfig
|
||||||
default_value?: DefaultValueForm[]
|
default_value?: DefaultValueForm[]
|
||||||
} & T & Partial<Pick<ToolDefaultValue, 'provider_id' | 'provider_type' | 'provider_name' | 'tool_name'>>
|
} & T & Partial<Pick<ToolDefaultValue, 'provider_id' | 'provider_type' | 'provider_name' | 'tool_name'>>
|
||||||
|
|
||||||
@ -293,6 +296,7 @@ export enum NodeRunningStatus {
|
|||||||
Succeeded = 'succeeded',
|
Succeeded = 'succeeded',
|
||||||
Failed = 'failed',
|
Failed = 'failed',
|
||||||
Exception = 'exception',
|
Exception = 'exception',
|
||||||
|
Retry = 'retry',
|
||||||
}
|
}
|
||||||
|
|
||||||
export type OnNodeAdd = (
|
export type OnNodeAdd = (
|
||||||
|
@ -26,6 +26,8 @@ import {
|
|||||||
} from './types'
|
} from './types'
|
||||||
import {
|
import {
|
||||||
CUSTOM_NODE,
|
CUSTOM_NODE,
|
||||||
|
DEFAULT_RETRY_INTERVAL,
|
||||||
|
DEFAULT_RETRY_MAX,
|
||||||
ITERATION_CHILDREN_Z_INDEX,
|
ITERATION_CHILDREN_Z_INDEX,
|
||||||
ITERATION_NODE_Z_INDEX,
|
ITERATION_NODE_Z_INDEX,
|
||||||
NODE_WIDTH_X_OFFSET,
|
NODE_WIDTH_X_OFFSET,
|
||||||
@ -279,6 +281,14 @@ export const initialNodes = (originNodes: Node[], originEdges: Edge[]) => {
|
|||||||
iterationNodeData.error_handle_mode = iterationNodeData.error_handle_mode || ErrorHandleMode.Terminated
|
iterationNodeData.error_handle_mode = iterationNodeData.error_handle_mode || ErrorHandleMode.Terminated
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (node.data.type === BlockEnum.HttpRequest && !node.data.retry_config) {
|
||||||
|
node.data.retry_config = {
|
||||||
|
retry_enabled: true,
|
||||||
|
max_retries: DEFAULT_RETRY_MAX,
|
||||||
|
retry_interval: DEFAULT_RETRY_INTERVAL,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
return node
|
return node
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
@ -797,3 +807,7 @@ export const isExceptionVariable = (variable: string, nodeType?: BlockEnum) => {
|
|||||||
|
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export const hasRetryNode = (nodeType?: BlockEnum) => {
|
||||||
|
return nodeType === BlockEnum.LLM || nodeType === BlockEnum.Tool || nodeType === BlockEnum.HttpRequest || nodeType === BlockEnum.Code
|
||||||
|
}
|
||||||
|
@ -329,6 +329,20 @@ const translation = {
|
|||||||
tip: 'There are {{num}} nodes in the process running abnormally, please go to tracing to check the logs.',
|
tip: 'There are {{num}} nodes in the process running abnormally, please go to tracing to check the logs.',
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
retry: {
|
||||||
|
retry: 'Retry',
|
||||||
|
retryOnFailure: 'retry on failure',
|
||||||
|
maxRetries: 'max retries',
|
||||||
|
retryInterval: 'retry interval',
|
||||||
|
retryTimes: 'Retry {{times}} times on failure',
|
||||||
|
retrying: 'Retrying...',
|
||||||
|
retrySuccessful: 'Retry successful',
|
||||||
|
retryFailed: 'Retry failed',
|
||||||
|
retryFailedTimes: '{{times}} retries failed',
|
||||||
|
times: 'times',
|
||||||
|
ms: 'ms',
|
||||||
|
retries: '{{num}} Retries',
|
||||||
|
},
|
||||||
},
|
},
|
||||||
start: {
|
start: {
|
||||||
required: 'required',
|
required: 'required',
|
||||||
|
@ -329,6 +329,20 @@ const translation = {
|
|||||||
tip: '流程中有 {{num}} 个节点运行异常,请前往追踪查看日志。',
|
tip: '流程中有 {{num}} 个节点运行异常,请前往追踪查看日志。',
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
retry: {
|
||||||
|
retry: '重试',
|
||||||
|
retryOnFailure: '失败时重试',
|
||||||
|
maxRetries: '最大重试次数',
|
||||||
|
retryInterval: '重试间隔',
|
||||||
|
retryTimes: '失败时重试 {{times}} 次',
|
||||||
|
retrying: '重试中...',
|
||||||
|
retrySuccessful: '重试成功',
|
||||||
|
retryFailed: '重试失败',
|
||||||
|
retryFailedTimes: '{{times}} 次重试失败',
|
||||||
|
times: '次',
|
||||||
|
ms: '毫秒',
|
||||||
|
retries: '{{num}} 重试次数',
|
||||||
|
},
|
||||||
},
|
},
|
||||||
start: {
|
start: {
|
||||||
required: '必填',
|
required: '必填',
|
||||||
|
@ -62,6 +62,7 @@ export type IOnNodeStarted = (nodeStarted: NodeStartedResponse) => void
|
|||||||
export type IOnNodeFinished = (nodeFinished: NodeFinishedResponse) => void
|
export type IOnNodeFinished = (nodeFinished: NodeFinishedResponse) => void
|
||||||
export type IOnIterationStarted = (workflowStarted: IterationStartedResponse) => void
|
export type IOnIterationStarted = (workflowStarted: IterationStartedResponse) => void
|
||||||
export type IOnIterationNext = (workflowStarted: IterationNextResponse) => void
|
export type IOnIterationNext = (workflowStarted: IterationNextResponse) => void
|
||||||
|
export type IOnNodeRetry = (nodeFinished: NodeFinishedResponse) => void
|
||||||
export type IOnIterationFinished = (workflowFinished: IterationFinishedResponse) => void
|
export type IOnIterationFinished = (workflowFinished: IterationFinishedResponse) => void
|
||||||
export type IOnParallelBranchStarted = (parallelBranchStarted: ParallelBranchStartedResponse) => void
|
export type IOnParallelBranchStarted = (parallelBranchStarted: ParallelBranchStartedResponse) => void
|
||||||
export type IOnParallelBranchFinished = (parallelBranchFinished: ParallelBranchFinishedResponse) => void
|
export type IOnParallelBranchFinished = (parallelBranchFinished: ParallelBranchFinishedResponse) => void
|
||||||
@ -92,6 +93,7 @@ export type IOtherOptions = {
|
|||||||
onIterationStart?: IOnIterationStarted
|
onIterationStart?: IOnIterationStarted
|
||||||
onIterationNext?: IOnIterationNext
|
onIterationNext?: IOnIterationNext
|
||||||
onIterationFinish?: IOnIterationFinished
|
onIterationFinish?: IOnIterationFinished
|
||||||
|
onNodeRetry?: IOnNodeRetry
|
||||||
onParallelBranchStarted?: IOnParallelBranchStarted
|
onParallelBranchStarted?: IOnParallelBranchStarted
|
||||||
onParallelBranchFinished?: IOnParallelBranchFinished
|
onParallelBranchFinished?: IOnParallelBranchFinished
|
||||||
onTextChunk?: IOnTextChunk
|
onTextChunk?: IOnTextChunk
|
||||||
@ -165,6 +167,7 @@ const handleStream = (
|
|||||||
onIterationStart?: IOnIterationStarted,
|
onIterationStart?: IOnIterationStarted,
|
||||||
onIterationNext?: IOnIterationNext,
|
onIterationNext?: IOnIterationNext,
|
||||||
onIterationFinish?: IOnIterationFinished,
|
onIterationFinish?: IOnIterationFinished,
|
||||||
|
onNodeRetry?: IOnNodeRetry,
|
||||||
onParallelBranchStarted?: IOnParallelBranchStarted,
|
onParallelBranchStarted?: IOnParallelBranchStarted,
|
||||||
onParallelBranchFinished?: IOnParallelBranchFinished,
|
onParallelBranchFinished?: IOnParallelBranchFinished,
|
||||||
onTextChunk?: IOnTextChunk,
|
onTextChunk?: IOnTextChunk,
|
||||||
@ -256,6 +259,9 @@ const handleStream = (
|
|||||||
else if (bufferObj.event === 'iteration_completed') {
|
else if (bufferObj.event === 'iteration_completed') {
|
||||||
onIterationFinish?.(bufferObj as IterationFinishedResponse)
|
onIterationFinish?.(bufferObj as IterationFinishedResponse)
|
||||||
}
|
}
|
||||||
|
else if (bufferObj.event === 'node_retry') {
|
||||||
|
onNodeRetry?.(bufferObj as NodeFinishedResponse)
|
||||||
|
}
|
||||||
else if (bufferObj.event === 'parallel_branch_started') {
|
else if (bufferObj.event === 'parallel_branch_started') {
|
||||||
onParallelBranchStarted?.(bufferObj as ParallelBranchStartedResponse)
|
onParallelBranchStarted?.(bufferObj as ParallelBranchStartedResponse)
|
||||||
}
|
}
|
||||||
@ -462,6 +468,7 @@ export const ssePost = (
|
|||||||
onIterationStart,
|
onIterationStart,
|
||||||
onIterationNext,
|
onIterationNext,
|
||||||
onIterationFinish,
|
onIterationFinish,
|
||||||
|
onNodeRetry,
|
||||||
onParallelBranchStarted,
|
onParallelBranchStarted,
|
||||||
onParallelBranchFinished,
|
onParallelBranchFinished,
|
||||||
onTextChunk,
|
onTextChunk,
|
||||||
@ -533,7 +540,7 @@ export const ssePost = (
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
onData?.(str, isFirstMessage, moreInfo)
|
onData?.(str, isFirstMessage, moreInfo)
|
||||||
}, onCompleted, onThought, onMessageEnd, onMessageReplace, onFile, onWorkflowStarted, onWorkflowFinished, onNodeStarted, onNodeFinished, onIterationStart, onIterationNext, onIterationFinish, onParallelBranchStarted, onParallelBranchFinished, onTextChunk, onTTSChunk, onTTSEnd, onTextReplace)
|
}, onCompleted, onThought, onMessageEnd, onMessageReplace, onFile, onWorkflowStarted, onWorkflowFinished, onNodeStarted, onNodeFinished, onIterationStart, onIterationNext, onIterationFinish, onNodeRetry, onParallelBranchStarted, onParallelBranchFinished, onTextChunk, onTTSChunk, onTTSEnd, onTextReplace)
|
||||||
}).catch((e) => {
|
}).catch((e) => {
|
||||||
if (e.toString() !== 'AbortError: The user aborted a request.' && !e.toString().errorMessage.includes('TypeError: Cannot assign to read only property'))
|
if (e.toString() !== 'AbortError: The user aborted a request.' && !e.toString().errorMessage.includes('TypeError: Cannot assign to read only property'))
|
||||||
Toast.notify({ type: 'error', message: e })
|
Toast.notify({ type: 'error', message: e })
|
||||||
|
@ -52,10 +52,12 @@ export type NodeTracing = {
|
|||||||
extras?: any
|
extras?: any
|
||||||
expand?: boolean // for UI
|
expand?: boolean // for UI
|
||||||
details?: NodeTracing[][] // iteration detail
|
details?: NodeTracing[][] // iteration detail
|
||||||
|
retryDetail?: NodeTracing[] // retry detail
|
||||||
parallel_id?: string
|
parallel_id?: string
|
||||||
parallel_start_node_id?: string
|
parallel_start_node_id?: string
|
||||||
parent_parallel_id?: string
|
parent_parallel_id?: string
|
||||||
parent_parallel_start_node_id?: string
|
parent_parallel_start_node_id?: string
|
||||||
|
retry_index?: number
|
||||||
}
|
}
|
||||||
|
|
||||||
export type FetchWorkflowDraftResponse = {
|
export type FetchWorkflowDraftResponse = {
|
||||||
@ -178,6 +180,7 @@ export type NodeFinishedResponse = {
|
|||||||
}
|
}
|
||||||
created_at: number
|
created_at: number
|
||||||
files?: FileResponse[]
|
files?: FileResponse[]
|
||||||
|
retry_index?: number
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Loading…
x
Reference in New Issue
Block a user