Skip to content

Commit

Permalink
feat: Add AI tool building capabilities (#7336)
Browse files Browse the repository at this point in the history
Github issue / Community forum post (link here to close automatically):
https://community.n8n.io/t/langchain-memory-chat/23733

---------

Signed-off-by: Oleg Ivaniv <[email protected]>
Co-authored-by: Oleg Ivaniv <[email protected]>
Co-authored-by: Val <[email protected]>
Co-authored-by: Alex Grozav <[email protected]>
Co-authored-by: कारतोफ्फेलस्क्रिप्ट™ <[email protected]>
Co-authored-by: Deborah <[email protected]>
Co-authored-by: Jesper Bylund <[email protected]>
Co-authored-by: Jon <[email protected]>
Co-authored-by: Michael Kret <[email protected]>
Co-authored-by: Giulio Andreini <[email protected]>
Co-authored-by: Mason Geloso <[email protected]>
Co-authored-by: Mason Geloso <[email protected]>
Co-authored-by: Mutasem Aldmour <[email protected]>
  • Loading branch information
13 people authored Nov 29, 2023
1 parent dbfd617 commit 87def60
Show file tree
Hide file tree
Showing 243 changed files with 21,688 additions and 483 deletions.
48 changes: 48 additions & 0 deletions cypress/composables/modals/chat-modal.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
/**
* Getters
*/

export function getManualChatModal() {
return cy.getByTestId('lmChat-modal');
}

export function getManualChatInput() {
return cy.getByTestId('workflow-chat-input');
}

export function getManualChatSendButton() {
return getManualChatModal().getByTestId('workflow-chat-send-button');
}

export function getManualChatMessages() {
return getManualChatModal().get('.messages .message');
}

export function getManualChatModalCloseButton() {
return getManualChatModal().get('.el-dialog__close');
}

export function getManualChatModalLogs() {
return getManualChatModal().getByTestId('lm-chat-logs');
}

export function getManualChatModalLogsTree() {
return getManualChatModalLogs().getByTestId('lm-chat-logs-tree');
}

export function getManualChatModalLogsEntries() {
return getManualChatModalLogs().getByTestId('lm-chat-logs-entry');
}

/**
* Actions
*/

export function sendManualChatMessage(message: string) {
getManualChatInput().type(message);
getManualChatSendButton().click();
}

export function closeManualChatModal() {
getManualChatModalCloseButton().click();
}
54 changes: 54 additions & 0 deletions cypress/composables/modals/credential-modal.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
/**
* Getters
*/

export function getCredentialConnectionParameterInputs() {
return cy.getByTestId('credential-connection-parameter');
}

export function getCredentialConnectionParameterInputByName(name: string) {
return cy.getByTestId(`parameter-input-${name}`);
}

export function getEditCredentialModal() {
return cy.getByTestId('editCredential-modal', { timeout: 5000 });
}

export function getCredentialSaveButton() {
return cy.getByTestId('credential-save-button', { timeout: 5000 });
}

export function getCredentialDeleteButton() {
return cy.getByTestId('credential-delete-button');
}

export function getCredentialModalCloseButton() {
return getEditCredentialModal().find('.el-dialog__close').first();
}

/**
* Actions
*/

export function setCredentialConnectionParameterInputByName(name: string, value: string) {
getCredentialConnectionParameterInputByName(name).type(value);
}

export function saveCredential() {
getCredentialSaveButton().click({ force: true });
}

export function closeCredentialModal() {
getCredentialModalCloseButton().click();
}

export function setCredentialValues(values: Record<string, any>, save = true) {
Object.entries(values).forEach(([key, value]) => {
setCredentialConnectionParameterInputByName(key, value);
});

if (save) {
saveCredential();
closeCredentialModal();
}
}
73 changes: 73 additions & 0 deletions cypress/composables/ndv.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
/**
* Getters
*/

export function getCredentialSelect(eq = 0) {
return cy.getByTestId('node-credentials-select').eq(eq);
}

export function getCreateNewCredentialOption() {
return cy.getByTestId('node-credentials-select-item-new');
}

export function getBackToCanvasButton() {
return cy.getByTestId('back-to-canvas');
}

export function getExecuteNodeButton() {
return cy.getByTestId('node-execute-button');
}

export function getParameterInputByName(name: string) {
return cy.getByTestId(`parameter-input-${name}`);
}

export function getInputPanel() {
return cy.getByTestId('input-panel');
}

export function getMainPanel() {
return cy.getByTestId('node-parameters');
}

export function getOutputPanel() {
return cy.getByTestId('output-panel');
}

export function getOutputPanelDataContainer() {
return getOutputPanel().getByTestId('ndv-data-container');
}

export function getOutputPanelTable() {
return getOutputPanelDataContainer().get('table');
}

/**
* Actions
*/

export function openCredentialSelect(eq = 0) {
getCredentialSelect(eq).click();
}

export function setCredentialByName(name: string) {
openCredentialSelect();
getCredentialSelect().contains(name).click();
}

export function clickCreateNewCredential() {
openCredentialSelect();
getCreateNewCredentialOption().click();
}

export function clickGetBackToCanvas() {
getBackToCanvasButton().click();
}

export function clickExecuteNode() {
getExecuteNodeButton().click();
}

export function setParameterInputByName(name: string, value: string) {
getParameterInputByName(name).clear().type(value);
}
142 changes: 142 additions & 0 deletions cypress/composables/workflow.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,142 @@
import { ROUTES } from '../constants';
import { getManualChatModal } from './modals/chat-modal';

/**
* Types
*/

export type EndpointType =
| 'ai_chain'
| 'ai_document'
| 'ai_embedding'
| 'ai_languageModel'
| 'ai_memory'
| 'ai_outputParser'
| 'ai_tool'
| 'ai_retriever'
| 'ai_textSplitter'
| 'ai_vectorRetriever'
| 'ai_vectorStore';

/**
* Getters
*/

export function getAddInputEndpointByType(nodeName: string, endpointType: EndpointType) {
return cy.get(
`.add-input-endpoint[data-jtk-scope-${endpointType}][data-endpoint-name="${nodeName}"]`,
);
}

export function getNodeCreatorItems() {
return cy.getByTestId('item-iterator-item');
}

export function getExecuteWorkflowButton() {
return cy.getByTestId('execute-workflow-button');
}

export function getManualChatButton() {
return cy.getByTestId('workflow-chat-button');
}

export function getNodes() {
return cy.getByTestId('canvas-node');
}

export function getNodeByName(name: string) {
return cy.getByTestId('canvas-node').filter(`[data-name="${name}"]`).eq(0);
}

export function getConnectionBySourceAndTarget(source: string, target: string) {
return cy
.get('.jtk-connector')
.filter(`[data-source-node="${source}"][data-target-node="${target}"]`)
.eq(0);
}

export function getNodeCreatorSearchBar() {
return cy.getByTestId('node-creator-search-bar');
}

export function getNodeCreatorPlusButton() {
return cy.getByTestId('node-creator-plus-button');
}

/**
* Actions
*/

export function addNodeToCanvas(
nodeDisplayName: string,
plusButtonClick = true,
preventNdvClose?: boolean,
action?: string,
) {
if (plusButtonClick) {
getNodeCreatorPlusButton().click();
}

getNodeCreatorSearchBar().type(nodeDisplayName);
getNodeCreatorSearchBar().type('{enter}');
cy.wait(500);
cy.get('body').then((body) => {
if (body.find('[data-test-id=node-creator]').length > 0) {
if (action) {
cy.contains(action).click();
} else {
// Select the first action
cy.get('[data-keyboard-nav-type="action"]').eq(0).click();
}
}
});

if (!preventNdvClose) cy.get('body').type('{esc}');
}

export function navigateToNewWorkflowPage(preventNodeViewUnload = true) {
cy.visit(ROUTES.NEW_WORKFLOW_PAGE);
cy.waitForLoad();
cy.window().then((win) => {
win.preventNodeViewBeforeUnload = preventNodeViewUnload;
});
}

export function addSupplementalNodeToParent(
nodeName: string,
endpointType: EndpointType,
parentNodeName: string,
) {
getAddInputEndpointByType(parentNodeName, endpointType).click({ force: true });
getNodeCreatorItems().contains(nodeName).click();
getConnectionBySourceAndTarget(parentNodeName, nodeName).should('exist');
}

export function addLanguageModelNodeToParent(nodeName: string, parentNodeName: string) {
addSupplementalNodeToParent(nodeName, 'ai_languageModel', parentNodeName);
}

export function addMemoryNodeToParent(nodeName: string, parentNodeName: string) {
addSupplementalNodeToParent(nodeName, 'ai_memory', parentNodeName);
}

export function addToolNodeToParent(nodeName: string, parentNodeName: string) {
addSupplementalNodeToParent(nodeName, 'ai_tool', parentNodeName);
}

export function addOutputParserNodeToParent(nodeName: string, parentNodeName: string) {
addSupplementalNodeToParent(nodeName, 'ai_outputParser', parentNodeName);
}

export function clickExecuteWorkflowButton() {
getExecuteWorkflowButton().click();
}

export function clickManualChatButton() {
getManualChatButton().click();
getManualChatModal().should('be.visible');
}

export function openNode(nodeName: string) {
getNodeByName(nodeName).dblclick();
}
13 changes: 13 additions & 0 deletions cypress/constants.ts
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@ export const INSTANCE_MEMBERS = [

export const MANUAL_TRIGGER_NODE_NAME = 'Manual Trigger';
export const MANUAL_TRIGGER_NODE_DISPLAY_NAME = 'When clicking "Execute Workflow"';
export const MANUAL_CHAT_TRIGGER_NODE_NAME = 'Manual Chat Trigger';
export const SCHEDULE_TRIGGER_NODE_NAME = 'Schedule Trigger';
export const CODE_NODE_NAME = 'Code';
export const SET_NODE_NAME = 'Set';
Expand All @@ -41,10 +42,22 @@ export const TRELLO_NODE_NAME = 'Trello';
export const NOTION_NODE_NAME = 'Notion';
export const PIPEDRIVE_NODE_NAME = 'Pipedrive';
export const HTTP_REQUEST_NODE_NAME = 'HTTP Request';
export const AGENT_NODE_NAME = 'AI Agent';
export const BASIC_LLM_CHAIN_NODE_NAME = 'Basic LLM Chain';
export const AI_MEMORY_WINDOW_BUFFER_MEMORY_NODE_NAME = 'Window Buffer Memory';
export const AI_TOOL_CALCULATOR_NODE_NAME = 'Calculator';
export const AI_TOOL_CODE_NODE_NAME = 'Custom Code Tool';
export const AI_TOOL_WIKIPEDIA_NODE_NAME = 'Wikipedia';
export const AI_LANGUAGE_MODEL_OPENAI_CHAT_MODEL_NODE_NAME = 'OpenAI Chat Model';
export const AI_OUTPUT_PARSER_AUTO_FIXING_NODE_NAME = 'Auto-fixing Output Parser';

export const META_KEY = Cypress.platform === 'darwin' ? '{meta}' : '{ctrl}';

export const NEW_GOOGLE_ACCOUNT_NAME = 'Gmail account';
export const NEW_TRELLO_ACCOUNT_NAME = 'Trello account';
export const NEW_NOTION_ACCOUNT_NAME = 'Notion account';
export const NEW_QUERY_AUTH_ACCOUNT_NAME = 'Query Auth account';

export const ROUTES = {
NEW_WORKFLOW_PAGE: '/workflow/new',
};
Loading

0 comments on commit 87def60

Please sign in to comment.