mirror of
https://github.com/langgenius/dify.git
synced 2026-05-03 00:48:04 +08:00
feat: Human Input Node (#32060)
The frontend and backend implementation for the human input node. Co-authored-by: twwu <twwu@dify.ai> Co-authored-by: JzoNg <jzongcode@gmail.com> Co-authored-by: yyh <92089059+lyzno1@users.noreply.github.com> Co-authored-by: zhsama <torvalds@linux.do>
This commit is contained in:
@ -2,6 +2,9 @@ export * from './use-workflow-agent-log'
|
||||
export * from './use-workflow-failed'
|
||||
export * from './use-workflow-finished'
|
||||
export * from './use-workflow-node-finished'
|
||||
export * from './use-workflow-node-human-input-form-filled'
|
||||
export * from './use-workflow-node-human-input-form-timeout'
|
||||
export * from './use-workflow-node-human-input-required'
|
||||
export * from './use-workflow-node-iteration-finished'
|
||||
export * from './use-workflow-node-iteration-next'
|
||||
export * from './use-workflow-node-iteration-started'
|
||||
@ -10,6 +13,7 @@ export * from './use-workflow-node-loop-next'
|
||||
export * from './use-workflow-node-loop-started'
|
||||
export * from './use-workflow-node-retry'
|
||||
export * from './use-workflow-node-started'
|
||||
export * from './use-workflow-paused'
|
||||
export * from './use-workflow-started'
|
||||
export * from './use-workflow-text-chunk'
|
||||
export * from './use-workflow-text-replace'
|
||||
|
||||
@ -49,6 +49,8 @@ export const useWorkflowNodeFinished = () => {
|
||||
|
||||
if (data.node_type === BlockEnum.QuestionClassifier)
|
||||
currentNode.data._runningBranchId = data?.outputs?.class_id
|
||||
if (data.node_type === BlockEnum.HumanInput)
|
||||
currentNode.data._runningBranchId = data?.outputs?.__action_id
|
||||
}
|
||||
})
|
||||
setNodes(newNodes)
|
||||
|
||||
@ -0,0 +1,34 @@
|
||||
import type { HumanInputFormFilledResponse } from '@/types/workflow'
|
||||
import { produce } from 'immer'
|
||||
import { useCallback } from 'react'
|
||||
import { useWorkflowStore } from '@/app/components/workflow/store'
|
||||
|
||||
export const useWorkflowNodeHumanInputFormFilled = () => {
|
||||
const workflowStore = useWorkflowStore()
|
||||
|
||||
const handleWorkflowNodeHumanInputFormFilled = useCallback((params: HumanInputFormFilledResponse) => {
|
||||
const { data } = params
|
||||
const {
|
||||
workflowRunningData,
|
||||
setWorkflowRunningData,
|
||||
} = workflowStore.getState()
|
||||
|
||||
const newWorkflowRunningData = produce(workflowRunningData!, (draft) => {
|
||||
if (draft.humanInputFormDataList?.length) {
|
||||
const currentFormIndex = draft.humanInputFormDataList.findIndex(item => item.node_id === data.node_id)
|
||||
draft.humanInputFormDataList.splice(currentFormIndex, 1)
|
||||
}
|
||||
if (!draft.humanInputFilledFormDataList) {
|
||||
draft.humanInputFilledFormDataList = [data]
|
||||
}
|
||||
else {
|
||||
draft.humanInputFilledFormDataList.push(data)
|
||||
}
|
||||
})
|
||||
setWorkflowRunningData(newWorkflowRunningData)
|
||||
}, [workflowStore])
|
||||
|
||||
return {
|
||||
handleWorkflowNodeHumanInputFormFilled,
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,28 @@
|
||||
import type { HumanInputFormTimeoutResponse } from '@/types/workflow'
|
||||
import { produce } from 'immer'
|
||||
import { useCallback } from 'react'
|
||||
import { useWorkflowStore } from '@/app/components/workflow/store'
|
||||
|
||||
export const useWorkflowNodeHumanInputFormTimeout = () => {
|
||||
const workflowStore = useWorkflowStore()
|
||||
|
||||
const handleWorkflowNodeHumanInputFormTimeout = useCallback((params: HumanInputFormTimeoutResponse) => {
|
||||
const { data } = params
|
||||
const {
|
||||
workflowRunningData,
|
||||
setWorkflowRunningData,
|
||||
} = workflowStore.getState()
|
||||
|
||||
const newWorkflowRunningData = produce(workflowRunningData!, (draft) => {
|
||||
if (draft.humanInputFormDataList?.length) {
|
||||
const currentFormIndex = draft.humanInputFormDataList.findIndex(item => item.node_id === data.node_id)
|
||||
draft.humanInputFormDataList[currentFormIndex].expiration_time = data.expiration_time
|
||||
}
|
||||
})
|
||||
setWorkflowRunningData(newWorkflowRunningData)
|
||||
}, [workflowStore])
|
||||
|
||||
return {
|
||||
handleWorkflowNodeHumanInputFormTimeout,
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,60 @@
|
||||
import type { HumanInputRequiredResponse } from '@/types/workflow'
|
||||
import { produce } from 'immer'
|
||||
import { useCallback } from 'react'
|
||||
import {
|
||||
useStoreApi,
|
||||
} from 'reactflow'
|
||||
import { useWorkflowStore } from '@/app/components/workflow/store'
|
||||
import { NodeRunningStatus } from '@/app/components/workflow/types'
|
||||
|
||||
export const useWorkflowNodeHumanInputRequired = () => {
|
||||
const store = useStoreApi()
|
||||
const workflowStore = useWorkflowStore()
|
||||
|
||||
// Notice: Human input required !== Workflow Paused
|
||||
const handleWorkflowNodeHumanInputRequired = useCallback((params: HumanInputRequiredResponse) => {
|
||||
const { data } = params
|
||||
const {
|
||||
workflowRunningData,
|
||||
setWorkflowRunningData,
|
||||
} = workflowStore.getState()
|
||||
|
||||
const newWorkflowRunningData = produce(workflowRunningData!, (draft) => {
|
||||
if (!draft.humanInputFormDataList) {
|
||||
draft.humanInputFormDataList = [data]
|
||||
}
|
||||
else {
|
||||
const currentFormIndex = draft.humanInputFormDataList.findIndex(item => item.node_id === data.node_id)
|
||||
if (currentFormIndex > -1) {
|
||||
draft.humanInputFormDataList[currentFormIndex] = data
|
||||
}
|
||||
else {
|
||||
draft.humanInputFormDataList.push(data)
|
||||
}
|
||||
}
|
||||
const currentIndex = draft.tracing!.findIndex(item => item.node_id === data.node_id)
|
||||
if (currentIndex > -1) {
|
||||
draft.tracing![currentIndex] = {
|
||||
...draft.tracing![currentIndex],
|
||||
status: NodeRunningStatus.Paused,
|
||||
}
|
||||
}
|
||||
})
|
||||
setWorkflowRunningData(newWorkflowRunningData)
|
||||
|
||||
const {
|
||||
getNodes,
|
||||
setNodes,
|
||||
} = store.getState()
|
||||
const nodes = getNodes()
|
||||
const currentNodeIndex = nodes.findIndex(node => node.id === data.node_id)
|
||||
const newNodes = produce(nodes, (draft) => {
|
||||
draft[currentNodeIndex].data._runningStatus = NodeRunningStatus.Paused
|
||||
})
|
||||
setNodes(newNodes)
|
||||
}, [store, workflowStore])
|
||||
|
||||
return {
|
||||
handleWorkflowNodeHumanInputRequired,
|
||||
}
|
||||
}
|
||||
@ -33,12 +33,23 @@ export const useWorkflowNodeStarted = () => {
|
||||
transform,
|
||||
} = store.getState()
|
||||
const nodes = getNodes()
|
||||
setWorkflowRunningData(produce(workflowRunningData!, (draft) => {
|
||||
draft.tracing!.push({
|
||||
...data,
|
||||
status: NodeRunningStatus.Running,
|
||||
})
|
||||
}))
|
||||
const currentIndex = workflowRunningData?.tracing?.findIndex(item => item.node_id === data.node_id)
|
||||
if (currentIndex && currentIndex > -1) {
|
||||
setWorkflowRunningData(produce(workflowRunningData!, (draft) => {
|
||||
draft.tracing![currentIndex] = {
|
||||
...data,
|
||||
status: NodeRunningStatus.Running,
|
||||
}
|
||||
}))
|
||||
}
|
||||
else {
|
||||
setWorkflowRunningData(produce(workflowRunningData!, (draft) => {
|
||||
draft.tracing!.push({
|
||||
...data,
|
||||
status: NodeRunningStatus.Running,
|
||||
})
|
||||
}))
|
||||
}
|
||||
|
||||
const {
|
||||
setViewport,
|
||||
|
||||
@ -0,0 +1,26 @@
|
||||
import { produce } from 'immer'
|
||||
import { useCallback } from 'react'
|
||||
import { useWorkflowStore } from '@/app/components/workflow/store'
|
||||
import { WorkflowRunningStatus } from '@/app/components/workflow/types'
|
||||
|
||||
export const useWorkflowPaused = () => {
|
||||
const workflowStore = useWorkflowStore()
|
||||
|
||||
const handleWorkflowPaused = useCallback(() => {
|
||||
const {
|
||||
workflowRunningData,
|
||||
setWorkflowRunningData,
|
||||
} = workflowStore.getState()
|
||||
|
||||
setWorkflowRunningData(produce(workflowRunningData!, (draft) => {
|
||||
draft.result = {
|
||||
...draft.result,
|
||||
status: WorkflowRunningStatus.Paused,
|
||||
}
|
||||
}))
|
||||
}, [workflowStore])
|
||||
|
||||
return {
|
||||
handleWorkflowPaused,
|
||||
}
|
||||
}
|
||||
@ -3,6 +3,9 @@ import {
|
||||
useWorkflowFailed,
|
||||
useWorkflowFinished,
|
||||
useWorkflowNodeFinished,
|
||||
useWorkflowNodeHumanInputFormFilled,
|
||||
useWorkflowNodeHumanInputFormTimeout,
|
||||
useWorkflowNodeHumanInputRequired,
|
||||
useWorkflowNodeIterationFinished,
|
||||
useWorkflowNodeIterationNext,
|
||||
useWorkflowNodeIterationStarted,
|
||||
@ -11,6 +14,7 @@ import {
|
||||
useWorkflowNodeLoopStarted,
|
||||
useWorkflowNodeRetry,
|
||||
useWorkflowNodeStarted,
|
||||
useWorkflowPaused,
|
||||
useWorkflowStarted,
|
||||
useWorkflowTextChunk,
|
||||
useWorkflowTextReplace,
|
||||
@ -32,6 +36,10 @@ export const useWorkflowRunEvent = () => {
|
||||
const { handleWorkflowTextChunk } = useWorkflowTextChunk()
|
||||
const { handleWorkflowTextReplace } = useWorkflowTextReplace()
|
||||
const { handleWorkflowAgentLog } = useWorkflowAgentLog()
|
||||
const { handleWorkflowPaused } = useWorkflowPaused()
|
||||
const { handleWorkflowNodeHumanInputRequired } = useWorkflowNodeHumanInputRequired()
|
||||
const { handleWorkflowNodeHumanInputFormFilled } = useWorkflowNodeHumanInputFormFilled()
|
||||
const { handleWorkflowNodeHumanInputFormTimeout } = useWorkflowNodeHumanInputFormTimeout()
|
||||
|
||||
return {
|
||||
handleWorkflowStarted,
|
||||
@ -49,5 +57,9 @@ export const useWorkflowRunEvent = () => {
|
||||
handleWorkflowTextChunk,
|
||||
handleWorkflowTextReplace,
|
||||
handleWorkflowAgentLog,
|
||||
handleWorkflowPaused,
|
||||
handleWorkflowNodeHumanInputFormFilled,
|
||||
handleWorkflowNodeHumanInputRequired,
|
||||
handleWorkflowNodeHumanInputFormTimeout,
|
||||
}
|
||||
}
|
||||
|
||||
@ -22,6 +22,15 @@ export const useWorkflowStarted = () => {
|
||||
edges,
|
||||
setEdges,
|
||||
} = store.getState()
|
||||
if (workflowRunningData?.result?.status === WorkflowRunningStatus.Paused) {
|
||||
setWorkflowRunningData(produce(workflowRunningData!, (draft) => {
|
||||
draft.result = {
|
||||
...draft.result,
|
||||
status: WorkflowRunningStatus.Running,
|
||||
}
|
||||
}))
|
||||
return
|
||||
}
|
||||
setIterParallelLogMap(new Map())
|
||||
setWorkflowRunningData(produce(workflowRunningData!, (draft) => {
|
||||
draft.task_id = task_id
|
||||
@ -30,6 +39,7 @@ export const useWorkflowStarted = () => {
|
||||
...data,
|
||||
status: WorkflowRunningStatus.Running,
|
||||
}
|
||||
draft.resultText = ''
|
||||
}))
|
||||
const nodes = getNodes()
|
||||
const newNodes = produce(nodes, (draft) => {
|
||||
|
||||
Reference in New Issue
Block a user