feat(editor): Chat Trigger tweaks (#9618)

Signed-off-by: Oleg Ivaniv <me@olegivaniv.com>
This commit is contained in:
oleg 2024-06-04 17:24:18 +02:00 committed by GitHub
parent 42ceec6879
commit 5322802992
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
11 changed files with 42 additions and 29 deletions

View file

@ -52,7 +52,7 @@ const markdownOptions = {
},
};
const messageComponents = options.messageComponents ?? {};
const messageComponents = options?.messageComponents ?? {};
</script>
<template>
<div class="chat-message" :class="classes">

View file

@ -23,7 +23,7 @@ export class ChatTrigger implements INodeType {
version: 1,
description: 'Runs the workflow when an n8n generated webchat is submitted',
defaults: {
name: 'Chat Trigger',
name: 'When chat message received',
},
codex: {
categories: ['Core Nodes'],

View file

@ -18,7 +18,7 @@ export class ManualChatTrigger implements INodeType {
maxNodes: 1,
hidden: true,
defaults: {
name: 'On new manual Chat Message',
name: 'When chat message received',
color: '#909298',
},
codex: {

View file

@ -76,7 +76,7 @@ export const versionDescription: INodeTypeDescription = {
name: 'OpenAI',
},
codex: {
alias: ['LangChain', 'ChatGPT', 'DallE'],
alias: ['LangChain', 'ChatGPT', 'DallE', 'whisper', 'audio', 'transcribe', 'tts', 'assistant'],
categories: ['AI'],
subcategories: {
AI: ['Agents', 'Miscellaneous', 'Root Nodes'],

View file

@ -181,7 +181,7 @@ export class ActiveWorkflows {
const cronTimeParts = cronTime.split(' ');
if (cronTimeParts.length > 0 && cronTimeParts[0].includes('*')) {
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.',
);
}

View file

@ -118,7 +118,7 @@ export abstract class DirectoryLoader {
if (currentVersionNode.hasOwnProperty('executeSingle')) {
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}` } },
);
}

View file

@ -1547,11 +1547,11 @@ export async function requestOAuth1(
const credentials = await this.getCredentials(credentialsType);
if (credentials === undefined) {
throw new ApplicationError('No credentials were returned!');
throw new ApplicationError('No credentials were returned');
}
if (credentials.oauthTokenData === undefined) {
throw new ApplicationError('OAuth credentials not connected!');
throw new ApplicationError('OAuth credentials not connected');
}
const oauth = new clientOAuth1({
@ -1647,7 +1647,7 @@ export async function httpRequestWithAuthentication(
if (credentialsDecrypted === undefined) {
throw new NodeOperationError(
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' },
);
}
@ -1844,7 +1844,7 @@ export async function requestWithAuthentication(
if (credentialsDecrypted === undefined) {
throw new NodeOperationError(
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' },
);
}
@ -1990,7 +1990,7 @@ export async function getCredentials(
if (nodeType === undefined) {
throw new NodeOperationError(
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) {
throw new NodeOperationError(
node,
`Node type "${node.type}" does not have any credentials defined!`,
`Node type "${node.type}" does not have any credentials defined`,
{ level: 'warning' },
);
}
@ -2014,7 +2014,7 @@ export async function getCredentials(
if (nodeCredentialDescription === undefined) {
throw new NodeOperationError(
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' },
);
}
@ -2039,16 +2039,14 @@ export async function getCredentials(
if (nodeCredentialDescription?.required === true) {
// Credentials are required so error
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',
});
}
if (!node.credentials[type]) {
throw new NodeOperationError(
node,
`Node does not have any credentials set for "${type}"!`,
{ level: 'warning' },
);
throw new NodeOperationError(node, `Node does not have any credentials set for "${type}"`, {
level: 'warning',
});
}
} else {
// Credentials are not required
@ -2782,7 +2780,7 @@ async function getInputConnectionData(
const nodes = await Promise.all(constParentNodes);
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 (
inputConfiguration.maxConnections !== undefined &&
@ -2790,7 +2788,7 @@ async function getInputConnectionData(
) {
throw new NodeOperationError(
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),
__emit: (): void => {
throw new ApplicationError(
'Overwrite NodeExecuteFunctions.getExecutePollFunctions.__emit function!',
'Overwrite NodeExecuteFunctions.getExecutePollFunctions.__emit function',
);
},
__emitError() {
throw new ApplicationError(
'Overwrite NodeExecuteFunctions.getExecutePollFunctions.__emitError function!',
'Overwrite NodeExecuteFunctions.getExecutePollFunctions.__emitError function',
);
},
getMode: () => mode,
@ -3398,12 +3396,12 @@ export function getExecuteTriggerFunctions(
...getCommonWorkflowFunctions(workflow, node, additionalData),
emit: (): void => {
throw new ApplicationError(
'Overwrite NodeExecuteFunctions.getExecuteTriggerFunctions.emit function!',
'Overwrite NodeExecuteFunctions.getExecuteTriggerFunctions.emit function',
);
},
emitError: (): void => {
throw new ApplicationError(
'Overwrite NodeExecuteFunctions.getExecuteTriggerFunctions.emit function!',
'Overwrite NodeExecuteFunctions.getExecuteTriggerFunctions.emit function',
);
},
getMode: () => mode,

View file

@ -1707,7 +1707,7 @@ export class WorkflowExecute {
return await this.processSuccessExecution(
startedAt,
workflow,
new WorkflowOperationError('Workflow has been canceled or timed out!'),
new WorkflowOperationError('Workflow has been canceled or timed out'),
closeFunction,
);
}

View file

@ -156,6 +156,7 @@ import { useRouter } from 'vue-router';
import { useNodeTypesStore } from '@/stores/nodeTypes.store';
import { useRunWorkflow } from '@/composables/useRunWorkflow';
import { usePinnedData } from '@/composables/usePinnedData';
import { isEmpty } from '@/utils/typesUtils';
const RunDataAi = defineAsyncComponent(
async () => await import('@/components/RunDataAi/RunDataAi.vue'),
@ -495,7 +496,9 @@ export default defineComponent({
this.waitForExecution(response.executionId);
},
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
const paths = ['output', 'text', 'response.text'];

View file

@ -170,6 +170,7 @@
"chat.window.chat.unpinAndExecute.title": "Unpin chat output data?",
"chat.window.chat.unpinAndExecute.confirm": "Unpin and send",
"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.link": "More info",
"chatEmbed.title": "Embed Chat in your website",

View file

@ -725,12 +725,23 @@ export default defineComponent({
);
},
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 {
if (
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;
}