feat(n8n Form Trigger Node, Chat Trigger Node): Allow to customize form and chat css (#13506)

This commit is contained in:
oleg
2025-02-28 12:27:49 +01:00
committed by GitHub
parent c4f3293778
commit 289041e997
29 changed files with 1278 additions and 377 deletions

View File

@@ -12,6 +12,7 @@ import type {
INodeProperties,
} from 'n8n-workflow';
import { cssVariables } from './constants';
import { validateAuth } from './GenericFunctions';
import { createPage } from './templates';
import type { LoadPreviousSessionChatOption } from './types';
@@ -378,6 +379,29 @@ export class ChatTrigger extends Node {
placeholder: 'e.g. Welcome',
description: 'Shown at the top of the chat',
},
{
displayName: 'Custom Chat Styling',
name: 'customCss',
type: 'string',
typeOptions: {
rows: 10,
editor: 'cssEditor',
},
displayOptions: {
show: {
'/mode': ['hostedChat'],
},
},
default: `
${cssVariables}
/* You can override any class styles, too. Right-click inspect in Chat UI to find class to override. */
.chat-message {
max-width: 50%;
}
`.trim(),
description: 'Override default styling of the public chat interface with CSS',
},
],
},
],
@@ -466,6 +490,7 @@ export class ChatTrigger extends Node {
title?: string;
allowFileUploads?: boolean;
allowedFilesMimeTypes?: string;
customCss?: string;
};
const req = ctx.getRequestObject();
@@ -517,6 +542,7 @@ export class ChatTrigger extends Node {
authentication,
allowFileUploads: options.allowFileUploads,
allowedFilesMimeTypes: options.allowedFilesMimeTypes,
customCss: options.customCss,
});
res.status(200).send(page).end();

View File

@@ -0,0 +1,122 @@
// CSS Variables are defined in `@n8n/chat/src/css/_tokens.scss`
export const cssVariables = `
:root {
/* Colors */
--chat--color-primary: #e74266;
--chat--color-primary-shade-50: #db4061;
--chat--color-primary-shade-100: #cf3c5c;
--chat--color-secondary: #20b69e;
--chat--color-secondary-shade-50: #1ca08a;
--chat--color-white: #ffffff;
--chat--color-light: #f2f4f8;
--chat--color-light-shade-50: #e6e9f1;
--chat--color-light-shade-100: #c2c5cc;
--chat--color-medium: #d2d4d9;
--chat--color-dark: #101330;
--chat--color-disabled: #777980;
--chat--color-typing: #404040;
/* Base Layout */
--chat--spacing: 1rem;
--chat--border-radius: 0.25rem;
--chat--transition-duration: 0.15s;
--chat--font-family: (
-apple-system,
BlinkMacSystemFont,
'Segoe UI',
Roboto,
Oxygen-Sans,
Ubuntu,
Cantarell,
'Helvetica Neue',
sans-serif
);
/* Window Dimensions */
--chat--window--width: 400px;
--chat--window--height: 600px;
--chat--window--bottom: var(--chat--spacing);
--chat--window--right: var(--chat--spacing);
--chat--window--z-index: 9999;
--chat--window--border: 1px solid var(--chat--color-light-shade-50);
--chat--window--border-radius: var(--chat--border-radius);
--chat--window--margin-bottom: var(--chat--spacing);
/* Header Styles */
--chat--header-height: auto;
--chat--header--padding: var(--chat--spacing);
--chat--header--background: var(--chat--color-dark);
--chat--header--color: var(--chat--color-light);
--chat--header--border-top: none;
--chat--header--border-bottom: none;
--chat--header--border-left: none;
--chat--header--border-right: none;
--chat--heading--font-size: 2em;
--chat--subtitle--font-size: inherit;
--chat--subtitle--line-height: 1.8;
/* Message Styles */
--chat--message--font-size: 1rem;
--chat--message--padding: var(--chat--spacing);
--chat--message--border-radius: var(--chat--border-radius);
--chat--message-line-height: 1.5;
--chat--message--margin-bottom: calc(var(--chat--spacing) * 1);
--chat--message--bot--background: var(--chat--color-white);
--chat--message--bot--color: var(--chat--color-dark);
--chat--message--bot--border: none;
--chat--message--user--background: var(--chat--color-secondary);
--chat--message--user--color: var(--chat--color-white);
--chat--message--user--border: none;
--chat--message--pre--background: rgba(0, 0, 0, 0.05);
--chat--messages-list--padding: var(--chat--spacing);
/* Toggle Button */
--chat--toggle--size: 64px;
--chat--toggle--width: var(--chat--toggle--size);
--chat--toggle--height: var(--chat--toggle--size);
--chat--toggle--border-radius: 50%;
--chat--toggle--background: var(--chat--color-primary);
--chat--toggle--hover--background: var(--chat--color-primary-shade-50);
--chat--toggle--active--background: var(--chat--color-primary-shade-100);
--chat--toggle--color: var(--chat--color-white);
/* Input Area */
--chat--textarea--height: 50px;
--chat--textarea--max-height: 30rem;
--chat--input--font-size: inherit;
--chat--input--border: 0;
--chat--input--border-radius: 0;
--chat--input--padding: 0.8rem;
--chat--input--background: var(--chat--color-white);
--chat--input--text-color: initial;
--chat--input--line-height: 1.5;
--chat--input--placeholder--font-size: var(--chat--input--font-size);
--chat--input--border-active: 0;
--chat--input--left--panel--width: 2rem;
/* Button Styles */
--chat--button--color: var(--chat--color-light);
--chat--button--background: var(--chat--color-primary);
--chat--button--padding: calc(var(--chat--spacing) * 1 / 2) var(--chat--spacing);
--chat--button--border-radius: var(--chat--border-radius);
--chat--button--hover--color: var(--chat--color-light);
--chat--button--hover--background: var(--chat--color-primary-shade-50);
--chat--close--button--color-hover: var(--chat--color-primary);
/* Send and File Buttons */
--chat--input--send--button--background: var(--chat--color-white);
--chat--input--send--button--color: var(--chat--color-light);
--chat--input--send--button--background-hover: var(--chat--color-primary-shade-50);
--chat--input--send--button--color-hover: var(--chat--color-secondary-shade-50);
--chat--input--file--button--background: var(--chat--color-white);
--chat--input--file--button--color: var(--chat--color-secondary);
--chat--input--file--button--background-hover: var(--chat--input--file--button--background);
--chat--input--file--button--color-hover: var(--chat--color-secondary-shade-50);
--chat--files-spacing: 0.25rem;
/* Body and Footer */
--chat--body--background: var(--chat--color-light);
--chat--footer--background: var(--chat--color-light);
--chat--footer--color: var(--chat--color-dark);
}
`;

View File

@@ -1,5 +1,6 @@
import type { AuthenticationChatOption, LoadPreviousSessionChatOption } from './types';
import sanitizeHtml from 'sanitize-html';
import type { AuthenticationChatOption, LoadPreviousSessionChatOption } from './types';
export function createPage({
instanceId,
webhookUrl,
@@ -10,6 +11,7 @@ export function createPage({
authentication,
allowFileUploads,
allowedFilesMimeTypes,
customCss,
}: {
instanceId: string;
webhookUrl?: string;
@@ -23,6 +25,7 @@ export function createPage({
authentication: AuthenticationChatOption;
allowFileUploads?: boolean;
allowedFilesMimeTypes?: string;
customCss?: string;
}) {
const validAuthenticationOptions: AuthenticationChatOption[] = [
'none',
@@ -41,6 +44,11 @@ export function createPage({
const sanitizedShowWelcomeScreen = !!showWelcomeScreen;
const sanitizedAllowFileUploads = !!allowFileUploads;
const sanitizedAllowedFilesMimeTypes = allowedFilesMimeTypes?.toString() ?? '';
const sanitizedCustomCss = sanitizeHtml(`<style>${customCss?.toString() ?? ''}</style>`, {
allowedTags: ['style'],
allowedAttributes: false,
});
const sanitizedLoadPreviousSession = validLoadPreviousSessionOptions.includes(
loadPreviousSession as LoadPreviousSessionChatOption,
)
@@ -63,6 +71,7 @@ export function createPage({
height: 100%;
}
</style>
${sanitizedCustomCss}
</head>
<body>
<script type="module">

View File

@@ -130,6 +130,7 @@
"@types/mime-types": "^2.1.0",
"@types/pg": "^8.11.6",
"@types/temp": "^0.9.1",
"@types/sanitize-html": "^2.11.0",
"n8n-core": "workspace:*"
},
"dependencies": {
@@ -183,6 +184,7 @@
"pdf-parse": "1.1.1",
"pg": "8.12.0",
"redis": "4.6.12",
"sanitize-html": "2.12.1",
"sqlite3": "5.1.7",
"temp": "0.9.4",
"tmp-promise": "3.0.3",