diff --git a/web/app/components/workflow/nodes/_base/components/before-run-form/__tests__/helpers.spec.ts b/web/app/components/workflow/nodes/_base/components/before-run-form/__tests__/helpers.spec.ts
index f4d456b6f6..961a56592a 100644
--- a/web/app/components/workflow/nodes/_base/components/before-run-form/__tests__/helpers.spec.ts
+++ b/web/app/components/workflow/nodes/_base/components/before-run-form/__tests__/helpers.spec.ts
@@ -57,6 +57,16 @@ describe('before-run-form helpers', () => {
values: createValues({ query: '' }),
})], [{}], t)).toContain('errorMsg.fieldRequired')
+ expect(getFormErrorMessage([createForm({
+ inputs: [createInput({ variable: 'file', label: 'File', type: InputVarType.singleFile, required: true })],
+ values: createValues({ file: [] }),
+ })], [{}], t)).toContain('errorMsg.fieldRequired')
+
+ expect(getFormErrorMessage([createForm({
+ inputs: [createInput({ variable: 'files', label: 'Files', type: InputVarType.multiFiles, required: true })],
+ values: createValues({ files: [] }),
+ })], [{}], t)).toContain('errorMsg.fieldRequired')
+
expect(getFormErrorMessage([createForm({
inputs: [createInput({ variable: 'file', label: 'File', type: InputVarType.singleFile })],
values: createValues({ file: { transferMethod: TransferMethod.local_file } }),
diff --git a/web/app/components/workflow/nodes/_base/components/before-run-form/helpers.ts b/web/app/components/workflow/nodes/_base/components/before-run-form/helpers.ts
index 3e5cdf9a74..c0e08f64ec 100644
--- a/web/app/components/workflow/nodes/_base/components/before-run-form/helpers.ts
+++ b/web/app/components/workflow/nodes/_base/components/before-run-form/helpers.ts
@@ -56,7 +56,16 @@ export const getFormErrorMessage = (
const missingRequired = input.required
&& input.type !== InputVarType.checkbox
&& !(input.variable in existVarValuesInForm)
- && (value === '' || value === undefined || value === null || (input.type === InputVarType.files && Array.isArray(value) && value.length === 0))
+ && (
+ value === '' || value === undefined || value === null
+ || (
+ (input.type === InputVarType.files
+ || input.type === InputVarType.multiFiles
+ || input.type === InputVarType.singleFile)
+ && Array.isArray(value)
+ && value.length === 0
+ )
+ )
if (!errMsg && missingRequired) {
errMsg = t('errorMsg.fieldRequired', { ns: 'workflow', field: typeof input.label === 'object' ? input.label.variable : input.label })
diff --git a/web/app/components/workflow/nodes/trigger-webhook/components/__tests__/generic-table.spec.tsx b/web/app/components/workflow/nodes/trigger-webhook/components/__tests__/generic-table.spec.tsx
index 4a4d94d3c3..b49c4dd911 100644
--- a/web/app/components/workflow/nodes/trigger-webhook/components/__tests__/generic-table.spec.tsx
+++ b/web/app/components/workflow/nodes/trigger-webhook/components/__tests__/generic-table.spec.tsx
@@ -3,6 +3,40 @@ import userEvent from '@testing-library/user-event'
import { useState } from 'react'
import GenericTable from '../generic-table'
+vi.mock('@/app/components/base/select', () => ({
+ SimpleSelect: ({
+ items,
+ defaultValue,
+ onSelect,
+ disabled,
+ placeholder,
+ }: {
+ items: Array<{ name: string, value: string }>
+ defaultValue?: string
+ onSelect: (item: { name: string, value: string }) => void
+ disabled?: boolean
+ placeholder?: string
+ }) => (
+
+ ),
+}))
+
const columns = [
{
key: 'name',
@@ -144,12 +178,11 @@ describe('GenericTable', () => {
,
)
- await user.click(screen.getByRole('button', { name: 'Choose method' }))
- await user.click(await screen.findByText('POST'))
+ await user.selectOptions(screen.getAllByRole('combobox', { name: 'Choose method' })[0], 'post')
await waitFor(() => {
expect(onChange).toHaveBeenCalledWith([{ method: 'post', preview: '' }])
- expect(screen.getByRole('button', { name: 'POST' })).toBeInTheDocument()
+ expect(screen.getAllByRole('combobox', { name: 'Choose method' })[0]).toHaveValue('post')
})
onChange.mockClear()
diff --git a/web/app/components/workflow/panel/chat-variable-panel/components/__tests__/use-variable-modal-state.spec.ts b/web/app/components/workflow/panel/chat-variable-panel/components/__tests__/use-variable-modal-state.spec.ts
index 47aeb57ae7..0b9da42961 100644
--- a/web/app/components/workflow/panel/chat-variable-panel/components/__tests__/use-variable-modal-state.spec.ts
+++ b/web/app/components/workflow/panel/chat-variable-panel/components/__tests__/use-variable-modal-state.spec.ts
@@ -158,6 +158,20 @@ describe('useVariableModalState', () => {
expect(result.current.editorContent).toBe(JSON.stringify(['True', 'False']))
})
+ it('should preserve zero values when switching number arrays into json mode', () => {
+ const { result } = renderHook(() => useVariableModalState(createOptions()))
+
+ act(() => {
+ result.current.handleTypeChange(ChatVarType.ArrayNumber)
+ result.current.setValue([0, 2, undefined])
+ result.current.handleEditorChange(true)
+ })
+
+ expect(result.current.editInJSON).toBe(true)
+ expect(result.current.value).toEqual([0, 2])
+ expect(result.current.editorContent).toBe(JSON.stringify([0, 2]))
+ })
+
it('should notify and stop saving when object keys are invalid', () => {
const notify = vi.fn()
const onSave = vi.fn()
diff --git a/web/app/components/workflow/panel/chat-variable-panel/components/__tests__/variable-modal.helpers.spec.ts b/web/app/components/workflow/panel/chat-variable-panel/components/__tests__/variable-modal.helpers.spec.ts
index 2caaf9b90d..1c88d3a63b 100644
--- a/web/app/components/workflow/panel/chat-variable-panel/components/__tests__/variable-modal.helpers.spec.ts
+++ b/web/app/components/workflow/panel/chat-variable-panel/components/__tests__/variable-modal.helpers.spec.ts
@@ -59,6 +59,13 @@ describe('variable-modal helpers', () => {
value: ['a', '', 'b'],
})).toEqual(['a', 'b'])
+ expect(formatChatVariableValue({
+ editInJSON: false,
+ objectValue: [],
+ type: ChatVarType.ArrayNumber,
+ value: [0, 1, undefined, null, ''] as unknown as Array,
+ })).toEqual([0, 1])
+
expect(formatChatVariableValue({
editInJSON: false,
objectValue: [],
@@ -99,6 +106,11 @@ describe('variable-modal helpers', () => {
type: ChatVarType.ArrayBoolean,
})).toEqual([true, false, true, false])
+ expect(() => parseEditorContent({
+ content: '{"enabled":true}',
+ type: ChatVarType.ArrayBoolean,
+ })).toThrow('JSON array')
+
expect(parseEditorContent({
content: '{"enabled":true}',
type: ChatVarType.Object,
diff --git a/web/app/components/workflow/panel/chat-variable-panel/components/__tests__/variable-modal.spec.tsx b/web/app/components/workflow/panel/chat-variable-panel/components/__tests__/variable-modal.spec.tsx
index e61a6bd085..319e3803f4 100644
--- a/web/app/components/workflow/panel/chat-variable-panel/components/__tests__/variable-modal.spec.tsx
+++ b/web/app/components/workflow/panel/chat-variable-panel/components/__tests__/variable-modal.spec.tsx
@@ -100,8 +100,10 @@ describe('variable-modal', () => {
expect(screen.getByDisplayValue('secret')).toBeInTheDocument()
expect(screen.getByDisplayValue('30')).toBeInTheDocument()
+ const timeoutInput = screen.getByDisplayValue('30') as HTMLInputElement
await user.clear(screen.getByDisplayValue('secret'))
- await user.type(screen.getByDisplayValue('30'), '5')
+ await user.clear(timeoutInput)
+ await user.type(timeoutInput, '5')
await user.click(screen.getByText('common.operation.save'))
expect(onSave).toHaveBeenCalledWith({
@@ -110,7 +112,7 @@ describe('variable-modal', () => {
value_type: ChatVarType.Object,
value: {
apiKey: null,
- timeout: 305,
+ timeout: 5,
},
description: 'settings',
})
diff --git a/web/app/components/workflow/panel/chat-variable-panel/components/use-variable-modal-state.ts b/web/app/components/workflow/panel/chat-variable-panel/components/use-variable-modal-state.ts
index 648fdc8eaf..07619029a3 100644
--- a/web/app/components/workflow/panel/chat-variable-panel/components/use-variable-modal-state.ts
+++ b/web/app/components/workflow/panel/chat-variable-panel/components/use-variable-modal-state.ts
@@ -133,8 +133,11 @@ export const useVariableModalState = ({
if (prev.type === ChatVarType.ArrayString || prev.type === ChatVarType.ArrayNumber) {
if (nextEditInJSON) {
- const nextValue = (Array.isArray(prev.value) && prev.value.length && prev.value.filter(Boolean).length)
- ? prev.value.filter(Boolean)
+ const compactValues = Array.isArray(prev.value)
+ ? prev.value.filter(item => item !== null && item !== undefined && item !== '')
+ : []
+ const nextValue = compactValues.length
+ ? compactValues
: undefined
nextState.value = nextValue
if (!prev.editorContent)
diff --git a/web/app/components/workflow/panel/chat-variable-panel/components/variable-modal.helpers.ts b/web/app/components/workflow/panel/chat-variable-panel/components/variable-modal.helpers.ts
index 07369ed1fe..999dc4e2c9 100644
--- a/web/app/components/workflow/panel/chat-variable-panel/components/variable-modal.helpers.ts
+++ b/web/app/components/workflow/panel/chat-variable-panel/components/variable-modal.helpers.ts
@@ -88,6 +88,9 @@ export const formatChatVariableValue = ({
type: ChatVarType
value: unknown
}) => {
+ const compactArrayValue = (items: unknown[]) =>
+ items.filter(item => item !== null && item !== undefined && item !== '')
+
switch (type) {
case ChatVarTypeEnum.String:
return value || ''
@@ -100,7 +103,7 @@ export const formatChatVariableValue = ({
case ChatVarTypeEnum.ArrayString:
case ChatVarTypeEnum.ArrayNumber:
case ChatVarTypeEnum.ArrayObject:
- return Array.isArray(value) ? value.filter(Boolean) : []
+ return Array.isArray(value) ? compactArrayValue(value) : []
case ChatVarTypeEnum.ArrayBoolean:
return value || []
}
@@ -151,6 +154,9 @@ export const parseEditorContent = ({
if (type !== ChatVarTypeEnum.ArrayBoolean)
return parsed
+ if (!Array.isArray(parsed))
+ throw new TypeError('ArrayBoolean editor content must be a JSON array')
+
return parsed
.map((item: string | boolean) => {
if (item === 'True' || item === 'true' || item === true)