mirror of
https://github.com/langgenius/dify.git
synced 2026-04-28 11:56:55 +08:00
feat: node before and after run
This commit is contained in:
parent
86d2c1184c
commit
1b8c8b0a43
@ -1,6 +1,7 @@
|
|||||||
'use client'
|
'use client'
|
||||||
import type { FC } from 'react'
|
import type { FC } from 'react'
|
||||||
import React, { useCallback } from 'react'
|
import React, { useCallback } from 'react'
|
||||||
|
import { useTranslation } from 'react-i18next'
|
||||||
import produce from 'immer'
|
import produce from 'immer'
|
||||||
import RemoveButton from '../remove-button'
|
import RemoveButton from '../remove-button'
|
||||||
import VarReferencePicker from './var-reference-picker'
|
import VarReferencePicker from './var-reference-picker'
|
||||||
@ -26,6 +27,8 @@ const VarList: FC<Props> = ({
|
|||||||
onlyLeafNodeVar,
|
onlyLeafNodeVar,
|
||||||
onlyVarType,
|
onlyVarType,
|
||||||
}) => {
|
}) => {
|
||||||
|
const { t } = useTranslation()
|
||||||
|
|
||||||
const handleVarNameChange = useCallback((index: number) => {
|
const handleVarNameChange = useCallback((index: number) => {
|
||||||
return (e: React.ChangeEvent<HTMLInputElement>) => {
|
return (e: React.ChangeEvent<HTMLInputElement>) => {
|
||||||
const newList = produce(list, (draft) => {
|
const newList = produce(list, (draft) => {
|
||||||
@ -54,7 +57,7 @@ const VarList: FC<Props> = ({
|
|||||||
})
|
})
|
||||||
onChange(newList)
|
onChange(newList)
|
||||||
}
|
}
|
||||||
}, [list, onChange])
|
}, [isSupportConstantValue, list, onChange])
|
||||||
|
|
||||||
const handleVarRemove = useCallback((index: number) => {
|
const handleVarRemove = useCallback((index: number) => {
|
||||||
return () => {
|
return () => {
|
||||||
@ -73,8 +76,10 @@ const VarList: FC<Props> = ({
|
|||||||
readOnly={readonly}
|
readOnly={readonly}
|
||||||
value={list[index].variable}
|
value={list[index].variable}
|
||||||
onChange={handleVarNameChange(index)}
|
onChange={handleVarNameChange(index)}
|
||||||
className='w-[120px] h-8 leading-8 px-2.5 rounded-lg border-0 bg-gray-100 text-gray-900 text-[13px] placeholder:text-gray-400 focus:outline-none focus:ring-1 focus:ring-inset focus:ring-gray-200'
|
placeholder={t('workflow.common.variableNamePlaceholder')!}
|
||||||
type='text' />
|
className='w-[120px] h-8 leading-8 px-2.5 rounded-lg border-0 bg-gray-100 text-gray-900 text-[13px] placeholder:text-gray-400 focus:outline-none focus:ring-1 focus:ring-inset focus:ring-gray-200'
|
||||||
|
type='text'
|
||||||
|
/>
|
||||||
<VarReferencePicker
|
<VarReferencePicker
|
||||||
nodeId={nodeId}
|
nodeId={nodeId}
|
||||||
readonly={readonly}
|
readonly={readonly}
|
||||||
|
|||||||
@ -1,6 +1,7 @@
|
|||||||
'use client'
|
'use client'
|
||||||
import type { FC } from 'react'
|
import type { FC } from 'react'
|
||||||
import React, { useCallback, useEffect, useRef, useState } from 'react'
|
import React, { useCallback, useEffect, useRef, useState } from 'react'
|
||||||
|
import { useTranslation } from 'react-i18next'
|
||||||
import cn from 'classnames'
|
import cn from 'classnames'
|
||||||
import { isArray } from 'lodash-es'
|
import { isArray } from 'lodash-es'
|
||||||
import VarReferencePopup from './var-reference-popup'
|
import VarReferencePopup from './var-reference-popup'
|
||||||
@ -57,6 +58,8 @@ const VarReferencePicker: FC<Props> = ({
|
|||||||
onlyLeafNodeVar,
|
onlyLeafNodeVar,
|
||||||
onlyVarType,
|
onlyVarType,
|
||||||
}) => {
|
}) => {
|
||||||
|
const { t } = useTranslation()
|
||||||
|
|
||||||
const isChatMode = useIsChatMode()
|
const isChatMode = useIsChatMode()
|
||||||
const [varKindType, setVarKindType] = useState<VarKindType>(defaultVarKindType)
|
const [varKindType, setVarKindType] = useState<VarKindType>(defaultVarKindType)
|
||||||
const isConstant = isSupportConstantValue && varKindType === VarKindType.static
|
const isConstant = isSupportConstantValue && varKindType === VarKindType.static
|
||||||
@ -110,7 +113,8 @@ const VarReferencePicker: FC<Props> = ({
|
|||||||
onChange('', value)
|
onChange('', value)
|
||||||
else
|
else
|
||||||
onChange([], value)
|
onChange([], value)
|
||||||
}, [varKindType])
|
}, [onChange])
|
||||||
|
|
||||||
const inputRef = useRef<HTMLInputElement>(null)
|
const inputRef = useRef<HTMLInputElement>(null)
|
||||||
const [isFocus, setIsFocus] = useState(false)
|
const [isFocus, setIsFocus] = useState(false)
|
||||||
const [controlFocus, setControlFocus] = useState(0)
|
const [controlFocus, setControlFocus] = useState(0)
|
||||||
@ -166,27 +170,29 @@ const VarReferencePicker: FC<Props> = ({
|
|||||||
)
|
)
|
||||||
: (
|
: (
|
||||||
<div className={cn('inline-flex h-full items-center px-1.5 rounded-[5px]', hasValue && 'bg-white')}>
|
<div className={cn('inline-flex h-full items-center px-1.5 rounded-[5px]', hasValue && 'bg-white')}>
|
||||||
{hasValue && (
|
{hasValue
|
||||||
<>
|
? (
|
||||||
{isShowNodeName && (
|
<>
|
||||||
<div className='flex items-center'>
|
{isShowNodeName && (
|
||||||
<div className='p-[1px]'>
|
<div className='flex items-center'>
|
||||||
<VarBlockIcon
|
<div className='p-[1px]'>
|
||||||
className='!text-gray-900'
|
<VarBlockIcon
|
||||||
type={outputVarNode?.type}
|
className='!text-gray-900'
|
||||||
/>
|
type={outputVarNode?.type}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<div className='mx-0.5 text-xs font-medium text-gray-700'>{outputVarNode?.title}</div>
|
||||||
|
<Line3 className='mr-0.5'></Line3>
|
||||||
</div>
|
</div>
|
||||||
<div className='mx-0.5 text-xs font-medium text-gray-700'>{outputVarNode?.title}</div>
|
)}
|
||||||
<Line3 className='mr-0.5'></Line3>
|
<div className='flex items-center text-primary-600'>
|
||||||
|
<Variable02 className='w-3.5 h-3.5' />
|
||||||
|
<div className='ml-0.5 text-xs font-medium'>{varName}</div>
|
||||||
</div>
|
</div>
|
||||||
)}
|
<div className='ml-0.5 text-xs font-normal text-gray-500 capitalize'>{getVarType()}</div>
|
||||||
<div className='flex items-center text-primary-600'>
|
</>
|
||||||
<Variable02 className='w-3.5 h-3.5' />
|
)
|
||||||
<div className='ml-0.5 text-xs font-medium'>{varName}</div>
|
: <div className='text-[13px] font-normal text-gray-400'>{t('workflow.common.setVarValuePlaceholder')}</div>}
|
||||||
</div>
|
|
||||||
<div className='ml-0.5 text-xs font-normal text-gray-500 capitalize'>{getVarType()}</div>
|
|
||||||
</>
|
|
||||||
)}
|
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@ -1,28 +1,62 @@
|
|||||||
import { useState } from 'react'
|
import { useEffect, useState } from 'react'
|
||||||
import { useTranslation } from 'react-i18next'
|
import { useTranslation } from 'react-i18next'
|
||||||
import { useNodeDataUpdate } from '@/app/components/workflow/hooks'
|
import { useNodeDataUpdate } from '@/app/components/workflow/hooks'
|
||||||
import type { CommonNodeType, InputVar, Variable } from '@/app/components/workflow/types'
|
import type { CheckValidRes, CommonNodeType, InputVar, Variable } from '@/app/components/workflow/types'
|
||||||
import { InputVarType, NodeRunningStatus } from '@/app/components/workflow/types'
|
import { BlockEnum, InputVarType, NodeRunningStatus } from '@/app/components/workflow/types'
|
||||||
import { useStore as useAppStore } from '@/app/components/app/store'
|
import { useStore as useAppStore } from '@/app/components/app/store'
|
||||||
import { singleNodeRun } from '@/service/workflow'
|
import { singleNodeRun } from '@/service/workflow'
|
||||||
// import Toast from '@/app/components/base/toast'
|
import Toast from '@/app/components/base/toast'
|
||||||
|
import CodeDefault from '@/app/components/workflow/nodes/code/default'
|
||||||
|
import HTTPDefault from '@/app/components/workflow/nodes/http/default'
|
||||||
|
const { checkValid: checkCodeValid } = CodeDefault
|
||||||
|
|
||||||
|
const checkValidFns: Record<BlockEnum, Function> = {
|
||||||
|
[BlockEnum.Code]: checkCodeValid,
|
||||||
|
[BlockEnum.HttpRequest]: HTTPDefault.checkValid,
|
||||||
|
} as any
|
||||||
|
|
||||||
type Params<T> = {
|
type Params<T> = {
|
||||||
id: string
|
id: string
|
||||||
data: CommonNodeType<T>
|
data: CommonNodeType<T>
|
||||||
defaultRunInputData: Record<string, any>
|
defaultRunInputData: Record<string, any>
|
||||||
isInvalid?: () => boolean
|
beforeRunCheckValid?: () => CheckValidRes
|
||||||
}
|
}
|
||||||
|
|
||||||
const useOneStepRun = <T>({ id, data, defaultRunInputData, isInvalid = () => true }: Params<T>) => {
|
const useOneStepRun = <T>({
|
||||||
|
id,
|
||||||
|
data,
|
||||||
|
defaultRunInputData,
|
||||||
|
beforeRunCheckValid = () => ({ isValid: true }),
|
||||||
|
}: Params<T>) => {
|
||||||
const { t } = useTranslation()
|
const { t } = useTranslation()
|
||||||
|
const checkValid = checkValidFns[data.type]
|
||||||
const appId = useAppStore.getState().appDetail?.id
|
const appId = useAppStore.getState().appDetail?.id
|
||||||
const [runInputData, setRunInputData] = useState<Record<string, any>>(defaultRunInputData || {})
|
const [runInputData, setRunInputData] = useState<Record<string, any>>(defaultRunInputData || {})
|
||||||
const [runResult, setRunResult] = useState<any>(null)
|
const [runResult, setRunResult] = useState<any>(null)
|
||||||
|
|
||||||
const { handleNodeDataUpdate }: { handleNodeDataUpdate: (data: any) => void } = useNodeDataUpdate()
|
const { handleNodeDataUpdate }: { handleNodeDataUpdate: (data: any) => void } = useNodeDataUpdate()
|
||||||
const isShowSingleRun = data._isSingleRun
|
const [canShowSingleRun, setCanShowSingleRun] = useState(false)
|
||||||
|
const isShowSingleRun = data._isSingleRun && canShowSingleRun
|
||||||
|
useEffect(() => {
|
||||||
|
if (data._isSingleRun) {
|
||||||
|
const { isValid, errorMessage } = checkValid(data, t)
|
||||||
|
setCanShowSingleRun(isValid)
|
||||||
|
if (!isValid) {
|
||||||
|
handleNodeDataUpdate({
|
||||||
|
id,
|
||||||
|
data: {
|
||||||
|
...data,
|
||||||
|
_isSingleRun: false,
|
||||||
|
},
|
||||||
|
})
|
||||||
|
Toast.notify({
|
||||||
|
type: 'error',
|
||||||
|
message: errorMessage,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||||
|
}, [data._isSingleRun])
|
||||||
const hideSingleRun = () => {
|
const hideSingleRun = () => {
|
||||||
handleNodeDataUpdate({
|
handleNodeDataUpdate({
|
||||||
id,
|
id,
|
||||||
@ -35,8 +69,14 @@ const useOneStepRun = <T>({ id, data, defaultRunInputData, isInvalid = () => tru
|
|||||||
const runningStatus = data._singleRunningStatus || NodeRunningStatus.NotStart
|
const runningStatus = data._singleRunningStatus || NodeRunningStatus.NotStart
|
||||||
const isCompleted = runningStatus === NodeRunningStatus.Succeeded || runningStatus === NodeRunningStatus.Failed
|
const isCompleted = runningStatus === NodeRunningStatus.Succeeded || runningStatus === NodeRunningStatus.Failed
|
||||||
const handleRun = async () => {
|
const handleRun = async () => {
|
||||||
if (!isInvalid())
|
const { isValid, errorMessage } = beforeRunCheckValid()
|
||||||
return
|
if (!isValid) {
|
||||||
|
Toast.notify({
|
||||||
|
type: 'error',
|
||||||
|
message: errorMessage!,
|
||||||
|
})
|
||||||
|
return false
|
||||||
|
}
|
||||||
handleNodeDataUpdate({
|
handleNodeDataUpdate({
|
||||||
id,
|
id,
|
||||||
data: {
|
data: {
|
||||||
@ -77,10 +117,6 @@ const useOneStepRun = <T>({ id, data, defaultRunInputData, isInvalid = () => tru
|
|||||||
_singleRunningStatus: NodeRunningStatus.Succeeded,
|
_singleRunningStatus: NodeRunningStatus.Succeeded,
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
// Toast.notify({
|
|
||||||
// type: 'success',
|
|
||||||
// message: t('common.api.success'),
|
|
||||||
// })
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const handleStop = () => {
|
const handleStop = () => {
|
||||||
|
|||||||
@ -2,6 +2,8 @@ import type { NodeDefault } from '../../types'
|
|||||||
import { CodeLanguage, type CodeNodeType } from './types'
|
import { CodeLanguage, type CodeNodeType } 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 i18nPrefix = 'workflow.errorMsg'
|
||||||
|
|
||||||
const nodeDefault: NodeDefault<CodeNodeType> = {
|
const nodeDefault: NodeDefault<CodeNodeType> = {
|
||||||
defaultValue: {
|
defaultValue: {
|
||||||
code: '',
|
code: '',
|
||||||
@ -17,18 +19,22 @@ const nodeDefault: NodeDefault<CodeNodeType> = {
|
|||||||
const nodes = isChatMode ? ALL_CHAT_AVAILABLE_BLOCKS : ALL_COMPLETION_AVAILABLE_BLOCKS
|
const nodes = isChatMode ? ALL_CHAT_AVAILABLE_BLOCKS : ALL_COMPLETION_AVAILABLE_BLOCKS
|
||||||
return nodes
|
return nodes
|
||||||
},
|
},
|
||||||
checkValid(payload: CodeNodeType) {
|
checkValid(payload: CodeNodeType, t: any) {
|
||||||
let isValid = true
|
|
||||||
let errorMessages = ''
|
let errorMessages = ''
|
||||||
if (payload.type) {
|
const { code, variables } = payload
|
||||||
isValid = true
|
if (!errorMessages && variables.filter(v => !v.variable).length > 0)
|
||||||
errorMessages = ''
|
errorMessages = t(`${i18nPrefix}.fieldRequired`, { field: t(`${i18nPrefix}.fields.variable`) })
|
||||||
}
|
if (!errorMessages && variables.filter(v => !v.value_selector.length).length > 0)
|
||||||
|
errorMessages = t(`${i18nPrefix}.fieldRequired`, { field: t(`${i18nPrefix}.fields.variableValue`) })
|
||||||
|
if (!errorMessages && !code)
|
||||||
|
errorMessages = t(`${i18nPrefix}.fieldRequired`, { field: t(`${i18nPrefix}.fields.code`) })
|
||||||
|
|
||||||
return {
|
return {
|
||||||
isValid,
|
isValid: !errorMessages,
|
||||||
errorMessage: errorMessages,
|
errorMessage: errorMessages,
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export default nodeDefault
|
export default nodeDefault
|
||||||
|
|||||||
@ -48,6 +48,12 @@ const useConfig = (id: string, payload: CodeNodeType) => {
|
|||||||
id,
|
id,
|
||||||
data: inputs,
|
data: inputs,
|
||||||
defaultRunInputData: {},
|
defaultRunInputData: {},
|
||||||
|
beforeRunCheckValid: () => {
|
||||||
|
return {
|
||||||
|
isValid: true,
|
||||||
|
// errorMessage: 'xxxx',
|
||||||
|
}
|
||||||
|
},
|
||||||
})
|
})
|
||||||
|
|
||||||
const varInputs = toVarInputs(inputs.variables)
|
const varInputs = toVarInputs(inputs.variables)
|
||||||
@ -63,7 +69,7 @@ const useConfig = (id: string, payload: CodeNodeType) => {
|
|||||||
|
|
||||||
const setInputVarValues = useCallback((newPayload: Record<string, any>) => {
|
const setInputVarValues = useCallback((newPayload: Record<string, any>) => {
|
||||||
setRunInputData(newPayload)
|
setRunInputData(newPayload)
|
||||||
}, [runInputData, setRunInputData])
|
}, [setRunInputData])
|
||||||
|
|
||||||
return {
|
return {
|
||||||
inputs,
|
inputs,
|
||||||
|
|||||||
@ -159,7 +159,7 @@ export type NodeDefault<T> = {
|
|||||||
defaultValue: Partial<T>
|
defaultValue: Partial<T>
|
||||||
getAvailablePrevNodes: (isChatMode: boolean) => BlockEnum[]
|
getAvailablePrevNodes: (isChatMode: boolean) => BlockEnum[]
|
||||||
getAvailableNextNodes: (isChatMode: boolean) => BlockEnum[]
|
getAvailableNextNodes: (isChatMode: boolean) => BlockEnum[]
|
||||||
checkValid: (payload: T) => { isValid: boolean; errorMessage?: string }
|
checkValid: (payload: T, t: any) => { isValid: boolean; errorMessage?: string }
|
||||||
}
|
}
|
||||||
|
|
||||||
export type OnSelectBlock = (type: BlockEnum, toolDefaultValue?: ToolDefaultValue) => void
|
export type OnSelectBlock = (type: BlockEnum, toolDefaultValue?: ToolDefaultValue) => void
|
||||||
@ -199,3 +199,8 @@ export type OnNodeAdd = (
|
|||||||
nextNodeTargetHandle?: string
|
nextNodeTargetHandle?: string
|
||||||
}
|
}
|
||||||
) => void
|
) => void
|
||||||
|
|
||||||
|
export type CheckValidRes = {
|
||||||
|
isValid: boolean
|
||||||
|
errorMessage?: string
|
||||||
|
}
|
||||||
|
|||||||
@ -24,6 +24,16 @@ const translation = {
|
|||||||
addTitle: 'Add title...',
|
addTitle: 'Add title...',
|
||||||
addDescription: 'Add description...',
|
addDescription: 'Add description...',
|
||||||
noVar: 'No variable',
|
noVar: 'No variable',
|
||||||
|
variableNamePlaceholder: 'Variable name',
|
||||||
|
setVarValuePlaceholder: 'Set variable',
|
||||||
|
},
|
||||||
|
errorMsg: {
|
||||||
|
fieldRequired: '{{field}} is required',
|
||||||
|
fields: {
|
||||||
|
variable: 'Variable Name',
|
||||||
|
variableValue: 'Variable Value',
|
||||||
|
code: 'Code',
|
||||||
|
},
|
||||||
},
|
},
|
||||||
singleRun: {
|
singleRun: {
|
||||||
testRun: 'Test Run ',
|
testRun: 'Test Run ',
|
||||||
|
|||||||
@ -24,6 +24,16 @@ const translation = {
|
|||||||
addTitle: '添加标题...',
|
addTitle: '添加标题...',
|
||||||
addDescription: '添加描述...',
|
addDescription: '添加描述...',
|
||||||
noVar: '没有变量',
|
noVar: '没有变量',
|
||||||
|
variableNamePlaceholder: '变量名',
|
||||||
|
setVarValuePlaceholder: '设置变量值',
|
||||||
|
},
|
||||||
|
errorMsg: {
|
||||||
|
fieldRequired: '{{field}} 不能为空',
|
||||||
|
fields: {
|
||||||
|
variable: '变量名',
|
||||||
|
variableValue: '变量值',
|
||||||
|
code: '代码',
|
||||||
|
},
|
||||||
},
|
},
|
||||||
singleRun: {
|
singleRun: {
|
||||||
testRun: '测试运行 ',
|
testRun: '测试运行 ',
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user