mirror of https://github.com/langgenius/dify.git
form
This commit is contained in:
parent
edeea0a8b9
commit
8266dc1dcc
|
|
@ -1,11 +1,14 @@
|
|||
import {
|
||||
isValidElement,
|
||||
memo,
|
||||
useCallback,
|
||||
useMemo,
|
||||
} from 'react'
|
||||
import {
|
||||
RiArrowDownSFill,
|
||||
RiDraftLine,
|
||||
RiExternalLinkLine,
|
||||
RiInputField,
|
||||
} from '@remixicon/react'
|
||||
import type { AnyFieldApi } from '@tanstack/react-form'
|
||||
import { useStore } from '@tanstack/react-form'
|
||||
|
|
@ -19,6 +22,11 @@ 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'
|
||||
import ObjectValueList from '@/app/components/workflow/panel/chat-variable-panel/components/object-value-list'
|
||||
import ArrayValueList from '@/app/components/workflow/panel/chat-variable-panel/components/array-value-list'
|
||||
import CodeEditor from '@/app/components/workflow/nodes/_base/components/editor/code-editor'
|
||||
import { CodeLanguage } from '@/app/components/workflow/nodes/code/types'
|
||||
import Button from '@/app/components/base/button'
|
||||
|
||||
export type BaseFieldProps = {
|
||||
fieldClassName?: string
|
||||
|
|
@ -40,6 +48,7 @@ const BaseField = ({
|
|||
}: BaseFieldProps) => {
|
||||
const renderI18nObject = useRenderI18nObject()
|
||||
const {
|
||||
type: typeOrFn,
|
||||
label,
|
||||
required,
|
||||
placeholder,
|
||||
|
|
@ -49,7 +58,13 @@ const BaseField = ({
|
|||
inputContainerClassName: formInputContainerClassName,
|
||||
inputClassName: formInputClassName,
|
||||
show_on = [],
|
||||
url,
|
||||
help,
|
||||
selfFormProps,
|
||||
onChange,
|
||||
} = formSchema
|
||||
const type = typeof typeOrFn === 'function' ? typeOrFn(field.form) : typeOrFn
|
||||
console.log('type', field.name, type)
|
||||
|
||||
const memorizedLabel = useMemo(() => {
|
||||
if (isValidElement(label))
|
||||
|
|
@ -86,7 +101,7 @@ const BaseField = ({
|
|||
|
||||
return option.show_on.every((condition) => {
|
||||
const conditionValue = optionValues[condition.variable]
|
||||
return conditionValue === condition.value
|
||||
return Array.isArray(condition.value) ? condition.value.includes(conditionValue) : conditionValue === condition.value
|
||||
})
|
||||
}).map((option) => {
|
||||
return {
|
||||
|
|
@ -97,17 +112,22 @@ const BaseField = ({
|
|||
}, [options, renderI18nObject])
|
||||
const value = useStore(field.form.store, s => s.values[field.name])
|
||||
const values = useStore(field.form.store, (s) => {
|
||||
return show_on.reduce((acc, condition) => {
|
||||
return (Array.isArray(show_on) ? show_on : show_on(field.form)).reduce((acc, condition) => {
|
||||
acc[condition.variable] = s.values[condition.variable]
|
||||
return acc
|
||||
}, {} as Record<string, any>)
|
||||
})
|
||||
const show = useMemo(() => {
|
||||
return show_on.every((condition) => {
|
||||
return (Array.isArray(show_on) ? show_on : show_on(field.form)).every((condition) => {
|
||||
const conditionValue = values[condition.variable]
|
||||
return conditionValue === condition.value
|
||||
console.log('conditionValue', condition.value, field.name, conditionValue)
|
||||
return Array.isArray(condition.value) ? condition.value.includes(conditionValue) : conditionValue === condition.value
|
||||
})
|
||||
}, [values, show_on])
|
||||
}, [values, show_on, field.name])
|
||||
const handleChange = useCallback((value: any) => {
|
||||
field.handleChange(value)
|
||||
onChange?.(field.form)
|
||||
}, [field, onChange])
|
||||
|
||||
if (!show)
|
||||
return null
|
||||
|
|
@ -122,26 +142,39 @@ const BaseField = ({
|
|||
)
|
||||
}
|
||||
{
|
||||
formSchema.type === FormTypeEnum.collapse && (
|
||||
type === FormTypeEnum.collapse && (
|
||||
<RiArrowDownSFill
|
||||
className={cn(
|
||||
'h-4 w-4',
|
||||
value && 'rotate-180',
|
||||
)}
|
||||
onClick={() => field.handleChange(!value)}
|
||||
onClick={() => handleChange(!value)}
|
||||
/>
|
||||
)
|
||||
}
|
||||
{
|
||||
type === FormTypeEnum.editMode && (
|
||||
<Button
|
||||
variant='ghost'
|
||||
size='small'
|
||||
className='text-text-tertiary'
|
||||
onClick={() => handleChange(!value)}
|
||||
>
|
||||
{value ? <RiInputField className='mr-1 h-3.5 w-3.5' /> : <RiDraftLine className='mr-1 h-3.5 w-3.5' />}
|
||||
{selfFormProps?.(field.form)?.editModeLabel}
|
||||
</Button>
|
||||
)
|
||||
}
|
||||
</div>
|
||||
<div className={cn(inputContainerClassName, formInputContainerClassName)}>
|
||||
{
|
||||
formSchema.type === FormTypeEnum.textInput && (
|
||||
type === FormTypeEnum.textInput && (
|
||||
<Input
|
||||
id={field.name}
|
||||
name={field.name}
|
||||
className={cn(inputClassName, formInputClassName)}
|
||||
value={value || ''}
|
||||
onChange={e => field.handleChange(e.target.value)}
|
||||
onChange={e => handleChange(e.target.value)}
|
||||
onBlur={field.handleBlur}
|
||||
disabled={disabled}
|
||||
placeholder={memorizedPlaceholder}
|
||||
|
|
@ -149,14 +182,14 @@ const BaseField = ({
|
|||
)
|
||||
}
|
||||
{
|
||||
formSchema.type === FormTypeEnum.secretInput && (
|
||||
type === FormTypeEnum.secretInput && (
|
||||
<Input
|
||||
id={field.name}
|
||||
name={field.name}
|
||||
type='password'
|
||||
className={cn(inputClassName, formInputClassName)}
|
||||
value={value || ''}
|
||||
onChange={e => field.handleChange(e.target.value)}
|
||||
onChange={e => handleChange(e.target.value)}
|
||||
onBlur={field.handleBlur}
|
||||
disabled={disabled}
|
||||
placeholder={memorizedPlaceholder}
|
||||
|
|
@ -164,14 +197,14 @@ const BaseField = ({
|
|||
)
|
||||
}
|
||||
{
|
||||
formSchema.type === FormTypeEnum.textNumber && (
|
||||
type === FormTypeEnum.textNumber && (
|
||||
<Input
|
||||
id={field.name}
|
||||
name={field.name}
|
||||
type='number'
|
||||
className={cn(inputClassName, formInputClassName)}
|
||||
value={value || ''}
|
||||
onChange={e => field.handleChange(e.target.value)}
|
||||
onChange={e => handleChange(e.target.value)}
|
||||
onBlur={field.handleBlur}
|
||||
disabled={disabled}
|
||||
placeholder={memorizedPlaceholder}
|
||||
|
|
@ -179,10 +212,10 @@ const BaseField = ({
|
|||
)
|
||||
}
|
||||
{
|
||||
formSchema.type === FormTypeEnum.select && (
|
||||
type === FormTypeEnum.select && (
|
||||
<PureSelect
|
||||
value={value}
|
||||
onChange={v => field.handleChange(v)}
|
||||
onChange={handleChange}
|
||||
disabled={disabled}
|
||||
placeholder={memorizedPlaceholder}
|
||||
options={memorizedOptions}
|
||||
|
|
@ -191,7 +224,7 @@ const BaseField = ({
|
|||
)
|
||||
}
|
||||
{
|
||||
formSchema.type === FormTypeEnum.radio && (
|
||||
type === FormTypeEnum.radio && (
|
||||
<div className={cn(
|
||||
memorizedOptions.length < 3 ? 'flex items-center space-x-2' : 'space-y-2',
|
||||
)}>
|
||||
|
|
@ -205,10 +238,10 @@ const BaseField = ({
|
|||
inputClassName,
|
||||
formInputClassName,
|
||||
)}
|
||||
onClick={() => field.handleChange(option.value)}
|
||||
onClick={() => handleChange(option.value)}
|
||||
>
|
||||
{
|
||||
formSchema.showRadioUI && (
|
||||
selfFormProps?.(field.form)?.showRadioUI && (
|
||||
<RadioE
|
||||
className='mr-2'
|
||||
isChecked={value === option.value}
|
||||
|
|
@ -223,7 +256,7 @@ const BaseField = ({
|
|||
)
|
||||
}
|
||||
{
|
||||
formSchema.type === FormTypeEnum.textareaInput && (
|
||||
type === FormTypeEnum.textareaInput && (
|
||||
<Textarea
|
||||
className={cn(
|
||||
'min-h-[80px]',
|
||||
|
|
@ -232,17 +265,17 @@ const BaseField = ({
|
|||
)}
|
||||
value={value}
|
||||
placeholder={memorizedPlaceholder}
|
||||
onChange={e => field.handleChange(e.target.value)}
|
||||
onChange={e => handleChange(e.target.value)}
|
||||
onBlur={field.handleBlur}
|
||||
disabled={disabled}
|
||||
/>
|
||||
)
|
||||
}
|
||||
{
|
||||
formSchema.type === FormTypeEnum.promptInput && (
|
||||
type === FormTypeEnum.promptInput && (
|
||||
<PromptEditor
|
||||
value={value}
|
||||
onChange={e => field.handleChange(e)}
|
||||
onChange={handleChange}
|
||||
onBlur={field.handleBlur}
|
||||
editable={!disabled}
|
||||
placeholder={memorizedPlaceholder}
|
||||
|
|
@ -255,11 +288,42 @@ const BaseField = ({
|
|||
)
|
||||
}
|
||||
{
|
||||
formSchema.type === FormTypeEnum.modelSelector && (
|
||||
type === FormTypeEnum.objectList && (
|
||||
<ObjectValueList
|
||||
list={value}
|
||||
onChange={handleChange}
|
||||
/>
|
||||
)
|
||||
}
|
||||
{
|
||||
type === FormTypeEnum.arrayList && (
|
||||
<ArrayValueList
|
||||
isString={selfFormProps?.(field.form)?.isString}
|
||||
list={value}
|
||||
onChange={handleChange}
|
||||
/>
|
||||
)
|
||||
}
|
||||
{
|
||||
type === FormTypeEnum.jsonInput && (
|
||||
<div className='w-full rounded-[10px] bg-components-input-bg-normal py-2 pl-3 pr-1' style={{ height: selfFormProps?.(field.form)?.editorMinHeight }}>
|
||||
<CodeEditor
|
||||
isExpand
|
||||
noWrapper
|
||||
language={CodeLanguage.json}
|
||||
value={value}
|
||||
placeholder={<div className='whitespace-pre'>{selfFormProps?.(field.form)?.placeholder as string}</div>}
|
||||
onChange={handleChange}
|
||||
/>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
{
|
||||
type === FormTypeEnum.modelSelector && (
|
||||
<ModelParameterModal
|
||||
popupClassName='!w-[387px]'
|
||||
value={value}
|
||||
setModel={p => field.handleChange(p)}
|
||||
setModel={handleChange}
|
||||
readonly={disabled}
|
||||
scope={formSchema.scope}
|
||||
isAdvancedMode
|
||||
|
|
@ -267,14 +331,14 @@ const BaseField = ({
|
|||
)
|
||||
}
|
||||
{
|
||||
formSchema.url && (
|
||||
url && (
|
||||
<a
|
||||
className='system-xs-regular mt-4 flex items-center text-text-accent'
|
||||
href={formSchema?.url}
|
||||
href={url}
|
||||
target='_blank'
|
||||
>
|
||||
<span className='break-all'>
|
||||
{renderI18nObject(formSchema?.help as any)}
|
||||
{renderI18nObject(help as any)}
|
||||
</span>
|
||||
{
|
||||
<RiExternalLinkLine className='ml-1 h-3 w-3' />
|
||||
|
|
|
|||
|
|
@ -15,7 +15,7 @@ export type TypeWithI18N<T = string> = {
|
|||
|
||||
export type FormShowOnObject = {
|
||||
variable: string
|
||||
value: string
|
||||
value: string | string[]
|
||||
}
|
||||
|
||||
export enum FormTypeEnum {
|
||||
|
|
@ -34,7 +34,11 @@ export enum FormTypeEnum {
|
|||
dynamicSelect = 'dynamic-select',
|
||||
textareaInput = 'textarea-input',
|
||||
promptInput = 'prompt-input',
|
||||
objectList = 'object-list',
|
||||
arrayList = 'array-list',
|
||||
jsonInput = 'json-input',
|
||||
collapse = 'collapse',
|
||||
editMode = 'edit-mode',
|
||||
}
|
||||
|
||||
export type FormOption = {
|
||||
|
|
@ -47,13 +51,13 @@ export type FormOption = {
|
|||
export type AnyValidators = FieldValidators<any, any, any, any, any, any, any, any, any, any>
|
||||
|
||||
export type FormSchema = {
|
||||
type: FormTypeEnum
|
||||
type: FormTypeEnum | ((form: AnyFormApi) => FormTypeEnum)
|
||||
name: string
|
||||
label: string | ReactNode | TypeWithI18N
|
||||
required: boolean
|
||||
default?: any
|
||||
tooltip?: string | TypeWithI18N
|
||||
show_on?: FormShowOnObject[]
|
||||
show_on?: FormShowOnObject[] | ((form: AnyFormApi) => FormShowOnObject[])
|
||||
url?: string
|
||||
scope?: string
|
||||
help?: string | TypeWithI18N
|
||||
|
|
@ -64,7 +68,8 @@ export type FormSchema = {
|
|||
inputContainerClassName?: string
|
||||
inputClassName?: string
|
||||
validators?: AnyValidators
|
||||
showRadioUI?: boolean
|
||||
selfFormProps?: (form: AnyFormApi) => Record<string, any>
|
||||
onChange?: (form: AnyFormApi) => void
|
||||
}
|
||||
|
||||
export type FormValues = Record<string, any>
|
||||
|
|
|
|||
|
|
@ -52,7 +52,7 @@ const ArrayValueList: FC<Props> = ({
|
|||
<div className='flex items-center space-x-1' key={index}>
|
||||
<Input
|
||||
placeholder={t('workflow.chatVariable.modal.arrayValue') || ''}
|
||||
value={list[index]}
|
||||
value={list[index] || ''}
|
||||
onChange={handleNameChange(index)}
|
||||
type={isString ? 'text' : 'number'}
|
||||
/>
|
||||
|
|
|
|||
|
|
@ -1,23 +1,21 @@
|
|||
import React, { useCallback, useEffect, useMemo } from 'react'
|
||||
import React, { useCallback, useRef } from 'react'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import { useContext } from 'use-context-selector'
|
||||
import { v4 as uuid4 } from 'uuid'
|
||||
import { RiCloseLine, RiDraftLine, RiInputField } from '@remixicon/react'
|
||||
import VariableTypeSelector from '@/app/components/workflow/panel/chat-variable-panel/components/variable-type-select'
|
||||
import ObjectValueList from '@/app/components/workflow/panel/chat-variable-panel/components/object-value-list'
|
||||
import { DEFAULT_OBJECT_VALUE } from '@/app/components/workflow/panel/chat-variable-panel/components/object-value-item'
|
||||
import ArrayValueList from '@/app/components/workflow/panel/chat-variable-panel/components/array-value-list'
|
||||
import {
|
||||
useForm as useTanstackForm,
|
||||
useStore as useTanstackStore,
|
||||
} from '@tanstack/react-form'
|
||||
import { RiCloseLine } from '@remixicon/react'
|
||||
import Button from '@/app/components/base/button'
|
||||
import Input from '@/app/components/base/input'
|
||||
import CodeEditor from '@/app/components/workflow/nodes/_base/components/editor/code-editor'
|
||||
import { ToastContext } from '@/app/components/base/toast'
|
||||
import { useStore } from '@/app/components/workflow/store'
|
||||
import type { ConversationVariable } from '@/app/components/workflow/types'
|
||||
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 { checkKeys } from '@/utils/var'
|
||||
import type { FormRefObject, FormSchema } from '@/app/components/base/form/types'
|
||||
import VariableForm from '@/app/components/base/form/form-scenarios/variable'
|
||||
import { useForm } from '../hooks'
|
||||
|
||||
export type ModalPropsType = {
|
||||
chatVar?: ConversationVariable
|
||||
|
|
@ -25,48 +23,6 @@ export type ModalPropsType = {
|
|||
onSave: (chatVar: ConversationVariable) => void
|
||||
}
|
||||
|
||||
type ObjectValueItem = {
|
||||
key: string
|
||||
type: ChatVarType
|
||||
value: string | number | undefined
|
||||
}
|
||||
|
||||
const typeList = [
|
||||
ChatVarType.String,
|
||||
ChatVarType.Number,
|
||||
ChatVarType.Object,
|
||||
ChatVarType.ArrayString,
|
||||
ChatVarType.ArrayNumber,
|
||||
ChatVarType.ArrayObject,
|
||||
]
|
||||
|
||||
const objectPlaceholder = `# example
|
||||
# {
|
||||
# "name": "ray",
|
||||
# "age": 20
|
||||
# }`
|
||||
const arrayStringPlaceholder = `# example
|
||||
# [
|
||||
# "value1",
|
||||
# "value2"
|
||||
# ]`
|
||||
const arrayNumberPlaceholder = `# example
|
||||
# [
|
||||
# 100,
|
||||
# 200
|
||||
# ]`
|
||||
const arrayObjectPlaceholder = `# example
|
||||
# [
|
||||
# {
|
||||
# "name": "ray",
|
||||
# "age": 20
|
||||
# },
|
||||
# {
|
||||
# "name": "lily",
|
||||
# "age": 18
|
||||
# }
|
||||
# ]`
|
||||
|
||||
const ChatVariableModal = ({
|
||||
chatVar,
|
||||
onClose,
|
||||
|
|
@ -75,133 +31,16 @@ const ChatVariableModal = ({
|
|||
const { t } = useTranslation()
|
||||
const { notify } = useContext(ToastContext)
|
||||
const varList = useStore(s => s.conversationVariables)
|
||||
const [name, setName] = React.useState('')
|
||||
const [type, setType] = React.useState<ChatVarType>(ChatVarType.String)
|
||||
const [value, setValue] = React.useState<any>()
|
||||
const [objectValue, setObjectValue] = React.useState<ObjectValueItem[]>([DEFAULT_OBJECT_VALUE])
|
||||
const [editorContent, setEditorContent] = React.useState<string>()
|
||||
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'
|
||||
return '120px'
|
||||
}, [type])
|
||||
const placeholder = useMemo(() => {
|
||||
if (type === ChatVarType.ArrayString)
|
||||
return arrayStringPlaceholder
|
||||
if (type === ChatVarType.ArrayNumber)
|
||||
return arrayNumberPlaceholder
|
||||
if (type === ChatVarType.ArrayObject)
|
||||
return arrayObjectPlaceholder
|
||||
return objectPlaceholder
|
||||
}, [type])
|
||||
const getObjectValue = useCallback(() => {
|
||||
if (!chatVar || Object.keys(chatVar.value).length === 0)
|
||||
return [DEFAULT_OBJECT_VALUE]
|
||||
|
||||
return Object.keys(chatVar.value).map((key) => {
|
||||
return {
|
||||
key,
|
||||
type: typeof chatVar.value[key] === 'string' ? ChatVarType.String : ChatVarType.Number,
|
||||
value: chatVar.value[key],
|
||||
}
|
||||
})
|
||||
}, [chatVar])
|
||||
const formatValueFromObject = useCallback((list: ObjectValueItem[]) => {
|
||||
return list.reduce((acc: any, curr) => {
|
||||
if (curr.key)
|
||||
acc[curr.key] = curr.value || null
|
||||
return acc
|
||||
}, {})
|
||||
}, [])
|
||||
|
||||
const formatValue = (value: any) => {
|
||||
switch (type) {
|
||||
case ChatVarType.String:
|
||||
return value || ''
|
||||
case ChatVarType.Number:
|
||||
return value || 0
|
||||
case ChatVarType.Object:
|
||||
return editInJSON ? value : formatValueFromObject(objectValue)
|
||||
case ChatVarType.ArrayString:
|
||||
case ChatVarType.ArrayNumber:
|
||||
case ChatVarType.ArrayObject:
|
||||
return value?.filter(Boolean) || []
|
||||
}
|
||||
}
|
||||
const {
|
||||
formSchemas,
|
||||
defaultValues,
|
||||
} = useForm(chatVar)
|
||||
const formRef = useRef<FormRefObject>(null)
|
||||
const form = useTanstackForm({
|
||||
defaultValues,
|
||||
})
|
||||
const type = useTanstackStore(form.store, s => s.values.type)
|
||||
|
||||
const checkVariableName = (value: string) => {
|
||||
const { isValid, errorMessageKey } = checkKeys([value], false)
|
||||
|
|
@ -215,121 +54,31 @@ const ChatVariableModal = ({
|
|||
return true
|
||||
}
|
||||
|
||||
const handleVarNameChange = (e: React.ChangeEvent<HTMLInputElement>) => {
|
||||
replaceSpaceWithUnderscoreInVarNameInput(e.target)
|
||||
if (!!e.target.value && !checkVariableName(e.target.value))
|
||||
return
|
||||
setName(e.target.value || '')
|
||||
}
|
||||
|
||||
const handleTypeChange = (v: ChatVarType) => {
|
||||
setValue(undefined)
|
||||
setEditorContent(undefined)
|
||||
if (v === ChatVarType.ArrayObject)
|
||||
setEditInJSON(true)
|
||||
if (v === ChatVarType.String || v === ChatVarType.Number || v === ChatVarType.Object)
|
||||
setEditInJSON(false)
|
||||
setType(v)
|
||||
}
|
||||
|
||||
const handleEditorChange = (editInJSON: boolean) => {
|
||||
if (type === ChatVarType.Object) {
|
||||
if (editInJSON) {
|
||||
const newValue = !objectValue[0].key ? undefined : formatValueFromObject(objectValue)
|
||||
setValue(newValue)
|
||||
setEditorContent(JSON.stringify(newValue))
|
||||
}
|
||||
else {
|
||||
if (!editorContent) {
|
||||
setValue(undefined)
|
||||
setObjectValue([DEFAULT_OBJECT_VALUE])
|
||||
}
|
||||
else {
|
||||
try {
|
||||
const newValue = JSON.parse(editorContent)
|
||||
setValue(newValue)
|
||||
const newObjectValue = Object.keys(newValue).map((key) => {
|
||||
return {
|
||||
key,
|
||||
type: typeof newValue[key] === 'string' ? ChatVarType.String : ChatVarType.Number,
|
||||
value: newValue[key],
|
||||
}
|
||||
})
|
||||
setObjectValue(newObjectValue)
|
||||
}
|
||||
catch {
|
||||
// ignore JSON.parse errors
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
if (type === ChatVarType.ArrayString || type === ChatVarType.ArrayNumber) {
|
||||
if (editInJSON) {
|
||||
const newValue = (value?.length && value.filter(Boolean).length) ? value.filter(Boolean) : undefined
|
||||
setValue(newValue)
|
||||
if (!editorContent)
|
||||
setEditorContent(JSON.stringify(newValue))
|
||||
}
|
||||
else {
|
||||
setValue(value?.length ? value : [undefined])
|
||||
}
|
||||
}
|
||||
setEditInJSON(editInJSON)
|
||||
}
|
||||
|
||||
const handleEditorValueChange = (content: string) => {
|
||||
if (!content) {
|
||||
setEditorContent(content)
|
||||
return setValue(undefined)
|
||||
}
|
||||
else {
|
||||
setEditorContent(content)
|
||||
try {
|
||||
const newValue = JSON.parse(content)
|
||||
setValue(newValue)
|
||||
}
|
||||
catch {
|
||||
// ignore JSON.parse errors
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const handleSave = () => {
|
||||
const handleConfirm = useCallback(async () => {
|
||||
const {
|
||||
values,
|
||||
} = formRef.current?.getFormValues({}) || { isCheckValidated: false, values: {} }
|
||||
const {
|
||||
name,
|
||||
type,
|
||||
'object-list-value': objectValue,
|
||||
} = values
|
||||
if (!checkVariableName(name))
|
||||
return
|
||||
if (!chatVar && varList.some(chatVar => chatVar.name === name))
|
||||
return notify({ type: 'error', message: 'name is existed' })
|
||||
// if (type !== ChatVarType.Object && !value)
|
||||
// return notify({ type: 'error', message: 'value can not be empty' })
|
||||
if (type === ChatVarType.Object && objectValue.some(item => !item.key && !!item.value))
|
||||
if (type === ChatVarType.Object && objectValue.some((item: any) => !item.key && !!item.value))
|
||||
return notify({ type: 'error', message: 'object key can not be empty' })
|
||||
|
||||
onSave({
|
||||
id: chatVar ? chatVar.id : uuid4(),
|
||||
name,
|
||||
value_type: type,
|
||||
value: formatValue(value),
|
||||
description,
|
||||
})
|
||||
// onSave({
|
||||
// id: chatVar ? chatVar.id : uuid4(),
|
||||
// name,
|
||||
// value_type: type,
|
||||
// value: values,
|
||||
// description,
|
||||
// })
|
||||
onClose()
|
||||
}
|
||||
|
||||
useEffect(() => {
|
||||
if (chatVar) {
|
||||
setName(chatVar.name)
|
||||
setType(chatVar.value_type)
|
||||
setValue(chatVar.value)
|
||||
setDescription(chatVar.description)
|
||||
setObjectValue(getObjectValue())
|
||||
if (chatVar.value_type === ChatVarType.ArrayObject) {
|
||||
setEditorContent(JSON.stringify(chatVar.value))
|
||||
setEditInJSON(true)
|
||||
}
|
||||
else {
|
||||
setEditInJSON(false)
|
||||
}
|
||||
}
|
||||
}, [chatVar, getObjectValue])
|
||||
}, [onClose, notify, t, varList, chatVar, checkVariableName])
|
||||
|
||||
return (
|
||||
<div
|
||||
|
|
@ -347,130 +96,17 @@ const ChatVariableModal = ({
|
|||
</div>
|
||||
</div>
|
||||
<div className='max-h-[480px] overflow-y-auto px-4 py-2'>
|
||||
{/* name */}
|
||||
<div className='mb-4'>
|
||||
<div className='system-sm-semibold mb-1 flex h-6 items-center text-text-secondary'>{t('workflow.chatVariable.modal.name')}</div>
|
||||
<div className='flex'>
|
||||
<Input
|
||||
placeholder={t('workflow.chatVariable.modal.namePlaceholder') || ''}
|
||||
value={name}
|
||||
onChange={handleVarNameChange}
|
||||
onBlur={e => checkVariableName(e.target.value)}
|
||||
type='text'
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
{/* type */}
|
||||
<div className='mb-4'>
|
||||
<div className='system-sm-semibold mb-1 flex h-6 items-center text-text-secondary'>{t('workflow.chatVariable.modal.type')}</div>
|
||||
<div className='flex'>
|
||||
<VariableTypeSelector
|
||||
value={type}
|
||||
list={typeList}
|
||||
onSelect={handleTypeChange}
|
||||
popupClassName='w-[327px]'
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
{/* default value */}
|
||||
<div className='mb-4'>
|
||||
<div className='system-sm-semibold mb-1 flex h-6 items-center justify-between text-text-secondary'>
|
||||
<div>{t('workflow.chatVariable.modal.value')}</div>
|
||||
{(type === ChatVarType.ArrayString || type === ChatVarType.ArrayNumber) && (
|
||||
<Button
|
||||
variant='ghost'
|
||||
size='small'
|
||||
className='text-text-tertiary'
|
||||
onClick={() => handleEditorChange(!editInJSON)}
|
||||
>
|
||||
{editInJSON ? <RiInputField className='mr-1 h-3.5 w-3.5' /> : <RiDraftLine className='mr-1 h-3.5 w-3.5' />}
|
||||
{editInJSON ? t('workflow.chatVariable.modal.oneByOne') : t('workflow.chatVariable.modal.editInJSON')}
|
||||
</Button>
|
||||
)}
|
||||
{type === ChatVarType.Object && (
|
||||
<Button
|
||||
variant='ghost'
|
||||
size='small'
|
||||
className='text-text-tertiary'
|
||||
onClick={() => handleEditorChange(!editInJSON)}
|
||||
>
|
||||
{editInJSON ? <RiInputField className='mr-1 h-3.5 w-3.5' /> : <RiDraftLine className='mr-1 h-3.5 w-3.5' />}
|
||||
{editInJSON ? t('workflow.chatVariable.modal.editInForm') : t('workflow.chatVariable.modal.editInJSON')}
|
||||
</Button>
|
||||
)}
|
||||
</div>
|
||||
<div className='flex'>
|
||||
{type === ChatVarType.String && (
|
||||
// Input will remove \n\r, so use Textarea just like description area
|
||||
<textarea
|
||||
className='system-sm-regular placeholder:system-sm-regular block h-20 w-full resize-none appearance-none rounded-lg border border-transparent bg-components-input-bg-normal p-2 text-components-input-text-filled caret-primary-600 outline-none placeholder:text-components-input-text-placeholder hover:border-components-input-border-hover hover:bg-components-input-bg-hover focus:border-components-input-border-active focus:bg-components-input-bg-active focus:shadow-xs'
|
||||
value={value}
|
||||
placeholder={t('workflow.chatVariable.modal.valuePlaceholder') || ''}
|
||||
onChange={e => setValue(e.target.value)}
|
||||
/>
|
||||
)}
|
||||
{type === ChatVarType.Number && (
|
||||
<Input
|
||||
placeholder={t('workflow.chatVariable.modal.valuePlaceholder') || ''}
|
||||
value={value}
|
||||
onChange={e => setValue(Number(e.target.value))}
|
||||
type='number'
|
||||
/>
|
||||
)}
|
||||
{type === ChatVarType.Object && !editInJSON && (
|
||||
<ObjectValueList
|
||||
list={objectValue}
|
||||
onChange={setObjectValue}
|
||||
/>
|
||||
)}
|
||||
{type === ChatVarType.ArrayString && !editInJSON && (
|
||||
<ArrayValueList
|
||||
isString
|
||||
list={value || [undefined]}
|
||||
onChange={setValue}
|
||||
/>
|
||||
)}
|
||||
{type === ChatVarType.ArrayNumber && !editInJSON && (
|
||||
<ArrayValueList
|
||||
isString={false}
|
||||
list={value || [undefined]}
|
||||
onChange={setValue}
|
||||
/>
|
||||
)}
|
||||
{editInJSON && (
|
||||
<div className='w-full rounded-[10px] bg-components-input-bg-normal py-2 pl-3 pr-1' style={{ height: editorMinHeight }}>
|
||||
<CodeEditor
|
||||
isExpand
|
||||
noWrapper
|
||||
language={CodeLanguage.json}
|
||||
value={editorContent}
|
||||
placeholder={<div className='whitespace-pre'>{placeholder}</div>}
|
||||
onChange={handleEditorValueChange}
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
{/* description */}
|
||||
<div className=''>
|
||||
<div className='system-sm-semibold mb-1 flex h-6 items-center text-text-secondary'>{t('workflow.chatVariable.modal.description')}</div>
|
||||
<div className='flex'>
|
||||
<textarea
|
||||
className='system-sm-regular placeholder:system-sm-regular block h-20 w-full resize-none appearance-none rounded-lg border border-transparent bg-components-input-bg-normal p-2 text-components-input-text-filled caret-primary-600 outline-none placeholder:text-components-input-text-placeholder hover:border-components-input-border-hover hover:bg-components-input-bg-hover focus:border-components-input-border-active focus:bg-components-input-bg-active focus:shadow-xs'
|
||||
value={description}
|
||||
placeholder={t('workflow.chatVariable.modal.descriptionPlaceholder') || ''}
|
||||
onChange={e => setDescription(e.target.value)}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<VariableForm
|
||||
formSchemas={variableFormSchemas}
|
||||
formFromProps={form}
|
||||
ref={formRef}
|
||||
formSchemas={formSchemas as FormSchema[]}
|
||||
defaultValues={defaultValues}
|
||||
/>
|
||||
</div>
|
||||
<div className='flex flex-row-reverse rounded-b-2xl p-4 pt-2'>
|
||||
<div className='flex gap-2'>
|
||||
<Button onClick={onClose}>{t('common.operation.cancel')}</Button>
|
||||
<Button variant='primary' onClick={handleSave}>{t('common.operation.save')}</Button>
|
||||
<Button variant='primary' onClick={handleConfirm}>{t('common.operation.save')}</Button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -0,0 +1 @@
|
|||
export * from './use-form'
|
||||
|
|
@ -0,0 +1,293 @@
|
|||
import { useTranslation } from 'react-i18next'
|
||||
import type {
|
||||
AnyFormApi,
|
||||
} from '@tanstack/react-form'
|
||||
import { ChatVarType } from '@/app/components/workflow/panel/chat-variable-panel/type'
|
||||
import { useCallback, useMemo } from 'react'
|
||||
// import { DEFAULT_OBJECT_VALUE } from '@/app/components/workflow/panel/chat-variable-panel/components/object-value-item'
|
||||
// import type { ConversationVariable } from '@/app/components/workflow/types'
|
||||
|
||||
const objectPlaceholder = `# example
|
||||
# {
|
||||
# "name": "ray",
|
||||
# "age": 20
|
||||
# }`
|
||||
const arrayStringPlaceholder = `# example
|
||||
# [
|
||||
# "value1",
|
||||
# "value2"
|
||||
# ]`
|
||||
const arrayNumberPlaceholder = `# example
|
||||
# [
|
||||
# 100,
|
||||
# 200
|
||||
# ]`
|
||||
const arrayObjectPlaceholder = `# example
|
||||
# [
|
||||
# {
|
||||
# "name": "ray",
|
||||
# "age": 20
|
||||
# },
|
||||
# {
|
||||
# "name": "lily",
|
||||
# "age": 18
|
||||
# }
|
||||
# ]`
|
||||
const typeList = [
|
||||
ChatVarType.String,
|
||||
ChatVarType.Number,
|
||||
ChatVarType.Object,
|
||||
ChatVarType.ArrayString,
|
||||
ChatVarType.ArrayNumber,
|
||||
ChatVarType.ArrayObject,
|
||||
'memory',
|
||||
]
|
||||
|
||||
export const useForm = () => {
|
||||
const { t } = useTranslation()
|
||||
const getEditModeLabel = useCallback((form: AnyFormApi) => {
|
||||
const {
|
||||
type,
|
||||
editInJSON,
|
||||
} = form.state.values
|
||||
const editModeLabelWhenFalse = t('workflow.chatVariable.modal.editInJSON')
|
||||
let editModeLabelWhenTrue = t('workflow.chatVariable.modal.oneByOne')
|
||||
if (type === ChatVarType.Object)
|
||||
editModeLabelWhenTrue = t('workflow.chatVariable.modal.editInForm')
|
||||
|
||||
return {
|
||||
editModeLabel: editInJSON ? editModeLabelWhenTrue : editModeLabelWhenFalse,
|
||||
}
|
||||
}, [t])
|
||||
const handleTypeChange = useCallback((form: AnyFormApi) => {
|
||||
const {
|
||||
resetField,
|
||||
} = form
|
||||
resetField('editInJSON')
|
||||
resetField('objectListValue')
|
||||
resetField('arrayListValue')
|
||||
resetField('jsonValue')
|
||||
}, [])
|
||||
const handleEditInJSONChange = useCallback((form: AnyFormApi) => {
|
||||
const {
|
||||
resetField,
|
||||
} = form
|
||||
resetField('objectListValue')
|
||||
resetField('arrayListValue')
|
||||
resetField('jsonValue')
|
||||
}, [])
|
||||
|
||||
const getValueFormType = useCallback((form: AnyFormApi) => {
|
||||
const {
|
||||
type,
|
||||
editInJSON,
|
||||
} = form.state.values
|
||||
if (type === ChatVarType.String) {
|
||||
return 'textarea-input'
|
||||
}
|
||||
else if (type === ChatVarType.Number) {
|
||||
return 'number-input'
|
||||
}
|
||||
else if (type === ChatVarType.Object) {
|
||||
if (editInJSON)
|
||||
return 'json-input'
|
||||
else
|
||||
return 'object-list'
|
||||
}
|
||||
else if (type === ChatVarType.ArrayString || type === ChatVarType.ArrayNumber) {
|
||||
if (editInJSON)
|
||||
return 'json-input'
|
||||
else
|
||||
return 'array-list'
|
||||
}
|
||||
else if (type === ChatVarType.ArrayObject) {
|
||||
return 'json-input'
|
||||
}
|
||||
}, [])
|
||||
const getSelfFormProps = useCallback((form: AnyFormApi) => {
|
||||
const {
|
||||
type,
|
||||
editInJSON,
|
||||
} = form.state.values
|
||||
if (editInJSON || type === ChatVarType.ArrayObject) {
|
||||
let minHeight = '120px'
|
||||
if (type === ChatVarType.ArrayObject)
|
||||
minHeight = '240px'
|
||||
let placeholder = objectPlaceholder
|
||||
if (type === ChatVarType.ArrayString)
|
||||
placeholder = arrayStringPlaceholder
|
||||
else if (type === ChatVarType.ArrayNumber)
|
||||
placeholder = arrayNumberPlaceholder
|
||||
else if (type === ChatVarType.ArrayObject)
|
||||
placeholder = arrayObjectPlaceholder
|
||||
return {
|
||||
editorMinHeight: minHeight,
|
||||
placeholder,
|
||||
}
|
||||
}
|
||||
if (type === ChatVarType.ArrayString || type === ChatVarType.ArrayNumber) {
|
||||
if (!editInJSON) {
|
||||
return {
|
||||
isString: type === ChatVarType.ArrayString,
|
||||
}
|
||||
}
|
||||
}
|
||||
}, [])
|
||||
|
||||
const formSchemas = useMemo(() => {
|
||||
return [
|
||||
{
|
||||
name: 'name',
|
||||
label: t('workflow.chatVariable.modal.name'),
|
||||
type: 'text-input',
|
||||
placeholder: t('workflow.chatVariable.modal.namePlaceholder'),
|
||||
required: true,
|
||||
},
|
||||
{
|
||||
name: 'type',
|
||||
label: t('workflow.chatVariable.modal.type'),
|
||||
type: 'select',
|
||||
options: typeList.map(type => ({
|
||||
label: type,
|
||||
value: type,
|
||||
})),
|
||||
onChange: handleTypeChange,
|
||||
required: true,
|
||||
},
|
||||
{
|
||||
name: 'editInJSON',
|
||||
label: '',
|
||||
type: 'edit-mode',
|
||||
show_on: [
|
||||
{
|
||||
variable: 'type',
|
||||
value: [ChatVarType.Object, ChatVarType.ArrayString, ChatVarType.ArrayNumber],
|
||||
},
|
||||
],
|
||||
selfFormProps: getEditModeLabel,
|
||||
labelClassName: '-mb-9 justify-end',
|
||||
onChange: handleEditInJSONChange,
|
||||
},
|
||||
{
|
||||
name: 'value',
|
||||
label: t('workflow.chatVariable.modal.value'),
|
||||
type: getValueFormType,
|
||||
placeholder: t('workflow.chatVariable.modal.valuePlaceholder'),
|
||||
show_on: [
|
||||
{
|
||||
variable: 'type',
|
||||
value: [ChatVarType.String, ChatVarType.Number, ChatVarType.Object, ChatVarType.ArrayString, ChatVarType.ArrayNumber, ChatVarType.ArrayObject],
|
||||
},
|
||||
],
|
||||
selfFormProps: getSelfFormProps,
|
||||
},
|
||||
// {
|
||||
// name: 'objectListValue',
|
||||
// label: t('workflow.chatVariable.modal.value'),
|
||||
// type: 'object-list',
|
||||
// placeholder: t('workflow.chatVariable.modal.valuePlaceholder'),
|
||||
// show_on: [
|
||||
// {
|
||||
// variable: 'type',
|
||||
// value: ChatVarType.Object,
|
||||
// },
|
||||
// {
|
||||
// variable: 'editInJSON',
|
||||
// value: false,
|
||||
// },
|
||||
// ],
|
||||
// },
|
||||
// {
|
||||
// name: 'arrayListValue',
|
||||
// label: t('workflow.chatVariable.modal.value'),
|
||||
// type: 'array-list',
|
||||
// placeholder: t('workflow.chatVariable.modal.valuePlaceholder'),
|
||||
// show_on: [
|
||||
// {
|
||||
// variable: 'type',
|
||||
// value: [ChatVarType.ArrayString, ChatVarType.ArrayNumber],
|
||||
// },
|
||||
// {
|
||||
// variable: 'editInJSON',
|
||||
// value: false,
|
||||
// },
|
||||
// ],
|
||||
// selfFormProps: getArrayListProps,
|
||||
// },
|
||||
// {
|
||||
// name: 'jsonValue',
|
||||
// label: t('workflow.chatVariable.modal.value'),
|
||||
// type: 'json-input',
|
||||
// placeholder: arrayObjectPlaceholder,
|
||||
// show_on: getJsonEditorShowOn,
|
||||
// selfFormProps: getJsonEditorProps,
|
||||
// },
|
||||
// {
|
||||
// name: 'description',
|
||||
// label: t('workflow.chatVariable.modal.description'),
|
||||
// type: 'textarea-input',
|
||||
// placeholder: t('workflow.chatVariable.modal.descriptionPlaceholder'),
|
||||
// show_on: [
|
||||
// {
|
||||
// variable: 'type',
|
||||
// value: [ChatVarType.String, ChatVarType.Number, ChatVarType.Object, ChatVarType.ArrayString, ChatVarType.ArrayNumber, ChatVarType.ArrayObject],
|
||||
// },
|
||||
// ],
|
||||
// },
|
||||
// {
|
||||
// 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,
|
||||
// },
|
||||
// ],
|
||||
// },
|
||||
]
|
||||
}, [t, handleTypeChange, handleEditInJSONChange, getValueFormType, getSelfFormProps])
|
||||
const defaultValues = useMemo(() => {
|
||||
return {
|
||||
type: ChatVarType.String,
|
||||
value: '',
|
||||
// textareaInputValue: '',
|
||||
// numberInputValue: 0,
|
||||
// objectListValue: [DEFAULT_OBJECT_VALUE],
|
||||
// arrayListValue: [undefined],
|
||||
editInJSON: false,
|
||||
}
|
||||
}, [])
|
||||
|
||||
return {
|
||||
formSchemas,
|
||||
defaultValues,
|
||||
}
|
||||
}
|
||||
Loading…
Reference in New Issue