rushchatbot 1.7.1 → 1.7.2
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/README.md +1 -1
- package/package/README.md +1 -1
- package/package/package.json +1 -1
- package/package/src/client/ChatWidget.tsx +10 -0
- package/package/src/vanilla/index.ts +115 -30
- package/package/src/vue/ChatWidget.vue +2 -1
- package/package/src/vue2/ChatWidget.vue +2 -1
- package/package.json +1 -1
- package/server/data/analytics.json +36 -0
package/README.md
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# RushChatbot
|
|
2
2
|
|
|
3
|
-
**Version:** 1.7.
|
|
3
|
+
**Version:** 1.7.2
|
|
4
4
|
|
|
5
5
|
Embeddable hybrid chatbot plugin that combines a click-based FAQ tree with a free-text query engine backed by n8n. Ships as an npm package with a Node.js/Express server and widgets for React, Vue 3, and Vanilla JS.
|
|
6
6
|
|
package/package/README.md
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# RushChatbot
|
|
2
2
|
|
|
3
|
-
**Version:** 1.7.
|
|
3
|
+
**Version:** 1.7.2
|
|
4
4
|
|
|
5
5
|
Embeddable hybrid chatbot plugin that combines a click-based FAQ tree with a free-text query engine backed by n8n. Ships as an npm package with a Node.js/Express server and widgets for React, Vue 3, and Vanilla JS.
|
|
6
6
|
|
package/package/package.json
CHANGED
|
@@ -142,6 +142,16 @@ export function ChatWidget({ apiBase, width = 380, height = 580 }: ChatWidgetPro
|
|
|
142
142
|
</div>
|
|
143
143
|
)}
|
|
144
144
|
|
|
145
|
+
{!state.messages.some(m => m.isEndChat) && (
|
|
146
|
+
<button onClick={() => chat.endChat()} style={{
|
|
147
|
+
textAlign: 'left', padding: '8px 12px', borderRadius: 8,
|
|
148
|
+
border: '1px solid #EF4444', background: 'transparent',
|
|
149
|
+
color: '#EF4444', cursor: 'pointer', fontSize: 14, marginTop: 4, width: '100%',
|
|
150
|
+
}}>
|
|
151
|
+
👋 I'm done, thanks!
|
|
152
|
+
</button>
|
|
153
|
+
)}
|
|
154
|
+
|
|
145
155
|
<div ref={bottomRef} />
|
|
146
156
|
</div>
|
|
147
157
|
|
|
@@ -74,11 +74,19 @@ class RushChatbotElement extends HTMLElement {
|
|
|
74
74
|
private state: RushChatbotState | null = null
|
|
75
75
|
private showScrollBtn = false
|
|
76
76
|
private resizeObserver: ResizeObserver | null = null
|
|
77
|
+
private isOpen = false
|
|
78
|
+
private btnX = -1
|
|
79
|
+
private btnY = -1
|
|
80
|
+
private dragging = false
|
|
81
|
+
private dragOffX = 0
|
|
82
|
+
private dragOffY = 0
|
|
83
|
+
private dragMoved = false
|
|
77
84
|
|
|
78
85
|
static get observedAttributes() { return OBSERVED }
|
|
79
86
|
|
|
80
87
|
connectedCallback() {
|
|
81
88
|
this.shadow = this.attachShadow({ mode: 'open' })
|
|
89
|
+
this.isOpen = this.readSettings().autoOpen ?? false
|
|
82
90
|
this.chat = new RushChatbot({ apiBase: this.getAttribute('api-base') ?? undefined, settings: this.readSettings() })
|
|
83
91
|
this.chat.subscribe(state => { this.state = state; this.render() })
|
|
84
92
|
this.chat.init()
|
|
@@ -128,23 +136,35 @@ class RushChatbotElement extends HTMLElement {
|
|
|
128
136
|
}
|
|
129
137
|
}
|
|
130
138
|
|
|
131
|
-
private
|
|
132
|
-
const
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
139
|
+
private initBtnPos() {
|
|
140
|
+
const pos = this.settings.position ?? 'bottom-right'
|
|
141
|
+
const margin = 24
|
|
142
|
+
const btnSize = 56
|
|
143
|
+
if (this.btnX === -1) {
|
|
144
|
+
this.btnX = pos === 'bottom-left'
|
|
145
|
+
? margin
|
|
146
|
+
: window.innerWidth - btnSize - margin
|
|
147
|
+
this.btnY = window.innerHeight - btnSize - margin
|
|
148
|
+
}
|
|
138
149
|
}
|
|
139
150
|
|
|
140
151
|
private render() {
|
|
141
152
|
if (!this.state || !this.state.config) return
|
|
142
153
|
const { theme, state, settings } = this
|
|
143
154
|
|
|
155
|
+
this.initBtnPos()
|
|
156
|
+
|
|
144
157
|
const borderRadius = settings.borderRadius ?? 16
|
|
145
158
|
const shadow = settings.showShadow !== false ? '0 8px 32px rgba(0,0,0,0.15)' : 'none'
|
|
146
159
|
const placeholder = settings.placeholder ?? 'Type a message...'
|
|
147
160
|
const sendLabel = settings.sendLabel ?? 'Send'
|
|
161
|
+
const chatW = this.getAttribute('width') ? Number(this.getAttribute('width')) : 380
|
|
162
|
+
const chatH = this.getAttribute('height') ? Number(this.getAttribute('height')) : 580
|
|
163
|
+
const btnSize = 56
|
|
164
|
+
|
|
165
|
+
// Clamp chat box so it stays in viewport
|
|
166
|
+
const boxLeft = Math.min(Math.max(this.btnX + btnSize / 2 - chatW / 2, 8), window.innerWidth - chatW - 8)
|
|
167
|
+
const boxBottom = window.innerHeight - this.btnY + 12
|
|
148
168
|
|
|
149
169
|
const msgsEl = this.shadow.querySelector('.messages') as HTMLElement | null
|
|
150
170
|
const wasAtBottom = msgsEl ? msgsEl.scrollHeight - msgsEl.scrollTop - msgsEl.clientHeight < 60 : true
|
|
@@ -152,32 +172,46 @@ class RushChatbotElement extends HTMLElement {
|
|
|
152
172
|
// Build static shell with no user data in innerHTML
|
|
153
173
|
const shellHtml = `
|
|
154
174
|
<style>${STYLES}</style>
|
|
155
|
-
<div
|
|
156
|
-
<div class="
|
|
157
|
-
<div class="header
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
175
|
+
<div id="chat-box" style="display:${this.isOpen ? 'flex' : 'none'};position:fixed;left:${boxLeft}px;bottom:${boxBottom}px;z-index:2147483646;flex-direction:column;width:${chatW}px;height:${chatH}px">
|
|
176
|
+
<div class="container" id="container">
|
|
177
|
+
<div class="header" id="header">
|
|
178
|
+
<div style="display:flex;align-items:center;justify-content:space-between">
|
|
179
|
+
<div>
|
|
180
|
+
<div class="header-title" id="header-title"></div>
|
|
181
|
+
<div class="header-subtitle" id="header-subtitle"></div>
|
|
182
|
+
</div>
|
|
183
|
+
<button id="close-btn" style="background:none;border:none;color:#fff;cursor:pointer;font-size:20px;line-height:1;padding:0 4px">×</button>
|
|
184
|
+
</div>
|
|
185
|
+
</div>
|
|
186
|
+
<div class="messages-wrap">
|
|
187
|
+
<div class="messages" id="msgs">
|
|
188
|
+
<div id="msgs-content"></div>
|
|
189
|
+
<div id="bottom"></div>
|
|
190
|
+
</div>
|
|
164
191
|
</div>
|
|
192
|
+
${settings.hideInput ? '' : `
|
|
193
|
+
<form class="input-row" id="form">
|
|
194
|
+
<input id="input" />
|
|
195
|
+
<button type="submit" id="send-btn"></button>
|
|
196
|
+
</form>`}
|
|
165
197
|
</div>
|
|
166
|
-
${settings.hideInput ? '' : `
|
|
167
|
-
<form class="input-row" id="form">
|
|
168
|
-
<input id="input" />
|
|
169
|
-
<button type="submit" id="send-btn"></button>
|
|
170
|
-
</form>`}
|
|
171
198
|
</div>
|
|
199
|
+
<button id="fab" style="position:fixed;left:${this.btnX}px;top:${this.btnY}px;z-index:2147483647;width:${btnSize}px;height:${btnSize}px;border-radius:50%;border:none;cursor:grab;display:flex;align-items:center;justify-content:center;box-shadow:0 4px 16px rgba(0,0,0,0.25);touch-action:none">
|
|
200
|
+
<svg id="fab-icon-chat" width="26" height="26" viewBox="0 0 24 24" fill="none" style="display:${this.isOpen ? 'none' : 'block'}"><path d="M20 2H4a2 2 0 0 0-2 2v18l4-4h14a2 2 0 0 0 2-2V4a2 2 0 0 0-2-2z" fill="#fff"/></svg>
|
|
201
|
+
<svg id="fab-icon-close" width="22" height="22" viewBox="0 0 24 24" fill="none" style="display:${this.isOpen ? 'block' : 'none'}"><path d="M18 6L6 18M6 6l12 12" stroke="#fff" stroke-width="2.5" stroke-linecap="round"/></svg>
|
|
202
|
+
</button>
|
|
172
203
|
`
|
|
173
204
|
this.shadow.innerHTML = shellHtml
|
|
174
205
|
|
|
175
206
|
// Populate dynamic values via DOM (safe from XSS)
|
|
176
207
|
const container = this.shadow.getElementById('container') as HTMLElement
|
|
177
|
-
container.style.cssText = `background:${escapeHtml(theme.backgroundColor)};color:${escapeHtml(theme.textColor)};width
|
|
208
|
+
container.style.cssText = `background:${escapeHtml(theme.backgroundColor)};color:${escapeHtml(theme.textColor)};width:100%;height:100%;font-family:${escapeHtml(theme.fontFamily)};border-radius:${borderRadius}px;box-shadow:${shadow};display:flex;flex-direction:column;overflow:hidden`
|
|
209
|
+
|
|
210
|
+
const fab = this.shadow.getElementById('fab') as HTMLElement
|
|
211
|
+
fab.style.background = escapeHtml(theme.primaryColor)
|
|
178
212
|
|
|
179
213
|
const header = this.shadow.getElementById('header') as HTMLElement
|
|
180
|
-
header.style.
|
|
214
|
+
header.style.cssText = `background:${escapeHtml(theme.primaryColor)};padding:16px 20px;color:#fff`
|
|
181
215
|
;(this.shadow.getElementById('header-title') as HTMLElement).textContent = state.config!.branding.title
|
|
182
216
|
;(this.shadow.getElementById('header-subtitle') as HTMLElement).textContent = state.config!.branding.subtitle
|
|
183
217
|
|
|
@@ -213,16 +247,17 @@ class RushChatbotElement extends HTMLElement {
|
|
|
213
247
|
btn.textContent = q.question
|
|
214
248
|
mainList.appendChild(btn)
|
|
215
249
|
})
|
|
216
|
-
if (!settings.hideEndChat) {
|
|
217
|
-
const endBtn = document.createElement('button')
|
|
218
|
-
endBtn.className = 'end-btn'
|
|
219
|
-
endBtn.dataset.action = 'endchat'
|
|
220
|
-
endBtn.textContent = "\uD83D\uDC4B I'm done, thanks!"
|
|
221
|
-
mainList.appendChild(endBtn)
|
|
222
|
-
}
|
|
223
250
|
msgsContent.appendChild(mainList)
|
|
224
251
|
}
|
|
225
252
|
|
|
253
|
+
if (!settings.hideEndChat && !state.messages.some(m => m.isEndChat)) {
|
|
254
|
+
const endBtn = document.createElement('button')
|
|
255
|
+
endBtn.className = 'end-btn'
|
|
256
|
+
endBtn.dataset.action = 'endchat'
|
|
257
|
+
endBtn.textContent = "\uD83D\uDC4B I'm done, thanks!"
|
|
258
|
+
msgsContent.appendChild(endBtn)
|
|
259
|
+
}
|
|
260
|
+
|
|
226
261
|
if (this.showScrollBtn) {
|
|
227
262
|
const scrollBtn = document.createElement('button')
|
|
228
263
|
scrollBtn.className = 'scroll-btn'
|
|
@@ -245,6 +280,7 @@ class RushChatbotElement extends HTMLElement {
|
|
|
245
280
|
}
|
|
246
281
|
|
|
247
282
|
const newMsgsEl = this.shadow.getElementById('msgs') as HTMLElement
|
|
283
|
+
if (!this.isOpen) return
|
|
248
284
|
if (wasAtBottom) {
|
|
249
285
|
newMsgsEl.scrollTop = newMsgsEl.scrollHeight
|
|
250
286
|
this.showScrollBtn = false
|
|
@@ -265,6 +301,55 @@ class RushChatbotElement extends HTMLElement {
|
|
|
265
301
|
}
|
|
266
302
|
|
|
267
303
|
this.bindEvents()
|
|
304
|
+
this.bindDrag()
|
|
305
|
+
}
|
|
306
|
+
|
|
307
|
+
private bindDrag() {
|
|
308
|
+
const fab = this.shadow.getElementById('fab') as HTMLElement
|
|
309
|
+
if (!fab) return
|
|
310
|
+
|
|
311
|
+
fab.addEventListener('pointerdown', (e: PointerEvent) => {
|
|
312
|
+
this.dragging = true
|
|
313
|
+
this.dragMoved = false
|
|
314
|
+
this.dragOffX = e.clientX - this.btnX
|
|
315
|
+
this.dragOffY = e.clientY - this.btnY
|
|
316
|
+
fab.setPointerCapture(e.pointerId)
|
|
317
|
+
fab.style.cursor = 'grabbing'
|
|
318
|
+
})
|
|
319
|
+
|
|
320
|
+
fab.addEventListener('pointermove', (e: PointerEvent) => {
|
|
321
|
+
if (!this.dragging) return
|
|
322
|
+
const newX = e.clientX - this.dragOffX
|
|
323
|
+
const newY = e.clientY - this.dragOffY
|
|
324
|
+
if (Math.abs(newX - this.btnX) > 4 || Math.abs(newY - this.btnY) > 4) this.dragMoved = true
|
|
325
|
+
this.btnX = Math.min(Math.max(newX, 0), window.innerWidth - 56)
|
|
326
|
+
this.btnY = Math.min(Math.max(newY, 0), window.innerHeight - 56)
|
|
327
|
+
fab.style.left = `${this.btnX}px`
|
|
328
|
+
fab.style.top = `${this.btnY}px`
|
|
329
|
+
const chatBox = this.shadow.getElementById('chat-box') as HTMLElement
|
|
330
|
+
if (chatBox && this.isOpen) {
|
|
331
|
+
const chatW = chatBox.offsetWidth || 380
|
|
332
|
+
const chatH = chatBox.offsetHeight || 580
|
|
333
|
+
const boxLeft = Math.min(Math.max(this.btnX + 28 - chatW / 2, 8), window.innerWidth - chatW - 8)
|
|
334
|
+
const boxBottom = window.innerHeight - this.btnY + 12
|
|
335
|
+
chatBox.style.left = `${boxLeft}px`
|
|
336
|
+
chatBox.style.bottom = `${boxBottom}px`
|
|
337
|
+
}
|
|
338
|
+
})
|
|
339
|
+
|
|
340
|
+
fab.addEventListener('pointerup', () => {
|
|
341
|
+
if (!this.dragMoved) {
|
|
342
|
+
this.isOpen = !this.isOpen
|
|
343
|
+
this.render()
|
|
344
|
+
}
|
|
345
|
+
this.dragging = false
|
|
346
|
+
fab.style.cursor = 'grab'
|
|
347
|
+
})
|
|
348
|
+
|
|
349
|
+
this.shadow.getElementById('close-btn')?.addEventListener('click', () => {
|
|
350
|
+
this.isOpen = false
|
|
351
|
+
this.render()
|
|
352
|
+
})
|
|
268
353
|
}
|
|
269
354
|
|
|
270
355
|
private bindEvents() {
|
|
@@ -42,9 +42,10 @@
|
|
|
42
42
|
:style="mainBtnStyle">
|
|
43
43
|
{{ q.question }}
|
|
44
44
|
</button>
|
|
45
|
-
<button v-if="!hideEndChat" @click="chat.endChat()" :style="endBtnStyle">👋 I'm done, thanks!</button>
|
|
46
45
|
</div>
|
|
47
46
|
|
|
47
|
+
<button v-if="!hideEndChat && !state.messages.some(m => m.isEndChat)" @click="chat.endChat()" :style="endBtnStyle">👋 I'm done, thanks!</button>
|
|
48
|
+
|
|
48
49
|
<div ref="bottom" />
|
|
49
50
|
</div>
|
|
50
51
|
|
|
@@ -44,9 +44,10 @@
|
|
|
44
44
|
:style="mainBtnStyle">
|
|
45
45
|
{{ q.question }}
|
|
46
46
|
</button>
|
|
47
|
-
<button v-if="!hideEndChat" @click="chat.endChat()" :style="endBtnStyle">👋 I'm done, thanks!</button>
|
|
48
47
|
</div>
|
|
49
48
|
|
|
49
|
+
<button v-if="!hideEndChat && !state.messages.some(function(m) { return m.isEndChat })" @click="chat.endChat()" :style="endBtnStyle">👋 I'm done, thanks!</button>
|
|
50
|
+
|
|
50
51
|
<div ref="bottom" />
|
|
51
52
|
</div>
|
|
52
53
|
|
package/package.json
CHANGED
|
@@ -827,5 +827,41 @@
|
|
|
827
827
|
"questionId": "q8c",
|
|
828
828
|
"timestamp": "2026-09-25T07:32:01.577Z",
|
|
829
829
|
"type": "click"
|
|
830
|
+
},
|
|
831
|
+
{
|
|
832
|
+
"sessionId": "sess_mugn4uve_9yrh57",
|
|
833
|
+
"questionId": "q8",
|
|
834
|
+
"timestamp": "2026-09-25T08:03:16.219Z",
|
|
835
|
+
"type": "click"
|
|
836
|
+
},
|
|
837
|
+
{
|
|
838
|
+
"sessionId": "sess_mugn4uve_9yrh57",
|
|
839
|
+
"questionId": "q8c",
|
|
840
|
+
"timestamp": "2026-09-25T08:03:18.364Z",
|
|
841
|
+
"type": "click"
|
|
842
|
+
},
|
|
843
|
+
{
|
|
844
|
+
"sessionId": "sess_mugn4uve_9yrh57",
|
|
845
|
+
"questionId": "q8b",
|
|
846
|
+
"timestamp": "2026-09-25T08:03:21.319Z",
|
|
847
|
+
"type": "click"
|
|
848
|
+
},
|
|
849
|
+
{
|
|
850
|
+
"sessionId": "sess_mugn4uve_9yrh57",
|
|
851
|
+
"questionId": "q8a",
|
|
852
|
+
"timestamp": "2026-09-25T08:03:24.033Z",
|
|
853
|
+
"type": "click"
|
|
854
|
+
},
|
|
855
|
+
{
|
|
856
|
+
"sessionId": "sess_mugn4uve_9yrh57",
|
|
857
|
+
"questionId": "q1",
|
|
858
|
+
"timestamp": "2026-09-25T08:03:29.705Z",
|
|
859
|
+
"type": "click"
|
|
860
|
+
},
|
|
861
|
+
{
|
|
862
|
+
"sessionId": "sess_mugn4uve_9yrh57",
|
|
863
|
+
"questionId": "q1b",
|
|
864
|
+
"timestamp": "2026-09-25T08:03:33.517Z",
|
|
865
|
+
"type": "click"
|
|
830
866
|
}
|
|
831
867
|
]
|