diff --git a/packages/@n8n/nodes-langchain/nodes/llms/LMChatOpenAi/LmChatOpenAi.node.ts b/packages/@n8n/nodes-langchain/nodes/llms/LMChatOpenAi/LmChatOpenAi.node.ts index 5cf188e752..eaaf4b6315 100644 --- a/packages/@n8n/nodes-langchain/nodes/llms/LMChatOpenAi/LmChatOpenAi.node.ts +++ b/packages/@n8n/nodes-langchain/nodes/llms/LMChatOpenAi/LmChatOpenAi.node.ts @@ -90,7 +90,11 @@ export class LmChatOpenAi implements INodeType { { type: 'filter', properties: { - pass: "={{ $responseItem.id.startsWith('gpt-') && !$responseItem.id.includes('instruct') }}", + // If the baseURL is not set or is set to api.openai.com, include only chat models + pass: `={{ + ($parameter.options?.baseURL && !$parameter.options?.baseURL?.includes('api.openai.com')) || + ($responseItem.id.startsWith('gpt-') && !$responseItem.id.includes('instruct')) + }}`, }, }, { @@ -119,6 +123,18 @@ export class LmChatOpenAi implements INodeType { }, default: 'gpt-3.5-turbo', }, + { + displayName: + 'When using non-OpenAI models via "Base URL" override, not all models might be chat-compatible or support other features, like tools calling or JSON response format', + name: 'notice', + type: 'notice', + default: '', + displayOptions: { + show: { + '/options.baseURL': [{ _cnd: { exists: true } }], + }, + }, + }, { displayName: 'Options', name: 'options', diff --git a/packages/@n8n/nodes-langchain/nodes/llms/LMOpenAi/LmOpenAi.node.ts b/packages/@n8n/nodes-langchain/nodes/llms/LMOpenAi/LmOpenAi.node.ts index 3f2d1c4315..5408fc4da9 100644 --- a/packages/@n8n/nodes-langchain/nodes/llms/LMOpenAi/LmOpenAi.node.ts +++ b/packages/@n8n/nodes-langchain/nodes/llms/LMOpenAi/LmOpenAi.node.ts @@ -97,6 +97,18 @@ export class LmOpenAi implements INodeType { }, }, }, + { + displayName: + 'When using non OpenAI models via Base URL override, not all models might be chat-compatible or support other features, like tools calling or JSON response format.', + name: 'notice', + type: 'notice', + default: '', + displayOptions: { + show: { + '/options.baseURL': [{ _cnd: { exists: true } }], + }, + }, + }, { displayName: 'Options', name: 'options', @@ -198,7 +210,7 @@ export class LmOpenAi implements INodeType { })) as { data: Array<{ owned_by: string; id: string }> }; for (const model of data) { - if (!model.owned_by?.startsWith('system')) continue; + if (!options.baseURL && !model.owned_by?.startsWith('system')) continue; results.push({ name: model.id, value: model.id, diff --git a/packages/workflow/src/Interfaces.ts b/packages/workflow/src/Interfaces.ts index 3a3aab1165..3433e0c45f 100644 --- a/packages/workflow/src/Interfaces.ts +++ b/packages/workflow/src/Interfaces.ts @@ -1296,7 +1296,8 @@ export type DisplayCondition = | { _cnd: { startsWith: string } } | { _cnd: { endsWith: string } } | { _cnd: { includes: string } } - | { _cnd: { regex: string } }; + | { _cnd: { regex: string } } + | { _cnd: { exists: true } }; export interface IDisplayOptions { hide?: { diff --git a/packages/workflow/src/NodeHelpers.ts b/packages/workflow/src/NodeHelpers.ts index b60aa3b1c2..1f27bacee9 100644 --- a/packages/workflow/src/NodeHelpers.ts +++ b/packages/workflow/src/NodeHelpers.ts @@ -359,7 +359,7 @@ const declarativeNodeOptionParameters: INodeProperties = { export function isSubNodeType( typeDescription: Pick | null, ): boolean { - if (!typeDescription || !typeDescription.outputs || typeof typeDescription.outputs === 'string') { + if (!typeDescription?.outputs || typeof typeDescription.outputs === 'string') { return false; } const outputTypes = getConnectionTypes(typeDescription.outputs); @@ -521,6 +521,9 @@ const checkConditions = ( if (key === 'regex') { return new RegExp(targetValue as string).test(propertyValue as string); } + if (key === 'exists') { + return propertyValue !== null && propertyValue !== undefined && propertyValue !== ''; + } return false; }); } diff --git a/packages/workflow/test/NodeHelpers.conditions.test.ts b/packages/workflow/test/NodeHelpers.conditions.test.ts index 9e1bcec5f0..05355d4a2e 100644 --- a/packages/workflow/test/NodeHelpers.conditions.test.ts +++ b/packages/workflow/test/NodeHelpers.conditions.test.ts @@ -1887,6 +1887,274 @@ describe('NodeHelpers', () => { }, }, }, + { + description: + 'simple values with displayOptions "show" using exists condition. No values set.', + input: { + nodePropertiesArray: [ + { + name: 'field1', + displayName: 'Field 1', + type: 'string', + default: '', + }, + { + name: 'field2', + displayName: 'Field 2', + displayOptions: { + show: { + field1: [{ _cnd: { exists: true } }], + }, + }, + type: 'string', + default: 'default field2', + }, + ], + nodeValues: {}, + }, + output: { + noneDisplayedFalse: { + defaultsFalse: {}, + defaultsTrue: { + field1: '', + }, + }, + noneDisplayedTrue: { + defaultsFalse: {}, + defaultsTrue: { + field1: '', + field2: 'default field2', + }, + }, + }, + }, + { + description: + 'simple values with displayOptions "show" using exists condition. Field1 has a value.', + input: { + nodePropertiesArray: [ + { + name: 'field1', + displayName: 'Field 1', + type: 'string', + default: '', + }, + { + name: 'field2', + displayName: 'Field 2', + displayOptions: { + show: { + field1: [{ _cnd: { exists: true } }], + }, + }, + type: 'string', + default: 'default field2', + }, + ], + nodeValues: { + field1: 'some value', + }, + }, + output: { + noneDisplayedFalse: { + defaultsFalse: { + field1: 'some value', + }, + defaultsTrue: { + field1: 'some value', + field2: 'default field2', + }, + }, + noneDisplayedTrue: { + defaultsFalse: { + field1: 'some value', + }, + defaultsTrue: { + field1: 'some value', + field2: 'default field2', + }, + }, + }, + }, + { + description: + 'complex type "fixedCollection" with "multipleValues: false" and with displayOptions "show" using exists condition.', + input: { + nodePropertiesArray: [ + { + name: 'values', + displayName: 'Values', + type: 'fixedCollection', + default: {}, + options: [ + { + name: 'data', + displayName: 'Data', + values: [ + { + name: 'field1', + displayName: 'Field 1', + type: 'string', + default: '', + }, + { + name: 'field2', + displayName: 'Field 2', + type: 'string', + displayOptions: { + show: { + field1: [{ _cnd: { exists: true } }], + }, + }, + default: 'default field2', + }, + ], + }, + ], + }, + ], + nodeValues: { + values: { + data: { + field1: 'some value', + }, + }, + }, + }, + output: { + noneDisplayedFalse: { + defaultsFalse: { + values: { + data: { + field1: 'some value', + }, + }, + }, + defaultsTrue: { + values: { + data: { + field1: 'some value', + field2: 'default field2', + }, + }, + }, + }, + noneDisplayedTrue: { + defaultsFalse: { + values: { + data: { + field1: 'some value', + }, + }, + }, + defaultsTrue: { + values: { + data: { + field1: 'some value', + field2: 'default field2', + }, + }, + }, + }, + }, + }, + { + description: + 'complex type "collection" with "multipleValues: true" and with displayOptions "show" using exists condition.', + input: { + nodePropertiesArray: [ + { + name: 'values', + displayName: 'Values', + type: 'collection', + typeOptions: { + multipleValues: true, + }, + default: {}, + options: [ + { + name: 'field1', + displayName: 'Field 1', + type: 'string', + default: '', + }, + { + name: 'field2', + displayName: 'Field 2', + type: 'string', + displayOptions: { + show: { + field1: [{ _cnd: { exists: true } }], + }, + }, + default: 'default field2', + }, + ], + }, + ], + nodeValues: { + values: [ + { + field1: 'value1', + }, + { + field1: '', + }, + {}, + ], + }, + }, + output: { + noneDisplayedFalse: { + defaultsFalse: { + values: [ + { + field1: 'value1', + }, + { + field1: '', + }, + {}, + ], + }, + defaultsTrue: { + values: [ + { + field1: 'value1', + }, + { + field1: '', + }, + {}, + ], + }, + }, + noneDisplayedTrue: { + defaultsFalse: { + values: [ + { + field1: 'value1', + }, + { + field1: '', + }, + {}, + ], + }, + defaultsTrue: { + values: [ + { + field1: 'value1', + }, + { + field1: '', + }, + {}, + ], + }, + }, + }, + }, ]; for (const testData of tests) {