@wabbit-dashboard/embed 1.0.10 → 1.0.12
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 +6 -2
- package/dist/wabbit-embed.cjs.js +342 -59
- package/dist/wabbit-embed.cjs.js.map +1 -1
- package/dist/wabbit-embed.d.ts +93 -5
- package/dist/wabbit-embed.esm.js +341 -60
- package/dist/wabbit-embed.esm.js.map +1 -1
- package/dist/wabbit-embed.umd.js +342 -59
- 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 +2 -2
package/README.md
CHANGED
|
@@ -33,12 +33,14 @@ import { Wabbit } from '@wabbit-dashboard/embed';
|
|
|
33
33
|
|
|
34
34
|
### Using CDN
|
|
35
35
|
|
|
36
|
-
**Production:**
|
|
36
|
+
**Production (deployed website):**
|
|
37
37
|
```html
|
|
38
38
|
<script src="https://unpkg.com/@wabbit-dashboard/embed@1/dist/wabbit-embed.umd.min.js"></script>
|
|
39
39
|
<script>
|
|
40
40
|
Wabbit.init({
|
|
41
41
|
apiKey: 'pk_live_xxx',
|
|
42
|
+
apiUrl: 'https://platform.insourcedata.ai',
|
|
43
|
+
wsUrl: 'wss://chat.insourcedata.ai/ws/chat',
|
|
42
44
|
chat: {
|
|
43
45
|
enabled: true,
|
|
44
46
|
collectionId: 'abc123',
|
|
@@ -48,7 +50,9 @@ import { Wabbit } from '@wabbit-dashboard/embed';
|
|
|
48
50
|
</script>
|
|
49
51
|
```
|
|
50
52
|
|
|
51
|
-
**
|
|
53
|
+
> **Note**: The `apiUrl` and `wsUrl` are required when your website runs on localhost but connects to production services. For fully deployed production websites, these can be omitted as the SDK auto-detects the correct URLs.
|
|
54
|
+
|
|
55
|
+
**Local Development (local services):**
|
|
52
56
|
```html
|
|
53
57
|
<script src="http://localhost:3000/sdk/wabbit-embed.umd.js"></script>
|
|
54
58
|
<script>
|
package/dist/wabbit-embed.cjs.js
CHANGED
|
@@ -23,6 +23,10 @@ function validateConfig(config) {
|
|
|
23
23
|
if (!config.chat.collectionId) {
|
|
24
24
|
throw new Error('[Wabbit] collectionId is required when chat is enabled');
|
|
25
25
|
}
|
|
26
|
+
// Validate inline mode requires a container
|
|
27
|
+
if (config.chat.mode === 'inline' && !config.chat.container) {
|
|
28
|
+
throw new Error('[Wabbit] container is required when chat mode is "inline"');
|
|
29
|
+
}
|
|
26
30
|
}
|
|
27
31
|
// Validate forms config if enabled
|
|
28
32
|
if (config.forms?.enabled) {
|
|
@@ -90,11 +94,14 @@ function mergeConfig(config) {
|
|
|
90
94
|
chat: config.chat
|
|
91
95
|
? {
|
|
92
96
|
...config.chat,
|
|
97
|
+
mode: config.chat.mode || 'widget',
|
|
93
98
|
position: config.chat.position || 'bottom-right',
|
|
94
99
|
triggerType: config.chat.triggerType || 'button',
|
|
95
100
|
theme: config.chat.theme || 'auto',
|
|
96
101
|
primaryColor: config.chat.primaryColor || '#6366f1',
|
|
97
|
-
placeholder: config.chat.placeholder || 'Type your message...'
|
|
102
|
+
placeholder: config.chat.placeholder || 'Type your message...',
|
|
103
|
+
showHeader: config.chat.showHeader ?? true,
|
|
104
|
+
headerTitle: config.chat.headerTitle || 'AI Assistant'
|
|
98
105
|
}
|
|
99
106
|
: undefined,
|
|
100
107
|
forms: config.forms
|
|
@@ -157,6 +164,11 @@ class Wabbit {
|
|
|
157
164
|
if (mergedConfig.chat?.enabled && mergedConfig.chat) {
|
|
158
165
|
// Import ChatWidget dynamically to avoid circular dependencies
|
|
159
166
|
Promise.resolve().then(function () { return ChatWidget$1; }).then(({ ChatWidget }) => {
|
|
167
|
+
// Check if instance was destroyed during async import (e.g., React StrictMode)
|
|
168
|
+
if (!Wabbit.instance) {
|
|
169
|
+
console.warn('[Wabbit] Instance was destroyed before chat widget could initialize');
|
|
170
|
+
return;
|
|
171
|
+
}
|
|
160
172
|
const chat = mergedConfig.chat;
|
|
161
173
|
const chatConfig = {
|
|
162
174
|
enabled: chat.enabled,
|
|
@@ -165,13 +177,23 @@ class Wabbit {
|
|
|
165
177
|
apiKey: chat.apiKey || mergedConfig.apiKey,
|
|
166
178
|
apiUrl: chat.apiUrl || mergedConfig.apiUrl,
|
|
167
179
|
wsUrl: mergedConfig.wsUrl || chat.wsUrl, // Use global wsUrl or chat-specific wsUrl
|
|
180
|
+
// Embedding mode
|
|
181
|
+
mode: chat.mode,
|
|
182
|
+
container: chat.container,
|
|
183
|
+
// Widget mode options
|
|
168
184
|
position: chat.position,
|
|
169
185
|
triggerType: chat.triggerType,
|
|
170
186
|
triggerDelay: chat.triggerDelay,
|
|
187
|
+
// Appearance options
|
|
171
188
|
theme: chat.theme,
|
|
172
189
|
primaryColor: chat.primaryColor,
|
|
173
190
|
welcomeMessage: chat.welcomeMessage,
|
|
174
|
-
placeholder: chat.placeholder
|
|
191
|
+
placeholder: chat.placeholder,
|
|
192
|
+
// Header customization
|
|
193
|
+
showHeader: chat.showHeader,
|
|
194
|
+
headerTitle: chat.headerTitle,
|
|
195
|
+
// Callbacks - prefer chat-specific, fallback to global
|
|
196
|
+
onChatReady: chat.onChatReady || config.onChatReady
|
|
175
197
|
};
|
|
176
198
|
const chatWidget = new ChatWidget(chatConfig);
|
|
177
199
|
chatWidget.init();
|
|
@@ -181,6 +203,11 @@ class Wabbit {
|
|
|
181
203
|
if (mergedConfig.forms?.enabled && mergedConfig.forms) {
|
|
182
204
|
// Import FormWidget dynamically to avoid circular dependencies
|
|
183
205
|
Promise.resolve().then(function () { return FormWidget$1; }).then(({ FormWidget }) => {
|
|
206
|
+
// Check if instance was destroyed during async import
|
|
207
|
+
if (!Wabbit.instance) {
|
|
208
|
+
console.warn('[Wabbit] Instance was destroyed before form widget could initialize');
|
|
209
|
+
return;
|
|
210
|
+
}
|
|
184
211
|
const forms = mergedConfig.forms;
|
|
185
212
|
const formConfig = {
|
|
186
213
|
enabled: forms.enabled,
|
|
@@ -205,6 +232,11 @@ class Wabbit {
|
|
|
205
232
|
if (mergedConfig.emailCapture?.enabled) {
|
|
206
233
|
const emailCapture = mergedConfig.emailCapture;
|
|
207
234
|
Promise.resolve().then(function () { return EmailCaptureWidget$1; }).then(({ EmailCaptureWidget }) => {
|
|
235
|
+
// Check if instance was destroyed during async import
|
|
236
|
+
if (!Wabbit.instance) {
|
|
237
|
+
console.warn('[Wabbit] Instance was destroyed before email capture widget could initialize');
|
|
238
|
+
return;
|
|
239
|
+
}
|
|
208
240
|
const emailCaptureConfig = {
|
|
209
241
|
enabled: emailCapture.enabled,
|
|
210
242
|
triggerAfterMessages: emailCapture.triggerAfterMessages,
|
|
@@ -219,7 +251,7 @@ class Wabbit {
|
|
|
219
251
|
// Connect to chat widget after it's initialized
|
|
220
252
|
// Use a small delay to ensure chat widget is ready
|
|
221
253
|
setTimeout(() => {
|
|
222
|
-
if (Wabbit.instance
|
|
254
|
+
if (Wabbit.instance?.chatWidget) {
|
|
223
255
|
const chatWidget = Wabbit.instance.chatWidget;
|
|
224
256
|
// Set WebSocket client
|
|
225
257
|
if (chatWidget.wsClient) {
|
|
@@ -239,7 +271,9 @@ class Wabbit {
|
|
|
239
271
|
});
|
|
240
272
|
}
|
|
241
273
|
// Auto-initialize forms with data-wabbit-form-id (backward compatibility)
|
|
242
|
-
Wabbit.instance
|
|
274
|
+
if (Wabbit.instance) {
|
|
275
|
+
Wabbit.instance.initLegacyForms(config);
|
|
276
|
+
}
|
|
243
277
|
// Call onReady callback if provided
|
|
244
278
|
if (config.onReady) {
|
|
245
279
|
// Wait for DOM to be ready
|
|
@@ -293,6 +327,60 @@ class Wabbit {
|
|
|
293
327
|
Wabbit.instance = null;
|
|
294
328
|
}
|
|
295
329
|
}
|
|
330
|
+
/**
|
|
331
|
+
* Get the URL for a dedicated chat page
|
|
332
|
+
*
|
|
333
|
+
* @param collectionId - The collection ID
|
|
334
|
+
* @param options - Optional parameters
|
|
335
|
+
* @returns Full URL to the chat page
|
|
336
|
+
*
|
|
337
|
+
* @example
|
|
338
|
+
* ```typescript
|
|
339
|
+
* const url = Wabbit.getChatPageUrl('abc123', {
|
|
340
|
+
* initialMessage: 'How do I get started?',
|
|
341
|
+
* theme: 'dark'
|
|
342
|
+
* });
|
|
343
|
+
* // Returns: https://platform.insourcedata.ai/c/abc123?q=How%20do%20I%20get%20started%3F&theme=dark
|
|
344
|
+
* ```
|
|
345
|
+
*/
|
|
346
|
+
static getChatPageUrl(collectionId, options) {
|
|
347
|
+
// Determine base URL
|
|
348
|
+
const baseUrl = options?.baseUrl ||
|
|
349
|
+
(typeof window !== 'undefined' && window.WABBIT_BASE_URL) ||
|
|
350
|
+
(typeof window !== 'undefined' &&
|
|
351
|
+
(window.location.hostname === 'localhost' ||
|
|
352
|
+
window.location.hostname === '127.0.0.1')
|
|
353
|
+
? 'http://localhost:3000'
|
|
354
|
+
: 'https://platform.insourcedata.ai');
|
|
355
|
+
const url = new URL(`/c/${collectionId}`, baseUrl);
|
|
356
|
+
// Add query parameters
|
|
357
|
+
if (options?.initialMessage) {
|
|
358
|
+
url.searchParams.set('q', options.initialMessage);
|
|
359
|
+
}
|
|
360
|
+
if (options?.theme && options.theme !== 'auto') {
|
|
361
|
+
url.searchParams.set('theme', options.theme);
|
|
362
|
+
}
|
|
363
|
+
if (options?.primaryColor) {
|
|
364
|
+
url.searchParams.set('color', options.primaryColor.replace('#', ''));
|
|
365
|
+
}
|
|
366
|
+
return url.toString();
|
|
367
|
+
}
|
|
368
|
+
/**
|
|
369
|
+
* Open chat in a new tab/window
|
|
370
|
+
*
|
|
371
|
+
* @param collectionId - The collection ID
|
|
372
|
+
* @param options - Optional parameters (same as getChatPageUrl)
|
|
373
|
+
*
|
|
374
|
+
* @example
|
|
375
|
+
* ```typescript
|
|
376
|
+
* // Open chat page with initial message
|
|
377
|
+
* Wabbit.openChatPage('abc123', { initialMessage: 'Hello!' });
|
|
378
|
+
* ```
|
|
379
|
+
*/
|
|
380
|
+
static openChatPage(collectionId, options) {
|
|
381
|
+
const url = Wabbit.getChatPageUrl(collectionId, options);
|
|
382
|
+
window.open(url, '_blank', 'noopener,noreferrer');
|
|
383
|
+
}
|
|
296
384
|
/**
|
|
297
385
|
* Get current configuration
|
|
298
386
|
*/
|
|
@@ -812,6 +900,8 @@ class ChatPanel {
|
|
|
812
900
|
this.sendButton = null;
|
|
813
901
|
this.messages = [];
|
|
814
902
|
this.isWaitingForResponse = false;
|
|
903
|
+
this.closeButton = null;
|
|
904
|
+
this.eventCleanup = [];
|
|
815
905
|
this.options = options;
|
|
816
906
|
}
|
|
817
907
|
/**
|
|
@@ -821,36 +911,83 @@ class ChatPanel {
|
|
|
821
911
|
if (this.element) {
|
|
822
912
|
return this.element;
|
|
823
913
|
}
|
|
824
|
-
|
|
914
|
+
const isInline = this.options.mode === 'inline';
|
|
915
|
+
// Main panel - use different class for inline mode
|
|
825
916
|
this.element = createElement('div', {
|
|
826
|
-
class:
|
|
917
|
+
class: isInline
|
|
918
|
+
? 'wabbit-chat-panel wabbit-chat-panel-inline'
|
|
919
|
+
: `wabbit-chat-panel ${this.options.position}`
|
|
827
920
|
});
|
|
828
|
-
// Header
|
|
921
|
+
// Header (conditionally shown based on showHeader option, default: true)
|
|
922
|
+
if (this.options.showHeader !== false) {
|
|
923
|
+
const header = this.createHeader(isInline);
|
|
924
|
+
this.element.appendChild(header);
|
|
925
|
+
}
|
|
926
|
+
// Messages area
|
|
927
|
+
this.messagesContainer = createElement('div', { class: 'wabbit-chat-messages' });
|
|
928
|
+
// Input area
|
|
929
|
+
const inputArea = this.createInputArea();
|
|
930
|
+
// Assemble panel
|
|
931
|
+
this.element.appendChild(this.messagesContainer);
|
|
932
|
+
this.element.appendChild(inputArea);
|
|
933
|
+
// Append to container (inline) or document.body (widget)
|
|
934
|
+
if (isInline && this.options.container) {
|
|
935
|
+
this.options.container.appendChild(this.element);
|
|
936
|
+
// In inline mode, always visible
|
|
937
|
+
this.element.style.display = 'flex';
|
|
938
|
+
}
|
|
939
|
+
else {
|
|
940
|
+
document.body.appendChild(this.element);
|
|
941
|
+
// In widget mode, initially hidden until opened
|
|
942
|
+
this.element.style.display = 'none';
|
|
943
|
+
}
|
|
944
|
+
// Show welcome message if provided
|
|
945
|
+
if (this.options.welcomeMessage) {
|
|
946
|
+
this.addSystemMessage(this.options.welcomeMessage);
|
|
947
|
+
}
|
|
948
|
+
// Ensure scroll to bottom after DOM settles (for initial load and history)
|
|
949
|
+
setTimeout(() => this.scrollToBottom(), 100);
|
|
950
|
+
return this.element;
|
|
951
|
+
}
|
|
952
|
+
/**
|
|
953
|
+
* Create the header element
|
|
954
|
+
*/
|
|
955
|
+
createHeader(isInline) {
|
|
829
956
|
const header = createElement('div', { class: 'wabbit-chat-panel-header' });
|
|
830
957
|
const headerTitle = createElement('div', { class: 'wabbit-chat-panel-header-title' });
|
|
831
958
|
const headerIcon = createElement('div', { class: 'wabbit-chat-panel-header-icon' });
|
|
832
959
|
headerIcon.textContent = 'AI';
|
|
833
960
|
const headerText = createElement('div', { class: 'wabbit-chat-panel-header-text' });
|
|
834
961
|
const headerH3 = createElement('h3');
|
|
835
|
-
headerH3.textContent = 'AI Assistant';
|
|
962
|
+
headerH3.textContent = this.options.headerTitle || 'AI Assistant';
|
|
836
963
|
const headerP = createElement('p');
|
|
837
964
|
headerP.textContent = 'Powered by Wabbit';
|
|
838
965
|
headerText.appendChild(headerH3);
|
|
839
966
|
headerText.appendChild(headerP);
|
|
840
967
|
headerTitle.appendChild(headerIcon);
|
|
841
968
|
headerTitle.appendChild(headerText);
|
|
842
|
-
const closeButton = createElement('button', {
|
|
843
|
-
class: 'wabbit-chat-panel-close',
|
|
844
|
-
'aria-label': 'Close chat',
|
|
845
|
-
type: 'button'
|
|
846
|
-
});
|
|
847
|
-
closeButton.innerHTML = '×';
|
|
848
|
-
closeButton.addEventListener('click', this.options.onClose);
|
|
849
969
|
header.appendChild(headerTitle);
|
|
850
|
-
|
|
851
|
-
|
|
852
|
-
|
|
853
|
-
|
|
970
|
+
// Only add close button for widget mode (not inline)
|
|
971
|
+
if (!isInline) {
|
|
972
|
+
this.closeButton = createElement('button', {
|
|
973
|
+
class: 'wabbit-chat-panel-close',
|
|
974
|
+
'aria-label': 'Close chat',
|
|
975
|
+
type: 'button'
|
|
976
|
+
});
|
|
977
|
+
this.closeButton.innerHTML = '×';
|
|
978
|
+
const closeHandler = this.options.onClose;
|
|
979
|
+
this.closeButton.addEventListener('click', closeHandler);
|
|
980
|
+
this.eventCleanup.push(() => {
|
|
981
|
+
this.closeButton?.removeEventListener('click', closeHandler);
|
|
982
|
+
});
|
|
983
|
+
header.appendChild(this.closeButton);
|
|
984
|
+
}
|
|
985
|
+
return header;
|
|
986
|
+
}
|
|
987
|
+
/**
|
|
988
|
+
* Create the input area element
|
|
989
|
+
*/
|
|
990
|
+
createInputArea() {
|
|
854
991
|
const inputArea = createElement('div', { class: 'wabbit-chat-input-area' });
|
|
855
992
|
const inputWrapper = createElement('div', { class: 'wabbit-chat-input-wrapper' });
|
|
856
993
|
this.inputElement = document.createElement('textarea');
|
|
@@ -858,17 +995,26 @@ class ChatPanel {
|
|
|
858
995
|
this.inputElement.placeholder = this.options.placeholder || 'Type your message...';
|
|
859
996
|
this.inputElement.rows = 1;
|
|
860
997
|
this.inputElement.disabled = this.options.disabled || false;
|
|
861
|
-
// Auto-resize textarea
|
|
862
|
-
|
|
998
|
+
// Auto-resize textarea and update send button state on input
|
|
999
|
+
const inputHandler = () => {
|
|
863
1000
|
this.inputElement.style.height = 'auto';
|
|
864
1001
|
this.inputElement.style.height = `${Math.min(this.inputElement.scrollHeight, 120)}px`;
|
|
1002
|
+
this.updateSendButtonState();
|
|
1003
|
+
};
|
|
1004
|
+
this.inputElement.addEventListener('input', inputHandler);
|
|
1005
|
+
this.eventCleanup.push(() => {
|
|
1006
|
+
this.inputElement?.removeEventListener('input', inputHandler);
|
|
865
1007
|
});
|
|
866
1008
|
// Send on Enter (Shift+Enter for new line)
|
|
867
|
-
|
|
1009
|
+
const keydownHandler = (e) => {
|
|
868
1010
|
if (e.key === 'Enter' && !e.shiftKey) {
|
|
869
1011
|
e.preventDefault();
|
|
870
1012
|
this.handleSend();
|
|
871
1013
|
}
|
|
1014
|
+
};
|
|
1015
|
+
this.inputElement.addEventListener('keydown', keydownHandler);
|
|
1016
|
+
this.eventCleanup.push(() => {
|
|
1017
|
+
this.inputElement?.removeEventListener('keydown', keydownHandler);
|
|
872
1018
|
});
|
|
873
1019
|
this.sendButton = createElement('button', {
|
|
874
1020
|
class: 'wabbit-chat-send-button',
|
|
@@ -881,23 +1027,16 @@ class ChatPanel {
|
|
|
881
1027
|
<polygon points="22 2 15 22 11 13 2 9 22 2"></polygon>
|
|
882
1028
|
</svg>
|
|
883
1029
|
`;
|
|
884
|
-
|
|
1030
|
+
const sendClickHandler = () => this.handleSend();
|
|
1031
|
+
this.sendButton.addEventListener('click', sendClickHandler);
|
|
1032
|
+
this.eventCleanup.push(() => {
|
|
1033
|
+
this.sendButton?.removeEventListener('click', sendClickHandler);
|
|
1034
|
+
});
|
|
885
1035
|
this.updateSendButtonState();
|
|
886
1036
|
inputWrapper.appendChild(this.inputElement);
|
|
887
1037
|
inputWrapper.appendChild(this.sendButton);
|
|
888
1038
|
inputArea.appendChild(inputWrapper);
|
|
889
|
-
|
|
890
|
-
this.element.appendChild(header);
|
|
891
|
-
this.element.appendChild(this.messagesContainer);
|
|
892
|
-
this.element.appendChild(inputArea);
|
|
893
|
-
document.body.appendChild(this.element);
|
|
894
|
-
// Initially hide the panel (it will be shown when opened)
|
|
895
|
-
this.element.style.display = 'none';
|
|
896
|
-
// Show welcome message if provided
|
|
897
|
-
if (this.options.welcomeMessage) {
|
|
898
|
-
this.addSystemMessage(this.options.welcomeMessage);
|
|
899
|
-
}
|
|
900
|
-
return this.element;
|
|
1039
|
+
return inputArea;
|
|
901
1040
|
}
|
|
902
1041
|
/**
|
|
903
1042
|
* Add a message to the panel
|
|
@@ -1020,7 +1159,12 @@ class ChatPanel {
|
|
|
1020
1159
|
return;
|
|
1021
1160
|
const hasText = this.inputElement.value.trim().length > 0;
|
|
1022
1161
|
const disabled = this.options.disabled || this.isWaitingForResponse || !hasText;
|
|
1023
|
-
|
|
1162
|
+
if (disabled) {
|
|
1163
|
+
this.sendButton.setAttribute('disabled', 'true');
|
|
1164
|
+
}
|
|
1165
|
+
else {
|
|
1166
|
+
this.sendButton.removeAttribute('disabled');
|
|
1167
|
+
}
|
|
1024
1168
|
if (this.sendButton instanceof HTMLButtonElement) {
|
|
1025
1169
|
this.sendButton.disabled = disabled;
|
|
1026
1170
|
}
|
|
@@ -1039,8 +1183,9 @@ class ChatPanel {
|
|
|
1039
1183
|
show() {
|
|
1040
1184
|
if (this.element) {
|
|
1041
1185
|
this.element.style.display = 'flex';
|
|
1042
|
-
// Focus input after a brief delay
|
|
1186
|
+
// Focus input and scroll to bottom after a brief delay
|
|
1043
1187
|
setTimeout(() => {
|
|
1188
|
+
this.scrollToBottom();
|
|
1044
1189
|
if (this.inputElement) {
|
|
1045
1190
|
this.inputElement.focus();
|
|
1046
1191
|
}
|
|
@@ -1056,15 +1201,19 @@ class ChatPanel {
|
|
|
1056
1201
|
}
|
|
1057
1202
|
}
|
|
1058
1203
|
/**
|
|
1059
|
-
* Remove the panel from DOM
|
|
1204
|
+
* Remove the panel from DOM and cleanup event listeners
|
|
1060
1205
|
*/
|
|
1061
1206
|
destroy() {
|
|
1207
|
+
// Run all event cleanup functions
|
|
1208
|
+
this.eventCleanup.forEach((cleanup) => cleanup());
|
|
1209
|
+
this.eventCleanup = [];
|
|
1062
1210
|
if (this.element) {
|
|
1063
1211
|
this.element.remove();
|
|
1064
1212
|
this.element = null;
|
|
1065
1213
|
this.messagesContainer = null;
|
|
1066
1214
|
this.inputElement = null;
|
|
1067
1215
|
this.sendButton = null;
|
|
1216
|
+
this.closeButton = null;
|
|
1068
1217
|
}
|
|
1069
1218
|
}
|
|
1070
1219
|
}
|
|
@@ -1528,6 +1677,47 @@ function injectChatStyles(primaryColor = '#6366f1', theme) {
|
|
|
1528
1677
|
.wabbit-chat-message {
|
|
1529
1678
|
animation: wabbit-fade-in 0.2s ease-out;
|
|
1530
1679
|
}
|
|
1680
|
+
|
|
1681
|
+
/* ========================================
|
|
1682
|
+
* INLINE MODE STYLES
|
|
1683
|
+
* ======================================== */
|
|
1684
|
+
|
|
1685
|
+
/* Inline Chat Panel - renders inside container instead of fixed position */
|
|
1686
|
+
.wabbit-chat-panel.wabbit-chat-panel-inline {
|
|
1687
|
+
position: relative !important;
|
|
1688
|
+
width: 100%;
|
|
1689
|
+
height: 100%;
|
|
1690
|
+
min-height: 400px;
|
|
1691
|
+
max-height: none;
|
|
1692
|
+
max-width: none;
|
|
1693
|
+
bottom: auto !important;
|
|
1694
|
+
right: auto !important;
|
|
1695
|
+
left: auto !important;
|
|
1696
|
+
border-radius: 12px;
|
|
1697
|
+
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.15);
|
|
1698
|
+
animation: none; /* Disable slide-in animation for inline */
|
|
1699
|
+
}
|
|
1700
|
+
|
|
1701
|
+
/* Inline mode messages area fills available space */
|
|
1702
|
+
.wabbit-chat-panel-inline .wabbit-chat-messages {
|
|
1703
|
+
flex: 1;
|
|
1704
|
+
min-height: 200px;
|
|
1705
|
+
}
|
|
1706
|
+
|
|
1707
|
+
/* Full-height variant for inline mode (used in standalone chat pages) */
|
|
1708
|
+
.wabbit-chat-panel-inline.wabbit-chat-panel-fullheight {
|
|
1709
|
+
height: 100vh;
|
|
1710
|
+
max-height: 100vh;
|
|
1711
|
+
border-radius: 0;
|
|
1712
|
+
}
|
|
1713
|
+
|
|
1714
|
+
/* Mobile responsive for inline mode */
|
|
1715
|
+
@media (max-width: 480px) {
|
|
1716
|
+
.wabbit-chat-panel.wabbit-chat-panel-inline {
|
|
1717
|
+
border-radius: 0;
|
|
1718
|
+
min-height: 300px;
|
|
1719
|
+
}
|
|
1720
|
+
}
|
|
1531
1721
|
`;
|
|
1532
1722
|
const style = document.createElement('style');
|
|
1533
1723
|
style.id = styleId;
|
|
@@ -1561,7 +1751,10 @@ class ChatWidget {
|
|
|
1561
1751
|
this.isOpen = false;
|
|
1562
1752
|
this.cleanup = [];
|
|
1563
1753
|
this.themeCleanup = null;
|
|
1754
|
+
this.onChatReadyCallback = null;
|
|
1755
|
+
this.chatReadyFired = false;
|
|
1564
1756
|
this.config = config;
|
|
1757
|
+
this.onChatReadyCallback = config.onChatReady || null;
|
|
1565
1758
|
}
|
|
1566
1759
|
/**
|
|
1567
1760
|
* Initialize the chat widget
|
|
@@ -1573,6 +1766,16 @@ class ChatWidget {
|
|
|
1573
1766
|
resolve();
|
|
1574
1767
|
});
|
|
1575
1768
|
});
|
|
1769
|
+
const isInlineMode = this.config.mode === 'inline';
|
|
1770
|
+
// Resolve container for inline mode
|
|
1771
|
+
let containerElement = null;
|
|
1772
|
+
if (isInlineMode) {
|
|
1773
|
+
containerElement = this.resolveContainer();
|
|
1774
|
+
if (!containerElement) {
|
|
1775
|
+
console.error(`[Wabbit] Container not found: ${this.config.container}`);
|
|
1776
|
+
return;
|
|
1777
|
+
}
|
|
1778
|
+
}
|
|
1576
1779
|
// Inject styles with theme configuration
|
|
1577
1780
|
injectChatStyles(this.config.primaryColor || '#6366f1', this.config.theme);
|
|
1578
1781
|
// Setup theme watcher if theme is 'auto'
|
|
@@ -1583,37 +1786,63 @@ class ChatWidget {
|
|
|
1583
1786
|
);
|
|
1584
1787
|
// Set up event handlers
|
|
1585
1788
|
this.setupWebSocketHandlers();
|
|
1586
|
-
//
|
|
1587
|
-
|
|
1588
|
-
|
|
1589
|
-
|
|
1590
|
-
|
|
1591
|
-
|
|
1789
|
+
// Only create bubble for widget mode (not inline)
|
|
1790
|
+
if (!isInlineMode) {
|
|
1791
|
+
this.bubble = new ChatBubble({
|
|
1792
|
+
position: this.config.position || 'bottom-right',
|
|
1793
|
+
onClick: () => this.toggle()
|
|
1794
|
+
});
|
|
1795
|
+
}
|
|
1796
|
+
// Create panel with mode-specific options
|
|
1592
1797
|
this.panel = new ChatPanel({
|
|
1593
1798
|
position: this.config.position || 'bottom-right',
|
|
1594
1799
|
welcomeMessage: this.config.welcomeMessage,
|
|
1595
1800
|
placeholder: this.config.placeholder,
|
|
1596
1801
|
onSendMessage: (content) => this.handleSendMessage(content),
|
|
1597
1802
|
onClose: () => this.close(),
|
|
1598
|
-
disabled: true
|
|
1803
|
+
disabled: true,
|
|
1804
|
+
mode: this.config.mode || 'widget',
|
|
1805
|
+
container: containerElement || undefined,
|
|
1806
|
+
showHeader: this.config.showHeader,
|
|
1807
|
+
headerTitle: this.config.headerTitle
|
|
1599
1808
|
});
|
|
1600
1809
|
// Render components
|
|
1601
|
-
this.bubble
|
|
1810
|
+
if (this.bubble) {
|
|
1811
|
+
this.bubble.render();
|
|
1812
|
+
}
|
|
1602
1813
|
this.panel.render();
|
|
1603
|
-
//
|
|
1604
|
-
|
|
1605
|
-
|
|
1606
|
-
this.
|
|
1814
|
+
// Set initial state based on mode
|
|
1815
|
+
if (isInlineMode) {
|
|
1816
|
+
// Inline mode: always visible, mark as open
|
|
1817
|
+
this.isOpen = true;
|
|
1607
1818
|
}
|
|
1608
|
-
|
|
1609
|
-
|
|
1819
|
+
else {
|
|
1820
|
+
// Widget mode: panel hidden, bubble visible
|
|
1821
|
+
if (this.panel) {
|
|
1822
|
+
this.panel.hide();
|
|
1823
|
+
}
|
|
1824
|
+
if (this.bubble) {
|
|
1825
|
+
this.bubble.show();
|
|
1826
|
+
}
|
|
1827
|
+
this.isOpen = false;
|
|
1828
|
+
// Handle trigger types (only for widget mode)
|
|
1829
|
+
this.handleTriggerType();
|
|
1610
1830
|
}
|
|
1611
|
-
this.isOpen = false; // Ensure state is consistent
|
|
1612
|
-
// Handle trigger types
|
|
1613
|
-
this.handleTriggerType();
|
|
1614
1831
|
// Connect WebSocket
|
|
1615
1832
|
await this.wsClient.connect();
|
|
1616
1833
|
}
|
|
1834
|
+
/**
|
|
1835
|
+
* Resolve container element for inline mode
|
|
1836
|
+
*/
|
|
1837
|
+
resolveContainer() {
|
|
1838
|
+
if (!this.config.container)
|
|
1839
|
+
return null;
|
|
1840
|
+
// Try as ID first (with or without # prefix), then as CSS selector
|
|
1841
|
+
const selector = this.config.container;
|
|
1842
|
+
const idWithoutHash = selector.startsWith('#') ? selector.slice(1) : selector;
|
|
1843
|
+
return document.getElementById(idWithoutHash) ||
|
|
1844
|
+
document.querySelector(selector);
|
|
1845
|
+
}
|
|
1617
1846
|
/**
|
|
1618
1847
|
* Setup WebSocket event handlers
|
|
1619
1848
|
*/
|
|
@@ -1628,6 +1857,18 @@ class ChatWidget {
|
|
|
1628
1857
|
this.panel.addSystemMessage(message);
|
|
1629
1858
|
}
|
|
1630
1859
|
}
|
|
1860
|
+
// Fire onChatReady callback once when chat is ready
|
|
1861
|
+
// Use requestAnimationFrame to ensure the panel DOM is fully painted
|
|
1862
|
+
// before the callback runs (important for sending initial messages)
|
|
1863
|
+
if (!this.chatReadyFired && this.onChatReadyCallback) {
|
|
1864
|
+
this.chatReadyFired = true;
|
|
1865
|
+
requestAnimationFrame(() => {
|
|
1866
|
+
console.log('[Wabbit] Chat ready, firing onChatReady callback');
|
|
1867
|
+
if (this.onChatReadyCallback) {
|
|
1868
|
+
this.onChatReadyCallback();
|
|
1869
|
+
}
|
|
1870
|
+
});
|
|
1871
|
+
}
|
|
1631
1872
|
};
|
|
1632
1873
|
this.wsClient.onMessageHistory = (messages) => {
|
|
1633
1874
|
console.log('[Wabbit] Loaded message history:', messages.length);
|
|
@@ -1726,9 +1967,12 @@ class ChatWidget {
|
|
|
1726
1967
|
this.wsClient.sendMessage(content);
|
|
1727
1968
|
}
|
|
1728
1969
|
/**
|
|
1729
|
-
* Open the chat panel
|
|
1970
|
+
* Open the chat panel (no-op in inline mode)
|
|
1730
1971
|
*/
|
|
1731
1972
|
open() {
|
|
1973
|
+
// Inline mode is always open
|
|
1974
|
+
if (this.config.mode === 'inline')
|
|
1975
|
+
return;
|
|
1732
1976
|
if (this.isOpen)
|
|
1733
1977
|
return;
|
|
1734
1978
|
this.isOpen = true;
|
|
@@ -1740,9 +1984,12 @@ class ChatWidget {
|
|
|
1740
1984
|
}
|
|
1741
1985
|
}
|
|
1742
1986
|
/**
|
|
1743
|
-
* Close the chat panel
|
|
1987
|
+
* Close the chat panel (no-op in inline mode)
|
|
1744
1988
|
*/
|
|
1745
1989
|
close() {
|
|
1990
|
+
// Inline mode cannot be closed
|
|
1991
|
+
if (this.config.mode === 'inline')
|
|
1992
|
+
return;
|
|
1746
1993
|
if (!this.isOpen)
|
|
1747
1994
|
return;
|
|
1748
1995
|
this.isOpen = false;
|
|
@@ -1754,9 +2001,12 @@ class ChatWidget {
|
|
|
1754
2001
|
}
|
|
1755
2002
|
}
|
|
1756
2003
|
/**
|
|
1757
|
-
* Toggle chat panel
|
|
2004
|
+
* Toggle chat panel (no-op in inline mode)
|
|
1758
2005
|
*/
|
|
1759
2006
|
toggle() {
|
|
2007
|
+
// Inline mode cannot be toggled
|
|
2008
|
+
if (this.config.mode === 'inline')
|
|
2009
|
+
return;
|
|
1760
2010
|
if (this.isOpen) {
|
|
1761
2011
|
this.close();
|
|
1762
2012
|
}
|
|
@@ -3562,12 +3812,43 @@ function getInstance() {
|
|
|
3562
3812
|
function destroy() {
|
|
3563
3813
|
return Wabbit.destroy();
|
|
3564
3814
|
}
|
|
3815
|
+
/**
|
|
3816
|
+
* Get the URL for a dedicated chat page
|
|
3817
|
+
*
|
|
3818
|
+
* @param collectionId - The collection ID
|
|
3819
|
+
* @param options - Optional parameters
|
|
3820
|
+
* @returns Full URL to the chat page
|
|
3821
|
+
*
|
|
3822
|
+
* @example
|
|
3823
|
+
* ```typescript
|
|
3824
|
+
* const url = Wabbit.getChatPageUrl('abc123', { initialMessage: 'Hello!' });
|
|
3825
|
+
* ```
|
|
3826
|
+
*/
|
|
3827
|
+
function getChatPageUrl(collectionId, options) {
|
|
3828
|
+
return Wabbit.getChatPageUrl(collectionId, options);
|
|
3829
|
+
}
|
|
3830
|
+
/**
|
|
3831
|
+
* Open chat in a new tab/window
|
|
3832
|
+
*
|
|
3833
|
+
* @param collectionId - The collection ID
|
|
3834
|
+
* @param options - Optional parameters
|
|
3835
|
+
*
|
|
3836
|
+
* @example
|
|
3837
|
+
* ```typescript
|
|
3838
|
+
* Wabbit.openChatPage('abc123', { initialMessage: 'Hello!' });
|
|
3839
|
+
* ```
|
|
3840
|
+
*/
|
|
3841
|
+
function openChatPage(collectionId, options) {
|
|
3842
|
+
return Wabbit.openChatPage(collectionId, options);
|
|
3843
|
+
}
|
|
3565
3844
|
// Create an object as default export (for ESM/CJS: import Wabbit from '@wabbit-dashboard/embed')
|
|
3566
3845
|
// Note: For UMD, we don't use default export, but directly use named exports
|
|
3567
3846
|
const WabbitSDK = {
|
|
3568
3847
|
init,
|
|
3569
3848
|
getInstance,
|
|
3570
|
-
destroy
|
|
3849
|
+
destroy,
|
|
3850
|
+
getChatPageUrl,
|
|
3851
|
+
openChatPage
|
|
3571
3852
|
};
|
|
3572
3853
|
|
|
3573
3854
|
exports.ApiClient = ApiClient;
|
|
@@ -3588,10 +3869,12 @@ exports.default = WabbitSDK;
|
|
|
3588
3869
|
exports.destroy = destroy;
|
|
3589
3870
|
exports.detectTheme = detectTheme;
|
|
3590
3871
|
exports.escapeHtml = escapeHtml;
|
|
3872
|
+
exports.getChatPageUrl = getChatPageUrl;
|
|
3591
3873
|
exports.getInstance = getInstance;
|
|
3592
3874
|
exports.init = init;
|
|
3593
3875
|
exports.mergeConfig = mergeConfig;
|
|
3594
3876
|
exports.onDOMReady = onDOMReady;
|
|
3877
|
+
exports.openChatPage = openChatPage;
|
|
3595
3878
|
exports.storage = storage;
|
|
3596
3879
|
exports.validateConfig = validateConfig;
|
|
3597
3880
|
exports.watchTheme = watchTheme;
|