feat(core): Add support for building LLM applications (#7235)
This extracts all core and editor changes from #7246 and #7137, so that we can get these changes merged first. ADO-1120 [DB Tests](https://github.com/n8n-io/n8n/actions/runs/6379749011) [E2E Tests](https://github.com/n8n-io/n8n/actions/runs/6379751480) [Workflow Tests](https://github.com/n8n-io/n8n/actions/runs/6379752828) --------- Co-authored-by: Jan Oberhauser <jan.oberhauser@gmail.com> Co-authored-by: Oleg Ivaniv <me@olegivaniv.com> Co-authored-by: Alex Grozav <alex@grozav.com> Co-authored-by: कारतोफ्फेलस्क्रिप्ट™ <aditya@netroy.in>
This commit is contained in:
committed by
GitHub
parent
04dfcd73be
commit
00a4b8b0c6
@@ -131,6 +131,31 @@
|
||||
"binaryDataDisplay.backToOverviewPage": "Back to overview page",
|
||||
"binaryDataDisplay.noDataFoundToDisplay": "No data found to display",
|
||||
"binaryDataDisplay.yourBrowserDoesNotSupport": "Your browser does not support the video element. Kindly update it to latest version.",
|
||||
"chat.window.title": "Chat Window ({nodeName})",
|
||||
"chat.window.logs": "Log (for last message)",
|
||||
"chat.window.noChatNode": "No Chat Node",
|
||||
"chat.window.noExecution": "Nothing got executed yet",
|
||||
"chat.window.chat.placeholder": "Type in message",
|
||||
"chat.window.chat.sendButtonText": "Send",
|
||||
"chat.window.chat.chatMessageOptions.reuseMessage": "Reuse Message",
|
||||
"chat.window.chat.chatMessageOptions.repostMessage": "Repost Message",
|
||||
"chat.window.chat.chatMessageOptions.executionId": "Execution ID",
|
||||
"chatEmbed.infoTip.description": "Add chat to external applications using the n8n chat package.",
|
||||
"chatEmbed.infoTip.link": "More info",
|
||||
"chatEmbed.title": "Embed Chat in your website",
|
||||
"chatEmbed.close": "Close",
|
||||
"chatEmbed.install": "First, install the n8n chat package:",
|
||||
"chatEmbed.paste.cdn": "Paste the following code anywhere in the {code} tag of your HTML file.",
|
||||
"chatEmbed.paste.cdn.file": "<body>",
|
||||
"chatEmbed.paste.vue": "Next, paste the following code in your {code} file.",
|
||||
"chatEmbed.paste.vue.file": "App.vue",
|
||||
"chatEmbed.paste.react": "Next, paste the following code in your {code} file.",
|
||||
"chatEmbed.paste.react.file": "App.ts",
|
||||
"chatEmbed.paste.other": "Next, paste the following code in your {code} file.",
|
||||
"chatEmbed.paste.other.file": "main.ts",
|
||||
"chatEmbed.packageInfo.description": "The n8n Chat widget can be easily customized to fit your needs.",
|
||||
"chatEmbed.packageInfo.link": "Read the full documentation",
|
||||
"chatEmbed.url": "https://www.npmjs.com/package/{'@'}n8n/chat",
|
||||
"codeEdit.edit": "Edit",
|
||||
"codeNodeEditor.askAi": "✨ Ask AI",
|
||||
"codeNodeEditor.completer.$()": "Output data of the {nodeName} node",
|
||||
@@ -711,7 +736,10 @@
|
||||
"ndv.input": "Input",
|
||||
"ndv.input.nodeDistance": "({count} node back) | ({count} nodes back)",
|
||||
"ndv.input.noNodesFound": "No nodes found",
|
||||
"ndv.input.mapping": "Mapping",
|
||||
"ndv.input.debugging": "Debugging",
|
||||
"ndv.input.parentNodes": "Parent nodes",
|
||||
"ndv.input.previousNode": "Previous node",
|
||||
"ndv.input.tooMuchData.title": "Input data is huge",
|
||||
"ndv.input.noOutputDataInBranch": "No input data in this branch",
|
||||
"ndv.input.noOutputDataInNode": "Node did not output any data. n8n stops executing the workflow when a node has no output data.",
|
||||
@@ -726,6 +754,9 @@
|
||||
"ndv.input.disabled": "The '{nodeName}' node is disabled and won’t execute.",
|
||||
"ndv.input.disabled.cta": "Enable it",
|
||||
"ndv.output": "Output",
|
||||
"ndv.output.ai.empty": "👈 This is {node}’s AI Logs. Click on a node to see the input it received and data it outputted.",
|
||||
"ndv.output.outType.logs": "Logs",
|
||||
"ndv.output.outType.regular": "Output",
|
||||
"ndv.output.edit": "Edit Output",
|
||||
"ndv.output.all": "all",
|
||||
"ndv.output.branch": "Branch",
|
||||
@@ -813,6 +844,18 @@
|
||||
"nodeCreator.subcategoryDescriptions.flow": "IF, Switch, Wait, Compare and Merge data, etc.",
|
||||
"nodeCreator.subcategoryDescriptions.helpers": "HTTP Requests (API Calls), date and time, scrape HTML, RSS, SSH, etc.",
|
||||
"nodeCreator.subcategoryDescriptions.otherTriggerNodes": "Runs the flow on workflow errors, file changes, etc.",
|
||||
"nodeCreator.subcategoryDescriptions.agents": "Autonomous entities that interact and make decisions.",
|
||||
"nodeCreator.subcategoryDescriptions.chains": "Structured assemblies for specific tasks.",
|
||||
"nodeCreator.subcategoryDescriptions.documentLoaders": "Handles loading of documents for processing.",
|
||||
"nodeCreator.subcategoryDescriptions.embeddings": "Transforms text into vector representations.",
|
||||
"nodeCreator.subcategoryDescriptions.languageModels": "AI models that understand and generate language.",
|
||||
"nodeCreator.subcategoryDescriptions.memory": "Manages storage and retrieval of information during execution.",
|
||||
"nodeCreator.subcategoryDescriptions.outputParsers": "Ensures the output adheres to a defined format.",
|
||||
"nodeCreator.subcategoryDescriptions.retrievers": "Fetches relevant information from a source.",
|
||||
"nodeCreator.subcategoryDescriptions.textSplitters": "Breaks down text into smaller parts.",
|
||||
"nodeCreator.subcategoryDescriptions.tools": "Utility components providing various functionalities.",
|
||||
"nodeCreator.subcategoryDescriptions.vectorStores": "Handles storage and retrieval of vector representations.",
|
||||
"nodeCreator.subcategoryDescriptions.miscellaneous": "Other AI related nodes.",
|
||||
"nodeCreator.subcategoryNames.appTriggerNodes": "On app event",
|
||||
"nodeCreator.subcategoryNames.appRegularNodes": "Action in an app",
|
||||
"nodeCreator.subcategoryNames.dataTransformation": "Data transformation",
|
||||
@@ -820,6 +863,18 @@
|
||||
"nodeCreator.subcategoryNames.flow": "Flow",
|
||||
"nodeCreator.subcategoryNames.helpers": "Helpers",
|
||||
"nodeCreator.subcategoryNames.otherTriggerNodes": "Other ways...",
|
||||
"nodeCreator.subcategoryNames.agents": "Agents",
|
||||
"nodeCreator.subcategoryNames.chains": "Chains",
|
||||
"nodeCreator.subcategoryNames.documentLoaders": "Document Loaders",
|
||||
"nodeCreator.subcategoryNames.embeddings": "Embeddings",
|
||||
"nodeCreator.subcategoryNames.languageModels": "Language Models",
|
||||
"nodeCreator.subcategoryNames.memory": "Memory",
|
||||
"nodeCreator.subcategoryNames.outputParsers": "Output Parsers",
|
||||
"nodeCreator.subcategoryNames.retrievers": "Retrievers",
|
||||
"nodeCreator.subcategoryNames.textSplitters": "Text Splitters",
|
||||
"nodeCreator.subcategoryNames.tools": "Tools",
|
||||
"nodeCreator.subcategoryNames.vectorStores": "Vector Stores",
|
||||
"nodeCreator.subcategoryNames.miscellaneous": "Miscellaneous",
|
||||
"nodeCreator.triggerHelperPanel.addAnotherTrigger": "Add another trigger",
|
||||
"nodeCreator.triggerHelperPanel.addAnotherTriggerDescription": "Triggers start your workflow. Workflows can have multiple triggers.",
|
||||
"nodeCreator.triggerHelperPanel.title": "When should this workflow run?",
|
||||
@@ -834,6 +889,27 @@
|
||||
"nodeCreator.triggerHelperPanel.selectATriggerDescription": "A trigger is a step that starts your workflow",
|
||||
"nodeCreator.triggerHelperPanel.workflowTriggerDisplayName": "When called by another workflow",
|
||||
"nodeCreator.triggerHelperPanel.workflowTriggerDescription": "Runs the flow when called by the Execute Workflow node from a different workflow",
|
||||
"nodeCreator.aiPanel.aiNodes": "AI Nodes",
|
||||
"nodeCreator.aiPanel.aiOtherNodes": "Other AI Nodes",
|
||||
"nodeCreator.aiPanel.aiOtherNodesDescription": "Embeddings, Vector Stores, LLMs and other AI nodes",
|
||||
"nodeCreator.aiPanel.selectAiNode": "Select an Al Node to add to your workflow",
|
||||
"nodeCreator.aiPanel.nodesForAi": "Build autonomous agents, summarize or interrogate documents, etc.",
|
||||
"nodeCreator.aiPanel.langchainAiNodes": "Advanced AI",
|
||||
"nodeCreator.aiPanel.title": "When should this workflow run?",
|
||||
"nodeCreator.aiPanel.infoBox": "Check out our <a href=\"/collections/8\" target=\"_blank\">templates</a> for workflow examples and inspiration.",
|
||||
"nodeCreator.aiPanel.scheduleTriggerDisplayName": "On a schedule",
|
||||
"nodeCreator.aiPanel.scheduleTriggerDescription": "Runs the flow every day, hour, or custom interval",
|
||||
"nodeCreator.aiPanel.webhookTriggerDisplayName": "On webhook call",
|
||||
"nodeCreator.aiPanel.webhookTriggerDescription": "Runs the flow when another app sends a webhook",
|
||||
"nodeCreator.aiPanel.manualTriggerDisplayName": "Manually",
|
||||
"nodeCreator.aiPanel.manualTriggerDescription": "Runs the flow on clicking a button in n8n",
|
||||
"nodeCreator.aiPanel.whatHappensNext": "What happens next?",
|
||||
"nodeCreator.aiPanel.selectATrigger": "Select an AI Component",
|
||||
"nodeCreator.aiPanel.selectATriggerDescription": "A trigger is a step that starts your workflow",
|
||||
"nodeCreator.aiPanel.workflowTriggerDisplayName": "When called by another workflow",
|
||||
"nodeCreator.aiPanel.workflowTriggerDescription": "Runs the flow when called by the Execute Workflow node from a different workflow",
|
||||
"nodeCreator.nodeItem.triggerIconTitle": "Trigger Node",
|
||||
"nodeCreator.nodeItem.aiIconTitle": "LangChain AI Node",
|
||||
"nodeCredentials.createNew": "Create New Credential",
|
||||
"nodeCredentials.credentialFor": "Credential for {credentialType}",
|
||||
"nodeCredentials.credentialsLabel": "Credential to connect with",
|
||||
@@ -935,6 +1011,8 @@
|
||||
"nodeView.showError.openWorkflow.title": "Problem opening workflow",
|
||||
"nodeView.showError.stopExecution.title": "Problem stopping execution",
|
||||
"nodeView.showError.stopWaitingForWebhook.title": "Problem deleting test webhook",
|
||||
"nodeView.showError.nodeNodeCompatible.title": "Connection not possible",
|
||||
"nodeView.showError.nodeNodeCompatible.message": "The node \"{sourceNodeName}\" can't be connected to the node \"{targetNodeName}\" because they are not compatible.",
|
||||
"nodeView.showMessage.addNodeButton.message": "'{nodeTypeName}' is an unknown node type",
|
||||
"nodeView.showMessage.addNodeButton.title": "Could not insert node",
|
||||
"nodeView.showMessage.keyDown.title": "Workflow created",
|
||||
@@ -1196,6 +1274,10 @@
|
||||
"runData.editor.save": "Save",
|
||||
"runData.editor.cancel": "Cancel",
|
||||
"runData.editor.copyDataInfo": "You can copy data from previous executions and paste it above.",
|
||||
"runData.aiContentBlock.startedAt": "Started at {startTime}",
|
||||
"runData.aiContentBlock.tokens": "{count} Tokens",
|
||||
"runData.aiContentBlock.tokens.prompt": "Prompt:",
|
||||
"runData.aiContentBlock.tokens.completion": "Completion:",
|
||||
"saveButton.save": "@:_reusableBaseText.save",
|
||||
"saveButton.saved": "Saved",
|
||||
"saveButton.saving": "Saving",
|
||||
@@ -1616,6 +1698,7 @@
|
||||
"nodeIssues.credentials.doNotExist.hint": "You can create credentials with the exact name and then they get auto-selected on refresh..",
|
||||
"nodeIssues.credentials.notIdentified": "Credentials with name {name} exist for {type}.",
|
||||
"nodeIssues.credentials.notIdentified.hint": "Credentials are not clearly identified. Please select the correct credentials.",
|
||||
"nodeIssues.input.missing": "No node connected to required input \"{inputName}\"",
|
||||
"ndv.trigger.moreInfo": "More info",
|
||||
"ndv.trigger.copiedTestUrl": "Test URL copied to clipboard",
|
||||
"ndv.trigger.webhookBasedNode.executionsHelp.inactive": "<b>While building your workflow</b>, click the 'listen' button, then go to {service} and make an event happen. This will trigger an execution, which will show up in this editor.<br /> <br /> <b>Once you're happy with your workflow</b>, <a data-key=\"activate\">activate</a> it. Then every time there's a matching event in {service}, the workflow will execute. These executions will show up in the <a data-key=\"executions\">executions list</a>, but not in the editor.",
|
||||
|
||||
@@ -13,10 +13,12 @@ import {
|
||||
faArrowDown,
|
||||
faAt,
|
||||
faBan,
|
||||
faBars,
|
||||
faBolt,
|
||||
faBook,
|
||||
faBoxOpen,
|
||||
faBug,
|
||||
faBrain,
|
||||
faCalculator,
|
||||
faCalendar,
|
||||
faChartBar,
|
||||
@@ -31,6 +33,8 @@ import {
|
||||
faCodeBranch,
|
||||
faCog,
|
||||
faCogs,
|
||||
faComment,
|
||||
faComments,
|
||||
faClipboardList,
|
||||
faClock,
|
||||
faClone,
|
||||
@@ -39,6 +43,7 @@ import {
|
||||
faCopy,
|
||||
faCube,
|
||||
faCut,
|
||||
faDatabase,
|
||||
faDotCircle,
|
||||
faEdit,
|
||||
faEllipsisH,
|
||||
@@ -67,7 +72,9 @@ import {
|
||||
faGift,
|
||||
faGlobe,
|
||||
faGraduationCap,
|
||||
faGripLinesVertical,
|
||||
faGripVertical,
|
||||
faHandScissors,
|
||||
faHandPointLeft,
|
||||
faHashtag,
|
||||
faHdd,
|
||||
@@ -78,6 +85,7 @@ import {
|
||||
faInfo,
|
||||
faInfoCircle,
|
||||
faKey,
|
||||
faLanguage,
|
||||
faLink,
|
||||
faList,
|
||||
faLightbulb,
|
||||
@@ -98,6 +106,7 @@ import {
|
||||
faQuestion,
|
||||
faQuestionCircle,
|
||||
faRedo,
|
||||
faRobot,
|
||||
faRss,
|
||||
faSave,
|
||||
faSatelliteDish,
|
||||
@@ -105,6 +114,7 @@ import {
|
||||
faSearchMinus,
|
||||
faSearchPlus,
|
||||
faServer,
|
||||
faScrewdriver,
|
||||
faSignInAlt,
|
||||
faSignOutAlt,
|
||||
faSlidersH,
|
||||
@@ -128,12 +138,17 @@ import {
|
||||
faUserCircle,
|
||||
faUserFriends,
|
||||
faUsers,
|
||||
faVectorSquare,
|
||||
faVideo,
|
||||
faTree,
|
||||
faStickyNote as faSolidStickyNote,
|
||||
faUserLock,
|
||||
faGem,
|
||||
faDownload,
|
||||
faRemoveFormat,
|
||||
faTools,
|
||||
faProjectDiagram,
|
||||
faStream,
|
||||
} from '@fortawesome/free-solid-svg-icons';
|
||||
import { faVariable, faXmark, faVault } from './custom';
|
||||
import { faStickyNote } from '@fortawesome/free-regular-svg-icons';
|
||||
@@ -156,10 +171,12 @@ export const FontAwesomePlugin: Plugin<{}> = {
|
||||
addIcon(faArrowDown);
|
||||
addIcon(faAt);
|
||||
addIcon(faBan);
|
||||
addIcon(faBars);
|
||||
addIcon(faBolt);
|
||||
addIcon(faBook);
|
||||
addIcon(faBoxOpen);
|
||||
addIcon(faBug);
|
||||
addIcon(faBrain);
|
||||
addIcon(faCalculator);
|
||||
addIcon(faCalendar);
|
||||
addIcon(faChartBar);
|
||||
@@ -174,6 +191,8 @@ export const FontAwesomePlugin: Plugin<{}> = {
|
||||
addIcon(faCodeBranch);
|
||||
addIcon(faCog);
|
||||
addIcon(faCogs);
|
||||
addIcon(faComment);
|
||||
addIcon(faComments);
|
||||
addIcon(faClipboardList);
|
||||
addIcon(faClock);
|
||||
addIcon(faClone);
|
||||
@@ -182,7 +201,9 @@ export const FontAwesomePlugin: Plugin<{}> = {
|
||||
addIcon(faCopy);
|
||||
addIcon(faCube);
|
||||
addIcon(faCut);
|
||||
addIcon(faDatabase);
|
||||
addIcon(faDotCircle);
|
||||
addIcon(faGripLinesVertical);
|
||||
addIcon(faGripVertical);
|
||||
addIcon(faEdit);
|
||||
addIcon(faEllipsisH);
|
||||
@@ -211,6 +232,7 @@ export const FontAwesomePlugin: Plugin<{}> = {
|
||||
addIcon(faGlobe);
|
||||
addIcon(faGlobeAmericas);
|
||||
addIcon(faGraduationCap);
|
||||
addIcon(faHandScissors);
|
||||
addIcon(faHandPointLeft);
|
||||
addIcon(faHashtag);
|
||||
addIcon(faHdd);
|
||||
@@ -221,6 +243,7 @@ export const FontAwesomePlugin: Plugin<{}> = {
|
||||
addIcon(faInfo);
|
||||
addIcon(faInfoCircle);
|
||||
addIcon(faKey);
|
||||
addIcon(faLanguage);
|
||||
addIcon(faLink);
|
||||
addIcon(faList);
|
||||
addIcon(faLightbulb);
|
||||
@@ -238,9 +261,12 @@ export const FontAwesomePlugin: Plugin<{}> = {
|
||||
addIcon(faPlus);
|
||||
addIcon(faPlusCircle);
|
||||
addIcon(faPlusSquare);
|
||||
addIcon(faProjectDiagram);
|
||||
addIcon(faQuestion);
|
||||
addIcon(faQuestionCircle);
|
||||
addIcon(faRedo);
|
||||
addIcon(faRemoveFormat);
|
||||
addIcon(faRobot);
|
||||
addIcon(faRss);
|
||||
addIcon(faSave);
|
||||
addIcon(faSatelliteDish);
|
||||
@@ -248,6 +274,7 @@ export const FontAwesomePlugin: Plugin<{}> = {
|
||||
addIcon(faSearchMinus);
|
||||
addIcon(faSearchPlus);
|
||||
addIcon(faServer);
|
||||
addIcon(faScrewdriver);
|
||||
addIcon(faSignInAlt);
|
||||
addIcon(faSignOutAlt);
|
||||
addIcon(faSlidersH);
|
||||
@@ -255,6 +282,7 @@ export const FontAwesomePlugin: Plugin<{}> = {
|
||||
addIcon(faSolidStickyNote);
|
||||
addIcon(faStickyNote as IconDefinition);
|
||||
addIcon(faStop);
|
||||
addIcon(faStream);
|
||||
addIcon(faSun);
|
||||
addIcon(faSync);
|
||||
addIcon(faSyncAlt);
|
||||
@@ -266,6 +294,7 @@ export const FontAwesomePlugin: Plugin<{}> = {
|
||||
addIcon(faTimes);
|
||||
addIcon(faTimesCircle);
|
||||
addIcon(faToolbox);
|
||||
addIcon(faTools);
|
||||
addIcon(faTrash);
|
||||
addIcon(faUndo);
|
||||
addIcon(faUnlink);
|
||||
@@ -275,6 +304,7 @@ export const FontAwesomePlugin: Plugin<{}> = {
|
||||
addIcon(faUsers);
|
||||
addIcon(faVariable);
|
||||
addIcon(faVault);
|
||||
addIcon(faVectorSquare);
|
||||
addIcon(faVideo);
|
||||
addIcon(faTree);
|
||||
addIcon(faUserLock);
|
||||
|
||||
@@ -0,0 +1,80 @@
|
||||
import { registerEndpointRenderer, svg } from '@jsplumb/browser-ui';
|
||||
import { N8nAddInputEndpoint } from './N8nAddInputEndpointType';
|
||||
|
||||
export const register = () => {
|
||||
registerEndpointRenderer<N8nAddInputEndpoint>(N8nAddInputEndpoint.type, {
|
||||
makeNode: (endpointInstance: N8nAddInputEndpoint) => {
|
||||
const xOffset = 1;
|
||||
const lineYOffset = -2;
|
||||
const width = endpointInstance.params.width;
|
||||
const height = endpointInstance.params.height;
|
||||
const unconnectedDiamondSize = width / 2;
|
||||
const unconnectedDiamondWidth = unconnectedDiamondSize * Math.sqrt(2);
|
||||
const unconnectedPlusStroke = 2;
|
||||
const unconnectedPlusSize = width - 2 * unconnectedPlusStroke;
|
||||
|
||||
const sizeDifference = (unconnectedPlusSize - unconnectedDiamondWidth) / 2;
|
||||
|
||||
const container = svg.node('g', {
|
||||
style: `--svg-color: var(${endpointInstance.params.color})`,
|
||||
width,
|
||||
height,
|
||||
});
|
||||
|
||||
const unconnectedGroup = svg.node('g', { class: 'add-input-endpoint-unconnected' });
|
||||
const unconnectedLine = svg.node('rect', {
|
||||
x: xOffset / 2 + unconnectedDiamondWidth / 2 + sizeDifference,
|
||||
y: unconnectedDiamondWidth + lineYOffset,
|
||||
width: 2,
|
||||
height: height - unconnectedDiamondWidth - unconnectedPlusSize,
|
||||
'stroke-width': 0,
|
||||
class: 'add-input-endpoint-line',
|
||||
});
|
||||
const unconnectedPlusGroup = svg.node('g', {
|
||||
transform: `translate(${xOffset / 2}, ${height - unconnectedPlusSize + lineYOffset})`,
|
||||
});
|
||||
const plusRectangle = svg.node('rect', {
|
||||
x: 1,
|
||||
y: 1,
|
||||
rx: 3,
|
||||
'stroke-width': unconnectedPlusStroke,
|
||||
fillOpacity: 0,
|
||||
height: unconnectedPlusSize,
|
||||
width: unconnectedPlusSize,
|
||||
class: 'add-input-endpoint-plus-rectangle',
|
||||
});
|
||||
const plusIcon = svg.node('path', {
|
||||
transform: `scale(${width / 24})`,
|
||||
d: 'm15.40655,9.89837l-3.30491,0l0,-3.30491c0,-0.40555 -0.32889,-0.73443 -0.73443,-0.73443l-0.73443,0c-0.40554,0 -0.73442,0.32888 -0.73442,0.73443l0,3.30491l-3.30491,0c-0.40555,0 -0.73443,0.32888 -0.73443,0.73442l0,0.73443c0,0.40554 0.32888,0.73443 0.73443,0.73443l3.30491,0l0,3.30491c0,0.40554 0.32888,0.73442 0.73442,0.73442l0.73443,0c0.40554,0 0.73443,-0.32888 0.73443,-0.73442l0,-3.30491l3.30491,0c0.40554,0 0.73442,-0.32889 0.73442,-0.73443l0,-0.73443c0,-0.40554 -0.32888,-0.73442 -0.73442,-0.73442z',
|
||||
class: 'add-input-endpoint-plus-icon',
|
||||
});
|
||||
|
||||
unconnectedPlusGroup.appendChild(plusRectangle);
|
||||
unconnectedPlusGroup.appendChild(plusIcon);
|
||||
unconnectedGroup.appendChild(unconnectedLine);
|
||||
unconnectedGroup.appendChild(unconnectedPlusGroup);
|
||||
|
||||
const defaultGroup = svg.node('g', { class: 'add-input-endpoint-default' });
|
||||
const defaultDiamond = svg.node('rect', {
|
||||
x: xOffset + sizeDifference + unconnectedPlusStroke,
|
||||
y: 0,
|
||||
'stroke-width': 0,
|
||||
width: unconnectedDiamondSize,
|
||||
height: unconnectedDiamondSize,
|
||||
transform: `translate(${unconnectedDiamondWidth / 2}, 0) rotate(45)`,
|
||||
class: 'add-input-endpoint-diamond',
|
||||
});
|
||||
|
||||
defaultGroup.appendChild(defaultDiamond);
|
||||
|
||||
container.appendChild(unconnectedGroup);
|
||||
container.appendChild(defaultGroup);
|
||||
|
||||
endpointInstance.setupOverlays();
|
||||
endpointInstance.setVisible(false);
|
||||
|
||||
return container;
|
||||
},
|
||||
updateNode: (endpointInstance: N8nAddInputEndpoint) => {},
|
||||
});
|
||||
};
|
||||
@@ -0,0 +1,79 @@
|
||||
import type { EndpointHandler, Endpoint } from '@jsplumb/core';
|
||||
import { EndpointRepresentation } from '@jsplumb/core';
|
||||
import type { AnchorPlacement, EndpointRepresentationParams } from '@jsplumb/common';
|
||||
import { EVENT_ENDPOINT_CLICK } from '@jsplumb/browser-ui';
|
||||
|
||||
export type ComputedN8nAddInputEndpoint = [number, number, number, number, number];
|
||||
interface N8nAddInputEndpointParams extends EndpointRepresentationParams {
|
||||
endpoint: Endpoint;
|
||||
width: number;
|
||||
height: number;
|
||||
color: string;
|
||||
multiple: boolean;
|
||||
}
|
||||
export const N8nAddInputEndpointType = 'N8nAddInput';
|
||||
export const EVENT_ADD_INPUT_ENDPOINT_CLICK = 'eventAddInputEndpointClick';
|
||||
export class N8nAddInputEndpoint extends EndpointRepresentation<ComputedN8nAddInputEndpoint> {
|
||||
params: N8nAddInputEndpointParams;
|
||||
|
||||
constructor(endpoint: Endpoint, params: N8nAddInputEndpointParams) {
|
||||
super(endpoint, params);
|
||||
|
||||
this.params = params;
|
||||
this.params.width = params.width || 18;
|
||||
this.params.height = params.height || 48;
|
||||
this.params.color = params.color || '--color-foreground-xdark';
|
||||
this.params.multiple = params.multiple || false;
|
||||
|
||||
this.unbindEvents();
|
||||
this.bindEvents();
|
||||
}
|
||||
|
||||
static type = N8nAddInputEndpointType;
|
||||
type = N8nAddInputEndpoint.type;
|
||||
|
||||
setupOverlays() {
|
||||
this.endpoint.instance.setSuspendDrawing(true);
|
||||
this.endpoint.instance.setSuspendDrawing(false);
|
||||
}
|
||||
bindEvents() {
|
||||
this.instance.bind(EVENT_ENDPOINT_CLICK, this.fireClickEvent);
|
||||
}
|
||||
unbindEvents() {
|
||||
this.instance.unbind(EVENT_ENDPOINT_CLICK, this.fireClickEvent);
|
||||
}
|
||||
fireClickEvent = (endpoint: Endpoint) => {
|
||||
if (endpoint === this.endpoint) {
|
||||
this.instance.fire(EVENT_ADD_INPUT_ENDPOINT_CLICK, this.endpoint);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
export const N8nAddInputEndpointHandler: EndpointHandler<
|
||||
N8nAddInputEndpoint,
|
||||
ComputedN8nAddInputEndpoint
|
||||
> = {
|
||||
type: N8nAddInputEndpoint.type,
|
||||
cls: N8nAddInputEndpoint,
|
||||
compute: (ep: N8nAddInputEndpoint, anchorPoint: AnchorPlacement): ComputedN8nAddInputEndpoint => {
|
||||
const x = anchorPoint.curX - ep.params.width / 2;
|
||||
const y = anchorPoint.curY - ep.params.width / 2;
|
||||
const w = ep.params.width;
|
||||
const h = ep.params.height;
|
||||
|
||||
ep.x = x;
|
||||
ep.y = y;
|
||||
ep.w = w;
|
||||
ep.h = h;
|
||||
|
||||
ep.addClass('add-input-endpoint');
|
||||
if (ep.params.multiple) {
|
||||
ep.addClass('add-input-endpoint-multiple');
|
||||
}
|
||||
return [x, y, w, h, ep.params.width];
|
||||
},
|
||||
|
||||
getParams: (ep: N8nAddInputEndpoint): N8nAddInputEndpointParams => {
|
||||
return ep.params;
|
||||
},
|
||||
};
|
||||
19
packages/editor-ui/src/plugins/jsplumb/index.ts
Normal file
19
packages/editor-ui/src/plugins/jsplumb/index.ts
Normal file
@@ -0,0 +1,19 @@
|
||||
import type { Plugin } from 'vue';
|
||||
import { N8nPlusEndpointHandler } from '@/plugins/jsplumb/N8nPlusEndpointType';
|
||||
import * as N8nPlusEndpointRenderer from '@/plugins/jsplumb/N8nPlusEndpointRenderer';
|
||||
import { N8nConnector } from '@/plugins/connectors/N8nCustomConnector';
|
||||
import * as N8nAddInputEndpointRenderer from '@/plugins/jsplumb/N8nAddInputEndpointRenderer';
|
||||
import { N8nAddInputEndpointHandler } from '@/plugins/jsplumb/N8nAddInputEndpointType';
|
||||
import { Connectors, EndpointFactory } from '@jsplumb/core';
|
||||
|
||||
export const JsPlumbPlugin: Plugin<{}> = {
|
||||
install: () => {
|
||||
Connectors.register(N8nConnector.type, N8nConnector);
|
||||
|
||||
N8nPlusEndpointRenderer.register();
|
||||
EndpointFactory.registerHandler(N8nPlusEndpointHandler);
|
||||
|
||||
N8nAddInputEndpointRenderer.register();
|
||||
EndpointFactory.registerHandler(N8nAddInputEndpointHandler);
|
||||
},
|
||||
};
|
||||
Reference in New Issue
Block a user