Skip to content

Commit f3f3605

Browse files
committed
feat: add object/array body param types and per-option show/hide on dropdowns
- Add object, array[string/number/boolean/object] as webhook body param types, available when content type is application/json - Extend options fields with show/hide conditions so individual dropdown choices can be hidden based on other param values
1 parent 2105ad5 commit f3f3605

8 files changed

Lines changed: 264 additions & 99 deletions

File tree

packages/agentflow/src/core/types/node.ts

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -84,7 +84,17 @@ export interface InputParam {
8484
type: string
8585
default?: unknown
8686
optional?: boolean
87-
options?: Array<{ label: string; name: string; description?: string; client?: Array<ClientType> } | string>
87+
options?: Array<
88+
| {
89+
label: string
90+
name: string
91+
description?: string
92+
client?: Array<ClientType>
93+
show?: Record<string, unknown>
94+
hide?: Record<string, unknown>
95+
}
96+
| string
97+
>
8898
placeholder?: string
8999
rows?: number
90100
description?: string

packages/agentflow/src/core/utils/fieldVisibility.test.ts

Lines changed: 98 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -194,6 +194,104 @@ describe('evaluateFieldVisibility', () => {
194194
expect(params[0].display).toBeUndefined()
195195
expect(params[1].display).toBeUndefined()
196196
})
197+
198+
describe('option-level show/hide filtering', () => {
199+
it('removes options whose hide condition matches', () => {
200+
const param = makeParam({
201+
type: 'options',
202+
options: [
203+
{ label: 'String', name: 'string' },
204+
{ label: 'Object', name: 'object', hide: { contentType: 'application/x-www-form-urlencoded' } }
205+
] as any
206+
})
207+
208+
const result = evaluateFieldVisibility([param], { contentType: 'application/x-www-form-urlencoded' })
209+
expect(result[0].options).toHaveLength(1)
210+
expect(result[0].options![0]).toMatchObject({ name: 'string' })
211+
})
212+
213+
it('keeps options whose hide condition does not match', () => {
214+
const param = makeParam({
215+
type: 'options',
216+
options: [
217+
{ label: 'String', name: 'string' },
218+
{ label: 'Object', name: 'object', hide: { contentType: 'application/x-www-form-urlencoded' } }
219+
] as any
220+
})
221+
222+
const result = evaluateFieldVisibility([param], { contentType: 'application/json' })
223+
expect(result[0].options).toHaveLength(2)
224+
})
225+
226+
it('removes options whose show condition does not match', () => {
227+
const param = makeParam({
228+
type: 'options',
229+
options: [
230+
{ label: 'Basic', name: 'basic' },
231+
{ label: 'Advanced', name: 'advanced', show: { mode: 'expert' } }
232+
] as any
233+
})
234+
235+
const result = evaluateFieldVisibility([param], { mode: 'beginner' })
236+
expect(result[0].options).toHaveLength(1)
237+
expect(result[0].options![0]).toMatchObject({ name: 'basic' })
238+
})
239+
240+
it('keeps options whose show condition matches', () => {
241+
const param = makeParam({
242+
type: 'options',
243+
options: [
244+
{ label: 'Basic', name: 'basic' },
245+
{ label: 'Advanced', name: 'advanced', show: { mode: 'expert' } }
246+
] as any
247+
})
248+
249+
const result = evaluateFieldVisibility([param], { mode: 'expert' })
250+
expect(result[0].options).toHaveLength(2)
251+
})
252+
253+
it('passes through string options unchanged', () => {
254+
const param = makeParam({
255+
type: 'options',
256+
options: ['one', 'two', 'three'] as any
257+
})
258+
259+
const result = evaluateFieldVisibility([param], {})
260+
expect(result[0].options).toHaveLength(3)
261+
})
262+
263+
it('passes through options with no show/hide unchanged', () => {
264+
const param = makeParam({
265+
type: 'options',
266+
options: [
267+
{ label: 'A', name: 'a' },
268+
{ label: 'B', name: 'b' }
269+
] as any
270+
})
271+
272+
const result = evaluateFieldVisibility([param], {})
273+
expect(result[0].options).toHaveLength(2)
274+
})
275+
276+
it('does not mutate the original options array', () => {
277+
const options = [
278+
{ label: 'String', name: 'string' },
279+
{ label: 'Object', name: 'object', hide: { contentType: 'application/x-www-form-urlencoded' } }
280+
] as any
281+
const param = makeParam({ type: 'options', options })
282+
283+
evaluateFieldVisibility([param], { contentType: 'application/x-www-form-urlencoded' })
284+
285+
// Original options array is untouched
286+
expect(options).toHaveLength(2)
287+
})
288+
289+
it('does not affect non-options params', () => {
290+
const param = makeParam({ type: 'string' })
291+
const result = evaluateFieldVisibility([param], {})
292+
expect(result[0].options).toBeUndefined()
293+
})
294+
})
197295
})
198296

199297
describe('evaluateFieldVisibility – nested array $index pattern (Start node formInputTypes)', () => {

packages/agentflow/src/core/utils/fieldVisibility.ts

Lines changed: 18 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -129,13 +129,27 @@ export function evaluateParamVisibility(param: InputParam, inputValues: Record<s
129129

130130
/**
131131
* Evaluate visibility for all params, returning new param objects with computed `display`.
132+
* Also filters individual options within `type: 'options'` params based on their own show/hide conditions.
132133
* Does not mutate the originals.
133134
*/
134135
export function evaluateFieldVisibility(params: InputParam[], inputValues: Record<string, unknown>, arrayIndex?: number): InputParam[] {
135-
return params.map((param) => ({
136-
...param,
137-
display: evaluateParamVisibility(param, inputValues, arrayIndex)
138-
}))
136+
return params.map((param) => {
137+
const withDisplay = { ...param, display: evaluateParamVisibility(param, inputValues, arrayIndex) }
138+
139+
if (withDisplay.type === 'options' && withDisplay.options) {
140+
const filteredOptions = withDisplay.options.filter((opt) => {
141+
if (typeof opt === 'string' || (!opt.show && !opt.hide)) return true
142+
return evaluateParamVisibility(
143+
{ id: '', name: '', label: '', type: '', show: opt.show, hide: opt.hide },
144+
inputValues,
145+
arrayIndex
146+
)
147+
})
148+
return filteredOptions.length === withDisplay.options.length ? withDisplay : { ...withDisplay, options: filteredOptions }
149+
}
150+
151+
return withDisplay
152+
})
139153
}
140154

141155
/**

packages/components/nodes/agentflow/Start/Start.ts

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -291,6 +291,31 @@ class Start_Agentflow implements INode {
291291
{
292292
label: 'Boolean',
293293
name: 'boolean'
294+
},
295+
{
296+
label: 'Object',
297+
name: 'object',
298+
hide: { webhookContentType: 'application/x-www-form-urlencoded' }
299+
},
300+
{
301+
label: 'Array[String]',
302+
name: 'array[string]',
303+
hide: { webhookContentType: 'application/x-www-form-urlencoded' }
304+
},
305+
{
306+
label: 'Array[Number]',
307+
name: 'array[number]',
308+
hide: { webhookContentType: 'application/x-www-form-urlencoded' }
309+
},
310+
{
311+
label: 'Array[Boolean]',
312+
name: 'array[boolean]',
313+
hide: { webhookContentType: 'application/x-www-form-urlencoded' }
314+
},
315+
{
316+
label: 'Array[Object]',
317+
name: 'array[object]',
318+
hide: { webhookContentType: 'application/x-www-form-urlencoded' }
294319
}
295320
],
296321
default: 'string'

packages/components/src/Interface.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -64,6 +64,8 @@ export interface INodeOptionsValue {
6464
description?: string
6565
imageSrc?: string
6666
client?: Array<ClientType>
67+
show?: INodeDisplay
68+
hide?: INodeDisplay
6769
}
6870

6971
export interface INodeOutputsValue {

0 commit comments

Comments
 (0)