mirror of
https://github.com/n8n-io/n8n.git
synced 2025-03-05 20:50:17 -08:00
feat(editor): Chat Trigger tweaks (#9618)
Signed-off-by: Oleg Ivaniv <me@olegivaniv.com>
This commit is contained in:
parent
42ceec6879
commit
5322802992
|
@ -52,7 +52,7 @@ const markdownOptions = {
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
const messageComponents = options.messageComponents ?? {};
|
const messageComponents = options?.messageComponents ?? {};
|
||||||
</script>
|
</script>
|
||||||
<template>
|
<template>
|
||||||
<div class="chat-message" :class="classes">
|
<div class="chat-message" :class="classes">
|
||||||
|
|
|
@ -23,7 +23,7 @@ export class ChatTrigger implements INodeType {
|
||||||
version: 1,
|
version: 1,
|
||||||
description: 'Runs the workflow when an n8n generated webchat is submitted',
|
description: 'Runs the workflow when an n8n generated webchat is submitted',
|
||||||
defaults: {
|
defaults: {
|
||||||
name: 'Chat Trigger',
|
name: 'When chat message received',
|
||||||
},
|
},
|
||||||
codex: {
|
codex: {
|
||||||
categories: ['Core Nodes'],
|
categories: ['Core Nodes'],
|
||||||
|
|
|
@ -18,7 +18,7 @@ export class ManualChatTrigger implements INodeType {
|
||||||
maxNodes: 1,
|
maxNodes: 1,
|
||||||
hidden: true,
|
hidden: true,
|
||||||
defaults: {
|
defaults: {
|
||||||
name: 'On new manual Chat Message',
|
name: 'When chat message received',
|
||||||
color: '#909298',
|
color: '#909298',
|
||||||
},
|
},
|
||||||
codex: {
|
codex: {
|
||||||
|
|
|
@ -76,7 +76,7 @@ export const versionDescription: INodeTypeDescription = {
|
||||||
name: 'OpenAI',
|
name: 'OpenAI',
|
||||||
},
|
},
|
||||||
codex: {
|
codex: {
|
||||||
alias: ['LangChain', 'ChatGPT', 'DallE'],
|
alias: ['LangChain', 'ChatGPT', 'DallE', 'whisper', 'audio', 'transcribe', 'tts', 'assistant'],
|
||||||
categories: ['AI'],
|
categories: ['AI'],
|
||||||
subcategories: {
|
subcategories: {
|
||||||
AI: ['Agents', 'Miscellaneous', 'Root Nodes'],
|
AI: ['Agents', 'Miscellaneous', 'Root Nodes'],
|
||||||
|
|
|
@ -181,7 +181,7 @@ export class ActiveWorkflows {
|
||||||
const cronTimeParts = cronTime.split(' ');
|
const cronTimeParts = cronTime.split(' ');
|
||||||
if (cronTimeParts.length > 0 && cronTimeParts[0].includes('*')) {
|
if (cronTimeParts.length > 0 && cronTimeParts[0].includes('*')) {
|
||||||
throw new ApplicationError(
|
throw new ApplicationError(
|
||||||
'The polling interval is too short. It has to be at least a minute!',
|
'The polling interval is too short. It has to be at least a minute.',
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -118,7 +118,7 @@ export abstract class DirectoryLoader {
|
||||||
|
|
||||||
if (currentVersionNode.hasOwnProperty('executeSingle')) {
|
if (currentVersionNode.hasOwnProperty('executeSingle')) {
|
||||||
throw new ApplicationError(
|
throw new ApplicationError(
|
||||||
'"executeSingle" has been removed. Please update the code of this node to use "execute" instead!',
|
'"executeSingle" has been removed. Please update the code of this node to use "execute" instead.',
|
||||||
{ extra: { nodeName: `${this.packageName}.${nodeName}` } },
|
{ extra: { nodeName: `${this.packageName}.${nodeName}` } },
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
|
@ -1547,11 +1547,11 @@ export async function requestOAuth1(
|
||||||
const credentials = await this.getCredentials(credentialsType);
|
const credentials = await this.getCredentials(credentialsType);
|
||||||
|
|
||||||
if (credentials === undefined) {
|
if (credentials === undefined) {
|
||||||
throw new ApplicationError('No credentials were returned!');
|
throw new ApplicationError('No credentials were returned');
|
||||||
}
|
}
|
||||||
|
|
||||||
if (credentials.oauthTokenData === undefined) {
|
if (credentials.oauthTokenData === undefined) {
|
||||||
throw new ApplicationError('OAuth credentials not connected!');
|
throw new ApplicationError('OAuth credentials not connected');
|
||||||
}
|
}
|
||||||
|
|
||||||
const oauth = new clientOAuth1({
|
const oauth = new clientOAuth1({
|
||||||
|
@ -1647,7 +1647,7 @@ export async function httpRequestWithAuthentication(
|
||||||
if (credentialsDecrypted === undefined) {
|
if (credentialsDecrypted === undefined) {
|
||||||
throw new NodeOperationError(
|
throw new NodeOperationError(
|
||||||
node,
|
node,
|
||||||
`Node "${node.name}" does not have any credentials of type "${credentialsType}" set!`,
|
`Node "${node.name}" does not have any credentials of type "${credentialsType}" set`,
|
||||||
{ level: 'warning' },
|
{ level: 'warning' },
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
@ -1844,7 +1844,7 @@ export async function requestWithAuthentication(
|
||||||
if (credentialsDecrypted === undefined) {
|
if (credentialsDecrypted === undefined) {
|
||||||
throw new NodeOperationError(
|
throw new NodeOperationError(
|
||||||
node,
|
node,
|
||||||
`Node "${node.name}" does not have any credentials of type "${credentialsType}" set!`,
|
`Node "${node.name}" does not have any credentials of type "${credentialsType}" set`,
|
||||||
{ level: 'warning' },
|
{ level: 'warning' },
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
@ -1990,7 +1990,7 @@ export async function getCredentials(
|
||||||
if (nodeType === undefined) {
|
if (nodeType === undefined) {
|
||||||
throw new NodeOperationError(
|
throw new NodeOperationError(
|
||||||
node,
|
node,
|
||||||
`Node type "${node.type}" is not known so can not get credentials!`,
|
`Node type "${node.type}" is not known so can not get credentials`,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -2003,7 +2003,7 @@ export async function getCredentials(
|
||||||
if (nodeType.description.credentials === undefined) {
|
if (nodeType.description.credentials === undefined) {
|
||||||
throw new NodeOperationError(
|
throw new NodeOperationError(
|
||||||
node,
|
node,
|
||||||
`Node type "${node.type}" does not have any credentials defined!`,
|
`Node type "${node.type}" does not have any credentials defined`,
|
||||||
{ level: 'warning' },
|
{ level: 'warning' },
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
@ -2014,7 +2014,7 @@ export async function getCredentials(
|
||||||
if (nodeCredentialDescription === undefined) {
|
if (nodeCredentialDescription === undefined) {
|
||||||
throw new NodeOperationError(
|
throw new NodeOperationError(
|
||||||
node,
|
node,
|
||||||
`Node type "${node.type}" does not have any credentials of type "${type}" defined!`,
|
`Node type "${node.type}" does not have any credentials of type "${type}" defined`,
|
||||||
{ level: 'warning' },
|
{ level: 'warning' },
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
@ -2039,16 +2039,14 @@ export async function getCredentials(
|
||||||
if (nodeCredentialDescription?.required === true) {
|
if (nodeCredentialDescription?.required === true) {
|
||||||
// Credentials are required so error
|
// Credentials are required so error
|
||||||
if (!node.credentials) {
|
if (!node.credentials) {
|
||||||
throw new NodeOperationError(node, 'Node does not have any credentials set!', {
|
throw new NodeOperationError(node, 'Node does not have any credentials set', {
|
||||||
level: 'warning',
|
level: 'warning',
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
if (!node.credentials[type]) {
|
if (!node.credentials[type]) {
|
||||||
throw new NodeOperationError(
|
throw new NodeOperationError(node, `Node does not have any credentials set for "${type}"`, {
|
||||||
node,
|
level: 'warning',
|
||||||
`Node does not have any credentials set for "${type}"!`,
|
});
|
||||||
{ level: 'warning' },
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
// Credentials are not required
|
// Credentials are not required
|
||||||
|
@ -2782,7 +2780,7 @@ async function getInputConnectionData(
|
||||||
const nodes = await Promise.all(constParentNodes);
|
const nodes = await Promise.all(constParentNodes);
|
||||||
|
|
||||||
if (inputConfiguration.required && nodes.length === 0) {
|
if (inputConfiguration.required && nodes.length === 0) {
|
||||||
throw new NodeOperationError(node, `A ${inputName} processor node must be connected!`);
|
throw new NodeOperationError(node, `A ${inputName} processor node must be connected`);
|
||||||
}
|
}
|
||||||
if (
|
if (
|
||||||
inputConfiguration.maxConnections !== undefined &&
|
inputConfiguration.maxConnections !== undefined &&
|
||||||
|
@ -2790,7 +2788,7 @@ async function getInputConnectionData(
|
||||||
) {
|
) {
|
||||||
throw new NodeOperationError(
|
throw new NodeOperationError(
|
||||||
node,
|
node,
|
||||||
`Only ${inputConfiguration.maxConnections} ${inputName} processor nodes are/is allowed to be connected!`,
|
`Only ${inputConfiguration.maxConnections} ${inputName} processor nodes are/is allowed to be connected`,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -3335,12 +3333,12 @@ export function getExecutePollFunctions(
|
||||||
...getCommonWorkflowFunctions(workflow, node, additionalData),
|
...getCommonWorkflowFunctions(workflow, node, additionalData),
|
||||||
__emit: (): void => {
|
__emit: (): void => {
|
||||||
throw new ApplicationError(
|
throw new ApplicationError(
|
||||||
'Overwrite NodeExecuteFunctions.getExecutePollFunctions.__emit function!',
|
'Overwrite NodeExecuteFunctions.getExecutePollFunctions.__emit function',
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
__emitError() {
|
__emitError() {
|
||||||
throw new ApplicationError(
|
throw new ApplicationError(
|
||||||
'Overwrite NodeExecuteFunctions.getExecutePollFunctions.__emitError function!',
|
'Overwrite NodeExecuteFunctions.getExecutePollFunctions.__emitError function',
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
getMode: () => mode,
|
getMode: () => mode,
|
||||||
|
@ -3398,12 +3396,12 @@ export function getExecuteTriggerFunctions(
|
||||||
...getCommonWorkflowFunctions(workflow, node, additionalData),
|
...getCommonWorkflowFunctions(workflow, node, additionalData),
|
||||||
emit: (): void => {
|
emit: (): void => {
|
||||||
throw new ApplicationError(
|
throw new ApplicationError(
|
||||||
'Overwrite NodeExecuteFunctions.getExecuteTriggerFunctions.emit function!',
|
'Overwrite NodeExecuteFunctions.getExecuteTriggerFunctions.emit function',
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
emitError: (): void => {
|
emitError: (): void => {
|
||||||
throw new ApplicationError(
|
throw new ApplicationError(
|
||||||
'Overwrite NodeExecuteFunctions.getExecuteTriggerFunctions.emit function!',
|
'Overwrite NodeExecuteFunctions.getExecuteTriggerFunctions.emit function',
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
getMode: () => mode,
|
getMode: () => mode,
|
||||||
|
|
|
@ -1707,7 +1707,7 @@ export class WorkflowExecute {
|
||||||
return await this.processSuccessExecution(
|
return await this.processSuccessExecution(
|
||||||
startedAt,
|
startedAt,
|
||||||
workflow,
|
workflow,
|
||||||
new WorkflowOperationError('Workflow has been canceled or timed out!'),
|
new WorkflowOperationError('Workflow has been canceled or timed out'),
|
||||||
closeFunction,
|
closeFunction,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
|
@ -156,6 +156,7 @@ import { useRouter } from 'vue-router';
|
||||||
import { useNodeTypesStore } from '@/stores/nodeTypes.store';
|
import { useNodeTypesStore } from '@/stores/nodeTypes.store';
|
||||||
import { useRunWorkflow } from '@/composables/useRunWorkflow';
|
import { useRunWorkflow } from '@/composables/useRunWorkflow';
|
||||||
import { usePinnedData } from '@/composables/usePinnedData';
|
import { usePinnedData } from '@/composables/usePinnedData';
|
||||||
|
import { isEmpty } from '@/utils/typesUtils';
|
||||||
|
|
||||||
const RunDataAi = defineAsyncComponent(
|
const RunDataAi = defineAsyncComponent(
|
||||||
async () => await import('@/components/RunDataAi/RunDataAi.vue'),
|
async () => await import('@/components/RunDataAi/RunDataAi.vue'),
|
||||||
|
@ -495,7 +496,9 @@ export default defineComponent({
|
||||||
this.waitForExecution(response.executionId);
|
this.waitForExecution(response.executionId);
|
||||||
},
|
},
|
||||||
extractResponseMessage(responseData?: IDataObject) {
|
extractResponseMessage(responseData?: IDataObject) {
|
||||||
if (!responseData) return '<NO RESPONSE FOUND>';
|
if (!responseData || isEmpty(responseData)) {
|
||||||
|
return this.$locale.baseText('chat.window.chat.response.empty');
|
||||||
|
}
|
||||||
|
|
||||||
// Paths where the response message might be located
|
// Paths where the response message might be located
|
||||||
const paths = ['output', 'text', 'response.text'];
|
const paths = ['output', 'text', 'response.text'];
|
||||||
|
|
|
@ -170,6 +170,7 @@
|
||||||
"chat.window.chat.unpinAndExecute.title": "Unpin chat output data?",
|
"chat.window.chat.unpinAndExecute.title": "Unpin chat output data?",
|
||||||
"chat.window.chat.unpinAndExecute.confirm": "Unpin and send",
|
"chat.window.chat.unpinAndExecute.confirm": "Unpin and send",
|
||||||
"chat.window.chat.unpinAndExecute.cancel": "Cancel",
|
"chat.window.chat.unpinAndExecute.cancel": "Cancel",
|
||||||
|
"chat.window.chat.response.empty": "[No response. Make sure the last executed node outputs the content to display here]",
|
||||||
"chatEmbed.infoTip.description": "Add chat to external applications using the n8n chat package.",
|
"chatEmbed.infoTip.description": "Add chat to external applications using the n8n chat package.",
|
||||||
"chatEmbed.infoTip.link": "More info",
|
"chatEmbed.infoTip.link": "More info",
|
||||||
"chatEmbed.title": "Embed Chat in your website",
|
"chatEmbed.title": "Embed Chat in your website",
|
||||||
|
|
|
@ -725,12 +725,23 @@ export default defineComponent({
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
isManualChatOnly(): boolean {
|
isManualChatOnly(): boolean {
|
||||||
return this.containsChatNodes && this.triggerNodes.length === 1;
|
if (!this.canvasChatNode) return false;
|
||||||
|
|
||||||
|
return this.containsChatNodes && this.triggerNodes.length === 1 && !this.pinnedChatNodeData;
|
||||||
|
},
|
||||||
|
canvasChatNode() {
|
||||||
|
return this.nodes.find((node) => node.type === CHAT_TRIGGER_NODE_TYPE);
|
||||||
|
},
|
||||||
|
pinnedChatNodeData() {
|
||||||
|
if (!this.canvasChatNode) return null;
|
||||||
|
|
||||||
|
return this.workflowsStore.pinDataByNodeName(this.canvasChatNode.name);
|
||||||
},
|
},
|
||||||
isExecutionDisabled(): boolean {
|
isExecutionDisabled(): boolean {
|
||||||
if (
|
if (
|
||||||
this.containsChatNodes &&
|
this.containsChatNodes &&
|
||||||
this.triggerNodes.every((node) => node.disabled || node.type === CHAT_TRIGGER_NODE_TYPE)
|
this.triggerNodes.every((node) => node.disabled || node.type === CHAT_TRIGGER_NODE_TYPE) &&
|
||||||
|
!this.pinnedChatNodeData
|
||||||
) {
|
) {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in a new issue