diff --git a/web/app/components/workflow/nodes/trigger-webhook/components/generic-table.tsx b/web/app/components/workflow/nodes/trigger-webhook/components/generic-table.tsx index 2b784a28a2..31a29121ac 100644 --- a/web/app/components/workflow/nodes/trigger-webhook/components/generic-table.tsx +++ b/web/app/components/workflow/nodes/trigger-webhook/components/generic-table.tsx @@ -8,6 +8,11 @@ import { SimpleSelect } from '@/app/components/base/select' import { replaceSpaceWithUnderscoreInVarNameInput } from '@/utils/var' import cn from '@/utils/classnames' +// Tiny utility to judge whether a cell value is effectively present +const isPresent = (v: unknown): boolean => { + if (typeof v === 'string') return v.trim() !== '' + return !(v === '' || v === null || v === undefined || v === false) +} // Column configuration types for table components export type ColumnType = 'input' | 'select' | 'switch' | 'custom' @@ -120,6 +125,11 @@ const GenericTable: FC = ({ onChange(next) }, [data, emptyRowData, onChange, readonly]) + // Determine the primary identifier column just once + const primaryKey = useMemo(() => ( + columns.find(col => col.key === 'key' || col.key === 'name')?.key ?? 'key' + ), [columns]) + const renderCell = (column: ColumnConfig, row: GenericTableRow, dataIndex: number | null) => { const value = row[column.key] const handleChange = (newValue: unknown) => updateRow(dataIndex, column.key, newValue) @@ -218,8 +228,8 @@ const GenericTable: FC = ({ const rowKey = `row-${renderIndex}` // Check if primary identifier column has content - const primaryColumn = columns.find(col => col.key === 'key' || col.key === 'name')?.key || 'key' - const hasContent = row[primaryColumn] && String(row[primaryColumn]).trim() !== '' + const primaryValue = row[primaryKey] + const hasContent = isPresent(primaryValue) return (
{ case 'boolean': case 'array': case 'object': + case 'file': return type default: return 'string' @@ -27,6 +28,7 @@ type ParameterTableProps = { placeholder?: string showType?: boolean isRequestBody?: boolean // Special handling for request body parameters + contentType?: string } const ParameterTable: FC = ({ @@ -37,17 +39,63 @@ const ParameterTable: FC = ({ placeholder, showType = true, isRequestBody = false, + contentType, }) => { const { t } = useTranslation() - // Type options for request body parameters - const typeOptions = [ - { name: 'String', value: 'string' }, - { name: 'Number', value: 'number' }, - { name: 'Boolean', value: 'boolean' }, - { name: 'Array', value: 'array' }, - { name: 'Object', value: 'object' }, - ] + const resolveTypeOptions = (): { name: string; value: ParameterType }[] => { + if (!isRequestBody) { + return [ + { name: 'String', value: 'string' }, + ] + } + + const ct = (contentType || '').toLowerCase() + // application/json -> all JSON-friendly types + if (ct === 'application/json') { + return [ + { name: 'String', value: 'string' }, + { name: 'Number', value: 'number' }, + { name: 'Boolean', value: 'boolean' }, + { name: 'Array', value: 'array' }, + { name: 'Object', value: 'object' }, + ] + } + // text/plain -> plain text only + if (ct === 'text/plain') { + return [ + { name: 'String', value: 'string' }, + ] + } + // x-www-form-urlencoded and forms -> simple key-value pairs + if (ct === 'application/x-www-form-urlencoded' || ct === 'forms') { + return [ + { name: 'String', value: 'string' }, + { name: 'Number', value: 'number' }, + { name: 'Boolean', value: 'boolean' }, + ] + } + // multipart/form-data -> allow file additionally + if (ct === 'multipart/form-data') { + return [ + { name: 'String', value: 'string' }, + { name: 'Number', value: 'number' }, + { name: 'Boolean', value: 'boolean' }, + { name: 'File', value: 'file' }, + ] + } + + // Fallback: all json-like types + return [ + { name: 'String', value: 'string' }, + { name: 'Number', value: 'number' }, + { name: 'Boolean', value: 'boolean' }, + { name: 'Array', value: 'array' }, + { name: 'Object', value: 'object' }, + ] + } + + const typeOptions = resolveTypeOptions() // Define columns based on component type - matching prototype design const columns: ColumnConfig[] = [ @@ -76,10 +124,16 @@ const ParameterTable: FC = ({ }, ] + // Choose sensible default type for new rows according to content type + const defaultTypeValue = ((): ParameterType => { + const first = typeOptions[0]?.value + return first || 'string' + })() + // Empty row template for new rows const emptyRowData: GenericTableRow = { key: '', - type: isRequestBody ? 'string' : '', + type: isRequestBody ? defaultTypeValue : '', required: false, } @@ -90,13 +144,21 @@ const ParameterTable: FC = ({ })) const handleDataChange = (data: GenericTableRow[]) => { - const newParams: WebhookParameter[] = data + // For text/plain, enforce single text body semantics: keep only first non-empty row and force string type + const isTextPlain = isRequestBody && (contentType || '').toLowerCase() === 'text/plain' + + const normalized = data .filter(row => typeof row.key === 'string' && (row.key as string).trim() !== '') .map(row => ({ name: String(row.key), - type: normalizeParamType((row.type as string) || 'string'), + type: isTextPlain ? 'string' : normalizeParamType((row.type as string) || 'string'), required: Boolean(row.required), })) + + const newParams: WebhookParameter[] = isTextPlain + ? normalized.slice(0, 1) + : normalized + onChange(newParams) } diff --git a/web/app/components/workflow/nodes/trigger-webhook/panel.tsx b/web/app/components/workflow/nodes/trigger-webhook/panel.tsx index 77352e90a8..1dc979b7a9 100644 --- a/web/app/components/workflow/nodes/trigger-webhook/panel.tsx +++ b/web/app/components/workflow/nodes/trigger-webhook/panel.tsx @@ -173,6 +173,7 @@ const Panel: FC> = ({ placeholder={t(`${i18nPrefix}.noBodyParameters`)} showType={true} isRequestBody={true} + contentType={inputs['content-type']} /> diff --git a/web/app/components/workflow/nodes/trigger-webhook/types.ts b/web/app/components/workflow/nodes/trigger-webhook/types.ts index f065e670d8..9e06da4f1a 100644 --- a/web/app/components/workflow/nodes/trigger-webhook/types.ts +++ b/web/app/components/workflow/nodes/trigger-webhook/types.ts @@ -4,7 +4,7 @@ import type { ErrorHandleTypeEnum } from '@/app/components/workflow/nodes/_base/ export type HttpMethod = 'GET' | 'POST' | 'PUT' | 'DELETE' | 'PATCH' | 'HEAD' -export type ParameterType = 'string' | 'number' | 'boolean' | 'array' | 'object' +export type ParameterType = 'string' | 'number' | 'boolean' | 'array' | 'object' | 'file' export type WebhookParameter = { name: string