@wabbit-dashboard/embed 1.0.17 → 1.1.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/dist/wabbit-embed.cjs.js +284 -15
- package/dist/wabbit-embed.cjs.js.map +1 -1
- package/dist/wabbit-embed.d.ts +49 -0
- package/dist/wabbit-embed.esm.js +284 -15
- package/dist/wabbit-embed.esm.js.map +1 -1
- package/dist/wabbit-embed.umd.js +284 -15
- 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/dist/wabbit-embed.umd.js
CHANGED
|
@@ -644,6 +644,9 @@
|
|
|
644
644
|
this.reconnectAttempts = 0;
|
|
645
645
|
this.maxReconnectAttempts = 3;
|
|
646
646
|
this.reconnectDelay = 1000;
|
|
647
|
+
// Message queue for handling messages during disconnection
|
|
648
|
+
this.messageQueue = [];
|
|
649
|
+
this.MAX_QUEUE_SIZE = 10;
|
|
647
650
|
// Event handlers
|
|
648
651
|
this.onStatusChange = null;
|
|
649
652
|
this.onWelcome = null;
|
|
@@ -651,6 +654,11 @@
|
|
|
651
654
|
this.onMessageHistory = null;
|
|
652
655
|
this.onError = null;
|
|
653
656
|
this.onDisconnect = null;
|
|
657
|
+
this.onQueueOverflow = null;
|
|
658
|
+
// Streaming event handlers
|
|
659
|
+
this.onMessageStart = null;
|
|
660
|
+
this.onMessageChunk = null;
|
|
661
|
+
this.onMessageEnd = null;
|
|
654
662
|
this.apiKey = apiKey;
|
|
655
663
|
this.wsUrl = wsUrl;
|
|
656
664
|
this.sessionId = sessionId || this.getStoredSessionId();
|
|
@@ -682,6 +690,8 @@
|
|
|
682
690
|
this.setStatus('connected');
|
|
683
691
|
this.reconnectAttempts = 0;
|
|
684
692
|
console.log('[Wabbit] WebSocket connected');
|
|
693
|
+
// Flush any queued messages
|
|
694
|
+
this.flushMessageQueue();
|
|
685
695
|
};
|
|
686
696
|
this.ws.onmessage = (event) => {
|
|
687
697
|
try {
|
|
@@ -740,7 +750,26 @@
|
|
|
740
750
|
this.onMessageHistory(messages);
|
|
741
751
|
}
|
|
742
752
|
break;
|
|
753
|
+
case 'assistant_message_start':
|
|
754
|
+
// Streaming: Start of assistant message
|
|
755
|
+
if (this.onMessageStart) {
|
|
756
|
+
this.onMessageStart(data.message_id);
|
|
757
|
+
}
|
|
758
|
+
break;
|
|
759
|
+
case 'assistant_message_chunk':
|
|
760
|
+
// Streaming: Chunk of assistant message
|
|
761
|
+
if (this.onMessageChunk) {
|
|
762
|
+
this.onMessageChunk(data.message_id, data.content);
|
|
763
|
+
}
|
|
764
|
+
break;
|
|
765
|
+
case 'assistant_message_end':
|
|
766
|
+
// Streaming: End of assistant message
|
|
767
|
+
if (this.onMessageEnd) {
|
|
768
|
+
this.onMessageEnd(data.message_id, data.metadata);
|
|
769
|
+
}
|
|
770
|
+
break;
|
|
743
771
|
case 'assistant_message':
|
|
772
|
+
// Non-streaming: Complete assistant message (backward compatibility)
|
|
744
773
|
if (this.onMessage) {
|
|
745
774
|
const message = {
|
|
746
775
|
id: data.message_id || crypto.randomUUID(),
|
|
@@ -762,18 +791,27 @@
|
|
|
762
791
|
}
|
|
763
792
|
}
|
|
764
793
|
sendMessage(content, metadata) {
|
|
765
|
-
if (!this.ws || this.ws.readyState !== WebSocket.OPEN) {
|
|
766
|
-
if (this.onError) {
|
|
767
|
-
this.onError('Not connected to chat service');
|
|
768
|
-
}
|
|
769
|
-
return;
|
|
770
|
-
}
|
|
771
794
|
const message = {
|
|
772
795
|
type: 'message',
|
|
773
796
|
content,
|
|
774
797
|
metadata: metadata || {},
|
|
775
798
|
};
|
|
776
|
-
|
|
799
|
+
// If connected, send immediately
|
|
800
|
+
if (this.ws && this.ws.readyState === WebSocket.OPEN) {
|
|
801
|
+
this.ws.send(JSON.stringify(message));
|
|
802
|
+
return;
|
|
803
|
+
}
|
|
804
|
+
// If disconnected or reconnecting, queue the message
|
|
805
|
+
if (this.messageQueue.length >= this.MAX_QUEUE_SIZE) {
|
|
806
|
+
console.warn('[Wabbit] Message queue full, cannot queue message');
|
|
807
|
+
if (this.onQueueOverflow) {
|
|
808
|
+
this.onQueueOverflow('Message queue full. Please wait for reconnection.');
|
|
809
|
+
}
|
|
810
|
+
return;
|
|
811
|
+
}
|
|
812
|
+
// Add to queue
|
|
813
|
+
this.messageQueue.push({ content, metadata });
|
|
814
|
+
console.log(`[Wabbit] Message queued (${this.messageQueue.length}/${this.MAX_QUEUE_SIZE})`);
|
|
777
815
|
}
|
|
778
816
|
disconnect() {
|
|
779
817
|
if (this.ws) {
|
|
@@ -795,13 +833,51 @@
|
|
|
795
833
|
this.connect();
|
|
796
834
|
}, delay);
|
|
797
835
|
}
|
|
836
|
+
/**
|
|
837
|
+
* Flush queued messages after connection is established
|
|
838
|
+
*/
|
|
839
|
+
flushMessageQueue() {
|
|
840
|
+
if (this.messageQueue.length === 0) {
|
|
841
|
+
return;
|
|
842
|
+
}
|
|
843
|
+
console.log(`[Wabbit] Flushing ${this.messageQueue.length} queued message(s)`);
|
|
844
|
+
// Send all queued messages in order
|
|
845
|
+
const queuedMessages = [...this.messageQueue];
|
|
846
|
+
this.messageQueue = [];
|
|
847
|
+
queuedMessages.forEach(({ content, metadata }) => {
|
|
848
|
+
if (this.ws && this.ws.readyState === WebSocket.OPEN) {
|
|
849
|
+
const message = {
|
|
850
|
+
type: 'message',
|
|
851
|
+
content,
|
|
852
|
+
metadata: metadata || {},
|
|
853
|
+
};
|
|
854
|
+
this.ws.send(JSON.stringify(message));
|
|
855
|
+
}
|
|
856
|
+
});
|
|
857
|
+
}
|
|
798
858
|
clearSession() {
|
|
799
859
|
this.sessionId = null;
|
|
800
860
|
storage.remove('session_id');
|
|
861
|
+
// Clear message queue only when user explicitly starts new session
|
|
862
|
+
// Queue should persist across reconnection attempts
|
|
863
|
+
this.messageQueue = [];
|
|
864
|
+
}
|
|
865
|
+
/**
|
|
866
|
+
* Clear the message queue
|
|
867
|
+
* Only call this when explicitly abandoning queued messages
|
|
868
|
+
*/
|
|
869
|
+
clearMessageQueue() {
|
|
870
|
+
this.messageQueue = [];
|
|
801
871
|
}
|
|
802
872
|
getStoredSessionId() {
|
|
803
873
|
return storage.get('session_id') || null;
|
|
804
874
|
}
|
|
875
|
+
/**
|
|
876
|
+
* Get current message queue size
|
|
877
|
+
*/
|
|
878
|
+
getQueueSize() {
|
|
879
|
+
return this.messageQueue.length;
|
|
880
|
+
}
|
|
805
881
|
}
|
|
806
882
|
|
|
807
883
|
/**
|
|
@@ -930,6 +1006,9 @@
|
|
|
930
1006
|
this.isWaitingForResponse = false;
|
|
931
1007
|
this.closeButton = null;
|
|
932
1008
|
this.eventCleanup = [];
|
|
1009
|
+
this.streamingMessages = new Map();
|
|
1010
|
+
this.streamingCleanupInterval = null;
|
|
1011
|
+
this.STREAMING_TIMEOUT_MS = 30000; // 30 seconds timeout for stale streams
|
|
933
1012
|
this.options = options;
|
|
934
1013
|
}
|
|
935
1014
|
/**
|
|
@@ -1168,6 +1247,132 @@
|
|
|
1168
1247
|
formatted = formatted.replace(/\n/g, '<br>');
|
|
1169
1248
|
return formatted;
|
|
1170
1249
|
}
|
|
1250
|
+
/**
|
|
1251
|
+
* Start a streaming assistant message
|
|
1252
|
+
*/
|
|
1253
|
+
startStreamingMessage(messageId) {
|
|
1254
|
+
if (!this.messagesContainer)
|
|
1255
|
+
return;
|
|
1256
|
+
// Create message container
|
|
1257
|
+
const messageDiv = createElement('div', {
|
|
1258
|
+
class: 'wabbit-chat-message wabbit-chat-message-assistant wabbit-chat-message-streaming',
|
|
1259
|
+
'data-message-id': messageId,
|
|
1260
|
+
});
|
|
1261
|
+
const bubble = createElement('div', { class: 'wabbit-chat-message-bubble' });
|
|
1262
|
+
const content = createElement('div', { class: 'wabbit-chat-message-content' });
|
|
1263
|
+
// Add streaming cursor
|
|
1264
|
+
content.innerHTML = '<span class="wabbit-chat-cursor">▋</span>';
|
|
1265
|
+
bubble.appendChild(content);
|
|
1266
|
+
messageDiv.appendChild(bubble);
|
|
1267
|
+
this.messagesContainer.appendChild(messageDiv);
|
|
1268
|
+
// Store reference with timestamp for timeout tracking
|
|
1269
|
+
this.streamingMessages.set(messageId, {
|
|
1270
|
+
element: messageDiv,
|
|
1271
|
+
content: '',
|
|
1272
|
+
startTime: Date.now()
|
|
1273
|
+
});
|
|
1274
|
+
// Start cleanup interval if not already running
|
|
1275
|
+
if (!this.streamingCleanupInterval) {
|
|
1276
|
+
this.streamingCleanupInterval = window.setInterval(() => {
|
|
1277
|
+
this.cleanupStaleStreamingMessages();
|
|
1278
|
+
}, 5000); // Check every 5 seconds
|
|
1279
|
+
}
|
|
1280
|
+
this.scrollToBottom();
|
|
1281
|
+
}
|
|
1282
|
+
/**
|
|
1283
|
+
* Append chunk to streaming message
|
|
1284
|
+
*/
|
|
1285
|
+
appendToStreamingMessage(messageId, chunk) {
|
|
1286
|
+
const streaming = this.streamingMessages.get(messageId);
|
|
1287
|
+
if (!streaming) {
|
|
1288
|
+
console.warn('[ChatPanel] No streaming message found for ID:', messageId);
|
|
1289
|
+
return;
|
|
1290
|
+
}
|
|
1291
|
+
// Append to content
|
|
1292
|
+
streaming.content += chunk;
|
|
1293
|
+
// Update DOM
|
|
1294
|
+
const contentDiv = streaming.element.querySelector('.wabbit-chat-message-content');
|
|
1295
|
+
if (contentDiv) {
|
|
1296
|
+
// Format the content and add cursor
|
|
1297
|
+
contentDiv.innerHTML = this.formatMessage(streaming.content) + '<span class="wabbit-chat-cursor">▋</span>';
|
|
1298
|
+
}
|
|
1299
|
+
this.scrollToBottom();
|
|
1300
|
+
}
|
|
1301
|
+
/**
|
|
1302
|
+
* Finish streaming message
|
|
1303
|
+
*/
|
|
1304
|
+
finishStreamingMessage(messageId, metadata) {
|
|
1305
|
+
const streaming = this.streamingMessages.get(messageId);
|
|
1306
|
+
if (!streaming) {
|
|
1307
|
+
// Don't warn - this is expected if cleanup already removed it or on error
|
|
1308
|
+
return;
|
|
1309
|
+
}
|
|
1310
|
+
// Remove streaming class and cursor
|
|
1311
|
+
streaming.element.classList.remove('wabbit-chat-message-streaming');
|
|
1312
|
+
const contentDiv = streaming.element.querySelector('.wabbit-chat-message-content');
|
|
1313
|
+
if (contentDiv) {
|
|
1314
|
+
// Remove cursor, keep formatted content
|
|
1315
|
+
contentDiv.innerHTML = this.formatMessage(streaming.content);
|
|
1316
|
+
}
|
|
1317
|
+
// Add to messages array
|
|
1318
|
+
const message = {
|
|
1319
|
+
id: messageId,
|
|
1320
|
+
role: 'assistant',
|
|
1321
|
+
content: streaming.content,
|
|
1322
|
+
timestamp: new Date(),
|
|
1323
|
+
metadata,
|
|
1324
|
+
};
|
|
1325
|
+
this.messages.push(message);
|
|
1326
|
+
// Clean up
|
|
1327
|
+
this.streamingMessages.delete(messageId);
|
|
1328
|
+
this.scrollToBottom();
|
|
1329
|
+
}
|
|
1330
|
+
/**
|
|
1331
|
+
* Cancel a streaming message (e.g., on error)
|
|
1332
|
+
* Cleans up the streaming state without adding to message history
|
|
1333
|
+
*/
|
|
1334
|
+
cancelStreamingMessage(messageId) {
|
|
1335
|
+
const streaming = this.streamingMessages.get(messageId);
|
|
1336
|
+
if (!streaming) {
|
|
1337
|
+
return;
|
|
1338
|
+
}
|
|
1339
|
+
// Remove the streaming message element from DOM
|
|
1340
|
+
streaming.element.remove();
|
|
1341
|
+
// Clean up from map
|
|
1342
|
+
this.streamingMessages.delete(messageId);
|
|
1343
|
+
}
|
|
1344
|
+
/**
|
|
1345
|
+
* Cancel all active streaming messages
|
|
1346
|
+
* Useful when connection drops or on error
|
|
1347
|
+
*/
|
|
1348
|
+
cancelAllStreamingMessages() {
|
|
1349
|
+
this.streamingMessages.forEach((streaming) => {
|
|
1350
|
+
streaming.element.remove();
|
|
1351
|
+
});
|
|
1352
|
+
this.streamingMessages.clear();
|
|
1353
|
+
}
|
|
1354
|
+
/**
|
|
1355
|
+
* Cleanup stale streaming messages that have exceeded timeout
|
|
1356
|
+
* Runs periodically to prevent memory leaks from abandoned streams
|
|
1357
|
+
*/
|
|
1358
|
+
cleanupStaleStreamingMessages() {
|
|
1359
|
+
const now = Date.now();
|
|
1360
|
+
const staleIds = [];
|
|
1361
|
+
this.streamingMessages.forEach((streaming, messageId) => {
|
|
1362
|
+
if (now - streaming.startTime > this.STREAMING_TIMEOUT_MS) {
|
|
1363
|
+
console.warn('[ChatPanel] Cleaning up stale streaming message:', messageId);
|
|
1364
|
+
streaming.element.remove();
|
|
1365
|
+
staleIds.push(messageId);
|
|
1366
|
+
}
|
|
1367
|
+
});
|
|
1368
|
+
// Remove stale entries from map
|
|
1369
|
+
staleIds.forEach(id => this.streamingMessages.delete(id));
|
|
1370
|
+
// Stop cleanup interval if no more streaming messages
|
|
1371
|
+
if (this.streamingMessages.size === 0 && this.streamingCleanupInterval) {
|
|
1372
|
+
clearInterval(this.streamingCleanupInterval);
|
|
1373
|
+
this.streamingCleanupInterval = null;
|
|
1374
|
+
}
|
|
1375
|
+
}
|
|
1171
1376
|
/**
|
|
1172
1377
|
* Handle send message
|
|
1173
1378
|
*/
|
|
@@ -1252,6 +1457,11 @@
|
|
|
1252
1457
|
if (this.element) {
|
|
1253
1458
|
this.element.style.display = 'none';
|
|
1254
1459
|
}
|
|
1460
|
+
// Stop streaming cleanup interval when hidden to prevent memory leaks
|
|
1461
|
+
if (this.streamingCleanupInterval) {
|
|
1462
|
+
clearInterval(this.streamingCleanupInterval);
|
|
1463
|
+
this.streamingCleanupInterval = null;
|
|
1464
|
+
}
|
|
1255
1465
|
}
|
|
1256
1466
|
/**
|
|
1257
1467
|
* Remove the panel from DOM and cleanup event listeners
|
|
@@ -1260,6 +1470,11 @@
|
|
|
1260
1470
|
// Run all event cleanup functions
|
|
1261
1471
|
this.eventCleanup.forEach((cleanup) => cleanup());
|
|
1262
1472
|
this.eventCleanup = [];
|
|
1473
|
+
// Clear streaming cleanup interval
|
|
1474
|
+
if (this.streamingCleanupInterval) {
|
|
1475
|
+
clearInterval(this.streamingCleanupInterval);
|
|
1476
|
+
this.streamingCleanupInterval = null;
|
|
1477
|
+
}
|
|
1263
1478
|
if (this.element) {
|
|
1264
1479
|
this.element.remove();
|
|
1265
1480
|
this.element = null;
|
|
@@ -1723,6 +1938,24 @@
|
|
|
1723
1938
|
}
|
|
1724
1939
|
}
|
|
1725
1940
|
|
|
1941
|
+
/* Streaming Cursor */
|
|
1942
|
+
.wabbit-chat-cursor {
|
|
1943
|
+
display: inline-block;
|
|
1944
|
+
color: var(--wabbit-primary);
|
|
1945
|
+
font-weight: bold;
|
|
1946
|
+
animation: wabbit-cursor-blink 1s infinite;
|
|
1947
|
+
margin-left: 2px;
|
|
1948
|
+
}
|
|
1949
|
+
|
|
1950
|
+
@keyframes wabbit-cursor-blink {
|
|
1951
|
+
0%, 50% {
|
|
1952
|
+
opacity: 1;
|
|
1953
|
+
}
|
|
1954
|
+
51%, 100% {
|
|
1955
|
+
opacity: 0;
|
|
1956
|
+
}
|
|
1957
|
+
}
|
|
1958
|
+
|
|
1726
1959
|
.wabbit-chat-panel {
|
|
1727
1960
|
animation: wabbit-fade-in 0.3s ease-out;
|
|
1728
1961
|
}
|
|
@@ -1737,17 +1970,17 @@
|
|
|
1737
1970
|
|
|
1738
1971
|
/* Inline Chat Panel - renders inside container instead of fixed position */
|
|
1739
1972
|
.wabbit-chat-panel.wabbit-chat-panel-inline {
|
|
1740
|
-
position:
|
|
1973
|
+
position: absolute !important;
|
|
1974
|
+
top: 0;
|
|
1975
|
+
left: 0;
|
|
1976
|
+
right: 0;
|
|
1977
|
+
bottom: 0;
|
|
1741
1978
|
width: 100%;
|
|
1742
1979
|
height: 100%;
|
|
1743
|
-
min-height: 400px;
|
|
1744
1980
|
max-height: none;
|
|
1745
1981
|
max-width: none;
|
|
1746
|
-
|
|
1747
|
-
|
|
1748
|
-
left: auto !important;
|
|
1749
|
-
border-radius: 12px;
|
|
1750
|
-
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.15);
|
|
1982
|
+
border-radius: 0;
|
|
1983
|
+
box-shadow: none;
|
|
1751
1984
|
animation: none; /* Disable slide-in animation for inline */
|
|
1752
1985
|
}
|
|
1753
1986
|
|
|
@@ -1930,6 +2163,26 @@
|
|
|
1930
2163
|
this.panel.setMessages(messages);
|
|
1931
2164
|
}
|
|
1932
2165
|
};
|
|
2166
|
+
// Streaming message handlers
|
|
2167
|
+
this.wsClient.onMessageStart = (messageId) => {
|
|
2168
|
+
console.log('[Wabbit] Streaming message started:', messageId);
|
|
2169
|
+
if (this.panel) {
|
|
2170
|
+
this.panel.startStreamingMessage(messageId);
|
|
2171
|
+
}
|
|
2172
|
+
};
|
|
2173
|
+
this.wsClient.onMessageChunk = (messageId, chunk) => {
|
|
2174
|
+
if (this.panel) {
|
|
2175
|
+
this.panel.appendToStreamingMessage(messageId, chunk);
|
|
2176
|
+
}
|
|
2177
|
+
};
|
|
2178
|
+
this.wsClient.onMessageEnd = (messageId, metadata) => {
|
|
2179
|
+
console.log('[Wabbit] Streaming message ended:', messageId);
|
|
2180
|
+
if (this.panel) {
|
|
2181
|
+
this.panel.finishStreamingMessage(messageId, metadata);
|
|
2182
|
+
this.panel.setWaitingForResponse(false);
|
|
2183
|
+
}
|
|
2184
|
+
};
|
|
2185
|
+
// Non-streaming message handler (backward compatibility)
|
|
1933
2186
|
this.wsClient.onMessage = (message) => {
|
|
1934
2187
|
if (this.panel) {
|
|
1935
2188
|
this.panel.addMessage(message);
|
|
@@ -1939,6 +2192,8 @@
|
|
|
1939
2192
|
this.wsClient.onError = (error) => {
|
|
1940
2193
|
console.error('[Wabbit] Chat error:', error);
|
|
1941
2194
|
if (this.panel) {
|
|
2195
|
+
// Clean up any active streaming messages on error
|
|
2196
|
+
this.panel.cancelAllStreamingMessages();
|
|
1942
2197
|
this.panel.addSystemMessage(`Error: ${error}`);
|
|
1943
2198
|
this.panel.setWaitingForResponse(false);
|
|
1944
2199
|
}
|
|
@@ -1950,10 +2205,24 @@
|
|
|
1950
2205
|
};
|
|
1951
2206
|
this.wsClient.onDisconnect = () => {
|
|
1952
2207
|
if (this.panel) {
|
|
1953
|
-
|
|
2208
|
+
// Clean up any active streaming messages on disconnect
|
|
2209
|
+
this.panel.cancelAllStreamingMessages();
|
|
2210
|
+
// Check if there are queued messages
|
|
2211
|
+
const queueSize = this.wsClient.getQueueSize();
|
|
2212
|
+
if (queueSize > 0) {
|
|
2213
|
+
this.panel.addSystemMessage(`Disconnected from chat service. ${queueSize} message(s) will be sent when reconnected.`);
|
|
2214
|
+
}
|
|
2215
|
+
else {
|
|
2216
|
+
this.panel.addSystemMessage('Disconnected from chat service');
|
|
2217
|
+
}
|
|
1954
2218
|
this.panel.setDisabled(true);
|
|
1955
2219
|
}
|
|
1956
2220
|
};
|
|
2221
|
+
this.wsClient.onQueueOverflow = (message) => {
|
|
2222
|
+
if (this.panel) {
|
|
2223
|
+
this.panel.addSystemMessage(message);
|
|
2224
|
+
}
|
|
2225
|
+
};
|
|
1957
2226
|
}
|
|
1958
2227
|
/**
|
|
1959
2228
|
* Handle trigger type configuration
|