Align human input submission payload types

This commit is contained in:
JzoNg 2026-04-22 08:19:28 +08:00
parent 94b8f8f170
commit add9260e58
11 changed files with 29 additions and 21 deletions

View File

@ -6,6 +6,7 @@ import type {
ChatConfig,
ChatItem,
} from '../../types'
import type { HumanInputFieldValue } from './human-input-content/field-renderer'
import type { AppData } from '@/models/share'
import { cn } from '@langgenius/dify-ui/cn'
import { memo, useCallback, useEffect, useRef, useState } from 'react'
@ -40,7 +41,7 @@ type AnswerProps = {
noChatInput?: boolean
switchSibling?: (siblingMessageId: string) => void
hideAvatar?: boolean
onHumanInputFormSubmit?: (formToken: string, formData: any) => Promise<void>
onHumanInputFormSubmit?: (formToken: string, formData: { inputs: Record<string, HumanInputFieldValue>, action: string }) => Promise<void>
}
const Answer: FC<AnswerProps> = ({
item,

View File

@ -10,6 +10,7 @@ import type {
OnRegenerate,
OnSend,
} from '../types'
import type { HumanInputFieldValue } from './answer/human-input-content/field-renderer'
import type { InputForm } from './type'
import type { Emoji } from '@/app/components/tools/types'
import type { AppData } from '@/models/share'
@ -69,7 +70,7 @@ export type ChatProps = {
sidebarCollapseState?: boolean
hideAvatar?: boolean
sendOnEnter?: boolean
onHumanInputFormSubmit?: (formToken: string, formData: any) => Promise<void>
onHumanInputFormSubmit?: (formToken: string, formData: { inputs: Record<string, HumanInputFieldValue>, action: string }) => Promise<void>
getHumanInputNodeData?: (nodeID: string) => any
}

View File

@ -1,4 +1,5 @@
import type { RefObject } from 'react'
import type { HumanInputFieldValue } from '../../chat/answer/human-input-content/field-renderer'
import type { ChatConfig, ChatItem, ChatItemInTree } from '../../types'
import type { EmbeddedChatbotContextValue } from '../context'
import type { ConversationItem } from '@/models/share'
@ -59,7 +60,7 @@ vi.mock('../../chat', () => ({
onSend: (message: string) => void
onRegenerate: (chatItem: ChatItem, editedQuestion?: { message: string, files?: never[] }) => void
switchSibling: (siblingMessageId: string) => void
onHumanInputFormSubmit: (formToken: string, formData: Record<string, string>) => Promise<void>
onHumanInputFormSubmit: (formToken: string, formData: { inputs: Record<string, HumanInputFieldValue>, action: string }) => Promise<void>
onStopResponding: () => void
}) => (
<div>
@ -78,7 +79,7 @@ vi.mock('../../chat', () => ({
<button onClick={() => switchSibling('sibling-2')}>switch sibling</button>
<button disabled={inputDisabled}>send message</button>
<button onClick={onStopResponding}>stop responding</button>
<button onClick={() => onHumanInputFormSubmit('form-token', { answer: 'ok' })}>submit human input</button>
<button onClick={() => onHumanInputFormSubmit('form-token', { inputs: { answer: 'ok' }, action: 'approve' })}>submit human input</button>
</div>
),
}))
@ -351,7 +352,7 @@ describe('EmbeddedChatbot chat-wrapper', () => {
fireEvent.click(screen.getByRole('button', { name: 'submit human input' }))
await waitFor(() => {
expect(submitHumanInputFormService).toHaveBeenCalledWith('form-token', { answer: 'ok' })
expect(submitHumanInputFormService).toHaveBeenCalledWith('form-token', { inputs: { answer: 'ok' }, action: 'approve' })
})
expect(submitHumanInputForm).not.toHaveBeenCalled()
})
@ -391,7 +392,7 @@ describe('EmbeddedChatbot chat-wrapper', () => {
fireEvent.click(screen.getByRole('button', { name: 'submit human input' }))
await waitFor(() => {
expect(submitHumanInputForm).toHaveBeenCalledWith('form-token', { answer: 'ok' })
expect(submitHumanInputForm).toHaveBeenCalledWith('form-token', { inputs: { answer: 'ok' }, action: 'approve' })
})
expect(handleSend).toHaveBeenCalledTimes(2)
const sendOptions = handleSend.mock.calls[0]?.[2] as { onGetSuggestedQuestions: (responseItemId: string) => void }

View File

@ -1,4 +1,5 @@
import type { FileEntity } from '../../file-uploader/types'
import type { HumanInputFieldValue } from '../chat/answer/human-input-content/field-renderer'
import type {
ChatConfig,
ChatItem,
@ -232,7 +233,7 @@ const ChatWrapper = () => {
}
}, [inputsForms.length, isMobile, currentConversationId, collapsed, allInputsHidden])
const handleSubmitHumanInputForm = useCallback(async (formToken: string, formData: any) => {
const handleSubmitHumanInputForm = useCallback(async (formToken: string, formData: { inputs: Record<string, HumanInputFieldValue>, action: string }) => {
if (isInstalledApp)
await submitHumanInputFormService(formToken, formData)
else

View File

@ -1,4 +1,5 @@
import type { ChatWrapperRefType } from '../index'
import type { HumanInputFieldValue } from '@/app/components/base/chat/chat/answer/human-input-content/field-renderer'
import { act, screen, waitFor } from '@testing-library/react'
import userEvent from '@testing-library/user-event'
import { useStore as useAppStore } from '@/app/components/app/store'
@ -37,7 +38,7 @@ vi.mock('@/app/components/base/chat/chat', () => ({
onSend?: (message: string, files: unknown[]) => void
onRegenerate?: (chatItem: { id: string, parentMessageId?: string, content?: string, message_files?: unknown[] }) => void
switchSibling?: (siblingMessageId: string) => void
onHumanInputFormSubmit?: (formToken: string, formData: Record<string, string>) => Promise<void>
onHumanInputFormSubmit?: (formToken: string, formData: { inputs: Record<string, HumanInputFieldValue>, action: string }) => Promise<void>
onFeatureBarClick?: (state: boolean) => void
}) => (
<div data-testid="chat-shell">
@ -55,7 +56,7 @@ vi.mock('@/app/components/base/chat/chat', () => ({
regenerate-chat
</button>
<button type="button" onClick={() => switchSibling?.('sibling-2')}>switch-sibling</button>
<button type="button" onClick={() => onHumanInputFormSubmit?.('token-1', { answer: 'ok' })}>submit-human-input</button>
<button type="button" onClick={() => onHumanInputFormSubmit?.('token-1', { inputs: { answer: 'ok' }, action: 'approve' })}>submit-human-input</button>
<button type="button" onClick={() => onFeatureBarClick?.(true)}>open-feature-panel</button>
{chatNode}
</div>
@ -296,7 +297,7 @@ describe('ChatWrapper', () => {
await user.click(screen.getByRole('button', { name: 'submit-human-input' }))
await waitFor(() => {
expect(handleSubmitHumanInputForm).toHaveBeenCalledWith('token-1', { answer: 'ok' })
expect(handleSubmitHumanInputForm).toHaveBeenCalledWith('token-1', { inputs: { answer: 'ok' }, action: 'approve' })
})
const subscription = mockUseSubscription.mock.calls[0]?.[0] as (payload: { type: string }) => void

View File

@ -1,4 +1,5 @@
import type { ChatWrapperRefType } from '../index'
import type { HumanInputFieldValue } from '@/app/components/base/chat/chat/answer/human-input-content/field-renderer'
import type { ConversationVariable } from '@/app/components/workflow/types'
import { act, fireEvent, screen, waitFor } from '@testing-library/react'
import userEvent from '@testing-library/user-event'
@ -78,7 +79,7 @@ vi.mock('@/app/components/base/chat/chat', () => ({
onSend?: (message: string, files: unknown[]) => void
onRegenerate?: (chatItem: { id: string, parentMessageId?: string, content?: string, message_files?: unknown[] }) => void
switchSibling?: (siblingMessageId: string) => void
onHumanInputFormSubmit?: (formToken: string, formData: Record<string, string>) => Promise<void>
onHumanInputFormSubmit?: (formToken: string, formData: { inputs: Record<string, HumanInputFieldValue>, action: string }) => Promise<void>
onFeatureBarClick?: (state: boolean) => void
}) => {
mockChatRender({
@ -101,7 +102,7 @@ vi.mock('@/app/components/base/chat/chat', () => ({
regenerate-chat
</button>
<button type="button" onClick={() => switchSibling?.('sibling-2')}>switch-sibling</button>
<button type="button" onClick={() => onHumanInputFormSubmit?.('token-1', { answer: 'ok' })}>submit-human-input</button>
<button type="button" onClick={() => onHumanInputFormSubmit?.('token-1', { inputs: { answer: 'ok' }, action: 'approve' })}>submit-human-input</button>
<button type="button" onClick={() => onFeatureBarClick?.(true)}>open-feature-panel</button>
{chatNode}
</div>
@ -593,7 +594,7 @@ describe('debug-and-preview components', () => {
await user.click(screen.getByRole('button', { name: 'submit-human-input' }))
await waitFor(() => {
expect(handleSubmitHumanInputForm).toHaveBeenCalledWith('token-1', { answer: 'ok' })
expect(handleSubmitHumanInputForm).toHaveBeenCalledWith('token-1', { inputs: { answer: 'ok' }, action: 'approve' })
})
const stopResponding = mockUseChat.mock.calls[0]?.[3] as (taskId: string) => void

View File

@ -126,10 +126,10 @@ describe('useChat', () => {
const { result } = renderHook(() => useChat({}))
await act(async () => {
await result.current.handleSubmitHumanInputForm('token-123', { field: 'value' })
await result.current.handleSubmitHumanInputForm('token-123', { inputs: { field: 'value' }, action: 'approve' })
})
expect(mockSubmitHumanInputForm).toHaveBeenCalledWith('token-123', { field: 'value' })
expect(mockSubmitHumanInputForm).toHaveBeenCalledWith('token-123', { inputs: { field: 'value' }, action: 'approve' })
expect(submitHumanInputForm).toBeDefined()
})

View File

@ -228,10 +228,10 @@ describe('useChat handleSubmitHumanInputForm', () => {
const { result } = renderHook(() => useChat({}))
await act(async () => {
await result.current.handleSubmitHumanInputForm('token-123', { field: 'value' })
await result.current.handleSubmitHumanInputForm('token-123', { inputs: { field: 'value' }, action: 'approve' })
})
expect(mockSubmitHumanInputForm).toHaveBeenCalledWith('token-123', { field: 'value' })
expect(mockSubmitHumanInputForm).toHaveBeenCalledWith('token-123', { inputs: { field: 'value' }, action: 'approve' })
})
})

View File

@ -1,5 +1,6 @@
import type { StartNodeType } from '../../nodes/start/types'
import type { ChatWrapperRefType } from './index'
import type { HumanInputFieldValue } from '@/app/components/base/chat/chat/answer/human-input-content/field-renderer'
import type { ChatItem, OnSend } from '@/app/components/base/chat/types'
import type { FileEntity } from '@/app/components/base/file-uploader/types'
import { memo, useCallback, useEffect, useImperativeHandle, useMemo } from 'react'
@ -129,8 +130,7 @@ const ChatWrapper = (
})
}, [handleSwitchSibling, appDetail])
const doHumanInputFormSubmit = useCallback(async (formToken: string, formData: any) => {
// Handle human input form submission
const doHumanInputFormSubmit = useCallback(async (formToken: string, formData: { inputs: Record<string, HumanInputFieldValue>, action: string }) => {
await handleSubmitHumanInputForm(formToken, formData)
}, [handleSubmitHumanInputForm])

View File

@ -1,3 +1,4 @@
import type { HumanInputFieldValue } from '@/app/components/base/chat/chat/answer/human-input-content/field-renderer'
import type { InputForm } from '@/app/components/base/chat/chat/type'
import type {
ChatItem,
@ -660,7 +661,7 @@ export const useChat = (
)
}, [threadMessages, chatTree.length, updateCurrentQAOnTree, handleResponding, formSettings?.inputsForm, handleRun, t, workflowStore, fetchInspectVars, invalidAllLastRun, config?.suggested_questions_after_answer?.enabled])
const handleSubmitHumanInputForm = async (formToken: string, formData: any) => {
const handleSubmitHumanInputForm = async (formToken: string, formData: { inputs: Record<string, HumanInputFieldValue>, action: string }) => {
await submitHumanInputForm(formToken, formData)
}

View File

@ -1,3 +1,4 @@
import type { HumanInputFieldValue } from '@/app/components/base/chat/chat/answer/human-input-content/field-renderer'
import { Button } from '@langgenius/dify-ui/button'
import { cn } from '@langgenius/dify-ui/cn'
import { toast } from '@langgenius/dify-ui/toast'
@ -97,7 +98,7 @@ const WorkflowPreview = () => {
}
}, [resize, stopResizing])
const handleSubmitHumanInputForm = useCallback(async (formToken: string, formData: any) => {
const handleSubmitHumanInputForm = useCallback(async (formToken: string, formData: { inputs: Record<string, HumanInputFieldValue>, action: string }) => {
await submitHumanInputForm(formToken, formData)
}, [])