mirror of https://github.com/langgenius/dify.git
feat: enhance human input handling by adding filled data support
This commit is contained in:
parent
d478822fe1
commit
9ec127ea8f
|
|
@ -4,14 +4,11 @@ import * as React from 'react'
|
|||
import { useCallback, useState } from 'react'
|
||||
import Button from '@/app/components/base/button'
|
||||
import ContentItem from './content-item'
|
||||
import ExpirationTime from './expiration-time'
|
||||
import { getButtonStyle, initializeInputs, splitByOutputVar } from './utils'
|
||||
|
||||
const HumanInputForm = ({
|
||||
formData,
|
||||
showTimeout,
|
||||
onSubmit,
|
||||
expirationTime,
|
||||
}: HumanInputFormProps) => {
|
||||
const formID = formData.form_id
|
||||
const defaultInputs = initializeInputs(formData.inputs)
|
||||
|
|
@ -56,9 +53,6 @@ const HumanInputForm = ({
|
|||
</Button>
|
||||
))}
|
||||
</div>
|
||||
{showTimeout && typeof expirationTime === 'number' && (
|
||||
<ExpirationTime expirationTime={expirationTime} />
|
||||
)}
|
||||
</>
|
||||
)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,7 +1,9 @@
|
|||
import type { HumanInputContentProps } from './type'
|
||||
import { Trans, useTranslation } from 'react-i18next'
|
||||
import Divider from '@/app/components/base/divider'
|
||||
import { TriggerAll } from '@/app/components/base/icons/src/vender/workflow'
|
||||
import { useSelector as useAppSelector } from '@/context/app-context'
|
||||
import ExpirationTime from './expiration-time'
|
||||
import HumanInputForm from './human-input-form'
|
||||
|
||||
const HumanInputContent = ({
|
||||
|
|
@ -10,6 +12,8 @@ const HumanInputContent = ({
|
|||
isEmailDebugMode = false,
|
||||
showDebugModeTip = false,
|
||||
showTimeout = false,
|
||||
executedAction,
|
||||
expirationTime,
|
||||
onSubmit,
|
||||
}: HumanInputContentProps) => {
|
||||
const { t } = useTranslation()
|
||||
|
|
@ -19,9 +23,9 @@ const HumanInputContent = ({
|
|||
<>
|
||||
<HumanInputForm
|
||||
formData={formData}
|
||||
showTimeout={showTimeout}
|
||||
onSubmit={onSubmit}
|
||||
/>
|
||||
{/* Tips */}
|
||||
{(showEmailTip || showDebugModeTip) && (
|
||||
<>
|
||||
<Divider className="!my-2 w-[30px]" />
|
||||
|
|
@ -43,6 +47,25 @@ const HumanInputContent = ({
|
|||
</div>
|
||||
</>
|
||||
)}
|
||||
{/* Timeout */}
|
||||
{showTimeout && typeof expirationTime === 'number' && (
|
||||
<ExpirationTime expirationTime={expirationTime} />
|
||||
)}
|
||||
{/* Executed Action */}
|
||||
{executedAction && (
|
||||
<div className="flex flex-col gap-y-1 py-1">
|
||||
<Divider className="mb-2 mt-1 w-[30px]" />
|
||||
<div className="system-xs-regular flex items-center gap-x-1 text-text-tertiary">
|
||||
<TriggerAll className="size-3.5 shrink-0" />
|
||||
<Trans
|
||||
i18nKey="nodes.humanInput.userActions.triggered"
|
||||
ns="workflow"
|
||||
components={{ strong: <span className="system-xs-medium text-text-secondary"></span> }}
|
||||
values={{ actionName: executedAction.title }}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
</>
|
||||
)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -19,8 +19,6 @@ export type HumanInputContentProps = {
|
|||
|
||||
export type HumanInputFormProps = {
|
||||
formData: HumanInputFormData
|
||||
showTimeout?: boolean
|
||||
expirationTime?: number
|
||||
onSubmit?: (formID: string, data: any) => Promise<void>
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -6,8 +6,10 @@ import type {
|
|||
ChatConfig,
|
||||
ChatItem,
|
||||
} from '../../types'
|
||||
import type { ExecutedAction } from './human-input-content/type'
|
||||
import type { DeliveryMethod } from '@/app/components/workflow/nodes/human-input/types'
|
||||
import type { AppData } from '@/models/share'
|
||||
import type { HumanInputFormData } from '@/types/workflow'
|
||||
import { memo, useCallback, useEffect, useMemo, useRef, useState } from 'react'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import { EditTitle } from '@/app/components/app/annotation/edit-annotation-modal/edit-item'
|
||||
|
|
@ -70,6 +72,7 @@ const Answer: FC<AnswerProps> = ({
|
|||
allFiles,
|
||||
message_files,
|
||||
humanInputFormData,
|
||||
humanInputFormFilledData,
|
||||
} = item
|
||||
const hasAgentThoughts = !!agent_thoughts?.length
|
||||
|
||||
|
|
@ -101,6 +104,30 @@ const Answer: FC<AnswerProps> = ({
|
|||
}
|
||||
}, [getHumanInputNodeData, humanInputFormData?.node_id])
|
||||
|
||||
const filledFormData = useMemo((): HumanInputFormData | undefined => {
|
||||
if (!humanInputFormFilledData)
|
||||
return
|
||||
return {
|
||||
form_id: '',
|
||||
node_id: humanInputFormFilledData.node_id,
|
||||
node_title: '',
|
||||
form_content: humanInputFormFilledData.rendered_content,
|
||||
inputs: [],
|
||||
actions: [],
|
||||
web_app_form_token: '',
|
||||
resolved_placeholder_values: {},
|
||||
}
|
||||
}, [humanInputFormFilledData])
|
||||
|
||||
const executedAction = useMemo((): ExecutedAction | undefined => {
|
||||
if (!humanInputFormFilledData)
|
||||
return
|
||||
return {
|
||||
id: humanInputFormFilledData.action_id,
|
||||
title: humanInputFormFilledData.action_text,
|
||||
}
|
||||
}, [humanInputFormFilledData])
|
||||
|
||||
const getContainerWidth = () => {
|
||||
if (containerRef.current)
|
||||
setContainerWidth(containerRef.current?.clientWidth + 16)
|
||||
|
|
@ -200,15 +227,25 @@ const Answer: FC<AnswerProps> = ({
|
|||
<BasicContent item={item} />
|
||||
)
|
||||
}
|
||||
{humanInputFormData && (
|
||||
<HumanInputContent
|
||||
formData={humanInputFormData}
|
||||
showEmailTip={deliveryMethodsConfig.showEmailTip}
|
||||
isEmailDebugMode={deliveryMethodsConfig.isEmailDebugMode}
|
||||
showDebugModeTip={deliveryMethodsConfig.showDebugModeTip}
|
||||
onSubmit={onHumanInputFormSubmit}
|
||||
/>
|
||||
)}
|
||||
{
|
||||
humanInputFormData && (
|
||||
<HumanInputContent
|
||||
formData={humanInputFormData}
|
||||
showEmailTip={deliveryMethodsConfig.showEmailTip}
|
||||
isEmailDebugMode={deliveryMethodsConfig.isEmailDebugMode}
|
||||
showDebugModeTip={deliveryMethodsConfig.showDebugModeTip}
|
||||
onSubmit={onHumanInputFormSubmit}
|
||||
/>
|
||||
)
|
||||
}
|
||||
{
|
||||
filledFormData && (
|
||||
<HumanInputContent
|
||||
formData={filledFormData}
|
||||
executedAction={executedAction}
|
||||
/>
|
||||
)
|
||||
}
|
||||
{
|
||||
(hasAgentThoughts) && (
|
||||
<AgentContent
|
||||
|
|
|
|||
|
|
@ -2,7 +2,11 @@ import type { FileEntity } from '@/app/components/base/file-uploader/types'
|
|||
import type { TypeWithI18N } from '@/app/components/header/account-setting/model-provider-page/declarations'
|
||||
import type { InputVarType } from '@/app/components/workflow/types'
|
||||
import type { Annotation, MessageRating } from '@/models/log'
|
||||
import type { FileResponse, HumanInputFormData } from '@/types/workflow'
|
||||
import type {
|
||||
FileResponse,
|
||||
HumanInputFormData,
|
||||
HumanInputFormFilledData,
|
||||
} from '@/types/workflow'
|
||||
|
||||
export type MessageMore = {
|
||||
time: string
|
||||
|
|
@ -106,6 +110,7 @@ export type IChatItem = {
|
|||
nextSibling?: string
|
||||
// for human input
|
||||
humanInputFormData?: HumanInputFormData
|
||||
humanInputFormFilledData?: HumanInputFormFilledData
|
||||
}
|
||||
|
||||
export type Metadata = {
|
||||
|
|
|
|||
|
|
@ -181,6 +181,7 @@ export const useWorkflowRun = () => {
|
|||
onError,
|
||||
onWorkflowPaused,
|
||||
onHumanInputRequired,
|
||||
onHumanInputFormFilled,
|
||||
onCompleted,
|
||||
...restCallback
|
||||
} = callback || {}
|
||||
|
|
@ -610,6 +611,7 @@ export const useWorkflowRun = () => {
|
|||
baseSseOptions.onTextReplace,
|
||||
baseSseOptions.onAgentLog,
|
||||
baseSseOptions.onHumanInputRequired,
|
||||
baseSseOptions.onHumanInputFormFilled,
|
||||
baseSseOptions.onWorkflowPaused,
|
||||
baseSseOptions.onDataSourceNodeProcessing,
|
||||
baseSseOptions.onDataSourceNodeCompleted,
|
||||
|
|
@ -792,6 +794,10 @@ export const useWorkflowRun = () => {
|
|||
if (onHumanInputRequired)
|
||||
onHumanInputRequired(params)
|
||||
},
|
||||
onHumanInputFormFilled: (params) => {
|
||||
if (onHumanInputFormFilled)
|
||||
onHumanInputFormFilled(params)
|
||||
},
|
||||
...restCallback,
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -539,6 +539,16 @@ export const useChat = (
|
|||
})
|
||||
}
|
||||
},
|
||||
onHumanInputFormFilled: ({ data }) => {
|
||||
delete responseItem.humanInputFormData
|
||||
responseItem.humanInputFormFilledData = data
|
||||
updateCurrentQAOnTree({
|
||||
placeholderQuestionId,
|
||||
questionItem,
|
||||
responseItem,
|
||||
parentId: params.parent_message_id,
|
||||
})
|
||||
},
|
||||
onWorkflowPaused: ({ data: _data }) => {
|
||||
responseItem.workflowProcess!.status = WorkflowRunningStatus.Paused
|
||||
updateCurrentQAOnTree({
|
||||
|
|
|
|||
|
|
@ -588,6 +588,7 @@
|
|||
"nodes.humanInput.userActions.emptyTip": "Click the '+' button to add user actions",
|
||||
"nodes.humanInput.userActions.title": "User Actions",
|
||||
"nodes.humanInput.userActions.tooltip": "Define buttons that users can click to respond to this form. Each button can trigger different workflow paths.",
|
||||
"nodes.humanInput.userActions.triggered": "<strong>{{actionName}}</strong> has been triggered",
|
||||
"nodes.ifElse.addCondition": "Add Condition",
|
||||
"nodes.ifElse.addSubVariable": "Sub Variable",
|
||||
"nodes.ifElse.and": "and",
|
||||
|
|
|
|||
|
|
@ -588,6 +588,7 @@
|
|||
"nodes.humanInput.userActions.emptyTip": "点击 '+' 按钮添加用户操作",
|
||||
"nodes.humanInput.userActions.title": "用户操作",
|
||||
"nodes.humanInput.userActions.tooltip": "定义用户可以点击以响应此表单的按钮。每个按钮都可以触发不同的工作流路径。",
|
||||
"nodes.humanInput.userActions.triggered": "已触发<strong>{{actionName}}</strong>",
|
||||
"nodes.ifElse.addCondition": "添加条件",
|
||||
"nodes.ifElse.addSubVariable": "添加子变量",
|
||||
"nodes.ifElse.and": "and",
|
||||
|
|
|
|||
|
|
@ -8,6 +8,7 @@ import type {
|
|||
} from '@/types/pipeline'
|
||||
import type {
|
||||
AgentLogResponse,
|
||||
HumanInputFormFilledResponse,
|
||||
HumanInputRequiredResponse,
|
||||
IterationFinishedResponse,
|
||||
IterationNextResponse,
|
||||
|
|
@ -73,6 +74,7 @@ export type IOnLoopFinished = (workflowFinished: LoopFinishedResponse) => void
|
|||
export type IOnAgentLog = (agentLog: AgentLogResponse) => void
|
||||
|
||||
export type IOHumanInputRequired = (humanInputRequired: HumanInputRequiredResponse) => void
|
||||
export type IOnHumanInputFormFilled = (humanInputFormFilled: HumanInputFormFilledResponse) => void
|
||||
export type IOWorkflowPaused = (workflowPaused: WorkflowPausedResponse) => void
|
||||
export type IOnDataSourceNodeProcessing = (dataSourceNodeProcessing: DataSourceNodeProcessingResponse) => void
|
||||
export type IOnDataSourceNodeCompleted = (dataSourceNodeCompleted: DataSourceNodeCompletedResponse) => void
|
||||
|
|
@ -113,6 +115,7 @@ export type IOtherOptions = {
|
|||
onLoopFinish?: IOnLoopFinished
|
||||
onAgentLog?: IOnAgentLog
|
||||
onHumanInputRequired?: IOHumanInputRequired
|
||||
onHumanInputFormFilled?: IOnHumanInputFormFilled
|
||||
onWorkflowPaused?: IOWorkflowPaused
|
||||
|
||||
// Pipeline data source node run
|
||||
|
|
@ -197,6 +200,7 @@ export const handleStream = (
|
|||
onTextReplace?: IOnTextReplace,
|
||||
onAgentLog?: IOnAgentLog,
|
||||
onHumanInputRequired?: IOHumanInputRequired,
|
||||
onHumanInputFormFilled?: IOnHumanInputFormFilled,
|
||||
onWorkflowPaused?: IOWorkflowPaused,
|
||||
onDataSourceNodeProcessing?: IOnDataSourceNodeProcessing,
|
||||
onDataSourceNodeCompleted?: IOnDataSourceNodeCompleted,
|
||||
|
|
@ -322,6 +326,9 @@ export const handleStream = (
|
|||
else if (bufferObj.event === 'human_input_required') {
|
||||
onHumanInputRequired?.(bufferObj as HumanInputRequiredResponse)
|
||||
}
|
||||
else if (bufferObj.event === 'human_input_form_filled') {
|
||||
onHumanInputFormFilled?.(bufferObj as HumanInputFormFilledResponse)
|
||||
}
|
||||
else if (bufferObj.event === 'workflow_paused') {
|
||||
onWorkflowPaused?.(bufferObj as WorkflowPausedResponse)
|
||||
}
|
||||
|
|
@ -448,6 +455,7 @@ export const ssePost = async (
|
|||
onLoopNext,
|
||||
onLoopFinish,
|
||||
onHumanInputRequired,
|
||||
onHumanInputFormFilled,
|
||||
onWorkflowPaused,
|
||||
onDataSourceNodeProcessing,
|
||||
onDataSourceNodeCompleted,
|
||||
|
|
@ -551,6 +559,7 @@ export const ssePost = async (
|
|||
onTextReplace,
|
||||
onAgentLog,
|
||||
onHumanInputRequired,
|
||||
onHumanInputFormFilled,
|
||||
onWorkflowPaused,
|
||||
onDataSourceNodeProcessing,
|
||||
onDataSourceNodeCompleted,
|
||||
|
|
@ -598,6 +607,7 @@ export const sseGet = async (
|
|||
onLoopNext,
|
||||
onLoopFinish,
|
||||
onHumanInputRequired,
|
||||
onHumanInputFormFilled,
|
||||
onWorkflowPaused,
|
||||
onDataSourceNodeProcessing,
|
||||
onDataSourceNodeCompleted,
|
||||
|
|
@ -694,6 +704,7 @@ export const sseGet = async (
|
|||
onTextReplace,
|
||||
onAgentLog,
|
||||
onHumanInputRequired,
|
||||
onHumanInputFormFilled,
|
||||
onWorkflowPaused,
|
||||
onDataSourceNodeProcessing,
|
||||
onDataSourceNodeCompleted,
|
||||
|
|
|
|||
|
|
@ -331,6 +331,20 @@ export type HumanInputRequiredResponse = {
|
|||
data: HumanInputFormData
|
||||
}
|
||||
|
||||
export type HumanInputFormFilledData = {
|
||||
node_id: string
|
||||
rendered_content: string
|
||||
action_id: string
|
||||
action_text: string
|
||||
}
|
||||
|
||||
export type HumanInputFormFilledResponse = {
|
||||
task_id: string
|
||||
workflow_run_id: string
|
||||
event: string
|
||||
data: HumanInputFormFilledData
|
||||
}
|
||||
|
||||
export type WorkflowRunHistory = {
|
||||
id: string
|
||||
version: string
|
||||
|
|
|
|||
Loading…
Reference in New Issue