From 1b8c8b0a43ff27e8282c047e0ca142e47c6507fa Mon Sep 17 00:00:00 2001 From: Joel Date: Fri, 15 Mar 2024 11:26:10 +0800 Subject: [PATCH] feat: node before and after run --- .../_base/components/variable/var-list.tsx | 11 +++- .../variable/var-reference-picker.tsx | 46 +++++++------ .../nodes/_base/hooks/use-one-step-run.ts | 64 +++++++++++++++---- .../components/workflow/nodes/code/default.ts | 20 ++++-- .../workflow/nodes/code/use-config.ts | 8 ++- web/app/components/workflow/types.ts | 7 +- web/i18n/en-US/workflow.ts | 10 +++ web/i18n/zh-Hans/workflow.ts | 10 +++ 8 files changed, 130 insertions(+), 46 deletions(-) diff --git a/web/app/components/workflow/nodes/_base/components/variable/var-list.tsx b/web/app/components/workflow/nodes/_base/components/variable/var-list.tsx index 71a360c4ca..de363d5cc8 100644 --- a/web/app/components/workflow/nodes/_base/components/variable/var-list.tsx +++ b/web/app/components/workflow/nodes/_base/components/variable/var-list.tsx @@ -1,6 +1,7 @@ 'use client' import type { FC } from 'react' import React, { useCallback } from 'react' +import { useTranslation } from 'react-i18next' import produce from 'immer' import RemoveButton from '../remove-button' import VarReferencePicker from './var-reference-picker' @@ -26,6 +27,8 @@ const VarList: FC = ({ onlyLeafNodeVar, onlyVarType, }) => { + const { t } = useTranslation() + const handleVarNameChange = useCallback((index: number) => { return (e: React.ChangeEvent) => { const newList = produce(list, (draft) => { @@ -54,7 +57,7 @@ const VarList: FC = ({ }) onChange(newList) } - }, [list, onChange]) + }, [isSupportConstantValue, list, onChange]) const handleVarRemove = useCallback((index: number) => { return () => { @@ -73,8 +76,10 @@ const VarList: FC = ({ readOnly={readonly} value={list[index].variable} 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' - type='text' /> + placeholder={t('workflow.common.variableNamePlaceholder')!} + 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' + /> = ({ onlyLeafNodeVar, onlyVarType, }) => { + const { t } = useTranslation() + const isChatMode = useIsChatMode() const [varKindType, setVarKindType] = useState(defaultVarKindType) const isConstant = isSupportConstantValue && varKindType === VarKindType.static @@ -110,7 +113,8 @@ const VarReferencePicker: FC = ({ onChange('', value) else onChange([], value) - }, [varKindType]) + }, [onChange]) + const inputRef = useRef(null) const [isFocus, setIsFocus] = useState(false) const [controlFocus, setControlFocus] = useState(0) @@ -166,27 +170,29 @@ const VarReferencePicker: FC = ({ ) : (
- {hasValue && ( - <> - {isShowNodeName && ( -
-
- + {hasValue + ? ( + <> + {isShowNodeName && ( +
+
+ +
+
{outputVarNode?.title}
+
-
{outputVarNode?.title}
- + )} +
+ +
{varName}
- )} -
- -
{varName}
-
-
{getVarType()}
- - )} +
{getVarType()}
+ + ) + :
{t('workflow.common.setVarValuePlaceholder')}
}
)}
diff --git a/web/app/components/workflow/nodes/_base/hooks/use-one-step-run.ts b/web/app/components/workflow/nodes/_base/hooks/use-one-step-run.ts index a29a3e3029..0d48f834d6 100644 --- a/web/app/components/workflow/nodes/_base/hooks/use-one-step-run.ts +++ b/web/app/components/workflow/nodes/_base/hooks/use-one-step-run.ts @@ -1,28 +1,62 @@ -import { useState } from 'react' +import { useEffect, useState } from 'react' import { useTranslation } from 'react-i18next' import { useNodeDataUpdate } from '@/app/components/workflow/hooks' -import type { CommonNodeType, InputVar, Variable } from '@/app/components/workflow/types' -import { InputVarType, NodeRunningStatus } from '@/app/components/workflow/types' +import type { CheckValidRes, CommonNodeType, InputVar, Variable } from '@/app/components/workflow/types' +import { BlockEnum, InputVarType, NodeRunningStatus } from '@/app/components/workflow/types' import { useStore as useAppStore } from '@/app/components/app/store' 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.Code]: checkCodeValid, + [BlockEnum.HttpRequest]: HTTPDefault.checkValid, +} as any type Params = { id: string data: CommonNodeType defaultRunInputData: Record - isInvalid?: () => boolean + beforeRunCheckValid?: () => CheckValidRes } -const useOneStepRun = ({ id, data, defaultRunInputData, isInvalid = () => true }: Params) => { +const useOneStepRun = ({ + id, + data, + defaultRunInputData, + beforeRunCheckValid = () => ({ isValid: true }), +}: Params) => { const { t } = useTranslation() - + const checkValid = checkValidFns[data.type] const appId = useAppStore.getState().appDetail?.id const [runInputData, setRunInputData] = useState>(defaultRunInputData || {}) const [runResult, setRunResult] = useState(null) 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 = () => { handleNodeDataUpdate({ id, @@ -35,8 +69,14 @@ const useOneStepRun = ({ id, data, defaultRunInputData, isInvalid = () => tru const runningStatus = data._singleRunningStatus || NodeRunningStatus.NotStart const isCompleted = runningStatus === NodeRunningStatus.Succeeded || runningStatus === NodeRunningStatus.Failed const handleRun = async () => { - if (!isInvalid()) - return + const { isValid, errorMessage } = beforeRunCheckValid() + if (!isValid) { + Toast.notify({ + type: 'error', + message: errorMessage!, + }) + return false + } handleNodeDataUpdate({ id, data: { @@ -77,10 +117,6 @@ const useOneStepRun = ({ id, data, defaultRunInputData, isInvalid = () => tru _singleRunningStatus: NodeRunningStatus.Succeeded, }, }) - // Toast.notify({ - // type: 'success', - // message: t('common.api.success'), - // }) } const handleStop = () => { diff --git a/web/app/components/workflow/nodes/code/default.ts b/web/app/components/workflow/nodes/code/default.ts index 215c8e6ae9..84dc2d70b2 100644 --- a/web/app/components/workflow/nodes/code/default.ts +++ b/web/app/components/workflow/nodes/code/default.ts @@ -2,6 +2,8 @@ import type { NodeDefault } from '../../types' import { CodeLanguage, type CodeNodeType } from './types' import { ALL_CHAT_AVAILABLE_BLOCKS, ALL_COMPLETION_AVAILABLE_BLOCKS } from '@/app/components/workflow/constants' +const i18nPrefix = 'workflow.errorMsg' + const nodeDefault: NodeDefault = { defaultValue: { code: '', @@ -17,18 +19,22 @@ const nodeDefault: NodeDefault = { const nodes = isChatMode ? ALL_CHAT_AVAILABLE_BLOCKS : ALL_COMPLETION_AVAILABLE_BLOCKS return nodes }, - checkValid(payload: CodeNodeType) { - let isValid = true + checkValid(payload: CodeNodeType, t: any) { let errorMessages = '' - if (payload.type) { - isValid = true - errorMessages = '' - } + const { code, variables } = payload + if (!errorMessages && variables.filter(v => !v.variable).length > 0) + 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 { - isValid, + isValid: !errorMessages, errorMessage: errorMessages, } }, + } export default nodeDefault diff --git a/web/app/components/workflow/nodes/code/use-config.ts b/web/app/components/workflow/nodes/code/use-config.ts index 185feb1f19..ad819043b9 100644 --- a/web/app/components/workflow/nodes/code/use-config.ts +++ b/web/app/components/workflow/nodes/code/use-config.ts @@ -48,6 +48,12 @@ const useConfig = (id: string, payload: CodeNodeType) => { id, data: inputs, defaultRunInputData: {}, + beforeRunCheckValid: () => { + return { + isValid: true, + // errorMessage: 'xxxx', + } + }, }) const varInputs = toVarInputs(inputs.variables) @@ -63,7 +69,7 @@ const useConfig = (id: string, payload: CodeNodeType) => { const setInputVarValues = useCallback((newPayload: Record) => { setRunInputData(newPayload) - }, [runInputData, setRunInputData]) + }, [setRunInputData]) return { inputs, diff --git a/web/app/components/workflow/types.ts b/web/app/components/workflow/types.ts index 09175374e4..80f3209765 100644 --- a/web/app/components/workflow/types.ts +++ b/web/app/components/workflow/types.ts @@ -159,7 +159,7 @@ export type NodeDefault = { defaultValue: Partial getAvailablePrevNodes: (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 @@ -199,3 +199,8 @@ export type OnNodeAdd = ( nextNodeTargetHandle?: string } ) => void + +export type CheckValidRes = { + isValid: boolean + errorMessage?: string +} diff --git a/web/i18n/en-US/workflow.ts b/web/i18n/en-US/workflow.ts index 92faa9c127..0c4e09f8ca 100644 --- a/web/i18n/en-US/workflow.ts +++ b/web/i18n/en-US/workflow.ts @@ -24,6 +24,16 @@ const translation = { addTitle: 'Add title...', addDescription: 'Add description...', noVar: 'No variable', + variableNamePlaceholder: 'Variable name', + setVarValuePlaceholder: 'Set variable', + }, + errorMsg: { + fieldRequired: '{{field}} is required', + fields: { + variable: 'Variable Name', + variableValue: 'Variable Value', + code: 'Code', + }, }, singleRun: { testRun: 'Test Run ', diff --git a/web/i18n/zh-Hans/workflow.ts b/web/i18n/zh-Hans/workflow.ts index 677850009f..3f1aa8546f 100644 --- a/web/i18n/zh-Hans/workflow.ts +++ b/web/i18n/zh-Hans/workflow.ts @@ -24,6 +24,16 @@ const translation = { addTitle: '添加标题...', addDescription: '添加描述...', noVar: '没有变量', + variableNamePlaceholder: '变量名', + setVarValuePlaceholder: '设置变量值', + }, + errorMsg: { + fieldRequired: '{{field}} 不能为空', + fields: { + variable: '变量名', + variableValue: '变量值', + code: '代码', + }, }, singleRun: { testRun: '测试运行 ',