feat: Hints for tools and agent (#13386)

This commit is contained in:
Michael Kret
2025-03-12 19:08:08 +02:00
committed by GitHub
parent 1d559861f1
commit ec8a719efa
2 changed files with 75 additions and 3 deletions

View File

@@ -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', () => {

View File

@@ -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(