@n8n/chat 0.9.1 → 0.11.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (207) hide show
  1. package/.eslintignore +2 -0
  2. package/.eslintrc.cjs +10 -0
  3. package/.np-config.json +5 -0
  4. package/.storybook/main.ts +4 -0
  5. package/.storybook/preview.scss +4 -0
  6. package/.storybook/preview.ts +15 -0
  7. package/.vscode/extensions.json +3 -0
  8. package/build.config.js +21 -0
  9. package/env.d.ts +1 -0
  10. package/index.html +13 -0
  11. package/package.json +2 -35
  12. package/resources/images/fullscreen.png +0 -0
  13. package/resources/images/windowed.png +0 -0
  14. package/resources/workflow-manual.json +238 -0
  15. package/resources/workflow.json +119 -0
  16. package/scripts/pack.js +11 -0
  17. package/scripts/postbuild.js +36 -0
  18. package/src/__stories__/App.stories.ts +43 -0
  19. package/src/__tests__/index.spec.ts +218 -0
  20. package/src/__tests__/utils/create.ts +16 -0
  21. package/src/__tests__/utils/fetch.ts +18 -0
  22. package/src/__tests__/utils/selectors.ts +53 -0
  23. package/src/api/generic.ts +63 -0
  24. package/src/api/message.ts +37 -0
  25. package/src/components/Button.vue +41 -0
  26. package/src/components/ChatWindow.vue +125 -0
  27. package/{components → src/components}/GetStarted.vue +7 -7
  28. package/{components → src/components}/GetStartedFooter.vue +2 -2
  29. package/{components → src/components}/Input.vue +39 -34
  30. package/{components → src/components}/Layout.vue +42 -26
  31. package/{components → src/components}/Message.vue +46 -39
  32. package/src/components/MessageTyping.vue +109 -0
  33. package/{components → src/components}/MessagesList.vue +4 -4
  34. package/{components → src/components}/PoweredBy.vue +7 -6
  35. package/src/composables/useChat.ts +7 -0
  36. package/src/composables/useI18n.ts +16 -0
  37. package/src/composables/useOptions.ts +11 -0
  38. package/src/constants/defaults.ts +29 -0
  39. package/{constants/localStorage.mjs → src/constants/localStorage.ts} +1 -1
  40. package/src/constants/symbols.ts +8 -0
  41. package/src/css/_tokens.scss +36 -0
  42. package/src/css/index.scss +1 -0
  43. package/src/event-buses/chatEventBus.ts +3 -0
  44. package/src/index.ts +42 -0
  45. package/src/main.scss +5 -0
  46. package/src/plugins/chat.ts +115 -0
  47. package/src/types/chat.ts +12 -0
  48. package/src/types/messages.ts +6 -0
  49. package/src/types/options.ts +28 -0
  50. package/src/types/webhook.ts +18 -0
  51. package/src/utils/event-bus.ts +51 -0
  52. package/src/utils/mount.ts +16 -0
  53. package/tsconfig.json +27 -0
  54. package/vite.config.ts +60 -0
  55. package/vitest.config.ts +20 -0
  56. package/__stories__/App.stories.d.ts +0 -16
  57. package/__stories__/App.stories.js +0 -38
  58. package/__stories__/App.stories.mjs +0 -32
  59. package/__tests__/index.spec.d.ts +0 -1
  60. package/__tests__/index.spec.js +0 -146
  61. package/__tests__/index.spec.mjs +0 -172
  62. package/__tests__/setup.js +0 -3
  63. package/__tests__/setup.mjs +0 -1
  64. package/__tests__/utils/create.d.ts +0 -5
  65. package/__tests__/utils/create.js +0 -16
  66. package/__tests__/utils/create.mjs +0 -10
  67. package/__tests__/utils/fetch.d.ts +0 -3
  68. package/__tests__/utils/fetch.js +0 -20
  69. package/__tests__/utils/fetch.mjs +0 -9
  70. package/__tests__/utils/index.js +0 -38
  71. package/__tests__/utils/index.mjs +0 -3
  72. package/__tests__/utils/selectors.d.ts +0 -12
  73. package/__tests__/utils/selectors.js +0 -58
  74. package/__tests__/utils/selectors.mjs +0 -41
  75. package/api/generic.d.ts +0 -6
  76. package/api/generic.js +0 -68
  77. package/api/generic.mjs +0 -54
  78. package/api/index.js +0 -27
  79. package/api/index.mjs +0 -2
  80. package/api/message.d.ts +0 -3
  81. package/api/message.js +0 -33
  82. package/api/message.mjs +0 -30
  83. package/chat.bundle.es.js +0 -10761
  84. package/chat.bundle.umd.js +0 -18
  85. package/chat.es.js +0 -6872
  86. package/chat.umd.js +0 -18
  87. package/components/Button.vue +0 -34
  88. package/components/ChatWindow.vue +0 -104
  89. package/components/MessageTyping.vue +0 -101
  90. package/components/index.js +0 -76
  91. package/components/index.mjs +0 -10
  92. package/composables/index.js +0 -38
  93. package/composables/index.mjs +0 -3
  94. package/composables/useChat.d.ts +0 -1
  95. package/composables/useChat.js +0 -11
  96. package/composables/useChat.mjs +0 -5
  97. package/composables/useI18n.d.ts +0 -4
  98. package/composables/useI18n.js +0 -23
  99. package/composables/useI18n.mjs +0 -12
  100. package/composables/useOptions.d.ts +0 -3
  101. package/composables/useOptions.js +0 -14
  102. package/composables/useOptions.mjs +0 -8
  103. package/constants/defaults.d.ts +0 -3
  104. package/constants/defaults.js +0 -32
  105. package/constants/defaults.mjs +0 -26
  106. package/constants/index.js +0 -38
  107. package/constants/index.mjs +0 -3
  108. package/constants/localStorage.d.ts +0 -2
  109. package/constants/localStorage.js +0 -8
  110. package/constants/symbols.d.ts +0 -3
  111. package/constants/symbols.js +0 -8
  112. package/constants/symbols.mjs +0 -2
  113. package/css/index.css +0 -31
  114. package/event-buses/chatEventBus.d.ts +0 -1
  115. package/event-buses/chatEventBus.js +0 -8
  116. package/event-buses/chatEventBus.mjs +0 -2
  117. package/event-buses/index.js +0 -16
  118. package/event-buses/index.mjs +0 -1
  119. package/index.d.ts +0 -3
  120. package/index.js +0 -43
  121. package/index.mjs +0 -36
  122. package/main.css +0 -151
  123. package/plugins/chat.d.ts +0 -3
  124. package/plugins/chat.js +0 -91
  125. package/plugins/chat.mjs +0 -90
  126. package/plugins/index.js +0 -16
  127. package/plugins/index.mjs +0 -1
  128. package/style.css +0 -1
  129. package/types/App.vue.d.ts +0 -8
  130. package/types/__stories__/App.stories.d.ts +0 -17
  131. package/types/__tests__/index.spec.d.ts +0 -1
  132. package/types/__tests__/setup.d.ts +0 -0
  133. package/types/__tests__/utils/create.d.ts +0 -5
  134. package/types/__tests__/utils/fetch.d.ts +0 -4
  135. package/types/__tests__/utils/index.d.ts +0 -3
  136. package/types/__tests__/utils/selectors.d.ts +0 -12
  137. package/types/api/generic.d.ts +0 -6
  138. package/types/api/index.d.ts +0 -2
  139. package/types/api/message.d.ts +0 -3
  140. package/types/chat.d.ts +0 -11
  141. package/types/chat.js +0 -1
  142. package/types/chat.mjs +0 -0
  143. package/types/components/Button.vue.d.ts +0 -9
  144. package/types/components/Chat.vue.d.ts +0 -2
  145. package/types/components/ChatWindow.vue.d.ts +0 -2
  146. package/types/components/GetStarted.vue.d.ts +0 -2
  147. package/types/components/GetStartedFooter.vue.d.ts +0 -2
  148. package/types/components/Input.vue.d.ts +0 -2
  149. package/types/components/Layout.vue.d.ts +0 -11
  150. package/types/components/Message.vue.d.ts +0 -21
  151. package/types/components/MessageTyping.vue.d.ts +0 -15
  152. package/types/components/MessagesList.vue.d.ts +0 -14
  153. package/types/components/PoweredBy.vue.d.ts +0 -2
  154. package/types/components/index.d.ts +0 -10
  155. package/types/composables/index.d.ts +0 -3
  156. package/types/composables/useChat.d.ts +0 -2
  157. package/types/composables/useI18n.d.ts +0 -4
  158. package/types/composables/useOptions.d.ts +0 -4
  159. package/types/constants/defaults.d.ts +0 -3
  160. package/types/constants/index.d.ts +0 -3
  161. package/types/constants/localStorage.d.ts +0 -2
  162. package/types/constants/symbols.d.ts +0 -4
  163. package/types/event-buses/chatEventBus.d.ts +0 -1
  164. package/types/event-buses/index.d.ts +0 -1
  165. package/types/index.js +0 -49
  166. package/types/index.mjs +0 -4
  167. package/types/messages.d.ts +0 -6
  168. package/types/messages.js +0 -1
  169. package/types/messages.mjs +0 -0
  170. package/types/options.d.ts +0 -25
  171. package/types/options.js +0 -1
  172. package/types/options.mjs +0 -0
  173. package/types/plugins/chat.d.ts +0 -3
  174. package/types/plugins/index.d.ts +0 -1
  175. package/types/types/chat.d.ts +0 -11
  176. package/types/types/index.d.ts +0 -4
  177. package/types/types/messages.d.ts +0 -6
  178. package/types/types/options.d.ts +0 -25
  179. package/types/types/webhook.d.ts +0 -16
  180. package/types/utils/event-bus.d.ts +0 -8
  181. package/types/utils/mount.d.ts +0 -1
  182. package/types/webhook.d.ts +0 -16
  183. package/types/webhook.js +0 -1
  184. package/types/webhook.mjs +0 -0
  185. package/utils/event-bus.d.ts +0 -8
  186. package/utils/event-bus.js +0 -38
  187. package/utils/event-bus.mjs +0 -32
  188. package/utils/index.d.ts +0 -2
  189. package/utils/index.js +0 -27
  190. package/utils/index.mjs +0 -2
  191. package/utils/mount.d.ts +0 -1
  192. package/utils/mount.js +0 -19
  193. package/utils/mount.mjs +0 -13
  194. /package/{favicon.ico → public/favicon.ico} +0 -0
  195. /package/{App.vue → src/App.vue} +0 -0
  196. /package/{__tests__/setup.d.ts → src/__tests__/setup.ts} +0 -0
  197. /package/{__tests__/utils/index.d.ts → src/__tests__/utils/index.ts} +0 -0
  198. /package/{api/index.d.ts → src/api/index.ts} +0 -0
  199. /package/{components → src/components}/Chat.vue +0 -0
  200. /package/{components/index.d.ts → src/components/index.ts} +0 -0
  201. /package/{composables/index.d.ts → src/composables/index.ts} +0 -0
  202. /package/{constants/index.d.ts → src/constants/index.ts} +0 -0
  203. /package/{event-buses/index.d.ts → src/event-buses/index.ts} +0 -0
  204. /package/{plugins/index.d.ts → src/plugins/index.ts} +0 -0
  205. /package/{shims.d.ts → src/shims.d.ts} +0 -0
  206. /package/{types/index.d.ts → src/types/index.ts} +0 -0
  207. /package/{types/utils/index.d.ts → src/utils/index.ts} +0 -0
@@ -0,0 +1,218 @@
1
+ import { fireEvent, waitFor } from '@testing-library/vue';
2
+ import {
3
+ createFetchResponse,
4
+ createGetLatestMessagesResponse,
5
+ createSendMessageResponse,
6
+ getChatInputSendButton,
7
+ getChatInputTextarea,
8
+ getChatMessage,
9
+ getChatMessageByText,
10
+ getChatMessages,
11
+ getChatMessageTyping,
12
+ getChatWindowToggle,
13
+ getChatWindowWrapper,
14
+ getChatWrapper,
15
+ getGetStartedButton,
16
+ getMountingTarget,
17
+ } from '@n8n/chat/__tests__/utils';
18
+ import { createChat } from '@n8n/chat/index';
19
+
20
+ describe('createChat()', () => {
21
+ let app: ReturnType<typeof createChat>;
22
+
23
+ afterEach(() => {
24
+ vi.clearAllMocks();
25
+
26
+ app.unmount();
27
+ });
28
+
29
+ describe('mode', () => {
30
+ it('should create fullscreen chat app with default options', () => {
31
+ const fetchSpy = vi.spyOn(window, 'fetch');
32
+ fetchSpy.mockImplementationOnce(createFetchResponse(createGetLatestMessagesResponse()));
33
+
34
+ app = createChat({
35
+ mode: 'fullscreen',
36
+ });
37
+
38
+ expect(getMountingTarget()).toBeVisible();
39
+ expect(getChatWrapper()).toBeVisible();
40
+ expect(getChatWindowWrapper()).not.toBeInTheDocument();
41
+ });
42
+
43
+ it('should create window chat app with default options', () => {
44
+ const fetchSpy = vi.spyOn(window, 'fetch');
45
+ fetchSpy.mockImplementationOnce(createFetchResponse(createGetLatestMessagesResponse()));
46
+
47
+ app = createChat({
48
+ mode: 'window',
49
+ });
50
+
51
+ expect(getMountingTarget()).toBeDefined();
52
+ expect(getChatWindowWrapper()).toBeVisible();
53
+ expect(getChatWrapper()).not.toBeVisible();
54
+ });
55
+
56
+ it('should open window chat app using toggle button', async () => {
57
+ const fetchSpy = vi.spyOn(window, 'fetch');
58
+ fetchSpy.mockImplementationOnce(createFetchResponse(createGetLatestMessagesResponse()));
59
+
60
+ app = createChat();
61
+
62
+ expect(getMountingTarget()).toBeVisible();
63
+ expect(getChatWindowWrapper()).toBeVisible();
64
+
65
+ const trigger = getChatWindowToggle();
66
+ await fireEvent.click(trigger as HTMLElement);
67
+
68
+ expect(getChatWrapper()).toBeVisible();
69
+ });
70
+ });
71
+
72
+ describe('loadPreviousMessages', () => {
73
+ it('should load previous messages on mount', async () => {
74
+ const fetchSpy = vi.spyOn(global, 'fetch');
75
+ fetchSpy.mockImplementation(createFetchResponse(createGetLatestMessagesResponse()));
76
+
77
+ app = createChat({
78
+ mode: 'fullscreen',
79
+ showWelcomeScreen: true,
80
+ });
81
+
82
+ const getStartedButton = getGetStartedButton();
83
+ await fireEvent.click(getStartedButton as HTMLElement);
84
+
85
+ expect(fetchSpy.mock.calls[0][1]).toEqual(
86
+ expect.objectContaining({
87
+ method: 'POST',
88
+ headers: {
89
+ 'Content-Type': 'application/json',
90
+ },
91
+ body: expect.stringContaining('"action":"loadPreviousSession"') as unknown,
92
+ mode: 'cors',
93
+ cache: 'no-cache',
94
+ }),
95
+ );
96
+ });
97
+ });
98
+
99
+ describe('initialMessages', () => {
100
+ it.each(['fullscreen', 'window'] as Array<'fullscreen' | 'window'>)(
101
+ 'should show initial default messages in %s mode',
102
+ async (mode) => {
103
+ const fetchSpy = vi.spyOn(window, 'fetch');
104
+ fetchSpy.mockImplementationOnce(createFetchResponse(createGetLatestMessagesResponse()));
105
+
106
+ const initialMessages = ['Hello tester!', 'How are you?'];
107
+ app = createChat({
108
+ mode,
109
+ initialMessages,
110
+ });
111
+
112
+ if (mode === 'window') {
113
+ const trigger = getChatWindowToggle();
114
+ await fireEvent.click(trigger as HTMLElement);
115
+ }
116
+
117
+ expect(getChatMessages().length).toBe(initialMessages.length);
118
+ expect(getChatMessageByText(initialMessages[0])).toBeInTheDocument();
119
+ expect(getChatMessageByText(initialMessages[1])).toBeInTheDocument();
120
+ },
121
+ );
122
+ });
123
+
124
+ describe('sendMessage', () => {
125
+ it.each(['window', 'fullscreen'] as Array<'fullscreen' | 'window'>)(
126
+ 'should send a message and render a text message in %s mode',
127
+ async (mode) => {
128
+ const input = 'Hello User World!';
129
+ const output = 'Hello Bot World!';
130
+
131
+ const fetchSpy = vi.spyOn(window, 'fetch');
132
+ fetchSpy
133
+ .mockImplementationOnce(createFetchResponse(createGetLatestMessagesResponse))
134
+ .mockImplementationOnce(createFetchResponse(createSendMessageResponse(output)));
135
+
136
+ app = createChat({
137
+ mode,
138
+ });
139
+
140
+ if (mode === 'window') {
141
+ const trigger = getChatWindowToggle();
142
+ await fireEvent.click(trigger as HTMLElement);
143
+ }
144
+
145
+ expect(getChatMessageTyping()).not.toBeInTheDocument();
146
+ expect(getChatMessages().length).toBe(2);
147
+
148
+ await waitFor(() => expect(getChatInputTextarea()).toBeInTheDocument());
149
+
150
+ const textarea = getChatInputTextarea();
151
+ const sendButton = getChatInputSendButton();
152
+ await fireEvent.update(textarea as HTMLElement, input);
153
+ expect(sendButton).not.toBeDisabled();
154
+ await fireEvent.click(sendButton as HTMLElement);
155
+
156
+ expect(fetchSpy.mock.calls[1][1]).toEqual(
157
+ expect.objectContaining({
158
+ method: 'POST',
159
+ headers: {
160
+ 'Content-Type': 'application/json',
161
+ },
162
+ body: expect.stringMatching(/"action":"sendMessage"/) as unknown,
163
+ mode: 'cors',
164
+ cache: 'no-cache',
165
+ }),
166
+ );
167
+ expect(fetchSpy.mock.calls[1][1]?.body).toContain(`"${input}"`);
168
+
169
+ expect(getChatMessages().length).toBe(3);
170
+ expect(getChatMessageByText(input)).toBeInTheDocument();
171
+ expect(getChatMessageTyping()).toBeVisible();
172
+
173
+ await waitFor(() => expect(getChatMessageTyping()).not.toBeInTheDocument());
174
+ expect(getChatMessageByText(output)).toBeInTheDocument();
175
+ },
176
+ );
177
+
178
+ it.each(['fullscreen', 'window'] as Array<'fullscreen' | 'window'>)(
179
+ 'should send a message and render a code markdown message in %s mode',
180
+ async (mode) => {
181
+ const input = 'Teach me javascript!';
182
+ const output = '# Code\n```js\nconsole.log("Hello World!");\n```';
183
+
184
+ const fetchSpy = vi.spyOn(window, 'fetch');
185
+ fetchSpy
186
+ .mockImplementationOnce(createFetchResponse(createGetLatestMessagesResponse))
187
+ .mockImplementationOnce(createFetchResponse(createSendMessageResponse(output)));
188
+
189
+ app = createChat({
190
+ mode,
191
+ });
192
+
193
+ if (mode === 'window') {
194
+ const trigger = getChatWindowToggle();
195
+ await fireEvent.click(trigger as HTMLElement);
196
+ }
197
+
198
+ await waitFor(() => expect(getChatInputTextarea()).toBeInTheDocument());
199
+
200
+ const textarea = getChatInputTextarea();
201
+ const sendButton = getChatInputSendButton();
202
+ await fireEvent.update(textarea as HTMLElement, input);
203
+ await fireEvent.click(sendButton as HTMLElement);
204
+
205
+ expect(getChatMessageByText(input)).toBeInTheDocument();
206
+ expect(getChatMessages().length).toBe(3);
207
+
208
+ await waitFor(() => expect(getChatMessageTyping()).not.toBeInTheDocument());
209
+
210
+ const lastMessage = getChatMessage(-1);
211
+ expect(lastMessage).toBeInTheDocument();
212
+
213
+ expect(lastMessage.querySelector('h1')).toHaveTextContent('Code');
214
+ expect(lastMessage.querySelector('code')).toHaveTextContent('console.log("Hello World!");');
215
+ },
216
+ );
217
+ });
218
+ });
@@ -0,0 +1,16 @@
1
+ import { createChat } from '@n8n/chat/index';
2
+
3
+ export function createTestChat(options: Parameters<typeof createChat>[0] = {}): {
4
+ unmount: () => void;
5
+ container: Element;
6
+ } {
7
+ const app = createChat(options);
8
+
9
+ const container = app._container as Element;
10
+ const unmount = () => app.unmount();
11
+
12
+ return {
13
+ unmount,
14
+ container,
15
+ };
16
+ }
@@ -0,0 +1,18 @@
1
+ import type { LoadPreviousSessionResponse, SendMessageResponse } from '@n8n/chat/types';
2
+
3
+ export function createFetchResponse<T>(data: T) {
4
+ return async () =>
5
+ ({
6
+ json: async () => await new Promise<T>((resolve) => resolve(data)),
7
+ }) as Response;
8
+ }
9
+
10
+ export const createGetLatestMessagesResponse = (
11
+ data: LoadPreviousSessionResponse['data'] = [],
12
+ ): LoadPreviousSessionResponse => ({ data });
13
+
14
+ export const createSendMessageResponse = (
15
+ output: SendMessageResponse['output'],
16
+ ): SendMessageResponse => ({
17
+ output,
18
+ });
@@ -0,0 +1,53 @@
1
+ import { screen } from '@testing-library/vue';
2
+ import { defaultMountingTarget } from '@n8n/chat/constants';
3
+
4
+ export function getMountingTarget(target = defaultMountingTarget) {
5
+ return document.querySelector(target);
6
+ }
7
+
8
+ export function getChatWindowWrapper() {
9
+ return document.querySelector('.chat-window-wrapper');
10
+ }
11
+
12
+ export function getChatWindowToggle() {
13
+ return document.querySelector('.chat-window-toggle');
14
+ }
15
+
16
+ export function getChatWrapper() {
17
+ return document.querySelector('.chat-wrapper');
18
+ }
19
+
20
+ export function getChatMessages() {
21
+ return document.querySelectorAll('.chat-message:not(.chat-message-typing)');
22
+ }
23
+
24
+ export function getChatMessage(index: number) {
25
+ const messages = getChatMessages();
26
+ return index < 0 ? messages[messages.length + index] : messages[index];
27
+ }
28
+
29
+ export function getChatMessageByText(text: string) {
30
+ return screen.queryByText(text, {
31
+ selector: '.chat-message:not(.chat-message-typing) .chat-message-markdown p',
32
+ });
33
+ }
34
+
35
+ export function getChatMessageTyping() {
36
+ return document.querySelector('.chat-message-typing');
37
+ }
38
+
39
+ export function getGetStartedButton() {
40
+ return document.querySelector('.chat-get-started .chat-button');
41
+ }
42
+
43
+ export function getChatInput() {
44
+ return document.querySelector('.chat-input');
45
+ }
46
+
47
+ export function getChatInputTextarea() {
48
+ return document.querySelector('.chat-input textarea');
49
+ }
50
+
51
+ export function getChatInputSendButton() {
52
+ return document.querySelector('.chat-input .chat-input-send-button');
53
+ }
@@ -0,0 +1,63 @@
1
+ async function getAccessToken() {
2
+ return '';
3
+ }
4
+
5
+ export async function authenticatedFetch<T>(...args: Parameters<typeof fetch>): Promise<T> {
6
+ const accessToken = await getAccessToken();
7
+
8
+ const response = await fetch(args[0], {
9
+ ...args[1],
10
+ mode: 'cors',
11
+ cache: 'no-cache',
12
+ headers: {
13
+ 'Content-Type': 'application/json',
14
+ ...(accessToken ? { authorization: `Bearer ${accessToken}` } : {}),
15
+ ...args[1]?.headers,
16
+ },
17
+ });
18
+
19
+ return (await response.json()) as T;
20
+ }
21
+
22
+ export async function get<T>(url: string, query: object = {}, options: RequestInit = {}) {
23
+ let resolvedUrl = url;
24
+ if (Object.keys(query).length > 0) {
25
+ resolvedUrl = `${resolvedUrl}?${new URLSearchParams(
26
+ query as Record<string, string>,
27
+ ).toString()}`;
28
+ }
29
+
30
+ return await authenticatedFetch<T>(resolvedUrl, { ...options, method: 'GET' });
31
+ }
32
+
33
+ export async function post<T>(url: string, body: object = {}, options: RequestInit = {}) {
34
+ return await authenticatedFetch<T>(url, {
35
+ ...options,
36
+ method: 'POST',
37
+ body: JSON.stringify(body),
38
+ });
39
+ }
40
+
41
+ export async function put<T>(url: string, body: object = {}, options: RequestInit = {}) {
42
+ return await authenticatedFetch<T>(url, {
43
+ ...options,
44
+ method: 'PUT',
45
+ body: JSON.stringify(body),
46
+ });
47
+ }
48
+
49
+ export async function patch<T>(url: string, body: object = {}, options: RequestInit = {}) {
50
+ return await authenticatedFetch<T>(url, {
51
+ ...options,
52
+ method: 'PATCH',
53
+ body: JSON.stringify(body),
54
+ });
55
+ }
56
+
57
+ export async function del<T>(url: string, body: object = {}, options: RequestInit = {}) {
58
+ return await authenticatedFetch<T>(url, {
59
+ ...options,
60
+ method: 'DELETE',
61
+ body: JSON.stringify(body),
62
+ });
63
+ }
@@ -0,0 +1,37 @@
1
+ import { get, post } from '@n8n/chat/api/generic';
2
+ import type {
3
+ ChatOptions,
4
+ LoadPreviousSessionResponse,
5
+ SendMessageResponse,
6
+ } from '@n8n/chat/types';
7
+
8
+ export async function loadPreviousSession(sessionId: string, options: ChatOptions) {
9
+ const method = options.webhookConfig?.method === 'POST' ? post : get;
10
+ return await method<LoadPreviousSessionResponse>(
11
+ `${options.webhookUrl}`,
12
+ {
13
+ action: 'loadPreviousSession',
14
+ [options.chatSessionKey as string]: sessionId,
15
+ ...(options.metadata ? { metadata: options.metadata } : {}),
16
+ },
17
+ {
18
+ headers: options.webhookConfig?.headers,
19
+ },
20
+ );
21
+ }
22
+
23
+ export async function sendMessage(message: string, sessionId: string, options: ChatOptions) {
24
+ const method = options.webhookConfig?.method === 'POST' ? post : get;
25
+ return await method<SendMessageResponse>(
26
+ `${options.webhookUrl}`,
27
+ {
28
+ action: 'sendMessage',
29
+ [options.chatSessionKey as string]: sessionId,
30
+ [options.chatInputKey as string]: message,
31
+ ...(options.metadata ? { metadata: options.metadata } : {}),
32
+ },
33
+ {
34
+ headers: options.webhookConfig?.headers,
35
+ },
36
+ );
37
+ }
@@ -0,0 +1,41 @@
1
+ <template>
2
+ <button class="chat-button">
3
+ <slot />
4
+ </button>
5
+ </template>
6
+ <style lang="scss">
7
+ .chat-button {
8
+ display: inline-flex;
9
+ text-align: center;
10
+ vertical-align: middle;
11
+ user-select: none;
12
+ color: var(--chat--button--color, var(--chat--color-light));
13
+ background-color: var(--chat--button--background, var(--chat--color-primary));
14
+ border: 1px solid transparent;
15
+ padding: var(--chat--button--padding, calc(var(--chat--spacing) * 1 / 2) var(--chat--spacing));
16
+ font-size: 1rem;
17
+ line-height: 1.5;
18
+ border-radius: var(--chat--button--border-radius, var(--chat--border-radius));
19
+ transition:
20
+ color var(--chat--transition-duration) ease-in-out,
21
+ background-color var(--chat--transition-duration) ease-in-out,
22
+ border-color var(--chat--transition-duration) ease-in-out,
23
+ box-shadow var(--chat--transition-duration) ease-in-out;
24
+ cursor: pointer;
25
+
26
+ &:hover {
27
+ color: var(--chat--button--hover--color, var(--chat--color-light));
28
+ background-color: var(--chat--button--hover--background, var(--chat--color-primary-shade-50));
29
+ text-decoration: none;
30
+ }
31
+
32
+ &:focus {
33
+ outline: 0;
34
+ box-shadow: 0 0 0 0.2rem rgba(0, 123, 255, 0.25);
35
+ }
36
+
37
+ &:disabled {
38
+ opacity: 0.65;
39
+ }
40
+ }
41
+ </style>
@@ -0,0 +1,125 @@
1
+ <script lang="ts" setup>
2
+ // eslint-disable-next-line import/no-unresolved
3
+ import IconChat from 'virtual:icons/mdi/chat';
4
+ // eslint-disable-next-line import/no-unresolved
5
+ import IconChevronDown from 'virtual:icons/mdi/chevron-down';
6
+ import { nextTick, ref } from 'vue';
7
+ import Chat from '@n8n/chat/components/Chat.vue';
8
+ import { chatEventBus } from '@n8n/chat/event-buses';
9
+
10
+ const isOpen = ref(false);
11
+
12
+ function toggle() {
13
+ isOpen.value = !isOpen.value;
14
+
15
+ if (isOpen.value) {
16
+ void nextTick(() => {
17
+ chatEventBus.emit('scrollToBottom');
18
+ });
19
+ }
20
+ }
21
+ </script>
22
+
23
+ <template>
24
+ <div class="chat-window-wrapper">
25
+ <Transition name="chat-window-transition">
26
+ <div v-show="isOpen" class="chat-window">
27
+ <Chat />
28
+ </div>
29
+ </Transition>
30
+ <div class="chat-window-toggle" @click="toggle">
31
+ <Transition name="chat-window-toggle-transition" mode="out-in">
32
+ <IconChat v-if="!isOpen" height="32" width="32" />
33
+ <IconChevronDown v-else height="32" width="32" />
34
+ </Transition>
35
+ </div>
36
+ </div>
37
+ </template>
38
+
39
+ <style lang="scss">
40
+ .chat-window-wrapper {
41
+ position: fixed;
42
+ display: flex;
43
+ flex-direction: column;
44
+ bottom: var(--chat--window--bottom, var(--chat--spacing));
45
+ right: var(--chat--window--right, var(--chat--spacing));
46
+ z-index: var(--chat--window--z-index, 9999);
47
+
48
+ max-width: calc(100% - var(--chat--window--right, var(--chat--spacing)) * 2);
49
+ max-height: calc(100% - var(--chat--window--bottom, var(--chat--spacing)) * 2);
50
+
51
+ .chat-window {
52
+ display: flex;
53
+ width: var(--chat--window--width);
54
+ height: var(--chat--window--height);
55
+ max-width: 100%;
56
+ max-height: 100%;
57
+ border: var(--chat--window--border, 1px solid var(--chat--color-light-shade-100));
58
+ border-radius: var(--chat--window--border-radius, var(--chat--border-radius));
59
+ margin-bottom: var(--chat--window--margin-bottom, var(--chat--spacing));
60
+ overflow: hidden;
61
+ transform-origin: bottom right;
62
+
63
+ .chat-layout {
64
+ width: auto;
65
+ height: auto;
66
+ flex: 1;
67
+ }
68
+ }
69
+
70
+ .chat-window-toggle {
71
+ flex: 0 0 auto;
72
+ background: var(--chat--toggle--background);
73
+ color: var(--chat--toggle--color);
74
+ cursor: pointer;
75
+ width: var(--chat--toggle--width, var(--chat--toggle--size));
76
+ height: var(--chat--toggle--height, var(--chat--toggle--size));
77
+ border-radius: var(--chat--toggle--border-radius, 50%);
78
+ display: inline-flex;
79
+ align-items: center;
80
+ justify-content: center;
81
+ margin-left: auto;
82
+ transition:
83
+ transform var(--chat--transition-duration) ease,
84
+ background var(--chat--transition-duration) ease;
85
+
86
+ &:hover,
87
+ &:focus {
88
+ transform: scale(1.05);
89
+ background: var(--chat--toggle--hover--background);
90
+ }
91
+
92
+ &:active {
93
+ transform: scale(0.95);
94
+ background: var(--chat--toggle--active--background);
95
+ }
96
+ }
97
+ }
98
+
99
+ .chat-window-transition {
100
+ &-enter-active,
101
+ &-leave-active {
102
+ transition:
103
+ transform var(--chat--transition-duration) ease,
104
+ opacity var(--chat--transition-duration) ease;
105
+ }
106
+
107
+ &-enter-from,
108
+ &-leave-to {
109
+ transform: scale(0);
110
+ opacity: 0;
111
+ }
112
+ }
113
+
114
+ .chat-window-toggle-transition {
115
+ &-enter-active,
116
+ &-leave-active {
117
+ transition: opacity var(--chat--transition-duration) ease;
118
+ }
119
+
120
+ &-enter-from,
121
+ &-leave-to {
122
+ opacity: 0;
123
+ }
124
+ }
125
+ </style>
@@ -12,13 +12,13 @@ const { t } = useI18n();
12
12
  </div>
13
13
  </template>
14
14
 
15
- <style>
15
+ <style lang="scss">
16
16
  .chat-get-started {
17
- padding-top: var(--chat--spacing);
18
- padding-bottom: var(--chat--spacing);
19
- display: flex;
20
- justify-content: center;
21
- align-items: center;
22
- height: 100%;
17
+ padding-top: var(--chat--spacing);
18
+ padding-bottom: var(--chat--spacing);
19
+ display: flex;
20
+ justify-content: center;
21
+ align-items: center;
22
+ height: 100%;
23
23
  }
24
24
  </style>
@@ -13,8 +13,8 @@ const { t, te } = useI18n();
13
13
  </div>
14
14
  </template>
15
15
 
16
- <style>
16
+ <style lang="scss">
17
17
  .chat-get-started-footer {
18
- padding: var(--chat--spacing);
18
+ padding: var(--chat--spacing);
19
19
  }
20
20
  </style>
@@ -49,40 +49,45 @@ async function onSubmitKeydown(event: KeyboardEvent) {
49
49
  </div>
50
50
  </template>
51
51
 
52
- <style>
52
+ <style lang="scss">
53
53
  .chat-input {
54
- display: flex;
55
- justify-content: center;
56
- align-items: center;
57
- width: 100%;
58
- }
59
- .chat-input textarea {
60
- font-family: inherit;
61
- font-size: inherit;
62
- width: 100%;
63
- border: 0;
64
- padding: var(--chat--spacing);
65
- max-height: var(--chat--textarea--height);
66
- resize: none;
67
- }
68
- .chat-input .chat-input-send-button {
69
- height: var(--chat--textarea--height);
70
- width: var(--chat--textarea--height);
71
- background: white;
72
- cursor: pointer;
73
- color: var(--chat--color-secondary);
74
- border: 0;
75
- font-size: 24px;
76
- display: inline-flex;
77
- align-items: center;
78
- justify-content: center;
79
- transition: color var(--chat--transition-duration) ease;
80
- }
81
- .chat-input .chat-input-send-button:hover, .chat-input .chat-input-send-button:focus {
82
- color: var(--chat--color-secondary-shade-50);
83
- }
84
- .chat-input .chat-input-send-button[disabled] {
85
- cursor: default;
86
- color: var(--chat--color-disabled);
54
+ display: flex;
55
+ justify-content: center;
56
+ align-items: center;
57
+ width: 100%;
58
+
59
+ textarea {
60
+ font-family: inherit;
61
+ font-size: inherit;
62
+ width: 100%;
63
+ border: 0;
64
+ padding: var(--chat--spacing);
65
+ max-height: var(--chat--textarea--height);
66
+ resize: none;
67
+ }
68
+
69
+ .chat-input-send-button {
70
+ height: var(--chat--textarea--height);
71
+ width: var(--chat--textarea--height);
72
+ background: white;
73
+ cursor: pointer;
74
+ color: var(--chat--color-secondary);
75
+ border: 0;
76
+ font-size: 24px;
77
+ display: inline-flex;
78
+ align-items: center;
79
+ justify-content: center;
80
+ transition: color var(--chat--transition-duration) ease;
81
+
82
+ &:hover,
83
+ &:focus {
84
+ color: var(--chat--color-secondary-shade-50);
85
+ }
86
+
87
+ &[disabled] {
88
+ cursor: default;
89
+ color: var(--chat--color-disabled);
90
+ }
91
+ }
87
92
  }
88
93
  </style>