mirror of
https://github.com/n8n-io/n8n.git
synced 2025-03-05 20:50:17 -08:00
fix(editor): Hide fromAI button in old workflow tool (#13552)
This commit is contained in:
parent
1c8c7e34f9
commit
6ef8d34f96
|
@ -27,7 +27,6 @@ import {
|
||||||
makeOverrideValue,
|
makeOverrideValue,
|
||||||
updateFromAIOverrideValues,
|
updateFromAIOverrideValues,
|
||||||
} from '../utils/fromAIOverrideUtils';
|
} from '../utils/fromAIOverrideUtils';
|
||||||
import { useNodeTypesStore } from '@/stores/nodeTypes.store';
|
|
||||||
import { useTelemetry } from '@/composables/useTelemetry';
|
import { useTelemetry } from '@/composables/useTelemetry';
|
||||||
|
|
||||||
type Props = {
|
type Props = {
|
||||||
|
@ -69,16 +68,10 @@ const menuExpanded = ref(false);
|
||||||
const forceShowExpression = ref(false);
|
const forceShowExpression = ref(false);
|
||||||
|
|
||||||
const ndvStore = useNDVStore();
|
const ndvStore = useNDVStore();
|
||||||
const nodeTypesStore = useNodeTypesStore();
|
|
||||||
const telemetry = useTelemetry();
|
const telemetry = useTelemetry();
|
||||||
|
|
||||||
const node = computed(() => ndvStore.activeNode);
|
const node = computed(() => ndvStore.activeNode);
|
||||||
const fromAIOverride = ref<FromAIOverride | null>(
|
const fromAIOverride = ref<FromAIOverride | null>(makeOverrideValue(props, node.value));
|
||||||
makeOverrideValue(
|
|
||||||
props,
|
|
||||||
node.value && nodeTypesStore.getNodeType(node.value.type, node.value.typeVersion),
|
|
||||||
),
|
|
||||||
);
|
|
||||||
|
|
||||||
const canBeContentOverride = computed(() => {
|
const canBeContentOverride = computed(() => {
|
||||||
// The resourceLocator handles overrides separately
|
// The resourceLocator handles overrides separately
|
||||||
|
|
|
@ -295,7 +295,7 @@ const fromAIOverride = ref<FromAIOverride | null>(
|
||||||
value: props.modelValue?.value ?? '',
|
value: props.modelValue?.value ?? '',
|
||||||
...props,
|
...props,
|
||||||
},
|
},
|
||||||
props.node && nodeTypesStore.getNodeType(props.node.type, props.node.typeVersion),
|
props.node,
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
|
|
||||||
|
|
|
@ -1,3 +1,4 @@
|
||||||
|
import type { INodeUi } from '@/Interface';
|
||||||
import type { FromAIOverride, OverrideContext } from './fromAIOverrideUtils';
|
import type { FromAIOverride, OverrideContext } from './fromAIOverrideUtils';
|
||||||
import {
|
import {
|
||||||
buildValueFromOverride,
|
buildValueFromOverride,
|
||||||
|
@ -8,6 +9,14 @@ import {
|
||||||
} from './fromAIOverrideUtils';
|
} from './fromAIOverrideUtils';
|
||||||
import type { INodeTypeDescription, NodePropertyTypes } from 'n8n-workflow';
|
import type { INodeTypeDescription, NodePropertyTypes } from 'n8n-workflow';
|
||||||
|
|
||||||
|
const getNodeType = vi.fn();
|
||||||
|
|
||||||
|
vi.mock('@/stores/nodeTypes.store', () => ({
|
||||||
|
useNodeTypesStore: vi.fn(() => ({
|
||||||
|
getNodeType,
|
||||||
|
})),
|
||||||
|
}));
|
||||||
|
|
||||||
const DISPLAY_NAME = 'aDisplayName';
|
const DISPLAY_NAME = 'aDisplayName';
|
||||||
const PARAMETER_NAME = 'aName';
|
const PARAMETER_NAME = 'aName';
|
||||||
|
|
||||||
|
@ -26,7 +35,7 @@ const makeContext = (
|
||||||
});
|
});
|
||||||
|
|
||||||
const MOCK_NODE_TYPE_MIXIN = {
|
const MOCK_NODE_TYPE_MIXIN = {
|
||||||
version: 0,
|
version: 1,
|
||||||
defaults: {},
|
defaults: {},
|
||||||
inputs: [],
|
inputs: [],
|
||||||
outputs: [],
|
outputs: [],
|
||||||
|
@ -74,22 +83,39 @@ const NON_AI_NODE_TYPE: INodeTypeDescription = {
|
||||||
...MOCK_NODE_TYPE_MIXIN,
|
...MOCK_NODE_TYPE_MIXIN,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
function mockNodeFromType(type: INodeTypeDescription) {
|
||||||
|
return vi.mocked<INodeUi>({
|
||||||
|
type: type.name,
|
||||||
|
typeVersion: type.version as number,
|
||||||
|
} as never);
|
||||||
|
}
|
||||||
|
|
||||||
describe('makeOverrideValue', () => {
|
describe('makeOverrideValue', () => {
|
||||||
test.each<[string, ...Parameters<typeof makeOverrideValue>]>([
|
test.each<[string, ...Parameters<typeof makeOverrideValue>]>([
|
||||||
['null nodeType', makeContext(''), null],
|
['null nodeType', makeContext(''), null],
|
||||||
['non-ai node type', makeContext(''), NON_AI_NODE_TYPE],
|
['non-ai node type', makeContext(''), mockNodeFromType(NON_AI_NODE_TYPE)],
|
||||||
['ai node type on denylist', makeContext(''), AI_DENYLIST_NODE_TYPE],
|
['ai node type on denylist', makeContext(''), mockNodeFromType(AI_DENYLIST_NODE_TYPE)],
|
||||||
['vector store type', makeContext(''), AI_VECTOR_STORE_NODE_TYPE],
|
['vector store type', makeContext(''), mockNodeFromType(AI_VECTOR_STORE_NODE_TYPE)],
|
||||||
['denied parameter name', makeContext('', 'parameters.toolName'), AI_NODE_TYPE],
|
[
|
||||||
['denied parameter type', makeContext('', undefined, 'credentialsSelect'), AI_NODE_TYPE],
|
'denied parameter name',
|
||||||
|
makeContext('', 'parameters.toolName'),
|
||||||
|
mockNodeFromType(AI_NODE_TYPE),
|
||||||
|
],
|
||||||
|
[
|
||||||
|
'denied parameter type',
|
||||||
|
makeContext('', undefined, 'credentialsSelect'),
|
||||||
|
mockNodeFromType(AI_NODE_TYPE),
|
||||||
|
],
|
||||||
])('should not create an override for %s', (_name, context, nodeType) => {
|
])('should not create an override for %s', (_name, context, nodeType) => {
|
||||||
|
getNodeType.mockReturnValue(nodeType);
|
||||||
expect(makeOverrideValue(context, nodeType)).toBeNull();
|
expect(makeOverrideValue(context, nodeType)).toBeNull();
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should create an fromAI override', () => {
|
it('should create an fromAI override', () => {
|
||||||
|
getNodeType.mockReturnValue(AI_NODE_TYPE);
|
||||||
const result = makeOverrideValue(
|
const result = makeOverrideValue(
|
||||||
makeContext(`={{ /*n8n-auto-generated-fromAI-override*/ $fromAI('${DISPLAY_NAME}') }}`),
|
makeContext(`={{ /*n8n-auto-generated-fromAI-override*/ $fromAI('${DISPLAY_NAME}') }}`),
|
||||||
AI_NODE_TYPE,
|
mockNodeFromType(AI_NODE_TYPE),
|
||||||
);
|
);
|
||||||
|
|
||||||
expect(result).not.toBeNull();
|
expect(result).not.toBeNull();
|
||||||
|
@ -97,12 +123,14 @@ describe('makeOverrideValue', () => {
|
||||||
});
|
});
|
||||||
|
|
||||||
it('parses existing fromAI overrides', () => {
|
it('parses existing fromAI overrides', () => {
|
||||||
|
getNodeType.mockReturnValue(AI_NODE_TYPE);
|
||||||
|
|
||||||
const description = 'a description';
|
const description = 'a description';
|
||||||
const result = makeOverrideValue(
|
const result = makeOverrideValue(
|
||||||
makeContext(
|
makeContext(
|
||||||
`={{ /*n8n-auto-generated-fromAI-override*/ $fromAI('${DISPLAY_NAME}', \`${description}\`) }}`,
|
`={{ /*n8n-auto-generated-fromAI-override*/ $fromAI('${DISPLAY_NAME}', \`${description}\`) }}`,
|
||||||
),
|
),
|
||||||
AI_NODE_TYPE,
|
mockNodeFromType(AI_NODE_TYPE),
|
||||||
);
|
);
|
||||||
|
|
||||||
expect(result).toBeDefined();
|
expect(result).toBeDefined();
|
||||||
|
@ -110,9 +138,11 @@ describe('makeOverrideValue', () => {
|
||||||
});
|
});
|
||||||
|
|
||||||
it('parses an existing fromAI override with default values without adding extraPropValue entry', () => {
|
it('parses an existing fromAI override with default values without adding extraPropValue entry', () => {
|
||||||
|
getNodeType.mockReturnValue(AI_NODE_TYPE);
|
||||||
|
|
||||||
const result = makeOverrideValue(
|
const result = makeOverrideValue(
|
||||||
makeContext("={{ /*n8n-auto-generated-fromAI-override*/ $fromAI('aName', ``) }}"),
|
makeContext("={{ /*n8n-auto-generated-fromAI-override*/ $fromAI('aName', ``) }}"),
|
||||||
AI_NODE_TYPE,
|
mockNodeFromType(AI_NODE_TYPE),
|
||||||
);
|
);
|
||||||
|
|
||||||
expect(result).toBeDefined();
|
expect(result).toBeDefined();
|
||||||
|
|
|
@ -1,11 +1,12 @@
|
||||||
import {
|
import {
|
||||||
extractFromAICalls,
|
extractFromAICalls,
|
||||||
FROM_AI_AUTO_GENERATED_MARKER,
|
FROM_AI_AUTO_GENERATED_MARKER,
|
||||||
type INodeTypeDescription,
|
|
||||||
type NodeParameterValueType,
|
type NodeParameterValueType,
|
||||||
type NodePropertyTypes,
|
type NodePropertyTypes,
|
||||||
} from 'n8n-workflow';
|
} from 'n8n-workflow';
|
||||||
import { i18n } from '@/plugins/i18n';
|
import { i18n } from '@/plugins/i18n';
|
||||||
|
import type { INodeUi } from '@/Interface';
|
||||||
|
import { useNodeTypesStore } from '@/stores/nodeTypes.store';
|
||||||
|
|
||||||
export type OverrideContext = {
|
export type OverrideContext = {
|
||||||
parameter: {
|
parameter: {
|
||||||
|
@ -44,7 +45,8 @@ function sanitizeFromAiParameterName(s: string) {
|
||||||
return s;
|
return s;
|
||||||
}
|
}
|
||||||
|
|
||||||
const NODE_DENYLIST = ['toolCode', 'toolHttpRequest'];
|
// nodeName | [nodeName, highestUnsupportedVersion]
|
||||||
|
const NODE_DENYLIST = ['toolCode', 'toolHttpRequest', ['toolWorkflow', 1.2]] as const;
|
||||||
|
|
||||||
const PATH_DENYLIST = [
|
const PATH_DENYLIST = [
|
||||||
'parameters.name',
|
'parameters.name',
|
||||||
|
@ -159,16 +161,26 @@ export function parseOverrides(
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function isDeniedNode(nodeDenyData: string | readonly [string, number], node: INodeUi) {
|
||||||
|
if (typeof nodeDenyData === 'string') {
|
||||||
|
return node.type.endsWith(nodeDenyData);
|
||||||
|
} else {
|
||||||
|
const [name, version] = nodeDenyData;
|
||||||
|
return node.type.endsWith(name) && node.typeVersion <= version;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
export function canBeContentOverride(
|
export function canBeContentOverride(
|
||||||
props: Pick<OverrideContext, 'path' | 'parameter'>,
|
props: Pick<OverrideContext, 'path' | 'parameter'>,
|
||||||
nodeType: INodeTypeDescription | null,
|
node: INodeUi,
|
||||||
) {
|
) {
|
||||||
if (NODE_DENYLIST.some((x) => nodeType?.name?.endsWith(x) ?? false)) return false;
|
if (NODE_DENYLIST.some((x) => isDeniedNode(x, node))) return false;
|
||||||
|
|
||||||
if (PATH_DENYLIST.includes(props.path)) return false;
|
if (PATH_DENYLIST.includes(props.path)) return false;
|
||||||
|
|
||||||
if (PROP_TYPE_DENYLIST.includes(props.parameter.type)) return false;
|
if (PROP_TYPE_DENYLIST.includes(props.parameter.type)) return false;
|
||||||
|
|
||||||
|
const nodeType = useNodeTypesStore().getNodeType(node.type, node?.typeVersion);
|
||||||
const codex = nodeType?.codex;
|
const codex = nodeType?.codex;
|
||||||
if (
|
if (
|
||||||
!codex?.categories?.includes('AI') ||
|
!codex?.categories?.includes('AI') ||
|
||||||
|
@ -182,11 +194,11 @@ export function canBeContentOverride(
|
||||||
|
|
||||||
export function makeOverrideValue(
|
export function makeOverrideValue(
|
||||||
context: OverrideContext,
|
context: OverrideContext,
|
||||||
nodeType: INodeTypeDescription | null | undefined,
|
node: INodeUi | null | undefined,
|
||||||
): FromAIOverride | null {
|
): FromAIOverride | null {
|
||||||
if (!nodeType) return null;
|
if (!node) return null;
|
||||||
|
|
||||||
if (canBeContentOverride(context, nodeType)) {
|
if (canBeContentOverride(context, node)) {
|
||||||
const fromAiOverride: FromAIOverride = {
|
const fromAiOverride: FromAIOverride = {
|
||||||
type: 'fromAI',
|
type: 'fromAI',
|
||||||
extraProps: fromAIExtraProps,
|
extraProps: fromAIExtraProps,
|
||||||
|
|
Loading…
Reference in a new issue