Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -41,8 +41,8 @@ export class Chat implements INodeType {
icon: 'fa:comments',
iconColor: 'black',
group: ['input'],
version: [1, 1.1, 1.2],
defaultVersion: 1.2,
version: [1, 1.1, 1.2, 1.3],
defaultVersion: 1.3,
description: 'Send a message into the chat',
defaults: {
name: 'Chat',
Expand Down Expand Up @@ -221,6 +221,9 @@ export class Chat implements INodeType {
}

if (!waitForReply) {
// return original message instead of input data
if (nodeVersion >= 1.3) return [[data]];

const inputData = context.getInputData();
return [inputData];
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -280,6 +280,34 @@ describe('Test Chat Node', () => {
expect(memory.chatHistory.addUserMessage).toHaveBeenCalledWith('user message');
});

it('v1.3 should return [[data]] (original message) instead of input data when not waiting for reply', async () => {
const chatNode = mock<INode>({
name: 'Chat',
type: CHAT_NODE_TYPE,
parameters: {},
typeVersion: 1.3,
});
const message = { json: { chatInput: 'user message' } };
const differentInputData = [{ json: { chatInput: 'other input' } }];
mockExecuteFunctions.getInputData.mockReturnValue(differentInputData);
mockExecuteFunctions.getNode.mockReturnValue(chatNode);
mockExecuteFunctions.getNodeParameter.mockImplementation((parameterName) => {
switch (parameterName) {
case 'operation':
return 'send';
case 'options':
return { memoryConnection: false };
default:
return undefined;
}
});

const result = await chat.onMessage(mockExecuteFunctions, message);

expect(result).toEqual([[message]]);
expect(result).not.toEqual([differentInputData]);
});

it('v1.2 should return output data directly without nesting into `data` field (except `approved`)', async () => {
const chatNode = mock<INode>({
name: 'Chat',
Expand Down
51 changes: 50 additions & 1 deletion packages/cli/src/chat/__tests__/utils.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,12 @@ import {
SEND_AND_WAIT_OPERATION,
} from 'n8n-workflow';

import { getMessage, getLastNodeExecuted, shouldResumeImmediately } from '../utils';
import {
getMessage,
getLastNodeExecuted,
getLastNodeMessage,
shouldResumeImmediately,
} from '../utils';

// helpers --------------------------------------------------------
const createMockExecution = (
Expand Down Expand Up @@ -492,3 +497,47 @@ describe('shouldResumeImmediately', () => {
expect(result).toBe(true);
});
});

describe('getLastNodeMessage', () => {
it('should return empty string when node type is not CHAT_NODE_TYPE', () => {
const execution = createMockExecution();
const node = createMockNode({ type: 'some-other-node-type' });
const result = getLastNodeMessage(execution, node);
expect(result).toBe('');
});

it('should return the message when node is CHAT_NODE_TYPE and execution has sendMessage', () => {
const execution = createMockExecution();
const node = createMockNode({ type: CHAT_NODE_TYPE });
const result = getLastNodeMessage(execution, node);
expect(result).toBe('Test message');
});

it('should return empty string when node is CHAT_NODE_TYPE but sendMessage is missing', () => {
const execution = createMockExecution({}, { json: { data: 'test' } });
const node = createMockNode({ type: CHAT_NODE_TYPE });
const result = getLastNodeMessage(execution, node);
expect(result).toBe('');
});

it('should return empty string when run data for the node is missing', () => {
const execution = createMockExecution({
data: {
resultData: {
lastNodeExecuted: 'TestNode',
runData: {},
},
},
});
const node = createMockNode({ type: CHAT_NODE_TYPE });
const result = getLastNodeMessage(execution, node);
expect(result).toBe('');
});

it('should return empty string when main output is missing', () => {
const execution = createMockExecution({}, undefined, [{ data: {} }]);
const node = createMockNode({ type: CHAT_NODE_TYPE });
const result = getLastNodeMessage(execution, node);
expect(result).toBe('');
});
});
9 changes: 7 additions & 2 deletions packages/cli/src/chat/chat-service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,12 @@ import {
type ChatRequest,
Session,
} from './chat-service.types';
import { getLastNodeExecuted, getMessage, shouldResumeImmediately } from './utils';
import {
getLastNodeExecuted,
getLastNodeMessage,
getMessage,
shouldResumeImmediately,
} from './utils';

const CHECK_FOR_RESPONSE_INTERVAL = 3000;
const DRAIN_TIMEOUT = 50;
Expand Down Expand Up @@ -150,7 +155,7 @@ export class ChatService {
session.connection.send(N8N_CONTINUE);
const data: ChatMessage = {
action: 'sendMessage',
chatInput: '',
chatInput: getLastNodeMessage(execution, lastNode),
sessionId: session.sessionId,
};
await this.resumeExecution(session.executionId, data, sessionKey);
Expand Down
9 changes: 9 additions & 0 deletions packages/cli/src/chat/utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -70,3 +70,12 @@ export function shouldResumeImmediately(lastNode: INode) {

return false;
}

export function getLastNodeMessage(execution: IExecutionResponse, lastNode: INode) {
if (lastNode.type !== CHAT_NODE_TYPE) return '';

const message =
execution.data?.resultData?.runData?.[lastNode.name]?.[0]?.data?.main?.[0]?.[0]?.sendMessage;

return (message as string) ?? '';
}