@wabbit-dashboard/embed 1.0.9 → 1.0.11

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 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
- **Local Development:**
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>
@@ -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,21 @@ 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
175
195
  };
176
196
  const chatWidget = new ChatWidget(chatConfig);
177
197
  chatWidget.init();
@@ -181,6 +201,11 @@ class Wabbit {
181
201
  if (mergedConfig.forms?.enabled && mergedConfig.forms) {
182
202
  // Import FormWidget dynamically to avoid circular dependencies
183
203
  Promise.resolve().then(function () { return FormWidget$1; }).then(({ FormWidget }) => {
204
+ // Check if instance was destroyed during async import
205
+ if (!Wabbit.instance) {
206
+ console.warn('[Wabbit] Instance was destroyed before form widget could initialize');
207
+ return;
208
+ }
184
209
  const forms = mergedConfig.forms;
185
210
  const formConfig = {
186
211
  enabled: forms.enabled,
@@ -205,6 +230,11 @@ class Wabbit {
205
230
  if (mergedConfig.emailCapture?.enabled) {
206
231
  const emailCapture = mergedConfig.emailCapture;
207
232
  Promise.resolve().then(function () { return EmailCaptureWidget$1; }).then(({ EmailCaptureWidget }) => {
233
+ // Check if instance was destroyed during async import
234
+ if (!Wabbit.instance) {
235
+ console.warn('[Wabbit] Instance was destroyed before email capture widget could initialize');
236
+ return;
237
+ }
208
238
  const emailCaptureConfig = {
209
239
  enabled: emailCapture.enabled,
210
240
  triggerAfterMessages: emailCapture.triggerAfterMessages,
@@ -219,7 +249,7 @@ class Wabbit {
219
249
  // Connect to chat widget after it's initialized
220
250
  // Use a small delay to ensure chat widget is ready
221
251
  setTimeout(() => {
222
- if (Wabbit.instance.chatWidget) {
252
+ if (Wabbit.instance?.chatWidget) {
223
253
  const chatWidget = Wabbit.instance.chatWidget;
224
254
  // Set WebSocket client
225
255
  if (chatWidget.wsClient) {
@@ -239,7 +269,9 @@ class Wabbit {
239
269
  });
240
270
  }
241
271
  // Auto-initialize forms with data-wabbit-form-id (backward compatibility)
242
- Wabbit.instance.initLegacyForms(config);
272
+ if (Wabbit.instance) {
273
+ Wabbit.instance.initLegacyForms(config);
274
+ }
243
275
  // Call onReady callback if provided
244
276
  if (config.onReady) {
245
277
  // Wait for DOM to be ready
@@ -293,6 +325,60 @@ class Wabbit {
293
325
  Wabbit.instance = null;
294
326
  }
295
327
  }
328
+ /**
329
+ * Get the URL for a dedicated chat page
330
+ *
331
+ * @param collectionId - The collection ID
332
+ * @param options - Optional parameters
333
+ * @returns Full URL to the chat page
334
+ *
335
+ * @example
336
+ * ```typescript
337
+ * const url = Wabbit.getChatPageUrl('abc123', {
338
+ * initialMessage: 'How do I get started?',
339
+ * theme: 'dark'
340
+ * });
341
+ * // Returns: https://platform.insourcedata.ai/c/abc123?q=How%20do%20I%20get%20started%3F&theme=dark
342
+ * ```
343
+ */
344
+ static getChatPageUrl(collectionId, options) {
345
+ // Determine base URL
346
+ const baseUrl = options?.baseUrl ||
347
+ (typeof window !== 'undefined' && window.WABBIT_BASE_URL) ||
348
+ (typeof window !== 'undefined' &&
349
+ (window.location.hostname === 'localhost' ||
350
+ window.location.hostname === '127.0.0.1')
351
+ ? 'http://localhost:3000'
352
+ : 'https://platform.insourcedata.ai');
353
+ const url = new URL(`/c/${collectionId}`, baseUrl);
354
+ // Add query parameters
355
+ if (options?.initialMessage) {
356
+ url.searchParams.set('q', options.initialMessage);
357
+ }
358
+ if (options?.theme && options.theme !== 'auto') {
359
+ url.searchParams.set('theme', options.theme);
360
+ }
361
+ if (options?.primaryColor) {
362
+ url.searchParams.set('color', options.primaryColor.replace('#', ''));
363
+ }
364
+ return url.toString();
365
+ }
366
+ /**
367
+ * Open chat in a new tab/window
368
+ *
369
+ * @param collectionId - The collection ID
370
+ * @param options - Optional parameters (same as getChatPageUrl)
371
+ *
372
+ * @example
373
+ * ```typescript
374
+ * // Open chat page with initial message
375
+ * Wabbit.openChatPage('abc123', { initialMessage: 'Hello!' });
376
+ * ```
377
+ */
378
+ static openChatPage(collectionId, options) {
379
+ const url = Wabbit.getChatPageUrl(collectionId, options);
380
+ window.open(url, '_blank', 'noopener,noreferrer');
381
+ }
296
382
  /**
297
383
  * Get current configuration
298
384
  */
@@ -812,6 +898,8 @@ class ChatPanel {
812
898
  this.sendButton = null;
813
899
  this.messages = [];
814
900
  this.isWaitingForResponse = false;
901
+ this.closeButton = null;
902
+ this.eventCleanup = [];
815
903
  this.options = options;
816
904
  }
817
905
  /**
@@ -821,36 +909,81 @@ class ChatPanel {
821
909
  if (this.element) {
822
910
  return this.element;
823
911
  }
824
- // Main panel
912
+ const isInline = this.options.mode === 'inline';
913
+ // Main panel - use different class for inline mode
825
914
  this.element = createElement('div', {
826
- class: `wabbit-chat-panel ${this.options.position}`
915
+ class: isInline
916
+ ? 'wabbit-chat-panel wabbit-chat-panel-inline'
917
+ : `wabbit-chat-panel ${this.options.position}`
827
918
  });
828
- // Header
919
+ // Header (conditionally shown based on showHeader option, default: true)
920
+ if (this.options.showHeader !== false) {
921
+ const header = this.createHeader(isInline);
922
+ this.element.appendChild(header);
923
+ }
924
+ // Messages area
925
+ this.messagesContainer = createElement('div', { class: 'wabbit-chat-messages' });
926
+ // Input area
927
+ const inputArea = this.createInputArea();
928
+ // Assemble panel
929
+ this.element.appendChild(this.messagesContainer);
930
+ this.element.appendChild(inputArea);
931
+ // Append to container (inline) or document.body (widget)
932
+ if (isInline && this.options.container) {
933
+ this.options.container.appendChild(this.element);
934
+ // In inline mode, always visible
935
+ this.element.style.display = 'flex';
936
+ }
937
+ else {
938
+ document.body.appendChild(this.element);
939
+ // In widget mode, initially hidden until opened
940
+ this.element.style.display = 'none';
941
+ }
942
+ // Show welcome message if provided
943
+ if (this.options.welcomeMessage) {
944
+ this.addSystemMessage(this.options.welcomeMessage);
945
+ }
946
+ return this.element;
947
+ }
948
+ /**
949
+ * Create the header element
950
+ */
951
+ createHeader(isInline) {
829
952
  const header = createElement('div', { class: 'wabbit-chat-panel-header' });
830
953
  const headerTitle = createElement('div', { class: 'wabbit-chat-panel-header-title' });
831
954
  const headerIcon = createElement('div', { class: 'wabbit-chat-panel-header-icon' });
832
955
  headerIcon.textContent = 'AI';
833
956
  const headerText = createElement('div', { class: 'wabbit-chat-panel-header-text' });
834
957
  const headerH3 = createElement('h3');
835
- headerH3.textContent = 'AI Assistant';
958
+ headerH3.textContent = this.options.headerTitle || 'AI Assistant';
836
959
  const headerP = createElement('p');
837
960
  headerP.textContent = 'Powered by Wabbit';
838
961
  headerText.appendChild(headerH3);
839
962
  headerText.appendChild(headerP);
840
963
  headerTitle.appendChild(headerIcon);
841
964
  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
965
  header.appendChild(headerTitle);
850
- header.appendChild(closeButton);
851
- // Messages area
852
- this.messagesContainer = createElement('div', { class: 'wabbit-chat-messages' });
853
- // Input area
966
+ // Only add close button for widget mode (not inline)
967
+ if (!isInline) {
968
+ this.closeButton = createElement('button', {
969
+ class: 'wabbit-chat-panel-close',
970
+ 'aria-label': 'Close chat',
971
+ type: 'button'
972
+ });
973
+ this.closeButton.innerHTML = '×';
974
+ const closeHandler = this.options.onClose;
975
+ this.closeButton.addEventListener('click', closeHandler);
976
+ this.eventCleanup.push(() => {
977
+ this.closeButton?.removeEventListener('click', closeHandler);
978
+ });
979
+ header.appendChild(this.closeButton);
980
+ }
981
+ return header;
982
+ }
983
+ /**
984
+ * Create the input area element
985
+ */
986
+ createInputArea() {
854
987
  const inputArea = createElement('div', { class: 'wabbit-chat-input-area' });
855
988
  const inputWrapper = createElement('div', { class: 'wabbit-chat-input-wrapper' });
856
989
  this.inputElement = document.createElement('textarea');
@@ -858,17 +991,26 @@ class ChatPanel {
858
991
  this.inputElement.placeholder = this.options.placeholder || 'Type your message...';
859
992
  this.inputElement.rows = 1;
860
993
  this.inputElement.disabled = this.options.disabled || false;
861
- // Auto-resize textarea
862
- this.inputElement.addEventListener('input', () => {
994
+ // Auto-resize textarea and update send button state on input
995
+ const inputHandler = () => {
863
996
  this.inputElement.style.height = 'auto';
864
997
  this.inputElement.style.height = `${Math.min(this.inputElement.scrollHeight, 120)}px`;
998
+ this.updateSendButtonState();
999
+ };
1000
+ this.inputElement.addEventListener('input', inputHandler);
1001
+ this.eventCleanup.push(() => {
1002
+ this.inputElement?.removeEventListener('input', inputHandler);
865
1003
  });
866
1004
  // Send on Enter (Shift+Enter for new line)
867
- this.inputElement.addEventListener('keydown', (e) => {
1005
+ const keydownHandler = (e) => {
868
1006
  if (e.key === 'Enter' && !e.shiftKey) {
869
1007
  e.preventDefault();
870
1008
  this.handleSend();
871
1009
  }
1010
+ };
1011
+ this.inputElement.addEventListener('keydown', keydownHandler);
1012
+ this.eventCleanup.push(() => {
1013
+ this.inputElement?.removeEventListener('keydown', keydownHandler);
872
1014
  });
873
1015
  this.sendButton = createElement('button', {
874
1016
  class: 'wabbit-chat-send-button',
@@ -881,23 +1023,16 @@ class ChatPanel {
881
1023
  <polygon points="22 2 15 22 11 13 2 9 22 2"></polygon>
882
1024
  </svg>
883
1025
  `;
884
- this.sendButton.addEventListener('click', () => this.handleSend());
1026
+ const sendClickHandler = () => this.handleSend();
1027
+ this.sendButton.addEventListener('click', sendClickHandler);
1028
+ this.eventCleanup.push(() => {
1029
+ this.sendButton?.removeEventListener('click', sendClickHandler);
1030
+ });
885
1031
  this.updateSendButtonState();
886
1032
  inputWrapper.appendChild(this.inputElement);
887
1033
  inputWrapper.appendChild(this.sendButton);
888
1034
  inputArea.appendChild(inputWrapper);
889
- // Assemble panel
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;
1035
+ return inputArea;
901
1036
  }
902
1037
  /**
903
1038
  * Add a message to the panel
@@ -1056,15 +1191,19 @@ class ChatPanel {
1056
1191
  }
1057
1192
  }
1058
1193
  /**
1059
- * Remove the panel from DOM
1194
+ * Remove the panel from DOM and cleanup event listeners
1060
1195
  */
1061
1196
  destroy() {
1197
+ // Run all event cleanup functions
1198
+ this.eventCleanup.forEach((cleanup) => cleanup());
1199
+ this.eventCleanup = [];
1062
1200
  if (this.element) {
1063
1201
  this.element.remove();
1064
1202
  this.element = null;
1065
1203
  this.messagesContainer = null;
1066
1204
  this.inputElement = null;
1067
1205
  this.sendButton = null;
1206
+ this.closeButton = null;
1068
1207
  }
1069
1208
  }
1070
1209
  }
@@ -1528,6 +1667,47 @@ function injectChatStyles(primaryColor = '#6366f1', theme) {
1528
1667
  .wabbit-chat-message {
1529
1668
  animation: wabbit-fade-in 0.2s ease-out;
1530
1669
  }
1670
+
1671
+ /* ========================================
1672
+ * INLINE MODE STYLES
1673
+ * ======================================== */
1674
+
1675
+ /* Inline Chat Panel - renders inside container instead of fixed position */
1676
+ .wabbit-chat-panel.wabbit-chat-panel-inline {
1677
+ position: relative !important;
1678
+ width: 100%;
1679
+ height: 100%;
1680
+ min-height: 400px;
1681
+ max-height: none;
1682
+ max-width: none;
1683
+ bottom: auto !important;
1684
+ right: auto !important;
1685
+ left: auto !important;
1686
+ border-radius: 12px;
1687
+ box-shadow: 0 4px 12px rgba(0, 0, 0, 0.15);
1688
+ animation: none; /* Disable slide-in animation for inline */
1689
+ }
1690
+
1691
+ /* Inline mode messages area fills available space */
1692
+ .wabbit-chat-panel-inline .wabbit-chat-messages {
1693
+ flex: 1;
1694
+ min-height: 200px;
1695
+ }
1696
+
1697
+ /* Full-height variant for inline mode (used in standalone chat pages) */
1698
+ .wabbit-chat-panel-inline.wabbit-chat-panel-fullheight {
1699
+ height: 100vh;
1700
+ max-height: 100vh;
1701
+ border-radius: 0;
1702
+ }
1703
+
1704
+ /* Mobile responsive for inline mode */
1705
+ @media (max-width: 480px) {
1706
+ .wabbit-chat-panel.wabbit-chat-panel-inline {
1707
+ border-radius: 0;
1708
+ min-height: 300px;
1709
+ }
1710
+ }
1531
1711
  `;
1532
1712
  const style = document.createElement('style');
1533
1713
  style.id = styleId;
@@ -1573,6 +1753,16 @@ class ChatWidget {
1573
1753
  resolve();
1574
1754
  });
1575
1755
  });
1756
+ const isInlineMode = this.config.mode === 'inline';
1757
+ // Resolve container for inline mode
1758
+ let containerElement = null;
1759
+ if (isInlineMode) {
1760
+ containerElement = this.resolveContainer();
1761
+ if (!containerElement) {
1762
+ console.error(`[Wabbit] Container not found: ${this.config.container}`);
1763
+ return;
1764
+ }
1765
+ }
1576
1766
  // Inject styles with theme configuration
1577
1767
  injectChatStyles(this.config.primaryColor || '#6366f1', this.config.theme);
1578
1768
  // Setup theme watcher if theme is 'auto'
@@ -1583,37 +1773,63 @@ class ChatWidget {
1583
1773
  );
1584
1774
  // Set up event handlers
1585
1775
  this.setupWebSocketHandlers();
1586
- // Create bubble
1587
- this.bubble = new ChatBubble({
1588
- position: this.config.position || 'bottom-right',
1589
- onClick: () => this.toggle()
1590
- });
1591
- // Create panel
1776
+ // Only create bubble for widget mode (not inline)
1777
+ if (!isInlineMode) {
1778
+ this.bubble = new ChatBubble({
1779
+ position: this.config.position || 'bottom-right',
1780
+ onClick: () => this.toggle()
1781
+ });
1782
+ }
1783
+ // Create panel with mode-specific options
1592
1784
  this.panel = new ChatPanel({
1593
1785
  position: this.config.position || 'bottom-right',
1594
1786
  welcomeMessage: this.config.welcomeMessage,
1595
1787
  placeholder: this.config.placeholder,
1596
1788
  onSendMessage: (content) => this.handleSendMessage(content),
1597
1789
  onClose: () => this.close(),
1598
- disabled: true
1790
+ disabled: true,
1791
+ mode: this.config.mode || 'widget',
1792
+ container: containerElement || undefined,
1793
+ showHeader: this.config.showHeader,
1794
+ headerTitle: this.config.headerTitle
1599
1795
  });
1600
1796
  // Render components
1601
- this.bubble.render();
1797
+ if (this.bubble) {
1798
+ this.bubble.render();
1799
+ }
1602
1800
  this.panel.render();
1603
- // Ensure initial state: panel hidden, bubble visible
1604
- // This prevents state mismatch where panel might be visible but isOpen is false
1605
- if (this.panel) {
1606
- this.panel.hide();
1801
+ // Set initial state based on mode
1802
+ if (isInlineMode) {
1803
+ // Inline mode: always visible, mark as open
1804
+ this.isOpen = true;
1607
1805
  }
1608
- if (this.bubble) {
1609
- this.bubble.show();
1806
+ else {
1807
+ // Widget mode: panel hidden, bubble visible
1808
+ if (this.panel) {
1809
+ this.panel.hide();
1810
+ }
1811
+ if (this.bubble) {
1812
+ this.bubble.show();
1813
+ }
1814
+ this.isOpen = false;
1815
+ // Handle trigger types (only for widget mode)
1816
+ this.handleTriggerType();
1610
1817
  }
1611
- this.isOpen = false; // Ensure state is consistent
1612
- // Handle trigger types
1613
- this.handleTriggerType();
1614
1818
  // Connect WebSocket
1615
1819
  await this.wsClient.connect();
1616
1820
  }
1821
+ /**
1822
+ * Resolve container element for inline mode
1823
+ */
1824
+ resolveContainer() {
1825
+ if (!this.config.container)
1826
+ return null;
1827
+ // Try as ID first (with or without # prefix), then as CSS selector
1828
+ const selector = this.config.container;
1829
+ const idWithoutHash = selector.startsWith('#') ? selector.slice(1) : selector;
1830
+ return document.getElementById(idWithoutHash) ||
1831
+ document.querySelector(selector);
1832
+ }
1617
1833
  /**
1618
1834
  * Setup WebSocket event handlers
1619
1835
  */
@@ -1726,9 +1942,12 @@ class ChatWidget {
1726
1942
  this.wsClient.sendMessage(content);
1727
1943
  }
1728
1944
  /**
1729
- * Open the chat panel
1945
+ * Open the chat panel (no-op in inline mode)
1730
1946
  */
1731
1947
  open() {
1948
+ // Inline mode is always open
1949
+ if (this.config.mode === 'inline')
1950
+ return;
1732
1951
  if (this.isOpen)
1733
1952
  return;
1734
1953
  this.isOpen = true;
@@ -1740,9 +1959,12 @@ class ChatWidget {
1740
1959
  }
1741
1960
  }
1742
1961
  /**
1743
- * Close the chat panel
1962
+ * Close the chat panel (no-op in inline mode)
1744
1963
  */
1745
1964
  close() {
1965
+ // Inline mode cannot be closed
1966
+ if (this.config.mode === 'inline')
1967
+ return;
1746
1968
  if (!this.isOpen)
1747
1969
  return;
1748
1970
  this.isOpen = false;
@@ -1754,9 +1976,12 @@ class ChatWidget {
1754
1976
  }
1755
1977
  }
1756
1978
  /**
1757
- * Toggle chat panel
1979
+ * Toggle chat panel (no-op in inline mode)
1758
1980
  */
1759
1981
  toggle() {
1982
+ // Inline mode cannot be toggled
1983
+ if (this.config.mode === 'inline')
1984
+ return;
1760
1985
  if (this.isOpen) {
1761
1986
  this.close();
1762
1987
  }
@@ -3562,12 +3787,43 @@ function getInstance() {
3562
3787
  function destroy() {
3563
3788
  return Wabbit.destroy();
3564
3789
  }
3790
+ /**
3791
+ * Get the URL for a dedicated chat page
3792
+ *
3793
+ * @param collectionId - The collection ID
3794
+ * @param options - Optional parameters
3795
+ * @returns Full URL to the chat page
3796
+ *
3797
+ * @example
3798
+ * ```typescript
3799
+ * const url = Wabbit.getChatPageUrl('abc123', { initialMessage: 'Hello!' });
3800
+ * ```
3801
+ */
3802
+ function getChatPageUrl(collectionId, options) {
3803
+ return Wabbit.getChatPageUrl(collectionId, options);
3804
+ }
3805
+ /**
3806
+ * Open chat in a new tab/window
3807
+ *
3808
+ * @param collectionId - The collection ID
3809
+ * @param options - Optional parameters
3810
+ *
3811
+ * @example
3812
+ * ```typescript
3813
+ * Wabbit.openChatPage('abc123', { initialMessage: 'Hello!' });
3814
+ * ```
3815
+ */
3816
+ function openChatPage(collectionId, options) {
3817
+ return Wabbit.openChatPage(collectionId, options);
3818
+ }
3565
3819
  // Create an object as default export (for ESM/CJS: import Wabbit from '@wabbit-dashboard/embed')
3566
3820
  // Note: For UMD, we don't use default export, but directly use named exports
3567
3821
  const WabbitSDK = {
3568
3822
  init,
3569
3823
  getInstance,
3570
- destroy
3824
+ destroy,
3825
+ getChatPageUrl,
3826
+ openChatPage
3571
3827
  };
3572
3828
 
3573
3829
  exports.ApiClient = ApiClient;
@@ -3588,10 +3844,12 @@ exports.default = WabbitSDK;
3588
3844
  exports.destroy = destroy;
3589
3845
  exports.detectTheme = detectTheme;
3590
3846
  exports.escapeHtml = escapeHtml;
3847
+ exports.getChatPageUrl = getChatPageUrl;
3591
3848
  exports.getInstance = getInstance;
3592
3849
  exports.init = init;
3593
3850
  exports.mergeConfig = mergeConfig;
3594
3851
  exports.onDOMReady = onDOMReady;
3852
+ exports.openChatPage = openChatPage;
3595
3853
  exports.storage = storage;
3596
3854
  exports.validateConfig = validateConfig;
3597
3855
  exports.watchTheme = watchTheme;