@wabbit-dashboard/embed 1.1.0 → 1.1.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 +30 -0
- package/dist/wabbit-embed.cjs.js +232 -115
- package/dist/wabbit-embed.cjs.js.map +1 -1
- package/dist/wabbit-embed.d.ts +49 -42
- package/dist/wabbit-embed.esm.js +232 -115
- package/dist/wabbit-embed.esm.js.map +1 -1
- package/dist/wabbit-embed.umd.js +232 -115
- package/dist/wabbit-embed.umd.js.map +1 -1
- package/dist/wabbit-embed.umd.min.js +1 -1
- package/dist/wabbit-embed.umd.min.js.map +1 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -127,6 +127,34 @@ Wabbit.init({
|
|
|
127
127
|
});
|
|
128
128
|
```
|
|
129
129
|
|
|
130
|
+
### Session Persistence
|
|
131
|
+
|
|
132
|
+
Control whether chat sessions persist across browser sessions:
|
|
133
|
+
|
|
134
|
+
```javascript
|
|
135
|
+
Wabbit.init({
|
|
136
|
+
apiKey: 'pk_live_xxx',
|
|
137
|
+
persistSession: true, // Default: sessions persist (uses localStorage)
|
|
138
|
+
chat: {
|
|
139
|
+
enabled: true,
|
|
140
|
+
collectionId: 'abc123'
|
|
141
|
+
}
|
|
142
|
+
});
|
|
143
|
+
```
|
|
144
|
+
|
|
145
|
+
**Options:**
|
|
146
|
+
- `persistSession: true` (default) - Sessions persist across browser close/reopen using **localStorage**. Users can continue conversations after reopening the browser.
|
|
147
|
+
- `persistSession: false` - Sessions are cleared when browser closes using **sessionStorage**. Users start fresh each browser session, but conversations persist during page refreshes.
|
|
148
|
+
|
|
149
|
+
**Use Cases:**
|
|
150
|
+
- **Persistent (true)**: Customer support, ongoing consultations, learning platforms
|
|
151
|
+
- **Ephemeral (false)**: Sensitive data, temporary assistance, demo sites
|
|
152
|
+
|
|
153
|
+
**Technical Details:**
|
|
154
|
+
- `true`: Session ID stored in localStorage, survives browser restart
|
|
155
|
+
- `false`: Session ID stored in sessionStorage, cleared on browser close but survives page refresh
|
|
156
|
+
- Message history fetched from server on reconnect (not stored locally)
|
|
157
|
+
|
|
130
158
|
## Testing
|
|
131
159
|
|
|
132
160
|
### Option 1: Copy Embed Code from Dashboard (Recommended)
|
|
@@ -266,6 +294,7 @@ Initialize the Wabbit SDK.
|
|
|
266
294
|
**Parameters:**
|
|
267
295
|
- `config.apiKey` (string, required): Your Wabbit API key
|
|
268
296
|
- `config.apiUrl` (string, optional): API base URL (default: auto-detected)
|
|
297
|
+
- `config.persistSession` (boolean, optional): Whether to persist sessions across browser sessions (default: `true`)
|
|
269
298
|
- `config.chat` (ChatConfig, optional): Chat widget configuration
|
|
270
299
|
- `config.forms` (FormConfig, optional): Form widget configuration
|
|
271
300
|
- `config.emailCapture` (EmailCaptureConfig, optional): Email capture configuration
|
|
@@ -274,6 +303,7 @@ Initialize the Wabbit SDK.
|
|
|
274
303
|
```javascript
|
|
275
304
|
Wabbit.init({
|
|
276
305
|
apiKey: 'pk_live_xxx',
|
|
306
|
+
persistSession: true, // Optional: default is true
|
|
277
307
|
chat: { enabled: true, collectionId: 'abc123' }
|
|
278
308
|
});
|
|
279
309
|
```
|
package/dist/wabbit-embed.cjs.js
CHANGED
|
@@ -120,6 +120,143 @@ function mergeConfig(config) {
|
|
|
120
120
|
};
|
|
121
121
|
}
|
|
122
122
|
|
|
123
|
+
/**
|
|
124
|
+
* localStorage wrapper with type safety
|
|
125
|
+
*/
|
|
126
|
+
/**
|
|
127
|
+
* Memory storage implementation (clears on page reload)
|
|
128
|
+
*/
|
|
129
|
+
class MemoryStorage {
|
|
130
|
+
constructor() {
|
|
131
|
+
this.store = new Map();
|
|
132
|
+
}
|
|
133
|
+
getItem(key) {
|
|
134
|
+
return this.store.get(key) ?? null;
|
|
135
|
+
}
|
|
136
|
+
setItem(key, value) {
|
|
137
|
+
this.store.set(key, value);
|
|
138
|
+
}
|
|
139
|
+
removeItem(key) {
|
|
140
|
+
this.store.delete(key);
|
|
141
|
+
}
|
|
142
|
+
clear() {
|
|
143
|
+
this.store.clear();
|
|
144
|
+
}
|
|
145
|
+
get length() {
|
|
146
|
+
return this.store.size;
|
|
147
|
+
}
|
|
148
|
+
key(index) {
|
|
149
|
+
const keys = Array.from(this.store.keys());
|
|
150
|
+
return keys[index] ?? null;
|
|
151
|
+
}
|
|
152
|
+
}
|
|
153
|
+
/**
|
|
154
|
+
* Safe storage wrapper
|
|
155
|
+
*/
|
|
156
|
+
class SafeStorage {
|
|
157
|
+
constructor(storage = localStorage, prefix = 'wabbit_') {
|
|
158
|
+
this.storage = storage;
|
|
159
|
+
this.prefix = prefix;
|
|
160
|
+
}
|
|
161
|
+
/**
|
|
162
|
+
* Get item from storage
|
|
163
|
+
*/
|
|
164
|
+
get(key) {
|
|
165
|
+
try {
|
|
166
|
+
const item = this.storage.getItem(this.prefix + key);
|
|
167
|
+
if (item === null) {
|
|
168
|
+
return null;
|
|
169
|
+
}
|
|
170
|
+
return JSON.parse(item);
|
|
171
|
+
}
|
|
172
|
+
catch (error) {
|
|
173
|
+
console.error(`[Wabbit] Failed to get storage item "${key}":`, error);
|
|
174
|
+
return null;
|
|
175
|
+
}
|
|
176
|
+
}
|
|
177
|
+
/**
|
|
178
|
+
* Set item in storage
|
|
179
|
+
*/
|
|
180
|
+
set(key, value) {
|
|
181
|
+
try {
|
|
182
|
+
this.storage.setItem(this.prefix + key, JSON.stringify(value));
|
|
183
|
+
return true;
|
|
184
|
+
}
|
|
185
|
+
catch (error) {
|
|
186
|
+
console.error(`[Wabbit] Failed to set storage item "${key}":`, error);
|
|
187
|
+
return false;
|
|
188
|
+
}
|
|
189
|
+
}
|
|
190
|
+
/**
|
|
191
|
+
* Remove item from storage
|
|
192
|
+
*/
|
|
193
|
+
remove(key) {
|
|
194
|
+
this.storage.removeItem(this.prefix + key);
|
|
195
|
+
}
|
|
196
|
+
/**
|
|
197
|
+
* Clear all items with prefix
|
|
198
|
+
*/
|
|
199
|
+
clear() {
|
|
200
|
+
if (this.storage.length !== undefined && this.storage.key) {
|
|
201
|
+
const keys = [];
|
|
202
|
+
for (let i = 0; i < this.storage.length; i++) {
|
|
203
|
+
const key = this.storage.key(i);
|
|
204
|
+
if (key && key.startsWith(this.prefix)) {
|
|
205
|
+
keys.push(key);
|
|
206
|
+
}
|
|
207
|
+
}
|
|
208
|
+
keys.forEach((key) => this.storage.removeItem(key));
|
|
209
|
+
}
|
|
210
|
+
else {
|
|
211
|
+
// Fallback: try to clear common keys
|
|
212
|
+
const commonKeys = ['session_id', 'email_capture_dismissed'];
|
|
213
|
+
commonKeys.forEach((key) => this.remove(key));
|
|
214
|
+
}
|
|
215
|
+
}
|
|
216
|
+
}
|
|
217
|
+
// Export singleton instance (for backward compatibility)
|
|
218
|
+
const storage = new SafeStorage();
|
|
219
|
+
/**
|
|
220
|
+
* Create a storage instance with the specified backend
|
|
221
|
+
*
|
|
222
|
+
* @param persistSession - Whether to persist sessions across browser sessions
|
|
223
|
+
* @returns SafeStorage instance with appropriate backend
|
|
224
|
+
*/
|
|
225
|
+
function createStorage(persistSession = true) {
|
|
226
|
+
let storageBackend;
|
|
227
|
+
if (persistSession) {
|
|
228
|
+
// Use localStorage (persists across browser sessions)
|
|
229
|
+
storageBackend = typeof window !== 'undefined' ? window.localStorage : new MemoryStorage();
|
|
230
|
+
}
|
|
231
|
+
else {
|
|
232
|
+
// Use sessionStorage (clears when browser closes)
|
|
233
|
+
storageBackend = typeof window !== 'undefined' ? window.sessionStorage : new MemoryStorage();
|
|
234
|
+
}
|
|
235
|
+
return new SafeStorage(storageBackend);
|
|
236
|
+
}
|
|
237
|
+
/**
|
|
238
|
+
* Get item from storage (simple string getter)
|
|
239
|
+
*/
|
|
240
|
+
function getStorageItem(key) {
|
|
241
|
+
try {
|
|
242
|
+
return localStorage.getItem('wabbit_' + key);
|
|
243
|
+
}
|
|
244
|
+
catch {
|
|
245
|
+
return null;
|
|
246
|
+
}
|
|
247
|
+
}
|
|
248
|
+
/**
|
|
249
|
+
* Set item in storage (simple string setter)
|
|
250
|
+
*/
|
|
251
|
+
function setStorageItem(key, value) {
|
|
252
|
+
try {
|
|
253
|
+
localStorage.setItem('wabbit_' + key, value);
|
|
254
|
+
}
|
|
255
|
+
catch (error) {
|
|
256
|
+
console.error(`[Wabbit] Failed to set storage item "${key}":`, error);
|
|
257
|
+
}
|
|
258
|
+
}
|
|
259
|
+
|
|
123
260
|
/**
|
|
124
261
|
* Main Wabbit SDK class
|
|
125
262
|
*
|
|
@@ -136,6 +273,8 @@ class Wabbit {
|
|
|
136
273
|
this.formsWidget = null; // FormWidget | null
|
|
137
274
|
this.emailCaptureWidget = null; // EmailCapture | null
|
|
138
275
|
this.config = config;
|
|
276
|
+
// Create storage instance with appropriate backend based on persistSession
|
|
277
|
+
this.storage = createStorage(config.persistSession ?? true);
|
|
139
278
|
}
|
|
140
279
|
/**
|
|
141
280
|
* Initialize the Wabbit SDK
|
|
@@ -215,7 +354,7 @@ class Wabbit {
|
|
|
215
354
|
}
|
|
216
355
|
}
|
|
217
356
|
};
|
|
218
|
-
const chatWidget = new ChatWidget(chatConfig);
|
|
357
|
+
const chatWidget = new ChatWidget(chatConfig, Wabbit.instance.storage);
|
|
219
358
|
chatWidget.init();
|
|
220
359
|
Wabbit.instance.chatWidget = chatWidget;
|
|
221
360
|
});
|
|
@@ -538,105 +677,13 @@ class Wabbit {
|
|
|
538
677
|
}
|
|
539
678
|
Wabbit.instance = null;
|
|
540
679
|
|
|
541
|
-
/**
|
|
542
|
-
* localStorage wrapper with type safety
|
|
543
|
-
*/
|
|
544
|
-
/**
|
|
545
|
-
* Safe storage wrapper
|
|
546
|
-
*/
|
|
547
|
-
class SafeStorage {
|
|
548
|
-
constructor(storage = localStorage, prefix = 'wabbit_') {
|
|
549
|
-
this.storage = storage;
|
|
550
|
-
this.prefix = prefix;
|
|
551
|
-
}
|
|
552
|
-
/**
|
|
553
|
-
* Get item from storage
|
|
554
|
-
*/
|
|
555
|
-
get(key) {
|
|
556
|
-
try {
|
|
557
|
-
const item = this.storage.getItem(this.prefix + key);
|
|
558
|
-
if (item === null) {
|
|
559
|
-
return null;
|
|
560
|
-
}
|
|
561
|
-
return JSON.parse(item);
|
|
562
|
-
}
|
|
563
|
-
catch (error) {
|
|
564
|
-
console.error(`[Wabbit] Failed to get storage item "${key}":`, error);
|
|
565
|
-
return null;
|
|
566
|
-
}
|
|
567
|
-
}
|
|
568
|
-
/**
|
|
569
|
-
* Set item in storage
|
|
570
|
-
*/
|
|
571
|
-
set(key, value) {
|
|
572
|
-
try {
|
|
573
|
-
this.storage.setItem(this.prefix + key, JSON.stringify(value));
|
|
574
|
-
return true;
|
|
575
|
-
}
|
|
576
|
-
catch (error) {
|
|
577
|
-
console.error(`[Wabbit] Failed to set storage item "${key}":`, error);
|
|
578
|
-
return false;
|
|
579
|
-
}
|
|
580
|
-
}
|
|
581
|
-
/**
|
|
582
|
-
* Remove item from storage
|
|
583
|
-
*/
|
|
584
|
-
remove(key) {
|
|
585
|
-
this.storage.removeItem(this.prefix + key);
|
|
586
|
-
}
|
|
587
|
-
/**
|
|
588
|
-
* Clear all items with prefix
|
|
589
|
-
*/
|
|
590
|
-
clear() {
|
|
591
|
-
if (this.storage.length !== undefined && this.storage.key) {
|
|
592
|
-
const keys = [];
|
|
593
|
-
for (let i = 0; i < this.storage.length; i++) {
|
|
594
|
-
const key = this.storage.key(i);
|
|
595
|
-
if (key && key.startsWith(this.prefix)) {
|
|
596
|
-
keys.push(key);
|
|
597
|
-
}
|
|
598
|
-
}
|
|
599
|
-
keys.forEach((key) => this.storage.removeItem(key));
|
|
600
|
-
}
|
|
601
|
-
else {
|
|
602
|
-
// Fallback: try to clear common keys
|
|
603
|
-
const commonKeys = ['session_id', 'email_capture_dismissed'];
|
|
604
|
-
commonKeys.forEach((key) => this.remove(key));
|
|
605
|
-
}
|
|
606
|
-
}
|
|
607
|
-
}
|
|
608
|
-
// Export singleton instance
|
|
609
|
-
const storage = new SafeStorage();
|
|
610
|
-
/**
|
|
611
|
-
* Get item from storage (simple string getter)
|
|
612
|
-
*/
|
|
613
|
-
function getStorageItem(key) {
|
|
614
|
-
try {
|
|
615
|
-
return localStorage.getItem('wabbit_' + key);
|
|
616
|
-
}
|
|
617
|
-
catch {
|
|
618
|
-
return null;
|
|
619
|
-
}
|
|
620
|
-
}
|
|
621
|
-
/**
|
|
622
|
-
* Set item in storage (simple string setter)
|
|
623
|
-
*/
|
|
624
|
-
function setStorageItem(key, value) {
|
|
625
|
-
try {
|
|
626
|
-
localStorage.setItem('wabbit_' + key, value);
|
|
627
|
-
}
|
|
628
|
-
catch (error) {
|
|
629
|
-
console.error(`[Wabbit] Failed to set storage item "${key}":`, error);
|
|
630
|
-
}
|
|
631
|
-
}
|
|
632
|
-
|
|
633
680
|
/**
|
|
634
681
|
* WebSocket client for chat functionality
|
|
635
682
|
*
|
|
636
683
|
* Based on demo-website/src/lib/websocket.ts but without React dependencies
|
|
637
684
|
*/
|
|
638
685
|
class ChatWebSocketClient {
|
|
639
|
-
constructor(apiKey, wsUrl, sessionId = null) {
|
|
686
|
+
constructor(apiKey, wsUrl, sessionId = null, storage) {
|
|
640
687
|
this.ws = null;
|
|
641
688
|
this.status = 'disconnected';
|
|
642
689
|
this.reconnectAttempts = 0;
|
|
@@ -659,6 +706,7 @@ class ChatWebSocketClient {
|
|
|
659
706
|
this.onMessageEnd = null;
|
|
660
707
|
this.apiKey = apiKey;
|
|
661
708
|
this.wsUrl = wsUrl;
|
|
709
|
+
this.storage = storage;
|
|
662
710
|
this.sessionId = sessionId || this.getStoredSessionId();
|
|
663
711
|
}
|
|
664
712
|
setStatus(status) {
|
|
@@ -727,9 +775,9 @@ class ChatWebSocketClient {
|
|
|
727
775
|
switch (data.type) {
|
|
728
776
|
case 'welcome':
|
|
729
777
|
this.sessionId = data.session_id;
|
|
730
|
-
// Store session ID in localStorage
|
|
778
|
+
// Store session ID in storage (localStorage or sessionStorage)
|
|
731
779
|
if (this.sessionId) {
|
|
732
|
-
storage.set('session_id', this.sessionId);
|
|
780
|
+
this.storage.set('session_id', this.sessionId);
|
|
733
781
|
}
|
|
734
782
|
if (this.onWelcome) {
|
|
735
783
|
this.onWelcome(data.session_id, data.collection_id, data.message || 'Connected');
|
|
@@ -855,7 +903,7 @@ class ChatWebSocketClient {
|
|
|
855
903
|
}
|
|
856
904
|
clearSession() {
|
|
857
905
|
this.sessionId = null;
|
|
858
|
-
storage.remove('session_id');
|
|
906
|
+
this.storage.remove('session_id');
|
|
859
907
|
// Clear message queue only when user explicitly starts new session
|
|
860
908
|
// Queue should persist across reconnection attempts
|
|
861
909
|
this.messageQueue = [];
|
|
@@ -868,7 +916,7 @@ class ChatWebSocketClient {
|
|
|
868
916
|
this.messageQueue = [];
|
|
869
917
|
}
|
|
870
918
|
getStoredSessionId() {
|
|
871
|
-
return storage.get('session_id') || null;
|
|
919
|
+
return this.storage.get('session_id') || null;
|
|
872
920
|
}
|
|
873
921
|
/**
|
|
874
922
|
* Get current message queue size
|
|
@@ -1003,6 +1051,7 @@ class ChatPanel {
|
|
|
1003
1051
|
this.messages = [];
|
|
1004
1052
|
this.isWaitingForResponse = false;
|
|
1005
1053
|
this.closeButton = null;
|
|
1054
|
+
this.statusIndicator = null;
|
|
1006
1055
|
this.eventCleanup = [];
|
|
1007
1056
|
this.streamingMessages = new Map();
|
|
1008
1057
|
this.streamingCleanupInterval = null;
|
|
@@ -1138,6 +1187,14 @@ class ChatPanel {
|
|
|
1138
1187
|
this.sendButton?.removeEventListener('click', sendClickHandler);
|
|
1139
1188
|
});
|
|
1140
1189
|
this.updateSendButtonState();
|
|
1190
|
+
this.statusIndicator = createElement('div', {
|
|
1191
|
+
class: 'wabbit-chat-status-indicator',
|
|
1192
|
+
'data-status': 'connecting',
|
|
1193
|
+
title: 'Connecting...',
|
|
1194
|
+
'aria-label': 'Connection status: connecting',
|
|
1195
|
+
role: 'status'
|
|
1196
|
+
});
|
|
1197
|
+
inputWrapper.appendChild(this.statusIndicator);
|
|
1141
1198
|
inputWrapper.appendChild(this.inputElement);
|
|
1142
1199
|
inputWrapper.appendChild(this.sendButton);
|
|
1143
1200
|
inputArea.appendChild(inputWrapper);
|
|
@@ -1209,6 +1266,22 @@ class ChatPanel {
|
|
|
1209
1266
|
this.updateSendButtonState();
|
|
1210
1267
|
}
|
|
1211
1268
|
}
|
|
1269
|
+
/**
|
|
1270
|
+
* Update the connection status indicator
|
|
1271
|
+
*/
|
|
1272
|
+
setConnectionStatus(status) {
|
|
1273
|
+
if (!this.statusIndicator)
|
|
1274
|
+
return;
|
|
1275
|
+
this.statusIndicator.setAttribute('data-status', status);
|
|
1276
|
+
const labels = {
|
|
1277
|
+
connected: 'Connected',
|
|
1278
|
+
disconnected: 'Disconnected',
|
|
1279
|
+
connecting: 'Connecting...',
|
|
1280
|
+
reconnecting: 'Reconnecting...',
|
|
1281
|
+
};
|
|
1282
|
+
this.statusIndicator.setAttribute('title', labels[status]);
|
|
1283
|
+
this.statusIndicator.setAttribute('aria-label', `Connection status: ${labels[status].toLowerCase()}`);
|
|
1284
|
+
}
|
|
1212
1285
|
/**
|
|
1213
1286
|
* Render a single message
|
|
1214
1287
|
*/
|
|
@@ -1238,10 +1311,21 @@ class ChatPanel {
|
|
|
1238
1311
|
formatMessage(content) {
|
|
1239
1312
|
// Escape HTML first
|
|
1240
1313
|
let formatted = escapeHtml(content);
|
|
1314
|
+
// Markdown links [text](url) - must come before URL auto-linking
|
|
1315
|
+
formatted = formatted.replace(/\[([^\]]+)\]\(([^)]+)\)/g, '<a href="$2" target="_blank" rel="noopener noreferrer">$1</a>');
|
|
1241
1316
|
// Simple markdown-like formatting
|
|
1242
1317
|
formatted = formatted.replace(/\*\*(.+?)\*\*/g, '<strong>$1</strong>');
|
|
1243
1318
|
formatted = formatted.replace(/\*(.+?)\*/g, '<em>$1</em>');
|
|
1244
1319
|
formatted = formatted.replace(/`(.+?)`/g, '<code style="background: rgba(0,0,0,0.1); padding: 2px 4px; border-radius: 3px;">$1</code>');
|
|
1320
|
+
// Auto-link URLs (not already inside an href attribute)
|
|
1321
|
+
formatted = formatted.replace(/(?<!href=")(https?:\/\/[^\s<]+)/g, '<a href="$1" target="_blank" rel="noopener noreferrer">$1</a>');
|
|
1322
|
+
// Auto-link email addresses
|
|
1323
|
+
formatted = formatted.replace(/(?<!["\/])([a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,})/g, '<a href="mailto:$1">$1</a>');
|
|
1324
|
+
// Auto-link phone numbers (international format with +)
|
|
1325
|
+
formatted = formatted.replace(/(?<!["\/])(\+\d[\d\s-]{7,})/g, (_match, phone) => {
|
|
1326
|
+
const cleanPhone = phone.replace(/[\s-]/g, '');
|
|
1327
|
+
return `<a href="tel:${cleanPhone}">${phone}</a>`;
|
|
1328
|
+
});
|
|
1245
1329
|
formatted = formatted.replace(/\n/g, '<br>');
|
|
1246
1330
|
return formatted;
|
|
1247
1331
|
}
|
|
@@ -1480,6 +1564,7 @@ class ChatPanel {
|
|
|
1480
1564
|
this.inputElement = null;
|
|
1481
1565
|
this.sendButton = null;
|
|
1482
1566
|
this.closeButton = null;
|
|
1567
|
+
this.statusIndicator = null;
|
|
1483
1568
|
}
|
|
1484
1569
|
}
|
|
1485
1570
|
}
|
|
@@ -1794,6 +1879,20 @@ function injectChatStyles(primaryColor = '#6366f1', theme) {
|
|
|
1794
1879
|
line-height: 1.5;
|
|
1795
1880
|
}
|
|
1796
1881
|
|
|
1882
|
+
.wabbit-chat-message-content a {
|
|
1883
|
+
color: var(--wabbit-primary);
|
|
1884
|
+
text-decoration: underline;
|
|
1885
|
+
text-underline-offset: 2px;
|
|
1886
|
+
}
|
|
1887
|
+
|
|
1888
|
+
.wabbit-chat-message-content a:hover {
|
|
1889
|
+
opacity: 0.8;
|
|
1890
|
+
}
|
|
1891
|
+
|
|
1892
|
+
.wabbit-chat-message-user .wabbit-chat-message-content a {
|
|
1893
|
+
color: inherit;
|
|
1894
|
+
}
|
|
1895
|
+
|
|
1797
1896
|
/* Typing Indicator */
|
|
1798
1897
|
.wabbit-chat-typing {
|
|
1799
1898
|
display: flex;
|
|
@@ -1906,6 +2005,35 @@ function injectChatStyles(primaryColor = '#6366f1', theme) {
|
|
|
1906
2005
|
height: 20px;
|
|
1907
2006
|
}
|
|
1908
2007
|
|
|
2008
|
+
/* Connection Status Indicator */
|
|
2009
|
+
.wabbit-chat-status-indicator {
|
|
2010
|
+
width: 8px;
|
|
2011
|
+
height: 8px;
|
|
2012
|
+
border-radius: 50%;
|
|
2013
|
+
flex-shrink: 0;
|
|
2014
|
+
align-self: center;
|
|
2015
|
+
transition: background-color 0.3s ease;
|
|
2016
|
+
}
|
|
2017
|
+
|
|
2018
|
+
.wabbit-chat-status-indicator[data-status="connected"] {
|
|
2019
|
+
background-color: #22c55e;
|
|
2020
|
+
}
|
|
2021
|
+
|
|
2022
|
+
.wabbit-chat-status-indicator[data-status="disconnected"] {
|
|
2023
|
+
background-color: #ef4444;
|
|
2024
|
+
}
|
|
2025
|
+
|
|
2026
|
+
.wabbit-chat-status-indicator[data-status="connecting"],
|
|
2027
|
+
.wabbit-chat-status-indicator[data-status="reconnecting"] {
|
|
2028
|
+
background-color: #f59e0b;
|
|
2029
|
+
animation: wabbit-status-pulse 1.5s ease-in-out infinite;
|
|
2030
|
+
}
|
|
2031
|
+
|
|
2032
|
+
@keyframes wabbit-status-pulse {
|
|
2033
|
+
0%, 100% { opacity: 1; }
|
|
2034
|
+
50% { opacity: 0.3; }
|
|
2035
|
+
}
|
|
2036
|
+
|
|
1909
2037
|
/* Scrollbar */
|
|
1910
2038
|
.wabbit-chat-messages::-webkit-scrollbar {
|
|
1911
2039
|
width: 6px;
|
|
@@ -2029,7 +2157,7 @@ function adjustColor(color, amount) {
|
|
|
2029
2157
|
* Chat Widget - Main class that integrates all chat components
|
|
2030
2158
|
*/
|
|
2031
2159
|
class ChatWidget {
|
|
2032
|
-
constructor(config) {
|
|
2160
|
+
constructor(config, storage) {
|
|
2033
2161
|
this.wsClient = null;
|
|
2034
2162
|
this.bubble = null;
|
|
2035
2163
|
this.panel = null;
|
|
@@ -2039,6 +2167,7 @@ class ChatWidget {
|
|
|
2039
2167
|
this.onChatReadyCallback = null;
|
|
2040
2168
|
this.chatReadyFired = false;
|
|
2041
2169
|
this.config = config;
|
|
2170
|
+
this.storage = storage;
|
|
2042
2171
|
this.onChatReadyCallback = config.onChatReady || null;
|
|
2043
2172
|
}
|
|
2044
2173
|
/**
|
|
@@ -2067,8 +2196,8 @@ class ChatWidget {
|
|
|
2067
2196
|
this.setupThemeWatcher();
|
|
2068
2197
|
// Create WebSocket client
|
|
2069
2198
|
const wsUrl = this.getWebSocketUrl();
|
|
2070
|
-
this.wsClient = new ChatWebSocketClient(this.config.apiKey || '', wsUrl, null // Will use stored session if available
|
|
2071
|
-
);
|
|
2199
|
+
this.wsClient = new ChatWebSocketClient(this.config.apiKey || '', wsUrl, null, // Will use stored session if available
|
|
2200
|
+
this.storage);
|
|
2072
2201
|
// Set up event handlers
|
|
2073
2202
|
this.setupWebSocketHandlers();
|
|
2074
2203
|
// Only create bubble for widget mode (not inline)
|
|
@@ -2190,36 +2319,24 @@ class ChatWidget {
|
|
|
2190
2319
|
this.wsClient.onError = (error) => {
|
|
2191
2320
|
console.error('[Wabbit] Chat error:', error);
|
|
2192
2321
|
if (this.panel) {
|
|
2193
|
-
// Clean up any active streaming messages on error
|
|
2194
2322
|
this.panel.cancelAllStreamingMessages();
|
|
2195
|
-
this.panel.addSystemMessage(`Error: ${error}`);
|
|
2196
2323
|
this.panel.setWaitingForResponse(false);
|
|
2197
2324
|
}
|
|
2198
2325
|
};
|
|
2199
2326
|
this.wsClient.onStatusChange = (status) => {
|
|
2200
2327
|
if (this.panel) {
|
|
2201
2328
|
this.panel.setDisabled(status !== 'connected');
|
|
2329
|
+
this.panel.setConnectionStatus(status);
|
|
2202
2330
|
}
|
|
2203
2331
|
};
|
|
2204
2332
|
this.wsClient.onDisconnect = () => {
|
|
2205
2333
|
if (this.panel) {
|
|
2206
|
-
// Clean up any active streaming messages on disconnect
|
|
2207
2334
|
this.panel.cancelAllStreamingMessages();
|
|
2208
|
-
// Check if there are queued messages
|
|
2209
|
-
const queueSize = this.wsClient.getQueueSize();
|
|
2210
|
-
if (queueSize > 0) {
|
|
2211
|
-
this.panel.addSystemMessage(`Disconnected from chat service. ${queueSize} message(s) will be sent when reconnected.`);
|
|
2212
|
-
}
|
|
2213
|
-
else {
|
|
2214
|
-
this.panel.addSystemMessage('Disconnected from chat service');
|
|
2215
|
-
}
|
|
2216
2335
|
this.panel.setDisabled(true);
|
|
2217
2336
|
}
|
|
2218
2337
|
};
|
|
2219
2338
|
this.wsClient.onQueueOverflow = (message) => {
|
|
2220
|
-
|
|
2221
|
-
this.panel.addSystemMessage(message);
|
|
2222
|
-
}
|
|
2339
|
+
console.warn('[Wabbit] Queue overflow:', message);
|
|
2223
2340
|
};
|
|
2224
2341
|
}
|
|
2225
2342
|
/**
|