Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: Hints for tools and agent #13386

Open
wants to merge 9 commits into
base: master
Choose a base branch
from
52 changes: 50 additions & 2 deletions packages/frontend/editor-ui/src/utils/nodeViewUtils.test.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import { generateOffsets, getGenericHints, getNewNodePosition } from './nodeViewUtils';
import type { INode, INodeTypeDescription, INodeExecutionData, Workflow } from 'n8n-workflow';
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 { mock, type MockProxy } from 'vitest-mock-extended';
Expand All @@ -19,7 +19,7 @@ describe('getGenericHints', () => {

beforeEach(() => {
mockWorkflowNode = mock<INode>();
mockNode = mock<INodeUi>();
mockNode = mock<INodeUi>({ type: 'test' });
mockNodeType = mock<INodeTypeDescription>();
mockNodeOutputData = [];
mockWorkflow = mock<Workflow>();
Expand Down Expand Up @@ -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', () => {
Expand Down
26 changes: 25 additions & 1 deletion packages/frontend/editor-ui/src/utils/nodeViewUtils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ import type {
NodeHint,
Workflow,
} from 'n8n-workflow';
import { NodeHelpers } from 'n8n-workflow';
import { NodeHelpers, SEND_AND_WAIT_OPERATION } from 'n8n-workflow';
import type { RouteLocation } from 'vue-router';

/*
Expand Down Expand Up @@ -280,6 +280,19 @@ export function getGenericHints({
}) {
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
if (hasNodeRun && workflowNode.parameters.limit) {
if (nodeOutputData.length === workflowNode.parameters.limit) {
Expand All @@ -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
if (node.type === SET_NODE_TYPE && node.parameters.mode === 'manual') {
const rawParameters = NodeHelpers.getNodeParameters(
Expand Down