feat: workflow new nodes (#4683)
Co-authored-by: Joel <iamjoel007@gmail.com> Co-authored-by: Patryk Garstecki <patryk20120@yahoo.pl> Co-authored-by: Sebastian.W <thiner@gmail.com> Co-authored-by: 呆萌闷油瓶 <253605712@qq.com> Co-authored-by: takatost <takatost@users.noreply.github.com> Co-authored-by: rechardwang <wh_goodjob@163.com> Co-authored-by: Nite Knite <nkCoding@gmail.com> Co-authored-by: Chenhe Gu <guchenhe@gmail.com> Co-authored-by: Joshua <138381132+joshua20231026@users.noreply.github.com> Co-authored-by: Weaxs <459312872@qq.com> Co-authored-by: Ikko Eltociear Ashimine <eltociear@gmail.com> Co-authored-by: leejoo0 <81673835+leejoo0@users.noreply.github.com> Co-authored-by: JzoNg <jzongcode@gmail.com> Co-authored-by: sino <sino2322@gmail.com> Co-authored-by: Vikey Chen <vikeytk@gmail.com> Co-authored-by: wanghl <Wang-HL@users.noreply.github.com> Co-authored-by: Haolin Wang-汪皓临 <haolin.wang@atlaslovestravel.com> Co-authored-by: Zixuan Cheng <61724187+Theysua@users.noreply.github.com> Co-authored-by: crazywoola <100913391+crazywoola@users.noreply.github.com> Co-authored-by: Bowen Liang <bowenliang@apache.org> Co-authored-by: Bowen Liang <liangbowen@gf.com.cn> Co-authored-by: fanghongtai <42790567+fanghongtai@users.noreply.github.com> Co-authored-by: wxfanghongtai <wxfanghongtai@gf.com.cn> Co-authored-by: Matri <qjp@bithuman.io> Co-authored-by: Benjamin <benjaminx@gmail.com>
This commit is contained in:
@@ -1,14 +1,16 @@
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import useNodeInfo from './use-node-info'
|
||||
import {
|
||||
useIsChatMode,
|
||||
useWorkflow,
|
||||
} from '@/app/components/workflow/hooks'
|
||||
import { toNodeOutputVars } from '@/app/components/workflow/nodes/_base/components/variable/utils'
|
||||
import { toNodeAvailableVars } from '@/app/components/workflow/nodes/_base/components/variable/utils'
|
||||
import type { ValueSelector, Var } from '@/app/components/workflow/types'
|
||||
|
||||
type Params = {
|
||||
onlyLeafNodeVar?: boolean
|
||||
filterVar: (payload: Var, selector: ValueSelector) => boolean
|
||||
}
|
||||
|
||||
const useAvailableVarList = (nodeId: string, {
|
||||
onlyLeafNodeVar,
|
||||
filterVar,
|
||||
@@ -16,14 +18,29 @@ const useAvailableVarList = (nodeId: string, {
|
||||
onlyLeafNodeVar: false,
|
||||
filterVar: () => true,
|
||||
}) => {
|
||||
const { t } = useTranslation()
|
||||
|
||||
const { getTreeLeafNodes, getBeforeNodesInSameBranch } = useWorkflow()
|
||||
const isChatMode = useIsChatMode()
|
||||
|
||||
const availableNodes = onlyLeafNodeVar ? getTreeLeafNodes(nodeId) : getBeforeNodesInSameBranch(nodeId)
|
||||
const availableVars = toNodeOutputVars(availableNodes, isChatMode, filterVar)
|
||||
|
||||
const {
|
||||
parentNode: iterationNode,
|
||||
} = useNodeInfo(nodeId)
|
||||
|
||||
const availableVars = toNodeAvailableVars({
|
||||
parentNode: iterationNode,
|
||||
t,
|
||||
beforeNodes: availableNodes,
|
||||
isChatMode,
|
||||
filterVar,
|
||||
})
|
||||
|
||||
return {
|
||||
availableVars,
|
||||
availableNodes,
|
||||
availableNodesWithParent: iterationNode ? [...availableNodes, iterationNode] : availableNodes,
|
||||
}
|
||||
}
|
||||
|
||||
|
@@ -0,0 +1,20 @@
|
||||
import { useStoreApi } from 'reactflow'
|
||||
|
||||
const useNodeInfo = (nodeId: string) => {
|
||||
const store = useStoreApi()
|
||||
const {
|
||||
getNodes,
|
||||
} = store.getState()
|
||||
const allNodes = getNodes()
|
||||
const node = allNodes.find(n => n.id === nodeId)
|
||||
const isInIteration = !!node?.data.isInIteration
|
||||
const parentNodeId = node?.parentId
|
||||
const parentNode = allNodes.find(n => n.id === parentNodeId)
|
||||
return {
|
||||
node,
|
||||
isInIteration,
|
||||
parentNode,
|
||||
}
|
||||
}
|
||||
|
||||
export default useNodeInfo
|
@@ -1,6 +1,7 @@
|
||||
import { useEffect, useState } from 'react'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import { unionBy } from 'lodash-es'
|
||||
import produce from 'immer'
|
||||
import {
|
||||
useIsChatMode,
|
||||
useNodeDataUpdate,
|
||||
@@ -11,7 +12,7 @@ import { getNodeInfoById, isSystemVar, toNodeOutputVars } from '@/app/components
|
||||
import type { CommonNodeType, InputVar, ValueSelector, Var, Variable } from '@/app/components/workflow/types'
|
||||
import { BlockEnum, InputVarType, NodeRunningStatus, VarType } from '@/app/components/workflow/types'
|
||||
import { useStore as useAppStore } from '@/app/components/app/store'
|
||||
import { singleNodeRun } from '@/service/workflow'
|
||||
import { getIterationSingleNodeRunUrl, singleNodeRun } from '@/service/workflow'
|
||||
import Toast from '@/app/components/base/toast'
|
||||
import LLMDefault from '@/app/components/workflow/nodes/llm/default'
|
||||
import KnowledgeRetrievalDefault from '@/app/components/workflow/nodes/knowledge-retrieval/default'
|
||||
@@ -22,7 +23,12 @@ import QuestionClassifyDefault from '@/app/components/workflow/nodes/question-cl
|
||||
import HTTPDefault from '@/app/components/workflow/nodes/http/default'
|
||||
import ToolDefault from '@/app/components/workflow/nodes/tool/default'
|
||||
import VariableAssigner from '@/app/components/workflow/nodes/variable-assigner/default'
|
||||
import ParameterExtractorDefault from '@/app/components/workflow/nodes/parameter-extractor/default'
|
||||
import IterationDefault from '@/app/components/workflow/nodes/iteration/default'
|
||||
import { ssePost } from '@/service/base'
|
||||
|
||||
import { getInputVars as doGetInputVars } from '@/app/components/base/prompt-editor/constants'
|
||||
import type { NodeTracing } from '@/types/workflow'
|
||||
const { checkValid: checkLLMValid } = LLMDefault
|
||||
const { checkValid: checkKnowledgeRetrievalValid } = KnowledgeRetrievalDefault
|
||||
const { checkValid: checkIfElseValid } = IfElseDefault
|
||||
@@ -32,6 +38,8 @@ const { checkValid: checkQuestionClassifyValid } = QuestionClassifyDefault
|
||||
const { checkValid: checkHttpValid } = HTTPDefault
|
||||
const { checkValid: checkToolValid } = ToolDefault
|
||||
const { checkValid: checkVariableAssignerValid } = VariableAssigner
|
||||
const { checkValid: checkParameterExtractorValid } = ParameterExtractorDefault
|
||||
const { checkValid: checkIterationValid } = IterationDefault
|
||||
|
||||
const checkValidFns: Record<BlockEnum, Function> = {
|
||||
[BlockEnum.LLM]: checkLLMValid,
|
||||
@@ -43,6 +51,9 @@ const checkValidFns: Record<BlockEnum, Function> = {
|
||||
[BlockEnum.HttpRequest]: checkHttpValid,
|
||||
[BlockEnum.Tool]: checkToolValid,
|
||||
[BlockEnum.VariableAssigner]: checkVariableAssignerValid,
|
||||
[BlockEnum.VariableAggregator]: checkVariableAssignerValid,
|
||||
[BlockEnum.ParameterExtractor]: checkParameterExtractorValid,
|
||||
[BlockEnum.Iteration]: checkIterationValid,
|
||||
} as any
|
||||
|
||||
type Params<T> = {
|
||||
@@ -50,6 +61,7 @@ type Params<T> = {
|
||||
data: CommonNodeType<T>
|
||||
defaultRunInputData: Record<string, any>
|
||||
moreDataForCheckValid?: any
|
||||
iteratorInputKey?: string
|
||||
}
|
||||
|
||||
const varTypeToInputVarType = (type: VarType, {
|
||||
@@ -78,13 +90,16 @@ const useOneStepRun = <T>({
|
||||
data,
|
||||
defaultRunInputData,
|
||||
moreDataForCheckValid,
|
||||
iteratorInputKey,
|
||||
}: Params<T>) => {
|
||||
const { t } = useTranslation()
|
||||
const { getBeforeNodesInSameBranch } = useWorkflow() as any
|
||||
const { getBeforeNodesInSameBranch, getBeforeNodesInSameBranchIncludeParent } = useWorkflow() as any
|
||||
const isChatMode = useIsChatMode()
|
||||
const isIteration = data.type === BlockEnum.Iteration
|
||||
|
||||
const availableNodes = getBeforeNodesInSameBranch(id)
|
||||
const allOutputVars = toNodeOutputVars(getBeforeNodesInSameBranch(id), isChatMode)
|
||||
const availableNodesIncludeParent = getBeforeNodesInSameBranchIncludeParent(id)
|
||||
const allOutputVars = toNodeOutputVars(availableNodes, isChatMode)
|
||||
const getVar = (valueSelector: ValueSelector): Var | undefined => {
|
||||
let res: Var | undefined
|
||||
const isSystem = valueSelector[0] === 'sys'
|
||||
@@ -95,14 +110,17 @@ const useOneStepRun = <T>({
|
||||
return targetVar.vars.find(item => item.variable.split('.')[1] === valueSelector[1])
|
||||
|
||||
let curr: any = targetVar.vars
|
||||
if (!curr)
|
||||
return
|
||||
|
||||
valueSelector.slice(1).forEach((key, i) => {
|
||||
const isLast = i === valueSelector.length - 2
|
||||
curr = curr.find((v: any) => v.variable === key)
|
||||
curr = curr?.find((v: any) => v.variable === key)
|
||||
if (isLast) {
|
||||
res = curr
|
||||
}
|
||||
else {
|
||||
if (curr.type === VarType.object)
|
||||
if (curr?.type === VarType.object)
|
||||
curr = curr.children
|
||||
}
|
||||
})
|
||||
@@ -113,11 +131,14 @@ const useOneStepRun = <T>({
|
||||
const checkValid = checkValidFns[data.type]
|
||||
const appId = useAppStore.getState().appDetail?.id
|
||||
const [runInputData, setRunInputData] = useState<Record<string, any>>(defaultRunInputData || {})
|
||||
const iterationTimes = iteratorInputKey ? runInputData[iteratorInputKey].length : 0
|
||||
const [runResult, setRunResult] = useState<any>(null)
|
||||
|
||||
const { handleNodeDataUpdate }: { handleNodeDataUpdate: (data: any) => void } = useNodeDataUpdate()
|
||||
const [canShowSingleRun, setCanShowSingleRun] = useState(false)
|
||||
const isShowSingleRun = data._isSingleRun && canShowSingleRun
|
||||
const [iterationRunResult, setIterationRunResult] = useState<NodeTracing[][]>([])
|
||||
|
||||
useEffect(() => {
|
||||
if (!checkValid) {
|
||||
setCanShowSingleRun(true)
|
||||
@@ -152,6 +173,15 @@ const useOneStepRun = <T>({
|
||||
},
|
||||
})
|
||||
}
|
||||
const showSingleRun = () => {
|
||||
handleNodeDataUpdate({
|
||||
id,
|
||||
data: {
|
||||
...data,
|
||||
_isSingleRun: true,
|
||||
},
|
||||
})
|
||||
}
|
||||
const runningStatus = data._singleRunningStatus || NodeRunningStatus.NotStart
|
||||
const isCompleted = runningStatus === NodeRunningStatus.Succeeded || runningStatus === NodeRunningStatus.Failed
|
||||
|
||||
@@ -165,34 +195,117 @@ const useOneStepRun = <T>({
|
||||
})
|
||||
let res: any
|
||||
try {
|
||||
res = await singleNodeRun(appId!, id, { inputs: submitData }) as any
|
||||
if (!isIteration) {
|
||||
res = await singleNodeRun(appId!, id, { inputs: submitData }) as any
|
||||
}
|
||||
else {
|
||||
setIterationRunResult([])
|
||||
let _iterationResult: NodeTracing[][] = []
|
||||
let _runResult: any = null
|
||||
ssePost(
|
||||
getIterationSingleNodeRunUrl(isChatMode, appId!, id),
|
||||
{ body: { inputs: submitData } },
|
||||
{
|
||||
onWorkflowStarted: () => {
|
||||
},
|
||||
onWorkflowFinished: (params) => {
|
||||
handleNodeDataUpdate({
|
||||
id,
|
||||
data: {
|
||||
...data,
|
||||
_singleRunningStatus: NodeRunningStatus.Succeeded,
|
||||
},
|
||||
})
|
||||
const { data: iterationData } = params
|
||||
_runResult.created_by = iterationData.created_by.name
|
||||
setRunResult(_runResult)
|
||||
},
|
||||
onIterationNext: () => {
|
||||
// iteration next trigger time is triggered one more time than iterationTimes
|
||||
if (_iterationResult.length >= iterationTimes!)
|
||||
return
|
||||
|
||||
const newIterationRunResult = produce(_iterationResult, (draft) => {
|
||||
draft.push([])
|
||||
})
|
||||
_iterationResult = newIterationRunResult
|
||||
setIterationRunResult(newIterationRunResult)
|
||||
},
|
||||
onIterationFinish: (params) => {
|
||||
_runResult = params.data
|
||||
setRunResult(_runResult)
|
||||
},
|
||||
onNodeStarted: (params) => {
|
||||
const newIterationRunResult = produce(_iterationResult, (draft) => {
|
||||
draft[draft.length - 1].push({
|
||||
...params.data,
|
||||
status: NodeRunningStatus.Running,
|
||||
} as NodeTracing)
|
||||
})
|
||||
_iterationResult = newIterationRunResult
|
||||
setIterationRunResult(newIterationRunResult)
|
||||
},
|
||||
onNodeFinished: (params) => {
|
||||
const iterationRunResult = _iterationResult
|
||||
|
||||
const { data } = params
|
||||
const currentIndex = iterationRunResult[iterationRunResult.length - 1].findIndex(trace => trace.node_id === data.node_id)
|
||||
const newIterationRunResult = produce(iterationRunResult, (draft) => {
|
||||
if (currentIndex > -1) {
|
||||
draft[draft.length - 1][currentIndex] = {
|
||||
...data,
|
||||
status: NodeRunningStatus.Succeeded,
|
||||
} as NodeTracing
|
||||
}
|
||||
})
|
||||
_iterationResult = newIterationRunResult
|
||||
setIterationRunResult(newIterationRunResult)
|
||||
},
|
||||
onError: () => {
|
||||
handleNodeDataUpdate({
|
||||
id,
|
||||
data: {
|
||||
...data,
|
||||
_singleRunningStatus: NodeRunningStatus.Failed,
|
||||
},
|
||||
})
|
||||
},
|
||||
},
|
||||
)
|
||||
}
|
||||
if (res.error)
|
||||
throw new Error(res.error)
|
||||
}
|
||||
catch (e: any) {
|
||||
if (!isIteration) {
|
||||
handleNodeDataUpdate({
|
||||
id,
|
||||
data: {
|
||||
...data,
|
||||
_singleRunningStatus: NodeRunningStatus.Failed,
|
||||
},
|
||||
})
|
||||
return false
|
||||
}
|
||||
}
|
||||
finally {
|
||||
if (!isIteration) {
|
||||
setRunResult({
|
||||
...res,
|
||||
total_tokens: res.execution_metadata?.total_tokens || 0,
|
||||
created_by: res.created_by_account?.name || '',
|
||||
})
|
||||
}
|
||||
}
|
||||
if (!isIteration) {
|
||||
handleNodeDataUpdate({
|
||||
id,
|
||||
data: {
|
||||
...data,
|
||||
_singleRunningStatus: NodeRunningStatus.Failed,
|
||||
_singleRunningStatus: NodeRunningStatus.Succeeded,
|
||||
},
|
||||
})
|
||||
return false
|
||||
}
|
||||
finally {
|
||||
setRunResult({
|
||||
...res,
|
||||
total_tokens: res.execution_metadata?.total_tokens || 0,
|
||||
created_by: res.created_by_account?.name || '',
|
||||
})
|
||||
}
|
||||
handleNodeDataUpdate({
|
||||
id,
|
||||
data: {
|
||||
...data,
|
||||
_singleRunningStatus: NodeRunningStatus.Succeeded,
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
const handleStop = () => {
|
||||
@@ -241,12 +354,12 @@ const useOneStepRun = <T>({
|
||||
})
|
||||
|
||||
const variables = unionBy(valueSelectors, item => item.join('.')).map((item) => {
|
||||
const varInfo = getNodeInfoById(availableNodes, item[0])?.data
|
||||
const varInfo = getNodeInfoById(availableNodesIncludeParent, item[0])?.data
|
||||
|
||||
return {
|
||||
label: {
|
||||
nodeType: varInfo?.type,
|
||||
nodeName: varInfo?.title || availableNodes[0]?.data.title, // default start node title
|
||||
nodeName: varInfo?.title || availableNodesIncludeParent[0]?.data.title, // default start node title
|
||||
variable: isSystemVar(item) ? item.join('.') : item[item.length - 1],
|
||||
},
|
||||
variable: `#${item.join('.')}#`,
|
||||
@@ -261,6 +374,7 @@ const useOneStepRun = <T>({
|
||||
return {
|
||||
isShowSingleRun,
|
||||
hideSingleRun,
|
||||
showSingleRun,
|
||||
toVarInputs,
|
||||
getInputVars,
|
||||
runningStatus,
|
||||
@@ -270,6 +384,7 @@ const useOneStepRun = <T>({
|
||||
runInputData,
|
||||
setRunInputData,
|
||||
runResult,
|
||||
iterationRunResult,
|
||||
}
|
||||
}
|
||||
|
||||
|
Reference in New Issue
Block a user