@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.
@@ -19,6 +19,10 @@ function validateConfig(config) {
19
19
  if (!config.chat.collectionId) {
20
20
  throw new Error('[Wabbit] collectionId is required when chat is enabled');
21
21
  }
22
+ // Validate inline mode requires a container
23
+ if (config.chat.mode === 'inline' && !config.chat.container) {
24
+ throw new Error('[Wabbit] container is required when chat mode is "inline"');
25
+ }
22
26
  }
23
27
  // Validate forms config if enabled
24
28
  if (config.forms?.enabled) {
@@ -86,11 +90,14 @@ function mergeConfig(config) {
86
90
  chat: config.chat
87
91
  ? {
88
92
  ...config.chat,
93
+ mode: config.chat.mode || 'widget',
89
94
  position: config.chat.position || 'bottom-right',
90
95
  triggerType: config.chat.triggerType || 'button',
91
96
  theme: config.chat.theme || 'auto',
92
97
  primaryColor: config.chat.primaryColor || '#6366f1',
93
- placeholder: config.chat.placeholder || 'Type your message...'
98
+ placeholder: config.chat.placeholder || 'Type your message...',
99
+ showHeader: config.chat.showHeader ?? true,
100
+ headerTitle: config.chat.headerTitle || 'AI Assistant'
94
101
  }
95
102
  : undefined,
96
103
  forms: config.forms
@@ -153,6 +160,11 @@ class Wabbit {
153
160
  if (mergedConfig.chat?.enabled && mergedConfig.chat) {
154
161
  // Import ChatWidget dynamically to avoid circular dependencies
155
162
  Promise.resolve().then(function () { return ChatWidget$1; }).then(({ ChatWidget }) => {
163
+ // Check if instance was destroyed during async import (e.g., React StrictMode)
164
+ if (!Wabbit.instance) {
165
+ console.warn('[Wabbit] Instance was destroyed before chat widget could initialize');
166
+ return;
167
+ }
156
168
  const chat = mergedConfig.chat;
157
169
  const chatConfig = {
158
170
  enabled: chat.enabled,
@@ -161,13 +173,23 @@ class Wabbit {
161
173
  apiKey: chat.apiKey || mergedConfig.apiKey,
162
174
  apiUrl: chat.apiUrl || mergedConfig.apiUrl,
163
175
  wsUrl: mergedConfig.wsUrl || chat.wsUrl, // Use global wsUrl or chat-specific wsUrl
176
+ // Embedding mode
177
+ mode: chat.mode,
178
+ container: chat.container,
179
+ // Widget mode options
164
180
  position: chat.position,
165
181
  triggerType: chat.triggerType,
166
182
  triggerDelay: chat.triggerDelay,
183
+ // Appearance options
167
184
  theme: chat.theme,
168
185
  primaryColor: chat.primaryColor,
169
186
  welcomeMessage: chat.welcomeMessage,
170
- placeholder: chat.placeholder
187
+ placeholder: chat.placeholder,
188
+ // Header customization
189
+ showHeader: chat.showHeader,
190
+ headerTitle: chat.headerTitle,
191
+ // Callbacks - prefer chat-specific, fallback to global
192
+ onChatReady: chat.onChatReady || config.onChatReady
171
193
  };
172
194
  const chatWidget = new ChatWidget(chatConfig);
173
195
  chatWidget.init();
@@ -177,6 +199,11 @@ class Wabbit {
177
199
  if (mergedConfig.forms?.enabled && mergedConfig.forms) {
178
200
  // Import FormWidget dynamically to avoid circular dependencies
179
201
  Promise.resolve().then(function () { return FormWidget$1; }).then(({ FormWidget }) => {
202
+ // Check if instance was destroyed during async import
203
+ if (!Wabbit.instance) {
204
+ console.warn('[Wabbit] Instance was destroyed before form widget could initialize');
205
+ return;
206
+ }
180
207
  const forms = mergedConfig.forms;
181
208
  const formConfig = {
182
209
  enabled: forms.enabled,
@@ -201,6 +228,11 @@ class Wabbit {
201
228
  if (mergedConfig.emailCapture?.enabled) {
202
229
  const emailCapture = mergedConfig.emailCapture;
203
230
  Promise.resolve().then(function () { return EmailCaptureWidget$1; }).then(({ EmailCaptureWidget }) => {
231
+ // Check if instance was destroyed during async import
232
+ if (!Wabbit.instance) {
233
+ console.warn('[Wabbit] Instance was destroyed before email capture widget could initialize');
234
+ return;
235
+ }
204
236
  const emailCaptureConfig = {
205
237
  enabled: emailCapture.enabled,
206
238
  triggerAfterMessages: emailCapture.triggerAfterMessages,
@@ -215,7 +247,7 @@ class Wabbit {
215
247
  // Connect to chat widget after it's initialized
216
248
  // Use a small delay to ensure chat widget is ready
217
249
  setTimeout(() => {
218
- if (Wabbit.instance.chatWidget) {
250
+ if (Wabbit.instance?.chatWidget) {
219
251
  const chatWidget = Wabbit.instance.chatWidget;
220
252
  // Set WebSocket client
221
253
  if (chatWidget.wsClient) {
@@ -235,7 +267,9 @@ class Wabbit {
235
267
  });
236
268
  }
237
269
  // Auto-initialize forms with data-wabbit-form-id (backward compatibility)
238
- Wabbit.instance.initLegacyForms(config);
270
+ if (Wabbit.instance) {
271
+ Wabbit.instance.initLegacyForms(config);
272
+ }
239
273
  // Call onReady callback if provided
240
274
  if (config.onReady) {
241
275
  // Wait for DOM to be ready
@@ -289,6 +323,60 @@ class Wabbit {
289
323
  Wabbit.instance = null;
290
324
  }
291
325
  }
326
+ /**
327
+ * Get the URL for a dedicated chat page
328
+ *
329
+ * @param collectionId - The collection ID
330
+ * @param options - Optional parameters
331
+ * @returns Full URL to the chat page
332
+ *
333
+ * @example
334
+ * ```typescript
335
+ * const url = Wabbit.getChatPageUrl('abc123', {
336
+ * initialMessage: 'How do I get started?',
337
+ * theme: 'dark'
338
+ * });
339
+ * // Returns: https://platform.insourcedata.ai/c/abc123?q=How%20do%20I%20get%20started%3F&theme=dark
340
+ * ```
341
+ */
342
+ static getChatPageUrl(collectionId, options) {
343
+ // Determine base URL
344
+ const baseUrl = options?.baseUrl ||
345
+ (typeof window !== 'undefined' && window.WABBIT_BASE_URL) ||
346
+ (typeof window !== 'undefined' &&
347
+ (window.location.hostname === 'localhost' ||
348
+ window.location.hostname === '127.0.0.1')
349
+ ? 'http://localhost:3000'
350
+ : 'https://platform.insourcedata.ai');
351
+ const url = new URL(`/c/${collectionId}`, baseUrl);
352
+ // Add query parameters
353
+ if (options?.initialMessage) {
354
+ url.searchParams.set('q', options.initialMessage);
355
+ }
356
+ if (options?.theme && options.theme !== 'auto') {
357
+ url.searchParams.set('theme', options.theme);
358
+ }
359
+ if (options?.primaryColor) {
360
+ url.searchParams.set('color', options.primaryColor.replace('#', ''));
361
+ }
362
+ return url.toString();
363
+ }
364
+ /**
365
+ * Open chat in a new tab/window
366
+ *
367
+ * @param collectionId - The collection ID
368
+ * @param options - Optional parameters (same as getChatPageUrl)
369
+ *
370
+ * @example
371
+ * ```typescript
372
+ * // Open chat page with initial message
373
+ * Wabbit.openChatPage('abc123', { initialMessage: 'Hello!' });
374
+ * ```
375
+ */
376
+ static openChatPage(collectionId, options) {
377
+ const url = Wabbit.getChatPageUrl(collectionId, options);
378
+ window.open(url, '_blank', 'noopener,noreferrer');
379
+ }
292
380
  /**
293
381
  * Get current configuration
294
382
  */
@@ -808,6 +896,8 @@ class ChatPanel {
808
896
  this.sendButton = null;
809
897
  this.messages = [];
810
898
  this.isWaitingForResponse = false;
899
+ this.closeButton = null;
900
+ this.eventCleanup = [];
811
901
  this.options = options;
812
902
  }
813
903
  /**
@@ -817,36 +907,83 @@ class ChatPanel {
817
907
  if (this.element) {
818
908
  return this.element;
819
909
  }
820
- // Main panel
910
+ const isInline = this.options.mode === 'inline';
911
+ // Main panel - use different class for inline mode
821
912
  this.element = createElement('div', {
822
- class: `wabbit-chat-panel ${this.options.position}`
913
+ class: isInline
914
+ ? 'wabbit-chat-panel wabbit-chat-panel-inline'
915
+ : `wabbit-chat-panel ${this.options.position}`
823
916
  });
824
- // Header
917
+ // Header (conditionally shown based on showHeader option, default: true)
918
+ if (this.options.showHeader !== false) {
919
+ const header = this.createHeader(isInline);
920
+ this.element.appendChild(header);
921
+ }
922
+ // Messages area
923
+ this.messagesContainer = createElement('div', { class: 'wabbit-chat-messages' });
924
+ // Input area
925
+ const inputArea = this.createInputArea();
926
+ // Assemble panel
927
+ this.element.appendChild(this.messagesContainer);
928
+ this.element.appendChild(inputArea);
929
+ // Append to container (inline) or document.body (widget)
930
+ if (isInline && this.options.container) {
931
+ this.options.container.appendChild(this.element);
932
+ // In inline mode, always visible
933
+ this.element.style.display = 'flex';
934
+ }
935
+ else {
936
+ document.body.appendChild(this.element);
937
+ // In widget mode, initially hidden until opened
938
+ this.element.style.display = 'none';
939
+ }
940
+ // Show welcome message if provided
941
+ if (this.options.welcomeMessage) {
942
+ this.addSystemMessage(this.options.welcomeMessage);
943
+ }
944
+ // Ensure scroll to bottom after DOM settles (for initial load and history)
945
+ setTimeout(() => this.scrollToBottom(), 100);
946
+ return this.element;
947
+ }
948
+ /**
949
+ * Create the header element
950
+ */
951
+ createHeader(isInline) {
825
952
  const header = createElement('div', { class: 'wabbit-chat-panel-header' });
826
953
  const headerTitle = createElement('div', { class: 'wabbit-chat-panel-header-title' });
827
954
  const headerIcon = createElement('div', { class: 'wabbit-chat-panel-header-icon' });
828
955
  headerIcon.textContent = 'AI';
829
956
  const headerText = createElement('div', { class: 'wabbit-chat-panel-header-text' });
830
957
  const headerH3 = createElement('h3');
831
- headerH3.textContent = 'AI Assistant';
958
+ headerH3.textContent = this.options.headerTitle || 'AI Assistant';
832
959
  const headerP = createElement('p');
833
960
  headerP.textContent = 'Powered by Wabbit';
834
961
  headerText.appendChild(headerH3);
835
962
  headerText.appendChild(headerP);
836
963
  headerTitle.appendChild(headerIcon);
837
964
  headerTitle.appendChild(headerText);
838
- const closeButton = createElement('button', {
839
- class: 'wabbit-chat-panel-close',
840
- 'aria-label': 'Close chat',
841
- type: 'button'
842
- });
843
- closeButton.innerHTML = '×';
844
- closeButton.addEventListener('click', this.options.onClose);
845
965
  header.appendChild(headerTitle);
846
- header.appendChild(closeButton);
847
- // Messages area
848
- this.messagesContainer = createElement('div', { class: 'wabbit-chat-messages' });
849
- // 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() {
850
987
  const inputArea = createElement('div', { class: 'wabbit-chat-input-area' });
851
988
  const inputWrapper = createElement('div', { class: 'wabbit-chat-input-wrapper' });
852
989
  this.inputElement = document.createElement('textarea');
@@ -854,17 +991,26 @@ class ChatPanel {
854
991
  this.inputElement.placeholder = this.options.placeholder || 'Type your message...';
855
992
  this.inputElement.rows = 1;
856
993
  this.inputElement.disabled = this.options.disabled || false;
857
- // Auto-resize textarea
858
- this.inputElement.addEventListener('input', () => {
994
+ // Auto-resize textarea and update send button state on input
995
+ const inputHandler = () => {
859
996
  this.inputElement.style.height = 'auto';
860
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);
861
1003
  });
862
1004
  // Send on Enter (Shift+Enter for new line)
863
- this.inputElement.addEventListener('keydown', (e) => {
1005
+ const keydownHandler = (e) => {
864
1006
  if (e.key === 'Enter' && !e.shiftKey) {
865
1007
  e.preventDefault();
866
1008
  this.handleSend();
867
1009
  }
1010
+ };
1011
+ this.inputElement.addEventListener('keydown', keydownHandler);
1012
+ this.eventCleanup.push(() => {
1013
+ this.inputElement?.removeEventListener('keydown', keydownHandler);
868
1014
  });
869
1015
  this.sendButton = createElement('button', {
870
1016
  class: 'wabbit-chat-send-button',
@@ -877,23 +1023,16 @@ class ChatPanel {
877
1023
  <polygon points="22 2 15 22 11 13 2 9 22 2"></polygon>
878
1024
  </svg>
879
1025
  `;
880
- 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
+ });
881
1031
  this.updateSendButtonState();
882
1032
  inputWrapper.appendChild(this.inputElement);
883
1033
  inputWrapper.appendChild(this.sendButton);
884
1034
  inputArea.appendChild(inputWrapper);
885
- // Assemble panel
886
- this.element.appendChild(header);
887
- this.element.appendChild(this.messagesContainer);
888
- this.element.appendChild(inputArea);
889
- document.body.appendChild(this.element);
890
- // Initially hide the panel (it will be shown when opened)
891
- this.element.style.display = 'none';
892
- // Show welcome message if provided
893
- if (this.options.welcomeMessage) {
894
- this.addSystemMessage(this.options.welcomeMessage);
895
- }
896
- return this.element;
1035
+ return inputArea;
897
1036
  }
898
1037
  /**
899
1038
  * Add a message to the panel
@@ -1016,7 +1155,12 @@ class ChatPanel {
1016
1155
  return;
1017
1156
  const hasText = this.inputElement.value.trim().length > 0;
1018
1157
  const disabled = this.options.disabled || this.isWaitingForResponse || !hasText;
1019
- this.sendButton.setAttribute('disabled', disabled ? 'true' : 'false');
1158
+ if (disabled) {
1159
+ this.sendButton.setAttribute('disabled', 'true');
1160
+ }
1161
+ else {
1162
+ this.sendButton.removeAttribute('disabled');
1163
+ }
1020
1164
  if (this.sendButton instanceof HTMLButtonElement) {
1021
1165
  this.sendButton.disabled = disabled;
1022
1166
  }
@@ -1035,8 +1179,9 @@ class ChatPanel {
1035
1179
  show() {
1036
1180
  if (this.element) {
1037
1181
  this.element.style.display = 'flex';
1038
- // Focus input after a brief delay
1182
+ // Focus input and scroll to bottom after a brief delay
1039
1183
  setTimeout(() => {
1184
+ this.scrollToBottom();
1040
1185
  if (this.inputElement) {
1041
1186
  this.inputElement.focus();
1042
1187
  }
@@ -1052,15 +1197,19 @@ class ChatPanel {
1052
1197
  }
1053
1198
  }
1054
1199
  /**
1055
- * Remove the panel from DOM
1200
+ * Remove the panel from DOM and cleanup event listeners
1056
1201
  */
1057
1202
  destroy() {
1203
+ // Run all event cleanup functions
1204
+ this.eventCleanup.forEach((cleanup) => cleanup());
1205
+ this.eventCleanup = [];
1058
1206
  if (this.element) {
1059
1207
  this.element.remove();
1060
1208
  this.element = null;
1061
1209
  this.messagesContainer = null;
1062
1210
  this.inputElement = null;
1063
1211
  this.sendButton = null;
1212
+ this.closeButton = null;
1064
1213
  }
1065
1214
  }
1066
1215
  }
@@ -1524,6 +1673,47 @@ function injectChatStyles(primaryColor = '#6366f1', theme) {
1524
1673
  .wabbit-chat-message {
1525
1674
  animation: wabbit-fade-in 0.2s ease-out;
1526
1675
  }
1676
+
1677
+ /* ========================================
1678
+ * INLINE MODE STYLES
1679
+ * ======================================== */
1680
+
1681
+ /* Inline Chat Panel - renders inside container instead of fixed position */
1682
+ .wabbit-chat-panel.wabbit-chat-panel-inline {
1683
+ position: relative !important;
1684
+ width: 100%;
1685
+ height: 100%;
1686
+ min-height: 400px;
1687
+ max-height: none;
1688
+ max-width: none;
1689
+ bottom: auto !important;
1690
+ right: auto !important;
1691
+ left: auto !important;
1692
+ border-radius: 12px;
1693
+ box-shadow: 0 4px 12px rgba(0, 0, 0, 0.15);
1694
+ animation: none; /* Disable slide-in animation for inline */
1695
+ }
1696
+
1697
+ /* Inline mode messages area fills available space */
1698
+ .wabbit-chat-panel-inline .wabbit-chat-messages {
1699
+ flex: 1;
1700
+ min-height: 200px;
1701
+ }
1702
+
1703
+ /* Full-height variant for inline mode (used in standalone chat pages) */
1704
+ .wabbit-chat-panel-inline.wabbit-chat-panel-fullheight {
1705
+ height: 100vh;
1706
+ max-height: 100vh;
1707
+ border-radius: 0;
1708
+ }
1709
+
1710
+ /* Mobile responsive for inline mode */
1711
+ @media (max-width: 480px) {
1712
+ .wabbit-chat-panel.wabbit-chat-panel-inline {
1713
+ border-radius: 0;
1714
+ min-height: 300px;
1715
+ }
1716
+ }
1527
1717
  `;
1528
1718
  const style = document.createElement('style');
1529
1719
  style.id = styleId;
@@ -1557,7 +1747,10 @@ class ChatWidget {
1557
1747
  this.isOpen = false;
1558
1748
  this.cleanup = [];
1559
1749
  this.themeCleanup = null;
1750
+ this.onChatReadyCallback = null;
1751
+ this.chatReadyFired = false;
1560
1752
  this.config = config;
1753
+ this.onChatReadyCallback = config.onChatReady || null;
1561
1754
  }
1562
1755
  /**
1563
1756
  * Initialize the chat widget
@@ -1569,6 +1762,16 @@ class ChatWidget {
1569
1762
  resolve();
1570
1763
  });
1571
1764
  });
1765
+ const isInlineMode = this.config.mode === 'inline';
1766
+ // Resolve container for inline mode
1767
+ let containerElement = null;
1768
+ if (isInlineMode) {
1769
+ containerElement = this.resolveContainer();
1770
+ if (!containerElement) {
1771
+ console.error(`[Wabbit] Container not found: ${this.config.container}`);
1772
+ return;
1773
+ }
1774
+ }
1572
1775
  // Inject styles with theme configuration
1573
1776
  injectChatStyles(this.config.primaryColor || '#6366f1', this.config.theme);
1574
1777
  // Setup theme watcher if theme is 'auto'
@@ -1579,37 +1782,63 @@ class ChatWidget {
1579
1782
  );
1580
1783
  // Set up event handlers
1581
1784
  this.setupWebSocketHandlers();
1582
- // Create bubble
1583
- this.bubble = new ChatBubble({
1584
- position: this.config.position || 'bottom-right',
1585
- onClick: () => this.toggle()
1586
- });
1587
- // Create panel
1785
+ // Only create bubble for widget mode (not inline)
1786
+ if (!isInlineMode) {
1787
+ this.bubble = new ChatBubble({
1788
+ position: this.config.position || 'bottom-right',
1789
+ onClick: () => this.toggle()
1790
+ });
1791
+ }
1792
+ // Create panel with mode-specific options
1588
1793
  this.panel = new ChatPanel({
1589
1794
  position: this.config.position || 'bottom-right',
1590
1795
  welcomeMessage: this.config.welcomeMessage,
1591
1796
  placeholder: this.config.placeholder,
1592
1797
  onSendMessage: (content) => this.handleSendMessage(content),
1593
1798
  onClose: () => this.close(),
1594
- disabled: true
1799
+ disabled: true,
1800
+ mode: this.config.mode || 'widget',
1801
+ container: containerElement || undefined,
1802
+ showHeader: this.config.showHeader,
1803
+ headerTitle: this.config.headerTitle
1595
1804
  });
1596
1805
  // Render components
1597
- this.bubble.render();
1806
+ if (this.bubble) {
1807
+ this.bubble.render();
1808
+ }
1598
1809
  this.panel.render();
1599
- // Ensure initial state: panel hidden, bubble visible
1600
- // This prevents state mismatch where panel might be visible but isOpen is false
1601
- if (this.panel) {
1602
- this.panel.hide();
1810
+ // Set initial state based on mode
1811
+ if (isInlineMode) {
1812
+ // Inline mode: always visible, mark as open
1813
+ this.isOpen = true;
1603
1814
  }
1604
- if (this.bubble) {
1605
- this.bubble.show();
1815
+ else {
1816
+ // Widget mode: panel hidden, bubble visible
1817
+ if (this.panel) {
1818
+ this.panel.hide();
1819
+ }
1820
+ if (this.bubble) {
1821
+ this.bubble.show();
1822
+ }
1823
+ this.isOpen = false;
1824
+ // Handle trigger types (only for widget mode)
1825
+ this.handleTriggerType();
1606
1826
  }
1607
- this.isOpen = false; // Ensure state is consistent
1608
- // Handle trigger types
1609
- this.handleTriggerType();
1610
1827
  // Connect WebSocket
1611
1828
  await this.wsClient.connect();
1612
1829
  }
1830
+ /**
1831
+ * Resolve container element for inline mode
1832
+ */
1833
+ resolveContainer() {
1834
+ if (!this.config.container)
1835
+ return null;
1836
+ // Try as ID first (with or without # prefix), then as CSS selector
1837
+ const selector = this.config.container;
1838
+ const idWithoutHash = selector.startsWith('#') ? selector.slice(1) : selector;
1839
+ return document.getElementById(idWithoutHash) ||
1840
+ document.querySelector(selector);
1841
+ }
1613
1842
  /**
1614
1843
  * Setup WebSocket event handlers
1615
1844
  */
@@ -1624,6 +1853,18 @@ class ChatWidget {
1624
1853
  this.panel.addSystemMessage(message);
1625
1854
  }
1626
1855
  }
1856
+ // Fire onChatReady callback once when chat is ready
1857
+ // Use requestAnimationFrame to ensure the panel DOM is fully painted
1858
+ // before the callback runs (important for sending initial messages)
1859
+ if (!this.chatReadyFired && this.onChatReadyCallback) {
1860
+ this.chatReadyFired = true;
1861
+ requestAnimationFrame(() => {
1862
+ console.log('[Wabbit] Chat ready, firing onChatReady callback');
1863
+ if (this.onChatReadyCallback) {
1864
+ this.onChatReadyCallback();
1865
+ }
1866
+ });
1867
+ }
1627
1868
  };
1628
1869
  this.wsClient.onMessageHistory = (messages) => {
1629
1870
  console.log('[Wabbit] Loaded message history:', messages.length);
@@ -1722,9 +1963,12 @@ class ChatWidget {
1722
1963
  this.wsClient.sendMessage(content);
1723
1964
  }
1724
1965
  /**
1725
- * Open the chat panel
1966
+ * Open the chat panel (no-op in inline mode)
1726
1967
  */
1727
1968
  open() {
1969
+ // Inline mode is always open
1970
+ if (this.config.mode === 'inline')
1971
+ return;
1728
1972
  if (this.isOpen)
1729
1973
  return;
1730
1974
  this.isOpen = true;
@@ -1736,9 +1980,12 @@ class ChatWidget {
1736
1980
  }
1737
1981
  }
1738
1982
  /**
1739
- * Close the chat panel
1983
+ * Close the chat panel (no-op in inline mode)
1740
1984
  */
1741
1985
  close() {
1986
+ // Inline mode cannot be closed
1987
+ if (this.config.mode === 'inline')
1988
+ return;
1742
1989
  if (!this.isOpen)
1743
1990
  return;
1744
1991
  this.isOpen = false;
@@ -1750,9 +1997,12 @@ class ChatWidget {
1750
1997
  }
1751
1998
  }
1752
1999
  /**
1753
- * Toggle chat panel
2000
+ * Toggle chat panel (no-op in inline mode)
1754
2001
  */
1755
2002
  toggle() {
2003
+ // Inline mode cannot be toggled
2004
+ if (this.config.mode === 'inline')
2005
+ return;
1756
2006
  if (this.isOpen) {
1757
2007
  this.close();
1758
2008
  }
@@ -3558,13 +3808,44 @@ function getInstance() {
3558
3808
  function destroy() {
3559
3809
  return Wabbit.destroy();
3560
3810
  }
3811
+ /**
3812
+ * Get the URL for a dedicated chat page
3813
+ *
3814
+ * @param collectionId - The collection ID
3815
+ * @param options - Optional parameters
3816
+ * @returns Full URL to the chat page
3817
+ *
3818
+ * @example
3819
+ * ```typescript
3820
+ * const url = Wabbit.getChatPageUrl('abc123', { initialMessage: 'Hello!' });
3821
+ * ```
3822
+ */
3823
+ function getChatPageUrl(collectionId, options) {
3824
+ return Wabbit.getChatPageUrl(collectionId, options);
3825
+ }
3826
+ /**
3827
+ * Open chat in a new tab/window
3828
+ *
3829
+ * @param collectionId - The collection ID
3830
+ * @param options - Optional parameters
3831
+ *
3832
+ * @example
3833
+ * ```typescript
3834
+ * Wabbit.openChatPage('abc123', { initialMessage: 'Hello!' });
3835
+ * ```
3836
+ */
3837
+ function openChatPage(collectionId, options) {
3838
+ return Wabbit.openChatPage(collectionId, options);
3839
+ }
3561
3840
  // Create an object as default export (for ESM/CJS: import Wabbit from '@wabbit-dashboard/embed')
3562
3841
  // Note: For UMD, we don't use default export, but directly use named exports
3563
3842
  const WabbitSDK = {
3564
3843
  init,
3565
3844
  getInstance,
3566
- destroy
3845
+ destroy,
3846
+ getChatPageUrl,
3847
+ openChatPage
3567
3848
  };
3568
3849
 
3569
- export { ApiClient, ChatBubble, ChatPanel, ChatWebSocketClient, ChatWidget, EmailCaptureModal, EmailCaptureWidget, EventEmitter, FormRenderer, FormStyles, FormWidget, SafeStorage, Wabbit, createElement, WabbitSDK as default, destroy, detectTheme, escapeHtml, getInstance, init, mergeConfig, onDOMReady, storage, validateConfig, watchTheme };
3850
+ export { ApiClient, ChatBubble, ChatPanel, ChatWebSocketClient, ChatWidget, EmailCaptureModal, EmailCaptureWidget, EventEmitter, FormRenderer, FormStyles, FormWidget, SafeStorage, Wabbit, createElement, WabbitSDK as default, destroy, detectTheme, escapeHtml, getChatPageUrl, getInstance, init, mergeConfig, onDOMReady, openChatPage, storage, validateConfig, watchTheme };
3570
3851
  //# sourceMappingURL=wabbit-embed.esm.js.map