This commit is contained in:
zxhlyh 2025-08-08 17:21:52 +08:00
parent 985becbc41
commit bca0b7c087
5 changed files with 206 additions and 6 deletions

View File

@ -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

View File

@ -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>
)
}

View File

@ -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)

View File

@ -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
}

View File

@ -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'>