@n8n/n8n-nodes-langchain
Version:

762 lines (760 loc) • 24.6 kB
JavaScript
;
var __create = Object.create;
var __defProp = Object.defineProperty;
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
var __getOwnPropNames = Object.getOwnPropertyNames;
var __getProtoOf = Object.getPrototypeOf;
var __hasOwnProp = Object.prototype.hasOwnProperty;
var __export = (target, all) => {
for (var name in all)
__defProp(target, name, { get: all[name], enumerable: true });
};
var __copyProps = (to, from, except, desc) => {
if (from && typeof from === "object" || typeof from === "function") {
for (let key of __getOwnPropNames(from))
if (!__hasOwnProp.call(to, key) && key !== except)
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
}
return to;
};
var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
// If the importer is in node compatibility mode or this is not an ESM
// file that has been converted to a CommonJS file using a Babel-
// compatible transform (i.e. "__esModule" has not been set), then set
// "default" to the CommonJS "module.exports" for node compatibility.
isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
mod
));
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
var ChatTrigger_node_exports = {};
__export(ChatTrigger_node_exports, {
ChatTrigger: () => ChatTrigger
});
module.exports = __toCommonJS(ChatTrigger_node_exports);
var import_pick = __toESM(require("lodash/pick"));
var import_n8n_workflow = require("n8n-workflow");
var a = __toESM(require("node:assert"));
var import_constants = require("./constants");
var import_GenericFunctions = require("./GenericFunctions");
var import_templates = require("./templates");
var import_types = require("./types");
const CHAT_TRIGGER_PATH_IDENTIFIER = "chat";
const allowFileUploadsOption = {
displayName: "Allow File Uploads",
name: "allowFileUploads",
type: "boolean",
default: false,
description: "Whether to allow file uploads in the chat"
};
const allowedFileMimeTypeOption = {
displayName: "Allowed File Mime Types",
name: "allowedFilesMimeTypes",
type: "string",
default: "*",
placeholder: "e.g. image/*, text/*, application/pdf",
description: 'Allowed file types for upload. Comma-separated list of <a href="https://developer.mozilla.org/en-US/docs/Web/HTTP/Basics_of_HTTP/MIME_types/Common_types" target="_blank">MIME types</a>.'
};
const respondToWebhookResponseMode = {
name: "Using 'Respond to Webhook' Node",
value: "responseNode",
description: "Response defined in that node"
};
const lastNodeResponseMode = {
name: "When Last Node Finishes",
value: "lastNode",
description: "Returns data of the last-executed node"
};
const streamingResponseMode = {
name: "Streaming",
value: "streaming",
description: "Streaming response from specified nodes (e.g. Agents)"
};
const respondNodesResponseMode = {
name: "Using Response Nodes",
value: "responseNodes",
description: "Send responses to the chat by using 'Respond to Chat' or 'Respond to Webhook' nodes"
};
const commonOptionsFields = [
// CORS parameters are only valid for when chat is used in hosted or webhook mode
{
displayName: "Allowed Origins (CORS)",
name: "allowedOrigins",
type: "string",
default: "*",
description: "Comma-separated list of URLs allowed for cross-origin non-preflight requests. Use * (default) to allow all origins.",
displayOptions: {
show: {
"/mode": ["hostedChat", "webhook"]
}
}
},
{
...allowFileUploadsOption,
displayOptions: {
show: {
"/mode": ["hostedChat"]
}
}
},
{
...allowedFileMimeTypeOption,
displayOptions: {
show: {
"/mode": ["hostedChat"]
}
}
},
{
displayName: "Input Placeholder",
name: "inputPlaceholder",
type: "string",
displayOptions: {
show: {
"/mode": ["hostedChat"]
}
},
default: "Type your question..",
placeholder: "e.g. Type your message here",
description: "Shown as placeholder text in the chat input field"
},
{
displayName: "Load Previous Session",
name: "loadPreviousSession",
type: "options",
options: [
{
name: "Off",
value: "notSupported",
description: "Loading messages of previous session is turned off"
},
{
name: "From Memory",
value: "memory",
description: "Load session messages from memory"
},
{
name: "Manually",
value: "manually",
description: "Manually return messages of session"
}
],
default: "notSupported",
description: "If loading messages of a previous session should be enabled"
},
{
displayName: "Require Button Click to Start Chat",
name: "showWelcomeScreen",
type: "boolean",
displayOptions: {
show: {
"/mode": ["hostedChat"]
}
},
default: false,
description: "Whether to show the welcome screen at the start of the chat"
},
{
displayName: "Start Conversation Button Text",
name: "getStarted",
type: "string",
displayOptions: {
show: {
showWelcomeScreen: [true],
"/mode": ["hostedChat"]
}
},
default: "New Conversation",
placeholder: "e.g. New Conversation",
description: "Shown as part of the welcome screen, in the middle of the chat window"
},
{
displayName: "Subtitle",
name: "subtitle",
type: "string",
displayOptions: {
show: {
"/mode": ["hostedChat"]
}
},
default: "Start a chat. We're here to help you 24/7.",
placeholder: "e.g. We're here for you",
description: "Shown at the top of the chat, under the title"
},
{
displayName: "Title",
name: "title",
type: "string",
displayOptions: {
show: {
"/mode": ["hostedChat"]
}
},
default: "Hi there! \u{1F44B}",
placeholder: "e.g. Welcome",
description: "Shown at the top of the chat"
},
{
displayName: "Custom Chat Styling",
name: "customCss",
type: "string",
typeOptions: {
rows: 10,
editor: "cssEditor"
},
displayOptions: {
show: {
"/mode": ["hostedChat"]
}
},
default: `
${import_constants.cssVariables}
/* You can override any class styles, too. Right-click inspect in Chat UI to find class to override. */
.chat-message {
max-width: 50%;
}
`.trim(),
description: "Override default styling of the public chat interface with CSS"
}
];
class ChatTrigger extends import_n8n_workflow.Node {
constructor() {
super(...arguments);
this.description = {
displayName: "Chat Trigger",
name: "chatTrigger",
icon: "fa:comments",
iconColor: "black",
group: ["trigger"],
version: [1, 1.1, 1.2, 1.3, 1.4],
defaultVersion: 1.4,
description: "Runs the workflow when an n8n generated webchat is submitted",
defaults: {
name: "When chat message received"
},
codex: {
categories: ["Core Nodes"],
resources: {
primaryDocumentation: [
{
url: "https://docs.n8n.io/integrations/builtin/core-nodes/n8n-nodes-langchain.chattrigger/"
}
]
}
},
maxNodes: 1,
inputs: `={{ (() => {
if (!['hostedChat', 'webhook'].includes($parameter.mode)) {
return [];
}
if ($parameter.options?.loadPreviousSession !== 'memory') {
return [];
}
return [
{
displayName: 'Memory',
maxConnections: 1,
type: '${import_n8n_workflow.NodeConnectionTypes.AiMemory}',
required: true,
}
];
})() }}`,
outputs: [import_n8n_workflow.NodeConnectionTypes.Main],
credentials: [
{
// eslint-disable-next-line n8n-nodes-base/node-class-description-credentials-name-unsuffixed
name: "httpBasicAuth",
required: true,
displayOptions: {
show: {
authentication: ["basicAuth"]
}
}
}
],
webhooks: [
{
name: "setup",
httpMethod: "GET",
responseMode: "onReceived",
path: CHAT_TRIGGER_PATH_IDENTIFIER,
ndvHideUrl: true
},
{
name: "default",
httpMethod: "POST",
responseMode: '={{$parameter.options?.["responseMode"] || "lastNode" }}',
path: CHAT_TRIGGER_PATH_IDENTIFIER,
ndvHideMethod: true,
ndvHideUrl: "={{ !$parameter.public }}"
}
],
eventTriggerDescription: "Waiting for you to submit the chat",
activationMessage: "You can now make calls to your production chat URL.",
triggerPanel: false,
properties: [
/**
* @note If we change this property, also update it in ChatEmbedModal.vue
*/
{
displayName: "Make Chat Publicly Available",
name: "public",
type: "boolean",
default: false,
description: "Whether the chat should be publicly available or only accessible through the manual chat interface"
},
{
displayName: "Mode",
name: "mode",
type: "options",
options: [
{
name: "Hosted Chat",
value: "hostedChat",
description: "Chat on a page served by n8n"
},
{
name: "Embedded Chat",
value: "webhook",
description: "Chat through a widget embedded in another page, or by calling a webhook"
}
],
default: "hostedChat",
displayOptions: {
show: {
public: [true]
}
}
},
{
displayName: "Chat will be live at the URL above once you activate this workflow. Live executions will show up in the \u2018executions\u2019 tab",
name: "hostedChatNotice",
type: "notice",
displayOptions: {
show: {
mode: ["hostedChat"],
public: [true]
}
},
default: ""
},
{
displayName: 'Follow the instructions <a href="https://www.npmjs.com/package/@n8n/chat" target="_blank">here</a> to embed chat in a webpage (or just call the webhook URL at the top of this section). Chat will be live once you activate this workflow',
name: "embeddedChatNotice",
type: "notice",
displayOptions: {
show: {
mode: ["webhook"],
public: [true]
}
},
default: ""
},
{
displayName: "Authentication",
name: "authentication",
type: "options",
displayOptions: {
show: {
public: [true]
}
},
options: [
{
name: "Basic Auth",
value: "basicAuth",
description: "Simple username and password (the same one for all users)"
},
{
// eslint-disable-next-line n8n-nodes-base/node-param-display-name-miscased
name: "n8n User Auth",
value: "n8nUserAuth",
description: "Require user to be logged in with their n8n account"
},
{
name: "None",
value: "none"
}
],
default: "none",
description: "The way to authenticate"
},
{
displayName: "Initial Message(s)",
name: "initialMessages",
type: "string",
displayOptions: {
show: {
mode: ["hostedChat"],
public: [true]
}
},
typeOptions: {
rows: 3
},
default: "Hi there! \u{1F44B}\nMy name is Nathan. How can I assist you today?",
description: "Default messages shown at the start of the chat, one per line"
},
{
// eslint-disable-next-line n8n-nodes-base/node-param-display-name-miscased
displayName: "Make Available in n8n Chat",
name: "availableInChat",
type: "boolean",
default: false,
noDataExpression: true,
description: "Whether to make the agent available in n8n Chat",
displayOptions: {
show: {
"@version": [{ _cnd: { gte: 1.4 } }]
}
}
},
{
displayName: "Agent Name",
name: "agentName",
type: "string",
default: "",
noDataExpression: true,
description: "The name of the agent on n8n Chat",
displayOptions: {
show: {
availableInChat: [true]
}
}
},
{
displayName: "Agent Description",
name: "agentDescription",
type: "string",
typeOptions: {
rows: 2
},
default: "",
noDataExpression: true,
description: "The description of the agent on n8n Chat",
displayOptions: {
show: {
availableInChat: [true]
}
}
},
{
displayName: "Options",
name: "options",
type: "collection",
displayOptions: {
show: {
public: [false],
"@version": [1, 1.1]
}
},
placeholder: "Add Field",
default: {},
options: [allowFileUploadsOption, allowedFileMimeTypeOption]
},
// Options for versions 1.0 and 1.1 (without streaming)
{
displayName: "Options",
name: "options",
type: "collection",
displayOptions: {
show: {
mode: ["hostedChat", "webhook"],
public: [true],
"@version": [1, 1.1]
}
},
placeholder: "Add Field",
default: {},
options: [
...commonOptionsFields,
{
displayName: "Response Mode",
name: "responseMode",
type: "options",
options: [lastNodeResponseMode, respondToWebhookResponseMode],
default: "lastNode",
description: "When and how to respond to the webhook"
}
]
},
// Options for version 1.2 (with streaming)
{
displayName: "Options",
name: "options",
type: "collection",
displayOptions: {
show: {
mode: ["hostedChat", "webhook"],
public: [true],
"@version": [1.2]
}
},
placeholder: "Add Field",
default: {},
options: [
...commonOptionsFields,
{
displayName: "Response Mode",
name: "responseMode",
type: "options",
options: [lastNodeResponseMode, respondToWebhookResponseMode, streamingResponseMode],
default: "lastNode",
description: "When and how to respond to the webhook"
}
]
},
{
displayName: "Options",
name: "options",
type: "collection",
displayOptions: {
show: {
public: [false],
"@version": [{ _cnd: { gte: 1.3 } }]
}
},
placeholder: "Add Field",
default: {},
options: [
allowFileUploadsOption,
allowedFileMimeTypeOption,
{
displayName: "Response Mode",
name: "responseMode",
type: "options",
options: [lastNodeResponseMode, respondNodesResponseMode],
default: "lastNode",
description: "When and how to respond to the chat"
}
]
},
{
displayName: "Options",
name: "options",
type: "collection",
displayOptions: {
show: {
mode: ["hostedChat", "webhook"],
public: [true],
"@version": [{ _cnd: { gte: 1.3 } }]
}
},
placeholder: "Add Field",
default: {},
options: [
...commonOptionsFields,
{
displayName: "Response Mode",
name: "responseMode",
type: "options",
options: [lastNodeResponseMode, streamingResponseMode, respondToWebhookResponseMode],
default: "lastNode",
description: "When and how to respond to the chat",
displayOptions: { show: { "/mode": ["webhook"] } }
},
{
displayName: "Response Mode",
name: "responseMode",
type: "options",
options: [lastNodeResponseMode, streamingResponseMode, respondNodesResponseMode],
default: "lastNode",
description: "When and how to respond to the webhook",
displayOptions: { show: { "/mode": ["hostedChat"] } }
}
]
}
]
};
}
async handleFormData(context) {
const req = context.getRequestObject();
a.ok(req.contentType === "multipart/form-data", "Expected multipart/form-data");
const options = context.getNodeParameter("options", {});
const { data, files } = req.body;
const returnItem = {
json: data
};
if (files && Object.keys(files).length) {
returnItem.json.files = [];
returnItem.binary = {};
const count = 0;
for (const fileKey of Object.keys(files)) {
const processedFiles = [];
if (Array.isArray(files[fileKey])) {
processedFiles.push(...files[fileKey]);
} else {
processedFiles.push(files[fileKey]);
}
let fileIndex = 0;
for (const file of processedFiles) {
let binaryPropertyName = "data";
if (binaryPropertyName.endsWith("[]")) {
binaryPropertyName = binaryPropertyName.slice(0, -2);
}
if (options.binaryPropertyName) {
binaryPropertyName = `${options.binaryPropertyName.toString()}${count}`;
}
const binaryFile = await context.nodeHelpers.copyBinaryFile(
file.filepath,
file.originalFilename ?? file.newFilename,
file.mimetype
);
const binaryKey = `${binaryPropertyName}${fileIndex}`;
const binaryInfo = {
...(0, import_pick.default)(binaryFile, ["fileName", "fileSize", "fileType", "mimeType", "fileExtension"]),
binaryKey
};
returnItem.binary = Object.assign(returnItem.binary ?? {}, {
[`${binaryKey}`]: binaryFile
});
returnItem.json.files = [
...returnItem.json.files,
binaryInfo
];
fileIndex += 1;
}
}
}
return returnItem;
}
async webhook(ctx) {
const res = ctx.getResponseObject();
const isPublic = ctx.getNodeParameter("public", false);
(0, import_n8n_workflow.assertParamIsBoolean)("public", isPublic, ctx.getNode());
const nodeMode = ctx.getNodeParameter("mode", "hostedChat");
(0, import_n8n_workflow.assertParamIsString)("mode", nodeMode, ctx.getNode());
if (!isPublic) {
res.status(404).end();
return {
noWebhookResponse: true
};
}
const options = ctx.getNodeParameter("options", {});
(0, import_n8n_workflow.validateNodeParameters)(
options,
{
getStarted: { type: "string" },
inputPlaceholder: { type: "string" },
loadPreviousSession: { type: "string" },
showWelcomeScreen: { type: "boolean" },
subtitle: { type: "string" },
title: { type: "string" },
allowFileUploads: { type: "boolean" },
allowedFilesMimeTypes: { type: "string" },
customCss: { type: "string" },
responseMode: { type: "string" }
},
ctx.getNode()
);
const loadPreviousSession = options.loadPreviousSession;
(0, import_types.assertValidLoadPreviousSessionOption)(loadPreviousSession, ctx.getNode());
const enableStreaming = options.responseMode === "streaming";
const req = ctx.getRequestObject();
const webhookName = ctx.getWebhookName();
const mode = ctx.getMode() === "manual" ? "test" : "production";
const bodyData = ctx.getBodyData() ?? {};
try {
await (0, import_GenericFunctions.validateAuth)(ctx);
} catch (error) {
if (error) {
res.writeHead(error.responseCode, {
"www-authenticate": 'Basic realm="Webhook"'
});
res.end(error.message);
return { noWebhookResponse: true };
}
throw error;
}
if (nodeMode === "hostedChat") {
if (webhookName === "setup") {
const webhookUrlRaw = ctx.getNodeWebhookUrl("default");
if (!webhookUrlRaw) {
throw new import_n8n_workflow.NodeOperationError(ctx.getNode(), "Default webhook url not set");
}
const webhookUrl = mode === "test" ? webhookUrlRaw.replace("/webhook", "/webhook-test") : webhookUrlRaw;
const authentication = ctx.getNodeParameter("authentication");
const initialMessagesRaw = ctx.getNodeParameter("initialMessages", "");
(0, import_n8n_workflow.assertParamIsString)("initialMessage", initialMessagesRaw, ctx.getNode());
const instanceId = ctx.getInstanceId();
const i18nConfig = {};
const keys = ["getStarted", "inputPlaceholder", "subtitle", "title"];
for (const key of keys) {
if (options[key] !== void 0) {
i18nConfig[key] = options[key];
}
}
const page = (0, import_templates.createPage)({
i18n: {
en: i18nConfig
},
showWelcomeScreen: options.showWelcomeScreen,
loadPreviousSession,
initialMessages: initialMessagesRaw,
webhookUrl,
mode,
instanceId,
authentication,
allowFileUploads: options.allowFileUploads,
allowedFilesMimeTypes: options.allowedFilesMimeTypes,
customCss: options.customCss,
enableStreaming
});
res.status(200).send(page).end();
return {
noWebhookResponse: true
};
}
}
if (bodyData.action === "loadPreviousSession") {
if (options?.loadPreviousSession === "memory") {
const memory = await ctx.getInputConnectionData(import_n8n_workflow.NodeConnectionTypes.AiMemory, 0);
const messages = (await memory?.chatHistory.getMessages() ?? []).filter((message) => !message?.additional_kwargs?.hideFromUI).map((message) => message?.toJSON());
return {
webhookResponse: { data: messages }
};
} else if (!options?.loadPreviousSession || options?.loadPreviousSession === "notSupported") {
return {
webhookResponse: { data: [] }
};
}
}
let returnData;
const webhookResponse = { status: 200 };
if (enableStreaming) {
res.writeHead(200, {
"Content-Type": "application/json; charset=utf-8",
"Transfer-Encoding": "chunked",
"Cache-Control": "no-cache",
Connection: "keep-alive"
});
res.flushHeaders();
if (req.contentType === "multipart/form-data") {
returnData = [await this.handleFormData(ctx)];
} else {
returnData = [{ json: bodyData }];
}
return {
workflowData: [ctx.helpers.returnJsonArray(returnData)],
noWebhookResponse: true
};
}
if (req.contentType === "multipart/form-data") {
returnData = [await this.handleFormData(ctx)];
return {
webhookResponse,
workflowData: [returnData]
};
} else {
returnData = [{ json: bodyData }];
}
return {
webhookResponse,
workflowData: [ctx.helpers.returnJsonArray(returnData)]
};
}
}
// Annotate the CommonJS export names for ESM import in node:
0 && (module.exports = {
ChatTrigger
});
//# sourceMappingURL=ChatTrigger.node.js.map