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

@ -27,6 +27,10 @@ import ArrayValueList from '@/app/components/workflow/panel/chat-variable-panel/
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'
import PromptGeneratorBtn from '@/app/components/workflow/nodes/llm/components/prompt-generator-btn'
import Switch from '@/app/components/base/switch'
import Slider from '@/app/components/base/slider'
import Tooltip from '@/app/components/base/tooltip'
export type BaseFieldProps = {
fieldClassName?: string
@ -62,6 +66,7 @@ const BaseField = ({
help,
selfFormProps,
onChange,
tooltip,
} = formSchema
const type = typeof typeOrFn === 'function' ? typeOrFn(field.form) : typeOrFn
@ -82,6 +87,13 @@ const BaseField = ({
if (typeof placeholder === 'object' && placeholder !== null)
return renderI18nObject(placeholder as Record<string, string>)
}, [placeholder, renderI18nObject])
const memorizedTooltip = useMemo(() => {
if (typeof tooltip === 'string')
return tooltip
if (typeof tooltip === 'object' && tooltip !== null)
return renderI18nObject(tooltip as Record<string, string>)
}, [tooltip, renderI18nObject])
const optionValues = useStore(field.form.store, (s) => {
const result: Record<string, any> = {}
options?.forEach((option) => {
@ -127,12 +139,26 @@ const BaseField = ({
onChange?.(field.form, value)
}, [field, onChange])
const selfProps = typeof selfFormProps === 'function' ? selfFormProps(field.form) : selfFormProps
if (!show)
return null
return (
<>
{
selfProps?.withTopDivider && (
<div className='h-px w-full bg-divider-subtle' />
)
}
<div className={cn(fieldClassName, formFieldClassName)}>
<div className={cn(labelClassName, formLabelClassName)}>
<div
className={cn(type === FormTypeEnum.collapse && 'cursor-pointer', labelClassName, formLabelClassName)}
onClick={() => {
if (type === FormTypeEnum.collapse)
handleChange(!value)
}}
>
{memorizedLabel}
{
required && !isValidElement(label) && (
@ -143,10 +169,9 @@ const BaseField = ({
type === FormTypeEnum.collapse && (
<RiArrowDownSFill
className={cn(
'h-4 w-4',
value && 'rotate-180',
'h-4 w-4 text-text-quaternary',
value && '-rotate-90',
)}
onClick={() => handleChange(!value)}
/>
)
}
@ -159,10 +184,18 @@ const BaseField = ({
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}
{selfProps?.editModeLabel}
</Button>
)
}
{
memorizedTooltip && (
<Tooltip
popupContent={memorizedTooltip}
triggerClassName='ml-1 w-4 h-4'
/>
)
}
</div>
<div className={cn(inputContainerClassName, formInputContainerClassName)}>
{
@ -195,7 +228,7 @@ const BaseField = ({
)
}
{
type === FormTypeEnum.textNumber && (
type === FormTypeEnum.textNumber && !selfProps?.withSlider && (
<Input
id={field.name}
name={field.name}
@ -209,6 +242,34 @@ const BaseField = ({
/>
)
}
{
type === FormTypeEnum.textNumber && selfProps?.withSlider && (
<div className='flex items-center space-x-2'>
<Slider
min={selfProps?.sliderMin}
max={selfProps?.sliderMax}
step={selfProps?.sliderStep}
value={value}
onChange={handleChange}
className={cn(selfProps.sliderClassName)}
trackClassName={cn(selfProps.sliderTrackClassName)}
thumbClassName={cn(selfProps.sliderThumbClassName)}
/>
<Input
id={field.name}
name={field.name}
type='number'
className={cn('', inputClassName, formInputClassName)}
wrapperClassName={cn(selfProps.inputWrapperClassName)}
value={value || ''}
onChange={e => handleChange(e.target.value)}
onBlur={field.handleBlur}
disabled={disabled}
placeholder={memorizedPlaceholder}
/>
</div>
)
}
{
type === FormTypeEnum.select && (
<PureSelect
@ -239,7 +300,7 @@ const BaseField = ({
onClick={() => handleChange(option.value)}
>
{
selfFormProps?.(field.form)?.showRadioUI && (
selfProps?.showRadioUI && (
<RadioE
className='mr-2'
isChecked={value === option.value}
@ -271,18 +332,34 @@ const BaseField = ({
}
{
type === FormTypeEnum.promptInput && (
<PromptEditor
value={value}
onChange={handleChange}
onBlur={field.handleBlur}
editable={!disabled}
placeholder={memorizedPlaceholder}
className={cn(
'min-h-[80px]',
inputClassName,
formInputClassName,
)}
/>
<div className={cn(
'relative rounded-lg bg-components-input-bg-normal p-2',
formInputContainerClassName,
)}>
{
selfProps?.enablePromptGenerator && (
<PromptGeneratorBtn
nodeId={selfProps?.nodeId}
className='absolute right-0 top-[-26px]'
onGenerated={handleChange}
modelConfig={selfProps?.modelConfig}
currentPrompt={value}
/>
)
}
<PromptEditor
value={value}
onChange={handleChange}
onBlur={field.handleBlur}
editable={!disabled}
placeholder={memorizedPlaceholder || selfProps?.placeholder}
className={cn(
'min-h-[80px]',
inputClassName,
formInputClassName,
)}
/>
</div>
)
}
{
@ -296,7 +373,7 @@ const BaseField = ({
{
type === FormTypeEnum.arrayList && (
<ArrayValueList
isString={selfFormProps?.(field.form)?.isString}
isString={selfProps?.isString}
list={value}
onChange={handleChange}
/>
@ -304,13 +381,13 @@ const BaseField = ({
}
{
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 }}>
<div className='w-full rounded-[10px] bg-components-input-bg-normal py-2 pl-3 pr-1' style={{ height: selfProps?.editorMinHeight }}>
<CodeEditor
isExpand
noWrapper
language={CodeLanguage.json}
value={value}
placeholder={<div className='whitespace-pre'>{selfFormProps?.(field.form)?.placeholder as string}</div>}
placeholder={<div className='whitespace-pre'>{selfProps?.placeholder as string}</div>}
onChange={handleChange}
/>
</div>
@ -328,6 +405,15 @@ const BaseField = ({
/>
)
}
{
type === FormTypeEnum.boolean && (
<Switch
defaultValue={value}
onChange={handleChange}
disabled={disabled}
/>
)
}
{
url && (
<a
@ -346,6 +432,12 @@ const BaseField = ({
}
</div>
</div>
{
selfProps?.withBottomDivider && (
<div className='h-px w-full bg-divider-subtle' />
)
}
</>
)
}

View File

@ -68,7 +68,7 @@ export type FormSchema = {
inputContainerClassName?: string
inputClassName?: string
validators?: AnyValidators
selfFormProps?: (form: AnyFormApi) => Record<string, any>
selfFormProps?: ((form: AnyFormApi) => Record<string, any>) | Record<string, any>
onChange?: (form: AnyFormApi, v: any) => void
}

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

View File

@ -156,13 +156,25 @@ export type EnvironmentVariable = {
description: string
}
export type MemoryVariable = {
template?: string
instruction?: string
schedule_mode?: string
model?: any
strategy?: string
update_turns?: number
scope?: string
term?: string
end_user_editable?: boolean
}
export type ConversationVariable = {
id: string
name: string
value_type: ChatVarType
value: any
description: string
}
} & MemoryVariable
export type GlobalVariable = {
name: string