feat: support bool type variable frontend (#24437)
Co-authored-by: QuantumGhost <obelisk.reg+git@gmail.com>
This commit is contained in:
parent
b5c2756261
commit
dac72b078d
126 changed files with 3832 additions and 512 deletions
|
|
@ -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 cn from '@/utils/classnames'
|
||||
import type { FileEntity } from '@/app/components/base/file-uploader/types'
|
||||
import BoolInput from './bool-input'
|
||||
|
||||
type Props = {
|
||||
payload: InputVar
|
||||
|
|
@ -92,6 +93,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
|
||||
|
|
@ -113,7 +115,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>}
|
||||
|
|
@ -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 && (
|
||||
<CodeEditor
|
||||
|
|
@ -176,6 +187,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}
|
||||
|
|
|
|||
|
|
@ -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) {
|
||||
|
|
|
|||
|
|
@ -64,7 +64,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
|
||||
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
'use client'
|
||||
import type { FC } from 'react'
|
||||
import React from 'react'
|
||||
import { RiAlignLeft, RiCheckboxMultipleLine, RiFileCopy2Line, RiFileList2Line, RiHashtag, RiTextSnippet } from '@remixicon/react'
|
||||
import { RiAlignLeft, RiBracesLine, RiCheckboxLine, RiCheckboxMultipleLine, RiFileCopy2Line, RiFileList2Line, RiHashtag, RiTextSnippet } from '@remixicon/react'
|
||||
import { InputVarType } from '../../../types'
|
||||
|
||||
type Props = {
|
||||
|
|
@ -15,6 +15,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,
|
||||
} as any)[type] || RiTextSnippet
|
||||
|
|
|
|||
|
|
@ -57,11 +57,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
|
||||
}
|
||||
|
||||
|
|
@ -228,14 +230,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({
|
||||
|
|
@ -690,6 +705,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:
|
||||
|
|
@ -743,6 +760,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:
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
|
|
|
|||
|
|
@ -477,7 +477,7 @@ const BasePanel: FC<BasePanelProps> = ({
|
|||
isRunAfterSingleRun={isRunAfterSingleRun}
|
||||
updateNodeRunningStatus={updateNodeRunningStatus}
|
||||
onSingleRunClicked={handleSingleRun}
|
||||
nodeInfo={nodeInfo}
|
||||
nodeInfo={nodeInfo!}
|
||||
singleRunResult={runResult!}
|
||||
isPaused={isPaused}
|
||||
{...passedLogParams}
|
||||
|
|
|
|||
|
|
@ -67,7 +67,6 @@ const LastRun: FC<Props> = ({
|
|||
updateNodeRunningStatus(hidePageOneStepFinishedStatus)
|
||||
resetHidePageStatus()
|
||||
}
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, [isOneStepRunSucceed, isOneStepRunFailed, oneStepRunRunningStatus])
|
||||
|
||||
useEffect(() => {
|
||||
|
|
@ -77,7 +76,6 @@ const LastRun: FC<Props> = ({
|
|||
|
||||
useEffect(() => {
|
||||
resetHidePageStatus()
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, [nodeId])
|
||||
|
||||
const handlePageVisibilityChange = useCallback(() => {
|
||||
|
|
@ -117,7 +115,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>
|
||||
|
|
|
|||
|
|
@ -146,8 +146,8 @@ const useLastRun = <T>({
|
|||
checkValid,
|
||||
} = oneStepRunRes
|
||||
|
||||
const nodeInfo = runResult
|
||||
const {
|
||||
nodeInfo,
|
||||
...singleRunParams
|
||||
} = useSingleRunFormParamsHooks(blockType)({
|
||||
id,
|
||||
|
|
@ -197,7 +197,6 @@ const useLastRun = <T>({
|
|||
setTabType(TabType.lastRun)
|
||||
|
||||
setInitShowLastRunTab(false)
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, [initShowLastRunTab])
|
||||
const invalidLastRun = useInvalidLastRun(appId!, id)
|
||||
|
||||
|
|
|
|||
|
|
@ -94,6 +94,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)
|
||||
|
|
@ -185,11 +187,11 @@ const useOneStepRun = <T>({
|
|||
const isPaused = isPausedRef.current
|
||||
|
||||
// The backend don't support pause the single run, so the frontend handle the pause state.
|
||||
if(isPaused)
|
||||
if (isPaused)
|
||||
return
|
||||
|
||||
const canRunLastRun = !isRunAfterSingleRun || runningStatus === NodeRunningStatus.Succeeded
|
||||
if(!canRunLastRun) {
|
||||
if (!canRunLastRun) {
|
||||
doSetRunResult(data)
|
||||
return
|
||||
}
|
||||
|
|
@ -199,9 +201,9 @@ const useOneStepRun = <T>({
|
|||
const { getNodes } = store.getState()
|
||||
const nodes = getNodes()
|
||||
appendNodeInspectVars(id, vars, nodes)
|
||||
if(data?.status === NodeRunningStatus.Succeeded) {
|
||||
if (data?.status === NodeRunningStatus.Succeeded) {
|
||||
invalidLastRun()
|
||||
if(isStartNode)
|
||||
if (isStartNode)
|
||||
invalidateSysVarValues()
|
||||
invalidateConversationVarValues() // loop, iteration, variable assigner node can update the conversation variables, but to simple the logic(some nodes may also can update in the future), all nodes refresh.
|
||||
}
|
||||
|
|
@ -218,21 +220,21 @@ const useOneStepRun = <T>({
|
|||
})
|
||||
}
|
||||
const checkValidWrap = () => {
|
||||
if(!checkValid)
|
||||
if (!checkValid)
|
||||
return { isValid: true, errorMessage: '' }
|
||||
const res = checkValid(data, t, moreDataForCheckValid)
|
||||
if(!res.isValid) {
|
||||
handleNodeDataUpdate({
|
||||
id,
|
||||
data: {
|
||||
...data,
|
||||
_isSingleRun: false,
|
||||
},
|
||||
})
|
||||
Toast.notify({
|
||||
type: 'error',
|
||||
message: res.errorMessage,
|
||||
})
|
||||
if (!res.isValid) {
|
||||
handleNodeDataUpdate({
|
||||
id,
|
||||
data: {
|
||||
...data,
|
||||
_isSingleRun: false,
|
||||
},
|
||||
})
|
||||
Toast.notify({
|
||||
type: 'error',
|
||||
message: res.errorMessage,
|
||||
})
|
||||
}
|
||||
return res
|
||||
}
|
||||
|
|
@ -251,7 +253,6 @@ const useOneStepRun = <T>({
|
|||
const { isValid } = checkValidWrap()
|
||||
setCanShowSingleRun(isValid)
|
||||
}
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, [data._isSingleRun])
|
||||
|
||||
useEffect(() => {
|
||||
|
|
@ -293,9 +294,9 @@ const useOneStepRun = <T>({
|
|||
if (!isIteration && !isLoop) {
|
||||
const isStartNode = data.type === BlockEnum.Start
|
||||
const postData: Record<string, any> = {}
|
||||
if(isStartNode) {
|
||||
if (isStartNode) {
|
||||
const { '#sys.query#': query, '#sys.files#': files, ...inputs } = submitData
|
||||
if(isChatMode)
|
||||
if (isChatMode)
|
||||
postData.conversation_id = ''
|
||||
|
||||
postData.inputs = inputs
|
||||
|
|
@ -317,7 +318,7 @@ const useOneStepRun = <T>({
|
|||
{
|
||||
onWorkflowStarted: noop,
|
||||
onWorkflowFinished: (params) => {
|
||||
if(isPausedRef.current)
|
||||
if (isPausedRef.current)
|
||||
return
|
||||
handleNodeDataUpdate({
|
||||
id,
|
||||
|
|
@ -396,7 +397,7 @@ const useOneStepRun = <T>({
|
|||
setIterationRunResult(newIterationRunResult)
|
||||
},
|
||||
onError: () => {
|
||||
if(isPausedRef.current)
|
||||
if (isPausedRef.current)
|
||||
return
|
||||
handleNodeDataUpdate({
|
||||
id,
|
||||
|
|
@ -420,7 +421,7 @@ const useOneStepRun = <T>({
|
|||
{
|
||||
onWorkflowStarted: noop,
|
||||
onWorkflowFinished: (params) => {
|
||||
if(isPausedRef.current)
|
||||
if (isPausedRef.current)
|
||||
return
|
||||
handleNodeDataUpdate({
|
||||
id,
|
||||
|
|
@ -500,7 +501,7 @@ const useOneStepRun = <T>({
|
|||
setLoopRunResult(newLoopRunResult)
|
||||
},
|
||||
onError: () => {
|
||||
if(isPausedRef.current)
|
||||
if (isPausedRef.current)
|
||||
return
|
||||
handleNodeDataUpdate({
|
||||
id,
|
||||
|
|
@ -522,7 +523,7 @@ const useOneStepRun = <T>({
|
|||
hasError = true
|
||||
invalidLastRun()
|
||||
if (!isIteration && !isLoop) {
|
||||
if(isPausedRef.current)
|
||||
if (isPausedRef.current)
|
||||
return
|
||||
handleNodeDataUpdate({
|
||||
id,
|
||||
|
|
@ -544,11 +545,11 @@ const useOneStepRun = <T>({
|
|||
})
|
||||
}
|
||||
}
|
||||
if(isPausedRef.current)
|
||||
if (isPausedRef.current)
|
||||
return
|
||||
|
||||
if (!isIteration && !isLoop && !hasError) {
|
||||
if(isPausedRef.current)
|
||||
if (isPausedRef.current)
|
||||
return
|
||||
handleNodeDataUpdate({
|
||||
id,
|
||||
|
|
@ -587,7 +588,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,
|
||||
|
|
|
|||
|
|
@ -13,7 +13,6 @@ const useToggleExpend = ({ ref, hasFooter = true, isInNode }: Params) => {
|
|||
useEffect(() => {
|
||||
if (!ref?.current) return
|
||||
setWrapHeight(ref.current?.clientHeight)
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, [isExpand])
|
||||
|
||||
const wrapClassName = (() => {
|
||||
|
|
|
|||
|
|
@ -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}
|
||||
|
|
|
|||
|
|
@ -33,7 +33,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) {
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
|
|
|
|||
|
|
@ -60,7 +60,6 @@ const useConfig = (id: string, payload: CodeNodeType) => {
|
|||
})
|
||||
syncOutputKeyOrders(defaultConfig.outputs)
|
||||
}
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, [defaultConfig])
|
||||
|
||||
const handleCodeChange = useCallback((code: string) => {
|
||||
|
|
@ -85,7 +84,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) {
|
||||
|
|
@ -159,7 +158,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) => {
|
||||
|
|
|
|||
|
|
@ -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'
|
||||
|
|
@ -142,12 +143,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])
|
||||
|
|
@ -203,8 +204,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,
|
||||
})
|
||||
|
|
@ -212,7 +217,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()))
|
||||
})
|
||||
|
|
@ -220,6 +225,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(
|
||||
|
|
@ -273,7 +286,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}
|
||||
|
|
@ -285,6 +298,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]'>
|
||||
|
|
|
|||
|
|
@ -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))
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
import { BlockEnum, type NodeDefault } from '../../types'
|
||||
import { BlockEnum, type NodeDefault, VarType } from '../../types'
|
||||
import { type IfElseNodeType, LogicalOperator } from './types'
|
||||
import { isEmptyRelatedOperator } from './utils'
|
||||
import { ALL_CHAT_AVAILABLE_BLOCKS, ALL_COMPLETION_AVAILABLE_BLOCKS } from '@/app/components/workflow/blocks'
|
||||
|
|
@ -58,13 +58,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`) })
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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}
|
||||
/>
|
||||
)
|
||||
|
||||
|
|
|
|||
|
|
@ -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
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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 : '',
|
||||
})
|
||||
}
|
||||
})
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
|
|
|
|||
|
|
@ -25,7 +25,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) => {
|
||||
|
|
|
|||
|
|
@ -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}
|
||||
|
|
|
|||
|
|
@ -38,7 +38,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') })
|
||||
|
|
@ -51,7 +51,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') })
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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 & {
|
||||
|
|
|
|||
|
|
@ -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) => {
|
||||
|
|
|
|||
|
|
@ -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))
|
||||
|
|
|
|||
|
|
@ -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> = ({
|
||||
|
|
|
|||
|
|
@ -303,6 +303,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
|
||||
|
|
|
|||
|
|
@ -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]'>
|
||||
|
|
|
|||
|
|
@ -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>
|
||||
)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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) => {
|
||||
|
|
|
|||
|
|
@ -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 (
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
import { BlockEnum } from '../../types'
|
||||
import { BlockEnum, VarType } from '../../types'
|
||||
import type { NodeDefault } from '../../types'
|
||||
import { ComparisonOperator, LogicalOperator, type LoopNodeType } from './types'
|
||||
import { isEmptyRelatedOperator } from './utils'
|
||||
|
|
@ -55,7 +55,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`) })
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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
|
||||
}
|
||||
|
|
|
|||
|
|
@ -63,7 +63,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)
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
|
|
|
|||
|
|
@ -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 = {
|
||||
|
|
|
|||
|
|
@ -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 (
|
||||
|
|
|
|||
|
|
@ -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])
|
||||
|
||||
|
|
|
|||
|
|
@ -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()
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
|
|
|
|||
|
|
@ -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 {
|
||||
|
|
|
|||
|
|
@ -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 => ({
|
||||
|
|
|
|||
|
|
@ -0,0 +1,72 @@
|
|||
'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'
|
||||
import cn from '@/utils/classnames'
|
||||
|
||||
type Props = {
|
||||
className?: string
|
||||
list: boolean[]
|
||||
onChange: (list: boolean[]) => void
|
||||
}
|
||||
|
||||
const ArrayValueList: FC<Props> = ({
|
||||
className,
|
||||
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(false)
|
||||
})
|
||||
onChange(newList)
|
||||
}, [list, onChange])
|
||||
|
||||
return (
|
||||
<div className={cn('w-full space-y-2', className)}>
|
||||
{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,37 @@
|
|||
'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 booleanValue = value
|
||||
const handleChange = useCallback((newValue: boolean) => {
|
||||
return () => {
|
||||
onChange(newValue)
|
||||
}
|
||||
}, [onChange])
|
||||
|
||||
return (
|
||||
<div className='flex w-full space-x-1'>
|
||||
<OptionCard className='grow'
|
||||
selected={booleanValue}
|
||||
title='True'
|
||||
onSelect={handleChange(true)}
|
||||
/>
|
||||
<OptionCard className='grow'
|
||||
selected={!booleanValue}
|
||||
title='False'
|
||||
onSelect={handleChange(false)}
|
||||
/>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
export default React.memo(BoolValue)
|
||||
|
|
@ -16,6 +16,15 @@ import type { ConversationVariable } from '@/app/components/workflow/types'
|
|||
import { CodeLanguage } from '@/app/components/workflow/nodes/code/types'
|
||||
import { ChatVarType } from '@/app/components/workflow/panel/chat-variable-panel/type'
|
||||
import cn from '@/utils/classnames'
|
||||
import 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'
|
||||
import { checkKeys, replaceSpaceWithUnderscoreInVarNameInput } from '@/utils/var'
|
||||
|
||||
export type ModalPropsType = {
|
||||
|
|
@ -33,39 +42,14 @@ type ObjectValueItem = {
|
|||
const typeList = [
|
||||
ChatVarType.String,
|
||||
ChatVarType.Number,
|
||||
ChatVarType.Boolean,
|
||||
ChatVarType.Object,
|
||||
ChatVarType.ArrayString,
|
||||
ChatVarType.ArrayNumber,
|
||||
ChatVarType.ArrayBoolean,
|
||||
ChatVarType.ArrayObject,
|
||||
]
|
||||
|
||||
const objectPlaceholder = `# example
|
||||
# {
|
||||
# "name": "ray",
|
||||
# "age": 20
|
||||
# }`
|
||||
const arrayStringPlaceholder = `# example
|
||||
# [
|
||||
# "value1",
|
||||
# "value2"
|
||||
# ]`
|
||||
const arrayNumberPlaceholder = `# example
|
||||
# [
|
||||
# 100,
|
||||
# 200
|
||||
# ]`
|
||||
const arrayObjectPlaceholder = `# example
|
||||
# [
|
||||
# {
|
||||
# "name": "ray",
|
||||
# "age": 20
|
||||
# },
|
||||
# {
|
||||
# "name": "lily",
|
||||
# "age": 18
|
||||
# }
|
||||
# ]`
|
||||
|
||||
const ChatVariableModal = ({
|
||||
chatVar,
|
||||
onClose,
|
||||
|
|
@ -94,6 +78,8 @@ const ChatVariableModal = ({
|
|||
return arrayNumberPlaceholder
|
||||
if (type === ChatVarType.ArrayObject)
|
||||
return arrayObjectPlaceholder
|
||||
if (type === ChatVarType.ArrayBoolean)
|
||||
return arrayBoolPlaceholder
|
||||
return objectPlaceholder
|
||||
}, [type])
|
||||
const getObjectValue = useCallback(() => {
|
||||
|
|
@ -122,12 +108,16 @@ const ChatVariableModal = ({
|
|||
return value || ''
|
||||
case ChatVarType.Number:
|
||||
return value || 0
|
||||
case ChatVarType.Boolean:
|
||||
return value === undefined ? true : value
|
||||
case ChatVarType.Object:
|
||||
return editInJSON ? value : formatValueFromObject(objectValue)
|
||||
case ChatVarType.ArrayString:
|
||||
case ChatVarType.ArrayNumber:
|
||||
case ChatVarType.ArrayObject:
|
||||
return value?.filter(Boolean) || []
|
||||
case ChatVarType.ArrayBoolean:
|
||||
return value || []
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -157,6 +147,10 @@ const ChatVariableModal = ({
|
|||
setEditInJSON(true)
|
||||
if (v === ChatVarType.String || v === ChatVarType.Number || v === ChatVarType.Object)
|
||||
setEditInJSON(false)
|
||||
if(v === ChatVarType.Boolean)
|
||||
setValue(false)
|
||||
if (v === ChatVarType.ArrayBoolean)
|
||||
setValue([false])
|
||||
setType(v)
|
||||
}
|
||||
|
||||
|
|
@ -202,6 +196,11 @@ const ChatVariableModal = ({
|
|||
setValue(value?.length ? value : [undefined])
|
||||
}
|
||||
}
|
||||
|
||||
if(type === ChatVarType.ArrayBoolean) {
|
||||
if(editInJSON)
|
||||
setEditorContent(JSON.stringify(value.map((item: boolean) => item ? 'True' : 'False')))
|
||||
}
|
||||
setEditInJSON(editInJSON)
|
||||
}
|
||||
|
||||
|
|
@ -213,7 +212,16 @@ const ChatVariableModal = ({
|
|||
else {
|
||||
setEditorContent(content)
|
||||
try {
|
||||
const newValue = JSON.parse(content)
|
||||
let newValue = JSON.parse(content)
|
||||
if(type === ChatVarType.ArrayBoolean) {
|
||||
newValue = newValue.map((item: string | boolean) => {
|
||||
if (item === 'True' || item === 'true' || item === true)
|
||||
return true
|
||||
if (item === 'False' || item === 'false' || item === false)
|
||||
return false
|
||||
return undefined
|
||||
}).filter((item?: boolean) => item !== undefined)
|
||||
}
|
||||
setValue(newValue)
|
||||
}
|
||||
catch {
|
||||
|
|
@ -304,7 +312,7 @@ const ChatVariableModal = ({
|
|||
<div className='mb-4'>
|
||||
<div className='system-sm-semibold mb-1 flex h-6 items-center justify-between text-text-secondary'>
|
||||
<div>{t('workflow.chatVariable.modal.value')}</div>
|
||||
{(type === ChatVarType.ArrayString || type === ChatVarType.ArrayNumber) && (
|
||||
{(type === ChatVarType.ArrayString || type === ChatVarType.ArrayNumber || type === ChatVarType.ArrayBoolean) && (
|
||||
<Button
|
||||
variant='ghost'
|
||||
size='small'
|
||||
|
|
@ -345,6 +353,12 @@ const ChatVariableModal = ({
|
|||
type='number'
|
||||
/>
|
||||
)}
|
||||
{type === ChatVarType.Boolean && (
|
||||
<BoolValue
|
||||
value={value}
|
||||
onChange={setValue}
|
||||
/>
|
||||
)}
|
||||
{type === ChatVarType.Object && !editInJSON && (
|
||||
<ObjectValueList
|
||||
list={objectValue}
|
||||
|
|
@ -365,6 +379,13 @@ const ChatVariableModal = ({
|
|||
onChange={setValue}
|
||||
/>
|
||||
)}
|
||||
{type === ChatVarType.ArrayBoolean && !editInJSON && (
|
||||
<ArrayBoolList
|
||||
list={value || [true]}
|
||||
onChange={setValue}
|
||||
/>
|
||||
)}
|
||||
|
||||
{editInJSON && (
|
||||
<div className='w-full rounded-[10px] bg-components-input-bg-normal py-2 pl-3 pr-1' style={{ height: editorMinHeight }}>
|
||||
<CodeEditor
|
||||
|
|
|
|||
|
|
@ -1,8 +1,10 @@
|
|||
export enum ChatVarType {
|
||||
Number = 'number',
|
||||
String = 'string',
|
||||
Boolean = 'boolean',
|
||||
Object = 'object',
|
||||
ArrayString = 'array[string]',
|
||||
ArrayNumber = 'array[number]',
|
||||
ArrayBoolean = 'array[boolean]',
|
||||
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"
|
||||
# ]`
|
||||
|
|
@ -180,9 +180,11 @@ export enum InputVarType {
|
|||
paragraph = 'paragraph',
|
||||
select = 'select',
|
||||
number = 'number',
|
||||
checkbox = 'checkbox',
|
||||
url = 'url',
|
||||
files = 'files',
|
||||
json = 'json', // obj, array
|
||||
jsonObject = 'json_object', // only object support define json schema
|
||||
contexts = 'contexts', // knowledge retrieval
|
||||
iterator = 'iterator', // iteration input
|
||||
singleFile = 'file',
|
||||
|
|
@ -208,6 +210,7 @@ export type InputVar = {
|
|||
getVarValueFromDependent?: boolean
|
||||
hide?: boolean
|
||||
isFileItem?: boolean
|
||||
json_schema?: string // for jsonObject type
|
||||
} & Partial<UploadFileSetting>
|
||||
|
||||
export type ModelConfig = {
|
||||
|
|
@ -266,6 +269,7 @@ export enum VarType {
|
|||
arrayString = 'array[string]',
|
||||
arrayNumber = 'array[number]',
|
||||
arrayObject = 'array[object]',
|
||||
arrayBoolean = 'array[boolean]',
|
||||
arrayFile = 'array[file]',
|
||||
any = 'any',
|
||||
arrayAny = 'array[any]',
|
||||
|
|
|
|||
|
|
@ -21,6 +21,7 @@ import { SupportUploadFileTypes } from '@/app/components/workflow/types'
|
|||
import type { VarInInspect } from '@/types/workflow'
|
||||
import { VarInInspectType } from '@/types/workflow'
|
||||
import cn from '@/utils/classnames'
|
||||
import BoolValue from '../panel/chat-variable-panel/components/bool-value'
|
||||
|
||||
type Props = {
|
||||
currentVar: VarInInspect
|
||||
|
|
@ -35,6 +36,8 @@ const ValueContent = ({
|
|||
const errorMessageRef = useRef<HTMLDivElement>(null)
|
||||
const [editorHeight, setEditorHeight] = useState(0)
|
||||
const showTextEditor = currentVar.value_type === 'secret' || currentVar.value_type === 'string' || currentVar.value_type === 'number'
|
||||
const showBoolEditor = typeof currentVar.value === 'boolean'
|
||||
const showBoolArrayEditor = Array.isArray(currentVar.value) && currentVar.value.every(v => typeof v === 'boolean')
|
||||
const isSysFiles = currentVar.type === VarInInspectType.system && currentVar.name === 'files'
|
||||
const showJSONEditor = !isSysFiles && (currentVar.value_type === 'object' || currentVar.value_type === 'array[string]' || currentVar.value_type === 'array[number]' || currentVar.value_type === 'array[object]' || currentVar.value_type === 'array[any]')
|
||||
const showFileEditor = isSysFiles || currentVar.value_type === 'file' || currentVar.value_type === 'array[file]'
|
||||
|
|
@ -72,7 +75,6 @@ const ValueContent = ({
|
|||
|
||||
if (showFileEditor)
|
||||
setFileValue(formatFileValue(currentVar))
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, [currentVar.id, currentVar.value])
|
||||
|
||||
const handleTextChange = (value: string) => {
|
||||
|
|
@ -178,6 +180,35 @@ const ValueContent = ({
|
|||
onChange={e => handleTextChange(e.target.value)}
|
||||
/>
|
||||
)}
|
||||
{showBoolEditor && (
|
||||
<div className='w-[295px]'>
|
||||
<BoolValue
|
||||
value={currentVar.value as boolean}
|
||||
onChange={(newValue) => {
|
||||
setValue(newValue)
|
||||
debounceValueChange(currentVar.id, newValue)
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
{
|
||||
showBoolArrayEditor && (
|
||||
<div className='w-[295px] space-y-1'>
|
||||
{currentVar.value.map((v: boolean, i: number) => (
|
||||
<BoolValue
|
||||
key={i}
|
||||
value={v}
|
||||
onChange={(newValue) => {
|
||||
const newArray = [...(currentVar.value as boolean[])]
|
||||
newArray[i] = newValue
|
||||
setValue(newArray)
|
||||
debounceValueChange(currentVar.id, newArray)
|
||||
}}
|
||||
/>
|
||||
))}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
{showJSONEditor && (
|
||||
<SchemaEditor
|
||||
readonly={JSONEditorDisabled}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue