diff --git a/web/app/components/base/chat/chat/answer/human-input-content/human-input-form.tsx b/web/app/components/base/chat/chat/answer/human-input-content/human-input-form.tsx index a05b29a018..95595c5b6d 100644 --- a/web/app/components/base/chat/chat/answer/human-input-content/human-input-form.tsx +++ b/web/app/components/base/chat/chat/answer/human-input-content/human-input-form.tsx @@ -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 = ({ ))} - {showTimeout && typeof expirationTime === 'number' && ( - - )} ) } diff --git a/web/app/components/base/chat/chat/answer/human-input-content/index.tsx b/web/app/components/base/chat/chat/answer/human-input-content/index.tsx index 87f69db468..052ee5f3e9 100644 --- a/web/app/components/base/chat/chat/answer/human-input-content/index.tsx +++ b/web/app/components/base/chat/chat/answer/human-input-content/index.tsx @@ -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 = ({ <> + {/* Tips */} {(showEmailTip || showDebugModeTip) && ( <> @@ -43,6 +47,25 @@ const HumanInputContent = ({ )} + {/* Timeout */} + {showTimeout && typeof expirationTime === 'number' && ( + + )} + {/* Executed Action */} + {executedAction && ( +
+ +
+ + }} + values={{ actionName: executedAction.title }} + /> +
+
+ )} ) } diff --git a/web/app/components/base/chat/chat/answer/human-input-content/type.ts b/web/app/components/base/chat/chat/answer/human-input-content/type.ts index 14528005be..63d5ef5d06 100644 --- a/web/app/components/base/chat/chat/answer/human-input-content/type.ts +++ b/web/app/components/base/chat/chat/answer/human-input-content/type.ts @@ -19,8 +19,6 @@ export type HumanInputContentProps = { export type HumanInputFormProps = { formData: HumanInputFormData - showTimeout?: boolean - expirationTime?: number onSubmit?: (formID: string, data: any) => Promise } diff --git a/web/app/components/base/chat/chat/answer/index.tsx b/web/app/components/base/chat/chat/answer/index.tsx index 39330c05bf..b9d84d8ecd 100644 --- a/web/app/components/base/chat/chat/answer/index.tsx +++ b/web/app/components/base/chat/chat/answer/index.tsx @@ -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 = ({ allFiles, message_files, humanInputFormData, + humanInputFormFilledData, } = item const hasAgentThoughts = !!agent_thoughts?.length @@ -101,6 +104,30 @@ const Answer: FC = ({ } }, [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 = ({ ) } - {humanInputFormData && ( - - )} + { + humanInputFormData && ( + + ) + } + { + filledFormData && ( + + ) + } { (hasAgentThoughts) && ( { 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, } diff --git a/web/app/components/workflow/panel/debug-and-preview/hooks.ts b/web/app/components/workflow/panel/debug-and-preview/hooks.ts index 5d8117089a..7906e91421 100644 --- a/web/app/components/workflow/panel/debug-and-preview/hooks.ts +++ b/web/app/components/workflow/panel/debug-and-preview/hooks.ts @@ -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({ diff --git a/web/i18n/en-US/workflow.json b/web/i18n/en-US/workflow.json index 37eeef06d3..0e84fe3e4b 100644 --- a/web/i18n/en-US/workflow.json +++ b/web/i18n/en-US/workflow.json @@ -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": "{{actionName}} has been triggered", "nodes.ifElse.addCondition": "Add Condition", "nodes.ifElse.addSubVariable": "Sub Variable", "nodes.ifElse.and": "and", diff --git a/web/i18n/zh-Hans/workflow.json b/web/i18n/zh-Hans/workflow.json index 99fc9ca68b..c9e2b797ec 100644 --- a/web/i18n/zh-Hans/workflow.json +++ b/web/i18n/zh-Hans/workflow.json @@ -588,6 +588,7 @@ "nodes.humanInput.userActions.emptyTip": "点击 '+' 按钮添加用户操作", "nodes.humanInput.userActions.title": "用户操作", "nodes.humanInput.userActions.tooltip": "定义用户可以点击以响应此表单的按钮。每个按钮都可以触发不同的工作流路径。", + "nodes.humanInput.userActions.triggered": "已触发{{actionName}}", "nodes.ifElse.addCondition": "添加条件", "nodes.ifElse.addSubVariable": "添加子变量", "nodes.ifElse.and": "and", diff --git a/web/service/base.ts b/web/service/base.ts index 4b60a791e3..d332e01ac9 100644 --- a/web/service/base.ts +++ b/web/service/base.ts @@ -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, diff --git a/web/types/workflow.ts b/web/types/workflow.ts index b74cb1ffb2..3b164ef095 100644 --- a/web/types/workflow.ts +++ b/web/types/workflow.ts @@ -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