Files
alighasami 3d5eaf9445
Security: Sync from Public / sync-from-public (push) Has been cancelled
Test: Benchmark Nightly / build (push) Has been cancelled
Test: Benchmark Nightly / Notify Cats on failure (push) Has been cancelled
CI: Python / Checks (push) Has been cancelled
Test: Evals Python / Workflow Comparison Python (push) Has been cancelled
Util: Check Docs URLs / check-docs-urls (push) Has been cancelled
Test: Visual Storybook / Cloudflare Pages (push) Has been cancelled
Test: E2E Performance / build-and-test-performance (push) Has been cancelled
Test: Workflows Nightly / Run Workflow Tests (push) Has been cancelled
Util: Cleanup CI Docker Images / Delete stale CI images (push) Has been cancelled
Test: Benchmark Destroy Env / build (push) Has been cancelled
Util: Update Node Popularity / update-popularity (push) Has been cancelled
Test: E2E Coverage Weekly / Coverage Tests (push) Has been cancelled
first commit
2026-03-17 16:22:57 +03:30

161 lines
4.2 KiB
TypeScript

import {
getSendAndWaitConfig,
getSendAndWaitProperties,
} from 'n8n-nodes-base/dist/utils/sendAndWait/utils';
import {
ChatNodeMessageType,
FREE_TEXT_CHAT_RESPONSE_TYPE,
NodeOperationError,
UserError,
WAIT_INDEFINITELY,
} from 'n8n-workflow';
import type {
ChatNodeMessage,
ChatNodeMessageButtonType,
IExecuteFunctions,
INodeProperties,
INodePropertyOptions,
} from 'n8n-workflow';
export function configureWaitTillDate(context: IExecuteFunctions) {
let waitTill = WAIT_INDEFINITELY;
const limitOptions = context.getNodeParameter('options.limitWaitTime.values', 0, {}) as {
limitType?: string;
resumeAmount?: number;
resumeUnit?: string;
maxDateAndTime?: string;
};
if (Object.keys(limitOptions).length) {
try {
if (limitOptions.limitType === 'afterTimeInterval') {
let waitAmount = limitOptions.resumeAmount as number;
if (limitOptions.resumeUnit === 'minutes') {
waitAmount *= 60;
}
if (limitOptions.resumeUnit === 'hours') {
waitAmount *= 60 * 60;
}
if (limitOptions.resumeUnit === 'days') {
waitAmount *= 60 * 60 * 24;
}
waitAmount *= 1000;
waitTill = new Date(new Date().getTime() + waitAmount);
} else {
waitTill = new Date(limitOptions.maxDateAndTime as string);
}
if (isNaN(waitTill.getTime())) {
throw new UserError('Invalid date format');
}
} catch (error) {
throw new NodeOperationError(context.getNode(), 'Could not configure Limit Wait Time', {
description: error.message,
});
}
}
return waitTill;
}
export const configureInputs = (parameters: { options?: { memoryConnection?: boolean } }) => {
const inputs = [
{
type: 'main',
},
];
if (parameters.options?.memoryConnection) {
return [
...inputs,
{
type: 'ai_memory',
displayName: 'Memory',
maxConnections: 1,
},
];
}
return inputs;
};
const freeTextResponseTypeOption: INodePropertyOptions = {
name: 'Free Text',
// use a different name to not show options for `freeText` response type
value: FREE_TEXT_CHAT_RESPONSE_TYPE,
description: 'User can submit a response in the chat',
};
const blockUserInput: INodeProperties = {
displayName: 'Block User Input',
name: 'blockUserInput',
type: 'boolean',
default: false,
description: 'Whether to block input from the user while waiting for approval',
displayOptions: {
show: {
responseType: ['approval'],
},
},
};
export const getSendAndWaitPropertiesForChatNode = () => {
const originalProperties = getSendAndWaitProperties([], null);
const filteredProperties = originalProperties.filter(
// `subject` is not needed and we provide our own `message` and `options` properties
(p) => p.name !== 'subject' && p.name !== 'message' && p.name !== 'options',
);
const responseTypeProperty = filteredProperties.find((p) => p.name === 'responseType');
if (responseTypeProperty) {
const approvalOption = responseTypeProperty.options?.find(
(o) => 'value' in o && o.value === 'approval',
);
responseTypeProperty.options = approvalOption
? [
// for now we only support `approval` and `freeText` response types
approvalOption,
freeTextResponseTypeOption,
]
: [freeTextResponseTypeOption];
responseTypeProperty.default = FREE_TEXT_CHAT_RESPONSE_TYPE;
}
filteredProperties.splice(1, 0, blockUserInput);
return filteredProperties;
};
export function getChatMessage(ctx: IExecuteFunctions): ChatNodeMessage {
const nodeVersion = ctx.getNode().typeVersion;
const message = ctx.getNodeParameter('message', 0, '') as string;
if (nodeVersion < 1.1) {
return message;
}
const responseType = ctx.getNodeParameter(
'responseType',
0,
FREE_TEXT_CHAT_RESPONSE_TYPE,
) as string;
if (responseType === FREE_TEXT_CHAT_RESPONSE_TYPE) {
// for free text, we just return the message
// since the user will respond with the text in the chat
return message;
}
const blockUserInput = ctx.getNodeParameter('blockUserInput', 0, false) as boolean;
const config = getSendAndWaitConfig(ctx);
return {
type: ChatNodeMessageType.WITH_BUTTONS,
text: message,
blockUserInput,
// the buttons are reversed to show the primary button first
buttons: [...config.options].reverse().map((option) => ({
text: option.label,
link: option.url,
type: option.style as ChatNodeMessageButtonType,
})),
};
}