Compare commits

...

23 Commits

@ -7,7 +7,6 @@ import produce from 'immer'
import ModalFoot from '../modal-foot' import ModalFoot from '../modal-foot'
import ConfigSelect from '../config-select' import ConfigSelect from '../config-select'
import ConfigString from '../config-string' import ConfigString from '../config-string'
import SelectTypeItem from '../select-type-item'
import Field from './field' import Field from './field'
import Input from '@/app/components/base/input' import Input from '@/app/components/base/input'
import Toast from '@/app/components/base/toast' import Toast from '@/app/components/base/toast'
@ -20,6 +19,8 @@ import FileUploadSetting from '@/app/components/workflow/nodes/_base/components/
import Checkbox from '@/app/components/base/checkbox' import Checkbox from '@/app/components/base/checkbox'
import { DEFAULT_FILE_UPLOAD_SETTING } from '@/app/components/workflow/constants' import { DEFAULT_FILE_UPLOAD_SETTING } from '@/app/components/workflow/constants'
import { DEFAULT_VALUE_MAX_LEN } from '@/config' import { DEFAULT_VALUE_MAX_LEN } from '@/config'
import type { Item as SelectItem } from './type-select'
import TypeSelector from './type-select'
const TEXT_MAX_LENGTH = 256 const TEXT_MAX_LENGTH = 256
@ -77,23 +78,56 @@ const ConfigModal: FC<IConfigModalProps> = ({
} }
}, []) }, [])
const handleTypeChange = useCallback((type: InputVarType) => { const selectOptions: SelectItem[] = [
return () => { {
const newPayload = produce(tempPayload, (draft) => { name: t('appDebug.variableConfig.text-input'),
draft.type = type value: InputVarType.textInput,
if ([InputVarType.singleFile, InputVarType.multiFiles].includes(type)) { },
(Object.keys(DEFAULT_FILE_UPLOAD_SETTING)).forEach((key) => { {
if (key !== 'max_length') name: t('appDebug.variableConfig.paragraph'),
(draft as any)[key] = (DEFAULT_FILE_UPLOAD_SETTING as any)[key] value: InputVarType.paragraph,
}) },
if (type === InputVarType.multiFiles) {
draft.max_length = DEFAULT_FILE_UPLOAD_SETTING.max_length name: t('appDebug.variableConfig.select'),
} value: InputVarType.select,
if (type === InputVarType.paragraph) },
draft.max_length = DEFAULT_VALUE_MAX_LEN {
}) name: t('appDebug.variableConfig.number'),
setTempPayload(newPayload) value: InputVarType.number,
} },
{
name: t('appDebug.variableConfig.boolean'),
value: InputVarType.boolean,
},
...(supportFile ? [
{
name: t('appDebug.variableConfig.single-file'),
value: InputVarType.singleFile,
},
{
name: t('appDebug.variableConfig.multi-files'),
value: InputVarType.multiFiles,
},
] : []),
]
const handleTypeChange = useCallback((item: SelectItem) => {
const type = item.value as InputVarType
const newPayload = produce(tempPayload, (draft) => {
draft.type = type
if ([InputVarType.singleFile, InputVarType.multiFiles].includes(type)) {
(Object.keys(DEFAULT_FILE_UPLOAD_SETTING)).forEach((key) => {
if (key !== 'max_length')
(draft as any)[key] = (DEFAULT_FILE_UPLOAD_SETTING as any)[key]
})
if (type === InputVarType.multiFiles)
draft.max_length = DEFAULT_FILE_UPLOAD_SETTING.max_length
}
if (type === InputVarType.paragraph)
draft.max_length = DEFAULT_VALUE_MAX_LEN
})
setTempPayload(newPayload)
}, [tempPayload]) }, [tempPayload])
const handleVarKeyBlur = useCallback((e: any) => { const handleVarKeyBlur = useCallback((e: any) => {
@ -135,15 +169,6 @@ const ConfigModal: FC<IConfigModalProps> = ({
if (!isVariableNameValid) if (!isVariableNameValid)
return return
// TODO: check if key already exists. should the consider the edit case
// if (varKeys.map(key => key?.trim()).includes(tempPayload.variable.trim())) {
// Toast.notify({
// type: 'error',
// message: t('appDebug.varKeyError.keyAlreadyExists', { key: tempPayload.variable }),
// })
// return
// }
if (!tempPayload.label) { if (!tempPayload.label) {
Toast.notify({ type: 'error', message: t('appDebug.variableConfig.errorMsg.labelNameRequired') }) Toast.notify({ type: 'error', message: t('appDebug.variableConfig.errorMsg.labelNameRequired') })
return return
@ -197,18 +222,8 @@ const ConfigModal: FC<IConfigModalProps> = ({
> >
<div className='mb-8' ref={modalRef} tabIndex={-1}> <div className='mb-8' ref={modalRef} tabIndex={-1}>
<div className='space-y-2'> <div className='space-y-2'>
<Field title={t('appDebug.variableConfig.fieldType')}> <Field title={t('appDebug.variableConfig.fieldType')}>
<div className='grid grid-cols-3 gap-2'> <TypeSelector value={type} items={selectOptions} onSelect={handleTypeChange} />
<SelectTypeItem type={InputVarType.textInput} selected={type === InputVarType.textInput} onClick={handleTypeChange(InputVarType.textInput)} />
<SelectTypeItem type={InputVarType.paragraph} selected={type === InputVarType.paragraph} onClick={handleTypeChange(InputVarType.paragraph)} />
<SelectTypeItem type={InputVarType.select} selected={type === InputVarType.select} onClick={handleTypeChange(InputVarType.select)} />
<SelectTypeItem type={InputVarType.number} selected={type === InputVarType.number} onClick={handleTypeChange(InputVarType.number)} />
{supportFile && <>
<SelectTypeItem type={InputVarType.singleFile} selected={type === InputVarType.singleFile} onClick={handleTypeChange(InputVarType.singleFile)} />
<SelectTypeItem type={InputVarType.multiFiles} selected={type === InputVarType.multiFiles} onClick={handleTypeChange(InputVarType.multiFiles)} />
</>}
</div>
</Field> </Field>
<Field title={t('appDebug.variableConfig.varName')}> <Field title={t('appDebug.variableConfig.varName')}>

@ -0,0 +1,99 @@
'use client'
import type { FC } from 'react'
import React, { useState } from 'react'
import { ChevronDownIcon } from '@heroicons/react/20/solid'
import { useTranslation } from 'react-i18next'
import classNames from '@/utils/classnames'
import {
PortalToFollowElem,
PortalToFollowElemContent,
PortalToFollowElemTrigger,
} from '@/app/components/base/portal-to-follow-elem'
import InputVarTypeIcon from '@/app/components/workflow/nodes/_base/components/input-var-type-icon'
import type { InputVarType } from '@/app/components/workflow/types'
import cn from '@/utils/classnames'
import Badge from '@/app/components/base/badge'
import { inputVarTypeToVarType } from '@/app/components/workflow/nodes/_base/components/variable/utils'
export type Item = {
value: InputVarType
name: string
}
type Props = {
value: string | number
onSelect: (value: Item) => void
items: Item[]
popupClassName?: string
popupInnerClassName?: string
readonly?: boolean
hideChecked?: boolean
}
const TypeSelector: FC<Props> = ({
value,
onSelect,
items,
popupInnerClassName,
readonly,
}) => {
const { t } = useTranslation()
const [open, setOpen] = useState(false)
const selectedItem = value ? items.find(item => item.value === value) : undefined
return (
<PortalToFollowElem
open={open}
onOpenChange={setOpen}
placement='bottom-start'
offset={4}
>
<PortalToFollowElemTrigger onClick={() => !readonly && setOpen(v => !v)} className='w-full'>
<div
className={classNames(`group flex h-9 items-center justify-between rounded-lg border-0 bg-components-input-bg-normal px-2 text-sm hover:bg-state-base-hover-alt ${readonly ? 'cursor-not-allowed' : 'cursor-pointer'}`)}
title={selectedItem?.name}
>
<div className='flex items-center'>
<InputVarTypeIcon type={selectedItem?.value as InputVarType} className='size-4 shrink-0 text-text-secondary' />
<span
className={`
ml-1.5 ${!selectedItem?.name && 'text-components-input-text-placeholder'}
`}
>
{selectedItem?.name}
</span>
</div>
<div className='flex items-center space-x-1'>
<Badge uppercase={false}>{inputVarTypeToVarType(selectedItem?.value as InputVarType)}</Badge>
<ChevronDownIcon className={cn('h-4 w-4 shrink-0 text-text-quaternary group-hover:text-text-secondary', open && 'text-text-secondary')} />
</div>
</div>
</PortalToFollowElemTrigger>
<PortalToFollowElemContent className='z-[61]'>
<div
className={classNames('w-[432px] rounded-md border-[0.5px] border-components-panel-border bg-components-panel-bg px-1 py-1 text-base shadow-lg focus:outline-none sm:text-sm', popupInnerClassName)}
>
{items.map((item: Item) => (
<div
key={item.value}
className={'flex h-9 cursor-pointer items-center justify-between rounded-lg px-2 text-text-secondary hover:bg-state-base-hover'}
title={item.name}
onClick={() => {
onSelect(item)
setOpen(false)
}}
>
<div className='flex items-center space-x-2'>
<InputVarTypeIcon type={item.value} className='size-4 shrink-0 text-text-secondary' />
<span title={item.name}>{item.name}</span>
</div>
<Badge uppercase={false}>{inputVarTypeToVarType(item.value)}</Badge>
</div>
))}
</div>
</PortalToFollowElemContent>
</PortalToFollowElem>
)
}
export default TypeSelector

@ -190,7 +190,7 @@ const ConfigVar: FC<IConfigVarProps> = ({ promptVariables, readonly, onPromptVar
const handleConfig = ({ key, type, index, name, config, icon, icon_background }: ExternalDataToolParams) => { const handleConfig = ({ key, type, index, name, config, icon, icon_background }: ExternalDataToolParams) => {
// setCurrKey(key) // setCurrKey(key)
setCurrIndex(index) setCurrIndex(index)
if (type !== 'string' && type !== 'paragraph' && type !== 'select' && type !== 'number') { if (type !== 'string' && type !== 'paragraph' && type !== 'select' && type !== 'number' && type !== 'boolean') {
handleOpenExternalDataToolModal({ key, type, index, name, config, icon, icon_background }, promptVariables) handleOpenExternalDataToolModal({ key, type, index, name, config, icon, icon_background }, promptVariables)
return return
} }

@ -65,6 +65,7 @@ const SelectVarType: FC<Props> = ({
<SelectItem type={InputVarType.paragraph} value='paragraph' text={t('appDebug.variableConfig.paragraph')} onClick={handleChange}></SelectItem> <SelectItem type={InputVarType.paragraph} value='paragraph' text={t('appDebug.variableConfig.paragraph')} onClick={handleChange}></SelectItem>
<SelectItem type={InputVarType.select} value='select' text={t('appDebug.variableConfig.select')} onClick={handleChange}></SelectItem> <SelectItem type={InputVarType.select} value='select' text={t('appDebug.variableConfig.select')} onClick={handleChange}></SelectItem>
<SelectItem type={InputVarType.number} value='number' text={t('appDebug.variableConfig.number')} onClick={handleChange}></SelectItem> <SelectItem type={InputVarType.number} value='number' text={t('appDebug.variableConfig.number')} onClick={handleChange}></SelectItem>
<SelectItem type={InputVarType.boolean} value='boolean' text={t('appDebug.variableConfig.boolean')} onClick={handleChange}></SelectItem>
</div> </div>
<div className='h-[1px] border-t border-components-panel-border'></div> <div className='h-[1px] border-t border-components-panel-border'></div>
<div className='p-1'> <div className='p-1'>

@ -8,6 +8,7 @@ import Textarea from '@/app/components/base/textarea'
import { DEFAULT_VALUE_MAX_LEN } from '@/config' import { DEFAULT_VALUE_MAX_LEN } from '@/config'
import type { Inputs } from '@/models/debug' import type { Inputs } from '@/models/debug'
import cn from '@/utils/classnames' import cn from '@/utils/classnames'
import BoolInput from '@/app/components/workflow/nodes/_base/components/before-run-form/bool-input'
type Props = { type Props = {
inputs: Inputs inputs: Inputs
@ -31,7 +32,7 @@ const ChatUserInput = ({
return obj return obj
})() })()
const handleInputValueChange = (key: string, value: string) => { const handleInputValueChange = (key: string, value: string | boolean) => {
if (!(key in promptVariableObj)) if (!(key in promptVariableObj))
return return
@ -55,10 +56,12 @@ const ChatUserInput = ({
className='mb-4 last-of-type:mb-0' className='mb-4 last-of-type:mb-0'
> >
<div> <div>
{type !== 'boolean' && (
<div className='system-sm-semibold mb-1 flex h-6 items-center gap-1 text-text-secondary'> <div className='system-sm-semibold mb-1 flex h-6 items-center gap-1 text-text-secondary'>
<div className='truncate'>{name || key}</div> <div className='truncate'>{name || key}</div>
{!required && <span className='system-xs-regular text-text-tertiary'>{t('workflow.panel.optional')}</span>} {!required && <span className='system-xs-regular text-text-tertiary'>{t('workflow.panel.optional')}</span>}
</div> </div>
)}
<div className='grow'> <div className='grow'>
{type === 'string' && ( {type === 'string' && (
<Input <Input
@ -96,6 +99,14 @@ const ChatUserInput = ({
maxLength={max_length || DEFAULT_VALUE_MAX_LEN} maxLength={max_length || DEFAULT_VALUE_MAX_LEN}
/> />
)} )}
{type === 'boolean' && (
<BoolInput
name={name || key}
value={!!inputs[key]}
required={required}
onChange={(value) => { handleInputValueChange(key, value) }}
/>
)}
</div> </div>
</div> </div>
</div> </div>

@ -34,7 +34,7 @@ import { RefreshCcw01 } from '@/app/components/base/icons/src/vender/line/arrows
import TooltipPlus from '@/app/components/base/tooltip' import TooltipPlus from '@/app/components/base/tooltip'
import ActionButton, { ActionButtonState } from '@/app/components/base/action-button' import ActionButton, { ActionButtonState } from '@/app/components/base/action-button'
import type { ModelConfig as BackendModelConfig, VisionFile, VisionSettings } from '@/types/app' import type { ModelConfig as BackendModelConfig, VisionFile, VisionSettings } from '@/types/app'
import { promptVariablesToUserInputsForm } from '@/utils/model-config' import { formatBooleanInputs, promptVariablesToUserInputsForm } from '@/utils/model-config'
import TextGeneration from '@/app/components/app/text-generate/item' import TextGeneration from '@/app/components/app/text-generate/item'
import { IS_CE_EDITION } from '@/config' import { IS_CE_EDITION } from '@/config'
import type { Inputs } from '@/models/debug' import type { Inputs } from '@/models/debug'
@ -259,7 +259,7 @@ const Debug: FC<IDebug> = ({
} }
const data: Record<string, any> = { const data: Record<string, any> = {
inputs, inputs: formatBooleanInputs(modelConfig.configs.prompt_variables, inputs),
model_config: postModelConfig, model_config: postModelConfig,
} }

@ -60,7 +60,6 @@ import {
useModelListAndDefaultModelAndCurrentProviderAndModel, useModelListAndDefaultModelAndCurrentProviderAndModel,
useTextGenerationCurrentProviderAndModelAndModelList, useTextGenerationCurrentProviderAndModelAndModelList,
} from '@/app/components/header/account-setting/model-provider-page/hooks' } from '@/app/components/header/account-setting/model-provider-page/hooks'
import { fetchCollectionList } from '@/service/tools'
import type { Collection } from '@/app/components/tools/types' import type { Collection } from '@/app/components/tools/types'
import { useStore as useAppStore } from '@/app/components/app/store' import { useStore as useAppStore } from '@/app/components/app/store'
import { import {
@ -82,6 +81,7 @@ import { supportFunctionCall } from '@/utils/tool-call'
import { MittProvider } from '@/context/mitt-context' import { MittProvider } from '@/context/mitt-context'
import { fetchAndMergeValidCompletionParams } from '@/utils/completion-params' import { fetchAndMergeValidCompletionParams } from '@/utils/completion-params'
import Toast from '@/app/components/base/toast' import Toast from '@/app/components/base/toast'
import { fetchCollectionList } from '@/service/tools'
type PublishConfig = { type PublishConfig = {
modelConfig: ModelConfig modelConfig: ModelConfig
@ -693,7 +693,6 @@ const Configuration: FC = () => {
setHasFetchedDetail(true) setHasFetchedDetail(true)
}) })
})() })()
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [appId]) }, [appId])
const promptEmpty = (() => { const promptEmpty = (() => {

@ -22,6 +22,7 @@ import type { VisionFile, VisionSettings } from '@/types/app'
import { DEFAULT_VALUE_MAX_LEN } from '@/config' import { DEFAULT_VALUE_MAX_LEN } from '@/config'
import { useStore as useAppStore } from '@/app/components/app/store' import { useStore as useAppStore } from '@/app/components/app/store'
import cn from '@/utils/classnames' import cn from '@/utils/classnames'
import BoolInput from '@/app/components/workflow/nodes/_base/components/before-run-form/bool-input'
export type IPromptValuePanelProps = { export type IPromptValuePanelProps = {
appType: AppType appType: AppType
@ -66,7 +67,7 @@ const PromptValuePanel: FC<IPromptValuePanelProps> = ({
else { return !modelConfig.configs.prompt_template } else { return !modelConfig.configs.prompt_template }
}, [chatPromptConfig.prompt, completionPromptConfig.prompt?.text, isAdvancedMode, mode, modelConfig.configs.prompt_template, modelModeType]) }, [chatPromptConfig.prompt, completionPromptConfig.prompt?.text, isAdvancedMode, mode, modelConfig.configs.prompt_template, modelModeType])
const handleInputValueChange = (key: string, value: string) => { const handleInputValueChange = (key: string, value: string | boolean) => {
if (!(key in promptVariableObj)) if (!(key in promptVariableObj))
return return
@ -109,10 +110,12 @@ const PromptValuePanel: FC<IPromptValuePanelProps> = ({
className='mb-4 last-of-type:mb-0' className='mb-4 last-of-type:mb-0'
> >
<div> <div>
<div className='system-sm-semibold mb-1 flex h-6 items-center gap-1 text-text-secondary'> {type !== 'boolean' && (
<div className='truncate'>{name || key}</div> <div className='system-sm-semibold mb-1 flex h-6 items-center gap-1 text-text-secondary'>
{!required && <span className='system-xs-regular text-text-tertiary'>{t('workflow.panel.optional')}</span>} <div className='truncate'>{name || key}</div>
</div> {!required && <span className='system-xs-regular text-text-tertiary'>{t('workflow.panel.optional')}</span>}
</div>
)}
<div className='grow'> <div className='grow'>
{type === 'string' && ( {type === 'string' && (
<Input <Input
@ -151,6 +154,14 @@ const PromptValuePanel: FC<IPromptValuePanelProps> = ({
maxLength={max_length || DEFAULT_VALUE_MAX_LEN} maxLength={max_length || DEFAULT_VALUE_MAX_LEN}
/> />
)} )}
{type === 'boolean' && (
<BoolInput
name={name || key}
value={!!inputs[key]}
required={required}
onChange={(value) => { handleInputValueChange(key, value) }}
/>
)}
</div> </div>
</div> </div>
</div> </div>

@ -23,6 +23,7 @@ import SuggestedQuestions from '@/app/components/base/chat/chat/answer/suggested
import { Markdown } from '@/app/components/base/markdown' import { Markdown } from '@/app/components/base/markdown'
import cn from '@/utils/classnames' import cn from '@/utils/classnames'
import type { FileEntity } from '../../file-uploader/types' import type { FileEntity } from '../../file-uploader/types'
import { formatBooleanInputs } from '@/utils/model-config'
import Avatar from '../../avatar' import Avatar from '../../avatar'
const ChatWrapper = () => { const ChatWrapper = () => {
@ -89,7 +90,7 @@ const ChatWrapper = () => {
let hasEmptyInput = '' let hasEmptyInput = ''
let fileIsUploading = false let fileIsUploading = false
const requiredVars = inputsForms.filter(({ required }) => required) const requiredVars = inputsForms.filter(({ required, type }) => required && type !== InputVarType.boolean)
if (requiredVars.length) { if (requiredVars.length) {
requiredVars.forEach(({ variable, label, type }) => { requiredVars.forEach(({ variable, label, type }) => {
if (hasEmptyInput) if (hasEmptyInput)
@ -131,7 +132,7 @@ const ChatWrapper = () => {
const data: any = { const data: any = {
query: message, query: message,
files, files,
inputs: currentConversationId ? currentConversationInputs : newConversationInputs, inputs: formatBooleanInputs(inputsForms, currentConversationId ? currentConversationInputs : newConversationInputs),
conversation_id: currentConversationId, conversation_id: currentConversationId,
parent_message_id: (isRegenerate ? parentAnswer?.id : getLastAnswer(chatList)?.id) || null, parent_message_id: (isRegenerate ? parentAnswer?.id : getLastAnswer(chatList)?.id) || null,
} }

@ -325,7 +325,7 @@ export const useChatWithHistory = (installedAppInfo?: InstalledApp) => {
let hasEmptyInput = '' let hasEmptyInput = ''
let fileIsUploading = false let fileIsUploading = false
const requiredVars = inputsForms.filter(({ required }) => required) const requiredVars = inputsForms.filter(({ required, type }) => required && type !== InputVarType.boolean)
if (requiredVars.length) { if (requiredVars.length) {
requiredVars.forEach(({ variable, label, type }) => { requiredVars.forEach(({ variable, label, type }) => {
if (hasEmptyInput) if (hasEmptyInput)

@ -6,6 +6,7 @@ import Textarea from '@/app/components/base/textarea'
import { PortalSelect } from '@/app/components/base/select' import { PortalSelect } from '@/app/components/base/select'
import { FileUploaderInAttachmentWrapper } from '@/app/components/base/file-uploader' import { FileUploaderInAttachmentWrapper } from '@/app/components/base/file-uploader'
import { InputVarType } from '@/app/components/workflow/types' import { InputVarType } from '@/app/components/workflow/types'
import BoolInput from '@/app/components/workflow/nodes/_base/components/before-run-form/bool-input'
type Props = { type Props = {
showTip?: boolean showTip?: boolean
@ -42,12 +43,14 @@ const InputsFormContent = ({ showTip }: Props) => {
<div className='space-y-4'> <div className='space-y-4'>
{visibleInputsForms.map(form => ( {visibleInputsForms.map(form => (
<div key={form.variable} className='space-y-1'> <div key={form.variable} className='space-y-1'>
<div className='flex h-6 items-center gap-1'> {form.type !== InputVarType.boolean && (
<div className='system-md-semibold text-text-secondary'>{form.label}</div> <div className='flex h-6 items-center gap-1'>
{!form.required && ( <div className='system-md-semibold text-text-secondary'>{form.label}</div>
<div className='system-xs-regular text-text-tertiary'>{t('appDebug.variableTable.optional')}</div> {!form.required && (
)} <div className='system-xs-regular text-text-tertiary'>{t('appDebug.variableTable.optional')}</div>
</div> )}
</div>
)}
{form.type === InputVarType.textInput && ( {form.type === InputVarType.textInput && (
<Input <Input
value={inputsFormValue?.[form.variable] || ''} value={inputsFormValue?.[form.variable] || ''}
@ -70,6 +73,14 @@ const InputsFormContent = ({ showTip }: Props) => {
placeholder={form.label} placeholder={form.label}
/> />
)} )}
{form.type === InputVarType.boolean && (
<BoolInput
name={form.label}
value={!!inputsFormValue?.[form.variable]}
required={form.required}
onChange={value => handleFormChange(form.variable, value)}
/>
)}
{form.type === InputVarType.select && ( {form.type === InputVarType.select && (
<PortalSelect <PortalSelect
popupClassName='w-[200px]' popupClassName='w-[200px]'

@ -12,7 +12,7 @@ export const useCheckInputsForms = () => {
const checkInputsForm = useCallback((inputs: Record<string, any>, inputsForm: InputForm[]) => { const checkInputsForm = useCallback((inputs: Record<string, any>, inputsForm: InputForm[]) => {
let hasEmptyInput = '' let hasEmptyInput = ''
let fileIsUploading = false let fileIsUploading = false
const requiredVars = inputsForm.filter(({ required }) => required) const requiredVars = inputsForm.filter(({ required, type }) => required && type !== InputVarType.boolean) // boolean can be not checked
if (requiredVars?.length) { if (requiredVars?.length) {
requiredVars.forEach(({ variable, label, type }) => { requiredVars.forEach(({ variable, label, type }) => {

@ -31,6 +31,12 @@ export const getProcessedInputs = (inputs: Record<string, any>, inputsForm: Inpu
inputsForm.forEach((item) => { inputsForm.forEach((item) => {
const inputValue = inputs[item.variable] const inputValue = inputs[item.variable]
// set boolean type default value
if(item.type === InputVarType.boolean) {
processedInputs[item.variable] = !!inputValue
return
}
if (!inputValue) if (!inputValue)
return return

@ -90,7 +90,7 @@ const ChatWrapper = () => {
let hasEmptyInput = '' let hasEmptyInput = ''
let fileIsUploading = false let fileIsUploading = false
const requiredVars = inputsForms.filter(({ required }) => required) const requiredVars = inputsForms.filter(({ required, type }) => required && type !== InputVarType.boolean)
if (requiredVars.length) { if (requiredVars.length) {
requiredVars.forEach(({ variable, label, type }) => { requiredVars.forEach(({ variable, label, type }) => {
if (hasEmptyInput) if (hasEmptyInput)

@ -312,7 +312,7 @@ export const useEmbeddedChatbot = () => {
let hasEmptyInput = '' let hasEmptyInput = ''
let fileIsUploading = false let fileIsUploading = false
const requiredVars = inputsForms.filter(({ required }) => required) const requiredVars = inputsForms.filter(({ required, type }) => required && type !== InputVarType.boolean)
if (requiredVars.length) { if (requiredVars.length) {
requiredVars.forEach(({ variable, label, type }) => { requiredVars.forEach(({ variable, label, type }) => {
if (hasEmptyInput) if (hasEmptyInput)

@ -6,6 +6,7 @@ import Textarea from '@/app/components/base/textarea'
import { PortalSelect } from '@/app/components/base/select' import { PortalSelect } from '@/app/components/base/select'
import { FileUploaderInAttachmentWrapper } from '@/app/components/base/file-uploader' import { FileUploaderInAttachmentWrapper } from '@/app/components/base/file-uploader'
import { InputVarType } from '@/app/components/workflow/types' import { InputVarType } from '@/app/components/workflow/types'
import BoolInput from '@/app/components/workflow/nodes/_base/components/before-run-form/bool-input'
type Props = { type Props = {
showTip?: boolean showTip?: boolean
@ -42,12 +43,14 @@ const InputsFormContent = ({ showTip }: Props) => {
<div className='space-y-4'> <div className='space-y-4'>
{visibleInputsForms.map(form => ( {visibleInputsForms.map(form => (
<div key={form.variable} className='space-y-1'> <div key={form.variable} className='space-y-1'>
{form.type !== InputVarType.boolean && (
<div className='flex h-6 items-center gap-1'> <div className='flex h-6 items-center gap-1'>
<div className='system-md-semibold text-text-secondary'>{form.label}</div> <div className='system-md-semibold text-text-secondary'>{form.label}</div>
{!form.required && ( {!form.required && (
<div className='system-xs-regular text-text-tertiary'>{t('appDebug.variableTable.optional')}</div> <div className='system-xs-regular text-text-tertiary'>{t('appDebug.variableTable.optional')}</div>
)} )}
</div> </div>
)}
{form.type === InputVarType.textInput && ( {form.type === InputVarType.textInput && (
<Input <Input
value={inputsFormValue?.[form.variable] || ''} value={inputsFormValue?.[form.variable] || ''}
@ -70,6 +73,14 @@ const InputsFormContent = ({ showTip }: Props) => {
placeholder={form.label} placeholder={form.label}
/> />
)} )}
{form.type === InputVarType.boolean && (
<BoolInput
name={form.label}
value={inputsFormValue?.[form.variable]}
required={form.required}
onChange={value => handleFormChange(form.variable, value)}
/>
)}
{form.type === InputVarType.select && ( {form.type === InputVarType.select && (
<PortalSelect <PortalSelect
popupClassName='w-[200px]' popupClassName='w-[200px]'

@ -21,6 +21,7 @@ import { TEXT_GENERATION_TIMEOUT_MS } from '@/config'
import { import {
getFilesInLogs, getFilesInLogs,
} from '@/app/components/base/file-uploader/utils' } from '@/app/components/base/file-uploader/utils'
import { formatBooleanInputs } from '@/utils/model-config'
export type IResultProps = { export type IResultProps = {
isWorkflow: boolean isWorkflow: boolean
@ -124,7 +125,9 @@ const Result: FC<IResultProps> = ({
} }
let hasEmptyInput = '' let hasEmptyInput = ''
const requiredVars = prompt_variables?.filter(({ key, name, required }) => { const requiredVars = prompt_variables?.filter(({ key, name, required, type }) => {
if(type === 'boolean')
return false // boolean input is not required
const res = (!key || !key.trim()) || (!name || !name.trim()) || (required || required === undefined || required === null) const res = (!key || !key.trim()) || (!name || !name.trim()) || (required || required === undefined || required === null)
return res return res
}) || [] // compatible with old version }) || [] // compatible with old version
@ -158,7 +161,7 @@ const Result: FC<IResultProps> = ({
return return
const data: Record<string, any> = { const data: Record<string, any> = {
inputs, inputs: formatBooleanInputs(promptConfig?.prompt_variables, inputs),
} }
if (visionConfig.enabled && completionFiles && completionFiles?.length > 0) { if (visionConfig.enabled && completionFiles && completionFiles?.length > 0) {
data.files = completionFiles.map((item) => { data.files = completionFiles.map((item) => {

@ -18,6 +18,7 @@ import { FileUploaderInAttachmentWrapper } from '@/app/components/base/file-uplo
import { getProcessedFiles } from '@/app/components/base/file-uploader/utils' import { getProcessedFiles } from '@/app/components/base/file-uploader/utils'
import useBreakpoints, { MediaType } from '@/hooks/use-breakpoints' import useBreakpoints, { MediaType } from '@/hooks/use-breakpoints'
import cn from '@/utils/classnames' import cn from '@/utils/classnames'
import BoolInput from '@/app/components/workflow/nodes/_base/components/before-run-form/bool-input'
export type IRunOnceProps = { export type IRunOnceProps = {
siteInfo: SiteInfo siteInfo: SiteInfo
@ -82,7 +83,9 @@ const RunOnce: FC<IRunOnceProps> = ({
{(inputs === null || inputs === undefined || Object.keys(inputs).length === 0) ? null {(inputs === null || inputs === undefined || Object.keys(inputs).length === 0) ? null
: promptConfig.prompt_variables.map(item => ( : promptConfig.prompt_variables.map(item => (
<div className='mt-4 w-full' key={item.key}> <div className='mt-4 w-full' key={item.key}>
<label className='system-md-semibold flex h-6 items-center text-text-secondary'>{item.name}</label> {item.type !== 'boolean' && (
<label className='system-md-semibold flex h-6 items-center text-text-secondary'>{item.name}</label>
)}
<div className='mt-1'> <div className='mt-1'>
{item.type === 'select' && ( {item.type === 'select' && (
<Select <Select
@ -107,7 +110,7 @@ const RunOnce: FC<IRunOnceProps> = ({
className='h-[104px] sm:text-xs' className='h-[104px] sm:text-xs'
placeholder={`${item.name}${!item.required ? `(${t('appDebug.variableTable.optional')})` : ''}`} placeholder={`${item.name}${!item.required ? `(${t('appDebug.variableTable.optional')})` : ''}`}
value={inputs[item.key]} value={inputs[item.key]}
onChange={(e: ChangeEvent<HTMLInputElement>) => { handleInputsChange({ ...inputsRef.current, [item.key]: e.target.value }) }} onChange={(e: ChangeEvent<HTMLTextAreaElement>) => { handleInputsChange({ ...inputsRef.current, [item.key]: e.target.value }) }}
/> />
)} )}
{item.type === 'number' && ( {item.type === 'number' && (
@ -118,6 +121,14 @@ const RunOnce: FC<IRunOnceProps> = ({
onChange={(e: ChangeEvent<HTMLInputElement>) => { handleInputsChange({ ...inputsRef.current, [item.key]: e.target.value }) }} onChange={(e: ChangeEvent<HTMLInputElement>) => { handleInputsChange({ ...inputsRef.current, [item.key]: e.target.value }) }}
/> />
)} )}
{item.type === 'boolean' && (
<BoolInput
name={item.name || item.key}
value={!!inputs[item.key]}
required={item.required}
onChange={(value) => { handleInputsChange({ ...inputsRef.current, [item.key]: value }) }}
/>
)}
{item.type === 'file' && ( {item.type === 'file' && (
<FileUploaderInAttachmentWrapper <FileUploaderInAttachmentWrapper
onChange={(files) => { handleInputsChange({ ...inputsRef.current, [item.key]: getProcessedFiles(files)[0] }) }} onChange={(files) => { handleInputsChange({ ...inputsRef.current, [item.key]: getProcessedFiles(files)[0] }) }}

@ -0,0 +1,38 @@
'use client'
import Checkbox from '@/app/components/base/checkbox'
import type { FC } from 'react'
import React, { useCallback } from 'react'
import { useTranslation } from 'react-i18next'
type Props = {
name: string
value: boolean
required?: boolean
onChange: (value: boolean) => void
}
const BoolInput: FC<Props> = ({
value,
onChange,
name,
required,
}) => {
const { t } = useTranslation()
const handleChange = useCallback(() => {
onChange(!value)
}, [value, onChange])
return (
<div className='flex h-6 items-center gap-2'>
<Checkbox
className='!h-4 !w-4'
checked={!!value}
onCheck={handleChange}
/>
<div className='system-sm-medium flex items-center gap-1 text-text-secondary'>
{name}
{!required && <span className='system-xs-regular text-text-tertiary'>{t('workflow.panel.optional')}</span>}
</div>
</div>
)
}
export default React.memo(BoolInput)

@ -25,6 +25,7 @@ import { BubbleX } from '@/app/components/base/icons/src/vender/line/others'
import { FILE_EXTS } from '@/app/components/base/prompt-editor/constants' import { FILE_EXTS } from '@/app/components/base/prompt-editor/constants'
import cn from '@/utils/classnames' import cn from '@/utils/classnames'
import type { FileEntity } from '@/app/components/base/file-uploader/types' import type { FileEntity } from '@/app/components/base/file-uploader/types'
import BoolInput from './bool-input'
type Props = { type Props = {
payload: InputVar payload: InputVar
@ -92,6 +93,7 @@ const FormItem: FC<Props> = ({
return '' return ''
})() })()
const isBooleanType = type === InputVarType.boolean
const isArrayLikeType = [InputVarType.contexts, InputVarType.iterator].includes(type) const isArrayLikeType = [InputVarType.contexts, InputVarType.iterator].includes(type)
const isContext = type === InputVarType.contexts const isContext = type === InputVarType.contexts
const isIterator = type === InputVarType.iterator const isIterator = type === InputVarType.iterator
@ -113,7 +115,7 @@ const FormItem: FC<Props> = ({
return ( return (
<div className={cn(className)}> <div className={cn(className)}>
{!isArrayLikeType && ( {!isArrayLikeType && !isBooleanType && (
<div className='system-sm-semibold mb-1 flex h-6 items-center gap-1 text-text-secondary'> <div className='system-sm-semibold mb-1 flex h-6 items-center gap-1 text-text-secondary'>
<div className='truncate'>{typeof payload.label === 'object' ? nodeKey : payload.label}</div> <div className='truncate'>{typeof payload.label === 'object' ? nodeKey : payload.label}</div>
{!payload.required && <span className='system-xs-regular text-text-tertiary'>{t('workflow.panel.optional')}</span>} {!payload.required && <span className='system-xs-regular text-text-tertiary'>{t('workflow.panel.optional')}</span>}
@ -166,6 +168,15 @@ const FormItem: FC<Props> = ({
) )
} }
{isBooleanType && (
<BoolInput
name={payload.label as string}
value={!!value}
required={payload.required}
onChange={onChange}
/>
)}
{ {
type === InputVarType.json && ( type === InputVarType.json && (
<CodeEditor <CodeEditor

@ -32,6 +32,8 @@ export type BeforeRunFormProps = {
} & Partial<SpecialResultPanelProps> } & Partial<SpecialResultPanelProps>
function formatValue(value: string | any, type: InputVarType) { function formatValue(value: string | any, type: InputVarType) {
if(type === InputVarType.boolean)
return !!value
if(value === undefined || value === null) if(value === undefined || value === null)
return value return value
if (type === InputVarType.number) if (type === InputVarType.number)
@ -87,7 +89,7 @@ const BeforeRunForm: FC<BeforeRunFormProps> = ({
form.inputs.forEach((input) => { form.inputs.forEach((input) => {
const value = form.values[input.variable] as any const value = form.values[input.variable] as any
if (!errMsg && input.required && !(input.variable in existVarValuesInForm) && (value === '' || value === undefined || value === null || (input.type === InputVarType.files && value.length === 0))) if (!errMsg && input.required && (input.type !== InputVarType.boolean) && !(input.variable in existVarValuesInForm) && (value === '' || value === undefined || value === null || (input.type === InputVarType.files && value.length === 0)))
errMsg = t('workflow.errorMsg.fieldRequired', { field: typeof input.label === 'object' ? input.label.variable : input.label }) errMsg = t('workflow.errorMsg.fieldRequired', { field: typeof input.label === 'object' ? input.label.variable : input.label })
if (!errMsg && (input.type === InputVarType.singleFile || input.type === InputVarType.multiFiles) && value) { if (!errMsg && (input.type === InputVarType.singleFile || input.type === InputVarType.multiFiles) && value) {

@ -1,7 +1,7 @@
'use client' 'use client'
import type { FC } from 'react' import type { FC } from 'react'
import React from 'react' import React from 'react'
import { RiAlignLeft, RiCheckboxMultipleLine, RiFileCopy2Line, RiFileList2Line, RiHashtag, RiTextSnippet } from '@remixicon/react' import { RiAlignLeft, RiCheckboxLine, RiCheckboxMultipleLine, RiFileCopy2Line, RiFileList2Line, RiHashtag, RiTextSnippet } from '@remixicon/react'
import { InputVarType } from '../../../types' import { InputVarType } from '../../../types'
type Props = { type Props = {
@ -15,6 +15,7 @@ const getIcon = (type: InputVarType) => {
[InputVarType.paragraph]: RiAlignLeft, [InputVarType.paragraph]: RiAlignLeft,
[InputVarType.select]: RiCheckboxMultipleLine, [InputVarType.select]: RiCheckboxMultipleLine,
[InputVarType.number]: RiHashtag, [InputVarType.number]: RiHashtag,
[InputVarType.boolean]: RiCheckboxLine,
[InputVarType.singleFile]: RiFileList2Line, [InputVarType.singleFile]: RiFileList2Line,
[InputVarType.multiFiles]: RiFileCopy2Line, [InputVarType.multiFiles]: RiFileCopy2Line,
} as any)[type] || RiTextSnippet } as any)[type] || RiTextSnippet

@ -49,9 +49,10 @@ export const isConversationVar = (valueSelector: ValueSelector) => {
return valueSelector[0] === 'conversation' return valueSelector[0] === 'conversation'
} }
const inputVarTypeToVarType = (type: InputVarType): VarType => { export const inputVarTypeToVarType = (type: InputVarType): VarType => {
return ({ return ({
[InputVarType.number]: VarType.number, [InputVarType.number]: VarType.number,
[InputVarType.boolean]: VarType.boolean,
[InputVarType.singleFile]: VarType.file, [InputVarType.singleFile]: VarType.file,
[InputVarType.multiFiles]: VarType.arrayFile, [InputVarType.multiFiles]: VarType.arrayFile,
} as any)[type] || VarType.string } as any)[type] || VarType.string

@ -18,7 +18,7 @@ type Props = {
onChange: (value: string) => void onChange: (value: string) => void
} }
const TYPES = [VarType.string, VarType.number, VarType.arrayNumber, VarType.arrayString, VarType.arrayObject, VarType.object] const TYPES = [VarType.string, VarType.number, VarType.boolean, VarType.arrayNumber, VarType.arrayString, VarType.arrayBoolean, VarType.arrayObject, VarType.object]
const VarReferencePicker: FC<Props> = ({ const VarReferencePicker: FC<Props> = ({
readonly, readonly,
className, className,

@ -16,6 +16,7 @@ import Input from '@/app/components/base/input'
import Textarea from '@/app/components/base/textarea' import Textarea from '@/app/components/base/textarea'
import CodeEditor from '@/app/components/workflow/nodes/_base/components/editor/code-editor' import CodeEditor from '@/app/components/workflow/nodes/_base/components/editor/code-editor'
import { noop } from 'lodash-es' import { noop } from 'lodash-es'
import BoolValue from '@/app/components/workflow/panel/chat-variable-panel/components/bool-value'
type Props = { type Props = {
readonly: boolean readonly: boolean
@ -75,7 +76,7 @@ const VarList: FC<Props> = ({
}, [list, onChange]) }, [list, onChange])
const handleToAssignedVarChange = useCallback((index: number) => { const handleToAssignedVarChange = useCallback((index: number) => {
return (value: ValueSelector | string | number) => { return (value: ValueSelector | string | number | boolean) => {
const newList = produce(list, (draft) => { const newList = produce(list, (draft) => {
draft[index].value = value as ValueSelector draft[index].value = value as ValueSelector
}) })
@ -188,6 +189,12 @@ const VarList: FC<Props> = ({
className='w-full' className='w-full'
/> />
)} )}
{assignedVarType === 'boolean' && (
<BoolValue
value={item.value as boolean}
onChange={value => handleToAssignedVarChange(index)(value)}
/>
)}
{assignedVarType === 'object' && ( {assignedVarType === 'object' && (
<CodeEditor <CodeEditor
value={item.value as string} value={item.value as string}

@ -43,7 +43,7 @@ export const getOperationItems = (
] ]
} }
if (writeModeTypes && ['string', 'object'].includes(assignedVarType || '')) { if (writeModeTypes && ['string', 'boolean', 'object'].includes(assignedVarType || '')) {
return writeModeTypes.map(type => ({ return writeModeTypes.map(type => ({
value: type, value: type,
name: type, name: type,

@ -60,7 +60,6 @@ const useConfig = (id: string, payload: CodeNodeType) => {
}) })
syncOutputKeyOrders(defaultConfig.outputs) syncOutputKeyOrders(defaultConfig.outputs)
} }
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [defaultConfig]) }, [defaultConfig])
const handleCodeChange = useCallback((code: string) => { const handleCodeChange = useCallback((code: string) => {
@ -85,7 +84,7 @@ const useConfig = (id: string, payload: CodeNodeType) => {
}, [allLanguageDefault, inputs, setInputs]) }, [allLanguageDefault, inputs, setInputs])
const handleSyncFunctionSignature = useCallback(() => { const handleSyncFunctionSignature = useCallback(() => {
const generateSyncSignatureCode = (code: string) => { const generateSyncSignatureCode = (code: string) => {
let mainDefRe let mainDefRe
let newMainDef let newMainDef
if (inputs.code_language === CodeLanguage.javascript) { if (inputs.code_language === CodeLanguage.javascript) {
@ -159,7 +158,7 @@ const useConfig = (id: string, payload: CodeNodeType) => {
}) })
const filterVar = useCallback((varPayload: Var) => { const filterVar = useCallback((varPayload: Var) => {
return [VarType.string, VarType.number, VarType.secret, VarType.object, VarType.array, VarType.arrayNumber, VarType.arrayString, VarType.arrayObject, VarType.file, VarType.arrayFile].includes(varPayload.type) return [VarType.string, VarType.number, VarType.boolean, VarType.secret, VarType.object, VarType.array, VarType.arrayNumber, VarType.arrayString, VarType.arrayObject, VarType.arrayBoolean, VarType.file, VarType.arrayFile].includes(varPayload.type)
}, []) }, [])
const handleCodeAndVarsChange = useCallback((code: string, inputVariables: Variable[], outputVariables: OutputVar) => { const handleCodeAndVarsChange = useCallback((code: string, inputVariables: Variable[], outputVariables: OutputVar) => {

@ -38,6 +38,7 @@ import { VarType } from '@/app/components/workflow/types'
import cn from '@/utils/classnames' import cn from '@/utils/classnames'
import { SimpleSelect as Select } from '@/app/components/base/select' import { SimpleSelect as Select } from '@/app/components/base/select'
import { Variable02 } from '@/app/components/base/icons/src/vender/solid/development' import { Variable02 } from '@/app/components/base/icons/src/vender/solid/development'
import BoolValue from '@/app/components/workflow/panel/chat-variable-panel/components/bool-value'
import { getVarType } from '@/app/components/workflow/nodes/_base/components/variable/utils' import { getVarType } from '@/app/components/workflow/nodes/_base/components/variable/utils'
import { useIsChatMode } from '@/app/components/workflow/hooks/use-workflow' import { useIsChatMode } from '@/app/components/workflow/hooks/use-workflow'
const optionNameI18NPrefix = 'workflow.nodes.ifElse.optionName' const optionNameI18NPrefix = 'workflow.nodes.ifElse.optionName'
@ -142,12 +143,12 @@ const ConditionItem = ({
const isArrayValue = fileAttr?.key === 'transfer_method' || fileAttr?.key === 'type' const isArrayValue = fileAttr?.key === 'transfer_method' || fileAttr?.key === 'type'
const handleUpdateConditionValue = useCallback((value: string) => { const handleUpdateConditionValue = useCallback((value: string | boolean) => {
if (value === condition.value || (isArrayValue && value === condition.value?.[0])) if (value === condition.value || (isArrayValue && value === (condition.value as string[])?.[0]))
return return
const newCondition = { const newCondition = {
...condition, ...condition,
value: isArrayValue ? [value] : value, value: isArrayValue ? [value as string] : value,
} }
doUpdateCondition(newCondition) doUpdateCondition(newCondition)
}, [condition, doUpdateCondition, isArrayValue]) }, [condition, doUpdateCondition, isArrayValue])
@ -203,8 +204,12 @@ const ConditionItem = ({
}, [caseId, condition, conditionId, isSubVariableKey, onRemoveCondition, onRemoveSubVariableCondition]) }, [caseId, condition, conditionId, isSubVariableKey, onRemoveCondition, onRemoveSubVariableCondition])
const handleVarChange = useCallback((valueSelector: ValueSelector, varItem: Var) => { const handleVarChange = useCallback((valueSelector: ValueSelector, varItem: Var) => {
const {
conversationVariables,
} = workflowStore.getState()
const resolvedVarType = getVarType({ const resolvedVarType = getVarType({
valueSelector, valueSelector,
conversationVariables,
availableNodes, availableNodes,
isChatMode, isChatMode,
}) })
@ -273,7 +278,7 @@ const ConditionItem = ({
/> />
</div> </div>
{ {
!comparisonOperatorNotRequireValue(condition.comparison_operator) && !isNotInput && condition.varType !== VarType.number && ( !comparisonOperatorNotRequireValue(condition.comparison_operator) && !isNotInput && condition.varType !== VarType.number && condition.varType !== VarType.boolean && (
<div className='max-h-[100px] overflow-y-auto border-t border-t-divider-subtle px-2 py-1'> <div className='max-h-[100px] overflow-y-auto border-t border-t-divider-subtle px-2 py-1'>
<ConditionInput <ConditionInput
disabled={disabled} disabled={disabled}
@ -285,6 +290,16 @@ const ConditionItem = ({
</div> </div>
) )
} }
{
!comparisonOperatorNotRequireValue(condition.comparison_operator) && !isNotInput && condition.varType === VarType.boolean && (
<div className='p-1'>
<BoolValue
value={condition.value as boolean}
onChange={handleUpdateConditionValue}
/>
</div>
)
}
{ {
!comparisonOperatorNotRequireValue(condition.comparison_operator) && !isNotInput && condition.varType === VarType.number && ( !comparisonOperatorNotRequireValue(condition.comparison_operator) && !isNotInput && condition.varType === VarType.number && (
<div className='border-t border-t-divider-subtle px-2 py-1 pt-[3px]'> <div className='border-t border-t-divider-subtle px-2 py-1 pt-[3px]'>

@ -24,7 +24,7 @@ type ConditionValueProps = {
variableSelector: string[] variableSelector: string[]
labelName?: string labelName?: string
operator: ComparisonOperator operator: ComparisonOperator
value: string | string[] value: string | string[] | boolean
} }
const ConditionValue = ({ const ConditionValue = ({
variableSelector, variableSelector,
@ -48,6 +48,9 @@ const ConditionValue = ({
if (Array.isArray(value)) // transfer method if (Array.isArray(value)) // transfer method
return value[0] return value[0]
if(value === true || value === false)
return value ? 'True' : 'False'
return value.replace(/{{#([^#]*)#}}/g, (a, b) => { return value.replace(/{{#([^#]*)#}}/g, (a, b) => {
const arr: string[] = b.split('.') const arr: string[] = b.split('.')
if (isSystemVar(arr)) if (isSystemVar(arr))

@ -1,4 +1,4 @@
import { BlockEnum, type NodeDefault } from '../../types' import { BlockEnum, type NodeDefault, VarType } from '../../types'
import { type IfElseNodeType, LogicalOperator } from './types' import { type IfElseNodeType, LogicalOperator } from './types'
import { isEmptyRelatedOperator } from './utils' import { isEmptyRelatedOperator } from './utils'
import { ALL_CHAT_AVAILABLE_BLOCKS, ALL_COMPLETION_AVAILABLE_BLOCKS } from '@/app/components/workflow/blocks' import { ALL_CHAT_AVAILABLE_BLOCKS, ALL_COMPLETION_AVAILABLE_BLOCKS } from '@/app/components/workflow/blocks'
@ -64,7 +64,7 @@ const nodeDefault: NodeDefault<IfElseNodeType> = {
errorMessages = t(`${i18nPrefix}.fieldRequired`, { field: t(`${i18nPrefix}.fields.variableValue`) }) errorMessages = t(`${i18nPrefix}.fieldRequired`, { field: t(`${i18nPrefix}.fields.variableValue`) })
} }
else { else {
if (!isEmptyRelatedOperator(condition.comparison_operator!) && !condition.value) if (!isEmptyRelatedOperator(condition.comparison_operator!) && (condition.varType === VarType.boolean ? condition.value === undefined : !condition.value))
errorMessages = t(`${i18nPrefix}.fieldRequired`, { field: t(`${i18nPrefix}.fields.variableValue`) }) errorMessages = t(`${i18nPrefix}.fieldRequired`, { field: t(`${i18nPrefix}.fields.variableValue`) })
} }
} }

@ -26,7 +26,7 @@ const IfElseNode: FC<NodeProps<IfElseNodeType>> = (props) => {
if (isEmptyRelatedOperator(c.comparison_operator!)) if (isEmptyRelatedOperator(c.comparison_operator!))
return true return true
return !!c.value return typeof c.value === 'boolean' ? true : !!c.value
}) })
return isSet return isSet
} }
@ -34,7 +34,7 @@ const IfElseNode: FC<NodeProps<IfElseNodeType>> = (props) => {
if (isEmptyRelatedOperator(condition.comparison_operator!)) if (isEmptyRelatedOperator(condition.comparison_operator!))
return true return true
return !!condition.value return typeof condition.value === 'boolean' ? true : !!condition.value
} }
}, []) }, [])
const conditionNotSet = (<div className='flex h-6 items-center space-x-1 rounded-md bg-workflow-block-parma-bg px-1 text-xs font-normal text-text-secondary'> const conditionNotSet = (<div className='flex h-6 items-center space-x-1 rounded-md bg-workflow-block-parma-bg px-1 text-xs font-normal text-text-secondary'>

@ -41,7 +41,7 @@ export type Condition = {
variable_selector?: ValueSelector variable_selector?: ValueSelector
key?: string // sub variable key key?: string // sub variable key
comparison_operator?: ComparisonOperator comparison_operator?: ComparisonOperator
value: string | string[] value: string | string[] | boolean
numberVarType?: NumberVarType numberVarType?: NumberVarType
sub_variable_condition?: CaseItem sub_variable_condition?: CaseItem
} }

@ -107,6 +107,13 @@ export const getOperators = (type?: VarType, file?: { key: string }) => {
ComparisonOperator.empty, ComparisonOperator.empty,
ComparisonOperator.notEmpty, ComparisonOperator.notEmpty,
] ]
case VarType.boolean:
return [
ComparisonOperator.is,
ComparisonOperator.isNot,
ComparisonOperator.empty,
ComparisonOperator.notEmpty,
]
case VarType.file: case VarType.file:
return [ return [
ComparisonOperator.exists, ComparisonOperator.exists,
@ -114,6 +121,7 @@ export const getOperators = (type?: VarType, file?: { key: string }) => {
] ]
case VarType.arrayString: case VarType.arrayString:
case VarType.arrayNumber: case VarType.arrayNumber:
case VarType.arrayBoolean:
return [ return [
ComparisonOperator.contains, ComparisonOperator.contains,
ComparisonOperator.notContains, ComparisonOperator.notContains,

@ -58,6 +58,9 @@ const useConfig = (id: string, payload: ListFilterNodeType) => {
case VarType.arrayObject: case VarType.arrayObject:
itemVarType = VarType.object itemVarType = VarType.object
break break
case VarType.arrayBoolean:
itemVarType = VarType.boolean
break
default: default:
itemVarType = varType itemVarType = varType
} }
@ -95,7 +98,7 @@ const useConfig = (id: string, payload: ListFilterNodeType) => {
const filterVar = useCallback((varPayload: Var) => { const filterVar = useCallback((varPayload: Var) => {
// Don't know the item struct of VarType.arrayObject, so not support it // Don't know the item struct of VarType.arrayObject, so not support it
return [VarType.arrayNumber, VarType.arrayString, VarType.arrayFile].includes(varPayload.type) return [VarType.arrayNumber, VarType.arrayString, VarType.arrayBoolean, VarType.arrayFile].includes(varPayload.type)
}, []) }, [])
const handleFilterEnabledChange = useCallback((enabled: boolean) => { const handleFilterEnabledChange = useCallback((enabled: boolean) => {

@ -39,21 +39,21 @@ type EditCardProps = {
const TYPE_OPTIONS = [ const TYPE_OPTIONS = [
{ value: Type.string, text: 'string' }, { value: Type.string, text: 'string' },
{ value: Type.number, text: 'number' }, { value: Type.number, text: 'number' },
// { value: Type.boolean, text: 'boolean' }, { value: Type.boolean, text: 'boolean' },
{ value: Type.object, text: 'object' }, { value: Type.object, text: 'object' },
{ value: ArrayType.string, text: 'array[string]' }, { value: ArrayType.string, text: 'array[string]' },
{ value: ArrayType.number, text: 'array[number]' }, { value: ArrayType.number, text: 'array[number]' },
// { value: ArrayType.boolean, text: 'array[boolean]' }, { value: ArrayType.boolean, text: 'array[boolean]' },
{ value: ArrayType.object, text: 'array[object]' }, { value: ArrayType.object, text: 'array[object]' },
] ]
const MAXIMUM_DEPTH_TYPE_OPTIONS = [ const MAXIMUM_DEPTH_TYPE_OPTIONS = [
{ value: Type.string, text: 'string' }, { value: Type.string, text: 'string' },
{ value: Type.number, text: 'number' }, { value: Type.number, text: 'number' },
// { value: Type.boolean, text: 'boolean' }, { value: Type.boolean, text: 'boolean' },
{ value: ArrayType.string, text: 'array[string]' }, { value: ArrayType.string, text: 'array[string]' },
{ value: ArrayType.number, text: 'array[number]' }, { value: ArrayType.number, text: 'array[number]' },
// { value: ArrayType.boolean, text: 'array[boolean]' }, { value: ArrayType.boolean, text: 'array[boolean]' },
] ]
const EditCard: FC<EditCardProps> = ({ const EditCard: FC<EditCardProps> = ({

@ -36,6 +36,7 @@ import cn from '@/utils/classnames'
import { SimpleSelect as Select } from '@/app/components/base/select' import { SimpleSelect as Select } from '@/app/components/base/select'
import { Variable02 } from '@/app/components/base/icons/src/vender/solid/development' import { Variable02 } from '@/app/components/base/icons/src/vender/solid/development'
import ConditionVarSelector from './condition-var-selector' import ConditionVarSelector from './condition-var-selector'
import BoolValue from '@/app/components/workflow/panel/chat-variable-panel/components/bool-value'
const optionNameI18NPrefix = 'workflow.nodes.ifElse.optionName' const optionNameI18NPrefix = 'workflow.nodes.ifElse.optionName'
@ -129,12 +130,12 @@ const ConditionItem = ({
const isArrayValue = fileAttr?.key === 'transfer_method' || fileAttr?.key === 'type' const isArrayValue = fileAttr?.key === 'transfer_method' || fileAttr?.key === 'type'
const handleUpdateConditionValue = useCallback((value: string) => { const handleUpdateConditionValue = useCallback((value: string | boolean) => {
if (value === condition.value || (isArrayValue && value === condition.value?.[0])) if (value === condition.value || (isArrayValue && value === (condition.value as string[])?.[0]))
return return
const newCondition = { const newCondition = {
...condition, ...condition,
value: isArrayValue ? [value] : value, value: isArrayValue ? [value as string] : value,
} }
doUpdateCondition(newCondition) doUpdateCondition(newCondition)
}, [condition, doUpdateCondition, isArrayValue]) }, [condition, doUpdateCondition, isArrayValue])
@ -253,7 +254,7 @@ const ConditionItem = ({
/> />
</div> </div>
{ {
!comparisonOperatorNotRequireValue(condition.comparison_operator) && !isNotInput && condition.varType !== VarType.number && ( !comparisonOperatorNotRequireValue(condition.comparison_operator) && !isNotInput && condition.varType !== VarType.number && condition.varType !== VarType.boolean && (
<div className='max-h-[100px] overflow-y-auto border-t border-t-divider-subtle px-2 py-1'> <div className='max-h-[100px] overflow-y-auto border-t border-t-divider-subtle px-2 py-1'>
<ConditionInput <ConditionInput
disabled={disabled} disabled={disabled}
@ -264,6 +265,14 @@ const ConditionItem = ({
</div> </div>
) )
} }
{!comparisonOperatorNotRequireValue(condition.comparison_operator) && condition.varType === VarType.boolean
&& <div className='p-1'>
<BoolValue
value={condition.value as boolean}
onChange={handleUpdateConditionValue}
/>
</div>
}
{ {
!comparisonOperatorNotRequireValue(condition.comparison_operator) && !isNotInput && condition.varType === VarType.number && ( !comparisonOperatorNotRequireValue(condition.comparison_operator) && !isNotInput && condition.varType === VarType.number && (
<div className='border-t border-t-divider-subtle px-2 py-1 pt-[3px]'> <div className='border-t border-t-divider-subtle px-2 py-1 pt-[3px]'>

@ -18,33 +18,15 @@ import {
ValueType, ValueType,
VarType, VarType,
} from '@/app/components/workflow/types' } from '@/app/components/workflow/types'
import BoolValue from '@/app/components/workflow/panel/chat-variable-panel/components/bool-value'
const objectPlaceholder = `# example import {
# { arrayBoolPlaceholder,
# "name": "ray", arrayNumberPlaceholder,
# "age": 20 arrayObjectPlaceholder,
# }` arrayStringPlaceholder,
const arrayStringPlaceholder = `# example objectPlaceholder,
# [ } from '@/app/components/workflow/panel/chat-variable-panel/utils'
# "value1",
# "value2"
# ]`
const arrayNumberPlaceholder = `# example
# [
# 100,
# 200
# ]`
const arrayObjectPlaceholder = `# example
# [
# {
# "name": "ray",
# "age": 20
# },
# {
# "name": "lily",
# "age": 18
# }
# ]`
type FormItemProps = { type FormItemProps = {
nodeId: string nodeId: string
@ -83,6 +65,8 @@ const FormItem = ({
return arrayNumberPlaceholder return arrayNumberPlaceholder
if (var_type === VarType.arrayObject) if (var_type === VarType.arrayObject)
return arrayObjectPlaceholder return arrayObjectPlaceholder
if (var_type === VarType.arrayBoolean)
return arrayBoolPlaceholder
return objectPlaceholder return objectPlaceholder
}, [var_type]) }, [var_type])
@ -120,9 +104,17 @@ const FormItem = ({
/> />
) )
} }
{
value_type === ValueType.constant && var_type === VarType.boolean && (
<BoolValue
value={value}
onChange={handleChange}
/>
)
}
{ {
value_type === ValueType.constant value_type === ValueType.constant
&& (var_type === VarType.object || var_type === VarType.arrayString || var_type === VarType.arrayNumber || var_type === VarType.arrayObject) && (var_type === VarType.object || var_type === VarType.arrayString || var_type === VarType.arrayNumber || var_type === VarType.arrayObject || var_type === VarType.arrayBoolean)
&& ( && (
<div className='w-full rounded-[10px] bg-components-input-bg-normal py-2 pl-3 pr-1' style={{ height: editorMinHeight }}> <div className='w-full rounded-[10px] bg-components-input-bg-normal py-2 pl-3 pr-1' style={{ height: editorMinHeight }}>
<CodeEditor <CodeEditor

@ -22,6 +22,10 @@ const VariableTypeSelect = ({
label: 'Object', label: 'Object',
value: VarType.object, value: VarType.object,
}, },
{
label: 'Boolean',
value: VarType.boolean,
},
{ {
label: 'Array[string]', label: 'Array[string]',
value: VarType.arrayString, value: VarType.arrayString,
@ -34,6 +38,10 @@ const VariableTypeSelect = ({
label: 'Array[object]', label: 'Array[object]',
value: VarType.arrayObject, value: VarType.arrayObject,
}, },
{
label: 'Array[boolean]',
value: VarType.arrayBoolean,
},
] ]
return ( return (

@ -1,4 +1,4 @@
import { BlockEnum } from '../../types' import { BlockEnum, VarType } from '../../types'
import type { NodeDefault } from '../../types' import type { NodeDefault } from '../../types'
import { ComparisonOperator, LogicalOperator, type LoopNodeType } from './types' import { ComparisonOperator, LogicalOperator, type LoopNodeType } from './types'
import { isEmptyRelatedOperator } from './utils' import { isEmptyRelatedOperator } from './utils'
@ -55,7 +55,7 @@ const nodeDefault: NodeDefault<LoopNodeType> = {
errorMessages = t(`${i18nPrefix}.fieldRequired`, { field: t(`${i18nPrefix}.fields.variableValue`) }) errorMessages = t(`${i18nPrefix}.fieldRequired`, { field: t(`${i18nPrefix}.fields.variableValue`) })
} }
else { else {
if (!isEmptyRelatedOperator(condition.comparison_operator!) && !condition.value) if (!isEmptyRelatedOperator(condition.comparison_operator!) && (condition.varType === VarType.boolean ? condition.value === undefined : !condition.value))
errorMessages = t(`${i18nPrefix}.fieldRequired`, { field: t(`${i18nPrefix}.fields.variableValue`) }) errorMessages = t(`${i18nPrefix}.fieldRequired`, { field: t(`${i18nPrefix}.fields.variableValue`) })
} }
} }

@ -44,7 +44,7 @@ export type Condition = {
variable_selector?: ValueSelector variable_selector?: ValueSelector
key?: string // sub variable key key?: string // sub variable key
comparison_operator?: ComparisonOperator comparison_operator?: ComparisonOperator
value: string | string[] value: string | string[] | boolean
numberVarType?: NumberVarType numberVarType?: NumberVarType
sub_variable_condition?: CaseItem sub_variable_condition?: CaseItem
} }

@ -107,6 +107,13 @@ export const getOperators = (type?: VarType, file?: { key: string }) => {
ComparisonOperator.empty, ComparisonOperator.empty,
ComparisonOperator.notEmpty, ComparisonOperator.notEmpty,
] ]
case VarType.boolean:
return [
ComparisonOperator.is,
ComparisonOperator.isNot,
ComparisonOperator.empty,
ComparisonOperator.notEmpty,
]
case VarType.object: case VarType.object:
return [ return [
ComparisonOperator.empty, ComparisonOperator.empty,

@ -35,7 +35,7 @@ type Props = {
onCancel?: () => void onCancel?: () => void
} }
const TYPES = [ParamType.string, ParamType.number, ParamType.arrayString, ParamType.arrayNumber, ParamType.arrayObject] const TYPES = [ParamType.string, ParamType.number, ParamType.bool, ParamType.arrayString, ParamType.arrayNumber, ParamType.arrayObject, ParamType.arrayBool]
const AddExtractParameter: FC<Props> = ({ const AddExtractParameter: FC<Props> = ({
type, type,

@ -3,11 +3,12 @@ import type { CommonNodeType, Memory, ModelConfig, ValueSelector, VisionSetting
export enum ParamType { export enum ParamType {
string = 'string', string = 'string',
number = 'number', number = 'number',
bool = 'bool', bool = 'boolean',
select = 'select', select = 'select',
arrayString = 'array[string]', arrayString = 'array[string]',
arrayNumber = 'array[number]', arrayNumber = 'array[number]',
arrayObject = 'array[object]', arrayObject = 'array[object]',
arrayBool = 'array[boolean]',
} }
export type Param = { export type Param = {

@ -0,0 +1,69 @@
'use client'
import type { FC } from 'react'
import React, { useCallback } from 'react'
import { useTranslation } from 'react-i18next'
import { RiAddLine } from '@remixicon/react'
import produce from 'immer'
import RemoveButton from '@/app/components/workflow/nodes/_base/components/remove-button'
import Button from '@/app/components/base/button'
import BoolValue from './bool-value'
type Props = {
list: any[]
onChange: (list: any[]) => void
}
const ArrayValueList: FC<Props> = ({
list,
onChange,
}) => {
const { t } = useTranslation()
const handleChange = useCallback((index: number) => {
return (value: boolean) => {
const newList = produce(list, (draft: any[]) => {
draft[index] = value
})
onChange(newList)
}
}, [list, onChange])
const handleItemRemove = useCallback((index: number) => {
return () => {
const newList = produce(list, (draft) => {
draft.splice(index, 1)
})
onChange(newList)
}
}, [list, onChange])
const handleItemAdd = useCallback(() => {
const newList = produce(list, (draft: any[]) => {
draft.push(true)
})
onChange(newList)
}, [list, onChange])
return (
<div className='w-full space-y-2'>
{list.map((item, index) => (
<div className='flex items-center space-x-1' key={index}>
<BoolValue
value={item}
onChange={handleChange(index)}
/>
<RemoveButton
className='!bg-gray-100 !p-2 hover:!bg-gray-200'
onClick={handleItemRemove(index)}
/>
</div>
))}
<Button variant='tertiary' className='w-full' onClick={handleItemAdd}>
<RiAddLine className='mr-1 h-4 w-4' />
<span>{t('workflow.chatVariable.modal.addArrayValue')}</span>
</Button>
</div>
)
}
export default React.memo(ArrayValueList)

@ -0,0 +1,35 @@
'use client'
import type { FC } from 'react'
import React, { useCallback } from 'react'
import OptionCard from '../../../nodes/_base/components/option-card'
type Props = {
value: boolean
onChange: (value: boolean) => void
}
const BoolValue: FC<Props> = ({
value,
onChange,
}) => {
const handleChange = useCallback((newValue: boolean) => {
return () => {
onChange(newValue)
}
}, [onChange])
return (
<div className='flex w-full space-x-1'>
<OptionCard className='grow'
selected={value}
title='True'
onSelect={handleChange(true)}
/>
<OptionCard className='grow'
selected={!value}
title='False'
onSelect={handleChange(false)}
/>
</div>
)
}
export default React.memo(BoolValue)

@ -17,6 +17,15 @@ import { CodeLanguage } from '@/app/components/workflow/nodes/code/types'
import { ChatVarType } from '@/app/components/workflow/panel/chat-variable-panel/type' import { ChatVarType } from '@/app/components/workflow/panel/chat-variable-panel/type'
import cn from '@/utils/classnames' import cn from '@/utils/classnames'
import { checkKeys, replaceSpaceWithUnderscreInVarNameInput } from '@/utils/var' import { checkKeys, replaceSpaceWithUnderscreInVarNameInput } from '@/utils/var'
import BoolValue from './bool-value'
import ArrayBoolList from './array-bool-list'
import {
arrayBoolPlaceholder,
arrayNumberPlaceholder,
arrayObjectPlaceholder,
arrayStringPlaceholder,
objectPlaceholder,
} from '@/app/components/workflow/panel/chat-variable-panel/utils'
export type ModalPropsType = { export type ModalPropsType = {
chatVar?: ConversationVariable chatVar?: ConversationVariable
@ -33,39 +42,14 @@ type ObjectValueItem = {
const typeList = [ const typeList = [
ChatVarType.String, ChatVarType.String,
ChatVarType.Number, ChatVarType.Number,
ChatVarType.Boolean,
ChatVarType.Object, ChatVarType.Object,
ChatVarType.ArrayString, ChatVarType.ArrayString,
ChatVarType.ArrayNumber, ChatVarType.ArrayNumber,
ChatVarType.ArrayBoolean,
ChatVarType.ArrayObject, 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 = ({ const ChatVariableModal = ({
chatVar, chatVar,
onClose, onClose,
@ -94,6 +78,8 @@ const ChatVariableModal = ({
return arrayNumberPlaceholder return arrayNumberPlaceholder
if (type === ChatVarType.ArrayObject) if (type === ChatVarType.ArrayObject)
return arrayObjectPlaceholder return arrayObjectPlaceholder
if (type === ChatVarType.ArrayBoolean)
return arrayBoolPlaceholder
return objectPlaceholder return objectPlaceholder
}, [type]) }, [type])
const getObjectValue = useCallback(() => { const getObjectValue = useCallback(() => {
@ -122,12 +108,16 @@ const ChatVariableModal = ({
return value || '' return value || ''
case ChatVarType.Number: case ChatVarType.Number:
return value || 0 return value || 0
case ChatVarType.Boolean:
return value === undefined ? true : value
case ChatVarType.Object: case ChatVarType.Object:
return editInJSON ? value : formatValueFromObject(objectValue) return editInJSON ? value : formatValueFromObject(objectValue)
case ChatVarType.ArrayString: case ChatVarType.ArrayString:
case ChatVarType.ArrayNumber: case ChatVarType.ArrayNumber:
case ChatVarType.ArrayObject: case ChatVarType.ArrayObject:
return value?.filter(Boolean) || [] return value?.filter(Boolean) || []
case ChatVarType.ArrayBoolean:
return value || []
} }
} }
@ -157,6 +147,10 @@ const ChatVariableModal = ({
setEditInJSON(true) setEditInJSON(true)
if (v === ChatVarType.String || v === ChatVarType.Number || v === ChatVarType.Object) if (v === ChatVarType.String || v === ChatVarType.Number || v === ChatVarType.Object)
setEditInJSON(false) setEditInJSON(false)
if(v === ChatVarType.Boolean)
setValue(true)
if (v === ChatVarType.ArrayBoolean)
setValue([true])
setType(v) setType(v)
} }
@ -202,6 +196,11 @@ const ChatVariableModal = ({
setValue(value?.length ? value : [undefined]) setValue(value?.length ? value : [undefined])
} }
} }
if(type === ChatVarType.ArrayBoolean) {
if(editInJSON)
setEditorContent(JSON.stringify(value.map((item: boolean) => item ? 'True' : 'False')))
}
setEditInJSON(editInJSON) setEditInJSON(editInJSON)
} }
@ -213,7 +212,16 @@ const ChatVariableModal = ({
else { else {
setEditorContent(content) setEditorContent(content)
try { try {
const newValue = JSON.parse(content) let newValue = JSON.parse(content)
if(type === ChatVarType.ArrayBoolean) {
newValue = newValue.map((item: string) => {
if (item === 'True' || item === 'true')
return true
if (item === 'False' || item === 'false')
return false
return undefined
}).filter((item?: boolean) => item !== undefined)
}
setValue(newValue) setValue(newValue)
} }
catch { catch {
@ -304,7 +312,7 @@ const ChatVariableModal = ({
<div className='mb-4'> <div className='mb-4'>
<div className='system-sm-semibold mb-1 flex h-6 items-center justify-between text-text-secondary'> <div className='system-sm-semibold mb-1 flex h-6 items-center justify-between text-text-secondary'>
<div>{t('workflow.chatVariable.modal.value')}</div> <div>{t('workflow.chatVariable.modal.value')}</div>
{(type === ChatVarType.ArrayString || type === ChatVarType.ArrayNumber) && ( {(type === ChatVarType.ArrayString || type === ChatVarType.ArrayNumber || type === ChatVarType.ArrayBoolean) && (
<Button <Button
variant='ghost' variant='ghost'
size='small' size='small'
@ -345,6 +353,12 @@ const ChatVariableModal = ({
type='number' type='number'
/> />
)} )}
{type === ChatVarType.Boolean && (
<BoolValue
value={value}
onChange={setValue}
/>
)}
{type === ChatVarType.Object && !editInJSON && ( {type === ChatVarType.Object && !editInJSON && (
<ObjectValueList <ObjectValueList
list={objectValue} list={objectValue}
@ -365,6 +379,13 @@ const ChatVariableModal = ({
onChange={setValue} onChange={setValue}
/> />
)} )}
{type === ChatVarType.ArrayBoolean && !editInJSON && (
<ArrayBoolList
list={value || [true]}
onChange={setValue}
/>
)}
{editInJSON && ( {editInJSON && (
<div className='w-full rounded-[10px] bg-components-input-bg-normal py-2 pl-3 pr-1' style={{ height: editorMinHeight }}> <div className='w-full rounded-[10px] bg-components-input-bg-normal py-2 pl-3 pr-1' style={{ height: editorMinHeight }}>
<CodeEditor <CodeEditor

@ -1,8 +1,10 @@
export enum ChatVarType { export enum ChatVarType {
Number = 'number', Number = 'number',
String = 'string', String = 'string',
Boolean = 'boolean',
Object = 'object', Object = 'object',
ArrayString = 'array[string]', ArrayString = 'array[string]',
ArrayNumber = 'array[number]', ArrayNumber = 'array[number]',
ArrayBoolean = 'array[boolean]',
ArrayObject = 'array[object]', ArrayObject = 'array[object]',
} }

@ -0,0 +1,35 @@
export const objectPlaceholder = `# example
# {
# "name": "ray",
# "age": 20
# }`
export const arrayStringPlaceholder = `# example
# [
# "value1",
# "value2"
# ]`
export const arrayNumberPlaceholder = `# example
# [
# 100,
# 200
# ]`
export const arrayObjectPlaceholder = `# example
# [
# {
# "name": "ray",
# "age": 20
# },
# {
# "name": "lily",
# "age": 18
# }
# ]`
export const arrayBoolPlaceholder = `# example
# [
# "True",
# "False"
# ]`

@ -178,6 +178,7 @@ export enum InputVarType {
paragraph = 'paragraph', paragraph = 'paragraph',
select = 'select', select = 'select',
number = 'number', number = 'number',
boolean = 'boolean',
url = 'url', url = 'url',
files = 'files', files = 'files',
json = 'json', // obj, array json = 'json', // obj, array
@ -264,6 +265,7 @@ export enum VarType {
arrayString = 'array[string]', arrayString = 'array[string]',
arrayNumber = 'array[number]', arrayNumber = 'array[number]',
arrayObject = 'array[object]', arrayObject = 'array[object]',
arrayBoolean = 'array[boolean]',
arrayFile = 'array[file]', arrayFile = 'array[file]',
any = 'any', any = 'any',
arrayAny = 'array[any]', arrayAny = 'array[any]',

@ -359,6 +359,7 @@ const translation = {
'paragraph': 'Paragraph', 'paragraph': 'Paragraph',
'select': 'Select', 'select': 'Select',
'number': 'Number', 'number': 'Number',
'boolean': 'Checkbox',
'single-file': 'Single File', 'single-file': 'Single File',
'multi-files': 'File List', 'multi-files': 'File List',
'notSet': 'Not set, try typing {{input}} in the prefix prompt', 'notSet': 'Not set, try typing {{input}} in the prefix prompt',

@ -349,6 +349,7 @@ const translation = {
'paragraph': '段落', 'paragraph': '段落',
'select': '下拉选项', 'select': '下拉选项',
'number': '数字', 'number': '数字',
'boolean': '复选框',
'single-file': '单文件', 'single-file': '单文件',
'multi-files': '文件列表', 'multi-files': '文件列表',
'notSet': '未设置,在 Prompt 中输入 {{input}} 试试', 'notSet': '未设置,在 Prompt 中输入 {{input}} 试试',

@ -9,7 +9,7 @@ import type {
MetadataFilteringModeEnum, MetadataFilteringModeEnum,
} from '@/app/components/workflow/nodes/knowledge-retrieval/types' } from '@/app/components/workflow/nodes/knowledge-retrieval/types'
import type { ModelConfig as NodeModelConfig } from '@/app/components/workflow/types' import type { ModelConfig as NodeModelConfig } from '@/app/components/workflow/types'
export type Inputs = Record<string, string | number | object> export type Inputs = Record<string, string | number | object | boolean>
export enum PromptMode { export enum PromptMode {
simple = 'simple', simple = 'simple',

@ -130,9 +130,9 @@ export const promptVariablesToUserInputsForm = (promptVariables: PromptVariable[
} as any) } as any)
return return
} }
if (item.type === 'number') { if (item.type === 'number' || item.type === 'boolean') {
userInputs.push({ userInputs.push({
number: { [item.type]: {
label: item.name, label: item.name,
variable: item.key, variable: item.key,
required: item.required !== false, // default true required: item.required !== false, // default true
@ -172,3 +172,17 @@ export const promptVariablesToUserInputsForm = (promptVariables: PromptVariable[
return userInputs return userInputs
} }
export const formatBooleanInputs = (useInputs?: PromptVariable[] | null, inputs?: Record<string, string | number | object | boolean> | null) => {
if(!useInputs)
return inputs
const res = { ...(inputs || {}) }
useInputs.forEach((item) => {
const isBooleanInput = item.type === 'boolean'
if (isBooleanInput) {
// Convert boolean inputs to boolean type
res[item.key] = !!res[item.key]
}
})
return res
}

Loading…
Cancel
Save