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
161 lines
4.2 KiB
TypeScript
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,
|
|
})),
|
|
};
|
|
}
|