This commit is contained in:
zxhlyh
2025-08-26 16:07:56 +08:00
parent 239e15eac6
commit 761ad336e9
12 changed files with 517 additions and 65 deletions

View File

@ -40,7 +40,7 @@ const ChatVariableModal = ({
const form = useTanstackForm({
defaultValues,
})
const type = useTanstackStore(form.store, s => s.values.type)
const type = useTanstackStore(form.store, (s: any) => s.values.value_type)
const checkVariableName = (value: string) => {
const { isValid, errorMessageKey } = checkKeys([value], false)
@ -57,19 +57,23 @@ const ChatVariableModal = ({
const handleConfirm = useCallback(async () => {
const {
values,
isCheckValidated,
} = formRef.current?.getFormValues({
needCheckValidatedValues: true,
}) || { isCheckValidated: false, values: {} }
const {
name,
type,
'object-list-value': objectValue,
value,
} = values
console.log(values, 'xxx')
if (!isCheckValidated)
return
if (!checkVariableName(name))
return
if (!chatVar && varList.some(chatVar => chatVar.name === name))
return notify({ type: 'error', message: 'name is existed' })
if (type === ChatVarType.Object && objectValue.some((item: any) => !item.key && !!item.value))
if (type === ChatVarType.Object && value.some((item: any) => !item.key && !!item.value))
return notify({ type: 'error', message: 'object key can not be empty' })
// onSave({

View File

@ -1,4 +1,5 @@
import { ChatVarType } from './type'
import { DEFAULT_OBJECT_VALUE } from '@/app/components/workflow/panel/chat-variable-panel/components/object-value-item'
export const objectPlaceholder = `# example
# {
@ -33,5 +34,12 @@ export const typeList = [
ChatVarType.ArrayString,
ChatVarType.ArrayNumber,
ChatVarType.ArrayObject,
'memory',
ChatVarType.Memory,
]
export const TYPE_STRING_DEFAULT_VALUE = ''
export const TYPE_NUMBER_DEFAULT_VALUE = 0
export const TYPE_OBJECT_DEFAULT_VALUE = [DEFAULT_OBJECT_VALUE]
export const TYPE_ARRAY_STRING_DEFAULT_VALUE = [undefined]
export const TYPE_ARRAY_NUMBER_DEFAULT_VALUE = [undefined]
export const TYPE_ARRAY_OBJECT_DEFAULT_VALUE = undefined

View File

@ -4,13 +4,20 @@ import { useMemo } from 'react'
import { useTypeSchema } from './use-type-schema'
import { useValueSchema } from './use-value-schema'
import { useEditInJSONSchema } from './use-edit-in-json-schema'
import {
useMemoryDefaultValues,
useMemorySchema,
} from './use-memory-schema'
import type { ConversationVariable } from '@/app/components/workflow/types'
export const useForm = () => {
export const useForm = (chatVar?: ConversationVariable) => {
const { t } = useTranslation()
const typeSchema = useTypeSchema()
const valueSchema = useValueSchema()
const editInJSONSchema = useEditInJSONSchema()
const memorySchema = useMemorySchema()
const memoryDefaultValues = useMemoryDefaultValues()
const formSchemas = useMemo(() => {
return [
@ -24,15 +31,31 @@ export const useForm = () => {
typeSchema,
editInJSONSchema,
valueSchema,
{
name: 'description',
label: t('workflow.chatVariable.modal.description'),
type: 'textarea-input',
placeholder: t('workflow.chatVariable.modal.descriptionPlaceholder'),
show_on: [
{
variable: 'value_type',
value: [ChatVarType.String, ChatVarType.Number, ChatVarType.Object, ChatVarType.ArrayString, ChatVarType.ArrayNumber, ChatVarType.ArrayObject],
},
],
},
...memorySchema,
]
}, [t, valueSchema, typeSchema, editInJSONSchema])
}, [t, valueSchema, typeSchema, editInJSONSchema, memorySchema])
const defaultValues = useMemo(() => {
if (chatVar)
return chatVar
return {
type: ChatVarType.String,
value_type: ChatVarType.Memory,
value: '',
editInJSON: false,
...memoryDefaultValues,
}
}, [])
}, [chatVar])
return {
formSchemas,

View File

@ -4,30 +4,35 @@ import type {
AnyFormApi,
} from '@tanstack/react-form'
import { ChatVarType } from '@/app/components/workflow/panel/chat-variable-panel/type'
import { getValue } from '@/app/components/workflow/panel/chat-variable-panel/utils'
export const useEditInJSONSchema = () => {
const { t } = useTranslation()
const getEditModeLabel = useCallback((form: AnyFormApi) => {
const {
type,
value_type,
editInJSON,
} = form.state.values
const editModeLabelWhenFalse = t('workflow.chatVariable.modal.editInJSON')
let editModeLabelWhenTrue = t('workflow.chatVariable.modal.oneByOne')
if (type === ChatVarType.Object)
if (value_type === ChatVarType.Object)
editModeLabelWhenTrue = t('workflow.chatVariable.modal.editInForm')
return {
editModeLabel: editInJSON ? editModeLabelWhenTrue : editModeLabelWhenFalse,
}
}, [t])
const handleEditInJSONChange = useCallback((form: AnyFormApi) => {
const handleEditInJSONChange = useCallback((form: AnyFormApi, v: boolean) => {
const {
resetField,
setFieldValue,
getFieldValue,
} = form
resetField('objectListValue')
resetField('arrayListValue')
resetField('jsonValue')
const type = getFieldValue('value_type')
const value = getFieldValue('value')
const newValue = getValue(type, !v, value)
setFieldValue('value', newValue)
}, [])
return {
@ -36,7 +41,7 @@ export const useEditInJSONSchema = () => {
type: 'edit-mode',
show_on: [
{
variable: 'type',
variable: 'value_type',
value: [ChatVarType.Object, ChatVarType.ArrayString, ChatVarType.ArrayNumber],
},
],

View File

@ -0,0 +1,227 @@
import { FormTypeEnum } from '@/app/components/base/form/types'
import type { FormSchema } from '@/app/components/base/form/types'
import { ChatVarType } from '@/app/components/workflow/panel/chat-variable-panel/type'
export const useMemorySchema = () => {
return [
{
name: 'template',
label: 'Memory Template',
type: FormTypeEnum.promptInput,
tooltip: 'template',
placeholder: 'Enter template for AI memory (e.g., user profile, preferences, context)...',
show_on: [
{
variable: 'value_type',
value: [ChatVarType.Memory],
},
],
selfFormProps: {
withTopDivider: true,
},
},
{
name: 'instruction',
label: 'Update Instructions',
type: FormTypeEnum.promptInput,
show_on: [
{
variable: 'value_type',
value: [ChatVarType.Memory],
},
],
selfFormProps: {
enablePromptGenerator: true,
placeholder: 'How should the AI update this memory...',
},
},
{
name: 'strategy',
label: 'Update trigger',
type: FormTypeEnum.radio,
default: 'on_turns',
fieldClassName: 'flex justify-between',
inputClassName: 'w-[102px]',
options: [
{
label: 'Every N turns',
value: 'on_turns',
},
{
label: 'Auto',
value: 'on_auto',
},
],
show_on: [
{
variable: 'value_type',
value: [ChatVarType.Memory],
},
],
selfFormProps: {
withTopDivider: true,
},
},
{
name: 'update_turns',
label: 'Update every',
type: FormTypeEnum.textNumber,
fieldClassName: 'flex justify-between',
show_on: [
{
variable: 'value_type',
value: [ChatVarType.Memory],
},
{
variable: 'strategy',
value: 'on_turns',
},
],
selfFormProps: {
withSlider: true,
sliderMin: 0,
sliderMax: 1000,
sliderStep: 50,
sliderClassName: 'w-[98px]',
inputWrapperClassName: 'w-[102px]',
},
},
{
name: 'scope',
label: 'Scope',
type: FormTypeEnum.radio,
default: 'app',
fieldClassName: 'flex justify-between',
inputClassName: 'w-[102px]',
options: [
{
label: 'Conversation',
value: 'conversation',
},
{
label: 'App',
value: 'app',
},
],
show_on: [
{
variable: 'value_type',
value: [ChatVarType.Memory],
},
],
selfFormProps: {
withTopDivider: true,
},
},
{
name: 'term',
label: 'Term',
type: FormTypeEnum.radio,
default: 'session',
fieldClassName: 'flex justify-between',
inputClassName: 'w-[102px]',
options: [
{
label: 'Session',
value: 'session',
},
{
label: 'Persistent',
value: 'persistent',
},
],
show_on: [
{
variable: 'value_type',
value: [ChatVarType.Memory],
},
],
selfFormProps: {
withBottomDivider: true,
},
},
{
name: 'more',
label: 'MoreSettings',
type: FormTypeEnum.collapse,
show_on: [
{
variable: 'value_type',
value: [ChatVarType.Memory],
},
],
},
{
name: 'model',
label: 'Memory model',
type: FormTypeEnum.modelSelector,
show_on: [
{
variable: 'value_type',
value: [ChatVarType.Memory],
},
{
variable: 'more',
value: true,
},
],
},
{
name: 'schedule_mode',
label: 'Update method',
type: FormTypeEnum.radio,
options: [
{
label: 'Sync',
value: 'sync',
},
{
label: 'Async',
value: 'async',
},
],
show_on: [
{
variable: 'value_type',
value: [ChatVarType.Memory],
},
{
variable: 'more',
value: true,
},
],
},
{
name: 'end_user_editable',
label: 'Editable in web app',
type: FormTypeEnum.boolean,
fieldClassName: 'flex justify-between',
show_on: [
{
variable: 'value_type',
value: [ChatVarType.Memory],
},
{
variable: 'more',
value: true,
},
],
},
] as FormSchema[]
}
export const useMemoryDefaultValues = () => {
return {
template: '',
instruction: '',
strategy: 'on_turns',
update_turns: 0,
scope: 'conversation',
term: 'session',
more: false,
model: '',
schedule_mode: 'sync',
end_user_visible: false,
end_user_editable: false,
}
}

View File

@ -4,8 +4,15 @@ import type {
AnyFormApi,
} from '@tanstack/react-form'
import { ChatVarType } from '@/app/components/workflow/panel/chat-variable-panel/type'
import { DEFAULT_OBJECT_VALUE } from '@/app/components/workflow/panel/chat-variable-panel/components/object-value-item'
import { typeList } from '@/app/components/workflow/panel/chat-variable-panel/constants'
import {
TYPE_ARRAY_NUMBER_DEFAULT_VALUE,
TYPE_ARRAY_OBJECT_DEFAULT_VALUE,
TYPE_ARRAY_STRING_DEFAULT_VALUE,
TYPE_NUMBER_DEFAULT_VALUE,
TYPE_OBJECT_DEFAULT_VALUE,
TYPE_STRING_DEFAULT_VALUE,
} from '@/app/components/workflow/panel/chat-variable-panel/constants'
export const useTypeSchema = () => {
const { t } = useTranslation()
@ -13,22 +20,23 @@ export const useTypeSchema = () => {
const {
setFieldValue,
} = form
setFieldValue('editInJSON', false)
if (v === ChatVarType.String)
setFieldValue('value', '')
setFieldValue('value', TYPE_STRING_DEFAULT_VALUE)
else if (v === ChatVarType.Number)
setFieldValue('value', 0)
setFieldValue('value', TYPE_NUMBER_DEFAULT_VALUE)
else if (v === ChatVarType.Object)
setFieldValue('value', [DEFAULT_OBJECT_VALUE])
setFieldValue('value', TYPE_OBJECT_DEFAULT_VALUE)
else if (v === ChatVarType.ArrayString)
setFieldValue('value', [undefined])
setFieldValue('value', TYPE_ARRAY_STRING_DEFAULT_VALUE)
else if (v === ChatVarType.ArrayNumber)
setFieldValue('value', [undefined])
setFieldValue('value', TYPE_ARRAY_NUMBER_DEFAULT_VALUE)
else if (v === ChatVarType.ArrayObject)
setFieldValue('value', undefined)
setFieldValue('value', TYPE_ARRAY_OBJECT_DEFAULT_VALUE)
}, [])
return {
name: 'type',
name: 'value_type',
label: t('workflow.chatVariable.modal.type'),
type: 'select',
options: typeList.map(type => ({

View File

@ -15,57 +15,57 @@ export const useValueSchema = () => {
const { t } = useTranslation()
const getValueFormType = useCallback((form: AnyFormApi) => {
const {
type,
value_type,
editInJSON,
} = form.state.values
console.log(editInJSON, 'editInJSON', type, 'type')
if (type === ChatVarType.String) {
if (value_type === ChatVarType.String) {
return 'textarea-input'
}
else if (type === ChatVarType.Number) {
else if (value_type === ChatVarType.Number) {
return 'number-input'
}
else if (type === ChatVarType.Object) {
else if (value_type === ChatVarType.Object) {
if (editInJSON)
return 'json-input'
else
return 'object-list'
}
else if (type === ChatVarType.ArrayString || type === ChatVarType.ArrayNumber) {
else if (value_type === ChatVarType.ArrayString || value_type === ChatVarType.ArrayNumber) {
if (editInJSON)
return 'json-input'
else
return 'array-list'
}
else if (type === ChatVarType.ArrayObject) {
else if (value_type === ChatVarType.ArrayObject) {
return 'json-input'
}
}, [])
const getSelfFormProps = useCallback((form: AnyFormApi) => {
const {
type,
value_type,
editInJSON,
} = form.state.values
if (editInJSON || type === ChatVarType.ArrayObject) {
if (editInJSON || value_type === ChatVarType.ArrayObject) {
let minHeight = '120px'
if (type === ChatVarType.ArrayObject)
if (value_type === ChatVarType.ArrayObject)
minHeight = '240px'
let placeholder = objectPlaceholder
if (type === ChatVarType.ArrayString)
if (value_type === ChatVarType.ArrayString)
placeholder = arrayStringPlaceholder
else if (type === ChatVarType.ArrayNumber)
else if (value_type === ChatVarType.ArrayNumber)
placeholder = arrayNumberPlaceholder
else if (type === ChatVarType.ArrayObject)
else if (value_type === ChatVarType.ArrayObject)
placeholder = arrayObjectPlaceholder
return {
editorMinHeight: minHeight,
placeholder,
}
}
if (type === ChatVarType.ArrayString || type === ChatVarType.ArrayNumber) {
if (value_type === ChatVarType.ArrayString || value_type === ChatVarType.ArrayNumber) {
if (!editInJSON) {
return {
isString: type === ChatVarType.ArrayString,
isString: value_type === ChatVarType.ArrayString,
}
}
}
@ -78,12 +78,12 @@ export const useValueSchema = () => {
placeholder: t('workflow.chatVariable.modal.valuePlaceholder'),
show_on: [
{
variable: 'type',
variable: 'value_type',
value: [ChatVarType.String, ChatVarType.Number, ChatVarType.Object, ChatVarType.ArrayString, ChatVarType.ArrayNumber, ChatVarType.ArrayObject],
},
{
variable: 'editInJSON',
value: [true, false],
value: [true, false, undefined],
},
],
selfFormProps: getSelfFormProps,

View File

@ -5,4 +5,11 @@ export enum ChatVarType {
ArrayString = 'array[string]',
ArrayNumber = 'array[number]',
ArrayObject = 'array[object]',
Memory = 'memory',
}
export type ObjectValueItem = {
key: string
type: ChatVarType
value: string | number | undefined
}

View File

@ -0,0 +1,66 @@
import type { ObjectValueItem } from './type'
import { ChatVarType } from './type'
import {
TYPE_ARRAY_STRING_DEFAULT_VALUE,
TYPE_OBJECT_DEFAULT_VALUE,
} from './constants'
const formatValueFromObject = (list: ObjectValueItem[]) => {
return list.reduce((acc: any, curr) => {
if (curr.key)
acc[curr.key] = curr.value || null
return acc
}, {})
}
export const getObjectValue = (fromJson: boolean, value?: any) => {
if (fromJson) {
if (!value)
return TYPE_OBJECT_DEFAULT_VALUE
try {
const result = JSON.parse(value)
const newValue = Object.keys(result).map((key) => {
return {
key,
type: typeof result[key] === 'string' ? ChatVarType.String : ChatVarType.Number,
value: result[key],
}
})
return newValue
}
catch {
return TYPE_OBJECT_DEFAULT_VALUE
}
}
else {
if (!value)
return undefined
return JSON.stringify(formatValueFromObject(value))
}
}
export const getArrayValue = (fromJson: boolean, value?: any) => {
if (fromJson) {
if (!value)
return TYPE_ARRAY_STRING_DEFAULT_VALUE
return JSON.parse(value)
}
else {
if (!value)
return undefined
return JSON.stringify((value?.length && value.filter(Boolean).length) ? value.filter(Boolean) : undefined)
}
}
export const getValue = (type: ChatVarType, fromJson: boolean, value?: any) => {
switch (type) {
case ChatVarType.Object:
return getObjectValue(fromJson, value)
case ChatVarType.ArrayNumber:
case ChatVarType.ArrayString:
return getArrayValue(fromJson, value)
default:
return value
}
}