feat: support bool type variable frontend (#24437)

Co-authored-by: QuantumGhost <obelisk.reg+git@gmail.com>
This commit is contained in:
Joel
2025-08-26 18:16:05 +08:00
committed by GitHub
parent b5c2756261
commit dac72b078d
126 changed files with 3832 additions and 512 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

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

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

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

View File

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

View File

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

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

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

View File

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

View File

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