feat: Add Chat Trigger node (#7409)
Signed-off-by: Oleg Ivaniv <me@olegivaniv.com> Co-authored-by: Jan Oberhauser <jan.oberhauser@gmail.com> Co-authored-by: Jesper Bylund <mail@jesperbylund.com> Co-authored-by: OlegIvaniv <me@olegivaniv.com> Co-authored-by: Deborah <deborah@starfallprojects.co.uk> Co-authored-by: Jan Oberhauser <janober@users.noreply.github.com> Co-authored-by: Jon <jonathan.bennetts@gmail.com> Co-authored-by: कारतोफ्फेलस्क्रिप्ट™ <aditya@netroy.in> Co-authored-by: Michael Kret <88898367+michael-radency@users.noreply.github.com> Co-authored-by: Giulio Andreini <andreini@netseven.it> Co-authored-by: Mason Geloso <Mason.geloso@gmail.com> Co-authored-by: Mason Geloso <hone@Masons-Mac-mini.local> Co-authored-by: Mutasem Aldmour <mutasem@n8n.io>
This commit is contained in:
@@ -2,9 +2,9 @@
|
||||
This is an embeddable Chat widget for n8n. It allows the execution of AI-Powered Workflows through a Chat window.
|
||||
|
||||
## Prerequisites
|
||||
Create a n8n workflow which you want to execute via chat. The workflow has to be triggered using a **Webhook** node and return data using the **Respond to Webhook** node.
|
||||
Create a n8n workflow which you want to execute via chat. The workflow has to be triggered using a **Chat Trigger** node.
|
||||
|
||||
Open the **Webhook** node and add your domain to the **Domain Allowlist** field. This makes sure that only requests from your domain are accepted.
|
||||
Open the **Chat Trigger** node and add your domain to the **Allowed Origins (CORS)** field. This makes sure that only requests from your domain are accepted.
|
||||
|
||||
[See example workflow](https://github.com/n8n-io/n8n/blob/master/packages/%40n8n/chat/resources/workflow.json)
|
||||
|
||||
@@ -17,8 +17,6 @@ Each request is accompanied by an `action` query parameter, where `action` can b
|
||||
- `loadPreviousSession` - When the user opens the Chatbot again and the previous chat session should be loaded
|
||||
- `sendMessage` - When the user sends a message
|
||||
|
||||
We use the `Switch` node to handle the different actions.
|
||||
|
||||
## Installation
|
||||
|
||||
Open the **Webhook** node and replace `YOUR_PRODUCTION_WEBHOOK_URL` with your production URL. This is the URL that the Chat widget will use to send requests to.
|
||||
@@ -106,6 +104,10 @@ createChat({
|
||||
},
|
||||
target: '#n8n-chat',
|
||||
mode: 'window',
|
||||
chatInputKey: 'chatInput',
|
||||
chatSessionKey: 'sessionId',
|
||||
metadata: {},
|
||||
showWelcomeScreen: false,
|
||||
defaultLanguage: 'en',
|
||||
initialMessages: [
|
||||
'Hi there! 👋',
|
||||
@@ -148,6 +150,21 @@ createChat({
|
||||
- In `window` mode, the Chat window will be embedded in the target element as a chat toggle button and a fixed size chat window.
|
||||
- In `fullscreen` mode, the Chat will take up the entire width and height of its target container.
|
||||
|
||||
### `showWelcomeScreen`
|
||||
- **Type**: `boolean`
|
||||
- **Default**: `false`
|
||||
- **Description**: Whether to show the welcome screen when the Chat window is opened.
|
||||
|
||||
### `chatSessionKey`
|
||||
- **Type**: `string`
|
||||
- **Default**: `'sessionId'`
|
||||
- **Description**: The key to use for sending the chat history session ID for the AI Memory node.
|
||||
|
||||
### `chatInputKey`
|
||||
- **Type**: `string`
|
||||
- **Default**: `'chatInput'`
|
||||
- **Description**: The key to use for sending the chat input for the AI Agent node.
|
||||
|
||||
### `defaultLanguage`
|
||||
- **Type**: `string`
|
||||
- **Default**: `'en'`
|
||||
|
||||
21
packages/@n8n/chat/build.config.js
Normal file
21
packages/@n8n/chat/build.config.js
Normal file
@@ -0,0 +1,21 @@
|
||||
import { defineBuildConfig } from 'unbuild';
|
||||
|
||||
export default defineBuildConfig({
|
||||
entries: [
|
||||
{
|
||||
builder: 'mkdist',
|
||||
format: 'esm',
|
||||
input: './src',
|
||||
outDir: './tmp/lib',
|
||||
},
|
||||
{
|
||||
builder: 'mkdist',
|
||||
format: 'cjs',
|
||||
input: './src',
|
||||
outDir: './tmp/cjs',
|
||||
},
|
||||
],
|
||||
clean: true,
|
||||
declaration: true,
|
||||
failOnWarn: false,
|
||||
});
|
||||
@@ -3,9 +3,11 @@
|
||||
"version": "0.6.0",
|
||||
"scripts": {
|
||||
"dev": "pnpm run storybook",
|
||||
"build": "pnpm type-check && pnpm build:vite && pnpm build:prepare",
|
||||
"build:vite": "vite build && npm run build:vite:full",
|
||||
"build": "pnpm type-check && pnpm build:vite && pnpm run build:individual && npm run build:prepare",
|
||||
"build:full": "pnpm type-check && pnpm build:vite && pnpm build:vite:full && pnpm run build:individual && npm run build:prepare",
|
||||
"build:vite": "vite build",
|
||||
"build:vite:full": "INCLUDE_VUE=true vite build",
|
||||
"build:individual": "unbuild",
|
||||
"build:prepare": "node scripts/postbuild.js",
|
||||
"build:pack": "node scripts/pack.js",
|
||||
"preview": "vite preview",
|
||||
@@ -16,7 +18,7 @@
|
||||
"format": "prettier --write src/",
|
||||
"storybook": "storybook dev -p 6006 --no-open",
|
||||
"build:storybook": "storybook build",
|
||||
"release": "pnpm run build && cd dist && pnpm publish"
|
||||
"release": "pnpm run build:full && cd dist && pnpm publish"
|
||||
},
|
||||
"main": "./chat.umd.cjs",
|
||||
"module": "./chat.es.js",
|
||||
@@ -29,6 +31,10 @@
|
||||
"./style.css": {
|
||||
"import": "./style.css",
|
||||
"require": "./style.css"
|
||||
},
|
||||
"./*": {
|
||||
"import": "./*",
|
||||
"require": "./*"
|
||||
}
|
||||
},
|
||||
"dependencies": {
|
||||
@@ -39,8 +45,8 @@
|
||||
},
|
||||
"devDependencies": {
|
||||
"@iconify-json/mdi": "^1.1.54",
|
||||
"n8n-design-system": "workspace:*",
|
||||
"shelljs": "^0.8.5",
|
||||
"unbuild": "^2.0.0",
|
||||
"unplugin-icons": "^0.17.0",
|
||||
"vite-plugin-dts": "^3.6.4"
|
||||
},
|
||||
|
||||
BIN
packages/@n8n/chat/resources/images/fullscreen.png
Normal file
BIN
packages/@n8n/chat/resources/images/fullscreen.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 46 KiB |
BIN
packages/@n8n/chat/resources/images/windowed.png
Normal file
BIN
packages/@n8n/chat/resources/images/windowed.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 115 KiB |
238
packages/@n8n/chat/resources/workflow-manual.json
Normal file
238
packages/@n8n/chat/resources/workflow-manual.json
Normal file
@@ -0,0 +1,238 @@
|
||||
{
|
||||
"name": "Hosted n8n AI Chat Manual",
|
||||
"nodes": [
|
||||
{
|
||||
"parameters": {
|
||||
"options": {}
|
||||
},
|
||||
"id": "e6043748-44fc-4019-9301-5690fe26c614",
|
||||
"name": "OpenAI Chat Model",
|
||||
"type": "@n8n/n8n-nodes-langchain.lmChatOpenAi",
|
||||
"typeVersion": 1,
|
||||
"position": [
|
||||
860,
|
||||
540
|
||||
],
|
||||
"credentials": {
|
||||
"openAiApi": {
|
||||
"id": "cIIkOhl7tUX1KsL6",
|
||||
"name": "OpenAi account"
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"parameters": {
|
||||
"sessionKey": "={{ $json.sessionId }}"
|
||||
},
|
||||
"id": "0a68a59a-8ab6-4fa5-a1ea-b7f99a93109b",
|
||||
"name": "Window Buffer Memory",
|
||||
"type": "@n8n/n8n-nodes-langchain.memoryBufferWindow",
|
||||
"typeVersion": 1,
|
||||
"position": [
|
||||
640,
|
||||
540
|
||||
]
|
||||
},
|
||||
{
|
||||
"parameters": {
|
||||
"text": "={{ $json.chatInput }}",
|
||||
"options": {}
|
||||
},
|
||||
"id": "3d4e0fbf-d761-4569-b02e-f5c1eeb830c8",
|
||||
"name": "AI Agent",
|
||||
"type": "@n8n/n8n-nodes-langchain.agent",
|
||||
"typeVersion": 1.1,
|
||||
"position": [
|
||||
840,
|
||||
300
|
||||
]
|
||||
},
|
||||
{
|
||||
"parameters": {
|
||||
"dataType": "string",
|
||||
"value1": "={{ $json.action }}",
|
||||
"rules": {
|
||||
"rules": [
|
||||
{
|
||||
"value2": "loadPreviousSession",
|
||||
"outputKey": "loadPreviousSession"
|
||||
},
|
||||
{
|
||||
"value2": "sendMessage",
|
||||
"outputKey": "sendMessage"
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
"id": "84213c7b-abc7-4f40-9567-cd3484a4ae6b",
|
||||
"name": "Switch",
|
||||
"type": "n8n-nodes-base.switch",
|
||||
"typeVersion": 2,
|
||||
"position": [
|
||||
300,
|
||||
280
|
||||
]
|
||||
},
|
||||
{
|
||||
"parameters": {
|
||||
"simplifyOutput": false
|
||||
},
|
||||
"id": "3be7f076-98ed-472a-80b6-bf8d9538ac87",
|
||||
"name": "Chat Messages Retriever",
|
||||
"type": "@n8n/n8n-nodes-langchain.memoryChatRetriever",
|
||||
"typeVersion": 1,
|
||||
"position": [
|
||||
620,
|
||||
140
|
||||
]
|
||||
},
|
||||
{
|
||||
"parameters": {
|
||||
"options": {}
|
||||
},
|
||||
"id": "3417c644-8a91-4524-974a-45b4a46d0e2e",
|
||||
"name": "Respond to Webhook",
|
||||
"type": "n8n-nodes-base.respondToWebhook",
|
||||
"typeVersion": 1,
|
||||
"position": [
|
||||
1240,
|
||||
140
|
||||
]
|
||||
},
|
||||
{
|
||||
"parameters": {
|
||||
"public": true,
|
||||
"authentication": "n8nUserAuth",
|
||||
"options": {
|
||||
"loadPreviousSession": "manually",
|
||||
"responseMode": "responseNode"
|
||||
}
|
||||
},
|
||||
"id": "1b30c239-a819-45b4-b0ae-bdd5b92a5424",
|
||||
"name": "Chat Trigger",
|
||||
"type": "@n8n/n8n-nodes-langchain.chatTrigger",
|
||||
"typeVersion": 1,
|
||||
"position": [
|
||||
80,
|
||||
280
|
||||
],
|
||||
"webhookId": "ed3dea26-7d68-42b3-9032-98fe967d441d"
|
||||
},
|
||||
{
|
||||
"parameters": {
|
||||
"aggregate": "aggregateAllItemData",
|
||||
"options": {}
|
||||
},
|
||||
"id": "79672cf0-686b-41eb-90ae-fd31b6da837d",
|
||||
"name": "Aggregate",
|
||||
"type": "n8n-nodes-base.aggregate",
|
||||
"typeVersion": 1,
|
||||
"position": [
|
||||
1000,
|
||||
140
|
||||
]
|
||||
}
|
||||
],
|
||||
"pinData": {},
|
||||
"connections": {
|
||||
"OpenAI Chat Model": {
|
||||
"ai_languageModel": [
|
||||
[
|
||||
{
|
||||
"node": "AI Agent",
|
||||
"type": "ai_languageModel",
|
||||
"index": 0
|
||||
}
|
||||
]
|
||||
]
|
||||
},
|
||||
"Window Buffer Memory": {
|
||||
"ai_memory": [
|
||||
[
|
||||
{
|
||||
"node": "AI Agent",
|
||||
"type": "ai_memory",
|
||||
"index": 0
|
||||
},
|
||||
{
|
||||
"node": "Chat Messages Retriever",
|
||||
"type": "ai_memory",
|
||||
"index": 0
|
||||
}
|
||||
]
|
||||
]
|
||||
},
|
||||
"Switch": {
|
||||
"main": [
|
||||
[
|
||||
{
|
||||
"node": "Chat Messages Retriever",
|
||||
"type": "main",
|
||||
"index": 0
|
||||
}
|
||||
],
|
||||
[
|
||||
{
|
||||
"node": "AI Agent",
|
||||
"type": "main",
|
||||
"index": 0
|
||||
}
|
||||
]
|
||||
]
|
||||
},
|
||||
"Chat Messages Retriever": {
|
||||
"main": [
|
||||
[
|
||||
{
|
||||
"node": "Aggregate",
|
||||
"type": "main",
|
||||
"index": 0
|
||||
}
|
||||
]
|
||||
]
|
||||
},
|
||||
"AI Agent": {
|
||||
"main": [
|
||||
[
|
||||
{
|
||||
"node": "Respond to Webhook",
|
||||
"type": "main",
|
||||
"index": 0
|
||||
}
|
||||
]
|
||||
]
|
||||
},
|
||||
"Chat Trigger": {
|
||||
"main": [
|
||||
[
|
||||
{
|
||||
"node": "Switch",
|
||||
"type": "main",
|
||||
"index": 0
|
||||
}
|
||||
]
|
||||
]
|
||||
},
|
||||
"Aggregate": {
|
||||
"main": [
|
||||
[
|
||||
{
|
||||
"node": "Respond to Webhook",
|
||||
"type": "main",
|
||||
"index": 0
|
||||
}
|
||||
]
|
||||
]
|
||||
}
|
||||
},
|
||||
"active": true,
|
||||
"settings": {
|
||||
"executionOrder": "v1"
|
||||
},
|
||||
"versionId": "425c0efe-3aa0-4e0e-8c06-abe12234b1fd",
|
||||
"id": "1569HF92Y02EUtsU",
|
||||
"meta": {
|
||||
"instanceId": "374b43d8b8d6299cc777811a4ad220fc688ee2d54a308cfb0de4450a5233ca9e"
|
||||
},
|
||||
"tags": []
|
||||
}
|
||||
@@ -1,245 +1,77 @@
|
||||
{
|
||||
"name": "AI Webhook Chat",
|
||||
"name": "Hosted n8n AI Chat",
|
||||
"nodes": [
|
||||
{
|
||||
"parameters": {
|
||||
"httpMethod": "POST",
|
||||
"path": "513107b3-6f3a-4a1e-af21-659f0ed14183",
|
||||
"responseMode": "responseNode",
|
||||
"options": {
|
||||
"domainAllowlist": "*.localhost"
|
||||
}
|
||||
},
|
||||
"id": "51ab2689-647d-4cff-9d6f-0ba4df45e904",
|
||||
"name": "Webhook",
|
||||
"type": "n8n-nodes-base.webhook",
|
||||
"typeVersion": 1,
|
||||
"position": [
|
||||
900,
|
||||
200
|
||||
],
|
||||
"webhookId": "513107b3-6f3a-4a1e-af21-659f0ed14183"
|
||||
},
|
||||
{
|
||||
"parameters": {
|
||||
"options": {}
|
||||
},
|
||||
"id": "3c7fd563-f610-41fa-b198-7fcf100e2815",
|
||||
"name": "Chat OpenAI",
|
||||
"id": "4c109d13-62a2-4e23-9979-e50201db743d",
|
||||
"name": "OpenAI Chat Model",
|
||||
"type": "@n8n/n8n-nodes-langchain.lmChatOpenAi",
|
||||
"typeVersion": 1,
|
||||
"position": [
|
||||
1720,
|
||||
620
|
||||
640,
|
||||
540
|
||||
],
|
||||
"credentials": {
|
||||
"openAiApi": {
|
||||
"id": "B5Fiv70Adfg6htxn",
|
||||
"name": "Alex's OpenAI Account"
|
||||
"id": "cIIkOhl7tUX1KsL6",
|
||||
"name": "OpenAi account"
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"parameters": {
|
||||
"sessionKey": "={{ $json.body.sessionId }}"
|
||||
"sessionKey": "={{ $json.sessionId }}"
|
||||
},
|
||||
"id": "ebc23ffa-3bcf-494f-bcb8-51a5fff91885",
|
||||
"id": "b416df7b-4802-462f-8f74-f0a71dc4c0be",
|
||||
"name": "Window Buffer Memory",
|
||||
"type": "@n8n/n8n-nodes-langchain.memoryBufferWindow",
|
||||
"typeVersion": 1,
|
||||
"position": [
|
||||
1920,
|
||||
620
|
||||
340,
|
||||
540
|
||||
]
|
||||
},
|
||||
{
|
||||
"parameters": {
|
||||
"simplifyOutput": false
|
||||
},
|
||||
"id": "d6721a60-159b-4a93-ac6b-b81e16d9f16f",
|
||||
"name": "Memory Chat Retriever",
|
||||
"type": "@n8n/n8n-nodes-langchain.memoryChatRetriever",
|
||||
"typeVersion": 1,
|
||||
"position": [
|
||||
1780,
|
||||
-40
|
||||
]
|
||||
},
|
||||
{
|
||||
"parameters": {
|
||||
"sessionKey": "={{ $json.body.sessionId }}"
|
||||
},
|
||||
"id": "347edc3a-1dda-4996-b778-dcdc447ecfd8",
|
||||
"name": "Memory Chat Retriever Window Buffer Memory",
|
||||
"type": "@n8n/n8n-nodes-langchain.memoryBufferWindow",
|
||||
"typeVersion": 1,
|
||||
"position": [
|
||||
1800,
|
||||
160
|
||||
]
|
||||
},
|
||||
{
|
||||
"parameters": {
|
||||
"options": {
|
||||
"responseCode": 200,
|
||||
"responseHeaders": {
|
||||
"entries": [
|
||||
{
|
||||
"name": "sessionId",
|
||||
"value": "={{ $json.body.sessionId }}"
|
||||
},
|
||||
{
|
||||
"name": "Access-Control-Allow-Headers",
|
||||
"value": "*"
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
},
|
||||
"id": "d229963e-e2f1-4381-87d2-47043bd6ccc7",
|
||||
"name": "Respond to Webhook",
|
||||
"type": "n8n-nodes-base.respondToWebhook",
|
||||
"typeVersion": 1,
|
||||
"position": [
|
||||
2460,
|
||||
220
|
||||
]
|
||||
},
|
||||
{
|
||||
"parameters": {
|
||||
"dataType": "string",
|
||||
"value1": "={{ $json.body.action }}",
|
||||
"rules": {
|
||||
"rules": [
|
||||
{
|
||||
"value2": "loadPreviousSession"
|
||||
},
|
||||
{
|
||||
"value2": "sendMessage",
|
||||
"output": 1
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
"id": "fc4ad994-5f38-4dce-b1e5-397acc512687",
|
||||
"name": "Chatbot Action",
|
||||
"type": "n8n-nodes-base.switch",
|
||||
"typeVersion": 1,
|
||||
"position": [
|
||||
1320,
|
||||
200
|
||||
]
|
||||
},
|
||||
{
|
||||
"parameters": {
|
||||
"jsCode": "const response = { data: [] };\n\nfor (const item of $input.all()) {\n response.data.push(item.json);\n}\n\nreturn {\n json: response,\n pairedItem: 0\n};"
|
||||
},
|
||||
"id": "e1a80bdc-411a-42df-88dd-36915b1ae8f4",
|
||||
"name": "Code",
|
||||
"type": "n8n-nodes-base.code",
|
||||
"typeVersion": 2,
|
||||
"position": [
|
||||
2160,
|
||||
-40
|
||||
]
|
||||
},
|
||||
{
|
||||
"parameters": {
|
||||
"text": "={{ $json.body.message }}",
|
||||
"text": "={{ $json.chatInput }}",
|
||||
"options": {}
|
||||
},
|
||||
"id": "f28f5c00-c742-41d5-8ddb-f0f59ab111a3",
|
||||
"name": "Agent",
|
||||
"id": "4de25807-a2ef-4453-900e-e00e0021ecdc",
|
||||
"name": "AI Agent",
|
||||
"type": "@n8n/n8n-nodes-langchain.agent",
|
||||
"typeVersion": 1,
|
||||
"typeVersion": 1.1,
|
||||
"position": [
|
||||
1780,
|
||||
340
|
||||
620,
|
||||
300
|
||||
]
|
||||
},
|
||||
{
|
||||
"parameters": {
|
||||
"jsCode": "// Loop over input items and add a new field called 'myNewField' to the JSON of each one\nfor (const item of $input.all()) {\n item.json.body = JSON.parse(item.json.body);\n}\n\nreturn $input.all();"
|
||||
"public": true,
|
||||
"options": {
|
||||
"loadPreviousSession": "memory"
|
||||
}
|
||||
},
|
||||
"id": "415c071b-18b2-4ac5-8634-e3d939bf36ac",
|
||||
"name": "Transform request body",
|
||||
"type": "n8n-nodes-base.code",
|
||||
"typeVersion": 2,
|
||||
"id": "5a9612ae-51c1-4be2-bd8b-8556872d1149",
|
||||
"name": "Chat Trigger",
|
||||
"type": "@n8n/n8n-nodes-langchain.chatTrigger",
|
||||
"typeVersion": 1,
|
||||
"position": [
|
||||
1120,
|
||||
200
|
||||
]
|
||||
340,
|
||||
300
|
||||
],
|
||||
"webhookId": "f406671e-c954-4691-b39a-66c90aa2f103"
|
||||
}
|
||||
],
|
||||
"pinData": {},
|
||||
"connections": {
|
||||
"Webhook": {
|
||||
"main": [
|
||||
[
|
||||
{
|
||||
"node": "Transform request body",
|
||||
"type": "main",
|
||||
"index": 0
|
||||
}
|
||||
]
|
||||
]
|
||||
},
|
||||
"Memory Chat Retriever": {
|
||||
"main": [
|
||||
[
|
||||
{
|
||||
"node": "Code",
|
||||
"type": "main",
|
||||
"index": 0
|
||||
}
|
||||
]
|
||||
]
|
||||
},
|
||||
"Memory Chat Retriever Window Buffer Memory": {
|
||||
"ai_memory": [
|
||||
[
|
||||
{
|
||||
"node": "Memory Chat Retriever",
|
||||
"type": "ai_memory",
|
||||
"index": 0
|
||||
}
|
||||
]
|
||||
]
|
||||
},
|
||||
"Chatbot Action": {
|
||||
"main": [
|
||||
[
|
||||
{
|
||||
"node": "Memory Chat Retriever",
|
||||
"type": "main",
|
||||
"index": 0
|
||||
}
|
||||
],
|
||||
[
|
||||
{
|
||||
"node": "Agent",
|
||||
"type": "main",
|
||||
"index": 0
|
||||
}
|
||||
]
|
||||
]
|
||||
},
|
||||
"Code": {
|
||||
"main": [
|
||||
[
|
||||
{
|
||||
"node": "Respond to Webhook",
|
||||
"type": "main",
|
||||
"index": 0
|
||||
}
|
||||
]
|
||||
]
|
||||
},
|
||||
"Chat OpenAI": {
|
||||
"OpenAI Chat Model": {
|
||||
"ai_languageModel": [
|
||||
[
|
||||
{
|
||||
"node": "Agent",
|
||||
"node": "AI Agent",
|
||||
"type": "ai_languageModel",
|
||||
"index": 0
|
||||
}
|
||||
@@ -250,29 +82,23 @@
|
||||
"ai_memory": [
|
||||
[
|
||||
{
|
||||
"node": "Agent",
|
||||
"node": "AI Agent",
|
||||
"type": "ai_memory",
|
||||
"index": 0
|
||||
},
|
||||
{
|
||||
"node": "Chat Trigger",
|
||||
"type": "ai_memory",
|
||||
"index": 0
|
||||
}
|
||||
]
|
||||
]
|
||||
},
|
||||
"Agent": {
|
||||
"Chat Trigger": {
|
||||
"main": [
|
||||
[
|
||||
{
|
||||
"node": "Respond to Webhook",
|
||||
"type": "main",
|
||||
"index": 0
|
||||
}
|
||||
]
|
||||
]
|
||||
},
|
||||
"Transform request body": {
|
||||
"main": [
|
||||
[
|
||||
{
|
||||
"node": "Chatbot Action",
|
||||
"node": "AI Agent",
|
||||
"type": "main",
|
||||
"index": 0
|
||||
}
|
||||
@@ -284,8 +110,8 @@
|
||||
"settings": {
|
||||
"executionOrder": "v1"
|
||||
},
|
||||
"versionId": "12c145a2-74bf-48b5-a87a-ba707949eaed",
|
||||
"id": "L3FlJuFOxZcHtoFT",
|
||||
"versionId": "6076136f-fdb4-48d9-b483-d1c24c95ef9e",
|
||||
"id": "zaBHnDtj22BzEQ6K",
|
||||
"meta": {
|
||||
"instanceId": "374b43d8b8d6299cc777811a4ad220fc688ee2d54a308cfb0de4450a5233ca9e"
|
||||
},
|
||||
|
||||
@@ -1,9 +1,13 @@
|
||||
const path = require('path');
|
||||
const shelljs = require('shelljs');
|
||||
const glob = require('fast-glob');
|
||||
|
||||
const rootDirPath = path.resolve(__dirname, '..');
|
||||
const n8nRootDirPath = path.resolve(rootDirPath, '..', '..', '..');
|
||||
const distDirPath = path.resolve(rootDirPath, 'dist');
|
||||
const srcDirPath = path.resolve(rootDirPath, 'src');
|
||||
const libDirPath = path.resolve(rootDirPath, 'tmp', 'lib');
|
||||
const cjsDirPath = path.resolve(rootDirPath, 'tmp', 'cjs');
|
||||
|
||||
const packageJsonFilePath = path.resolve(rootDirPath, 'package.json');
|
||||
const readmeFilePath = path.resolve(rootDirPath, 'README.md');
|
||||
@@ -14,3 +18,19 @@ shelljs.cp(readmeFilePath, distDirPath);
|
||||
shelljs.cp(licenseFilePath, distDirPath);
|
||||
|
||||
shelljs.mv(path.resolve(distDirPath, 'src'), path.resolve(distDirPath, 'types'));
|
||||
|
||||
function moveFiles(files, from, to) {
|
||||
files.forEach((file) => {
|
||||
const toFile = file.replace(from, to);
|
||||
shelljs.mkdir('-p', path.dirname(toFile));
|
||||
shelljs.mv(file, toFile);
|
||||
});
|
||||
}
|
||||
|
||||
const cjsFiles = glob.sync(path.resolve(cjsDirPath, '**', '*'));
|
||||
moveFiles(cjsFiles, 'tmp/cjs', 'dist');
|
||||
shelljs.rm('-rf', cjsDirPath);
|
||||
|
||||
const libFiles = glob.sync(path.resolve(libDirPath, '**/*'));
|
||||
moveFiles(libFiles, 'tmp/lib', 'dist');
|
||||
shelljs.rm('-rf', libDirPath);
|
||||
|
||||
@@ -1,10 +1,10 @@
|
||||
<script lang="ts" setup>
|
||||
import { Chat, ChatWindow } from '@/components';
|
||||
import { Chat, ChatWindow } from '@n8n/chat/components';
|
||||
import { computed, onMounted } from 'vue';
|
||||
import hljs from 'highlight.js/lib/core';
|
||||
import hljsXML from 'highlight.js/lib/languages/xml';
|
||||
import hljsJavascript from 'highlight.js/lib/languages/javascript';
|
||||
import { useOptions } from '@/composables';
|
||||
import { useOptions } from '@n8n/chat/composables';
|
||||
|
||||
defineProps({});
|
||||
|
||||
|
||||
@@ -1,10 +1,10 @@
|
||||
/* eslint-disable @typescript-eslint/naming-convention */
|
||||
import type { StoryObj } from '@storybook/vue3';
|
||||
import type { ChatOptions } from '@/types';
|
||||
import { createChat } from '@/index';
|
||||
import type { ChatOptions } from '@n8n/chat/types';
|
||||
import { createChat } from '@n8n/chat/index';
|
||||
import { onMounted } from 'vue';
|
||||
|
||||
const webhookUrl = 'http://localhost:5678/webhook/513107b3-6f3a-4a1e-af21-659f0ed14183';
|
||||
const webhookUrl = 'http://localhost:5678/webhook/f406671e-c954-4691-b39a-66c90aa2f103/chat';
|
||||
|
||||
const meta = {
|
||||
title: 'Chat',
|
||||
|
||||
@@ -14,9 +14,8 @@ import {
|
||||
getChatWrapper,
|
||||
getGetStartedButton,
|
||||
getMountingTarget,
|
||||
} from '@/__tests__/utils';
|
||||
import { createChat } from '@/index';
|
||||
import { useChat } from '@/composables';
|
||||
} from '@n8n/chat/__tests__/utils';
|
||||
import { createChat } from '@n8n/chat/index';
|
||||
|
||||
describe('createChat()', () => {
|
||||
let app: ReturnType<typeof createChat>;
|
||||
@@ -77,6 +76,7 @@ describe('createChat()', () => {
|
||||
|
||||
app = createChat({
|
||||
mode: 'fullscreen',
|
||||
showWelcomeScreen: true,
|
||||
});
|
||||
|
||||
const getStartedButton = getGetStartedButton();
|
||||
@@ -85,7 +85,9 @@ describe('createChat()', () => {
|
||||
expect(fetchSpy.mock.calls[0][1]).toEqual(
|
||||
expect.objectContaining({
|
||||
method: 'POST',
|
||||
headers: {},
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
},
|
||||
body: expect.stringContaining('"action":"loadPreviousSession"') as unknown,
|
||||
mode: 'cors',
|
||||
cache: 'no-cache',
|
||||
@@ -112,9 +114,6 @@ describe('createChat()', () => {
|
||||
await fireEvent.click(trigger as HTMLElement);
|
||||
}
|
||||
|
||||
const getStartedButton = getGetStartedButton();
|
||||
await fireEvent.click(getStartedButton as HTMLElement);
|
||||
|
||||
expect(getChatMessages().length).toBe(initialMessages.length);
|
||||
expect(getChatMessageByText(initialMessages[0])).toBeInTheDocument();
|
||||
expect(getChatMessageByText(initialMessages[1])).toBeInTheDocument();
|
||||
@@ -144,12 +143,10 @@ describe('createChat()', () => {
|
||||
}
|
||||
|
||||
expect(getChatMessageTyping()).not.toBeInTheDocument();
|
||||
|
||||
const getStartedButton = getGetStartedButton();
|
||||
await fireEvent.click(getStartedButton as HTMLElement);
|
||||
|
||||
expect(getChatMessages().length).toBe(2);
|
||||
|
||||
await waitFor(() => expect(getChatInputTextarea()).toBeInTheDocument());
|
||||
|
||||
const textarea = getChatInputTextarea();
|
||||
const sendButton = getChatInputSendButton();
|
||||
await fireEvent.update(textarea as HTMLElement, input);
|
||||
@@ -159,7 +156,9 @@ describe('createChat()', () => {
|
||||
expect(fetchSpy.mock.calls[1][1]).toEqual(
|
||||
expect.objectContaining({
|
||||
method: 'POST',
|
||||
headers: {},
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
},
|
||||
body: expect.stringMatching(/"action":"sendMessage"/) as unknown,
|
||||
mode: 'cors',
|
||||
cache: 'no-cache',
|
||||
@@ -182,9 +181,6 @@ describe('createChat()', () => {
|
||||
const input = 'Teach me javascript!';
|
||||
const output = '# Code\n```js\nconsole.log("Hello World!");\n```';
|
||||
|
||||
const chatStore = useChat();
|
||||
console.log(chatStore);
|
||||
|
||||
const fetchSpy = vi.spyOn(window, 'fetch');
|
||||
fetchSpy
|
||||
.mockImplementationOnce(createFetchResponse(createGetLatestMessagesResponse))
|
||||
@@ -199,8 +195,7 @@ describe('createChat()', () => {
|
||||
await fireEvent.click(trigger as HTMLElement);
|
||||
}
|
||||
|
||||
const getStartedButton = getGetStartedButton();
|
||||
await fireEvent.click(getStartedButton as HTMLElement);
|
||||
await waitFor(() => expect(getChatInputTextarea()).toBeInTheDocument());
|
||||
|
||||
const textarea = getChatInputTextarea();
|
||||
const sendButton = getChatInputSendButton();
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { createChat } from '@/index';
|
||||
import { createChat } from '@n8n/chat/index';
|
||||
|
||||
export function createTestChat(options: Parameters<typeof createChat>[0] = {}): {
|
||||
unmount: () => void;
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import type { LoadPreviousSessionResponse, SendMessageResponse } from '@/types';
|
||||
import type { LoadPreviousSessionResponse, SendMessageResponse } from '@n8n/chat/types';
|
||||
|
||||
export function createFetchResponse<T>(data: T) {
|
||||
return async () =>
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { screen } from '@testing-library/vue';
|
||||
import { defaultMountingTarget } from '@/constants';
|
||||
import { defaultMountingTarget } from '@n8n/chat/constants';
|
||||
|
||||
export function getMountingTarget(target = defaultMountingTarget) {
|
||||
return document.querySelector(target);
|
||||
|
||||
@@ -10,6 +10,7 @@ export async function authenticatedFetch<T>(...args: Parameters<typeof fetch>):
|
||||
mode: 'cors',
|
||||
cache: 'no-cache',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
...(accessToken ? { authorization: `Bearer ${accessToken}` } : {}),
|
||||
...args[1]?.headers,
|
||||
},
|
||||
@@ -18,14 +19,12 @@ export async function authenticatedFetch<T>(...args: Parameters<typeof fetch>):
|
||||
return (await response.json()) as Promise<T>;
|
||||
}
|
||||
|
||||
export async function get<T>(
|
||||
url: string,
|
||||
query: Record<string, string> = {},
|
||||
options: RequestInit = {},
|
||||
) {
|
||||
export async function get<T>(url: string, query: object = {}, options: RequestInit = {}) {
|
||||
let resolvedUrl = url;
|
||||
if (Object.keys(query).length > 0) {
|
||||
resolvedUrl = `${resolvedUrl}?${new URLSearchParams(query).toString()}`;
|
||||
resolvedUrl = `${resolvedUrl}?${new URLSearchParams(
|
||||
query as Record<string, string>,
|
||||
).toString()}`;
|
||||
}
|
||||
|
||||
return authenticatedFetch<T>(resolvedUrl, { ...options, method: 'GET' });
|
||||
|
||||
@@ -1,5 +1,9 @@
|
||||
import { get, post } from '@/api/generic';
|
||||
import type { ChatOptions, LoadPreviousSessionResponse, SendMessageResponse } from '@/types';
|
||||
import { get, post } from '@n8n/chat/api/generic';
|
||||
import type {
|
||||
ChatOptions,
|
||||
LoadPreviousSessionResponse,
|
||||
SendMessageResponse,
|
||||
} from '@n8n/chat/types';
|
||||
|
||||
export async function loadPreviousSession(sessionId: string, options: ChatOptions) {
|
||||
const method = options.webhookConfig?.method === 'POST' ? post : get;
|
||||
@@ -7,7 +11,8 @@ export async function loadPreviousSession(sessionId: string, options: ChatOption
|
||||
`${options.webhookUrl}`,
|
||||
{
|
||||
action: 'loadPreviousSession',
|
||||
sessionId,
|
||||
[options.chatSessionKey as string]: sessionId,
|
||||
...(options.metadata ? { metadata: options.metadata } : {}),
|
||||
},
|
||||
{
|
||||
headers: options.webhookConfig?.headers,
|
||||
@@ -21,8 +26,9 @@ export async function sendMessage(message: string, sessionId: string, options: C
|
||||
`${options.webhookUrl}`,
|
||||
{
|
||||
action: 'sendMessage',
|
||||
sessionId,
|
||||
message,
|
||||
[options.chatSessionKey as string]: sessionId,
|
||||
[options.chatInputKey as string]: message,
|
||||
...(options.metadata ? { metadata: options.metadata } : {}),
|
||||
},
|
||||
{
|
||||
headers: options.webhookConfig?.headers,
|
||||
|
||||
@@ -1,24 +1,18 @@
|
||||
<script setup lang="ts">
|
||||
import Layout from '@/components/Layout.vue';
|
||||
import GetStarted from '@/components/GetStarted.vue';
|
||||
import GetStartedFooter from '@/components/GetStartedFooter.vue';
|
||||
import MessagesList from '@/components/MessagesList.vue';
|
||||
import Input from '@/components/Input.vue';
|
||||
import Layout from '@n8n/chat/components/Layout.vue';
|
||||
import GetStarted from '@n8n/chat/components/GetStarted.vue';
|
||||
import GetStartedFooter from '@n8n/chat/components/GetStartedFooter.vue';
|
||||
import MessagesList from '@n8n/chat/components/MessagesList.vue';
|
||||
import Input from '@n8n/chat/components/Input.vue';
|
||||
import { nextTick, onMounted } from 'vue';
|
||||
import { useI18n, useChat } from '@/composables';
|
||||
import { chatEventBus } from '@/event-buses';
|
||||
import { useI18n, useChat, useOptions } from '@n8n/chat/composables';
|
||||
import { chatEventBus } from '@n8n/chat/event-buses';
|
||||
|
||||
const { t } = useI18n();
|
||||
const chatStore = useChat();
|
||||
|
||||
const { messages, currentSessionId } = chatStore;
|
||||
|
||||
async function initialize() {
|
||||
await chatStore.loadPreviousSession();
|
||||
void nextTick(() => {
|
||||
chatEventBus.emit('scrollToBottom');
|
||||
});
|
||||
}
|
||||
const { options } = useOptions();
|
||||
|
||||
async function getStarted() {
|
||||
void chatStore.startNewSession();
|
||||
@@ -27,18 +21,28 @@ async function getStarted() {
|
||||
});
|
||||
}
|
||||
|
||||
onMounted(() => {
|
||||
void initialize();
|
||||
async function initialize() {
|
||||
await chatStore.loadPreviousSession();
|
||||
void nextTick(() => {
|
||||
chatEventBus.emit('scrollToBottom');
|
||||
});
|
||||
}
|
||||
|
||||
onMounted(async () => {
|
||||
await initialize();
|
||||
if (!options.showWelcomeScreen && !currentSessionId.value) {
|
||||
await getStarted();
|
||||
}
|
||||
});
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<Layout class="chat-wrapper">
|
||||
<template #header v-if="!currentSessionId">
|
||||
<template #header>
|
||||
<h1>{{ t('title') }}</h1>
|
||||
<p>{{ t('subtitle') }}</p>
|
||||
</template>
|
||||
<GetStarted v-if="!currentSessionId" @click:button="getStarted" />
|
||||
<GetStarted v-if="!currentSessionId && options.showWelcomeScreen" @click:button="getStarted" />
|
||||
<MessagesList v-else :messages="messages" />
|
||||
<template #footer>
|
||||
<Input v-if="currentSessionId" />
|
||||
|
||||
@@ -3,9 +3,9 @@
|
||||
import IconChat from 'virtual:icons/mdi/chat';
|
||||
// eslint-disable-next-line import/no-unresolved
|
||||
import IconChevronDown from 'virtual:icons/mdi/chevron-down';
|
||||
import Chat from '@/components/Chat.vue';
|
||||
import Chat from '@n8n/chat/components/Chat.vue';
|
||||
import { nextTick, ref } from 'vue';
|
||||
import { chatEventBus } from '@/event-buses';
|
||||
import { chatEventBus } from '@n8n/chat/event-buses';
|
||||
|
||||
const isOpen = ref(false);
|
||||
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
<script setup lang="ts">
|
||||
import Button from '@/components/Button.vue';
|
||||
import { useI18n } from '@/composables';
|
||||
import Button from '@n8n/chat/components/Button.vue';
|
||||
import { useI18n } from '@n8n/chat/composables';
|
||||
|
||||
const { t } = useI18n();
|
||||
</script>
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
<script setup lang="ts">
|
||||
import { useI18n } from '@/composables';
|
||||
import PoweredBy from '@/components/PoweredBy.vue';
|
||||
import { useI18n } from '@n8n/chat/composables';
|
||||
import PoweredBy from '@n8n/chat/components/PoweredBy.vue';
|
||||
|
||||
const { t, te } = useI18n();
|
||||
</script>
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
<script setup lang="ts">
|
||||
// eslint-disable-next-line import/no-unresolved
|
||||
import IconSend from 'virtual:icons/mdi/send';
|
||||
import { useI18n, useChat } from '@/composables';
|
||||
import { useI18n, useChat } from '@n8n/chat/composables';
|
||||
import { computed, ref } from 'vue';
|
||||
|
||||
const chatStore = useChat();
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
<script setup lang="ts">
|
||||
import { onBeforeUnmount, onMounted, ref } from 'vue';
|
||||
import { chatEventBus } from '@/event-buses';
|
||||
import { chatEventBus } from '@n8n/chat/event-buses';
|
||||
|
||||
const chatBodyRef = ref<HTMLElement | null>(null);
|
||||
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
<script lang="ts" setup>
|
||||
/* eslint-disable @typescript-eslint/naming-convention */
|
||||
import type { ChatMessage } from '@/types';
|
||||
import type { ChatMessage } from '@n8n/chat/types';
|
||||
import type { PropType } from 'vue';
|
||||
import { computed, toRefs } from 'vue';
|
||||
import VueMarkdown from 'vue-markdown-render';
|
||||
@@ -15,6 +15,10 @@ const props = defineProps({
|
||||
|
||||
const { message } = toRefs(props);
|
||||
|
||||
const messageText = computed(() => {
|
||||
return message.value.text || '<Empty response>';
|
||||
});
|
||||
|
||||
const classes = computed(() => {
|
||||
return {
|
||||
'chat-message-from-user': message.value.sender === 'user',
|
||||
@@ -39,7 +43,7 @@ const markdownOptions = {
|
||||
<slot>
|
||||
<vue-markdown
|
||||
class="chat-message-markdown"
|
||||
:source="message.text"
|
||||
:source="messageText"
|
||||
:options="markdownOptions"
|
||||
/>
|
||||
</slot>
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
<script lang="ts" setup>
|
||||
import type { ChatMessage } from '@/types';
|
||||
import type { ChatMessage } from '@n8n/chat/types';
|
||||
import { Message } from './index';
|
||||
import type { PropType } from 'vue';
|
||||
import { computed } from 'vue';
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
<script lang="ts" setup>
|
||||
import type { PropType } from 'vue';
|
||||
import Message from '@/components/Message.vue';
|
||||
import type { ChatMessage } from '@/types';
|
||||
import MessageTyping from '@/components/MessageTyping.vue';
|
||||
import { useChat } from '@/composables';
|
||||
import Message from '@n8n/chat/components/Message.vue';
|
||||
import type { ChatMessage } from '@n8n/chat/types';
|
||||
import MessageTyping from '@n8n/chat/components/MessageTyping.vue';
|
||||
import { useChat } from '@n8n/chat/composables';
|
||||
|
||||
defineProps({
|
||||
messages: {
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import { inject } from 'vue';
|
||||
import { ChatSymbol } from '@/constants';
|
||||
import type { Chat } from '@/types';
|
||||
import { ChatSymbol } from '@n8n/chat/constants';
|
||||
import type { Chat } from '@n8n/chat/types';
|
||||
|
||||
export function useChat() {
|
||||
return inject(ChatSymbol) as Chat;
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { useOptions } from '@/composables/useOptions';
|
||||
import { useOptions } from '@n8n/chat/composables/useOptions';
|
||||
|
||||
export function useI18n() {
|
||||
const { options } = useOptions();
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import { inject } from 'vue';
|
||||
import { ChatOptionsSymbol } from '@/constants';
|
||||
import type { ChatOptions } from '@/types';
|
||||
import { ChatOptionsSymbol } from '@n8n/chat/constants';
|
||||
import type { ChatOptions } from '@n8n/chat/types';
|
||||
|
||||
export function useOptions() {
|
||||
const options = inject(ChatOptionsSymbol) as ChatOptions;
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import type { ChatOptions } from '@/types';
|
||||
import type { ChatOptions } from '@n8n/chat/types';
|
||||
|
||||
export const defaultOptions: ChatOptions = {
|
||||
webhookUrl: 'http://localhost:5678',
|
||||
@@ -8,7 +8,11 @@ export const defaultOptions: ChatOptions = {
|
||||
},
|
||||
target: '#n8n-chat',
|
||||
mode: 'window',
|
||||
loadPreviousSession: true,
|
||||
chatInputKey: 'chatInput',
|
||||
chatSessionKey: 'sessionId',
|
||||
defaultLanguage: 'en',
|
||||
showWelcomeScreen: false,
|
||||
initialMessages: ['Hi there! 👋', 'My name is Nathan. How can I assist you today?'],
|
||||
i18n: {
|
||||
en: {
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import type { InjectionKey } from 'vue';
|
||||
import type { Chat, ChatOptions } from '@/types';
|
||||
import type { Chat, ChatOptions } from '@n8n/chat/types';
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/naming-convention
|
||||
export const ChatSymbol = 'Chat' as unknown as InjectionKey<Chat>;
|
||||
|
||||
36
packages/@n8n/chat/src/css/_tokens.scss
Normal file
36
packages/@n8n/chat/src/css/_tokens.scss
Normal file
@@ -0,0 +1,36 @@
|
||||
:root {
|
||||
--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;
|
||||
|
||||
--chat--spacing: 1rem;
|
||||
--chat--border-radius: 0.25rem;
|
||||
--chat--transition-duration: 0.15s;
|
||||
|
||||
--chat--window--width: 400px;
|
||||
--chat--window--height: 600px;
|
||||
|
||||
--chat--textarea--height: 50px;
|
||||
|
||||
--chat--message--bot--background: var(--chat--color-white);
|
||||
--chat--message--bot--color: var(--chat--color-dark);
|
||||
--chat--message--user--background: var(--chat--color-secondary);
|
||||
--chat--message--user--color: var(--chat--color-white);
|
||||
--chat--message--pre--background: rgba(0, 0, 0, 0.05);
|
||||
|
||||
--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);
|
||||
--chat--toggle--size: 64px;
|
||||
}
|
||||
1
packages/@n8n/chat/src/css/index.scss
Normal file
1
packages/@n8n/chat/src/css/index.scss
Normal file
@@ -0,0 +1 @@
|
||||
@import 'tokens';
|
||||
@@ -1,3 +1,3 @@
|
||||
import { createEventBus } from '@/utils';
|
||||
import { createEventBus } from '@n8n/chat/utils';
|
||||
|
||||
export const chatEventBus = createEventBus();
|
||||
|
||||
@@ -2,10 +2,10 @@ import './main.scss';
|
||||
|
||||
import { createApp } from 'vue';
|
||||
import App from './App.vue';
|
||||
import type { ChatOptions } from '@/types';
|
||||
import { defaultMountingTarget, defaultOptions } from '@/constants';
|
||||
import { createDefaultMountingTarget } from '@/utils';
|
||||
import { ChatPlugin } from '@/plugins';
|
||||
import type { ChatOptions } from '@n8n/chat/types';
|
||||
import { defaultMountingTarget, defaultOptions } from '@n8n/chat/constants';
|
||||
import { createDefaultMountingTarget } from '@n8n/chat/utils';
|
||||
import { ChatPlugin } from '@n8n/chat/plugins';
|
||||
|
||||
export function createChat(options?: Partial<ChatOptions>) {
|
||||
const resolvedOptions: ChatOptions = {
|
||||
|
||||
@@ -2,39 +2,4 @@
|
||||
@import 'highlight.js/styles/github';
|
||||
}
|
||||
|
||||
:root {
|
||||
--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;
|
||||
|
||||
--chat--spacing: 1rem;
|
||||
--chat--border-radius: 0.25rem;
|
||||
--chat--transition-duration: 0.15s;
|
||||
|
||||
--chat--window--width: 400px;
|
||||
--chat--window--height: 600px;
|
||||
|
||||
--chat--textarea--height: 50px;
|
||||
|
||||
--chat--message--bot--background: var(--chat--color-white);
|
||||
--chat--message--bot--color: var(--chat--color-dark);
|
||||
--chat--message--user--background: var(--chat--color-secondary);
|
||||
--chat--message--user--color: var(--chat--color-white);
|
||||
--chat--message--pre--background: rgba(0, 0, 0, 0.05);
|
||||
|
||||
--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);
|
||||
--chat--toggle--size: 64px;
|
||||
}
|
||||
@import 'css';
|
||||
|
||||
@@ -1,10 +1,10 @@
|
||||
import type { Plugin } from 'vue';
|
||||
import { computed, nextTick, ref } from 'vue';
|
||||
import type { ChatMessage, ChatOptions } from '@/types';
|
||||
import type { ChatMessage, ChatOptions } from '@n8n/chat/types';
|
||||
import { v4 as uuidv4 } from 'uuid';
|
||||
import { chatEventBus } from '@/event-buses';
|
||||
import * as api from '@/api';
|
||||
import { ChatOptionsSymbol, ChatSymbol, localStorageSessionIdKey } from '@/constants';
|
||||
import { chatEventBus } from '@n8n/chat/event-buses';
|
||||
import * as api from '@n8n/chat/api';
|
||||
import { ChatOptionsSymbol, ChatSymbol, localStorageSessionIdKey } from '@n8n/chat/constants';
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/naming-convention
|
||||
export const ChatPlugin: Plugin<ChatOptions> = {
|
||||
@@ -61,6 +61,10 @@ export const ChatPlugin: Plugin<ChatOptions> = {
|
||||
}
|
||||
|
||||
async function loadPreviousSession() {
|
||||
if (!options.loadPreviousSession) {
|
||||
return;
|
||||
}
|
||||
|
||||
const sessionId = localStorage.getItem(localStorageSessionIdKey) ?? uuidv4();
|
||||
const previousMessagesResponse = await api.loadPreviousSession(sessionId, options);
|
||||
const timestamp = new Date().toISOString();
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import type { ChatMessage } from '@/types/messages';
|
||||
import type { ChatMessage } from '@n8n/chat/types/messages';
|
||||
import type { Ref } from 'vue';
|
||||
|
||||
export interface Chat {
|
||||
@@ -6,7 +6,7 @@ export interface Chat {
|
||||
messages: Ref<ChatMessage[]>;
|
||||
currentSessionId: Ref<string | null>;
|
||||
waitingForResponse: Ref<boolean>;
|
||||
loadPreviousSession: () => Promise<string>;
|
||||
loadPreviousSession: () => Promise<string | undefined>;
|
||||
startNewSession: () => Promise<void>;
|
||||
sendMessage: (text: string) => Promise<void>;
|
||||
}
|
||||
|
||||
@@ -6,8 +6,13 @@ export interface ChatOptions {
|
||||
};
|
||||
target?: string | Element;
|
||||
mode?: 'window' | 'fullscreen';
|
||||
showWelcomeScreen?: boolean;
|
||||
loadPreviousSession?: boolean;
|
||||
chatInputKey?: string;
|
||||
chatSessionKey?: string;
|
||||
defaultLanguage?: 'en';
|
||||
initialMessages?: string[];
|
||||
metadata?: Record<string, unknown>;
|
||||
i18n: Record<
|
||||
string,
|
||||
{
|
||||
|
||||
@@ -14,7 +14,7 @@
|
||||
"types": ["vitest/globals", "unplugin-icons/types/vue"],
|
||||
"paths": {
|
||||
"@/*": ["src/*"],
|
||||
"n8n-design-system/*": ["../design-system/src/*"]
|
||||
"@n8n/chat/*": ["src/*"]
|
||||
},
|
||||
"lib": ["esnext", "dom", "dom.iterable", "scripthost"],
|
||||
// TODO: remove all options below this line
|
||||
|
||||
@@ -7,6 +7,7 @@ import icons from 'unplugin-icons/vite';
|
||||
import dts from 'vite-plugin-dts';
|
||||
|
||||
const includeVue = process.env.INCLUDE_VUE === 'true';
|
||||
const srcPath = fileURLToPath(new URL('./src', import.meta.url));
|
||||
|
||||
// https://vitejs.dev/config/
|
||||
export default defineConfig({
|
||||
@@ -19,7 +20,8 @@ export default defineConfig({
|
||||
],
|
||||
resolve: {
|
||||
alias: {
|
||||
'@': fileURLToPath(new URL('./src', import.meta.url)),
|
||||
'@': srcPath,
|
||||
'@n8n/chat': srcPath,
|
||||
},
|
||||
},
|
||||
define: {
|
||||
|
||||
Reference in New Issue
Block a user