mirror of https://github.com/langgenius/dify.git
Merge branch 'feat/hitl-frontend' of https://github.com/langgenius/dify into feat/hitl-frontend
This commit is contained in:
commit
a4b87be5f4
|
|
@ -1,4 +1,5 @@
|
|||
'use client'
|
||||
import type { ButtonProps } from '@/app/components/base/button'
|
||||
import type { FormInputItem, UserAction } from '@/app/components/workflow/nodes/human-input/types'
|
||||
import type { HumanInputFormError } from '@/service/use-share'
|
||||
import {
|
||||
|
|
@ -66,10 +67,10 @@ const FormContent = () => {
|
|||
return
|
||||
const initialInputs: Record<string, string> = {}
|
||||
formData.inputs.forEach((item) => {
|
||||
initialInputs[item.output_variable_name] = ''
|
||||
initialInputs[item.output_variable_name] = item.placeholder.type === 'variable' ? formData.resolved_placeholder_values[item.output_variable_name] || '' : item.placeholder.value
|
||||
})
|
||||
setInputs(initialInputs)
|
||||
}, [formData?.inputs])
|
||||
}, [formData?.inputs, formData?.resolved_placeholder_values])
|
||||
|
||||
// use immer
|
||||
const handleInputsChange = (name: string, value: string) => {
|
||||
|
|
@ -227,15 +228,14 @@ const FormContent = () => {
|
|||
formInputFields={formData.inputs}
|
||||
inputs={inputs}
|
||||
onInputChange={handleInputsChange}
|
||||
resolvedPlaceholderValues={formData.resolved_placeholder_values}
|
||||
/>
|
||||
))}
|
||||
<div className="flex flex-wrap gap-1 py-1">
|
||||
{formData.user_actions.map((action: any) => (
|
||||
{formData.user_actions.map((action: UserAction) => (
|
||||
<Button
|
||||
key={action.id}
|
||||
disabled={isSubmitting}
|
||||
variant={getButtonStyle(action.button_style) as any}
|
||||
variant={getButtonStyle(action.button_style) as ButtonProps['variant']}
|
||||
onClick={() => submit(action.id)}
|
||||
>
|
||||
{action.title}
|
||||
|
|
|
|||
|
|
@ -8,7 +8,6 @@ const ContentItem = ({
|
|||
content,
|
||||
formInputFields,
|
||||
inputs,
|
||||
resolvedPlaceholderValues,
|
||||
onInputChange,
|
||||
}: ContentItemProps) => {
|
||||
const isInputField = (field: string) => {
|
||||
|
|
@ -30,14 +29,6 @@ const ContentItem = ({
|
|||
return formInputFields.find(field => field.output_variable_name === fieldName)
|
||||
}, [formInputFields, fieldName])
|
||||
|
||||
const placeholder = useMemo(() => {
|
||||
if (!formInputField)
|
||||
return ''
|
||||
return formInputField.placeholder.type === 'variable'
|
||||
? resolvedPlaceholderValues?.[fieldName] || ''
|
||||
: formInputField.placeholder.value
|
||||
}, [formInputField, resolvedPlaceholderValues, fieldName])
|
||||
|
||||
if (!isInputField(content)) {
|
||||
return (
|
||||
<Markdown content={content} />
|
||||
|
|
@ -52,7 +43,6 @@ const ContentItem = ({
|
|||
{formInputField.type === 'paragraph' && (
|
||||
<Textarea
|
||||
className="h-[104px] sm:text-xs"
|
||||
placeholder={placeholder}
|
||||
value={inputs[fieldName]}
|
||||
onChange={(e) => { onInputChange(fieldName, e.target.value) }}
|
||||
/>
|
||||
|
|
|
|||
|
|
@ -1,5 +1,7 @@
|
|||
'use client'
|
||||
import type { HumanInputFormProps } from './type'
|
||||
import type { ButtonProps } from '@/app/components/base/button'
|
||||
import type { UserAction } from '@/app/components/workflow/nodes/human-input/types'
|
||||
import * as React from 'react'
|
||||
import { useCallback, useState } from 'react'
|
||||
import Button from '@/app/components/base/button'
|
||||
|
|
@ -11,7 +13,7 @@ const HumanInputForm = ({
|
|||
onSubmit,
|
||||
}: HumanInputFormProps) => {
|
||||
const formToken = formData.form_token
|
||||
const defaultInputs = initializeInputs(formData.inputs)
|
||||
const defaultInputs = initializeInputs(formData.inputs, formData.resolved_placeholder_values || {})
|
||||
const contentList = splitByOutputVar(formData.form_content)
|
||||
const [inputs, setInputs] = useState(defaultInputs)
|
||||
const [isSubmitting, setIsSubmitting] = useState(false)
|
||||
|
|
@ -36,17 +38,16 @@ const HumanInputForm = ({
|
|||
key={index}
|
||||
content={content}
|
||||
formInputFields={formData.inputs}
|
||||
resolvedPlaceholderValues={formData.resolved_placeholder_values || {}}
|
||||
inputs={inputs}
|
||||
onInputChange={handleInputsChange}
|
||||
/>
|
||||
))}
|
||||
<div className="flex flex-wrap gap-1 py-1">
|
||||
{formData.actions.map((action: any) => (
|
||||
{formData.actions.map((action: UserAction) => (
|
||||
<Button
|
||||
key={action.id}
|
||||
disabled={isSubmitting}
|
||||
variant={getButtonStyle(action.button_style) as any}
|
||||
variant={getButtonStyle(action.button_style) as ButtonProps['variant']}
|
||||
onClick={() => submit(formToken, action.id, inputs)}
|
||||
>
|
||||
{action.title}
|
||||
|
|
|
|||
|
|
@ -29,6 +29,5 @@ export type ContentItemProps = {
|
|||
content: string
|
||||
formInputFields: FormInputItem[]
|
||||
inputs: Record<string, string>
|
||||
resolvedPlaceholderValues?: Record<string, string>
|
||||
onInputChange: (name: string, value: any) => void
|
||||
}
|
||||
|
|
|
|||
|
|
@ -30,11 +30,11 @@ export const splitByOutputVar = (content: string): string[] => {
|
|||
return parts.filter(part => part.length > 0)
|
||||
}
|
||||
|
||||
export const initializeInputs = (formInputs: FormInputItem[]) => {
|
||||
export const initializeInputs = (formInputs: FormInputItem[], defaultValues: Record<string, string> = {}) => {
|
||||
const initialInputs: Record<string, any> = {}
|
||||
formInputs.forEach((item) => {
|
||||
if (item.type === 'text-input' || item.type === 'paragraph')
|
||||
initialInputs[item.output_variable_name] = ''
|
||||
initialInputs[item.output_variable_name] = item.placeholder.type === 'variable' ? defaultValues[item.output_variable_name] || '' : item.placeholder.value
|
||||
else
|
||||
initialInputs[item.output_variable_name] = undefined
|
||||
})
|
||||
|
|
|
|||
|
|
@ -71,17 +71,19 @@ const HITLInputComponentUI: FC<HITLInputComponentUIProps> = ({
|
|||
if (editBtn)
|
||||
editBtn.removeEventListener('click', showEditModal)
|
||||
}
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, [])
|
||||
|
||||
const removeBtnRef = useRef<HTMLDivElement>(null)
|
||||
useEffect(() => {
|
||||
const removeBtn = removeBtnRef.current
|
||||
const removeHandler = () => onRemove(varName)
|
||||
if (removeBtn)
|
||||
removeBtn.addEventListener('click', () => onRemove(varName))
|
||||
removeBtn.addEventListener('click', removeHandler)
|
||||
|
||||
return () => {
|
||||
if (removeBtn)
|
||||
removeBtn.removeEventListener('click', () => onRemove(varName))
|
||||
removeBtn.removeEventListener('click', removeHandler)
|
||||
}
|
||||
}, [onRemove, varName])
|
||||
|
||||
|
|
@ -123,7 +125,7 @@ const HITLInputComponentUI: FC<HITLInputComponentUIProps> = ({
|
|||
/>
|
||||
)}
|
||||
{!isPlaceholderVariable && (
|
||||
<div className="system-xs-medium max-w-full truncate text-text-quaternary">{formInput.placeholder.value}</div>
|
||||
<div className="system-xs-medium max-w-full truncate text-components-input-text-filled">{formInput.placeholder.value}</div>
|
||||
)}
|
||||
</div>
|
||||
|
||||
|
|
|
|||
|
|
@ -7,6 +7,7 @@ import { produce } from 'immer'
|
|||
import * as React from 'react'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import Tooltip from '@/app/components/base/tooltip'
|
||||
import { useNodesSyncDraft } from '@/app/components/workflow/hooks'
|
||||
import MethodItem from './method-item'
|
||||
import MethodSelector from './method-selector'
|
||||
import UpgradeModal from './upgrade-modal'
|
||||
|
|
@ -35,6 +36,7 @@ const DeliveryMethodForm: React.FC<Props> = ({
|
|||
readonly,
|
||||
}) => {
|
||||
const { t } = useTranslation()
|
||||
const { handleSyncWorkflowDraft } = useNodesSyncDraft()
|
||||
|
||||
const handleMethodChange = (target: DeliveryMethod) => {
|
||||
const newMethods = produce(value, (draft) => {
|
||||
|
|
@ -43,6 +45,7 @@ const DeliveryMethodForm: React.FC<Props> = ({
|
|||
draft[index] = target
|
||||
})
|
||||
onChange(newMethods)
|
||||
handleSyncWorkflowDraft(true, true)
|
||||
}
|
||||
|
||||
const handleMethodAdd = (newMethod: DeliveryMethod) => {
|
||||
|
|
|
|||
|
|
@ -81,23 +81,32 @@ const EmailInput = ({
|
|||
return emailRegex.test(email)
|
||||
}
|
||||
|
||||
const handleEmailAdd = () => {
|
||||
const emailAddress = searchKey.trim()
|
||||
if (!checkEmailValid(emailAddress))
|
||||
return
|
||||
if (value.some(item => item.email === emailAddress))
|
||||
return
|
||||
if (list.some(item => item.email === emailAddress)) {
|
||||
const item = list.find(item => item.email === emailAddress)!
|
||||
onSelect(item.id)
|
||||
}
|
||||
else {
|
||||
onAdd(emailAddress)
|
||||
}
|
||||
setSearchKey('')
|
||||
setOpen(false)
|
||||
}
|
||||
|
||||
const handleInputBlur = () => {
|
||||
setIsFocus(false)
|
||||
handleEmailAdd()
|
||||
}
|
||||
|
||||
const handleKeyDown = (e: React.KeyboardEvent<HTMLInputElement>) => {
|
||||
if (e.key === 'Enter' || e.key === 'Tab' || e.key === ' ' || e.key === ',') {
|
||||
e.preventDefault()
|
||||
const emailAddress = searchKey.trim()
|
||||
if (!checkEmailValid(emailAddress))
|
||||
return
|
||||
if (value.some(item => item.email === emailAddress))
|
||||
return
|
||||
if (list.some(item => item.email === emailAddress)) {
|
||||
const item = list.find(item => item.email === emailAddress)!
|
||||
onSelect(item.id)
|
||||
}
|
||||
else {
|
||||
onAdd(emailAddress)
|
||||
}
|
||||
setSearchKey('')
|
||||
setOpen(false)
|
||||
handleEmailAdd()
|
||||
}
|
||||
else if (e.key === 'Backspace') {
|
||||
if (searchKey === '' && value.length > 0) {
|
||||
|
|
@ -144,7 +153,7 @@ const EmailInput = ({
|
|||
className="system-sm-regular h-6 min-w-[166px] appearance-none bg-transparent p-1 text-components-input-text-filled caret-primary-600 outline-none placeholder:text-components-input-text-placeholder"
|
||||
placeholder={placeholder}
|
||||
onFocus={() => setIsFocus(true)}
|
||||
onBlur={() => setIsFocus(false)}
|
||||
onBlur={handleInputBlur}
|
||||
value={searchKey}
|
||||
onChange={handleValueChange}
|
||||
onKeyDown={handleKeyDown}
|
||||
|
|
|
|||
|
|
@ -25,7 +25,7 @@ const FormContent = ({
|
|||
onSubmit,
|
||||
}: Props) => {
|
||||
const { t } = useTranslation()
|
||||
const defaultInputs = initializeInputs(data.inputs)
|
||||
const defaultInputs = initializeInputs(data.inputs, data.resolved_placeholder_values || {})
|
||||
const contentList = splitByOutputVar(data.form_content)
|
||||
const [inputs, setInputs] = useState(defaultInputs)
|
||||
const [isSubmitting, setIsSubmitting] = useState(false)
|
||||
|
|
@ -63,7 +63,6 @@ const FormContent = ({
|
|||
formInputFields={data.inputs}
|
||||
inputs={inputs}
|
||||
onInputChange={handleInputsChange}
|
||||
resolvedPlaceholderValues={data.resolved_placeholder_values}
|
||||
/>
|
||||
))}
|
||||
<div className="flex flex-wrap gap-1 py-1">
|
||||
|
|
|
|||
|
|
@ -796,7 +796,7 @@
|
|||
},
|
||||
"app/components/base/chat/chat/answer/human-input-content/human-input-form.tsx": {
|
||||
"ts/no-explicit-any": {
|
||||
"count": 4
|
||||
"count": 2
|
||||
}
|
||||
},
|
||||
"app/components/base/chat/chat/answer/human-input-content/type.ts": {
|
||||
|
|
|
|||
Loading…
Reference in New Issue