mirror of
https://github.com/n8n-io/n8n.git
synced 2025-03-05 20:50:17 -08:00
Merge branch 'master' of https://github.com/n8n-io/n8n into node-2015-google-calendar-confusing-errors
This commit is contained in:
commit
85cb052ff9
|
@ -2,7 +2,7 @@
|
||||||
* Getters
|
* Getters
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import { getVisibleSelect } from '../utils';
|
import { getVisibleSelect } from '../utils/popper';
|
||||||
|
|
||||||
export function getCredentialSelect(eq = 0) {
|
export function getCredentialSelect(eq = 0) {
|
||||||
return cy.getByTestId('node-credentials-select').eq(eq);
|
return cy.getByTestId('node-credentials-select').eq(eq);
|
||||||
|
|
|
@ -1,4 +1,5 @@
|
||||||
import { getManualChatModal } from './modals/chat-modal';
|
import { getManualChatModal } from './modals/chat-modal';
|
||||||
|
import { clickGetBackToCanvas, getParameterInputByName } from './ndv';
|
||||||
import { ROUTES } from '../constants';
|
import { ROUTES } from '../constants';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -127,7 +128,7 @@ export function navigateToNewWorkflowPage(preventNodeViewUnload = true) {
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
export function addSupplementalNodeToParent(
|
function connectNodeToParent(
|
||||||
nodeName: string,
|
nodeName: string,
|
||||||
endpointType: EndpointType,
|
endpointType: EndpointType,
|
||||||
parentNodeName: string,
|
parentNodeName: string,
|
||||||
|
@ -141,6 +142,15 @@ export function addSupplementalNodeToParent(
|
||||||
} else {
|
} else {
|
||||||
getNodeCreatorItems().contains(nodeName).click();
|
getNodeCreatorItems().contains(nodeName).click();
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export function addSupplementalNodeToParent(
|
||||||
|
nodeName: string,
|
||||||
|
endpointType: EndpointType,
|
||||||
|
parentNodeName: string,
|
||||||
|
exactMatch = false,
|
||||||
|
) {
|
||||||
|
connectNodeToParent(nodeName, endpointType, parentNodeName, exactMatch);
|
||||||
getConnectionBySourceAndTarget(parentNodeName, nodeName).should('exist');
|
getConnectionBySourceAndTarget(parentNodeName, nodeName).should('exist');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -160,6 +170,15 @@ export function addToolNodeToParent(nodeName: string, parentNodeName: string) {
|
||||||
addSupplementalNodeToParent(nodeName, 'ai_tool', parentNodeName);
|
addSupplementalNodeToParent(nodeName, 'ai_tool', parentNodeName);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function addVectorStoreToolToParent(nodeName: string, parentNodeName: string) {
|
||||||
|
connectNodeToParent(nodeName, 'ai_tool', parentNodeName, false);
|
||||||
|
getParameterInputByName('mode')
|
||||||
|
.find('input')
|
||||||
|
.should('have.value', 'Retrieve Documents (As Tool for AI Agent)');
|
||||||
|
clickGetBackToCanvas();
|
||||||
|
getConnectionBySourceAndTarget(nodeName, parentNodeName).should('exist');
|
||||||
|
}
|
||||||
|
|
||||||
export function addOutputParserNodeToParent(nodeName: string, parentNodeName: string) {
|
export function addOutputParserNodeToParent(nodeName: string, parentNodeName: string) {
|
||||||
addSupplementalNodeToParent(nodeName, 'ai_outputParser', parentNodeName);
|
addSupplementalNodeToParent(nodeName, 'ai_outputParser', parentNodeName);
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,10 +1,12 @@
|
||||||
|
import { clickGetBackToCanvas } from '../composables/ndv';
|
||||||
import {
|
import {
|
||||||
addNodeToCanvas,
|
addNodeToCanvas,
|
||||||
addRetrieverNodeToParent,
|
addRetrieverNodeToParent,
|
||||||
addVectorStoreNodeToParent,
|
addVectorStoreNodeToParent,
|
||||||
|
addVectorStoreToolToParent,
|
||||||
getNodeCreatorItems,
|
getNodeCreatorItems,
|
||||||
} from '../composables/workflow';
|
} from '../composables/workflow';
|
||||||
import { IF_NODE_NAME } from '../constants';
|
import { AGENT_NODE_NAME, IF_NODE_NAME, MANUAL_CHAT_TRIGGER_NODE_NAME } from '../constants';
|
||||||
import { NodeCreator } from '../pages/features/node-creator';
|
import { NodeCreator } from '../pages/features/node-creator';
|
||||||
import { NDV } from '../pages/ndv';
|
import { NDV } from '../pages/ndv';
|
||||||
import { WorkflowPage as WorkflowPageClass } from '../pages/workflow';
|
import { WorkflowPage as WorkflowPageClass } from '../pages/workflow';
|
||||||
|
@ -536,7 +538,7 @@ describe('Node Creator', () => {
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should add node directly for sub-connection', () => {
|
it('should add node directly for sub-connection as vector store', () => {
|
||||||
addNodeToCanvas('Question and Answer Chain', true);
|
addNodeToCanvas('Question and Answer Chain', true);
|
||||||
addRetrieverNodeToParent('Vector Store Retriever', 'Question and Answer Chain');
|
addRetrieverNodeToParent('Vector Store Retriever', 'Question and Answer Chain');
|
||||||
cy.realPress('Escape');
|
cy.realPress('Escape');
|
||||||
|
@ -544,4 +546,12 @@ describe('Node Creator', () => {
|
||||||
cy.realPress('Escape');
|
cy.realPress('Escape');
|
||||||
WorkflowPage.getters.canvasNodes().should('have.length', 4);
|
WorkflowPage.getters.canvasNodes().should('have.length', 4);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it('should add node directly for sub-connection as tool', () => {
|
||||||
|
addNodeToCanvas(MANUAL_CHAT_TRIGGER_NODE_NAME, true);
|
||||||
|
addNodeToCanvas(AGENT_NODE_NAME, true, true);
|
||||||
|
clickGetBackToCanvas();
|
||||||
|
|
||||||
|
addVectorStoreToolToParent('In-Memory Vector Store', AGENT_NODE_NAME);
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
|
@ -15,15 +15,15 @@ import { getConnectionHintNoticeField } from '@utils/sharedFields';
|
||||||
|
|
||||||
export class ToolVectorStore implements INodeType {
|
export class ToolVectorStore implements INodeType {
|
||||||
description: INodeTypeDescription = {
|
description: INodeTypeDescription = {
|
||||||
displayName: 'Vector Store Tool',
|
displayName: 'Vector Store Question Answer Tool',
|
||||||
name: 'toolVectorStore',
|
name: 'toolVectorStore',
|
||||||
icon: 'fa:database',
|
icon: 'fa:database',
|
||||||
iconColor: 'black',
|
iconColor: 'black',
|
||||||
group: ['transform'],
|
group: ['transform'],
|
||||||
version: [1],
|
version: [1],
|
||||||
description: 'Retrieve context from vector store',
|
description: 'Answer questions with a vector store',
|
||||||
defaults: {
|
defaults: {
|
||||||
name: 'Vector Store Tool',
|
name: 'Answer questions with a vector store',
|
||||||
},
|
},
|
||||||
codex: {
|
codex: {
|
||||||
categories: ['AI'],
|
categories: ['AI'],
|
||||||
|
@ -60,20 +60,23 @@ export class ToolVectorStore implements INodeType {
|
||||||
properties: [
|
properties: [
|
||||||
getConnectionHintNoticeField([NodeConnectionType.AiAgent]),
|
getConnectionHintNoticeField([NodeConnectionType.AiAgent]),
|
||||||
{
|
{
|
||||||
displayName: 'Name',
|
displayName: 'Data Name',
|
||||||
name: 'name',
|
name: 'name',
|
||||||
type: 'string',
|
type: 'string',
|
||||||
default: '',
|
default: '',
|
||||||
placeholder: 'e.g. company_knowledge_base',
|
placeholder: 'e.g. users_info',
|
||||||
validateType: 'string-alphanumeric',
|
validateType: 'string-alphanumeric',
|
||||||
description: 'Name of the vector store',
|
description:
|
||||||
|
'Name of the data in vector store. This will be used to fill this tool description: Useful for when you need to answer questions about [name]. Whenever you need information about [data description], you should ALWAYS use this. Input should be a fully formed question.',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
displayName: 'Description',
|
displayName: 'Description of Data',
|
||||||
name: 'description',
|
name: 'description',
|
||||||
type: 'string',
|
type: 'string',
|
||||||
default: '',
|
default: '',
|
||||||
placeholder: 'Retrieves data about [insert information about your data here]...',
|
placeholder: "[Describe your data here, e.g. a user's name, email, etc.]",
|
||||||
|
description:
|
||||||
|
'Describe the data in vector store. This will be used to fill this tool description: Useful for when you need to answer questions about [name]. Whenever you need information about [data description], you should ALWAYS use this. Input should be a fully formed question.',
|
||||||
typeOptions: {
|
typeOptions: {
|
||||||
rows: 3,
|
rows: 3,
|
||||||
},
|
},
|
||||||
|
|
|
@ -228,7 +228,7 @@ export class VectorStorePGVector extends createVectorStoreNode({
|
||||||
testedBy: 'postgresConnectionTest',
|
testedBy: 'postgresConnectionTest',
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
operationModes: ['load', 'insert', 'retrieve'],
|
operationModes: ['load', 'insert', 'retrieve', 'retrieve-as-tool'],
|
||||||
},
|
},
|
||||||
sharedFields,
|
sharedFields,
|
||||||
insertFields,
|
insertFields,
|
||||||
|
|
|
@ -65,7 +65,7 @@ export class VectorStorePinecone extends createVectorStoreNode({
|
||||||
required: true,
|
required: true,
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
operationModes: ['load', 'insert', 'retrieve', 'update'],
|
operationModes: ['load', 'insert', 'retrieve', 'update', 'retrieve-as-tool'],
|
||||||
},
|
},
|
||||||
methods: { listSearch: { pineconeIndexSearch } },
|
methods: { listSearch: { pineconeIndexSearch } },
|
||||||
retrieveFields,
|
retrieveFields,
|
||||||
|
|
|
@ -55,7 +55,7 @@ export class VectorStoreSupabase extends createVectorStoreNode({
|
||||||
required: true,
|
required: true,
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
operationModes: ['load', 'insert', 'retrieve', 'update'],
|
operationModes: ['load', 'insert', 'retrieve', 'update', 'retrieve-as-tool'],
|
||||||
},
|
},
|
||||||
methods: {
|
methods: {
|
||||||
listSearch: { supabaseTableNameSearch },
|
listSearch: { supabaseTableNameSearch },
|
||||||
|
|
|
@ -0,0 +1,161 @@
|
||||||
|
import type { DocumentInterface } from '@langchain/core/documents';
|
||||||
|
import type { Embeddings } from '@langchain/core/embeddings';
|
||||||
|
import type { VectorStore } from '@langchain/core/vectorstores';
|
||||||
|
import { mock } from 'jest-mock-extended';
|
||||||
|
import type { DynamicTool } from 'langchain/tools';
|
||||||
|
import type { ISupplyDataFunctions, NodeParameterValueType } from 'n8n-workflow';
|
||||||
|
|
||||||
|
import type { VectorStoreNodeConstructorArgs } from './createVectorStoreNode';
|
||||||
|
import { createVectorStoreNode } from './createVectorStoreNode';
|
||||||
|
|
||||||
|
jest.mock('@utils/logWrapper', () => ({
|
||||||
|
logWrapper: jest.fn().mockImplementation((val: DynamicTool) => ({ logWrapped: val })),
|
||||||
|
}));
|
||||||
|
|
||||||
|
const DEFAULT_PARAMETERS = {
|
||||||
|
options: {},
|
||||||
|
topK: 1,
|
||||||
|
};
|
||||||
|
|
||||||
|
const MOCK_DOCUMENTS: Array<[DocumentInterface, number]> = [
|
||||||
|
[
|
||||||
|
{
|
||||||
|
pageContent: 'first page',
|
||||||
|
metadata: {
|
||||||
|
id: 123,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
0,
|
||||||
|
],
|
||||||
|
[
|
||||||
|
{
|
||||||
|
pageContent: 'second page',
|
||||||
|
metadata: {
|
||||||
|
id: 567,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
0,
|
||||||
|
],
|
||||||
|
];
|
||||||
|
|
||||||
|
const MOCK_SEARCH_VALUE = 'search value';
|
||||||
|
const MOCK_EMBEDDED_SEARCH_VALUE = [1, 2, 3];
|
||||||
|
|
||||||
|
describe('createVectorStoreNode', () => {
|
||||||
|
const vectorStore = mock<VectorStore>({
|
||||||
|
similaritySearchVectorWithScore: jest.fn().mockResolvedValue(MOCK_DOCUMENTS),
|
||||||
|
});
|
||||||
|
|
||||||
|
const vectorStoreNodeArgs = mock<VectorStoreNodeConstructorArgs>({
|
||||||
|
sharedFields: [],
|
||||||
|
insertFields: [],
|
||||||
|
loadFields: [],
|
||||||
|
retrieveFields: [],
|
||||||
|
updateFields: [],
|
||||||
|
getVectorStoreClient: jest.fn().mockReturnValue(vectorStore),
|
||||||
|
});
|
||||||
|
|
||||||
|
const embeddings = mock<Embeddings>({
|
||||||
|
embedQuery: jest.fn().mockResolvedValue(MOCK_EMBEDDED_SEARCH_VALUE),
|
||||||
|
});
|
||||||
|
|
||||||
|
const context = mock<ISupplyDataFunctions>({
|
||||||
|
getNodeParameter: jest.fn(),
|
||||||
|
getInputConnectionData: jest.fn().mockReturnValue(embeddings),
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('retrieve mode', () => {
|
||||||
|
it('supplies vector store as data', async () => {
|
||||||
|
// ARRANGE
|
||||||
|
const parameters: Record<string, NodeParameterValueType | object> = {
|
||||||
|
...DEFAULT_PARAMETERS,
|
||||||
|
mode: 'retrieve',
|
||||||
|
};
|
||||||
|
context.getNodeParameter.mockImplementation(
|
||||||
|
(parameterName: string): NodeParameterValueType | object => parameters[parameterName],
|
||||||
|
);
|
||||||
|
|
||||||
|
// ACT
|
||||||
|
const VectorStoreNodeType = createVectorStoreNode(vectorStoreNodeArgs);
|
||||||
|
const nodeType = new VectorStoreNodeType();
|
||||||
|
const data = await nodeType.supplyData.call(context, 1);
|
||||||
|
const wrappedVectorStore = (data.response as { logWrapped: VectorStore }).logWrapped;
|
||||||
|
|
||||||
|
// ASSERT
|
||||||
|
expect(wrappedVectorStore).toEqual(vectorStore);
|
||||||
|
expect(vectorStoreNodeArgs.getVectorStoreClient).toHaveBeenCalled();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('retrieve-as-tool mode', () => {
|
||||||
|
it('supplies DynamicTool that queries vector store and returns documents with metadata', async () => {
|
||||||
|
// ARRANGE
|
||||||
|
const parameters: Record<string, NodeParameterValueType | object> = {
|
||||||
|
...DEFAULT_PARAMETERS,
|
||||||
|
mode: 'retrieve-as-tool',
|
||||||
|
description: 'tool description',
|
||||||
|
toolName: 'tool name',
|
||||||
|
includeDocumentMetadata: true,
|
||||||
|
};
|
||||||
|
context.getNodeParameter.mockImplementation(
|
||||||
|
(parameterName: string): NodeParameterValueType | object => parameters[parameterName],
|
||||||
|
);
|
||||||
|
|
||||||
|
// ACT
|
||||||
|
const VectorStoreNodeType = createVectorStoreNode(vectorStoreNodeArgs);
|
||||||
|
const nodeType = new VectorStoreNodeType();
|
||||||
|
const data = await nodeType.supplyData.call(context, 1);
|
||||||
|
const tool = (data.response as { logWrapped: DynamicTool }).logWrapped;
|
||||||
|
const output = await tool?.func(MOCK_SEARCH_VALUE);
|
||||||
|
|
||||||
|
// ASSERT
|
||||||
|
expect(tool?.getName()).toEqual(parameters.toolName);
|
||||||
|
expect(tool?.description).toEqual(parameters.toolDescription);
|
||||||
|
expect(embeddings.embedQuery).toHaveBeenCalledWith(MOCK_SEARCH_VALUE);
|
||||||
|
expect(vectorStore.similaritySearchVectorWithScore).toHaveBeenCalledWith(
|
||||||
|
MOCK_EMBEDDED_SEARCH_VALUE,
|
||||||
|
parameters.topK,
|
||||||
|
parameters.filter,
|
||||||
|
);
|
||||||
|
expect(output).toEqual([
|
||||||
|
{ type: 'text', text: JSON.stringify(MOCK_DOCUMENTS[0][0]) },
|
||||||
|
{ type: 'text', text: JSON.stringify(MOCK_DOCUMENTS[1][0]) },
|
||||||
|
]);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('supplies DynamicTool that queries vector store and returns documents without metadata', async () => {
|
||||||
|
// ARRANGE
|
||||||
|
const parameters: Record<string, NodeParameterValueType | object> = {
|
||||||
|
...DEFAULT_PARAMETERS,
|
||||||
|
mode: 'retrieve-as-tool',
|
||||||
|
description: 'tool description',
|
||||||
|
toolName: 'tool name',
|
||||||
|
includeDocumentMetadata: false,
|
||||||
|
};
|
||||||
|
context.getNodeParameter.mockImplementation(
|
||||||
|
(parameterName: string): NodeParameterValueType | object => parameters[parameterName],
|
||||||
|
);
|
||||||
|
|
||||||
|
// ACT
|
||||||
|
const VectorStoreNodeType = createVectorStoreNode(vectorStoreNodeArgs);
|
||||||
|
const nodeType = new VectorStoreNodeType();
|
||||||
|
const data = await nodeType.supplyData.call(context, 1);
|
||||||
|
const tool = (data.response as { logWrapped: DynamicTool }).logWrapped;
|
||||||
|
const output = await tool?.func(MOCK_SEARCH_VALUE);
|
||||||
|
|
||||||
|
// ASSERT
|
||||||
|
expect(tool?.getName()).toEqual(parameters.toolName);
|
||||||
|
expect(tool?.description).toEqual(parameters.toolDescription);
|
||||||
|
expect(embeddings.embedQuery).toHaveBeenCalledWith(MOCK_SEARCH_VALUE);
|
||||||
|
expect(vectorStore.similaritySearchVectorWithScore).toHaveBeenCalledWith(
|
||||||
|
MOCK_EMBEDDED_SEARCH_VALUE,
|
||||||
|
parameters.topK,
|
||||||
|
parameters.filter,
|
||||||
|
);
|
||||||
|
expect(output).toEqual([
|
||||||
|
{ type: 'text', text: JSON.stringify({ pageContent: MOCK_DOCUMENTS[0][0].pageContent }) },
|
||||||
|
{ type: 'text', text: JSON.stringify({ pageContent: MOCK_DOCUMENTS[1][0].pageContent }) },
|
||||||
|
]);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
|
@ -3,6 +3,7 @@
|
||||||
import type { Document } from '@langchain/core/documents';
|
import type { Document } from '@langchain/core/documents';
|
||||||
import type { Embeddings } from '@langchain/core/embeddings';
|
import type { Embeddings } from '@langchain/core/embeddings';
|
||||||
import type { VectorStore } from '@langchain/core/vectorstores';
|
import type { VectorStore } from '@langchain/core/vectorstores';
|
||||||
|
import { DynamicTool } from 'langchain/tools';
|
||||||
import { NodeConnectionType, NodeOperationError } from 'n8n-workflow';
|
import { NodeConnectionType, NodeOperationError } from 'n8n-workflow';
|
||||||
import type {
|
import type {
|
||||||
IExecuteFunctions,
|
IExecuteFunctions,
|
||||||
|
@ -28,9 +29,14 @@ import { getConnectionHintNoticeField } from '@utils/sharedFields';
|
||||||
|
|
||||||
import { processDocument } from './processDocuments';
|
import { processDocument } from './processDocuments';
|
||||||
|
|
||||||
type NodeOperationMode = 'insert' | 'load' | 'retrieve' | 'update';
|
type NodeOperationMode = 'insert' | 'load' | 'retrieve' | 'update' | 'retrieve-as-tool';
|
||||||
|
|
||||||
const DEFAULT_OPERATION_MODES: NodeOperationMode[] = ['load', 'insert', 'retrieve'];
|
const DEFAULT_OPERATION_MODES: NodeOperationMode[] = [
|
||||||
|
'load',
|
||||||
|
'insert',
|
||||||
|
'retrieve',
|
||||||
|
'retrieve-as-tool',
|
||||||
|
];
|
||||||
|
|
||||||
interface NodeMeta {
|
interface NodeMeta {
|
||||||
displayName: string;
|
displayName: string;
|
||||||
|
@ -43,7 +49,7 @@ interface NodeMeta {
|
||||||
operationModes?: NodeOperationMode[];
|
operationModes?: NodeOperationMode[];
|
||||||
}
|
}
|
||||||
|
|
||||||
interface VectorStoreNodeConstructorArgs {
|
export interface VectorStoreNodeConstructorArgs {
|
||||||
meta: NodeMeta;
|
meta: NodeMeta;
|
||||||
methods?: {
|
methods?: {
|
||||||
listSearch?: {
|
listSearch?: {
|
||||||
|
@ -102,10 +108,18 @@ function getOperationModeOptions(args: VectorStoreNodeConstructorArgs): INodePro
|
||||||
action: 'Add documents to vector store',
|
action: 'Add documents to vector store',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: 'Retrieve Documents (For Agent/Chain)',
|
name: 'Retrieve Documents (As Vector Store for AI Agent)',
|
||||||
value: 'retrieve',
|
value: 'retrieve',
|
||||||
description: 'Retrieve documents from vector store to be used with AI nodes',
|
description: 'Retrieve documents from vector store to be used as vector store with AI nodes',
|
||||||
action: 'Retrieve documents for AI processing',
|
action: 'Retrieve documents for AI processing as Vector Store',
|
||||||
|
outputConnectionType: NodeConnectionType.AiVectorStore,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'Retrieve Documents (As Tool for AI Agent)',
|
||||||
|
value: 'retrieve-as-tool',
|
||||||
|
description: 'Retrieve documents from vector store to be used as tool with AI nodes',
|
||||||
|
action: 'Retrieve documents for AI processing as Tool',
|
||||||
|
outputConnectionType: NodeConnectionType.AiTool,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: 'Update Documents',
|
name: 'Update Documents',
|
||||||
|
@ -136,7 +150,8 @@ export const createVectorStoreNode = (args: VectorStoreNodeConstructorArgs) =>
|
||||||
codex: {
|
codex: {
|
||||||
categories: ['AI'],
|
categories: ['AI'],
|
||||||
subcategories: {
|
subcategories: {
|
||||||
AI: ['Vector Stores', 'Root Nodes'],
|
AI: ['Vector Stores', 'Tools', 'Root Nodes'],
|
||||||
|
Tools: ['Other Tools'],
|
||||||
},
|
},
|
||||||
resources: {
|
resources: {
|
||||||
primaryDocumentation: [
|
primaryDocumentation: [
|
||||||
|
@ -153,6 +168,10 @@ export const createVectorStoreNode = (args: VectorStoreNodeConstructorArgs) =>
|
||||||
const mode = parameters?.mode;
|
const mode = parameters?.mode;
|
||||||
const inputs = [{ displayName: "Embedding", type: "${NodeConnectionType.AiEmbedding}", required: true, maxConnections: 1}]
|
const inputs = [{ displayName: "Embedding", type: "${NodeConnectionType.AiEmbedding}", required: true, maxConnections: 1}]
|
||||||
|
|
||||||
|
if (mode === 'retrieve-as-tool') {
|
||||||
|
return inputs;
|
||||||
|
}
|
||||||
|
|
||||||
if (['insert', 'load', 'update'].includes(mode)) {
|
if (['insert', 'load', 'update'].includes(mode)) {
|
||||||
inputs.push({ displayName: "", type: "${NodeConnectionType.Main}"})
|
inputs.push({ displayName: "", type: "${NodeConnectionType.Main}"})
|
||||||
}
|
}
|
||||||
|
@ -166,6 +185,11 @@ export const createVectorStoreNode = (args: VectorStoreNodeConstructorArgs) =>
|
||||||
outputs: `={{
|
outputs: `={{
|
||||||
((parameters) => {
|
((parameters) => {
|
||||||
const mode = parameters?.mode ?? 'retrieve';
|
const mode = parameters?.mode ?? 'retrieve';
|
||||||
|
|
||||||
|
if (mode === 'retrieve-as-tool') {
|
||||||
|
return [{ displayName: "Tool", type: "${NodeConnectionType.AiTool}"}]
|
||||||
|
}
|
||||||
|
|
||||||
if (mode === 'retrieve') {
|
if (mode === 'retrieve') {
|
||||||
return [{ displayName: "Vector Store", type: "${NodeConnectionType.AiVectorStore}"}]
|
return [{ displayName: "Vector Store", type: "${NodeConnectionType.AiVectorStore}"}]
|
||||||
}
|
}
|
||||||
|
@ -189,6 +213,37 @@ export const createVectorStoreNode = (args: VectorStoreNodeConstructorArgs) =>
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
displayName: 'Name',
|
||||||
|
name: 'toolName',
|
||||||
|
type: 'string',
|
||||||
|
default: '',
|
||||||
|
required: true,
|
||||||
|
description: 'Name of the vector store',
|
||||||
|
placeholder: 'e.g. company_knowledge_base',
|
||||||
|
validateType: 'string-alphanumeric',
|
||||||
|
displayOptions: {
|
||||||
|
show: {
|
||||||
|
mode: ['retrieve-as-tool'],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
displayName: 'Description',
|
||||||
|
name: 'toolDescription',
|
||||||
|
type: 'string',
|
||||||
|
default: '',
|
||||||
|
required: true,
|
||||||
|
typeOptions: { rows: 2 },
|
||||||
|
description:
|
||||||
|
'Explain to the LLM what this tool does, a good, specific description would allow LLMs to produce expected results much more often',
|
||||||
|
placeholder: `e.g. ${args.meta.description}`,
|
||||||
|
displayOptions: {
|
||||||
|
show: {
|
||||||
|
mode: ['retrieve-as-tool'],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
...args.sharedFields,
|
...args.sharedFields,
|
||||||
...transformDescriptionForOperationMode(args.insertFields ?? [], 'insert'),
|
...transformDescriptionForOperationMode(args.insertFields ?? [], 'insert'),
|
||||||
// Prompt and topK are always used for the load operation
|
// Prompt and topK are always used for the load operation
|
||||||
|
@ -214,7 +269,19 @@ export const createVectorStoreNode = (args: VectorStoreNodeConstructorArgs) =>
|
||||||
description: 'Number of top results to fetch from vector store',
|
description: 'Number of top results to fetch from vector store',
|
||||||
displayOptions: {
|
displayOptions: {
|
||||||
show: {
|
show: {
|
||||||
mode: ['load'],
|
mode: ['load', 'retrieve-as-tool'],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
displayName: 'Include Metadata',
|
||||||
|
name: 'includeDocumentMetadata',
|
||||||
|
type: 'boolean',
|
||||||
|
default: true,
|
||||||
|
description: 'Whether or not to include document metadata',
|
||||||
|
displayOptions: {
|
||||||
|
show: {
|
||||||
|
mode: ['load', 'retrieve-as-tool'],
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
@ -271,10 +338,16 @@ export const createVectorStoreNode = (args: VectorStoreNodeConstructorArgs) =>
|
||||||
filter,
|
filter,
|
||||||
);
|
);
|
||||||
|
|
||||||
|
const includeDocumentMetadata = this.getNodeParameter(
|
||||||
|
'includeDocumentMetadata',
|
||||||
|
itemIndex,
|
||||||
|
true,
|
||||||
|
) as boolean;
|
||||||
|
|
||||||
const serializedDocs = docs.map(([doc, score]) => {
|
const serializedDocs = docs.map(([doc, score]) => {
|
||||||
const document = {
|
const document = {
|
||||||
metadata: doc.metadata,
|
|
||||||
pageContent: doc.pageContent,
|
pageContent: doc.pageContent,
|
||||||
|
...(includeDocumentMetadata ? { metadata: doc.metadata } : {}),
|
||||||
};
|
};
|
||||||
|
|
||||||
return {
|
return {
|
||||||
|
@ -381,12 +454,12 @@ export const createVectorStoreNode = (args: VectorStoreNodeConstructorArgs) =>
|
||||||
|
|
||||||
throw new NodeOperationError(
|
throw new NodeOperationError(
|
||||||
this.getNode(),
|
this.getNode(),
|
||||||
'Only the "load" and "insert" operation modes are supported with execute',
|
'Only the "load", "update" and "insert" operation modes are supported with execute',
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
async supplyData(this: ISupplyDataFunctions, itemIndex: number): Promise<SupplyData> {
|
async supplyData(this: ISupplyDataFunctions, itemIndex: number): Promise<SupplyData> {
|
||||||
const mode = this.getNodeParameter('mode', 0) as 'load' | 'insert' | 'retrieve';
|
const mode = this.getNodeParameter('mode', 0) as NodeOperationMode;
|
||||||
const filter = getMetadataFiltersValues(this, itemIndex);
|
const filter = getMetadataFiltersValues(this, itemIndex);
|
||||||
const embeddings = (await this.getInputConnectionData(
|
const embeddings = (await this.getInputConnectionData(
|
||||||
NodeConnectionType.AiEmbedding,
|
NodeConnectionType.AiEmbedding,
|
||||||
|
@ -400,9 +473,54 @@ export const createVectorStoreNode = (args: VectorStoreNodeConstructorArgs) =>
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (mode === 'retrieve-as-tool') {
|
||||||
|
const toolDescription = this.getNodeParameter('toolDescription', itemIndex) as string;
|
||||||
|
const toolName = this.getNodeParameter('toolName', itemIndex) as string;
|
||||||
|
const topK = this.getNodeParameter('topK', itemIndex, 4) as number;
|
||||||
|
const includeDocumentMetadata = this.getNodeParameter(
|
||||||
|
'includeDocumentMetadata',
|
||||||
|
itemIndex,
|
||||||
|
true,
|
||||||
|
) as boolean;
|
||||||
|
|
||||||
|
const vectorStoreTool = new DynamicTool({
|
||||||
|
name: toolName,
|
||||||
|
description: toolDescription,
|
||||||
|
func: async (input) => {
|
||||||
|
const vectorStore = await args.getVectorStoreClient(
|
||||||
|
this,
|
||||||
|
filter,
|
||||||
|
embeddings,
|
||||||
|
itemIndex,
|
||||||
|
);
|
||||||
|
const embeddedPrompt = await embeddings.embedQuery(input);
|
||||||
|
const documents = await vectorStore.similaritySearchVectorWithScore(
|
||||||
|
embeddedPrompt,
|
||||||
|
topK,
|
||||||
|
filter,
|
||||||
|
);
|
||||||
|
return documents
|
||||||
|
.map((document) => {
|
||||||
|
if (includeDocumentMetadata) {
|
||||||
|
return { type: 'text', text: JSON.stringify(document[0]) };
|
||||||
|
}
|
||||||
|
return {
|
||||||
|
type: 'text',
|
||||||
|
text: JSON.stringify({ pageContent: document[0].pageContent }),
|
||||||
|
};
|
||||||
|
})
|
||||||
|
.filter((document) => !!document);
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
return {
|
||||||
|
response: logWrapper(vectorStoreTool, this),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
throw new NodeOperationError(
|
throw new NodeOperationError(
|
||||||
this.getNode(),
|
this.getNode(),
|
||||||
'Only the "retrieve" operation mode is supported to supply data',
|
'Only the "retrieve" and "retrieve-as-tool" operation mode is supported to supply data',
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
|
@ -720,6 +720,7 @@ export interface ActionTypeDescription extends SimplifiedNodeType {
|
||||||
displayOptions?: IDisplayOptions;
|
displayOptions?: IDisplayOptions;
|
||||||
values?: IDataObject;
|
values?: IDataObject;
|
||||||
actionKey: string;
|
actionKey: string;
|
||||||
|
outputConnectionType?: NodeConnectionType;
|
||||||
codex: {
|
codex: {
|
||||||
label: string;
|
label: string;
|
||||||
categories: string[];
|
categories: string[];
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { camelCase } from 'lodash-es';
|
import { camelCase } from 'lodash-es';
|
||||||
import { computed } from 'vue';
|
import { computed } from 'vue';
|
||||||
import type { INodeCreateElement, NodeFilterType } from '@/Interface';
|
import type { INodeCreateElement, NodeCreateElement, NodeFilterType } from '@/Interface';
|
||||||
import {
|
import {
|
||||||
TRIGGER_NODE_CREATOR_VIEW,
|
TRIGGER_NODE_CREATOR_VIEW,
|
||||||
HTTP_REQUEST_NODE_TYPE,
|
HTTP_REQUEST_NODE_TYPE,
|
||||||
|
@ -25,6 +25,8 @@ import NoResults from '../Panel/NoResults.vue';
|
||||||
import { useI18n } from '@/composables/useI18n';
|
import { useI18n } from '@/composables/useI18n';
|
||||||
import { getNodeIcon, getNodeIconColor, getNodeIconUrl } from '@/utils/nodeTypesUtils';
|
import { getNodeIcon, getNodeIconColor, getNodeIconUrl } from '@/utils/nodeTypesUtils';
|
||||||
import { useUIStore } from '@/stores/ui.store';
|
import { useUIStore } from '@/stores/ui.store';
|
||||||
|
import { useActions } from '../composables/useActions';
|
||||||
|
import type { INodeParameters } from 'n8n-workflow';
|
||||||
|
|
||||||
export interface Props {
|
export interface Props {
|
||||||
rootView: 'trigger' | 'action';
|
rootView: 'trigger' | 'action';
|
||||||
|
@ -40,12 +42,21 @@ const rootStore = useRootStore();
|
||||||
|
|
||||||
const { mergedNodes, actions, onSubcategorySelected } = useNodeCreatorStore();
|
const { mergedNodes, actions, onSubcategorySelected } = useNodeCreatorStore();
|
||||||
const { pushViewStack, popViewStack } = useViewStacks();
|
const { pushViewStack, popViewStack } = useViewStacks();
|
||||||
|
const { setAddedNodeActionParameters } = useActions();
|
||||||
|
|
||||||
const { registerKeyHook } = useKeyboardNavigation();
|
const { registerKeyHook } = useKeyboardNavigation();
|
||||||
|
|
||||||
const activeViewStack = computed(() => useViewStacks().activeViewStack);
|
const activeViewStack = computed(() => useViewStacks().activeViewStack);
|
||||||
const globalSearchItemsDiff = computed(() => useViewStacks().globalSearchItemsDiff);
|
const globalSearchItemsDiff = computed(() => useViewStacks().globalSearchItemsDiff);
|
||||||
|
|
||||||
|
function getFilteredActions(node: NodeCreateElement) {
|
||||||
|
const nodeActions = actions?.[node.key] || [];
|
||||||
|
if (activeViewStack.value.actionsFilter) {
|
||||||
|
return activeViewStack.value.actionsFilter(nodeActions);
|
||||||
|
}
|
||||||
|
return nodeActions;
|
||||||
|
}
|
||||||
|
|
||||||
function selectNodeType(nodeTypes: string[]) {
|
function selectNodeType(nodeTypes: string[]) {
|
||||||
emit('nodeTypeSelected', nodeTypes);
|
emit('nodeTypeSelected', nodeTypes);
|
||||||
}
|
}
|
||||||
|
@ -87,9 +98,21 @@ function onSelected(item: INodeCreateElement) {
|
||||||
}
|
}
|
||||||
|
|
||||||
if (item.type === 'node') {
|
if (item.type === 'node') {
|
||||||
const nodeActions = actions?.[item.key] || [];
|
const nodeActions = getFilteredActions(item);
|
||||||
|
|
||||||
|
// If there is only one action, use it
|
||||||
|
if (nodeActions.length === 1) {
|
||||||
|
selectNodeType([item.key]);
|
||||||
|
setAddedNodeActionParameters({
|
||||||
|
name: nodeActions[0].defaults.name ?? item.properties.displayName,
|
||||||
|
key: item.key,
|
||||||
|
value: nodeActions[0].values as INodeParameters,
|
||||||
|
});
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
// Only show actions if there are more than one or if the view is not an AI subcategory
|
// Only show actions if there are more than one or if the view is not an AI subcategory
|
||||||
if (nodeActions.length <= 1 || activeViewStack.value.hideActions) {
|
if (nodeActions.length === 0 || activeViewStack.value.hideActions) {
|
||||||
selectNodeType([item.key]);
|
selectNodeType([item.key]);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
@ -158,7 +181,7 @@ function subcategoriesMapper(item: INodeCreateElement) {
|
||||||
if (item.type !== 'node') return item;
|
if (item.type !== 'node') return item;
|
||||||
|
|
||||||
const hasTriggerGroup = item.properties.group.includes('trigger');
|
const hasTriggerGroup = item.properties.group.includes('trigger');
|
||||||
const nodeActions = actions?.[item.key] || [];
|
const nodeActions = getFilteredActions(item);
|
||||||
const hasActions = nodeActions.length > 0;
|
const hasActions = nodeActions.length > 0;
|
||||||
|
|
||||||
if (hasTriggerGroup && hasActions) {
|
if (hasTriggerGroup && hasActions) {
|
||||||
|
@ -179,7 +202,7 @@ function baseSubcategoriesFilter(item: INodeCreateElement): boolean {
|
||||||
if (item.type !== 'node') return false;
|
if (item.type !== 'node') return false;
|
||||||
|
|
||||||
const hasTriggerGroup = item.properties.group.includes('trigger');
|
const hasTriggerGroup = item.properties.group.includes('trigger');
|
||||||
const nodeActions = actions?.[item.key] || [];
|
const nodeActions = getFilteredActions(item);
|
||||||
const hasActions = nodeActions.length > 0;
|
const hasActions = nodeActions.length > 0;
|
||||||
|
|
||||||
const isTriggerRootView = activeViewStack.value.rootView === TRIGGER_NODE_CREATOR_VIEW;
|
const isTriggerRootView = activeViewStack.value.rootView === TRIGGER_NODE_CREATOR_VIEW;
|
||||||
|
|
|
@ -1,5 +1,11 @@
|
||||||
import type { ActionTypeDescription, ActionsRecord, SimplifiedNodeType } from '@/Interface';
|
import type { ActionTypeDescription, ActionsRecord, SimplifiedNodeType } from '@/Interface';
|
||||||
import { AI_SUBCATEGORY, CUSTOM_API_CALL_KEY, HTTP_REQUEST_NODE_TYPE } from '@/constants';
|
import {
|
||||||
|
AI_CATEGORY_ROOT_NODES,
|
||||||
|
AI_CATEGORY_TOOLS,
|
||||||
|
AI_SUBCATEGORY,
|
||||||
|
CUSTOM_API_CALL_KEY,
|
||||||
|
HTTP_REQUEST_NODE_TYPE,
|
||||||
|
} from '@/constants';
|
||||||
import { memoize, startCase } from 'lodash-es';
|
import { memoize, startCase } from 'lodash-es';
|
||||||
import type {
|
import type {
|
||||||
ICredentialType,
|
ICredentialType,
|
||||||
|
@ -87,6 +93,7 @@ function operationsCategory(nodeTypeDescription: INodeTypeDescription): ActionTy
|
||||||
displayName: item.action ?? startCase(item.name),
|
displayName: item.action ?? startCase(item.name),
|
||||||
description: item.description ?? '',
|
description: item.description ?? '',
|
||||||
displayOptions: matchedProperty.displayOptions,
|
displayOptions: matchedProperty.displayOptions,
|
||||||
|
outputConnectionType: item.outputConnectionType,
|
||||||
values: {
|
values: {
|
||||||
[matchedProperty.name]: matchedProperty.type === 'multiOptions' ? [item.value] : item.value,
|
[matchedProperty.name]: matchedProperty.type === 'multiOptions' ? [item.value] : item.value,
|
||||||
},
|
},
|
||||||
|
@ -117,6 +124,7 @@ function modeCategory(nodeTypeDescription: INodeTypeDescription): ActionTypeDesc
|
||||||
displayName: item.action ?? startCase(item.name),
|
displayName: item.action ?? startCase(item.name),
|
||||||
description: item.description ?? '',
|
description: item.description ?? '',
|
||||||
displayOptions: matchedProperty.displayOptions,
|
displayOptions: matchedProperty.displayOptions,
|
||||||
|
outputConnectionType: item.outputConnectionType,
|
||||||
values: {
|
values: {
|
||||||
[matchedProperty.name]: item.value,
|
[matchedProperty.name]: item.value,
|
||||||
},
|
},
|
||||||
|
@ -261,7 +269,11 @@ function resourceCategories(nodeTypeDescription: INodeTypeDescription): ActionTy
|
||||||
export function useActionsGenerator() {
|
export function useActionsGenerator() {
|
||||||
function generateNodeActions(node: INodeTypeDescription | undefined) {
|
function generateNodeActions(node: INodeTypeDescription | undefined) {
|
||||||
if (!node) return [];
|
if (!node) return [];
|
||||||
if (node.codex?.subcategories?.AI?.includes('Tools')) return [];
|
if (
|
||||||
|
node.codex?.subcategories?.AI?.includes(AI_CATEGORY_TOOLS) &&
|
||||||
|
!node.codex?.subcategories?.AI?.includes(AI_CATEGORY_ROOT_NODES)
|
||||||
|
)
|
||||||
|
return [];
|
||||||
return [
|
return [
|
||||||
...triggersCategory(node),
|
...triggersCategory(node),
|
||||||
...operationsCategory(node),
|
...operationsCategory(node),
|
||||||
|
@ -269,6 +281,7 @@ export function useActionsGenerator() {
|
||||||
...modeCategory(node),
|
...modeCategory(node),
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
|
||||||
function filterActions(actions: ActionTypeDescription[]) {
|
function filterActions(actions: ActionTypeDescription[]) {
|
||||||
// Do not show single action nodes
|
// Do not show single action nodes
|
||||||
if (actions.length <= 1) return [];
|
if (actions.length <= 1) return [];
|
||||||
|
@ -320,7 +333,6 @@ export function useActionsGenerator() {
|
||||||
const visibleNodeTypes = [...nodeTypes];
|
const visibleNodeTypes = [...nodeTypes];
|
||||||
const actions: ActionsRecord<typeof mergedNodes> = {};
|
const actions: ActionsRecord<typeof mergedNodes> = {};
|
||||||
const mergedNodes: SimplifiedNodeType[] = [];
|
const mergedNodes: SimplifiedNodeType[] = [];
|
||||||
|
|
||||||
visibleNodeTypes
|
visibleNodeTypes
|
||||||
.filter((node) => !node.group.includes('trigger'))
|
.filter((node) => !node.group.includes('trigger'))
|
||||||
.forEach((app) => {
|
.forEach((app) => {
|
||||||
|
|
|
@ -1,4 +1,5 @@
|
||||||
import type {
|
import type {
|
||||||
|
ActionTypeDescription,
|
||||||
INodeCreateElement,
|
INodeCreateElement,
|
||||||
NodeCreateElement,
|
NodeCreateElement,
|
||||||
NodeFilterType,
|
NodeFilterType,
|
||||||
|
@ -6,6 +7,7 @@ import type {
|
||||||
} from '@/Interface';
|
} from '@/Interface';
|
||||||
import {
|
import {
|
||||||
AI_CATEGORY_ROOT_NODES,
|
AI_CATEGORY_ROOT_NODES,
|
||||||
|
AI_CATEGORY_TOOLS,
|
||||||
AI_CODE_NODE_TYPE,
|
AI_CODE_NODE_TYPE,
|
||||||
AI_NODE_CREATOR_VIEW,
|
AI_NODE_CREATOR_VIEW,
|
||||||
AI_OTHERS_NODE_CREATOR_VIEW,
|
AI_OTHERS_NODE_CREATOR_VIEW,
|
||||||
|
@ -36,12 +38,8 @@ import { useI18n } from '@/composables/useI18n';
|
||||||
import { useKeyboardNavigation } from './useKeyboardNavigation';
|
import { useKeyboardNavigation } from './useKeyboardNavigation';
|
||||||
|
|
||||||
import { useNodeTypesStore } from '@/stores/nodeTypes.store';
|
import { useNodeTypesStore } from '@/stores/nodeTypes.store';
|
||||||
import {
|
import { AI_TRANSFORM_NODE_TYPE } from 'n8n-workflow';
|
||||||
AI_TRANSFORM_NODE_TYPE,
|
import type { NodeConnectionType, INodeInputFilter, Themed } from 'n8n-workflow';
|
||||||
type INodeInputFilter,
|
|
||||||
type NodeConnectionType,
|
|
||||||
type Themed,
|
|
||||||
} from 'n8n-workflow';
|
|
||||||
import { useCanvasStore } from '@/stores/canvas.store';
|
import { useCanvasStore } from '@/stores/canvas.store';
|
||||||
import { useSettingsStore } from '@/stores/settings.store';
|
import { useSettingsStore } from '@/stores/settings.store';
|
||||||
|
|
||||||
|
@ -71,6 +69,7 @@ interface ViewStack {
|
||||||
hideActions?: boolean;
|
hideActions?: boolean;
|
||||||
baseFilter?: (item: INodeCreateElement) => boolean;
|
baseFilter?: (item: INodeCreateElement) => boolean;
|
||||||
itemsMapper?: (item: INodeCreateElement) => INodeCreateElement;
|
itemsMapper?: (item: INodeCreateElement) => INodeCreateElement;
|
||||||
|
actionsFilter?: (items: ActionTypeDescription[]) => ActionTypeDescription[];
|
||||||
panelClass?: string;
|
panelClass?: string;
|
||||||
sections?: string[] | NodeViewItemSection[];
|
sections?: string[] | NodeViewItemSection[];
|
||||||
}
|
}
|
||||||
|
@ -207,8 +206,10 @@ export const useViewStacks = defineStore('nodeCreatorViewStacks', () => {
|
||||||
return items.filter((node) => {
|
return items.filter((node) => {
|
||||||
if (node.type !== 'node') return false;
|
if (node.type !== 'node') return false;
|
||||||
|
|
||||||
return node.properties.codex?.subcategories?.[AI_SUBCATEGORY].includes(
|
const subcategories = node.properties.codex?.subcategories?.[AI_SUBCATEGORY] ?? [];
|
||||||
AI_CATEGORY_ROOT_NODES,
|
return (
|
||||||
|
subcategories.includes(AI_CATEGORY_ROOT_NODES) &&
|
||||||
|
!subcategories?.includes(AI_CATEGORY_TOOLS)
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
@ -346,6 +347,13 @@ export const useViewStacks = defineStore('nodeCreatorViewStacks', () => {
|
||||||
subcategory: connectionType,
|
subcategory: connectionType,
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
|
actionsFilter: (items: ActionTypeDescription[]) => {
|
||||||
|
// Filter out actions that are not compatible with the connection type
|
||||||
|
if (items.some((item) => item.outputConnectionType)) {
|
||||||
|
return items.filter((item) => item.outputConnectionType === connectionType);
|
||||||
|
}
|
||||||
|
return items;
|
||||||
|
},
|
||||||
hideActions: true,
|
hideActions: true,
|
||||||
preventBack: true,
|
preventBack: true,
|
||||||
});
|
});
|
||||||
|
|
|
@ -8,6 +8,7 @@ import type {
|
||||||
} from '@/Interface';
|
} from '@/Interface';
|
||||||
import {
|
import {
|
||||||
AI_CATEGORY_AGENTS,
|
AI_CATEGORY_AGENTS,
|
||||||
|
AI_CATEGORY_OTHER_TOOLS,
|
||||||
AI_SUBCATEGORY,
|
AI_SUBCATEGORY,
|
||||||
AI_TRANSFORM_NODE_TYPE,
|
AI_TRANSFORM_NODE_TYPE,
|
||||||
CORE_NODES_CATEGORY,
|
CORE_NODES_CATEGORY,
|
||||||
|
@ -169,6 +170,7 @@ export function groupItemsInSections(
|
||||||
result.sort((a, b) => {
|
result.sort((a, b) => {
|
||||||
if (a.key.toLowerCase().includes('recommended')) return -1;
|
if (a.key.toLowerCase().includes('recommended')) return -1;
|
||||||
if (b.key.toLowerCase().includes('recommended')) return 1;
|
if (b.key.toLowerCase().includes('recommended')) return 1;
|
||||||
|
if (b.key === AI_CATEGORY_OTHER_TOOLS) return -1;
|
||||||
|
|
||||||
return 0;
|
return 0;
|
||||||
});
|
});
|
||||||
|
|
|
@ -283,6 +283,7 @@ export const AI_CATEGORY_RETRIEVERS = 'Retrievers';
|
||||||
export const AI_CATEGORY_EMBEDDING = 'Embeddings';
|
export const AI_CATEGORY_EMBEDDING = 'Embeddings';
|
||||||
export const AI_CATEGORY_DOCUMENT_LOADERS = 'Document Loaders';
|
export const AI_CATEGORY_DOCUMENT_LOADERS = 'Document Loaders';
|
||||||
export const AI_CATEGORY_TEXT_SPLITTERS = 'Text Splitters';
|
export const AI_CATEGORY_TEXT_SPLITTERS = 'Text Splitters';
|
||||||
|
export const AI_CATEGORY_OTHER_TOOLS = 'Other Tools';
|
||||||
export const AI_CATEGORY_ROOT_NODES = 'Root Nodes';
|
export const AI_CATEGORY_ROOT_NODES = 'Root Nodes';
|
||||||
export const AI_UNCATEGORIZED_CATEGORY = 'Miscellaneous';
|
export const AI_UNCATEGORIZED_CATEGORY = 'Miscellaneous';
|
||||||
export const AI_CODE_TOOL_LANGCHAIN_NODE_TYPE = '@n8n/n8n-nodes-langchain.toolCode';
|
export const AI_CODE_TOOL_LANGCHAIN_NODE_TYPE = '@n8n/n8n-nodes-langchain.toolCode';
|
||||||
|
|
|
@ -1484,6 +1484,7 @@ export interface INodePropertyOptions {
|
||||||
action?: string;
|
action?: string;
|
||||||
description?: string;
|
description?: string;
|
||||||
routing?: INodePropertyRouting;
|
routing?: INodePropertyRouting;
|
||||||
|
outputConnectionType?: NodeConnectionType;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface INodeListSearchItems extends INodePropertyOptions {
|
export interface INodeListSearchItems extends INodePropertyOptions {
|
||||||
|
|
Loading…
Reference in a new issue