mirror of
https://github.com/Abdulazizzn/n8n-enterprise-unlocked.git
synced 2025-12-17 18:12:04 +00:00
feat: Hints for tools and agent (#13386)
This commit is contained in:
@@ -1,7 +1,7 @@
|
|||||||
import { generateOffsets, getGenericHints, getNewNodePosition } from './nodeViewUtils';
|
import { generateOffsets, getGenericHints, getNewNodePosition } from './nodeViewUtils';
|
||||||
import type { INode, INodeTypeDescription, INodeExecutionData, Workflow } from 'n8n-workflow';
|
import type { INode, INodeTypeDescription, INodeExecutionData, Workflow } from 'n8n-workflow';
|
||||||
import type { INodeUi, XYPosition } from '@/Interface';
|
import type { INodeUi, XYPosition } from '@/Interface';
|
||||||
import { NodeHelpers } from 'n8n-workflow';
|
import { NodeHelpers, SEND_AND_WAIT_OPERATION } from 'n8n-workflow';
|
||||||
|
|
||||||
import { describe, it, expect, beforeEach } from 'vitest';
|
import { describe, it, expect, beforeEach } from 'vitest';
|
||||||
import { mock, type MockProxy } from 'vitest-mock-extended';
|
import { mock, type MockProxy } from 'vitest-mock-extended';
|
||||||
@@ -19,7 +19,7 @@ describe('getGenericHints', () => {
|
|||||||
|
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
mockWorkflowNode = mock<INode>();
|
mockWorkflowNode = mock<INode>();
|
||||||
mockNode = mock<INodeUi>();
|
mockNode = mock<INodeUi>({ type: 'test' });
|
||||||
mockNodeType = mock<INodeTypeDescription>();
|
mockNodeType = mock<INodeTypeDescription>();
|
||||||
mockNodeOutputData = [];
|
mockNodeOutputData = [];
|
||||||
mockWorkflow = mock<Workflow>();
|
mockWorkflow = mock<Workflow>();
|
||||||
@@ -138,6 +138,54 @@ describe('getGenericHints', () => {
|
|||||||
},
|
},
|
||||||
]);
|
]);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it('should return an hint for tool nodes without AI expressions', () => {
|
||||||
|
mockNode.type = 'custom-tool-node';
|
||||||
|
hasNodeRun = true;
|
||||||
|
mockWorkflowNode.parameters = { param1: 'staticValue' };
|
||||||
|
|
||||||
|
const hints = getGenericHints({
|
||||||
|
workflowNode: mockWorkflowNode,
|
||||||
|
node: mockNode,
|
||||||
|
nodeType: mockNodeType,
|
||||||
|
nodeOutputData: mockNodeOutputData,
|
||||||
|
hasMultipleInputItems,
|
||||||
|
workflow: mockWorkflow,
|
||||||
|
hasNodeRun,
|
||||||
|
});
|
||||||
|
|
||||||
|
expect(hints).toEqual([
|
||||||
|
{
|
||||||
|
message:
|
||||||
|
'No parameters are set up to be filled by AI. Click on the ✨ button next to a parameter to allow AI to set its value.',
|
||||||
|
location: 'outputPane',
|
||||||
|
whenToDisplay: 'afterExecution',
|
||||||
|
},
|
||||||
|
]);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should return a hint for sendAndWait operation with multiple input items', () => {
|
||||||
|
hasMultipleInputItems = true;
|
||||||
|
mockWorkflowNode.parameters.operation = SEND_AND_WAIT_OPERATION;
|
||||||
|
mockWorkflow.getNode.mockReturnValue({ executeOnce: false } as unknown as INode);
|
||||||
|
|
||||||
|
const hints = getGenericHints({
|
||||||
|
workflowNode: mockWorkflowNode,
|
||||||
|
node: mockNode,
|
||||||
|
nodeType: mockNodeType,
|
||||||
|
nodeOutputData: mockNodeOutputData,
|
||||||
|
hasMultipleInputItems,
|
||||||
|
workflow: mockWorkflow,
|
||||||
|
hasNodeRun,
|
||||||
|
});
|
||||||
|
|
||||||
|
expect(hints).toEqual([
|
||||||
|
{
|
||||||
|
message: 'This action will run only once, for the first input item',
|
||||||
|
location: 'outputPane',
|
||||||
|
},
|
||||||
|
]);
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
describe('generateOffsets', () => {
|
describe('generateOffsets', () => {
|
||||||
|
|||||||
@@ -15,7 +15,7 @@ import type {
|
|||||||
NodeHint,
|
NodeHint,
|
||||||
Workflow,
|
Workflow,
|
||||||
} from 'n8n-workflow';
|
} from 'n8n-workflow';
|
||||||
import { NodeHelpers } from 'n8n-workflow';
|
import { NodeHelpers, SEND_AND_WAIT_OPERATION } from 'n8n-workflow';
|
||||||
import type { RouteLocation } from 'vue-router';
|
import type { RouteLocation } from 'vue-router';
|
||||||
|
|
||||||
/*
|
/*
|
||||||
@@ -280,6 +280,19 @@ export function getGenericHints({
|
|||||||
}) {
|
}) {
|
||||||
const nodeHints: NodeHint[] = [];
|
const nodeHints: NodeHint[] = [];
|
||||||
|
|
||||||
|
// tools hints
|
||||||
|
if (node?.type.toLocaleLowerCase().includes('tool') && hasNodeRun) {
|
||||||
|
const stringifiedParameters = JSON.stringify(workflowNode.parameters);
|
||||||
|
if (!stringifiedParameters.includes('$fromAI')) {
|
||||||
|
nodeHints.push({
|
||||||
|
message:
|
||||||
|
'No parameters are set up to be filled by AI. Click on the ✨ button next to a parameter to allow AI to set its value.',
|
||||||
|
location: 'outputPane',
|
||||||
|
whenToDisplay: 'afterExecution',
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// add limit reached hint
|
// add limit reached hint
|
||||||
if (hasNodeRun && workflowNode.parameters.limit) {
|
if (hasNodeRun && workflowNode.parameters.limit) {
|
||||||
if (nodeOutputData.length === workflowNode.parameters.limit) {
|
if (nodeOutputData.length === workflowNode.parameters.limit) {
|
||||||
@@ -306,6 +319,17 @@ export function getGenericHints({
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// add sendAndWait hint
|
||||||
|
if (hasMultipleInputItems && workflowNode.parameters.operation === SEND_AND_WAIT_OPERATION) {
|
||||||
|
const executeOnce = workflow.getNode(node.name)?.executeOnce;
|
||||||
|
if (!executeOnce) {
|
||||||
|
nodeHints.push({
|
||||||
|
message: 'This action will run only once, for the first input item',
|
||||||
|
location: 'outputPane',
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// add expression in field name hint for Set node
|
// add expression in field name hint for Set node
|
||||||
if (node.type === SET_NODE_TYPE && node.parameters.mode === 'manual') {
|
if (node.type === SET_NODE_TYPE && node.parameters.mode === 'manual') {
|
||||||
const rawParameters = NodeHelpers.getNodeParameters(
|
const rawParameters = NodeHelpers.getNodeParameters(
|
||||||
|
|||||||
Reference in New Issue
Block a user