mirror of https://github.com/langgenius/dify.git
form
This commit is contained in:
parent
985becbc41
commit
bca0b7c087
|
|
@ -3,7 +3,10 @@ import {
|
|||
memo,
|
||||
useMemo,
|
||||
} from 'react'
|
||||
import { RiExternalLinkLine } from '@remixicon/react'
|
||||
import {
|
||||
RiArrowDownSFill,
|
||||
RiExternalLinkLine,
|
||||
} from '@remixicon/react'
|
||||
import type { AnyFieldApi } from '@tanstack/react-form'
|
||||
import { useStore } from '@tanstack/react-form'
|
||||
import cn from '@/utils/classnames'
|
||||
|
|
@ -13,6 +16,9 @@ import type { FormSchema } from '@/app/components/base/form/types'
|
|||
import { FormTypeEnum } from '@/app/components/base/form/types'
|
||||
import { useRenderI18nObject } from '@/hooks/use-i18n'
|
||||
import RadioE from '@/app/components/base/radio/ui'
|
||||
import Textarea from '@/app/components/base/textarea'
|
||||
import PromptEditor from '@/app/components/base/prompt-editor'
|
||||
import ModelParameterModal from '@/app/components/plugins/plugin-detail-panel/model-selector'
|
||||
|
||||
export type BaseFieldProps = {
|
||||
fieldClassName?: string
|
||||
|
|
@ -39,6 +45,9 @@ const BaseField = ({
|
|||
placeholder,
|
||||
options,
|
||||
labelClassName: formLabelClassName,
|
||||
fieldClassName: formFieldClassName,
|
||||
inputContainerClassName: formInputContainerClassName,
|
||||
inputClassName: formInputClassName,
|
||||
show_on = [],
|
||||
} = formSchema
|
||||
|
||||
|
|
@ -104,7 +113,7 @@ const BaseField = ({
|
|||
return null
|
||||
|
||||
return (
|
||||
<div className={cn(fieldClassName)}>
|
||||
<div className={cn(fieldClassName, formFieldClassName)}>
|
||||
<div className={cn(labelClassName, formLabelClassName)}>
|
||||
{memorizedLabel}
|
||||
{
|
||||
|
|
@ -112,14 +121,25 @@ const BaseField = ({
|
|||
<span className='ml-1 text-text-destructive-secondary'>*</span>
|
||||
)
|
||||
}
|
||||
{
|
||||
formSchema.type === FormTypeEnum.collapse && (
|
||||
<RiArrowDownSFill
|
||||
className={cn(
|
||||
'h-4 w-4',
|
||||
value && 'rotate-180',
|
||||
)}
|
||||
onClick={() => field.handleChange(!value)}
|
||||
/>
|
||||
)
|
||||
}
|
||||
</div>
|
||||
<div className={cn(inputContainerClassName)}>
|
||||
<div className={cn(inputContainerClassName, formInputContainerClassName)}>
|
||||
{
|
||||
formSchema.type === FormTypeEnum.textInput && (
|
||||
<Input
|
||||
id={field.name}
|
||||
name={field.name}
|
||||
className={cn(inputClassName)}
|
||||
className={cn(inputClassName, formInputClassName)}
|
||||
value={value || ''}
|
||||
onChange={e => field.handleChange(e.target.value)}
|
||||
onBlur={field.handleBlur}
|
||||
|
|
@ -134,7 +154,7 @@ const BaseField = ({
|
|||
id={field.name}
|
||||
name={field.name}
|
||||
type='password'
|
||||
className={cn(inputClassName)}
|
||||
className={cn(inputClassName, formInputClassName)}
|
||||
value={value || ''}
|
||||
onChange={e => field.handleChange(e.target.value)}
|
||||
onBlur={field.handleBlur}
|
||||
|
|
@ -149,7 +169,7 @@ const BaseField = ({
|
|||
id={field.name}
|
||||
name={field.name}
|
||||
type='number'
|
||||
className={cn(inputClassName)}
|
||||
className={cn(inputClassName, formInputClassName)}
|
||||
value={value || ''}
|
||||
onChange={e => field.handleChange(e.target.value)}
|
||||
onBlur={field.handleBlur}
|
||||
|
|
@ -183,6 +203,7 @@ const BaseField = ({
|
|||
'system-sm-regular hover:bg-components-option-card-option-hover-bg hover:border-components-option-card-option-hover-border flex h-8 flex-[1] grow cursor-pointer items-center justify-center rounded-lg border border-components-option-card-option-border bg-components-option-card-option-bg p-2 text-text-secondary',
|
||||
value === option.value && 'border-components-option-card-option-selected-border bg-components-option-card-option-selected-bg text-text-primary shadow-xs',
|
||||
inputClassName,
|
||||
formInputClassName,
|
||||
)}
|
||||
onClick={() => field.handleChange(option.value)}
|
||||
>
|
||||
|
|
@ -201,6 +222,50 @@ const BaseField = ({
|
|||
</div>
|
||||
)
|
||||
}
|
||||
{
|
||||
formSchema.type === FormTypeEnum.textareaInput && (
|
||||
<Textarea
|
||||
className={cn(
|
||||
'min-h-[80px]',
|
||||
inputClassName,
|
||||
formInputClassName,
|
||||
)}
|
||||
value={value}
|
||||
placeholder={memorizedPlaceholder}
|
||||
onChange={e => field.handleChange(e.target.value)}
|
||||
onBlur={field.handleBlur}
|
||||
disabled={disabled}
|
||||
/>
|
||||
)
|
||||
}
|
||||
{
|
||||
formSchema.type === FormTypeEnum.promptInput && (
|
||||
<PromptEditor
|
||||
value={value}
|
||||
onChange={e => field.handleChange(e)}
|
||||
onBlur={field.handleBlur}
|
||||
editable={!disabled}
|
||||
placeholder={memorizedPlaceholder}
|
||||
className={cn(
|
||||
'min-h-[80px]',
|
||||
inputClassName,
|
||||
formInputClassName,
|
||||
)}
|
||||
/>
|
||||
)
|
||||
}
|
||||
{
|
||||
formSchema.type === FormTypeEnum.modelSelector && (
|
||||
<ModelParameterModal
|
||||
popupClassName='!w-[387px]'
|
||||
value={value}
|
||||
setModel={p => field.handleChange(p)}
|
||||
readonly={disabled}
|
||||
scope={formSchema.scope}
|
||||
isAdvancedMode
|
||||
/>
|
||||
)
|
||||
}
|
||||
{
|
||||
formSchema.url && (
|
||||
<a
|
||||
|
|
|
|||
|
|
@ -4,6 +4,7 @@ import {
|
|||
useImperativeHandle,
|
||||
useMemo,
|
||||
} from 'react'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import type {
|
||||
AnyFieldApi,
|
||||
AnyFormApi,
|
||||
|
|
@ -24,6 +25,7 @@ import {
|
|||
useGetFormValues,
|
||||
useGetValidators,
|
||||
} from '@/app/components/base/form/hooks'
|
||||
import { Button } from '@/app/components/base/button'
|
||||
|
||||
export type BaseFormProps = {
|
||||
formSchemas?: FormSchema[]
|
||||
|
|
@ -32,6 +34,8 @@ export type BaseFormProps = {
|
|||
ref?: FormRef
|
||||
disabled?: boolean
|
||||
formFromProps?: AnyFormApi
|
||||
onSubmit?: (values: Record<string, any>) => void
|
||||
onCancel?: () => void
|
||||
} & Pick<BaseFieldProps, 'fieldClassName' | 'labelClassName' | 'inputContainerClassName' | 'inputClassName'>
|
||||
|
||||
const BaseForm = ({
|
||||
|
|
@ -45,7 +49,10 @@ const BaseForm = ({
|
|||
ref,
|
||||
disabled,
|
||||
formFromProps,
|
||||
onSubmit,
|
||||
onCancel,
|
||||
}: BaseFormProps) => {
|
||||
const { t } = useTranslation()
|
||||
const initialDefaultValues = useMemo(() => {
|
||||
if (defaultValues)
|
||||
return defaultValues
|
||||
|
|
@ -119,6 +126,28 @@ const BaseForm = ({
|
|||
className={cn(formClassName)}
|
||||
>
|
||||
{formSchemas.map(renderFieldWrapper)}
|
||||
{
|
||||
onSubmit && (
|
||||
<div className='flex justify-end space-x-2'>
|
||||
{
|
||||
onCancel && (
|
||||
<Button
|
||||
variant='secondary'
|
||||
onClick={onCancel}
|
||||
>
|
||||
{t('common.operation.cancel')}
|
||||
</Button>
|
||||
)
|
||||
}
|
||||
<Button
|
||||
variant='primary'
|
||||
onClick={() => onSubmit(form.getValues())}
|
||||
>
|
||||
{t('common.operation.save')}
|
||||
</Button>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
</form>
|
||||
)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -0,0 +1,25 @@
|
|||
import { memo } from 'react'
|
||||
import { BaseForm } from '../../components/base'
|
||||
import type { BaseFormProps } from '../../components/base'
|
||||
|
||||
const VariableForm = ({
|
||||
formSchemas = [],
|
||||
defaultValues,
|
||||
ref,
|
||||
formFromProps,
|
||||
...rest
|
||||
}: BaseFormProps) => {
|
||||
return (
|
||||
<BaseForm
|
||||
ref={ref}
|
||||
formSchemas={formSchemas}
|
||||
defaultValues={defaultValues}
|
||||
formClassName='space-y-3'
|
||||
labelClassName='h-6 flex items-center mb-1 system-sm-medium text-text-secondary'
|
||||
formFromProps={formFromProps}
|
||||
{...rest}
|
||||
/>
|
||||
)
|
||||
}
|
||||
|
||||
export default memo(VariableForm)
|
||||
|
|
@ -32,6 +32,9 @@ export enum FormTypeEnum {
|
|||
multiToolSelector = 'array[tools]',
|
||||
appSelector = 'app-selector',
|
||||
dynamicSelect = 'dynamic-select',
|
||||
textareaInput = 'textarea-input',
|
||||
promptInput = 'prompt-input',
|
||||
collapse = 'collapse',
|
||||
}
|
||||
|
||||
export type FormOption = {
|
||||
|
|
@ -56,7 +59,10 @@ export type FormSchema = {
|
|||
help?: string | TypeWithI18N
|
||||
placeholder?: string | TypeWithI18N
|
||||
options?: FormOption[]
|
||||
fieldClassName?: string
|
||||
labelClassName?: string
|
||||
inputContainerClassName?: string
|
||||
inputClassName?: string
|
||||
validators?: AnyValidators
|
||||
showRadioUI?: boolean
|
||||
}
|
||||
|
|
|
|||
|
|
@ -17,6 +17,7 @@ import { CodeLanguage } from '@/app/components/workflow/nodes/code/types'
|
|||
import { ChatVarType } from '@/app/components/workflow/panel/chat-variable-panel/type'
|
||||
import cn from '@/utils/classnames'
|
||||
import { checkKeys, replaceSpaceWithUnderscoreInVarNameInput } from '@/utils/var'
|
||||
import VariableForm from '@/app/components/base/form/form-scenarios/variable'
|
||||
|
||||
export type ModalPropsType = {
|
||||
chatVar?: ConversationVariable
|
||||
|
|
@ -82,6 +83,77 @@ const ChatVariableModal = ({
|
|||
const [editInJSON, setEditInJSON] = React.useState(false)
|
||||
const [description, setDescription] = React.useState<string>('')
|
||||
|
||||
const variableFormSchemas = useMemo(() => {
|
||||
return [
|
||||
{
|
||||
name: 'name',
|
||||
label: t('workflow.chatVariable.modal.name'),
|
||||
type: 'text-input',
|
||||
placeholder: t('workflow.chatVariable.modal.namePlaceholder'),
|
||||
},
|
||||
{
|
||||
name: 'type',
|
||||
label: t('workflow.chatVariable.modal.type'),
|
||||
type: 'select',
|
||||
options: typeList.map(type => ({
|
||||
label: type,
|
||||
value: type,
|
||||
})),
|
||||
},
|
||||
{
|
||||
name: 'value',
|
||||
label: t('workflow.chatVariable.modal.value'),
|
||||
type: 'textarea',
|
||||
placeholder: t('workflow.chatVariable.modal.valuePlaceholder'),
|
||||
fieldClassName: 'h-20',
|
||||
},
|
||||
{
|
||||
name: 'description',
|
||||
label: t('workflow.chatVariable.modal.description'),
|
||||
type: 'textarea-input',
|
||||
placeholder: t('workflow.chatVariable.modal.descriptionPlaceholder'),
|
||||
},
|
||||
{
|
||||
name: 'memoryTemplate',
|
||||
label: 'Memory template',
|
||||
type: 'prompt-input',
|
||||
},
|
||||
{
|
||||
name: 'updateTrigger',
|
||||
label: 'Update trigger',
|
||||
type: 'radio',
|
||||
required: true,
|
||||
fieldClassName: 'flex items-center justify-between',
|
||||
options: [
|
||||
{
|
||||
label: 'Every N turns',
|
||||
value: 'every_n_turns',
|
||||
},
|
||||
{
|
||||
label: 'Auto',
|
||||
value: 'auto',
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
name: 'moreSettings',
|
||||
label: 'More settings',
|
||||
type: 'collapse',
|
||||
},
|
||||
{
|
||||
name: 'memoryModel',
|
||||
label: 'Memory model',
|
||||
type: 'model-selector',
|
||||
show_on: [
|
||||
{
|
||||
variable: 'moreSettings',
|
||||
value: true,
|
||||
},
|
||||
],
|
||||
},
|
||||
]
|
||||
}, [])
|
||||
|
||||
const editorMinHeight = useMemo(() => {
|
||||
if (type === ChatVarType.ArrayObject)
|
||||
return '240px'
|
||||
|
|
@ -391,6 +463,9 @@ const ChatVariableModal = ({
|
|||
/>
|
||||
</div>
|
||||
</div>
|
||||
<VariableForm
|
||||
formSchemas={variableFormSchemas}
|
||||
/>
|
||||
</div>
|
||||
<div className='flex flex-row-reverse rounded-b-2xl p-4 pt-2'>
|
||||
<div className='flex gap-2'>
|
||||
|
|
|
|||
Loading…
Reference in New Issue