Merge branch 'main' into feat/rag-2

This commit is contained in:
twwu
2025-08-27 11:16:27 +08:00
438 changed files with 17986 additions and 7846 deletions

View File

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

View File

@ -24,6 +24,7 @@ import { BubbleX } from '@/app/components/base/icons/src/vender/line/others'
import { FILE_EXTS } from '@/app/components/base/prompt-editor/constants'
import cn from '@/utils/classnames'
import type { FileEntity } from '@/app/components/base/file-uploader/types'
import BoolInput from './bool-input'
import { useHooksStore } from '@/app/components/workflow/hooks-store'
type Props = {
@ -93,6 +94,7 @@ const FormItem: FC<Props> = ({
return ''
})()
const isBooleanType = type === InputVarType.checkbox
const isArrayLikeType = [InputVarType.contexts, InputVarType.iterator].includes(type)
const isContext = type === InputVarType.contexts
const isIterator = type === InputVarType.iterator
@ -114,7 +116,7 @@ const FormItem: FC<Props> = ({
return (
<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='truncate'>{typeof payload.label === 'object' ? nodeKey : payload.label}</div>
{!payload.required && <span className='system-xs-regular text-text-tertiary'>{t('workflow.panel.optional')}</span>}
@ -167,6 +169,15 @@ const FormItem: FC<Props> = ({
)
}
{isBooleanType && (
<BoolInput
name={payload.label as string}
value={!!value}
required={payload.required}
onChange={onChange}
/>
)}
{
type === InputVarType.json && (
<CodeEditor
@ -177,6 +188,18 @@ const FormItem: FC<Props> = ({
/>
)
}
{type === InputVarType.jsonObject && (
<CodeEditor
value={value}
language={CodeLanguage.json}
onChange={onChange}
noWrapper
className='bg h-[80px] overflow-y-auto rounded-[10px] bg-components-input-bg-normal p-1'
placeholder={
<div className='whitespace-pre'>{payload.json_schema}</div>
}
/>
)}
{(type === InputVarType.singleFile) && (
<FileUploaderInAttachmentWrapper
value={singleFileValue}

View File

@ -32,6 +32,8 @@ export type BeforeRunFormProps = {
} & Partial<SpecialResultPanelProps>
function formatValue(value: string | any, type: InputVarType) {
if(type === InputVarType.checkbox)
return !!value
if(value === undefined || value === null)
return value
if (type === InputVarType.number)
@ -87,7 +89,7 @@ const BeforeRunForm: FC<BeforeRunFormProps> = ({
form.inputs.forEach((input) => {
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.checkbox) && !(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 })
if (!errMsg && (input.type === InputVarType.singleFile || input.type === InputVarType.multiFiles) && value) {

View File

@ -68,7 +68,7 @@ const FormInputItem: FC<Props> = ({
const isSelect = type === FormTypeEnum.select || type === FormTypeEnum.dynamicSelect
const isAppSelector = type === FormTypeEnum.appSelector
const isModelSelector = type === FormTypeEnum.modelSelector
const showTypeSwitch = isNumber || isObject || isArray
const showTypeSwitch = isNumber || isBoolean || isObject || isArray
const isConstant = varInput?.type === VarKindType.constant || !varInput?.type
const showVariableSelector = isFile || varInput?.type === VarKindType.variable

View File

@ -3,6 +3,7 @@ import type { FC } from 'react'
import React from 'react'
import {
RiAlignLeft,
RiBracesLine,
RiCheckboxLine,
RiCheckboxMultipleLine,
RiFileCopy2Line,
@ -23,6 +24,8 @@ const getIcon = (type: InputVarType) => {
[InputVarType.paragraph]: RiAlignLeft,
[InputVarType.select]: RiCheckboxMultipleLine,
[InputVarType.number]: RiHashtag,
[InputVarType.checkbox]: RiCheckboxLine,
[InputVarType.jsonObject]: RiBracesLine,
[InputVarType.singleFile]: RiFileList2Line,
[InputVarType.multiFiles]: RiFileCopy2Line,
[InputVarType.checkbox]: RiCheckboxLine,

View File

@ -71,11 +71,13 @@ export const hasValidChildren = (children: any): boolean => {
)
}
const inputVarTypeToVarType = (type: InputVarType): VarType => {
export const inputVarTypeToVarType = (type: InputVarType): VarType => {
return ({
[InputVarType.number]: VarType.number,
[InputVarType.checkbox]: VarType.boolean,
[InputVarType.singleFile]: VarType.file,
[InputVarType.multiFiles]: VarType.arrayFile,
[InputVarType.jsonObject]: VarType.object,
} as any)[type] || VarType.string
}
@ -245,14 +247,27 @@ const formatItem = (
variables,
} = data as StartNodeType
res.vars = variables.map((v) => {
return {
const type = inputVarTypeToVarType(v.type)
const varRes: Var = {
variable: v.variable,
type: inputVarTypeToVarType(v.type),
type,
isParagraph: v.type === InputVarType.paragraph,
isSelect: v.type === InputVarType.select,
options: v.options,
required: v.required,
}
try {
if(type === VarType.object && v.json_schema) {
varRes.children = {
schema: JSON.parse(v.json_schema),
}
}
}
catch (error) {
console.error('Error formatting variable:', error)
}
return varRes
})
if (isChatMode) {
res.vars.push({
@ -729,6 +744,8 @@ const getIterationItemType = ({
return VarType.string
case VarType.arrayNumber:
return VarType.number
case VarType.arrayBoolean:
return VarType.boolean
case VarType.arrayObject:
return VarType.object
case VarType.array:
@ -782,6 +799,8 @@ const getLoopItemType = ({
return VarType.number
case VarType.arrayObject:
return VarType.object
case VarType.arrayBoolean:
return VarType.boolean
case VarType.array:
return VarType.any
case VarType.arrayFile:

View File

@ -18,7 +18,7 @@ type Props = {
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> = ({
readonly,
className,

View File

@ -546,7 +546,7 @@ const BasePanel: FC<BasePanelProps> = ({
isRunAfterSingleRun={isRunAfterSingleRun}
updateNodeRunningStatus={updateNodeRunningStatus}
onSingleRunClicked={handleSingleRun}
nodeInfo={nodeInfo}
nodeInfo={nodeInfo!}
singleRunResult={runResult!}
isPaused={isPaused}
{...passedLogParams}

View File

@ -70,7 +70,6 @@ const LastRun: FC<Props> = ({
updateNodeRunningStatus(hidePageOneStepFinishedStatus)
resetHidePageStatus()
}
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [isOneStepRunSucceed, isOneStepRunFailed, oneStepRunRunningStatus])
useEffect(() => {
@ -80,7 +79,6 @@ const LastRun: FC<Props> = ({
useEffect(() => {
resetHidePageStatus()
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [nodeId])
const handlePageVisibilityChange = useCallback(() => {
@ -120,7 +118,7 @@ const LastRun: FC<Props> = ({
status={isPaused ? NodeRunningStatus.Stopped : ((runResult as any).status || otherResultPanelProps.status)}
total_tokens={(runResult as any)?.execution_metadata?.total_tokens || otherResultPanelProps?.total_tokens}
created_by={(runResult as any)?.created_by_account?.created_by || otherResultPanelProps?.created_by}
nodeInfo={nodeInfo}
nodeInfo={runResult as NodeTracing}
showSteps={false}
/>
</div>

View File

@ -156,8 +156,8 @@ const useLastRun = <T>({
checkValid,
} = oneStepRunRes
const nodeInfo = runResult
const {
nodeInfo,
...singleRunParams
} = useSingleRunFormParamsHooks(blockType)({
id,

View File

@ -96,6 +96,8 @@ const varTypeToInputVarType = (type: VarType, {
return InputVarType.paragraph
if (type === VarType.number)
return InputVarType.number
if (type === VarType.boolean)
return InputVarType.checkbox
if ([VarType.object, VarType.array, VarType.arrayNumber, VarType.arrayString, VarType.arrayObject].includes(type))
return InputVarType.json
if (type === VarType.file)
@ -601,7 +603,7 @@ const useOneStepRun = <T>({
}
}
return {
label: item.label || item.variable,
label: (typeof item.label === 'object' ? item.label.variable : item.label) || item.variable,
variable: item.variable,
type: varTypeToInputVarType(originalVar.type, {
isSelect: !!originalVar.isSelect,

View File

@ -9,13 +9,15 @@ import { AssignerNodeInputType, WriteMode } from '../../types'
import type { AssignerNodeOperation } from '../../types'
import ListNoDataPlaceholder from '@/app/components/workflow/nodes/_base/components/list-no-data-placeholder'
import VarReferencePicker from '@/app/components/workflow/nodes/_base/components/variable/var-reference-picker'
import type { ValueSelector, Var, VarType } from '@/app/components/workflow/types'
import type { ValueSelector, Var } from '@/app/components/workflow/types'
import { VarType } from '@/app/components/workflow/types'
import { CodeLanguage } from '@/app/components/workflow/nodes/code/types'
import ActionButton from '@/app/components/base/action-button'
import Input from '@/app/components/base/input'
import Textarea from '@/app/components/base/textarea'
import CodeEditor from '@/app/components/workflow/nodes/_base/components/editor/code-editor'
import { noop } from 'lodash-es'
import BoolValue from '@/app/components/workflow/panel/chat-variable-panel/components/bool-value'
type Props = {
readonly: boolean
@ -59,23 +61,27 @@ const VarList: FC<Props> = ({
}
}, [list, onChange])
const handleOperationChange = useCallback((index: number) => {
const handleOperationChange = useCallback((index: number, varType: VarType) => {
return (item: { value: string | number }) => {
const newList = produce(list, (draft) => {
draft[index].operation = item.value as WriteMode
draft[index].value = '' // Clear value when operation changes
if (item.value === WriteMode.set || item.value === WriteMode.increment || item.value === WriteMode.decrement
|| item.value === WriteMode.multiply || item.value === WriteMode.divide)
|| item.value === WriteMode.multiply || item.value === WriteMode.divide) {
if(varType === VarType.boolean)
draft[index].value = false
draft[index].input_type = AssignerNodeInputType.constant
else
}
else {
draft[index].input_type = AssignerNodeInputType.variable
}
})
onChange(newList)
}
}, [list, onChange])
const handleToAssignedVarChange = useCallback((index: number) => {
return (value: ValueSelector | string | number) => {
return (value: ValueSelector | string | number | boolean) => {
const newList = produce(list, (draft) => {
draft[index].value = value as ValueSelector
})
@ -145,7 +151,7 @@ const VarList: FC<Props> = ({
value={item.operation}
placeholder='Operation'
disabled={!item.variable_selector || item.variable_selector.length === 0}
onSelect={handleOperationChange(index)}
onSelect={handleOperationChange(index, assignedVarType!)}
assignedVarType={assignedVarType}
writeModeTypes={writeModeTypes}
writeModeTypesArr={writeModeTypesArr}
@ -188,6 +194,12 @@ const VarList: FC<Props> = ({
className='w-full'
/>
)}
{assignedVarType === 'boolean' && (
<BoolValue
value={item.value as boolean}
onChange={value => handleToAssignedVarChange(index)(value)}
/>
)}
{assignedVarType === 'object' && (
<CodeEditor
value={item.value as string}

View File

@ -31,7 +31,7 @@ const nodeDefault: NodeDefault<AssignerNodeType> = {
if (value.operation === WriteMode.set || value.operation === WriteMode.increment
|| value.operation === WriteMode.decrement || value.operation === WriteMode.multiply
|| value.operation === WriteMode.divide) {
if (!value.value && typeof value.value !== 'number')
if (!value.value && value.value !== false && typeof value.value !== 'number')
errorMessages = t(`${i18nPrefix}.fieldRequired`, { field: t('workflow.nodes.assigner.variable') })
}
else if (!value.value?.length) {

View File

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

View File

@ -76,7 +76,6 @@ const useConfig = (id: string, payload: CodeNodeType) => {
})
syncOutputKeyOrders(defaultConfig.outputs)
}
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [defaultConfig])
const handleCodeChange = useCallback((code: string) => {
@ -101,7 +100,7 @@ const useConfig = (id: string, payload: CodeNodeType) => {
}, [allLanguageDefault, inputs, setInputs])
const handleSyncFunctionSignature = useCallback(() => {
const generateSyncSignatureCode = (code: string) => {
const generateSyncSignatureCode = (code: string) => {
let mainDefRe
let newMainDef
if (inputs.code_language === CodeLanguage.javascript) {
@ -175,7 +174,7 @@ const useConfig = (id: string, payload: CodeNodeType) => {
})
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) => {

View File

@ -38,6 +38,7 @@ import { VarType } from '@/app/components/workflow/types'
import cn from '@/utils/classnames'
import { SimpleSelect as Select } from '@/app/components/base/select'
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 { useIsChatMode } from '@/app/components/workflow/hooks/use-workflow'
const optionNameI18NPrefix = 'workflow.nodes.ifElse.optionName'
@ -147,12 +148,12 @@ const ConditionItem = ({
const isArrayValue = fileAttr?.key === 'transfer_method' || fileAttr?.key === 'type'
const handleUpdateConditionValue = useCallback((value: string) => {
if (value === condition.value || (isArrayValue && value === condition.value?.[0]))
const handleUpdateConditionValue = useCallback((value: string | boolean) => {
if (value === condition.value || (isArrayValue && value === (condition.value as string[])?.[0]))
return
const newCondition = {
...condition,
value: isArrayValue ? [value] : value,
value: isArrayValue ? [value as string] : value,
}
doUpdateCondition(newCondition)
}, [condition, doUpdateCondition, isArrayValue])
@ -208,8 +209,12 @@ const ConditionItem = ({
}, [caseId, condition, conditionId, isSubVariableKey, onRemoveCondition, onRemoveSubVariableCondition])
const handleVarChange = useCallback((valueSelector: ValueSelector, _varItem: Var) => {
const {
conversationVariables,
} = workflowStore.getState()
const resolvedVarType = getVarType({
valueSelector,
conversationVariables,
availableNodes,
isChatMode,
allPluginInfoList: {
@ -224,7 +229,7 @@ const ConditionItem = ({
const newCondition = produce(condition, (draft) => {
draft.variable_selector = valueSelector
draft.varType = resolvedVarType
draft.value = ''
draft.value = resolvedVarType === VarType.boolean ? false : ''
draft.comparison_operator = getOperators(resolvedVarType)[0]
setTimeout(() => setControlPromptEditorRerenderKey(Date.now()))
})
@ -232,6 +237,14 @@ const ConditionItem = ({
setOpen(false)
}, [condition, doUpdateCondition, availableNodes, isChatMode, setControlPromptEditorRerenderKey])
const showBooleanInput = useMemo(() => {
if(condition.varType === VarType.boolean)
return true
// eslint-disable-next-line sonarjs/prefer-single-boolean-return
if(condition.varType === VarType.arrayBoolean && [ComparisonOperator.contains, ComparisonOperator.notContains].includes(condition.comparison_operator!))
return true
return false
}, [condition])
return (
<div className={cn('mb-1 flex last-of-type:mb-0', className)}>
<div className={cn(
@ -285,7 +298,7 @@ const ConditionItem = ({
/>
</div>
{
!comparisonOperatorNotRequireValue(condition.comparison_operator) && !isNotInput && condition.varType !== VarType.number && (
!comparisonOperatorNotRequireValue(condition.comparison_operator) && !isNotInput && condition.varType !== VarType.number && !showBooleanInput && (
<div className='max-h-[100px] overflow-y-auto border-t border-t-divider-subtle px-2 py-1'>
<ConditionInput
disabled={disabled}
@ -297,6 +310,16 @@ const ConditionItem = ({
</div>
)
}
{
!comparisonOperatorNotRequireValue(condition.comparison_operator) && !isNotInput && showBooleanInput && (
<div className='p-1'>
<BoolValue
value={condition.value as boolean}
onChange={handleUpdateConditionValue}
/>
</div>
)
}
{
!comparisonOperatorNotRequireValue(condition.comparison_operator) && !isNotInput && condition.varType === VarType.number && (
<div className='border-t border-t-divider-subtle px-2 py-1 pt-[3px]'>

View File

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

View File

@ -1,4 +1,4 @@
import type { NodeDefault } from '../../types'
import type { NodeDefault, VarType } from '../../types'
import { type IfElseNodeType, LogicalOperator } from './types'
import { isEmptyRelatedOperator } from './utils'
import { genNodeMetaData } from '@/app/components/workflow/utils'
@ -57,13 +57,13 @@ const nodeDefault: NodeDefault<IfElseNodeType> = {
if (isEmptyRelatedOperator(c.comparison_operator!))
return true
return !!c.value
return (c.varType === VarType.boolean || c.varType === VarType.arrayBoolean) ? c.value === undefined : !!c.value
})
if (!isSet)
errorMessages = t(`${i18nPrefix}.fieldRequired`, { field: t(`${i18nPrefix}.fields.variableValue`) })
}
else {
if (!isEmptyRelatedOperator(condition.comparison_operator!) && !condition.value)
if (!isEmptyRelatedOperator(condition.comparison_operator!) && ((condition.varType === VarType.boolean || condition.varType === VarType.arrayBoolean) ? condition.value === undefined : !condition.value))
errorMessages = t(`${i18nPrefix}.fieldRequired`, { field: t(`${i18nPrefix}.fields.variableValue`) })
}
}

View File

@ -7,6 +7,7 @@ import { isEmptyRelatedOperator } from './utils'
import type { Condition, IfElseNodeType } from './types'
import ConditionValue from './components/condition-value'
import ConditionFilesListValue from './components/condition-files-list-value'
import { VarType } from '../../types'
const i18nPrefix = 'workflow.nodes.ifElse'
const IfElseNode: FC<NodeProps<IfElseNodeType>> = (props) => {
@ -23,18 +24,14 @@ const IfElseNode: FC<NodeProps<IfElseNodeType>> = (props) => {
if (!c.comparison_operator)
return false
if (isEmptyRelatedOperator(c.comparison_operator!))
return true
return !!c.value
return (c.varType === VarType.boolean || c.varType === VarType.arrayBoolean) ? true : !!c.value
})
return isSet
}
else {
if (isEmptyRelatedOperator(condition.comparison_operator!))
return true
return !!condition.value
return (condition.varType === VarType.boolean || condition.varType === VarType.arrayBoolean) ? 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'>
@ -73,7 +70,7 @@ const IfElseNode: FC<NodeProps<IfElseNodeType>> = (props) => {
<ConditionValue
variableSelector={condition.variable_selector!}
operator={condition.comparison_operator!}
value={condition.value}
value={condition.varType === VarType.boolean ? (!condition.value ? 'False' : condition.value) : condition.value}
/>
)

View File

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

View File

@ -144,7 +144,7 @@ const useConfig = (id: string, payload: IfElseNodeType) => {
varType: varItem.type,
variable_selector: valueSelector,
comparison_operator: getOperators(varItem.type, getIsVarFileAttribute(valueSelector) ? { key: valueSelector.slice(-1)[0] } : undefined)[0],
value: '',
value: (varItem.type === VarType.boolean || varItem.type === VarType.arrayBoolean) ? false : '',
})
}
})

View File

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

View File

@ -26,7 +26,7 @@ const useConfig = (id: string, payload: IterationNodeType) => {
const { inputs, setInputs } = useNodeCrud<IterationNodeType>(id, payload)
const filterInputVar = useCallback((varPayload: Var) => {
return [VarType.array, VarType.arrayString, VarType.arrayNumber, VarType.arrayObject, VarType.arrayFile].includes(varPayload.type)
return [VarType.array, VarType.arrayString, VarType.arrayBoolean, VarType.arrayNumber, VarType.arrayObject, VarType.arrayFile].includes(varPayload.type)
}, [])
const handleInputChange = useCallback((input: ValueSelector | string, _varKindType: VarKindType, varInfo?: Var) => {

View File

@ -9,6 +9,7 @@ import { comparisonOperatorNotRequireValue, getOperators } from '../../if-else/u
import SubVariablePicker from './sub-variable-picker'
import { FILE_TYPE_OPTIONS, TRANSFER_METHOD } from '@/app/components/workflow/nodes/constants'
import { SimpleSelect as Select } from '@/app/components/base/select'
import BoolValue from '../../../panel/chat-variable-panel/components/bool-value'
import Input from '@/app/components/workflow/nodes/_base/components/input-support-select-var'
import useAvailableVarList from '@/app/components/workflow/nodes/_base/hooks/use-available-var-list'
import cn from '@/utils/classnames'
@ -28,8 +29,8 @@ const VAR_INPUT_SUPPORTED_KEYS: Record<string, VarType> = {
type Props = {
condition: Condition
onChange: (condition: Condition) => void
varType: VarType
onChange: (condition: Condition) => void
hasSubVariable: boolean
readOnly: boolean
nodeId: string
@ -58,6 +59,7 @@ const FilterCondition: FC<Props> = ({
const isSelect = [ComparisonOperator.in, ComparisonOperator.notIn, ComparisonOperator.allOf].includes(condition.comparison_operator)
const isArrayValue = condition.key === 'transfer_method' || condition.key === 'type'
const isBoolean = varType === VarType.boolean
const selectOptions = useMemo(() => {
if (isSelect) {
@ -112,6 +114,12 @@ const FilterCondition: FC<Props> = ({
/>
)
}
else if (isBoolean) {
inputElement = (<BoolValue
value={condition.value as boolean}
onChange={handleChange('value')}
/>)
}
else if (supportVariableInput) {
inputElement = (
<Input
@ -162,7 +170,7 @@ const FilterCondition: FC<Props> = ({
<div className='flex space-x-1'>
<ConditionOperator
className='h-8 bg-components-input-bg-normal'
varType={expectedVarType ?? VarType.string}
varType={expectedVarType ?? varType ?? VarType.string}
value={condition.comparison_operator}
onSelect={handleChange('comparison_operator')}
file={hasSubVariable ? { key: condition.key } : undefined}

View File

@ -35,7 +35,7 @@ const nodeDefault: NodeDefault<ListFilterNodeType> = {
},
checkValid(payload: ListFilterNodeType, t: any) {
let errorMessages = ''
const { variable, var_type, filter_by } = payload
const { variable, var_type, filter_by, item_var_type } = payload
if (!errorMessages && !variable?.length)
errorMessages = t(`${i18nPrefix}.fieldRequired`, { field: t('workflow.nodes.listFilter.inputVar') })
@ -48,7 +48,7 @@ const nodeDefault: NodeDefault<ListFilterNodeType> = {
if (!errorMessages && !filter_by.conditions[0]?.comparison_operator)
errorMessages = t(`${i18nPrefix}.fieldRequired`, { field: t('workflow.nodes.listFilter.filterConditionComparisonOperator') })
if (!errorMessages && !comparisonOperatorNotRequireValue(filter_by.conditions[0]?.comparison_operator) && !filter_by.conditions[0]?.value)
if (!errorMessages && !comparisonOperatorNotRequireValue(filter_by.conditions[0]?.comparison_operator) && (item_var_type === VarType.boolean ? !filter_by.conditions[0]?.value === undefined : !filter_by.conditions[0]?.value))
errorMessages = t(`${i18nPrefix}.fieldRequired`, { field: t('workflow.nodes.listFilter.filterConditionComparisonValue') })
}

View File

@ -14,7 +14,7 @@ export type Limit = {
export type Condition = {
key: string
comparison_operator: ComparisonOperator
value: string | number | string[]
value: string | number | boolean | string[]
}
export type ListFilterNodeType = CommonNodeType & {

View File

@ -45,7 +45,7 @@ const useConfig = (id: string, payload: ListFilterNodeType) => {
isChatMode,
isConstant: false,
})
let itemVarType = varType
let itemVarType
switch (varType) {
case VarType.arrayNumber:
itemVarType = VarType.number
@ -59,6 +59,11 @@ const useConfig = (id: string, payload: ListFilterNodeType) => {
case VarType.arrayObject:
itemVarType = VarType.object
break
case VarType.arrayBoolean:
itemVarType = VarType.boolean
break
default:
itemVarType = varType
}
return { varType, itemVarType }
}, [availableNodes, getCurrentVariableType, inputs.variable, isChatMode, isInIteration, iterationNode, loopNode])
@ -84,7 +89,7 @@ const useConfig = (id: string, payload: ListFilterNodeType) => {
draft.filter_by.conditions = [{
key: (isFileArray && !draft.filter_by.conditions[0]?.key) ? 'name' : '',
comparison_operator: getOperators(itemVarType, isFileArray ? { key: 'name' } : undefined)[0],
value: '',
value: itemVarType === VarType.boolean ? false : '',
}]
if (isFileArray && draft.order_by.enabled && !draft.order_by.key)
draft.order_by.key = 'name'
@ -94,7 +99,7 @@ const useConfig = (id: string, payload: ListFilterNodeType) => {
const filterVar = useCallback((varPayload: Var) => {
// 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) => {

View File

@ -11,7 +11,6 @@ import VisualEditor from './visual-editor'
import SchemaEditor from './schema-editor'
import {
checkJsonSchemaDepth,
convertBooleanToString,
getValidationErrorMessage,
jsonToSchema,
preValidateSchema,
@ -87,7 +86,6 @@ const JsonSchemaConfig: FC<JsonSchemaConfigProps> = ({
setValidationError(`Schema exceeds maximum depth of ${JSON_SCHEMA_MAX_DEPTH}.`)
return
}
convertBooleanToString(schema)
const validationErrors = validateSchemaAgainstDraft7(schema)
if (validationErrors.length > 0) {
setValidationError(getValidationErrorMessage(validationErrors))
@ -168,7 +166,6 @@ const JsonSchemaConfig: FC<JsonSchemaConfigProps> = ({
setValidationError(`Schema exceeds maximum depth of ${JSON_SCHEMA_MAX_DEPTH}.`)
return
}
convertBooleanToString(schema)
const validationErrors = validateSchemaAgainstDraft7(schema)
if (validationErrors.length > 0) {
setValidationError(getValidationErrorMessage(validationErrors))

View File

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

View File

@ -246,7 +246,7 @@ const useConfig = (id: string, payload: LLMNodeType) => {
}, [inputs, setInputs])
const handlePromptChange = useCallback((newPrompt: PromptItem[] | PromptItem) => {
const newInputs = produce(inputs, (draft) => {
const newInputs = produce(inputRef.current, (draft) => {
draft.prompt_template = newPrompt
})
setInputs(newInputs)

View File

@ -306,6 +306,7 @@ export const getValidationErrorMessage = (errors: ValidationError[]) => {
return message
}
// Previous Not support boolean type, so transform boolean to string when paste it into schema editor
export const convertBooleanToString = (schema: any) => {
if (schema.type === Type.boolean)
schema.type = Type.string

View File

@ -36,6 +36,7 @@ import cn from '@/utils/classnames'
import { SimpleSelect as Select } from '@/app/components/base/select'
import { Variable02 } from '@/app/components/base/icons/src/vender/solid/development'
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'
@ -129,12 +130,12 @@ const ConditionItem = ({
const isArrayValue = fileAttr?.key === 'transfer_method' || fileAttr?.key === 'type'
const handleUpdateConditionValue = useCallback((value: string) => {
if (value === condition.value || (isArrayValue && value === condition.value?.[0]))
const handleUpdateConditionValue = useCallback((value: string | boolean) => {
if (value === condition.value || (isArrayValue && value === (condition.value as string[])?.[0]))
return
const newCondition = {
...condition,
value: isArrayValue ? [value] : value,
value: isArrayValue ? [value as string] : value,
}
doUpdateCondition(newCondition)
}, [condition, doUpdateCondition, isArrayValue])
@ -253,7 +254,7 @@ const ConditionItem = ({
/>
</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'>
<ConditionInput
disabled={disabled}
@ -264,6 +265,14 @@ const ConditionItem = ({
</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 && (
<div className='border-t border-t-divider-subtle px-2 py-1 pt-[3px]'>

View File

@ -18,33 +18,16 @@ import {
ValueType,
VarType,
} from '@/app/components/workflow/types'
import BoolValue from '@/app/components/workflow/panel/chat-variable-panel/components/bool-value'
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
# }
# ]`
import {
arrayBoolPlaceholder,
arrayNumberPlaceholder,
arrayObjectPlaceholder,
arrayStringPlaceholder,
objectPlaceholder,
} from '@/app/components/workflow/panel/chat-variable-panel/utils'
import ArrayBoolList from '@/app/components/workflow/panel/chat-variable-panel/components/array-bool-list'
type FormItemProps = {
nodeId: string
@ -83,6 +66,8 @@ const FormItem = ({
return arrayNumberPlaceholder
if (var_type === VarType.arrayObject)
return arrayObjectPlaceholder
if (var_type === VarType.arrayBoolean)
return arrayBoolPlaceholder
return objectPlaceholder
}, [var_type])
@ -120,6 +105,14 @@ const FormItem = ({
/>
)
}
{
value_type === ValueType.constant && var_type === VarType.boolean && (
<BoolValue
value={value}
onChange={handleChange}
/>
)
}
{
value_type === ValueType.constant
&& (var_type === VarType.object || var_type === VarType.arrayString || var_type === VarType.arrayNumber || var_type === VarType.arrayObject)
@ -137,6 +130,15 @@ const FormItem = ({
</div>
)
}
{
value_type === ValueType.constant && var_type === VarType.arrayBoolean && (
<ArrayBoolList
className='mt-2'
list={value || [false]}
onChange={handleChange}
/>
)
}
</div>
)
}

View File

@ -12,6 +12,7 @@ import type {
} from '@/app/components/workflow/nodes/loop/types'
import { checkKeys, replaceSpaceWithUnderscoreInVarNameInput } from '@/utils/var'
import Toast from '@/app/components/base/toast'
import { ValueType, VarType } from '@/app/components/workflow/types'
type ItemProps = {
item: LoopVariable
@ -42,12 +43,25 @@ const Item = ({
handleUpdateLoopVariable(item.id, { label: e.target.value })
}, [item.id, handleUpdateLoopVariable])
const getDefaultValue = useCallback((varType: VarType, valueType: ValueType) => {
if(valueType === ValueType.variable)
return undefined
switch (varType) {
case VarType.boolean:
return false
case VarType.arrayBoolean:
return [false]
default:
return undefined
}
}, [])
const handleUpdateItemVarType = useCallback((value: any) => {
handleUpdateLoopVariable(item.id, { var_type: value, value: undefined })
handleUpdateLoopVariable(item.id, { var_type: value, value: getDefaultValue(value, item.value_type) })
}, [item.id, handleUpdateLoopVariable])
const handleUpdateItemValueType = useCallback((value: any) => {
handleUpdateLoopVariable(item.id, { value_type: value, value: undefined })
handleUpdateLoopVariable(item.id, { value_type: value, value: getDefaultValue(item.var_type, value) })
}, [item.id, handleUpdateLoopVariable])
const handleUpdateItemValue = useCallback((value: any) => {

View File

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

View File

@ -1,3 +1,4 @@
import { VarType } from '../../types'
import type { NodeDefault } from '../../types'
import { ComparisonOperator, LogicalOperator, type LoopNodeType } from './types'
import { isEmptyRelatedOperator } from './utils'
@ -53,7 +54,7 @@ const nodeDefault: NodeDefault<LoopNodeType> = {
errorMessages = t(`${i18nPrefix}.fieldRequired`, { field: t(`${i18nPrefix}.fields.variableValue`) })
}
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`) })
}
}

View File

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

View File

@ -75,7 +75,7 @@ const useConfig = (id: string, payload: LoopNodeType) => {
varType: varItem.type,
variable_selector: valueSelector,
comparison_operator: getOperators(varItem.type, getIsVarFileAttribute(valueSelector) ? { key: valueSelector.slice(-1)[0] } : undefined)[0],
value: '',
value: varItem.type === VarType.boolean ? 'false' : '',
})
})
setInputs(newInputs)

View File

@ -107,7 +107,7 @@ const useSingleRunFormParams = ({
}, [runResult, loopRunResult, t])
const setInputVarValues = useCallback((newPayload: Record<string, any>) => {
setRunInputData(newPayload)
setRunInputData(newPayload)
}, [setRunInputData])
const inputVarValues = (() => {
@ -149,16 +149,15 @@ const useSingleRunFormParams = ({
})
payload.loop_variables?.forEach((loopVariable) => {
if(loopVariable.value_type === ValueType.variable)
if (loopVariable.value_type === ValueType.variable)
allInputs.push(loopVariable.value)
})
const inputVarsFromValue: InputVar[] = []
const varInputs = [...varSelectorsToVarInputs(allInputs), ...inputVarsFromValue]
const existVarsKey: Record<string, boolean> = {}
const uniqueVarInputs: InputVar[] = []
varInputs.forEach((input) => {
if(!input)
if (!input)
return
if (!existVarsKey[input.variable]) {
existVarsKey[input.variable] = true
@ -191,7 +190,7 @@ const useSingleRunFormParams = ({
if (condition.variable_selector)
vars.push(condition.variable_selector)
if(condition.sub_variable_condition && condition.sub_variable_condition.conditions?.length)
if (condition.sub_variable_condition && condition.sub_variable_condition.conditions?.length)
vars.push(...getVarFromCaseItem(condition.sub_variable_condition))
return vars
}
@ -203,7 +202,7 @@ const useSingleRunFormParams = ({
vars.push(...conditionVars)
})
payload.loop_variables?.forEach((loopVariable) => {
if(loopVariable.value_type === ValueType.variable)
if (loopVariable.value_type === ValueType.variable)
vars.push(loopVariable.value)
})
const hasFilterLoopVars = vars.filter(item => item[0] !== id)

View File

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

View File

@ -35,7 +35,7 @@ type Props = {
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> = ({
type,

View File

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

View File

@ -19,7 +19,7 @@ type Props = {
className?: string
readonly: boolean
payload: InputVar
onChange?: (item: InputVar, moreInfo?: MoreInfo) => void
onChange?: (item: InputVar, moreInfo?: MoreInfo) => boolean
onRemove?: () => void
rightContent?: React.JSX.Element
varKeys?: string[]
@ -31,7 +31,7 @@ const VarItem: FC<Props> = ({
className,
readonly,
payload,
onChange = noop,
onChange = () => true,
onRemove = noop,
rightContent,
varKeys = [],
@ -48,7 +48,9 @@ const VarItem: FC<Props> = ({
}] = useBoolean(false)
const handlePayloadChange = useCallback((payload: InputVar, moreInfo?: MoreInfo) => {
onChange(payload, moreInfo)
const isValid = onChange(payload, moreInfo)
if(!isValid)
return
hideEditVarModal()
}, [onChange, hideEditVarModal])
return (

View File

@ -9,6 +9,8 @@ import { v4 as uuid4 } from 'uuid'
import { ReactSortable } from 'react-sortablejs'
import { RiDraggable } from '@remixicon/react'
import cn from '@/utils/classnames'
import { hasDuplicateStr } from '@/utils/var'
import Toast from '@/app/components/base/toast'
type Props = {
readonly: boolean
@ -28,7 +30,26 @@ const VarList: FC<Props> = ({
const newList = produce(list, (draft) => {
draft[index] = payload
})
let errorMsgKey = ''
let typeName = ''
if (hasDuplicateStr(newList.map(item => item.variable))) {
errorMsgKey = 'appDebug.varKeyError.keyAlreadyExists'
typeName = 'appDebug.variableConfig.varName'
}
else if (hasDuplicateStr(newList.map(item => item.label as string))) {
errorMsgKey = 'appDebug.varKeyError.keyAlreadyExists'
typeName = 'appDebug.variableConfig.labelName'
}
if (errorMsgKey) {
Toast.notify({
type: 'error',
message: t(errorMsgKey, { key: t(typeName) }),
})
return false
}
onChange(newList, moreInfo ? { index, payload: moreInfo } : undefined)
return true
}
}, [list, onChange])

View File

@ -34,7 +34,8 @@ const Panel: FC<NodePanelProps<StartNodeType>> = ({
} = useConfig(id, data)
const handleAddVarConfirm = (payload: InputVar) => {
handleAddVariable(payload)
const isValid = handleAddVariable(payload)
if (!isValid) return
hideAddVarModal()
}

View File

@ -11,8 +11,12 @@ import {
useWorkflow,
} from '@/app/components/workflow/hooks'
import useInspectVarsCrud from '../../hooks/use-inspect-vars-crud'
import { hasDuplicateStr } from '@/utils/var'
import Toast from '@/app/components/base/toast'
import { useTranslation } from 'react-i18next'
const useConfig = (id: string, payload: StartNodeType) => {
const { t } = useTranslation()
const { nodesReadOnly: readOnly } = useNodesReadOnly()
const { handleOutVarRenameChange, isVarUsedInNodes, removeUsedVarInNodes } = useWorkflow()
const isChatMode = useIsChatMode()
@ -80,7 +84,27 @@ const useConfig = (id: string, payload: StartNodeType) => {
const newInputs = produce(inputs, (draft: StartNodeType) => {
draft.variables.push(payload)
})
const newList = newInputs.variables
let errorMsgKey = ''
let typeName = ''
if(hasDuplicateStr(newList.map(item => item.variable))) {
errorMsgKey = 'appDebug.varKeyError.keyAlreadyExists'
typeName = 'appDebug.variableConfig.varName'
}
else if(hasDuplicateStr(newList.map(item => item.label as string))) {
errorMsgKey = 'appDebug.varKeyError.keyAlreadyExists'
typeName = 'appDebug.variableConfig.labelName'
}
if (errorMsgKey) {
Toast.notify({
type: 'error',
message: t(errorMsgKey, { key: t(typeName) }),
})
return false
}
setInputs(newInputs)
return true
}, [inputs, setInputs])
return {
readOnly,

View File

@ -65,7 +65,6 @@ const useConfig = (id: string, payload: TemplateTransformNodeType) => {
...defaultConfig,
})
}
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [defaultConfig])
const handleCodeChange = useCallback((template: string) => {
@ -76,7 +75,7 @@ const useConfig = (id: string, payload: TemplateTransformNodeType) => {
}, [setInputs])
const filterVar = useCallback((varPayload: Var) => {
return [VarType.string, VarType.number, VarType.object, VarType.array, VarType.arrayNumber, VarType.arrayString, VarType.arrayObject].includes(varPayload.type)
return [VarType.string, VarType.number, VarType.boolean, VarType.object, VarType.array, VarType.arrayNumber, VarType.arrayString, VarType.arrayBoolean, VarType.arrayObject].includes(varPayload.type)
}, [])
return {

View File

@ -132,7 +132,6 @@ export const useGetAvailableVars = () => {
if (!currentNode)
return []
const beforeNodes = getBeforeNodesInSameBranchIncludeParent(nodeId)
availableNodes.push(...beforeNodes)
const parentNode = nodes.find(node => node.id === currentNode.parentId)
@ -143,7 +142,7 @@ export const useGetAvailableVars = () => {
beforeNodes: uniqBy(availableNodes, 'id').filter(node => node.id !== nodeId),
isChatMode,
hideEnv,
hideChatVar: hideEnv,
hideChatVar: false,
filterVar,
})
.map(node => ({