mirror of
https://github.com/langgenius/dify.git
synced 2026-05-09 21:28:25 +08:00
Use shared renderer for human input content
This commit is contained in:
parent
8d3ddee7d3
commit
5309b56225
@ -1,5 +1,6 @@
|
||||
'use client'
|
||||
import type { ButtonProps } from '@langgenius/dify-ui/button'
|
||||
import type { HumanInputFieldValue } from '@/app/components/base/chat/chat/answer/human-input-content/field-renderer'
|
||||
import type { FormInputItem, UserAction } from '@/app/components/workflow/nodes/human-input/types'
|
||||
import type { SiteInfo } from '@/models/share'
|
||||
import type { HumanInputFormError } from '@/service/use-share'
|
||||
@ -81,9 +82,9 @@ const FormContent = () => {
|
||||
}, [formData?.inputs, formData?.resolved_default_values])
|
||||
|
||||
// use immer
|
||||
const handleInputsChange = (name: string, value: string) => {
|
||||
const handleInputsChange = (name: string, value: HumanInputFieldValue) => {
|
||||
const newInputs = produce(inputs, (draft) => {
|
||||
draft[name] = value
|
||||
draft[name] = typeof value === 'string' ? value : ''
|
||||
})
|
||||
setInputs(newInputs)
|
||||
}
|
||||
|
||||
@ -1,5 +1,6 @@
|
||||
'use client'
|
||||
import type { FC } from 'react'
|
||||
import type { HumanInputFieldValue } from '@/app/components/base/chat/chat/answer/human-input-content/field-renderer'
|
||||
import type { FeedbackType } from '@/app/components/base/chat/chat/type'
|
||||
import type { WorkflowProcess } from '@/app/components/base/chat/types'
|
||||
import type { SiteInfo } from '@/models/share'
|
||||
@ -178,7 +179,7 @@ const GenerationItem: FC<IGenerationItemProps> = ({
|
||||
// eslint-disable-next-line react/set-state-in-effect
|
||||
setCurrentTab(getDefaultGenerationTab(workflowProcessData))
|
||||
}, [workflowProcessData])
|
||||
const handleSubmitHumanInputForm = useCallback(async (formToken: string, formData: { inputs: Record<string, string>, action: string }) => {
|
||||
const handleSubmitHumanInputForm = useCallback(async (formToken: string, formData: { inputs: Record<string, HumanInputFieldValue>, action: string }) => {
|
||||
if (appSourceType === AppSourceType.installedApp)
|
||||
await submitHumanInputFormService(formToken, formData)
|
||||
else
|
||||
|
||||
@ -1,5 +1,6 @@
|
||||
'use client'
|
||||
import type { FC } from 'react'
|
||||
import type { HumanInputFieldValue } from '@/app/components/base/chat/chat/answer/human-input-content/field-renderer'
|
||||
import type { WorkflowProcess } from '@/app/components/base/chat/types'
|
||||
import type { SiteInfo } from '@/models/share'
|
||||
import { cn } from '@langgenius/dify-ui/cn'
|
||||
@ -17,7 +18,7 @@ type WorkflowBodyProps = {
|
||||
depth: number
|
||||
hideProcessDetail?: boolean
|
||||
isError: boolean
|
||||
onSubmitHumanInputForm: (formToken: string, formData: { inputs: Record<string, string>, action: string }) => Promise<void>
|
||||
onSubmitHumanInputForm: (formToken: string, formData: { inputs: Record<string, HumanInputFieldValue>, action: string }) => Promise<void>
|
||||
onSwitchTab: (tab: string) => void
|
||||
showResultTabs: boolean
|
||||
siteInfo: SiteInfo | null
|
||||
|
||||
@ -8,6 +8,15 @@ vi.mock('@/app/components/base/markdown', () => ({
|
||||
Markdown: ({ content }: { content: string }) => <div data-testid="mock-markdown">{content}</div>,
|
||||
}))
|
||||
|
||||
vi.mock('../field-renderer', () => ({
|
||||
__esModule: true,
|
||||
default: ({ field, onChange }: { field: FormInputItem, onChange: (value: unknown) => void }) => (
|
||||
<button type="button" data-testid={`renderer-${field.type}`} onClick={() => onChange(field.type === 'paragraph' ? 'updated value' : field.type)}>
|
||||
{field.type}
|
||||
</button>
|
||||
),
|
||||
}))
|
||||
|
||||
describe('ContentItem', () => {
|
||||
const mockOnInputChange = vi.fn()
|
||||
const mockFormInputFields: FormInputItem[] = [
|
||||
@ -49,9 +58,8 @@ describe('ContentItem', () => {
|
||||
/>,
|
||||
)
|
||||
|
||||
const textarea = screen.getByTestId('content-item-textarea')
|
||||
const textarea = screen.getByTestId('renderer-paragraph')
|
||||
expect(textarea).toBeInTheDocument()
|
||||
expect(textarea).toHaveValue('Initial bio')
|
||||
expect(screen.queryByTestId('mock-markdown')).not.toBeInTheDocument()
|
||||
})
|
||||
|
||||
@ -66,10 +74,9 @@ describe('ContentItem', () => {
|
||||
/>,
|
||||
)
|
||||
|
||||
const textarea = screen.getByTestId('content-item-textarea')
|
||||
await user.type(textarea, 'x')
|
||||
await user.click(screen.getByTestId('renderer-paragraph'))
|
||||
|
||||
expect(mockOnInputChange).toHaveBeenCalledWith('user_bio', 'Initial biox')
|
||||
expect(mockOnInputChange).toHaveBeenCalledWith('user_bio', 'updated value')
|
||||
})
|
||||
|
||||
it('should render nothing if field name is valid but not found in formInputFields', () => {
|
||||
@ -85,8 +92,10 @@ describe('ContentItem', () => {
|
||||
expect(container.firstChild).toBeNull()
|
||||
})
|
||||
|
||||
it('should render nothing if input type is not supported', () => {
|
||||
const { container } = render(
|
||||
it('should delegate select fields to the shared renderer', async () => {
|
||||
const user = userEvent.setup()
|
||||
|
||||
render(
|
||||
<ContentItem
|
||||
content="{{#$output.user_bio#}}"
|
||||
formInputFields={[
|
||||
@ -105,7 +114,8 @@ describe('ContentItem', () => {
|
||||
/>,
|
||||
)
|
||||
|
||||
expect(container.querySelector('[data-testid="content-item-textarea"]')).not.toBeInTheDocument()
|
||||
expect(container.querySelector('.py-3')?.textContent).toBe('')
|
||||
await user.click(screen.getByTestId('renderer-select'))
|
||||
|
||||
expect(mockOnInputChange).toHaveBeenCalledWith('user_bio', 'select')
|
||||
})
|
||||
})
|
||||
|
||||
@ -2,7 +2,7 @@ import type { ContentItemProps } from './type'
|
||||
import * as React from 'react'
|
||||
import { useMemo } from 'react'
|
||||
import { Markdown } from '@/app/components/base/markdown'
|
||||
import Textarea from '@/app/components/base/textarea'
|
||||
import HumanInputFieldRenderer from './field-renderer'
|
||||
|
||||
const ContentItem = ({
|
||||
content,
|
||||
@ -40,14 +40,11 @@ const ContentItem = ({
|
||||
|
||||
return (
|
||||
<div className="py-3">
|
||||
{formInputField.type === 'paragraph' && (
|
||||
<Textarea
|
||||
className="h-[104px] sm:text-xs"
|
||||
value={inputs[fieldName]!}
|
||||
onChange={(e) => { onInputChange(fieldName, e.target.value) }}
|
||||
data-testid="content-item-textarea"
|
||||
/>
|
||||
)}
|
||||
<HumanInputFieldRenderer
|
||||
field={formInputField}
|
||||
value={inputs[fieldName]}
|
||||
onChange={value => onInputChange(fieldName, value)}
|
||||
/>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
@ -1,5 +1,6 @@
|
||||
'use client'
|
||||
import type { ButtonProps } from '@langgenius/dify-ui/button'
|
||||
import type { HumanInputFieldValue } from './field-renderer'
|
||||
import type { HumanInputFormProps } from './type'
|
||||
import type { UserAction } from '@/app/components/workflow/nodes/human-input/types'
|
||||
import { Button } from '@langgenius/dify-ui/button'
|
||||
@ -18,14 +19,14 @@ const HumanInputForm = ({
|
||||
const [inputs, setInputs] = useState(defaultInputs)
|
||||
const [isSubmitting, setIsSubmitting] = useState(false)
|
||||
|
||||
const handleInputsChange = useCallback((name: string, value: string) => {
|
||||
const handleInputsChange = useCallback((name: string, value: HumanInputFieldValue) => {
|
||||
setInputs(prev => ({
|
||||
...prev,
|
||||
[name]: value,
|
||||
}))
|
||||
}, [])
|
||||
|
||||
const submit = async (formToken: string, actionID: string, inputs: Record<string, string>) => {
|
||||
const submit = async (formToken: string, actionID: string, inputs: Record<string, HumanInputFieldValue>) => {
|
||||
setIsSubmitting(true)
|
||||
await onSubmit?.(formToken, { inputs, action: actionID })
|
||||
setIsSubmitting(false)
|
||||
|
||||
@ -1,3 +1,4 @@
|
||||
import type { HumanInputFieldValue } from './field-renderer'
|
||||
import type { FormInputItem } from '@/app/components/workflow/nodes/human-input/types'
|
||||
import type { HumanInputFilledFormData, HumanInputFormData } from '@/types/workflow'
|
||||
|
||||
@ -11,7 +12,7 @@ export type UnsubmittedHumanInputContentProps = {
|
||||
showEmailTip?: boolean
|
||||
isEmailDebugMode?: boolean
|
||||
showDebugModeTip?: boolean
|
||||
onSubmit?: (formToken: string, data: { inputs: Record<string, string>, action: string }) => Promise<void>
|
||||
onSubmit?: (formToken: string, data: { inputs: Record<string, HumanInputFieldValue>, action: string }) => Promise<void>
|
||||
}
|
||||
|
||||
export type SubmittedHumanInputContentProps = {
|
||||
@ -20,12 +21,12 @@ export type SubmittedHumanInputContentProps = {
|
||||
|
||||
export type HumanInputFormProps = {
|
||||
formData: HumanInputFormData
|
||||
onSubmit?: (formToken: string, data: { inputs: Record<string, string>, action: string }) => Promise<void>
|
||||
onSubmit?: (formToken: string, data: { inputs: Record<string, HumanInputFieldValue>, action: string }) => Promise<void>
|
||||
}
|
||||
|
||||
export type ContentItemProps = {
|
||||
content: string
|
||||
formInputFields: FormInputItem[]
|
||||
inputs: Record<string, string>
|
||||
onInputChange: (name: string, value: string) => void
|
||||
inputs: Record<string, HumanInputFieldValue>
|
||||
onInputChange: (name: string, value: HumanInputFieldValue) => void
|
||||
}
|
||||
|
||||
@ -1,3 +1,4 @@
|
||||
import type { HumanInputFieldValue } from './field-renderer'
|
||||
import type { FormInputItem } from '@/app/components/workflow/nodes/human-input/types'
|
||||
import type { Locale } from '@/i18n-config'
|
||||
import type { HumanInputResolvedValue } from '@/types/workflow'
|
||||
@ -36,7 +37,7 @@ export const splitByOutputVar = (content: string): string[] => {
|
||||
}
|
||||
|
||||
export const initializeInputs = (formInputs: FormInputItem[], defaultValues: Record<string, HumanInputResolvedValue> = {}) => {
|
||||
const initialInputs: Record<string, string> = {}
|
||||
const initialInputs: Record<string, HumanInputFieldValue> = {}
|
||||
formInputs.forEach((item) => {
|
||||
if (isParagraphFormInput(item)) {
|
||||
const resolvedValue = defaultValues[item.output_variable_name]
|
||||
|
||||
@ -1,3 +1,4 @@
|
||||
import type { HumanInputFieldValue } from './human-input-content/field-renderer'
|
||||
import type { DeliveryMethod, HumanInputNodeType } from '@/app/components/workflow/nodes/human-input/types'
|
||||
import type { Node } from '@/app/components/workflow/types'
|
||||
import type { HumanInputFormData } from '@/types/workflow'
|
||||
@ -8,7 +9,7 @@ import { UnsubmittedHumanInputContent } from './human-input-content/unsubmitted'
|
||||
|
||||
type HumanInputFormListProps = {
|
||||
humanInputFormDataList: HumanInputFormData[]
|
||||
onHumanInputFormSubmit?: (formToken: string, formData: { inputs: Record<string, string>, action: string }) => Promise<void>
|
||||
onHumanInputFormSubmit?: (formToken: string, formData: { inputs: Record<string, HumanInputFieldValue>, action: string }) => Promise<void>
|
||||
getHumanInputNodeData?: (nodeID: string) => Node<HumanInputNodeType> | undefined
|
||||
}
|
||||
|
||||
|
||||
@ -1,5 +1,6 @@
|
||||
'use client'
|
||||
import type { ButtonProps } from '@langgenius/dify-ui/button'
|
||||
import type { HumanInputFieldValue } from '@/app/components/base/chat/chat/answer/human-input-content/field-renderer'
|
||||
import type { UserAction } from '@/app/components/workflow/nodes/human-input/types'
|
||||
import type { HumanInputFormData } from '@/types/workflow'
|
||||
import { Button } from '@langgenius/dify-ui/button'
|
||||
@ -16,7 +17,7 @@ type Props = {
|
||||
data: HumanInputFormData
|
||||
showBackButton?: boolean
|
||||
handleBack?: () => void
|
||||
onSubmit?: ({ inputs, action }: { inputs: Record<string, string>, action: string }) => Promise<void>
|
||||
onSubmit?: ({ inputs, action }: { inputs: Record<string, HumanInputFieldValue>, action: string }) => Promise<void>
|
||||
}
|
||||
|
||||
const FormContent = ({
|
||||
@ -32,7 +33,7 @@ const FormContent = ({
|
||||
const [inputs, setInputs] = useState(defaultInputs)
|
||||
const [isSubmitting, setIsSubmitting] = useState(false)
|
||||
|
||||
const handleInputsChange = (name: string, value: string) => {
|
||||
const handleInputsChange = (name: string, value: HumanInputFieldValue) => {
|
||||
setInputs(prev => ({
|
||||
...prev,
|
||||
[name]: value,
|
||||
|
||||
@ -275,7 +275,7 @@ export const getHumanInputForm = (token: string) => {
|
||||
}
|
||||
|
||||
export const submitHumanInputForm = (token: string, data: {
|
||||
inputs: Record<string, string>
|
||||
inputs: Record<string, unknown>
|
||||
action: string
|
||||
}) => {
|
||||
return post(`/form/human_input/${token}`, { body: data })
|
||||
|
||||
@ -131,7 +131,7 @@ export const updateFeatures = ({ appId, features }: {
|
||||
}
|
||||
|
||||
export const submitHumanInputForm = (token: string, data: {
|
||||
inputs: Record<string, string>
|
||||
inputs: Record<string, unknown>
|
||||
action: string
|
||||
}) => {
|
||||
return post(`/form/human_input/${token}`, { body: data })
|
||||
@ -140,7 +140,7 @@ export const submitHumanInputForm = (token: string, data: {
|
||||
export const fetchHumanInputNodeStepRunForm = (
|
||||
url: string,
|
||||
data: {
|
||||
inputs: Record<string, string>
|
||||
inputs: Record<string, unknown>
|
||||
},
|
||||
) => {
|
||||
return post<HumanInputFormData>(`${url}/preview`, { body: data })
|
||||
@ -149,8 +149,8 @@ export const fetchHumanInputNodeStepRunForm = (
|
||||
export const submitHumanInputNodeStepRunForm = (
|
||||
url: string,
|
||||
data: {
|
||||
inputs: Record<string, string> | undefined
|
||||
form_inputs: Record<string, string> | undefined
|
||||
inputs: Record<string, unknown> | undefined
|
||||
form_inputs: Record<string, unknown> | undefined
|
||||
action: string
|
||||
},
|
||||
) => {
|
||||
|
||||
Loading…
Reference in New Issue
Block a user