n8n-chat-pretty 1.0.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.
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2024 baufer
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,185 @@
1
+ # n8n-chat-pretty
2
+
3
+ Drop-in replacement for [@n8n/chat](https://www.npmjs.com/package/@n8n/chat) with message-style conversation experience inspired by [Julian Garnier's work](https://github.com/juliangarnier).
4
+ **[Try Demo](https://chat.baufer.beauty)**
5
+
6
+ ![alt text](Screenshot.png)
7
+
8
+ ## Features
9
+
10
+ - 💬 **Conversational bubbles** - Messages split naturally into chat-friendly chunks
11
+ - ⌨️ **Typing indicator** - Animated dots between messages with proportional delays
12
+ - 🌙 **Dark mode** - Automatic system preference detection
13
+ - 📱 **Mobile-first** - Responsive design optimized for mobile devices
14
+ - 🎨 **Customizable** - CSS variables for easy theming
15
+
16
+ ## Installation
17
+
18
+ ### CDN (Quickest)
19
+
20
+ ```html
21
+ <link href="https://cdn.jsdelivr.net/npm/n8n-chat-pretty/dist/style.css" rel="stylesheet" />
22
+ <script type="module">
23
+ import { createChat } from 'https://cdn.jsdelivr.net/npm/n8n-chat-pretty/dist/chat.es.js';
24
+
25
+ createChat({
26
+ webhookUrl: 'YOUR_N8N_WEBHOOK_URL'
27
+ });
28
+ </script>
29
+ ```
30
+
31
+ ### npm
32
+
33
+ ```bash
34
+ npm install n8n-chat-pretty
35
+ ```
36
+
37
+ ```javascript
38
+ import 'n8n-chat-pretty/style.css';
39
+ import { createChat } from 'n8n-chat-pretty';
40
+
41
+ createChat({
42
+ webhookUrl: 'YOUR_N8N_WEBHOOK_URL'
43
+ });
44
+ ```
45
+
46
+ ## Usage
47
+
48
+ ### Basic
49
+
50
+ ```html
51
+ <!DOCTYPE html>
52
+ <html>
53
+ <head>
54
+ <link href="https://cdn.jsdelivr.net/npm/n8n-chat-pretty/dist/style.css" rel="stylesheet" />
55
+ </head>
56
+ <body>
57
+ <div id="n8n-chat" style="height: 100vh;"></div>
58
+
59
+ <script type="module">
60
+ import { createChat } from 'https://cdn.jsdelivr.net/npm/n8n-chat-pretty/dist/chat.es.js';
61
+
62
+ createChat({
63
+ webhookUrl: 'YOUR_N8N_WEBHOOK_URL',
64
+ initialMessages: [
65
+ 'Hey there! 👋',
66
+ 'How can I help you today?'
67
+ ]
68
+ });
69
+ </script>
70
+ </body>
71
+ </html>
72
+ ```
73
+
74
+ ### With Options
75
+
76
+ ```javascript
77
+ const chat = createChat({
78
+ webhookUrl: 'YOUR_N8N_WEBHOOK_URL',
79
+ target: '#my-chat-container',
80
+ mode: 'fullscreen', // or 'window'
81
+
82
+ initialMessages: [
83
+ 'Welcome! 👋',
84
+ 'Ask me anything about our services.'
85
+ ],
86
+
87
+ theme: {
88
+ primaryColor: '#007bff',
89
+ botMessageBackground: '#f0f0f0',
90
+ userMessageBackground: '#007bff',
91
+ },
92
+
93
+ i18n: {
94
+ inputPlaceholder: 'Ask a question...',
95
+ sendButtonText: 'Send',
96
+ errorMessage: 'Oops! Something went wrong.'
97
+ },
98
+
99
+ typingIndicator: {
100
+ msPerChar: 25, // Typing speed
101
+ baseDelay: 400 // Minimum delay
102
+ },
103
+
104
+ maxBubbleLength: 180, // Max chars before splitting
105
+
106
+ metadata: {
107
+ source: 'website',
108
+ page: window.location.pathname
109
+ }
110
+ });
111
+
112
+ // Programmatic control
113
+ chat.sendMessage('Hello!');
114
+ chat.clear();
115
+ chat.destroy();
116
+ ```
117
+
118
+ ## Options
119
+
120
+ | Option | Type | Default | Description |
121
+ |--------|------|---------|-------------|
122
+ | `webhookUrl` | `string` | **required** | Your n8n webhook URL |
123
+ | `target` | `string` | `'#n8n-chat'` | CSS selector for container |
124
+ | `mode` | `'fullscreen' \| 'window'` | `'fullscreen'` | Display mode |
125
+ | `initialMessages` | `string[]` | `['Hey there! 👋', ...]` | Welcome messages |
126
+ | `chatInputKey` | `string` | `'chatInput'` | Key for message in webhook payload |
127
+ | `chatSessionKey` | `string` | `'sessionId'` | Key for session ID |
128
+ | `loadPreviousSession` | `boolean` | `true` | Persist session across reloads |
129
+ | `metadata` | `object` | `{}` | Extra data to send with each message |
130
+ | `theme` | `ThemeOptions` | - | Theme customization |
131
+ | `i18n` | `I18nOptions` | - | Text customization |
132
+ | `typingIndicator` | `object` | `{msPerChar: 20, baseDelay: 300}` | Typing animation speed |
133
+ | `maxBubbleLength` | `number` | `200` | Max chars per bubble |
134
+ | `enableAnimations` | `boolean` | `true` | Enable bubble animations |
135
+
136
+ ## Theme Options
137
+
138
+ ```javascript
139
+ theme: {
140
+ primaryColor: '#e74266', // Buttons, user messages
141
+ backgroundColor: '#ffffff', // Main background
142
+ textColor: '#000000', // Text color
143
+ botMessageBackground: '#f0f0f0', // Bot bubble background
144
+ userMessageBackground: '#e74266', // User bubble background
145
+ fontFamily: 'system-ui, sans-serif',
146
+ borderRadius: '1.25rem'
147
+ }
148
+ ```
149
+
150
+ ## CSS Variables
151
+
152
+ You can also customize via CSS:
153
+
154
+ ```css
155
+ :root {
156
+ --n8n-chat-primary: #e74266;
157
+ --n8n-chat-bg: #ffffff;
158
+ --n8n-chat-text: #000000;
159
+ --n8n-chat-bot-bg: rgba(206, 206, 206, 0.5);
160
+ --n8n-chat-user-bg: #e74266;
161
+ --n8n-chat-user-text: #ffffff;
162
+ --n8n-chat-border-radius: 1.25rem;
163
+ --n8n-chat-font-family: system-ui, sans-serif;
164
+ }
165
+ ```
166
+
167
+ ## Comparison with @n8n/chat
168
+
169
+ | Feature | @n8n/chat | n8n-chat-pretty |
170
+ |---------|-----------|-----------------|
171
+ | Message splitting | ❌ | ✅ Natural sentence/paragraph splitting |
172
+ | Typing indicator | ⚠️ Single | ✅ Between each bubble |
173
+ | Typing delay | ❌ Fixed | ✅ Proportional to message length |
174
+ | Mobile optimized | ⚠️ Basic | ✅ Mobile-first design |
175
+
176
+ ## n8n Workflow Setup
177
+
178
+ 1. Create a workflow with **Chat Trigger** node
179
+ 2. Add your domain to **Allowed Origins (CORS)**
180
+ 3. Connect to your AI agent (OpenAI, Anthropic, etc.)
181
+ 4. Use the webhook URL in `createChat()`
182
+
183
+ ## License
184
+
185
+ MIT
@@ -0,0 +1,205 @@
1
+ const J = {
2
+ webhookConfig: {
3
+ method: "POST",
4
+ headers: {}
5
+ },
6
+ target: "#n8n-chat",
7
+ mode: "fullscreen",
8
+ initialMessages: [
9
+ "Hey there! 👋",
10
+ "How can I help you today?"
11
+ ],
12
+ chatInputKey: "chatInput",
13
+ chatSessionKey: "sessionId",
14
+ loadPreviousSession: !0,
15
+ metadata: {},
16
+ i18n: {
17
+ inputPlaceholder: "Type your message...",
18
+ sendButtonText: "Send",
19
+ errorMessage: "Connection error. Please try again.",
20
+ fallbackResponse: "Sorry, I couldn't process that."
21
+ },
22
+ typingIndicator: {
23
+ msPerChar: 20,
24
+ baseDelay: 300
25
+ },
26
+ maxBubbleLength: 200,
27
+ enableAnimations: !0
28
+ };
29
+ function _(R) {
30
+ var N, B;
31
+ const n = { ...J, ...R };
32
+ if (!n.webhookUrl)
33
+ throw new Error("n8n-chat-pretty: webhookUrl is required");
34
+ function C() {
35
+ return typeof crypto < "u" && typeof crypto.randomUUID == "function" ? crypto.randomUUID() : `sid_${Date.now().toString(36)}_${Math.random().toString(36).slice(2)}`;
36
+ }
37
+ const S = `n8n-chat-session-${n.chatSessionKey}`;
38
+ let m = n.loadPreviousSession && localStorage.getItem(S) || C();
39
+ n.loadPreviousSession && localStorage.setItem(S, m);
40
+ const v = document.querySelector(n.target);
41
+ if (!v)
42
+ throw new Error(`n8n-chat-pretty: target element "${n.target}" not found`);
43
+ const c = document.createElement("div");
44
+ c.className = `n8n-chat ${n.mode}-mode`;
45
+ const l = document.createElement("div");
46
+ l.className = "n8n-chat-messages";
47
+ const f = document.createElement("div");
48
+ f.className = "n8n-chat-input-area";
49
+ const d = document.createElement("input");
50
+ d.type = "text", d.className = "n8n-chat-input", d.placeholder = ((N = n.i18n) == null ? void 0 : N.inputPlaceholder) || "Type your message...", d.autocomplete = "off";
51
+ const g = document.createElement("button");
52
+ if (g.className = "n8n-chat-send-btn", g.textContent = ((B = n.i18n) == null ? void 0 : B.sendButtonText) || "Send", f.appendChild(d), f.appendChild(g), c.appendChild(l), c.appendChild(f), v.appendChild(c), n.theme) {
53
+ const e = n.theme;
54
+ e.primaryColor && c.style.setProperty("--n8n-chat-primary", e.primaryColor), e.backgroundColor && c.style.setProperty("--n8n-chat-bg", e.backgroundColor), e.textColor && c.style.setProperty("--n8n-chat-text", e.textColor), e.botMessageBackground && c.style.setProperty("--n8n-chat-bot-bg", e.botMessageBackground), e.userMessageBackground && c.style.setProperty("--n8n-chat-user-bg", e.userMessageBackground), e.fontFamily && c.style.setProperty("--n8n-chat-font-family", e.fontFamily), e.borderRadius && c.style.setProperty("--n8n-chat-border-radius", e.borderRadius);
55
+ }
56
+ function E() {
57
+ l.scrollTop = l.scrollHeight;
58
+ }
59
+ function $(e, o) {
60
+ const s = document.createElement("div");
61
+ s.className = `n8n-chat-bubble ${o}`;
62
+ const a = document.createElement("span");
63
+ return a.className = "message", a.textContent = e, s.appendChild(a), s;
64
+ }
65
+ function P() {
66
+ const e = document.createElement("div");
67
+ e.className = "n8n-chat-bubble left";
68
+ const o = document.createElement("span");
69
+ return o.className = "loading", o.innerHTML = "<b>•</b><b>•</b><b>•</b>", e.appendChild(o), e;
70
+ }
71
+ function h(e, o, s = !1) {
72
+ const a = document.createElement("div");
73
+ a.className = `n8n-chat-message-row ${o}`;
74
+ const t = $(e, o);
75
+ return n.enableAnimations && t.classList.add("animate-in"), a.appendChild(t), l.appendChild(a), s && E(), t;
76
+ }
77
+ function U(e) {
78
+ const o = n.maxBubbleLength || 200;
79
+ let s = e.split(/\n\n+/).map((t) => t.trim()).filter((t) => t);
80
+ if (s.length === 1) {
81
+ const t = e.split(/(?=\d+\.\s+\*\*|\n\d+\.\s|\n[-•]\s)/);
82
+ t.length > 1 ? s = t.map((i) => i.trim()).filter((i) => i) : (s = e.match(/[^.!?]+[.!?]+|[^.!?]+$/g) || [e], s = s.map((i) => i.trim()).filter((i) => i));
83
+ }
84
+ const a = [];
85
+ for (const t of s)
86
+ if (a.length > 0 && t.length < 30)
87
+ a[a.length - 1] += " " + t;
88
+ else if (t.length > o) {
89
+ const i = t.match(/[^.!?]+[.!?]+|[^.!?]+$/g) || [t];
90
+ let r = "";
91
+ for (const u of i)
92
+ r.length + u.length > o && r.length > 0 ? (a.push(r.trim()), r = u) : r += (r ? " " : "") + u.trim();
93
+ r.trim() && a.push(r.trim());
94
+ } else
95
+ a.push(t);
96
+ return a.length > 0 ? a : [e];
97
+ }
98
+ async function O(e, o) {
99
+ const { msPerChar: s = 20, baseDelay: a = 300 } = n.typingIndicator || {};
100
+ for (let t = 0; t < e.length; t++) {
101
+ if (t > 0) {
102
+ const r = document.createElement("div");
103
+ r.className = "n8n-chat-message-row left";
104
+ const u = P();
105
+ r.appendChild(u), l.appendChild(r);
106
+ const p = e[t].length * s + a;
107
+ await new Promise((y) => setTimeout(y, p)), r.remove();
108
+ }
109
+ const i = t === 0;
110
+ h(e[t], o, i);
111
+ }
112
+ }
113
+ async function H(e) {
114
+ if ((e.headers.get("content-type") || "").includes("application/json"))
115
+ return await e.json();
116
+ const s = await e.text();
117
+ if (!s) return null;
118
+ try {
119
+ return JSON.parse(s);
120
+ } catch {
121
+ return s;
122
+ }
123
+ }
124
+ function K(e) {
125
+ var a, t;
126
+ if (typeof e == "string") return e;
127
+ if (!e || typeof e != "object") return ((a = n.i18n) == null ? void 0 : a.fallbackResponse) || "";
128
+ const o = e, s = o.output ?? o.text ?? o.message;
129
+ if (typeof s == "string") return s;
130
+ if (s == null) return ((t = n.i18n) == null ? void 0 : t.fallbackResponse) || "";
131
+ try {
132
+ return JSON.stringify(s);
133
+ } catch {
134
+ return String(s);
135
+ }
136
+ }
137
+ async function T(e) {
138
+ var a, t, i, r, u;
139
+ const o = document.createElement("div");
140
+ o.className = "n8n-chat-message-row left";
141
+ const s = P();
142
+ o.appendChild(s), l.appendChild(o), E();
143
+ try {
144
+ const p = {
145
+ action: "sendMessage",
146
+ [n.chatSessionKey]: m,
147
+ [n.chatInputKey]: e,
148
+ ...n.metadata
149
+ }, y = (((a = n.webhookConfig) == null ? void 0 : a.method) || "POST").toUpperCase(), w = {
150
+ ...(t = n.webhookConfig) == null ? void 0 : t.headers
151
+ };
152
+ let k = n.webhookUrl;
153
+ const x = { method: y, headers: w };
154
+ if (y === "GET") {
155
+ const L = new URL(k, window.location.href);
156
+ for (const [A, b] of Object.entries(p))
157
+ b != null && L.searchParams.set(A, typeof b == "string" ? b : JSON.stringify(b));
158
+ k = L.toString();
159
+ } else
160
+ w["Content-Type"] = w["Content-Type"] || "application/json", x.body = JSON.stringify(p);
161
+ const M = await fetch(k, x), j = await H(M);
162
+ if (o.remove(), !M.ok) {
163
+ h(((i = n.i18n) == null ? void 0 : i.errorMessage) || `Request failed (${M.status})`, "left", !0);
164
+ return;
165
+ }
166
+ const D = K(j) || ((r = n.i18n) == null ? void 0 : r.fallbackResponse) || "", q = U(D);
167
+ await O(q, "left");
168
+ } catch {
169
+ o.remove(), h(((u = n.i18n) == null ? void 0 : u.errorMessage) || "Connection error.", "left", !0);
170
+ }
171
+ }
172
+ function I() {
173
+ const e = d.value.trim();
174
+ e && (d.value = "", h(e, "right", !0), T(e));
175
+ }
176
+ if (g.addEventListener("click", I), d.addEventListener("keydown", (e) => {
177
+ e.key === "Enter" && I();
178
+ }), n.initialMessages && n.initialMessages.length > 0) {
179
+ let e = 300;
180
+ for (const o of n.initialMessages)
181
+ setTimeout(() => h(o, "left", !0), e), e += 700;
182
+ }
183
+ return {
184
+ sendMessage: async (e) => {
185
+ h(e, "right", !0), await T(e);
186
+ },
187
+ addMessage: (e, o) => {
188
+ h(e, o, !0);
189
+ },
190
+ clear: () => {
191
+ l.innerHTML = "";
192
+ },
193
+ destroy: () => {
194
+ c.remove();
195
+ },
196
+ getSessionId: () => m,
197
+ resetSession: () => {
198
+ m = C(), n.loadPreviousSession && localStorage.setItem(S, m);
199
+ }
200
+ };
201
+ }
202
+ export {
203
+ _ as createChat
204
+ };
205
+ //# sourceMappingURL=chat.es.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"chat.es.js","sources":["../src/index.ts"],"sourcesContent":["import './styles.css';\nimport type { ChatOptions, ChatInstance } from './types';\n\n// Default options matching n8n/chat API\nconst defaultOptions: Partial<ChatOptions> = {\n webhookConfig: {\n method: 'POST',\n headers: {}\n },\n target: '#n8n-chat',\n mode: 'fullscreen',\n initialMessages: [\n 'Hey there! 👋',\n 'How can I help you today?'\n ],\n chatInputKey: 'chatInput',\n chatSessionKey: 'sessionId',\n loadPreviousSession: true,\n metadata: {},\n i18n: {\n inputPlaceholder: 'Type your message...',\n sendButtonText: 'Send',\n errorMessage: 'Connection error. Please try again.',\n fallbackResponse: \"Sorry, I couldn't process that.\"\n },\n typingIndicator: {\n msPerChar: 20,\n baseDelay: 300\n },\n maxBubbleLength: 200,\n enableAnimations: true\n};\n\n/**\n * Creates a new chat instance\n */\nexport function createChat(options: ChatOptions): ChatInstance {\n const config = { ...defaultOptions, ...options };\n \n // Validate required options\n if (!config.webhookUrl) {\n throw new Error('n8n-chat-pretty: webhookUrl is required');\n }\n\n function generateSessionId(): string {\n if (typeof crypto !== 'undefined' && typeof crypto.randomUUID === 'function') {\n return crypto.randomUUID();\n }\n // Fallback for older browsers/webviews\n return `sid_${Date.now().toString(36)}_${Math.random().toString(36).slice(2)}`;\n }\n\n // Get or create session ID\n const sessionKey = `n8n-chat-session-${config.chatSessionKey}`;\n let sessionId = config.loadPreviousSession\n ? (localStorage.getItem(sessionKey) || generateSessionId())\n : generateSessionId();\n\n if (config.loadPreviousSession) {\n localStorage.setItem(sessionKey, sessionId);\n }\n\n // Get target element\n const targetEl = document.querySelector(config.target!) as HTMLElement;\n if (!targetEl) {\n throw new Error(`n8n-chat-pretty: target element \"${config.target}\" not found`);\n }\n\n // Create chat structure\n const container = document.createElement('div');\n container.className = `n8n-chat ${config.mode}-mode`;\n \n const messagesEl = document.createElement('div');\n messagesEl.className = 'n8n-chat-messages';\n \n const inputArea = document.createElement('div');\n inputArea.className = 'n8n-chat-input-area';\n \n const input = document.createElement('input');\n input.type = 'text';\n input.className = 'n8n-chat-input';\n input.placeholder = config.i18n?.inputPlaceholder || 'Type your message...';\n input.autocomplete = 'off';\n \n const sendBtn = document.createElement('button');\n sendBtn.className = 'n8n-chat-send-btn';\n sendBtn.textContent = config.i18n?.sendButtonText || 'Send';\n \n inputArea.appendChild(input);\n inputArea.appendChild(sendBtn);\n container.appendChild(messagesEl);\n container.appendChild(inputArea);\n targetEl.appendChild(container);\n\n // Apply theme\n if (config.theme) {\n const t = config.theme;\n if (t.primaryColor) container.style.setProperty('--n8n-chat-primary', t.primaryColor);\n if (t.backgroundColor) container.style.setProperty('--n8n-chat-bg', t.backgroundColor);\n if (t.textColor) container.style.setProperty('--n8n-chat-text', t.textColor);\n if (t.botMessageBackground) container.style.setProperty('--n8n-chat-bot-bg', t.botMessageBackground);\n if (t.userMessageBackground) container.style.setProperty('--n8n-chat-user-bg', t.userMessageBackground);\n if (t.fontFamily) container.style.setProperty('--n8n-chat-font-family', t.fontFamily);\n if (t.borderRadius) container.style.setProperty('--n8n-chat-border-radius', t.borderRadius);\n }\n\n // Helper functions\n function scrollToBottom() {\n messagesEl.scrollTop = messagesEl.scrollHeight;\n }\n\n function createBubble(text: string, position: 'left' | 'right'): HTMLElement {\n const bubble = document.createElement('div');\n bubble.className = `n8n-chat-bubble ${position}`;\n const msg = document.createElement('span');\n msg.className = 'message';\n // Use textContent to avoid XSS via untrusted webhook/user content\n msg.textContent = text;\n bubble.appendChild(msg);\n return bubble;\n }\n\n function createLoadingBubble(): HTMLElement {\n const bubble = document.createElement('div');\n bubble.className = 'n8n-chat-bubble left';\n const loading = document.createElement('span');\n loading.className = 'loading';\n loading.innerHTML = '<b>•</b><b>•</b><b>•</b>';\n bubble.appendChild(loading);\n return bubble;\n }\n\n function addMessage(text: string, position: 'left' | 'right', shouldScroll = false): HTMLElement {\n const row = document.createElement('div');\n row.className = `n8n-chat-message-row ${position}`;\n const bubble = createBubble(text, position);\n \n if (config.enableAnimations) {\n bubble.classList.add('animate-in');\n }\n \n row.appendChild(bubble);\n messagesEl.appendChild(row);\n \n if (shouldScroll) {\n scrollToBottom();\n }\n \n return bubble;\n }\n\n function splitIntoChunks(text: string): string[] {\n const MAX_LENGTH = config.maxBubbleLength || 200;\n \n // First try splitting by double newlines (paragraphs)\n let chunks = text.split(/\\n\\n+/).map(s => s.trim()).filter(s => s);\n \n // If only one chunk, try other split strategies\n if (chunks.length === 1) {\n // Try splitting by numbered lists or markdown headers\n const listSplit = text.split(/(?=\\d+\\.\\s+\\*\\*|\\n\\d+\\.\\s|\\n[-•]\\s)/);\n if (listSplit.length > 1) {\n chunks = listSplit.map(s => s.trim()).filter(s => s);\n } else {\n // Split by sentence endings\n chunks = text.match(/[^.!?]+[.!?]+|[^.!?]+$/g) || [text];\n chunks = chunks.map(s => s.trim()).filter(s => s);\n }\n }\n \n // Merge very short chunks, split very long ones\n const result: string[] = [];\n for (const chunk of chunks) {\n if (result.length > 0 && chunk.length < 30) {\n result[result.length - 1] += ' ' + chunk;\n } else if (chunk.length > MAX_LENGTH) {\n const sentences = chunk.match(/[^.!?]+[.!?]+|[^.!?]+$/g) || [chunk];\n let current = '';\n for (const sentence of sentences) {\n if (current.length + sentence.length > MAX_LENGTH && current.length > 0) {\n result.push(current.trim());\n current = sentence;\n } else {\n current += (current ? ' ' : '') + sentence.trim();\n }\n }\n if (current.trim()) result.push(current.trim());\n } else {\n result.push(chunk);\n }\n }\n \n return result.length > 0 ? result : [text];\n }\n\n async function addMessagesSequentially(chunks: string[], position: 'left' | 'right') {\n const { msPerChar = 20, baseDelay = 300 } = config.typingIndicator || {};\n \n for (let i = 0; i < chunks.length; i++) {\n if (i > 0) {\n const typingRow = document.createElement('div');\n typingRow.className = 'n8n-chat-message-row left';\n const typingBubble = createLoadingBubble();\n typingRow.appendChild(typingBubble);\n messagesEl.appendChild(typingRow);\n \n const delay = chunks[i].length * msPerChar + baseDelay;\n await new Promise(r => setTimeout(r, delay));\n \n typingRow.remove();\n }\n \n const shouldScroll = (i === 0);\n addMessage(chunks[i], position, shouldScroll);\n }\n }\n\n async function parseWebhookResponse(response: Response): Promise<unknown> {\n const contentType = response.headers.get('content-type') || '';\n if (contentType.includes('application/json')) {\n return await response.json();\n }\n const text = await response.text();\n if (!text) return null;\n try {\n return JSON.parse(text);\n } catch {\n return text;\n }\n }\n\n function extractBotMessage(data: unknown): string {\n if (typeof data === 'string') return data;\n if (!data || typeof data !== 'object') return config.i18n?.fallbackResponse || '';\n\n const record = data as Record<string, unknown>;\n const candidate = record.output ?? record.text ?? record.message;\n if (typeof candidate === 'string') return candidate;\n if (candidate == null) return config.i18n?.fallbackResponse || '';\n try {\n return JSON.stringify(candidate);\n } catch {\n return String(candidate);\n }\n }\n\n async function sendToWebhook(message: string) {\n const row = document.createElement('div');\n row.className = 'n8n-chat-message-row left';\n const loadingBubble = createLoadingBubble();\n row.appendChild(loadingBubble);\n messagesEl.appendChild(row);\n scrollToBottom();\n\n try {\n const payload = {\n action: 'sendMessage',\n [config.chatSessionKey!]: sessionId,\n [config.chatInputKey!]: message,\n ...config.metadata\n };\n\n const method = (config.webhookConfig?.method || 'POST').toUpperCase();\n const headers: Record<string, string> = {\n ...config.webhookConfig?.headers\n };\n\n let url = config.webhookUrl;\n const init: RequestInit = { method, headers };\n\n if (method === 'GET') {\n const u = new URL(url, window.location.href);\n for (const [key, value] of Object.entries(payload)) {\n if (value == null) continue;\n u.searchParams.set(key, typeof value === 'string' ? value : JSON.stringify(value));\n }\n url = u.toString();\n } else {\n headers['Content-Type'] = headers['Content-Type'] || 'application/json';\n init.body = JSON.stringify(payload);\n }\n\n const response = await fetch(url, init);\n const data = await parseWebhookResponse(response);\n row.remove();\n\n if (!response.ok) {\n addMessage(config.i18n?.errorMessage || `Request failed (${response.status})`, 'left', true);\n return;\n }\n\n const botMessage = extractBotMessage(data) || config.i18n?.fallbackResponse || '';\n const chunks = splitIntoChunks(botMessage);\n await addMessagesSequentially(chunks, 'left');\n \n } catch (error) {\n row.remove();\n addMessage(config.i18n?.errorMessage || 'Connection error.', 'left', true);\n }\n }\n\n function handleSend() {\n const text = input.value.trim();\n if (!text) return;\n \n input.value = '';\n addMessage(text, 'right', true);\n sendToWebhook(text);\n }\n\n // Event listeners\n sendBtn.addEventListener('click', handleSend);\n input.addEventListener('keydown', (e) => {\n if (e.key === 'Enter') handleSend();\n });\n\n // Initial messages\n if (config.initialMessages && config.initialMessages.length > 0) {\n let delay = 300;\n for (const msg of config.initialMessages) {\n setTimeout(() => addMessage(msg, 'left', true), delay);\n delay += 700;\n }\n }\n\n // Return chat instance\n return {\n sendMessage: async (text: string) => {\n addMessage(text, 'right', true);\n await sendToWebhook(text);\n },\n addMessage: (text: string, position: 'left' | 'right') => {\n addMessage(text, position, true);\n },\n clear: () => {\n messagesEl.innerHTML = '';\n },\n destroy: () => {\n container.remove();\n },\n getSessionId: () => sessionId,\n resetSession: () => {\n sessionId = generateSessionId();\n if (config.loadPreviousSession) {\n localStorage.setItem(sessionKey, sessionId);\n }\n }\n };\n}\n\n// Re-export types\nexport type { ChatOptions, ChatInstance, ThemeOptions, I18nOptions } from './types';\n"],"names":["defaultOptions","createChat","options","_a","_b","config","generateSessionId","sessionKey","sessionId","targetEl","container","messagesEl","inputArea","input","sendBtn","t","scrollToBottom","createBubble","text","position","bubble","msg","createLoadingBubble","loading","addMessage","shouldScroll","row","splitIntoChunks","MAX_LENGTH","chunks","s","listSplit","result","chunk","sentences","current","sentence","addMessagesSequentially","msPerChar","baseDelay","i","typingRow","typingBubble","delay","r","parseWebhookResponse","response","extractBotMessage","data","record","candidate","sendToWebhook","message","_c","_d","_e","loadingBubble","payload","method","headers","url","init","u","key","value","botMessage","handleSend"],"mappings":"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"}
@@ -0,0 +1,2 @@
1
+ (function(h,p){typeof exports=="object"&&typeof module<"u"?p(exports):typeof define=="function"&&define.amd?define(["exports"],p):(h=typeof globalThis<"u"?globalThis:h||self,p(h.N8nChatPretty={}))})(this,function(h){"use strict";const p={webhookConfig:{method:"POST",headers:{}},target:"#n8n-chat",mode:"fullscreen",initialMessages:["Hey there! 👋","How can I help you today?"],chatInputKey:"chatInput",chatSessionKey:"sessionId",loadPreviousSession:!0,metadata:{},i18n:{inputPlaceholder:"Type your message...",sendButtonText:"Send",errorMessage:"Connection error. Please try again.",fallbackResponse:"Sorry, I couldn't process that."},typingIndicator:{msPerChar:20,baseDelay:300},maxBubbleLength:200,enableAnimations:!0};function O(U){var x,L;const n={...p,...U};if(!n.webhookUrl)throw new Error("n8n-chat-pretty: webhookUrl is required");function w(){return typeof crypto<"u"&&typeof crypto.randomUUID=="function"?crypto.randomUUID():`sid_${Date.now().toString(36)}_${Math.random().toString(36).slice(2)}`}const k=`n8n-chat-session-${n.chatSessionKey}`;let m=n.loadPreviousSession&&localStorage.getItem(k)||w();n.loadPreviousSession&&localStorage.setItem(k,m);const T=document.querySelector(n.target);if(!T)throw new Error(`n8n-chat-pretty: target element "${n.target}" not found`);const c=document.createElement("div");c.className=`n8n-chat ${n.mode}-mode`;const l=document.createElement("div");l.className="n8n-chat-messages";const y=document.createElement("div");y.className="n8n-chat-input-area";const d=document.createElement("input");d.type="text",d.className="n8n-chat-input",d.placeholder=((x=n.i18n)==null?void 0:x.inputPlaceholder)||"Type your message...",d.autocomplete="off";const b=document.createElement("button");if(b.className="n8n-chat-send-btn",b.textContent=((L=n.i18n)==null?void 0:L.sendButtonText)||"Send",y.appendChild(d),y.appendChild(b),c.appendChild(l),c.appendChild(y),T.appendChild(c),n.theme){const e=n.theme;e.primaryColor&&c.style.setProperty("--n8n-chat-primary",e.primaryColor),e.backgroundColor&&c.style.setProperty("--n8n-chat-bg",e.backgroundColor),e.textColor&&c.style.setProperty("--n8n-chat-text",e.textColor),e.botMessageBackground&&c.style.setProperty("--n8n-chat-bot-bg",e.botMessageBackground),e.userMessageBackground&&c.style.setProperty("--n8n-chat-user-bg",e.userMessageBackground),e.fontFamily&&c.style.setProperty("--n8n-chat-font-family",e.fontFamily),e.borderRadius&&c.style.setProperty("--n8n-chat-border-radius",e.borderRadius)}function E(){l.scrollTop=l.scrollHeight}function j(e,o){const s=document.createElement("div");s.className=`n8n-chat-bubble ${o}`;const a=document.createElement("span");return a.className="message",a.textContent=e,s.appendChild(a),s}function N(){const e=document.createElement("div");e.className="n8n-chat-bubble left";const o=document.createElement("span");return o.className="loading",o.innerHTML="<b>•</b><b>•</b><b>•</b>",e.appendChild(o),e}function f(e,o,s=!1){const a=document.createElement("div");a.className=`n8n-chat-message-row ${o}`;const t=j(e,o);return n.enableAnimations&&t.classList.add("animate-in"),a.appendChild(t),l.appendChild(a),s&&E(),t}function H(e){const o=n.maxBubbleLength||200;let s=e.split(/\n\n+/).map(t=>t.trim()).filter(t=>t);if(s.length===1){const t=e.split(/(?=\d+\.\s+\*\*|\n\d+\.\s|\n[-•]\s)/);t.length>1?s=t.map(i=>i.trim()).filter(i=>i):(s=e.match(/[^.!?]+[.!?]+|[^.!?]+$/g)||[e],s=s.map(i=>i.trim()).filter(i=>i))}const a=[];for(const t of s)if(a.length>0&&t.length<30)a[a.length-1]+=" "+t;else if(t.length>o){const i=t.match(/[^.!?]+[.!?]+|[^.!?]+$/g)||[t];let r="";for(const u of i)r.length+u.length>o&&r.length>0?(a.push(r.trim()),r=u):r+=(r?" ":"")+u.trim();r.trim()&&a.push(r.trim())}else a.push(t);return a.length>0?a:[e]}async function K(e,o){const{msPerChar:s=20,baseDelay:a=300}=n.typingIndicator||{};for(let t=0;t<e.length;t++){if(t>0){const r=document.createElement("div");r.className="n8n-chat-message-row left";const u=N();r.appendChild(u),l.appendChild(r);const g=e[t].length*s+a;await new Promise(C=>setTimeout(C,g)),r.remove()}const i=t===0;f(e[t],o,i)}}async function D(e){if((e.headers.get("content-type")||"").includes("application/json"))return await e.json();const s=await e.text();if(!s)return null;try{return JSON.parse(s)}catch{return s}}function q(e){var a,t;if(typeof e=="string")return e;if(!e||typeof e!="object")return((a=n.i18n)==null?void 0:a.fallbackResponse)||"";const o=e,s=o.output??o.text??o.message;if(typeof s=="string")return s;if(s==null)return((t=n.i18n)==null?void 0:t.fallbackResponse)||"";try{return JSON.stringify(s)}catch{return String(s)}}async function I(e){var a,t,i,r,u;const o=document.createElement("div");o.className="n8n-chat-message-row left";const s=N();o.appendChild(s),l.appendChild(o),E();try{const g={action:"sendMessage",[n.chatSessionKey]:m,[n.chatInputKey]:e,...n.metadata},C=(((a=n.webhookConfig)==null?void 0:a.method)||"POST").toUpperCase(),M={...(t=n.webhookConfig)==null?void 0:t.headers};let v=n.webhookUrl;const R={method:C,headers:M};if(C==="GET"){const $=new URL(v,window.location.href);for(const[F,S]of Object.entries(g))S!=null&&$.searchParams.set(F,typeof S=="string"?S:JSON.stringify(S));v=$.toString()}else M["Content-Type"]=M["Content-Type"]||"application/json",R.body=JSON.stringify(g);const P=await fetch(v,R),A=await D(P);if(o.remove(),!P.ok){f(((i=n.i18n)==null?void 0:i.errorMessage)||`Request failed (${P.status})`,"left",!0);return}const J=q(A)||((r=n.i18n)==null?void 0:r.fallbackResponse)||"",_=H(J);await K(_,"left")}catch{o.remove(),f(((u=n.i18n)==null?void 0:u.errorMessage)||"Connection error.","left",!0)}}function B(){const e=d.value.trim();e&&(d.value="",f(e,"right",!0),I(e))}if(b.addEventListener("click",B),d.addEventListener("keydown",e=>{e.key==="Enter"&&B()}),n.initialMessages&&n.initialMessages.length>0){let e=300;for(const o of n.initialMessages)setTimeout(()=>f(o,"left",!0),e),e+=700}return{sendMessage:async e=>{f(e,"right",!0),await I(e)},addMessage:(e,o)=>{f(e,o,!0)},clear:()=>{l.innerHTML=""},destroy:()=>{c.remove()},getSessionId:()=>m,resetSession:()=>{m=w(),n.loadPreviousSession&&localStorage.setItem(k,m)}}}h.createChat=O,Object.defineProperty(h,Symbol.toStringTag,{value:"Module"})});
2
+ //# sourceMappingURL=chat.umd.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"chat.umd.js","sources":["../src/index.ts"],"sourcesContent":["import './styles.css';\nimport type { ChatOptions, ChatInstance } from './types';\n\n// Default options matching n8n/chat API\nconst defaultOptions: Partial<ChatOptions> = {\n webhookConfig: {\n method: 'POST',\n headers: {}\n },\n target: '#n8n-chat',\n mode: 'fullscreen',\n initialMessages: [\n 'Hey there! 👋',\n 'How can I help you today?'\n ],\n chatInputKey: 'chatInput',\n chatSessionKey: 'sessionId',\n loadPreviousSession: true,\n metadata: {},\n i18n: {\n inputPlaceholder: 'Type your message...',\n sendButtonText: 'Send',\n errorMessage: 'Connection error. Please try again.',\n fallbackResponse: \"Sorry, I couldn't process that.\"\n },\n typingIndicator: {\n msPerChar: 20,\n baseDelay: 300\n },\n maxBubbleLength: 200,\n enableAnimations: true\n};\n\n/**\n * Creates a new chat instance\n */\nexport function createChat(options: ChatOptions): ChatInstance {\n const config = { ...defaultOptions, ...options };\n \n // Validate required options\n if (!config.webhookUrl) {\n throw new Error('n8n-chat-pretty: webhookUrl is required');\n }\n\n function generateSessionId(): string {\n if (typeof crypto !== 'undefined' && typeof crypto.randomUUID === 'function') {\n return crypto.randomUUID();\n }\n // Fallback for older browsers/webviews\n return `sid_${Date.now().toString(36)}_${Math.random().toString(36).slice(2)}`;\n }\n\n // Get or create session ID\n const sessionKey = `n8n-chat-session-${config.chatSessionKey}`;\n let sessionId = config.loadPreviousSession\n ? (localStorage.getItem(sessionKey) || generateSessionId())\n : generateSessionId();\n\n if (config.loadPreviousSession) {\n localStorage.setItem(sessionKey, sessionId);\n }\n\n // Get target element\n const targetEl = document.querySelector(config.target!) as HTMLElement;\n if (!targetEl) {\n throw new Error(`n8n-chat-pretty: target element \"${config.target}\" not found`);\n }\n\n // Create chat structure\n const container = document.createElement('div');\n container.className = `n8n-chat ${config.mode}-mode`;\n \n const messagesEl = document.createElement('div');\n messagesEl.className = 'n8n-chat-messages';\n \n const inputArea = document.createElement('div');\n inputArea.className = 'n8n-chat-input-area';\n \n const input = document.createElement('input');\n input.type = 'text';\n input.className = 'n8n-chat-input';\n input.placeholder = config.i18n?.inputPlaceholder || 'Type your message...';\n input.autocomplete = 'off';\n \n const sendBtn = document.createElement('button');\n sendBtn.className = 'n8n-chat-send-btn';\n sendBtn.textContent = config.i18n?.sendButtonText || 'Send';\n \n inputArea.appendChild(input);\n inputArea.appendChild(sendBtn);\n container.appendChild(messagesEl);\n container.appendChild(inputArea);\n targetEl.appendChild(container);\n\n // Apply theme\n if (config.theme) {\n const t = config.theme;\n if (t.primaryColor) container.style.setProperty('--n8n-chat-primary', t.primaryColor);\n if (t.backgroundColor) container.style.setProperty('--n8n-chat-bg', t.backgroundColor);\n if (t.textColor) container.style.setProperty('--n8n-chat-text', t.textColor);\n if (t.botMessageBackground) container.style.setProperty('--n8n-chat-bot-bg', t.botMessageBackground);\n if (t.userMessageBackground) container.style.setProperty('--n8n-chat-user-bg', t.userMessageBackground);\n if (t.fontFamily) container.style.setProperty('--n8n-chat-font-family', t.fontFamily);\n if (t.borderRadius) container.style.setProperty('--n8n-chat-border-radius', t.borderRadius);\n }\n\n // Helper functions\n function scrollToBottom() {\n messagesEl.scrollTop = messagesEl.scrollHeight;\n }\n\n function createBubble(text: string, position: 'left' | 'right'): HTMLElement {\n const bubble = document.createElement('div');\n bubble.className = `n8n-chat-bubble ${position}`;\n const msg = document.createElement('span');\n msg.className = 'message';\n // Use textContent to avoid XSS via untrusted webhook/user content\n msg.textContent = text;\n bubble.appendChild(msg);\n return bubble;\n }\n\n function createLoadingBubble(): HTMLElement {\n const bubble = document.createElement('div');\n bubble.className = 'n8n-chat-bubble left';\n const loading = document.createElement('span');\n loading.className = 'loading';\n loading.innerHTML = '<b>•</b><b>•</b><b>•</b>';\n bubble.appendChild(loading);\n return bubble;\n }\n\n function addMessage(text: string, position: 'left' | 'right', shouldScroll = false): HTMLElement {\n const row = document.createElement('div');\n row.className = `n8n-chat-message-row ${position}`;\n const bubble = createBubble(text, position);\n \n if (config.enableAnimations) {\n bubble.classList.add('animate-in');\n }\n \n row.appendChild(bubble);\n messagesEl.appendChild(row);\n \n if (shouldScroll) {\n scrollToBottom();\n }\n \n return bubble;\n }\n\n function splitIntoChunks(text: string): string[] {\n const MAX_LENGTH = config.maxBubbleLength || 200;\n \n // First try splitting by double newlines (paragraphs)\n let chunks = text.split(/\\n\\n+/).map(s => s.trim()).filter(s => s);\n \n // If only one chunk, try other split strategies\n if (chunks.length === 1) {\n // Try splitting by numbered lists or markdown headers\n const listSplit = text.split(/(?=\\d+\\.\\s+\\*\\*|\\n\\d+\\.\\s|\\n[-•]\\s)/);\n if (listSplit.length > 1) {\n chunks = listSplit.map(s => s.trim()).filter(s => s);\n } else {\n // Split by sentence endings\n chunks = text.match(/[^.!?]+[.!?]+|[^.!?]+$/g) || [text];\n chunks = chunks.map(s => s.trim()).filter(s => s);\n }\n }\n \n // Merge very short chunks, split very long ones\n const result: string[] = [];\n for (const chunk of chunks) {\n if (result.length > 0 && chunk.length < 30) {\n result[result.length - 1] += ' ' + chunk;\n } else if (chunk.length > MAX_LENGTH) {\n const sentences = chunk.match(/[^.!?]+[.!?]+|[^.!?]+$/g) || [chunk];\n let current = '';\n for (const sentence of sentences) {\n if (current.length + sentence.length > MAX_LENGTH && current.length > 0) {\n result.push(current.trim());\n current = sentence;\n } else {\n current += (current ? ' ' : '') + sentence.trim();\n }\n }\n if (current.trim()) result.push(current.trim());\n } else {\n result.push(chunk);\n }\n }\n \n return result.length > 0 ? result : [text];\n }\n\n async function addMessagesSequentially(chunks: string[], position: 'left' | 'right') {\n const { msPerChar = 20, baseDelay = 300 } = config.typingIndicator || {};\n \n for (let i = 0; i < chunks.length; i++) {\n if (i > 0) {\n const typingRow = document.createElement('div');\n typingRow.className = 'n8n-chat-message-row left';\n const typingBubble = createLoadingBubble();\n typingRow.appendChild(typingBubble);\n messagesEl.appendChild(typingRow);\n \n const delay = chunks[i].length * msPerChar + baseDelay;\n await new Promise(r => setTimeout(r, delay));\n \n typingRow.remove();\n }\n \n const shouldScroll = (i === 0);\n addMessage(chunks[i], position, shouldScroll);\n }\n }\n\n async function parseWebhookResponse(response: Response): Promise<unknown> {\n const contentType = response.headers.get('content-type') || '';\n if (contentType.includes('application/json')) {\n return await response.json();\n }\n const text = await response.text();\n if (!text) return null;\n try {\n return JSON.parse(text);\n } catch {\n return text;\n }\n }\n\n function extractBotMessage(data: unknown): string {\n if (typeof data === 'string') return data;\n if (!data || typeof data !== 'object') return config.i18n?.fallbackResponse || '';\n\n const record = data as Record<string, unknown>;\n const candidate = record.output ?? record.text ?? record.message;\n if (typeof candidate === 'string') return candidate;\n if (candidate == null) return config.i18n?.fallbackResponse || '';\n try {\n return JSON.stringify(candidate);\n } catch {\n return String(candidate);\n }\n }\n\n async function sendToWebhook(message: string) {\n const row = document.createElement('div');\n row.className = 'n8n-chat-message-row left';\n const loadingBubble = createLoadingBubble();\n row.appendChild(loadingBubble);\n messagesEl.appendChild(row);\n scrollToBottom();\n\n try {\n const payload = {\n action: 'sendMessage',\n [config.chatSessionKey!]: sessionId,\n [config.chatInputKey!]: message,\n ...config.metadata\n };\n\n const method = (config.webhookConfig?.method || 'POST').toUpperCase();\n const headers: Record<string, string> = {\n ...config.webhookConfig?.headers\n };\n\n let url = config.webhookUrl;\n const init: RequestInit = { method, headers };\n\n if (method === 'GET') {\n const u = new URL(url, window.location.href);\n for (const [key, value] of Object.entries(payload)) {\n if (value == null) continue;\n u.searchParams.set(key, typeof value === 'string' ? value : JSON.stringify(value));\n }\n url = u.toString();\n } else {\n headers['Content-Type'] = headers['Content-Type'] || 'application/json';\n init.body = JSON.stringify(payload);\n }\n\n const response = await fetch(url, init);\n const data = await parseWebhookResponse(response);\n row.remove();\n\n if (!response.ok) {\n addMessage(config.i18n?.errorMessage || `Request failed (${response.status})`, 'left', true);\n return;\n }\n\n const botMessage = extractBotMessage(data) || config.i18n?.fallbackResponse || '';\n const chunks = splitIntoChunks(botMessage);\n await addMessagesSequentially(chunks, 'left');\n \n } catch (error) {\n row.remove();\n addMessage(config.i18n?.errorMessage || 'Connection error.', 'left', true);\n }\n }\n\n function handleSend() {\n const text = input.value.trim();\n if (!text) return;\n \n input.value = '';\n addMessage(text, 'right', true);\n sendToWebhook(text);\n }\n\n // Event listeners\n sendBtn.addEventListener('click', handleSend);\n input.addEventListener('keydown', (e) => {\n if (e.key === 'Enter') handleSend();\n });\n\n // Initial messages\n if (config.initialMessages && config.initialMessages.length > 0) {\n let delay = 300;\n for (const msg of config.initialMessages) {\n setTimeout(() => addMessage(msg, 'left', true), delay);\n delay += 700;\n }\n }\n\n // Return chat instance\n return {\n sendMessage: async (text: string) => {\n addMessage(text, 'right', true);\n await sendToWebhook(text);\n },\n addMessage: (text: string, position: 'left' | 'right') => {\n addMessage(text, position, true);\n },\n clear: () => {\n messagesEl.innerHTML = '';\n },\n destroy: () => {\n container.remove();\n },\n getSessionId: () => sessionId,\n resetSession: () => {\n sessionId = generateSessionId();\n if (config.loadPreviousSession) {\n localStorage.setItem(sessionKey, sessionId);\n }\n }\n };\n}\n\n// Re-export types\nexport type { ChatOptions, ChatInstance, ThemeOptions, I18nOptions } from './types';\n"],"names":["defaultOptions","createChat","options","config","generateSessionId","sessionKey","sessionId","targetEl","container","messagesEl","inputArea","input","_a","sendBtn","_b","t","scrollToBottom","createBubble","text","position","bubble","msg","createLoadingBubble","loading","addMessage","shouldScroll","row","splitIntoChunks","MAX_LENGTH","chunks","s","listSplit","result","chunk","sentences","current","sentence","addMessagesSequentially","msPerChar","baseDelay","i","typingRow","typingBubble","delay","r","parseWebhookResponse","response","extractBotMessage","data","record","candidate","sendToWebhook","message","loadingBubble","payload","method","headers","url","init","u","key","value","_c","botMessage","_d","_e","handleSend"],"mappings":"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"}
package/dist/style.css ADDED
@@ -0,0 +1 @@
1
+ :root{--n8n-chat-primary: #e74266;--n8n-chat-primary-hover: #d63a5c;--n8n-chat-bg: #FFF;--n8n-chat-bg-secondary: rgba(206,206,206,.5);--n8n-chat-text: #000;--n8n-chat-text-secondary: rgba(0,0,0,.5);--n8n-chat-user-bg: var(--n8n-chat-primary);--n8n-chat-user-text: #fff;--n8n-chat-bot-bg: var(--n8n-chat-bg-secondary);--n8n-chat-bot-text: var(--n8n-chat-text);--n8n-chat-border: rgba(206,206,206,.5);--n8n-chat-font-family: system-ui, -apple-system, sans-serif;--n8n-chat-font-size: 1rem;--n8n-chat-spacing: 1rem;--n8n-chat-border-radius: 1.25rem;--n8n-chat-window-width: 400px;--n8n-chat-window-height: 600px}@media (prefers-color-scheme: dark){:root{--n8n-chat-bg: #0a0a0a;--n8n-chat-bg-secondary: rgba(206,206,206,.125);--n8n-chat-text: #FFF;--n8n-chat-text-secondary: rgba(206,206,206,.5);--n8n-chat-border: rgba(206,206,206,.2)}}.n8n-chat{display:flex;flex-direction:column;height:100%;width:100%;background:var(--n8n-chat-bg);color:var(--n8n-chat-text);font-family:var(--n8n-chat-font-family);font-size:var(--n8n-chat-font-size);position:relative;overflow:hidden}.n8n-chat *,.n8n-chat *:before,.n8n-chat *:after{box-sizing:border-box;margin:0;padding:0}.n8n-chat-messages{flex:1;overflow-y:auto;overflow-x:hidden;padding:var(--n8n-chat-spacing);display:flex;flex-direction:column;gap:.25rem}.n8n-chat-message-row{display:flex;margin-bottom:.25rem}.n8n-chat-message-row.right{justify-content:flex-end}.n8n-chat-bubble{display:inline-block;padding:.5rem .85rem;line-height:1.3;border-radius:var(--n8n-chat-border-radius);max-width:85%;word-wrap:break-word}.n8n-chat-bubble.left{background:var(--n8n-chat-bot-bg);color:var(--n8n-chat-bot-text);border-bottom-left-radius:0}.n8n-chat-bubble.right{background:var(--n8n-chat-user-bg);color:var(--n8n-chat-user-text);border-bottom-right-radius:0}.n8n-chat-bubble .message{display:inline}.n8n-chat-bubble .loading{white-space:pre;font-size:1.5rem;line-height:1rem}.n8n-chat-bubble .loading b{display:inline-block;color:var(--n8n-chat-text-secondary);animation:n8n-chat-pulse .6s infinite alternate}.n8n-chat-bubble .loading b:nth-child(2){animation-delay:.1s}.n8n-chat-bubble .loading b:nth-child(3){animation-delay:.2s}@keyframes n8n-chat-pulse{to{opacity:.3;transform:scale(.8)}}.n8n-chat-input-area{display:flex;gap:.5rem;padding:var(--n8n-chat-spacing);background:var(--n8n-chat-bg);border-top:1px solid var(--n8n-chat-border);flex-shrink:0}.n8n-chat-input{flex:1;padding:.6rem 1rem;border:1px solid var(--n8n-chat-border);border-radius:1.5rem;font-size:1rem;font-family:inherit;background:var(--n8n-chat-bg);color:var(--n8n-chat-text);outline:none;transition:border-color .2s}.n8n-chat-input:focus{border-color:var(--n8n-chat-primary)}.n8n-chat-input::placeholder{color:var(--n8n-chat-text-secondary)}.n8n-chat-send-btn{padding:.6rem 1.2rem;border:none;border-radius:1.5rem;background:var(--n8n-chat-primary);color:#fff;font-size:1rem;font-family:inherit;cursor:pointer;transition:background-color .2s}.n8n-chat-send-btn:hover{background:var(--n8n-chat-primary-hover)}.n8n-chat-send-btn:disabled{opacity:.5;cursor:not-allowed}.n8n-chat.window-mode{position:fixed;bottom:20px;right:20px;width:var(--n8n-chat-window-width);height:var(--n8n-chat-window-height);border-radius:12px;box-shadow:0 4px 24px #00000026;z-index:9999}.n8n-chat.fullscreen-mode{position:absolute;top:0;left:0;right:0;bottom:0}.n8n-chat-bubble.animate-in{animation:n8n-chat-bubble-in .4s cubic-bezier(.34,1.56,.64,1) forwards}@keyframes n8n-chat-bubble-in{0%{opacity:0;transform:scale(.8)}to{opacity:1;transform:scale(1)}}@media (max-width: 480px){.n8n-chat.window-mode{bottom:0;right:0;left:0;width:100%;height:100%;border-radius:0}}
@@ -0,0 +1,8 @@
1
+ import './styles.css';
2
+ import type { ChatOptions, ChatInstance } from './types';
3
+ /**
4
+ * Creates a new chat instance
5
+ */
6
+ export declare function createChat(options: ChatOptions): ChatInstance;
7
+ export type { ChatOptions, ChatInstance, ThemeOptions, I18nOptions } from './types';
8
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,cAAc,CAAC;AACtB,OAAO,KAAK,EAAE,WAAW,EAAE,YAAY,EAAE,MAAM,SAAS,CAAC;AAgCzD;;GAEG;AACH,wBAAgB,UAAU,CAAC,OAAO,EAAE,WAAW,GAAG,YAAY,CAwT7D;AAGD,YAAY,EAAE,WAAW,EAAE,YAAY,EAAE,YAAY,EAAE,WAAW,EAAE,MAAM,SAAS,CAAC"}
@@ -0,0 +1,172 @@
1
+ /**
2
+ * Configuration options for n8n-chat-pretty
3
+ */
4
+ export interface ChatOptions {
5
+ /**
6
+ * The URL of your n8n webhook endpoint
7
+ * @required
8
+ */
9
+ webhookUrl: string;
10
+ /**
11
+ * Configuration for the webhook request
12
+ */
13
+ webhookConfig?: {
14
+ method?: 'GET' | 'POST';
15
+ headers?: Record<string, string>;
16
+ };
17
+ /**
18
+ * CSS selector for the target container
19
+ * @default '#n8n-chat'
20
+ */
21
+ target?: string;
22
+ /**
23
+ * Render mode
24
+ * @default 'fullscreen'
25
+ */
26
+ mode?: 'window' | 'fullscreen';
27
+ /**
28
+ * Initial greeting messages
29
+ */
30
+ initialMessages?: string[];
31
+ /**
32
+ * Key used for sending chat input to n8n
33
+ * @default 'chatInput'
34
+ */
35
+ chatInputKey?: string;
36
+ /**
37
+ * Key used for session ID
38
+ * @default 'sessionId'
39
+ */
40
+ chatSessionKey?: string;
41
+ /**
42
+ * Whether to load previous session
43
+ * @default true
44
+ */
45
+ loadPreviousSession?: boolean;
46
+ /**
47
+ * Additional metadata to send with each message
48
+ */
49
+ metadata?: Record<string, unknown>;
50
+ /**
51
+ * Theme configuration
52
+ */
53
+ theme?: ThemeOptions;
54
+ /**
55
+ * Internationalization strings
56
+ */
57
+ i18n?: I18nOptions;
58
+ /**
59
+ * Typing indicator settings
60
+ */
61
+ typingIndicator?: {
62
+ /**
63
+ * Milliseconds per character for typing delay
64
+ * @default 20
65
+ */
66
+ msPerChar?: number;
67
+ /**
68
+ * Base delay in milliseconds
69
+ * @default 300
70
+ */
71
+ baseDelay?: number;
72
+ };
73
+ /**
74
+ * Maximum characters per message bubble before splitting
75
+ * @default 200
76
+ */
77
+ maxBubbleLength?: number;
78
+ /**
79
+ * Enable bubble animations
80
+ * @default true
81
+ */
82
+ enableAnimations?: boolean;
83
+ }
84
+ export interface ThemeOptions {
85
+ /**
86
+ * Primary accent color (user messages, buttons)
87
+ * @default '#e74266'
88
+ */
89
+ primaryColor?: string;
90
+ /**
91
+ * Background color for bot messages
92
+ * @default 'rgba(206,206,206,.5)'
93
+ */
94
+ botMessageBackground?: string;
95
+ /**
96
+ * Background color for user messages
97
+ * @default '#e74266'
98
+ */
99
+ userMessageBackground?: string;
100
+ /**
101
+ * Main background color
102
+ * @default '#FFF'
103
+ */
104
+ backgroundColor?: string;
105
+ /**
106
+ * Main text color
107
+ * @default '#000'
108
+ */
109
+ textColor?: string;
110
+ /**
111
+ * Font family
112
+ * @default 'system-ui, -apple-system, sans-serif'
113
+ */
114
+ fontFamily?: string;
115
+ /**
116
+ * Border radius for bubbles
117
+ * @default '1.25rem'
118
+ */
119
+ borderRadius?: string;
120
+ }
121
+ export interface I18nOptions {
122
+ /**
123
+ * Placeholder text for input field
124
+ * @default 'Type your message...'
125
+ */
126
+ inputPlaceholder?: string;
127
+ /**
128
+ * Send button text
129
+ * @default 'Send'
130
+ */
131
+ sendButtonText?: string;
132
+ /**
133
+ * Error message for connection failures
134
+ * @default 'Connection error. Please try again.'
135
+ */
136
+ errorMessage?: string;
137
+ /**
138
+ * Fallback response when bot returns empty
139
+ * @default "Sorry, I couldn't process that."
140
+ */
141
+ fallbackResponse?: string;
142
+ }
143
+ /**
144
+ * Chat instance returned by createChat
145
+ */
146
+ export interface ChatInstance {
147
+ /**
148
+ * Send a message programmatically
149
+ */
150
+ sendMessage: (text: string) => Promise<void>;
151
+ /**
152
+ * Add a message to the chat (without sending to webhook)
153
+ */
154
+ addMessage: (text: string, position: 'left' | 'right') => void;
155
+ /**
156
+ * Clear all messages
157
+ */
158
+ clear: () => void;
159
+ /**
160
+ * Destroy the chat instance
161
+ */
162
+ destroy: () => void;
163
+ /**
164
+ * Get the current session ID
165
+ */
166
+ getSessionId: () => string;
167
+ /**
168
+ * Reset the session (creates new session ID)
169
+ */
170
+ resetSession: () => void;
171
+ }
172
+ //# sourceMappingURL=types.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../src/types.ts"],"names":[],"mappings":"AAAA;;GAEG;AACH,MAAM,WAAW,WAAW;IAC1B;;;OAGG;IACH,UAAU,EAAE,MAAM,CAAC;IAEnB;;OAEG;IACH,aAAa,CAAC,EAAE;QACd,MAAM,CAAC,EAAE,KAAK,GAAG,MAAM,CAAC;QACxB,OAAO,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;KAClC,CAAC;IAEF;;;OAGG;IACH,MAAM,CAAC,EAAE,MAAM,CAAC;IAEhB;;;OAGG;IACH,IAAI,CAAC,EAAE,QAAQ,GAAG,YAAY,CAAC;IAE/B;;OAEG;IACH,eAAe,CAAC,EAAE,MAAM,EAAE,CAAC;IAE3B;;;OAGG;IACH,YAAY,CAAC,EAAE,MAAM,CAAC;IAEtB;;;OAGG;IACH,cAAc,CAAC,EAAE,MAAM,CAAC;IAExB;;;OAGG;IACH,mBAAmB,CAAC,EAAE,OAAO,CAAC;IAE9B;;OAEG;IACH,QAAQ,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;IAEnC;;OAEG;IACH,KAAK,CAAC,EAAE,YAAY,CAAC;IAErB;;OAEG;IACH,IAAI,CAAC,EAAE,WAAW,CAAC;IAEnB;;OAEG;IACH,eAAe,CAAC,EAAE;QAChB;;;WAGG;QACH,SAAS,CAAC,EAAE,MAAM,CAAC;QACnB;;;WAGG;QACH,SAAS,CAAC,EAAE,MAAM,CAAC;KACpB,CAAC;IAEF;;;OAGG;IACH,eAAe,CAAC,EAAE,MAAM,CAAC;IAEzB;;;OAGG;IACH,gBAAgB,CAAC,EAAE,OAAO,CAAC;CAC5B;AAED,MAAM,WAAW,YAAY;IAC3B;;;OAGG;IACH,YAAY,CAAC,EAAE,MAAM,CAAC;IAEtB;;;OAGG;IACH,oBAAoB,CAAC,EAAE,MAAM,CAAC;IAE9B;;;OAGG;IACH,qBAAqB,CAAC,EAAE,MAAM,CAAC;IAE/B;;;OAGG;IACH,eAAe,CAAC,EAAE,MAAM,CAAC;IAEzB;;;OAGG;IACH,SAAS,CAAC,EAAE,MAAM,CAAC;IAEnB;;;OAGG;IACH,UAAU,CAAC,EAAE,MAAM,CAAC;IAEpB;;;OAGG;IACH,YAAY,CAAC,EAAE,MAAM,CAAC;CACvB;AAED,MAAM,WAAW,WAAW;IAC1B;;;OAGG;IACH,gBAAgB,CAAC,EAAE,MAAM,CAAC;IAE1B;;;OAGG;IACH,cAAc,CAAC,EAAE,MAAM,CAAC;IAExB;;;OAGG;IACH,YAAY,CAAC,EAAE,MAAM,CAAC;IAEtB;;;OAGG;IACH,gBAAgB,CAAC,EAAE,MAAM,CAAC;CAC3B;AAED;;GAEG;AACH,MAAM,WAAW,YAAY;IAC3B;;OAEG;IACH,WAAW,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,OAAO,CAAC,IAAI,CAAC,CAAC;IAE7C;;OAEG;IACH,UAAU,EAAE,CAAC,IAAI,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,GAAG,OAAO,KAAK,IAAI,CAAC;IAE/D;;OAEG;IACH,KAAK,EAAE,MAAM,IAAI,CAAC;IAElB;;OAEG;IACH,OAAO,EAAE,MAAM,IAAI,CAAC;IAEpB;;OAEG;IACH,YAAY,EAAE,MAAM,MAAM,CAAC;IAE3B;;OAEG;IACH,YAAY,EAAE,MAAM,IAAI,CAAC;CAC1B"}
package/package.json ADDED
@@ -0,0 +1,50 @@
1
+ {
2
+ "name": "n8n-chat-pretty",
3
+ "version": "1.0.0",
4
+ "description": "A beautiful, mobile-friendly chat widget for n8n workflows with conversational bubble animations",
5
+ "main": "dist/chat.umd.js",
6
+ "module": "dist/chat.es.js",
7
+ "types": "dist/types/index.d.ts",
8
+ "exports": {
9
+ ".": {
10
+ "import": "./dist/chat.es.js",
11
+ "require": "./dist/chat.umd.js",
12
+ "types": "./dist/types/index.d.ts"
13
+ },
14
+ "./style.css": "./dist/style.css"
15
+ },
16
+ "files": [
17
+ "dist"
18
+ ],
19
+ "scripts": {
20
+ "dev": "vite",
21
+ "build": "vite build && tsc --emitDeclarationOnly --outDir dist/types",
22
+ "preview": "vite preview",
23
+ "prepublishOnly": "npm run build"
24
+ },
25
+ "keywords": [
26
+ "n8n",
27
+ "chat",
28
+ "chatbot",
29
+ "ai",
30
+ "widget",
31
+ "webhook",
32
+ "conversational",
33
+ "bubble",
34
+ "animation"
35
+ ],
36
+ "author": "baufer",
37
+ "license": "MIT",
38
+ "repository": {
39
+ "type": "git",
40
+ "url": "git+https://github.com/baufergroup/n8n-chat-pretty.git"
41
+ },
42
+ "homepage": "https://github.com/baufergroup/n8n-chat-pretty#readme",
43
+ "bugs": {
44
+ "url": "https://github.com/baufergroup/n8n-chat-pretty/issues"
45
+ },
46
+ "devDependencies": {
47
+ "typescript": "^5.3.0",
48
+ "vite": "^5.0.0"
49
+ }
50
+ }