From 34f400ca40c2886fad2e7794d61d8dd95d5bd06f Mon Sep 17 00:00:00 2001 From: Lucio Lelii Date: Fri, 13 Mar 2026 16:51:41 +0100 Subject: [PATCH] Support richer schema visibility conditions --- src/app/shared/nodes/node-utility.spec.ts | 8 ++++ src/app/shared/nodes/node-utility.ts | 47 ++++++++++++++++++----- 2 files changed, 46 insertions(+), 9 deletions(-) diff --git a/src/app/shared/nodes/node-utility.spec.ts b/src/app/shared/nodes/node-utility.spec.ts index effec8a..8bd652d 100644 --- a/src/app/shared/nodes/node-utility.spec.ts +++ b/src/app/shared/nodes/node-utility.spec.ts @@ -100,8 +100,13 @@ describe('node-utility', () => { field: 'useLlm', equals: 'true' }); + expect(readUiConditionRule({ field: 'method', in: ['POST', 'PUT'] })).toEqual({ + field: 'method', + in: ['POST', 'PUT'] + }); expect(readUiConditionRule({ field: '', equals: 'true' })).toBeNull(); expect(readUiConditionRule({ field: 'useLlm', equals: true })).toBeNull(); + expect(readUiConditionRule({ field: 'method', in: [] })).toBeNull(); }); it('readUiGroup normalizes logical group labels', () => { @@ -123,6 +128,9 @@ describe('node-utility', () => { expect(evaluateUiConditionRule({ field: 'useLlm', equals: 'false' }, config, resolveFieldSchema)).toBe(false); expect(evaluateUiConditionRule({ field: 'retries', equals: '3' }, config, resolveFieldSchema)).toBe(true); expect(evaluateUiConditionRule({ field: 'provider', equals: 'openai' }, config, resolveFieldSchema)).toBe(true); + expect(evaluateUiConditionRule({ field: 'provider', in: ['openai', 'anthropic'] }, config, resolveFieldSchema)).toBe(true); + expect(evaluateUiConditionRule({ field: 'retries', in: ['1', '3'] }, config, resolveFieldSchema)).toBe(true); + expect(evaluateUiConditionRule({ field: 'useLlm', in: ['false', 'true'] }, config, resolveFieldSchema)).toBe(true); }); it('splitTemplatedTextParts identifies dynamic placeholders', () => { diff --git a/src/app/shared/nodes/node-utility.ts b/src/app/shared/nodes/node-utility.ts index 2d3d069..3378f88 100644 --- a/src/app/shared/nodes/node-utility.ts +++ b/src/app/shared/nodes/node-utility.ts @@ -1,5 +1,7 @@ export type TemplatedTextPart = { text: string; isDynamicInput: boolean }; -export type UiConditionRule = { field: string; equals: string }; +export type UiConditionRule = + | { field: string; equals: string } + | { field: string; in: string[] }; export function toStringOrNull(value: unknown): string | null { if (typeof value === 'string' && value.trim().length > 0) return value; @@ -74,13 +76,25 @@ export function readUiConditionRule(value: unknown): UiConditionRule | null { const field = (value as Record)['field']; const equals = (value as Record)['equals']; + const includes = (value as Record)['in']; if (typeof field !== 'string' || field.trim().length === 0) return null; - if (typeof equals !== 'string') return null; + if (typeof equals === 'string') { + return { + field: field.trim(), + equals + }; + } - return { - field: field.trim(), - equals - }; + if (Array.isArray(includes)) { + const values = includes.filter((item): item is string => typeof item === 'string'); + if (!values.length) return null; + return { + field: field.trim(), + in: values + }; + } + + return null; } export function readUiGroup(value: unknown): string | null { @@ -107,17 +121,32 @@ export function evaluateUiConditionRule( const schema = resolveFieldSchema?.(rule.field); const schemaType = schema?.['type']; const type = typeof schemaType === 'string' ? schemaType : null; + const expectedValues = 'in' in rule ? rule.in : null; + const expectedValue = 'equals' in rule ? rule.equals : null; if (type === 'boolean' || typeof actualValue === 'boolean') { - return actualValue === parseBooleanCondition(rule.equals); + if (expectedValues) { + return expectedValues.some((value) => actualValue === parseBooleanCondition(value)); + } + return expectedValue != null && actualValue === parseBooleanCondition(expectedValue); } if (type === 'number' || type === 'integer' || typeof actualValue === 'number') { - const expected = Number(rule.equals); + if (expectedValues) { + return expectedValues + .map((value) => Number(value)) + .filter((value) => Number.isFinite(value)) + .some((value) => actualValue === value); + } + const expected = Number(expectedValue); return Number.isFinite(expected) && actualValue === expected; } - return String(actualValue ?? '') === rule.equals; + if (expectedValues) { + return expectedValues.includes(String(actualValue ?? '')); + } + + return expectedValue != null && String(actualValue ?? '') === expectedValue; } function parseBooleanCondition(value: string): boolean {