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,
|
||||
updateFromAIOverrideValues,
|
||||
} from '../utils/fromAIOverrideUtils';
|
||||
import { useNodeTypesStore } from '@/stores/nodeTypes.store';
|
||||
import { useTelemetry } from '@/composables/useTelemetry';
|
||||
|
||||
type Props = {
|
||||
|
@ -69,16 +68,10 @@ const menuExpanded = ref(false);
|
|||
const forceShowExpression = ref(false);
|
||||
|
||||
const ndvStore = useNDVStore();
|
||||
const nodeTypesStore = useNodeTypesStore();
|
||||
const telemetry = useTelemetry();
|
||||
|
||||
const node = computed(() => ndvStore.activeNode);
|
||||
const fromAIOverride = ref<FromAIOverride | null>(
|
||||
makeOverrideValue(
|
||||
props,
|
||||
node.value && nodeTypesStore.getNodeType(node.value.type, node.value.typeVersion),
|
||||
),
|
||||
);
|
||||
const fromAIOverride = ref<FromAIOverride | null>(makeOverrideValue(props, node.value));
|
||||
|
||||
const canBeContentOverride = computed(() => {
|
||||
// The resourceLocator handles overrides separately
|
||||
|
|
|
@ -295,7 +295,7 @@ const fromAIOverride = ref<FromAIOverride | null>(
|
|||
value: props.modelValue?.value ?? '',
|
||||
...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 {
|
||||
buildValueFromOverride,
|
||||
|
@ -8,6 +9,14 @@ import {
|
|||
} from './fromAIOverrideUtils';
|
||||
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 PARAMETER_NAME = 'aName';
|
||||
|
||||
|
@ -26,7 +35,7 @@ const makeContext = (
|
|||
});
|
||||
|
||||
const MOCK_NODE_TYPE_MIXIN = {
|
||||
version: 0,
|
||||
version: 1,
|
||||
defaults: {},
|
||||
inputs: [],
|
||||
outputs: [],
|
||||
|
@ -74,22 +83,39 @@ const NON_AI_NODE_TYPE: INodeTypeDescription = {
|
|||
...MOCK_NODE_TYPE_MIXIN,
|
||||
};
|
||||
|
||||
function mockNodeFromType(type: INodeTypeDescription) {
|
||||
return vi.mocked<INodeUi>({
|
||||
type: type.name,
|
||||
typeVersion: type.version as number,
|
||||
} as never);
|
||||
}
|
||||
|
||||
describe('makeOverrideValue', () => {
|
||||
test.each<[string, ...Parameters<typeof makeOverrideValue>]>([
|
||||
['null nodeType', makeContext(''), null],
|
||||
['non-ai node type', makeContext(''), NON_AI_NODE_TYPE],
|
||||
['ai node type on denylist', makeContext(''), AI_DENYLIST_NODE_TYPE],
|
||||
['vector store type', makeContext(''), AI_VECTOR_STORE_NODE_TYPE],
|
||||
['denied parameter name', makeContext('', 'parameters.toolName'), AI_NODE_TYPE],
|
||||
['denied parameter type', makeContext('', undefined, 'credentialsSelect'), AI_NODE_TYPE],
|
||||
['non-ai node type', makeContext(''), mockNodeFromType(NON_AI_NODE_TYPE)],
|
||||
['ai node type on denylist', makeContext(''), mockNodeFromType(AI_DENYLIST_NODE_TYPE)],
|
||||
['vector store type', makeContext(''), mockNodeFromType(AI_VECTOR_STORE_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) => {
|
||||
getNodeType.mockReturnValue(nodeType);
|
||||
expect(makeOverrideValue(context, nodeType)).toBeNull();
|
||||
});
|
||||
|
||||
it('should create an fromAI override', () => {
|
||||
getNodeType.mockReturnValue(AI_NODE_TYPE);
|
||||
const result = makeOverrideValue(
|
||||
makeContext(`={{ /*n8n-auto-generated-fromAI-override*/ $fromAI('${DISPLAY_NAME}') }}`),
|
||||
AI_NODE_TYPE,
|
||||
mockNodeFromType(AI_NODE_TYPE),
|
||||
);
|
||||
|
||||
expect(result).not.toBeNull();
|
||||
|
@ -97,12 +123,14 @@ describe('makeOverrideValue', () => {
|
|||
});
|
||||
|
||||
it('parses existing fromAI overrides', () => {
|
||||
getNodeType.mockReturnValue(AI_NODE_TYPE);
|
||||
|
||||
const description = 'a description';
|
||||
const result = makeOverrideValue(
|
||||
makeContext(
|
||||
`={{ /*n8n-auto-generated-fromAI-override*/ $fromAI('${DISPLAY_NAME}', \`${description}\`) }}`,
|
||||
),
|
||||
AI_NODE_TYPE,
|
||||
mockNodeFromType(AI_NODE_TYPE),
|
||||
);
|
||||
|
||||
expect(result).toBeDefined();
|
||||
|
@ -110,9 +138,11 @@ describe('makeOverrideValue', () => {
|
|||
});
|
||||
|
||||
it('parses an existing fromAI override with default values without adding extraPropValue entry', () => {
|
||||
getNodeType.mockReturnValue(AI_NODE_TYPE);
|
||||
|
||||
const result = makeOverrideValue(
|
||||
makeContext("={{ /*n8n-auto-generated-fromAI-override*/ $fromAI('aName', ``) }}"),
|
||||
AI_NODE_TYPE,
|
||||
mockNodeFromType(AI_NODE_TYPE),
|
||||
);
|
||||
|
||||
expect(result).toBeDefined();
|
||||
|
|
|
@ -1,11 +1,12 @@
|
|||
import {
|
||||
extractFromAICalls,
|
||||
FROM_AI_AUTO_GENERATED_MARKER,
|
||||
type INodeTypeDescription,
|
||||
type NodeParameterValueType,
|
||||
type NodePropertyTypes,
|
||||
} from 'n8n-workflow';
|
||||
import { i18n } from '@/plugins/i18n';
|
||||
import type { INodeUi } from '@/Interface';
|
||||
import { useNodeTypesStore } from '@/stores/nodeTypes.store';
|
||||
|
||||
export type OverrideContext = {
|
||||
parameter: {
|
||||
|
@ -44,7 +45,8 @@ function sanitizeFromAiParameterName(s: string) {
|
|||
return s;
|
||||
}
|
||||
|
||||
const NODE_DENYLIST = ['toolCode', 'toolHttpRequest'];
|
||||
// nodeName | [nodeName, highestUnsupportedVersion]
|
||||
const NODE_DENYLIST = ['toolCode', 'toolHttpRequest', ['toolWorkflow', 1.2]] as const;
|
||||
|
||||
const PATH_DENYLIST = [
|
||||
'parameters.name',
|
||||
|
@ -159,16 +161,26 @@ export function parseOverrides(
|
|||
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(
|
||||
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 (PROP_TYPE_DENYLIST.includes(props.parameter.type)) return false;
|
||||
|
||||
const nodeType = useNodeTypesStore().getNodeType(node.type, node?.typeVersion);
|
||||
const codex = nodeType?.codex;
|
||||
if (
|
||||
!codex?.categories?.includes('AI') ||
|
||||
|
@ -182,11 +194,11 @@ export function canBeContentOverride(
|
|||
|
||||
export function makeOverrideValue(
|
||||
context: OverrideContext,
|
||||
nodeType: INodeTypeDescription | null | undefined,
|
||||
node: INodeUi | null | undefined,
|
||||
): FromAIOverride | null {
|
||||
if (!nodeType) return null;
|
||||
if (!node) return null;
|
||||
|
||||
if (canBeContentOverride(context, nodeType)) {
|
||||
if (canBeContentOverride(context, node)) {
|
||||
const fromAiOverride: FromAIOverride = {
|
||||
type: 'fromAI',
|
||||
extraProps: fromAIExtraProps,
|
||||
|
|
Loading…
Reference in a new issue