mirror of
https://github.com/langgenius/dify.git
synced 2026-04-05 18:31:45 +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:
@@ -8,6 +8,9 @@ import type {
|
||||
} from '@/types/pipeline'
|
||||
import type {
|
||||
AgentLogResponse,
|
||||
HumanInputFormFilledResponse,
|
||||
HumanInputFormTimeoutResponse,
|
||||
HumanInputRequiredResponse,
|
||||
IterationFinishedResponse,
|
||||
IterationNextResponse,
|
||||
IterationStartedResponse,
|
||||
@@ -21,6 +24,7 @@ import type {
|
||||
TextChunkResponse,
|
||||
TextReplaceResponse,
|
||||
WorkflowFinishedResponse,
|
||||
WorkflowPausedResponse,
|
||||
WorkflowStartedResponse,
|
||||
} from '@/types/workflow'
|
||||
import Cookies from 'js-cookie'
|
||||
@@ -29,7 +33,7 @@ import { API_PREFIX, CSRF_COOKIE_NAME, CSRF_HEADER_NAME, IS_CE_EDITION, PASSPORT
|
||||
import { asyncRunSafe } from '@/utils'
|
||||
import { basePath } from '@/utils/var'
|
||||
import { base, ContentType, getBaseOptions } from './fetch'
|
||||
import { refreshAccessTokenOrRelogin } from './refresh-token'
|
||||
import { refreshAccessTokenOrReLogin } from './refresh-token'
|
||||
import { getWebAppPassport } from './webapp-auth'
|
||||
|
||||
const TIME_OUT = 100000
|
||||
@@ -70,6 +74,10 @@ export type IOnLoopNext = (workflowStarted: LoopNextResponse) => void
|
||||
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 IOnHumanInputFormTimeout = (humanInputFormTimeout: HumanInputFormTimeoutResponse) => void
|
||||
export type IOWorkflowPaused = (workflowPaused: WorkflowPausedResponse) => void
|
||||
export type IOnDataSourceNodeProcessing = (dataSourceNodeProcessing: DataSourceNodeProcessingResponse) => void
|
||||
export type IOnDataSourceNodeCompleted = (dataSourceNodeCompleted: DataSourceNodeCompletedResponse) => void
|
||||
export type IOnDataSourceNodeError = (dataSourceNodeError: DataSourceNodeErrorResponse) => void
|
||||
@@ -113,6 +121,10 @@ export type IOtherOptions = {
|
||||
onLoopNext?: IOnLoopNext
|
||||
onLoopFinish?: IOnLoopFinished
|
||||
onAgentLog?: IOnAgentLog
|
||||
onHumanInputRequired?: IOHumanInputRequired
|
||||
onHumanInputFormFilled?: IOnHumanInputFormFilled
|
||||
onHumanInputFormTimeout?: IOnHumanInputFormTimeout
|
||||
onWorkflowPaused?: IOWorkflowPaused
|
||||
|
||||
// Pipeline data source node run
|
||||
onDataSourceNodeProcessing?: IOnDataSourceNodeProcessing
|
||||
@@ -153,6 +165,14 @@ function requiredWebSSOLogin(message?: string, code?: number) {
|
||||
globalThis.location.href = `${globalThis.location.origin}${basePath}${WBB_APP_LOGIN_PATH}?${params.toString()}`
|
||||
}
|
||||
|
||||
function formatURL(url: string, isPublicAPI: boolean) {
|
||||
const urlPrefix = isPublicAPI ? PUBLIC_API_PREFIX : API_PREFIX
|
||||
if (url.startsWith('http://') || url.startsWith('https://'))
|
||||
return url
|
||||
const urlWithoutProtocol = url.startsWith('/') ? url : `/${url}`
|
||||
return `${urlPrefix}${urlWithoutProtocol}`
|
||||
}
|
||||
|
||||
export function format(text: string) {
|
||||
let res = text.trim()
|
||||
if (res.startsWith('\n'))
|
||||
@@ -187,6 +207,10 @@ export const handleStream = (
|
||||
onTTSEnd?: IOnTTSEnd,
|
||||
onTextReplace?: IOnTextReplace,
|
||||
onAgentLog?: IOnAgentLog,
|
||||
onHumanInputRequired?: IOHumanInputRequired,
|
||||
onHumanInputFormFilled?: IOnHumanInputFormFilled,
|
||||
onHumanInputFormTimeout?: IOnHumanInputFormTimeout,
|
||||
onWorkflowPaused?: IOWorkflowPaused,
|
||||
onDataSourceNodeProcessing?: IOnDataSourceNodeProcessing,
|
||||
onDataSourceNodeCompleted?: IOnDataSourceNodeCompleted,
|
||||
onDataSourceNodeError?: IOnDataSourceNodeError,
|
||||
@@ -319,6 +343,18 @@ export const handleStream = (
|
||||
else if (bufferObj.event === 'tts_message_end') {
|
||||
onTTSEnd?.(bufferObj.message_id, bufferObj.audio)
|
||||
}
|
||||
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 === 'human_input_form_timeout') {
|
||||
onHumanInputFormTimeout?.(bufferObj as HumanInputFormTimeoutResponse)
|
||||
}
|
||||
else if (bufferObj.event === 'workflow_paused') {
|
||||
onWorkflowPaused?.(bufferObj as WorkflowPausedResponse)
|
||||
}
|
||||
else if (bufferObj.event === 'datasource_processing') {
|
||||
onDataSourceNodeProcessing?.(bufferObj as DataSourceNodeProcessingResponse)
|
||||
}
|
||||
@@ -441,6 +477,10 @@ export const ssePost = async (
|
||||
onLoopStart,
|
||||
onLoopNext,
|
||||
onLoopFinish,
|
||||
onHumanInputRequired,
|
||||
onHumanInputFormFilled,
|
||||
onHumanInputFormTimeout,
|
||||
onWorkflowPaused,
|
||||
onDataSourceNodeProcessing,
|
||||
onDataSourceNodeCompleted,
|
||||
onDataSourceNodeError,
|
||||
@@ -467,10 +507,7 @@ export const ssePost = async (
|
||||
|
||||
getAbortController?.(abortController)
|
||||
|
||||
const urlPrefix = isPublicAPI ? PUBLIC_API_PREFIX : API_PREFIX
|
||||
const urlWithPrefix = (url.startsWith('http://') || url.startsWith('https://'))
|
||||
? url
|
||||
: `${urlPrefix}${url.startsWith('/') ? url : `/${url}`}`
|
||||
const urlWithPrefix = formatURL(url, isPublicAPI)
|
||||
|
||||
const { body } = options
|
||||
if (body)
|
||||
@@ -495,7 +532,7 @@ export const ssePost = async (
|
||||
})
|
||||
}
|
||||
else {
|
||||
refreshAccessTokenOrRelogin(TIME_OUT).then(() => {
|
||||
refreshAccessTokenOrReLogin(TIME_OUT).then(() => {
|
||||
ssePost(url, fetchOptions, otherOptions)
|
||||
}).catch((err) => {
|
||||
console.error(err)
|
||||
@@ -545,6 +582,157 @@ export const ssePost = async (
|
||||
onTTSEnd,
|
||||
onTextReplace,
|
||||
onAgentLog,
|
||||
onHumanInputRequired,
|
||||
onHumanInputFormFilled,
|
||||
onHumanInputFormTimeout,
|
||||
onWorkflowPaused,
|
||||
onDataSourceNodeProcessing,
|
||||
onDataSourceNodeCompleted,
|
||||
onDataSourceNodeError,
|
||||
)
|
||||
})
|
||||
.catch((e) => {
|
||||
if (e.toString() !== 'AbortError: The user aborted a request.' && !e.toString().errorMessage.includes('TypeError: Cannot assign to read only property'))
|
||||
Toast.notify({ type: 'error', message: e })
|
||||
onError?.(e)
|
||||
})
|
||||
}
|
||||
|
||||
export const sseGet = async (
|
||||
url: string,
|
||||
fetchOptions: FetchOptionType,
|
||||
otherOptions: IOtherOptions,
|
||||
) => {
|
||||
const {
|
||||
isPublicAPI = false,
|
||||
onData,
|
||||
onCompleted,
|
||||
onThought,
|
||||
onFile,
|
||||
onMessageEnd,
|
||||
onMessageReplace,
|
||||
onWorkflowStarted,
|
||||
onWorkflowFinished,
|
||||
onNodeStarted,
|
||||
onNodeFinished,
|
||||
onIterationStart,
|
||||
onIterationNext,
|
||||
onIterationFinish,
|
||||
onNodeRetry,
|
||||
onParallelBranchStarted,
|
||||
onParallelBranchFinished,
|
||||
onTextChunk,
|
||||
onTTSChunk,
|
||||
onTTSEnd,
|
||||
onTextReplace,
|
||||
onAgentLog,
|
||||
onError,
|
||||
getAbortController,
|
||||
onLoopStart,
|
||||
onLoopNext,
|
||||
onLoopFinish,
|
||||
onHumanInputRequired,
|
||||
onHumanInputFormFilled,
|
||||
onHumanInputFormTimeout,
|
||||
onWorkflowPaused,
|
||||
onDataSourceNodeProcessing,
|
||||
onDataSourceNodeCompleted,
|
||||
onDataSourceNodeError,
|
||||
} = otherOptions
|
||||
const abortController = new AbortController()
|
||||
|
||||
const baseOptions = getBaseOptions()
|
||||
const shareCode = globalThis.location.pathname.split('/').slice(-1)[0]
|
||||
const options = Object.assign({}, baseOptions, {
|
||||
signal: abortController.signal,
|
||||
headers: new Headers({
|
||||
[CSRF_HEADER_NAME]: Cookies.get(CSRF_COOKIE_NAME()) || '',
|
||||
[WEB_APP_SHARE_CODE_HEADER_NAME]: shareCode,
|
||||
[PASSPORT_HEADER_NAME]: getWebAppPassport(shareCode),
|
||||
}),
|
||||
} as RequestInit, fetchOptions)
|
||||
|
||||
const contentType = (options.headers as Headers).get('Content-Type')
|
||||
if (!contentType)
|
||||
(options.headers as Headers).set('Content-Type', ContentType.json)
|
||||
|
||||
getAbortController?.(abortController)
|
||||
|
||||
const urlWithPrefix = formatURL(url, isPublicAPI)
|
||||
|
||||
globalThis.fetch(urlWithPrefix, options as RequestInit)
|
||||
.then((res) => {
|
||||
if (!/^[23]\d{2}$/.test(String(res.status))) {
|
||||
if (res.status === 401) {
|
||||
if (isPublicAPI) {
|
||||
res.json().then((data: { code?: string, message?: string }) => {
|
||||
if (isPublicAPI) {
|
||||
if (data.code === 'web_app_access_denied')
|
||||
requiredWebSSOLogin(data.message, 403)
|
||||
|
||||
if (data.code === 'web_sso_auth_required')
|
||||
requiredWebSSOLogin()
|
||||
|
||||
if (data.code === 'unauthorized')
|
||||
requiredWebSSOLogin()
|
||||
}
|
||||
})
|
||||
}
|
||||
else {
|
||||
refreshAccessTokenOrReLogin(TIME_OUT).then(() => {
|
||||
sseGet(url, fetchOptions, otherOptions)
|
||||
}).catch((err) => {
|
||||
console.error(err)
|
||||
})
|
||||
}
|
||||
}
|
||||
else {
|
||||
res.json().then((data) => {
|
||||
Toast.notify({ type: 'error', message: data.message || 'Server Error' })
|
||||
})
|
||||
onError?.('Server Error')
|
||||
}
|
||||
return
|
||||
}
|
||||
return handleStream(
|
||||
res,
|
||||
(str: string, isFirstMessage: boolean, moreInfo: IOnDataMoreInfo) => {
|
||||
if (moreInfo.errorMessage) {
|
||||
onError?.(moreInfo.errorMessage, moreInfo.errorCode)
|
||||
// TypeError: Cannot assign to read only property ... will happen in page leave, so it should be ignored.
|
||||
if (moreInfo.errorMessage !== 'AbortError: The user aborted a request.' && !moreInfo.errorMessage.includes('TypeError: Cannot assign to read only property'))
|
||||
Toast.notify({ type: 'error', message: moreInfo.errorMessage })
|
||||
return
|
||||
}
|
||||
onData?.(str, isFirstMessage, moreInfo)
|
||||
},
|
||||
onCompleted,
|
||||
onThought,
|
||||
onMessageEnd,
|
||||
onMessageReplace,
|
||||
onFile,
|
||||
onWorkflowStarted,
|
||||
onWorkflowFinished,
|
||||
onNodeStarted,
|
||||
onNodeFinished,
|
||||
onIterationStart,
|
||||
onIterationNext,
|
||||
onIterationFinish,
|
||||
onLoopStart,
|
||||
onLoopNext,
|
||||
onLoopFinish,
|
||||
onNodeRetry,
|
||||
onParallelBranchStarted,
|
||||
onParallelBranchFinished,
|
||||
onTextChunk,
|
||||
onTTSChunk,
|
||||
onTTSEnd,
|
||||
onTextReplace,
|
||||
onAgentLog,
|
||||
onHumanInputRequired,
|
||||
onHumanInputFormFilled,
|
||||
onHumanInputFormTimeout,
|
||||
onWorkflowPaused,
|
||||
onDataSourceNodeProcessing,
|
||||
onDataSourceNodeCompleted,
|
||||
onDataSourceNodeError,
|
||||
@@ -612,7 +800,7 @@ export const request = async<T>(url: string, options = {}, otherOptions?: IOther
|
||||
}
|
||||
|
||||
// refresh token
|
||||
const [refreshErr] = await asyncRunSafe(refreshAccessTokenOrRelogin(TIME_OUT))
|
||||
const [refreshErr] = await asyncRunSafe(refreshAccessTokenOrReLogin(TIME_OUT))
|
||||
if (refreshErr === null)
|
||||
return baseFetch<T>(url, options, otherOptionsForBaseFetch)
|
||||
if (location.pathname !== `${basePath}/signin` || !IS_CE_EDITION) {
|
||||
|
||||
@@ -80,7 +80,7 @@ function releaseRefreshLock() {
|
||||
globalThis.removeEventListener('beforeunload', releaseRefreshLock)
|
||||
}
|
||||
|
||||
export async function refreshAccessTokenOrRelogin(timeout: number) {
|
||||
export async function refreshAccessTokenOrReLogin(timeout: number) {
|
||||
return Promise.race([new Promise<void>((resolve, reject) => setTimeout(() => {
|
||||
releaseRefreshLock()
|
||||
reject(new Error('request timeout'))
|
||||
|
||||
@@ -2,20 +2,10 @@ import type {
|
||||
IOnCompleted,
|
||||
IOnData,
|
||||
IOnError,
|
||||
IOnIterationFinished,
|
||||
IOnIterationNext,
|
||||
IOnIterationStarted,
|
||||
IOnLoopFinished,
|
||||
IOnLoopNext,
|
||||
IOnLoopStarted,
|
||||
IOnMessageReplace,
|
||||
IOnNodeFinished,
|
||||
IOnNodeStarted,
|
||||
IOnTextChunk,
|
||||
IOnTextReplace,
|
||||
IOnWorkflowFinished,
|
||||
IOnWorkflowStarted,
|
||||
IOtherOptions,
|
||||
} from './base'
|
||||
import type { FormData as HumanInputFormData } from '@/app/(humanInputLayout)/form/[token]/form'
|
||||
import type { FeedbackType } from '@/app/components/base/chat/chat/type'
|
||||
import type { ChatConfig } from '@/app/components/base/chat/types'
|
||||
import type { AccessMode } from '@/models/access-control'
|
||||
@@ -95,33 +85,7 @@ export const sendCompletionMessage = async (body: Record<string, any>, { onData,
|
||||
|
||||
export const sendWorkflowMessage = async (
|
||||
body: Record<string, any>,
|
||||
{
|
||||
onWorkflowStarted,
|
||||
onNodeStarted,
|
||||
onNodeFinished,
|
||||
onWorkflowFinished,
|
||||
onIterationStart,
|
||||
onIterationNext,
|
||||
onIterationFinish,
|
||||
onLoopStart,
|
||||
onLoopNext,
|
||||
onLoopFinish,
|
||||
onTextChunk,
|
||||
onTextReplace,
|
||||
}: {
|
||||
onWorkflowStarted: IOnWorkflowStarted
|
||||
onNodeStarted: IOnNodeStarted
|
||||
onNodeFinished: IOnNodeFinished
|
||||
onWorkflowFinished: IOnWorkflowFinished
|
||||
onIterationStart: IOnIterationStarted
|
||||
onIterationNext: IOnIterationNext
|
||||
onIterationFinish: IOnIterationFinished
|
||||
onLoopStart: IOnLoopStarted
|
||||
onLoopNext: IOnLoopNext
|
||||
onLoopFinish: IOnLoopFinished
|
||||
onTextChunk: IOnTextChunk
|
||||
onTextReplace: IOnTextReplace
|
||||
},
|
||||
otherOptions: IOtherOptions,
|
||||
appSourceType: AppSourceType,
|
||||
appId = '',
|
||||
) => {
|
||||
@@ -131,19 +95,8 @@ export const sendWorkflowMessage = async (
|
||||
response_mode: 'streaming',
|
||||
},
|
||||
}, {
|
||||
onNodeStarted,
|
||||
onWorkflowStarted,
|
||||
onWorkflowFinished,
|
||||
...otherOptions,
|
||||
isPublicAPI: getIsPublicAPI(appSourceType),
|
||||
onNodeFinished,
|
||||
onIterationStart,
|
||||
onIterationNext,
|
||||
onIterationFinish,
|
||||
onLoopStart,
|
||||
onLoopNext,
|
||||
onLoopFinish,
|
||||
onTextChunk,
|
||||
onTextReplace,
|
||||
})
|
||||
}
|
||||
|
||||
@@ -320,3 +273,14 @@ export const getUserCanAccess = (appId: string, isInstalledApp: boolean) => {
|
||||
export const getAppAccessModeByAppCode = (appCode: string) => {
|
||||
return get<{ accessMode: AccessMode }>(`/webapp/access-mode?appCode=${appCode}`)
|
||||
}
|
||||
|
||||
export const getHumanInputForm = (token: string) => {
|
||||
return get<HumanInputFormData>(`/form/human_input/${token}`)
|
||||
}
|
||||
|
||||
export const submitHumanInputForm = (token: string, data: {
|
||||
inputs: Record<string, string>
|
||||
action: string
|
||||
}) => {
|
||||
return post(`/form/human_input/${token}`, { body: data })
|
||||
}
|
||||
|
||||
@@ -108,7 +108,7 @@ export const useLangGeniusVersion = (currentVersion?: string | null, enabled?: b
|
||||
export const useCurrentWorkspace = () => {
|
||||
return useQuery<ICurrentWorkspace>({
|
||||
queryKey: commonQueryKeys.currentWorkspace,
|
||||
queryFn: () => post<ICurrentWorkspace>('/workspaces/current', { body: {} }),
|
||||
queryFn: () => post<ICurrentWorkspace>('/workspaces/current'),
|
||||
})
|
||||
}
|
||||
|
||||
|
||||
@@ -7,6 +7,7 @@ import type {
|
||||
CompletionConversationsRequest,
|
||||
CompletionConversationsResponse,
|
||||
WorkflowLogsResponse,
|
||||
WorkflowPausedDetailsResponse,
|
||||
} from '@/models/log'
|
||||
import { useQuery } from '@tanstack/react-query'
|
||||
import { get } from './base'
|
||||
@@ -87,3 +88,18 @@ export const useWorkflowLogs = ({ appId, params }: WorkflowLogsParams) => {
|
||||
enabled: !!appId,
|
||||
})
|
||||
}
|
||||
|
||||
// ============ Workflow Pause Details ============
|
||||
|
||||
type WorkflowPausedDetailsParams = {
|
||||
workflowRunId: string
|
||||
enabled?: boolean
|
||||
}
|
||||
|
||||
export const useWorkflowPausedDetails = ({ workflowRunId, enabled = true }: WorkflowPausedDetailsParams) => {
|
||||
return useQuery<WorkflowPausedDetailsResponse>({
|
||||
queryKey: [NAME_SPACE, 'workflow-paused-details', workflowRunId],
|
||||
queryFn: () => get<WorkflowPausedDetailsResponse>(`/workflow/${workflowRunId}/pause-details`),
|
||||
enabled: enabled && !!workflowRunId,
|
||||
})
|
||||
}
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import type { FormData as HumanInputFormData } from '@/app/(humanInputLayout)/form/[token]/form'
|
||||
import type { AppConversationData, ConversationItem } from '@/models/share'
|
||||
import { useQuery } from '@tanstack/react-query'
|
||||
import { useMutation, useQuery } from '@tanstack/react-query'
|
||||
import {
|
||||
AppSourceType,
|
||||
fetchAppInfo,
|
||||
@@ -9,6 +10,8 @@ import {
|
||||
fetchConversations,
|
||||
generationConversationName,
|
||||
getAppAccessModeByAppCode,
|
||||
getHumanInputForm,
|
||||
submitHumanInputForm,
|
||||
} from './share'
|
||||
import { useInvalid } from './use-base'
|
||||
|
||||
@@ -49,6 +52,7 @@ export const shareQueryKeys = {
|
||||
conversationList: (params: ShareConversationsParams) => [NAME_SPACE, 'conversations', params] as const,
|
||||
chatList: (params: ShareChatListParams) => [NAME_SPACE, 'chatList', params] as const,
|
||||
conversationName: (params: ShareConversationNameParams) => [NAME_SPACE, 'conversationName', params] as const,
|
||||
humanInputForm: (token: string) => [NAME_SPACE, 'humanInputForm', token] as const,
|
||||
}
|
||||
|
||||
export const useGetWebAppAccessModeByCode = (code: string | null) => {
|
||||
@@ -149,3 +153,60 @@ export const useShareConversationName = (params: ShareConversationNameParams, op
|
||||
export const useInvalidateShareConversations = () => {
|
||||
return useInvalid(shareQueryKeys.conversations)
|
||||
}
|
||||
|
||||
export class HumanInputFormError extends Error {
|
||||
code: string
|
||||
status: number
|
||||
|
||||
constructor(code: string, message: string, status: number) {
|
||||
super(message)
|
||||
this.name = 'HumanInputFormError'
|
||||
this.code = code
|
||||
this.status = status
|
||||
}
|
||||
}
|
||||
|
||||
export const useGetHumanInputForm = (token: string, options: ShareQueryOptions = {}) => {
|
||||
const {
|
||||
enabled = true,
|
||||
refetchOnReconnect,
|
||||
refetchOnWindowFocus,
|
||||
} = options
|
||||
return useQuery<HumanInputFormData, HumanInputFormError>({
|
||||
queryKey: shareQueryKeys.humanInputForm(token),
|
||||
queryFn: async () => {
|
||||
try {
|
||||
return await getHumanInputForm(token)
|
||||
}
|
||||
catch (error) {
|
||||
const response = error as Response
|
||||
if (response.status && response.json) {
|
||||
const errorData = await response.json() as { code: string, message: string }
|
||||
throw new HumanInputFormError(errorData.code, errorData.message, response.status)
|
||||
}
|
||||
throw error
|
||||
}
|
||||
},
|
||||
enabled: enabled && !!token,
|
||||
refetchOnReconnect,
|
||||
refetchOnWindowFocus,
|
||||
retry: false,
|
||||
})
|
||||
}
|
||||
|
||||
export type SubmitHumanInputFormParams = {
|
||||
token: string
|
||||
data: {
|
||||
inputs: Record<string, string>
|
||||
action: string
|
||||
}
|
||||
}
|
||||
|
||||
export const useSubmitHumanInputForm = () => {
|
||||
return useMutation({
|
||||
mutationKey: [NAME_SPACE, 'submit-human-input-form'],
|
||||
mutationFn: ({ token, data }: SubmitHumanInputFormParams) => {
|
||||
return submitHumanInputForm(token, data)
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
@@ -227,3 +227,18 @@ export const useEditInspectorVar = (flowType: FlowType, flowId: string) => {
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
export const useTestEmailSender = () => {
|
||||
return useMutation({
|
||||
mutationKey: [NAME_SPACE, 'test email sender'],
|
||||
mutationFn: async (data: { appID: string, nodeID: string, deliveryID: string, inputs: Record<string, any> }) => {
|
||||
const { appID, nodeID, deliveryID, inputs } = data
|
||||
return post<CommonResponse>(`/apps/${appID}/workflows/draft/human-input/nodes/${nodeID}/delivery-test`, {
|
||||
body: {
|
||||
delivery_method_id: deliveryID,
|
||||
inputs,
|
||||
},
|
||||
})
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
@@ -4,6 +4,7 @@ import type { FlowType } from '@/types/common'
|
||||
import type {
|
||||
ConversationVariableResponse,
|
||||
FetchWorkflowDraftResponse,
|
||||
HumanInputFormData,
|
||||
NodesDefaultConfigsResponse,
|
||||
VarInInspect,
|
||||
} from '@/types/workflow'
|
||||
@@ -94,3 +95,30 @@ export const fetchNodeInspectVars = async (flowType: FlowType, flowId: string, n
|
||||
const { items } = (await get(`${getFlowPrefix(flowType)}/${flowId}/workflows/draft/nodes/${nodeId}/variables`)) as { items: VarInInspect[] }
|
||||
return items
|
||||
}
|
||||
|
||||
export const submitHumanInputForm = (token: string, data: {
|
||||
inputs: Record<string, string>
|
||||
action: string
|
||||
}) => {
|
||||
return post(`/form/human_input/${token}`, { body: data })
|
||||
}
|
||||
|
||||
export const fetchHumanInputNodeStepRunForm = (
|
||||
url: string,
|
||||
data: {
|
||||
inputs: Record<string, string>
|
||||
},
|
||||
) => {
|
||||
return post<HumanInputFormData>(`${url}/preview`, { body: data })
|
||||
}
|
||||
|
||||
export const submitHumanInputNodeStepRunForm = (
|
||||
url: string,
|
||||
data: {
|
||||
inputs: Record<string, string> | undefined
|
||||
form_inputs: Record<string, string> | undefined
|
||||
action: string
|
||||
},
|
||||
) => {
|
||||
return post<CommonResponse>(`${url}/run`, { body: data })
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user