open-chat-studio-widget 0.6.0 → 0.8.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.
Files changed (45) hide show
  1. package/README.md +28 -25
  2. package/dist/cjs/{index-CcvroTR_.js → index-Cf6K60f1.js} +3 -3
  3. package/dist/cjs/{index-CcvroTR_.js.map → index-Cf6K60f1.js.map} +1 -1
  4. package/dist/cjs/loader.cjs.js +2 -2
  5. package/dist/cjs/open-chat-studio-widget.cjs.entry.js +480 -178
  6. package/dist/cjs/open-chat-studio-widget.cjs.entry.js.map +1 -1
  7. package/dist/cjs/open-chat-studio-widget.cjs.js +2 -2
  8. package/dist/cjs/open-chat-studio-widget.entry.cjs.js.map +1 -1
  9. package/dist/collection/components/ocs-chat/icons.js +2 -2
  10. package/dist/collection/components/ocs-chat/icons.js.map +1 -1
  11. package/dist/collection/components/ocs-chat/ocs-chat.css +29 -34
  12. package/dist/collection/components/ocs-chat/ocs-chat.js +374 -102
  13. package/dist/collection/components/ocs-chat/ocs-chat.js.map +1 -1
  14. package/dist/collection/services/chat-session-service.js +39 -1
  15. package/dist/collection/services/chat-session-service.js.map +1 -1
  16. package/dist/collection/services/file-attachment-manager.js +4 -7
  17. package/dist/collection/services/file-attachment-manager.js.map +1 -1
  18. package/dist/collection/utils/cookies.js.map +1 -1
  19. package/dist/collection/utils/markdown.js +43 -17
  20. package/dist/collection/utils/markdown.js.map +1 -1
  21. package/dist/collection/utils/translations.js +1 -3
  22. package/dist/collection/utils/translations.js.map +1 -1
  23. package/dist/collection/utils/utils.js +2 -2
  24. package/dist/collection/utils/utils.js.map +1 -1
  25. package/dist/components/open-chat-studio-widget.js +487 -178
  26. package/dist/components/open-chat-studio-widget.js.map +1 -1
  27. package/dist/esm/{index-BKVXO_5E.js → index-DXf2dIht.js} +3 -3
  28. package/dist/esm/{index-BKVXO_5E.js.map → index-DXf2dIht.js.map} +1 -1
  29. package/dist/esm/loader.js +3 -3
  30. package/dist/esm/open-chat-studio-widget.entry.js +480 -178
  31. package/dist/esm/open-chat-studio-widget.entry.js.map +1 -1
  32. package/dist/esm/open-chat-studio-widget.js +3 -3
  33. package/dist/open-chat-studio-widget/open-chat-studio-widget.entry.esm.js.map +1 -1
  34. package/dist/open-chat-studio-widget/open-chat-studio-widget.esm.js +1 -1
  35. package/dist/open-chat-studio-widget/{p-BKVXO_5E.js → p-DXf2dIht.js} +2 -2
  36. package/dist/open-chat-studio-widget/{p-BKVXO_5E.js.map → p-DXf2dIht.js.map} +1 -1
  37. package/dist/open-chat-studio-widget/p-ff47dabf.entry.js +4 -0
  38. package/dist/open-chat-studio-widget/p-ff47dabf.entry.js.map +1 -0
  39. package/dist/types/components/ocs-chat/ocs-chat.d.ts +44 -2
  40. package/dist/types/components.d.ts +33 -4
  41. package/dist/types/services/chat-session-service.d.ts +7 -0
  42. package/dist/types/utils/markdown.d.ts +8 -0
  43. package/package.json +7 -2
  44. package/dist/open-chat-studio-widget/p-a0d04423.entry.js +0 -4
  45. package/dist/open-chat-studio-widget/p-a0d04423.entry.js.map +0 -1
@@ -1,6 +1,6 @@
1
1
  // eslint-disable-next-line @typescript-eslint/no-unused-vars
2
2
  import { Host, h, Env } from "@stencil/core";
3
- import { XMarkIcon, GripDotsVerticalIcon, PlusWithCircleIcon, ArrowsPointingOutIcon, ArrowsPointingInIcon, PaperClipIcon, CheckDocumentIcon, XIcon, OcsWidgetAvatar } from "./icons";
3
+ import { XMarkIcon, GripDotsVerticalIcon, PlusWithCircleIcon, ArrowsPointingOutIcon, ArrowsPointingInIcon, PaperClipIcon, CheckDocumentIcon, XIcon, OcsWidgetAvatar, } from "./icons";
4
4
  import { renderMarkdownSync as renderMarkdownComplete } from "../../utils/markdown";
5
5
  import { varToPixels } from "../../utils/utils";
6
6
  import { TranslationManager, defaultTranslations } from "../../utils/translations";
@@ -11,11 +11,23 @@ export class OcsChat {
11
11
  /**
12
12
  * The base URL for the API.
13
13
  */
14
- this.apiBaseUrl = "https://www.openchatstudio.com";
14
+ this.apiBaseUrl = 'https://www.openchatstudio.com';
15
15
  /**
16
16
  * The shape of the chat button. 'round' makes it circular, 'square' keeps it rectangular.
17
17
  */
18
18
  this.buttonShape = 'square';
19
+ /**
20
+ * Whether to show the launcher button. Set to false to hide the button
21
+ * and open the chat window programmatically via the `visible` property.
22
+ */
23
+ this.showButton = true;
24
+ /**
25
+ * The operating mode of the widget.
26
+ * - 'standard': Default floating window with launcher button.
27
+ * - 'kiosk': Fills parent container, always visible, no header or launcher button.
28
+ * The parent element must establish a containing block (e.g. `position: relative`).
29
+ */
30
+ this.mode = 'standard';
19
31
  /**
20
32
  * Whether the chat widget is visible on load.
21
33
  */
@@ -26,6 +38,7 @@ export class OcsChat {
26
38
  this.position = 'right';
27
39
  /**
28
40
  * Whether to persist session data to local storage to allow resuming previous conversations after page reload.
41
+ * Ignored when `sessionId` is provided.
29
42
  */
30
43
  this.persistentSession = true;
31
44
  /**
@@ -41,12 +54,13 @@ export class OcsChat {
41
54
  * Allow the user to attach files to their messages.
42
55
  */
43
56
  this.allowAttachments = false;
44
- this.error = "";
57
+ this.error = '';
45
58
  this.messages = [];
46
59
  this.isLoading = false;
47
60
  this.isTyping = false;
48
- this.messageInput = "";
49
- this.currentPollTaskId = "";
61
+ this.typingProgressMessage = '';
62
+ this.messageInput = '';
63
+ this.currentPollTaskId = '';
50
64
  this.isDragging = false;
51
65
  this.dragOffset = { x: 0, y: 0 };
52
66
  this.windowPosition = { x: 0, y: 0 };
@@ -75,6 +89,7 @@ export class OcsChat {
75
89
  this.chatWindowWidth = 450;
76
90
  this.chatWindowFullscreenWidth = 1024;
77
91
  this.positionInitialized = false;
92
+ this.sessionEpoch = 0;
78
93
  this.handleMouseDown = (event) => {
79
94
  if (!this.isFullscreen && window.innerWidth < OcsChat.MOBILE_BREAKPOINT)
80
95
  return;
@@ -120,6 +135,8 @@ export class OcsChat {
120
135
  };
121
136
  this.handleWindowResize = () => {
122
137
  var _a, _b;
138
+ if (this.isKioskMode())
139
+ return;
123
140
  this.positionInitialized = false;
124
141
  this.initializePosition();
125
142
  // Revalidate button position after resize to keep it within viewport bounds
@@ -131,7 +148,7 @@ export class OcsChat {
131
148
  const minPadding = 10;
132
149
  this.buttonPosition = {
133
150
  x: Math.max(minPadding, Math.min(this.buttonPosition.x, windowWidth - buttonWidth - minPadding)),
134
- y: Math.max(minPadding, Math.min(this.buttonPosition.y, windowHeight - buttonHeight - minPadding))
151
+ y: Math.max(minPadding, Math.min(this.buttonPosition.y, windowHeight - buttonHeight - minPadding)),
135
152
  };
136
153
  this.updateHostPosition();
137
154
  }
@@ -149,7 +166,7 @@ export class OcsChat {
149
166
  const rect = this.host.getBoundingClientRect();
150
167
  this.buttonDragOffset = {
151
168
  x: pointer.clientX - rect.left,
152
- y: pointer.clientY - rect.top
169
+ y: pointer.clientY - rect.top,
153
170
  };
154
171
  this.addButtonEventListeners();
155
172
  };
@@ -166,7 +183,7 @@ export class OcsChat {
166
183
  const rect = this.host.getBoundingClientRect();
167
184
  this.buttonDragOffset = {
168
185
  x: pointer.clientX - rect.left,
169
- y: pointer.clientY - rect.top
186
+ y: pointer.clientY - rect.top,
170
187
  };
171
188
  this.addButtonEventListeners();
172
189
  };
@@ -213,12 +230,19 @@ export class OcsChat {
213
230
  this.error = 'Chatbot ID is required';
214
231
  return;
215
232
  }
233
+ if (this.isKioskMode()) {
234
+ this.visible = true;
235
+ }
216
236
  await this.initializeTranslations();
217
- // Always try to load existing session if localStorage is available
218
- if (this.persistentSession && this.isLocalStorageAvailable()) {
237
+ if (this.isSessionBound()) {
238
+ // Bound to an externally-managed session: the host page is the source of truth.
239
+ this.activeSessionId = this.sessionId;
240
+ }
241
+ else if (this.persistentSession && this.isLocalStorageAvailable()) {
242
+ // Always try to load existing session if localStorage is available
219
243
  const { sessionId, messages } = this.loadSessionFromStorage();
220
244
  if (sessionId && messages) {
221
- this.sessionId = sessionId;
245
+ this.activeSessionId = sessionId;
222
246
  this.messages = messages;
223
247
  }
224
248
  }
@@ -235,15 +259,29 @@ export class OcsChat {
235
259
  this.chatWindowWidth = varToPixels(windowWidthVar, window.innerWidth, this.chatWindowWidth);
236
260
  this.chatWindowFullscreenWidth = varToPixels(fullscreenWidthVar, window.innerWidth, this.chatWindowFullscreenWidth);
237
261
  // Initialize button position from computed styles
238
- this.initializeButtonPosition();
239
- // Defer position initialization to avoid state changes during componentDidLoad
262
+ if (this.showButton && !this.isKioskMode()) {
263
+ this.initializeButtonPosition();
264
+ }
265
+ // Defer state changes to avoid triggering them during componentDidLoad
240
266
  setTimeout(() => {
267
+ // Restore visible state after dimensions are read so initializePosition
268
+ // uses the correct CSS-derived chatWindowWidth/chatWindowHeight.
269
+ if (!this.isKioskMode() && this.showButton && this.persistentSession && this.isLocalStorageAvailable()) {
270
+ this.restoreVisibleState();
271
+ }
241
272
  if (this.visible) {
242
- this.initializePosition();
273
+ if (!this.isKioskMode()) {
274
+ this.initializePosition();
275
+ }
243
276
  }
244
277
  // Resume polling for existing session (don't auto-start new sessions)
245
- if (this.visible && this.sessionId) {
246
- this.startMessagePolling();
278
+ if (this.visible && this.activeSessionId) {
279
+ if (this.isSessionBound()) {
280
+ void this.loadBoundSessionHistory();
281
+ }
282
+ else {
283
+ this.startMessagePolling();
284
+ }
247
285
  }
248
286
  }, 0);
249
287
  window.addEventListener('resize', this.handleWindowResize);
@@ -272,7 +310,7 @@ export class OcsChat {
272
310
  created_at: new Date().toISOString(),
273
311
  role: 'system',
274
312
  content: `**Error:** ${errorText}\nPlease try again.`,
275
- attachments: []
313
+ attachments: [],
276
314
  };
277
315
  this.messages = [...this.messages, errorMessage];
278
316
  this.saveSessionToStorage();
@@ -322,7 +360,7 @@ export class OcsChat {
322
360
  return;
323
361
  }
324
362
  if (typeof this.pageContext !== 'object' || Array.isArray(this.pageContext)) {
325
- console.error("pageContext is expected to be a plain JavaScript object.");
363
+ console.error('pageContext is expected to be a plain JavaScript object.');
326
364
  return;
327
365
  }
328
366
  this.internalPageContext = this.pageContext;
@@ -350,6 +388,7 @@ export class OcsChat {
350
388
  this.currentPollTaskId = '';
351
389
  }
352
390
  async startSession() {
391
+ const epoch = this.sessionEpoch;
353
392
  try {
354
393
  this.isLoading = true;
355
394
  const userId = this.getOrGenerateUserId();
@@ -357,34 +396,65 @@ export class OcsChat {
357
396
  chatbot_id: this.chatbotId,
358
397
  session_data: {
359
398
  source: 'widget',
360
- page_url: window.location.href
399
+ page_url: window.location.href,
361
400
  },
362
- participant_remote_id: userId
401
+ participant_remote_id: userId,
363
402
  };
364
403
  if (this.userName) {
365
404
  requestBody.participant_name = this.userName;
366
405
  }
406
+ if (this.versionNumber != null) {
407
+ requestBody.version_number = this.versionNumber;
408
+ }
367
409
  const data = await this.getChatService().startSession(requestBody);
368
- this.sessionId = data.session_id;
410
+ if (epoch !== this.sessionEpoch)
411
+ return;
412
+ this.activeSessionId = data.session_id;
369
413
  this.saveSessionToStorage();
370
414
  this.startMessagePolling();
371
415
  }
372
416
  catch (_error) {
417
+ if (epoch !== this.sessionEpoch)
418
+ return;
373
419
  this.handleError('Failed to start chat session');
374
420
  }
375
421
  finally {
376
422
  this.isLoading = false;
377
423
  }
378
424
  }
425
+ /**
426
+ * Load the full message history for a session provided via the `session-id`
427
+ * prop, then begin regular polling.
428
+ */
429
+ async loadBoundSessionHistory() {
430
+ const epoch = this.sessionEpoch;
431
+ try {
432
+ const history = await this.getChatService().fetchAllMessages(this.activeSessionId);
433
+ if (epoch !== this.sessionEpoch)
434
+ return;
435
+ // Keep messages added while the history was loading (e.g. an optimistic
436
+ // user message) by appending any that aren't part of the fetched history.
437
+ const known = new Set(history.map(m => `${m.created_at}|${m.role}|${m.content}`));
438
+ const pending = this.messages.filter(m => !known.has(`${m.created_at}|${m.role}|${m.content}`));
439
+ this.messages = [...history, ...pending];
440
+ this.scrollToBottom(true);
441
+ }
442
+ catch (error) {
443
+ if (epoch !== this.sessionEpoch)
444
+ return;
445
+ console.warn('Failed to load chat history:', error);
446
+ }
447
+ this.startMessagePolling();
448
+ }
379
449
  async uploadFiles() {
380
- if (this.selectedFiles.length === 0 || !this.sessionId || !this.allowAttachments) {
450
+ if (this.selectedFiles.length === 0 || !this.activeSessionId || !this.allowAttachments) {
381
451
  return [];
382
452
  }
383
453
  this.isUploadingFiles = true;
384
454
  try {
385
455
  const uploadResult = await this.attachmentManager.uploadPendingFiles(this.selectedFiles, {
386
456
  apiBaseUrl: this.apiBaseUrl || 'https://www.openchatstudio.com',
387
- sessionId: this.sessionId,
457
+ sessionId: this.activeSessionId,
388
458
  participantId: this.getOrGenerateUserId(),
389
459
  participantName: this.userName,
390
460
  });
@@ -398,15 +468,16 @@ export class OcsChat {
398
468
  async sendMessage(message) {
399
469
  if (!message.trim())
400
470
  return;
471
+ const epoch = this.sessionEpoch;
401
472
  // Start session if we don't have one yet
402
- if (!this.sessionId) {
473
+ if (!this.activeSessionId) {
403
474
  // Prevent concurrent session initialization
404
475
  if (this.isLoading) {
405
476
  return;
406
477
  }
407
478
  await this.startSession();
408
479
  // Check if session started successfully
409
- if (!this.sessionId) {
480
+ if (!this.activeSessionId) {
410
481
  return; // startSession already handled the error
411
482
  }
412
483
  }
@@ -431,7 +502,7 @@ export class OcsChat {
431
502
  created_at: new Date(now.getTime() - (welcomeMessagesToAdd.length - index) * 1000).toISOString(),
432
503
  role: 'assistant',
433
504
  content: welcomeMsg,
434
- attachments: []
505
+ attachments: [],
435
506
  }));
436
507
  this.messages = [...this.messages, ...welcomeMessages];
437
508
  }
@@ -440,13 +511,15 @@ export class OcsChat {
440
511
  created_at: new Date().toISOString(),
441
512
  role: 'user',
442
513
  content: message.trim(),
443
- attachments: this.allowAttachments ? this.selectedFiles
444
- .filter(sf => !sf.error && sf.uploaded)
445
- .map(sf => ({
446
- name: sf.file.name,
447
- content_type: sf.file.type,
448
- size: sf.file.size,
449
- })) : []
514
+ attachments: this.allowAttachments
515
+ ? this.selectedFiles
516
+ .filter(sf => !sf.error && sf.uploaded)
517
+ .map(sf => ({
518
+ name: sf.file.name,
519
+ content_type: sf.file.type,
520
+ size: sf.file.size,
521
+ }))
522
+ : [],
450
523
  };
451
524
  this.messages = [...this.messages, userMessage];
452
525
  this.saveSessionToStorage();
@@ -462,7 +535,12 @@ export class OcsChat {
462
535
  if (this.internalPageContext) {
463
536
  requestBody.context = this.internalPageContext;
464
537
  }
465
- const data = await this.getChatService().sendMessage(this.sessionId, requestBody);
538
+ if (this.versionNumber != null) {
539
+ requestBody.version_number = this.versionNumber;
540
+ }
541
+ const data = await this.getChatService().sendMessage(this.activeSessionId, requestBody);
542
+ if (epoch !== this.sessionEpoch)
543
+ return;
466
544
  if (data.status === 'error') {
467
545
  throw new Error(data.error || 'Failed to send message');
468
546
  }
@@ -470,6 +548,8 @@ export class OcsChat {
470
548
  this.startTaskPolling(data.task_id);
471
549
  }
472
550
  catch (error) {
551
+ if (epoch !== this.sessionEpoch)
552
+ return;
473
553
  const errorText = error instanceof Error ? error.message : 'Failed to send message';
474
554
  this.handleError(errorText);
475
555
  }
@@ -492,10 +572,10 @@ export class OcsChat {
492
572
  const childRect = lastChild.getBoundingClientRect();
493
573
  const currentScrollTop = this.messageListRef.scrollTop;
494
574
  const childTopRelativeToParent = childRect.top - parentRect.top;
495
- const targetScroll = currentScrollTop + childTopRelativeToParent - (parentRect.height / 2);
575
+ const targetScroll = currentScrollTop + childTopRelativeToParent - parentRect.height / 2;
496
576
  this.messageListRef.scrollTo({
497
577
  top: targetScroll,
498
- behavior: 'smooth'
578
+ behavior: 'smooth',
499
579
  });
500
580
  }
501
581
  else {
@@ -540,10 +620,10 @@ export class OcsChat {
540
620
  const k = 1024;
541
621
  if (bytes < k * k) {
542
622
  // Less than 1MB, show in KB
543
- return Math.round(bytes / k * 100) / 100 + ' KB';
623
+ return Math.round((bytes / k) * 100) / 100 + ' KB';
544
624
  }
545
625
  else {
546
- return Math.round(bytes / (k * k) * 100) / 100 + ' MB';
626
+ return Math.round((bytes / (k * k)) * 100) / 100 + ' MB';
547
627
  }
548
628
  }
549
629
  formatTime(dateString) {
@@ -561,23 +641,40 @@ export class OcsChat {
561
641
  pageContextHandler() {
562
642
  this.loadInternalPageContext();
563
643
  }
644
+ async chatbotConfigHandler() {
645
+ await this.clearSession();
646
+ }
564
647
  /**
565
648
  * Watch for changes to the `visible` attribute and update accordingly.
566
649
  *
567
650
  * @param visible - The new value for the field.
568
651
  */
569
652
  async visibilityHandler(visible) {
653
+ // Kiosk mode is always visible
654
+ if (this.isKioskMode() && !visible) {
655
+ this.visible = true;
656
+ return;
657
+ }
658
+ this.saveVisibleState(visible);
570
659
  if (this.isButtonDragging) {
571
660
  this.isButtonDragging = false;
572
661
  this.buttonWasDragged = false;
573
662
  this.removeButtonEventListeners();
574
663
  }
575
664
  if (visible) {
576
- this.initializePosition();
665
+ if (!this.isKioskMode()) {
666
+ this.initializePosition();
667
+ }
577
668
  // Resume polling for existing session (don't auto-start new sessions)
578
- if (this.sessionId) {
669
+ if (this.activeSessionId) {
579
670
  this.scrollToBottom(true);
580
- this.startMessagePolling();
671
+ if (this.isSessionBound() && this.messages.length === 0) {
672
+ // A bound widget that was hidden at load has not fetched its history yet.
673
+ void this.loadBoundSessionHistory();
674
+ }
675
+ else {
676
+ this.startMessagePolling();
677
+ }
581
678
  }
582
679
  }
583
680
  else {
@@ -585,7 +682,7 @@ export class OcsChat {
585
682
  }
586
683
  }
587
684
  startTaskPolling(taskId) {
588
- if (!this.sessionId)
685
+ if (!this.activeSessionId)
589
686
  return;
590
687
  this.currentPollTaskId = taskId;
591
688
  this.isTyping = true;
@@ -593,50 +690,56 @@ export class OcsChat {
593
690
  if (this.taskPollingHandle) {
594
691
  this.taskPollingHandle.cancel();
595
692
  }
596
- this.taskPollingHandle = this.getChatService().pollTask(this.sessionId, taskId, {
597
- onMessage: (message) => {
693
+ this.taskPollingHandle = this.getChatService().pollTask(this.activeSessionId, taskId, {
694
+ onMessage: message => {
598
695
  this.messages = [...this.messages, message];
599
696
  this.saveSessionToStorage();
600
697
  this.scrollToBottom();
601
698
  this.isTyping = false;
699
+ this.typingProgressMessage = '';
602
700
  this.currentPollTaskId = '';
603
701
  this.taskPollingHandle = undefined;
604
702
  this.startMessagePolling();
605
703
  this.focusInput();
606
704
  },
705
+ onProgress: message => {
706
+ this.typingProgressMessage = message;
707
+ },
607
708
  onTimeout: () => {
608
709
  const timeoutMessage = {
609
710
  created_at: new Date().toISOString(),
610
711
  role: 'system',
611
712
  content: 'The response is taking longer than expected. The system may be experiencing delays. Please try sending your message again.',
612
- attachments: []
713
+ attachments: [],
613
714
  };
614
715
  this.messages = [...this.messages, timeoutMessage];
615
716
  this.saveSessionToStorage();
616
717
  this.scrollToBottom();
617
718
  this.isTyping = false;
719
+ this.typingProgressMessage = '';
618
720
  this.currentPollTaskId = '';
619
721
  this.taskPollingHandle = undefined;
620
722
  this.startMessagePolling();
621
723
  this.focusInput();
622
724
  },
623
- onError: (error) => {
725
+ onError: error => {
726
+ this.typingProgressMessage = '';
624
727
  this.handleError(error.message);
625
728
  this.taskPollingHandle = undefined;
626
729
  this.startMessagePolling();
627
- }
730
+ },
628
731
  });
629
732
  }
630
733
  startMessagePolling() {
631
- if (!this.sessionId || this.currentPollTaskId || !this.visible) {
734
+ if (!this.activeSessionId || this.currentPollTaskId || !this.visible) {
632
735
  return;
633
736
  }
634
737
  if (this.messagePollingHandle) {
635
738
  return;
636
739
  }
637
- this.messagePollingHandle = this.getChatService().startMessagePolling(this.sessionId, {
638
- getSince: () => { var _a; return this.messages.length > 0 ? (_a = this.messages.at(-1)) === null || _a === void 0 ? void 0 : _a.created_at : undefined; },
639
- onMessages: (messages) => {
740
+ this.messagePollingHandle = this.getChatService().startMessagePolling(this.activeSessionId, {
741
+ getSince: () => { var _a; return (this.messages.length > 0 ? (_a = this.messages.at(-1)) === null || _a === void 0 ? void 0 : _a.created_at : undefined); },
742
+ onMessages: messages => {
640
743
  if (messages.length === 0)
641
744
  return;
642
745
  this.messages = [...this.messages, ...messages];
@@ -646,7 +749,7 @@ export class OcsChat {
646
749
  },
647
750
  onError: () => {
648
751
  // Silently ignore polling errors to match previous behaviour
649
- }
752
+ },
650
753
  });
651
754
  }
652
755
  stopMessagePolling() {
@@ -665,6 +768,9 @@ export class OcsChat {
665
768
  this.position = position;
666
769
  }
667
770
  getPositionClasses() {
771
+ if (this.isKioskMode()) {
772
+ return 'chat-window-kiosk';
773
+ }
668
774
  if (this.isFullscreen) {
669
775
  return 'chat-window-fullscreen';
670
776
  }
@@ -680,6 +786,9 @@ export class OcsChat {
680
786
  return { windowWidth, actualChatWidth, centeredX, maxOffset };
681
787
  }
682
788
  getPositionStyles() {
789
+ if (this.isKioskMode()) {
790
+ return {};
791
+ }
683
792
  if (this.isFullscreen) {
684
793
  const { centeredX } = this.getFullscreenBounds();
685
794
  const finalX = centeredX + this.fullscreenPosition.x;
@@ -711,19 +820,19 @@ export class OcsChat {
711
820
  case 'left':
712
821
  this.windowPosition = {
713
822
  x: OcsChat.WINDOW_MARGIN,
714
- y: windowHeight - this.chatWindowHeight - OcsChat.WINDOW_MARGIN
823
+ y: windowHeight - this.chatWindowHeight - OcsChat.WINDOW_MARGIN,
715
824
  };
716
825
  break;
717
826
  case 'right':
718
827
  this.windowPosition = {
719
828
  x: windowWidth - chatWidth - OcsChat.WINDOW_MARGIN,
720
- y: windowHeight - this.chatWindowHeight - OcsChat.WINDOW_MARGIN
829
+ y: windowHeight - this.chatWindowHeight - OcsChat.WINDOW_MARGIN,
721
830
  };
722
831
  break;
723
832
  case 'center':
724
833
  this.windowPosition = {
725
834
  x: (windowWidth - chatWidth) / 2,
726
- y: (windowHeight - this.chatWindowHeight) / 2
835
+ y: (windowHeight - this.chatWindowHeight) / 2,
727
836
  };
728
837
  break;
729
838
  }
@@ -746,14 +855,14 @@ export class OcsChat {
746
855
  // For fullscreen, track relative to current position
747
856
  this.dragOffset = {
748
857
  x: pointer.clientX,
749
- y: pointer.clientY
858
+ y: pointer.clientY,
750
859
  };
751
860
  }
752
861
  else {
753
862
  const rect = this.chatWindowRef.getBoundingClientRect();
754
863
  this.dragOffset = {
755
864
  x: pointer.clientX - rect.left,
756
- y: pointer.clientY - rect.top
865
+ y: pointer.clientY - rect.top,
757
866
  };
758
867
  }
759
868
  }
@@ -765,7 +874,7 @@ export class OcsChat {
765
874
  const { maxOffset } = this.getFullscreenBounds();
766
875
  const deltaX = pointer.clientX - this.dragOffset.x;
767
876
  this.fullscreenPosition = {
768
- x: Math.max(-maxOffset, Math.min(maxOffset, deltaX))
877
+ x: Math.max(-maxOffset, Math.min(maxOffset, deltaX)),
769
878
  };
770
879
  }
771
880
  else {
@@ -778,7 +887,7 @@ export class OcsChat {
778
887
  const chatHeight = this.chatWindowRef.offsetHeight;
779
888
  this.windowPosition = {
780
889
  x: Math.max(0, Math.min(newX, windowWidth - chatWidth)),
781
- y: Math.max(0, Math.min(newY, windowHeight - chatHeight))
890
+ y: Math.max(0, Math.min(newY, windowHeight - chatHeight)),
782
891
  };
783
892
  }
784
893
  }
@@ -825,7 +934,7 @@ export class OcsChat {
825
934
  const verticalValue = this.buttonVerticalSide === 'top' ? resolvedTop : resolvedBottom;
826
935
  this.buttonPosition = {
827
936
  x: horizontalValue,
828
- y: verticalValue
937
+ y: verticalValue,
829
938
  };
830
939
  // Apply the position to the host
831
940
  this.updateHostPosition();
@@ -868,12 +977,8 @@ export class OcsChat {
868
977
  const maxTop = windowHeight - buttonHeight - minPadding;
869
978
  const constrainedLeft = Math.max(minLeft, Math.min(candidateLeft, maxLeft));
870
979
  const constrainedTop = Math.max(minTop, Math.min(candidateTop, maxTop));
871
- const newHorizontalValue = this.buttonHorizontalSide === 'left'
872
- ? constrainedLeft
873
- : Math.max(minPadding, windowWidth - (constrainedLeft + buttonWidth));
874
- const newVerticalValue = this.buttonVerticalSide === 'top'
875
- ? constrainedTop
876
- : Math.max(minPadding, windowHeight - (constrainedTop + buttonHeight));
980
+ const newHorizontalValue = this.buttonHorizontalSide === 'left' ? constrainedLeft : Math.max(minPadding, windowWidth - (constrainedLeft + buttonWidth));
981
+ const newVerticalValue = this.buttonVerticalSide === 'top' ? constrainedTop : Math.max(minPadding, windowHeight - (constrainedTop + buttonHeight));
877
982
  if (newHorizontalValue !== this.buttonPosition.x || newVerticalValue !== this.buttonPosition.y) {
878
983
  this.buttonWasDragged = true;
879
984
  this.buttonPosition = { x: newHorizontalValue, y: newVerticalValue };
@@ -936,16 +1041,12 @@ export class OcsChat {
936
1041
  return fallback;
937
1042
  }
938
1043
  getWelcomeMessages() {
939
- const translated = this.translationManager.getArray("content.welcomeMessages");
940
- return translated && translated.length > 0
941
- ? translated
942
- : this.parsedWelcomeMessages;
1044
+ const translated = this.translationManager.getArray('content.welcomeMessages');
1045
+ return translated && translated.length > 0 ? translated : this.parsedWelcomeMessages;
943
1046
  }
944
1047
  getStarterQuestions() {
945
- const translated = this.translationManager.getArray("content.starterQuestions");
946
- return translated && translated.length > 0
947
- ? translated
948
- : this.parsedStarterQuestions;
1048
+ const translated = this.translationManager.getArray('content.starterQuestions');
1049
+ return translated && translated.length > 0 ? translated : this.parsedStarterQuestions;
949
1050
  }
950
1051
  getButtonClasses() {
951
1052
  const buttonText = this.translationManager.get('branding.buttonText', this.buttonText);
@@ -956,6 +1057,9 @@ export class OcsChat {
956
1057
  }
957
1058
  renderButton() {
958
1059
  var _a;
1060
+ if (!this.showButton || this.isKioskMode()) {
1061
+ return null;
1062
+ }
959
1063
  const buttonText = this.translationManager.get('branding.buttonText', this.buttonText);
960
1064
  const hasText = !!(buttonText && buttonText.trim());
961
1065
  const hasCustomIcon = this.iconUrl && this.iconUrl.trim();
@@ -965,31 +1069,34 @@ export class OcsChat {
965
1069
  const buttonAriaLabel = finalButtonText ? `${openLabel} - ${finalButtonText}` : openLabel;
966
1070
  // Only show drag cursor if button is draggable
967
1071
  const isDraggable = this.isButtonDraggable();
968
- const buttonStyle = isDraggable ? {
969
- cursor: this.isButtonDragging ? 'grabbing' : 'grab',
970
- } : {};
1072
+ const buttonStyle = isDraggable
1073
+ ? {
1074
+ cursor: this.isButtonDragging ? 'grabbing' : 'grab',
1075
+ }
1076
+ : {};
971
1077
  if (hasText) {
972
- return (h("button", { ref: (el) => this.buttonRef = el, class: buttonClasses, "aria-label": buttonAriaLabel, title: finalButtonText || openLabel, style: buttonStyle, onClick: () => this.handleButtonClick(), onMouseDown: (e) => this.handleButtonMouseDown(e), onTouchStart: (e) => this.handleButtonTouchStart(e), "aria-grabbed": this.isButtonDragging, "aria-describedby": isDraggable ? "chat-button-drag-hint" : undefined }, hasCustomIcon ? h("img", { src: this.iconUrl, alt: "" }) : h(OcsWidgetAvatar, null), h("span", null, finalButtonText), isDraggable && (h("span", { id: "chat-button-drag-hint", style: { display: 'none' } }, "Draggable. Use mouse or touch to reposition."))));
1078
+ return (h("button", { ref: el => (this.buttonRef = el), class: buttonClasses, "aria-label": buttonAriaLabel, title: finalButtonText || openLabel, style: buttonStyle, onClick: () => this.handleButtonClick(), onMouseDown: e => this.handleButtonMouseDown(e), onTouchStart: e => this.handleButtonTouchStart(e), "aria-grabbed": this.isButtonDragging, "aria-describedby": isDraggable ? 'chat-button-drag-hint' : undefined }, hasCustomIcon ? h("img", { src: this.iconUrl, alt: "" }) : h(OcsWidgetAvatar, null), h("span", null, finalButtonText), isDraggable && (h("span", { id: "chat-button-drag-hint", style: { display: 'none' } }, "Draggable. Use mouse or touch to reposition."))));
973
1079
  }
974
1080
  else {
975
- return (h("button", { ref: (el) => this.buttonRef = el, class: buttonClasses, "aria-label": openLabel, title: openLabel, style: buttonStyle, onClick: () => this.handleButtonClick(), onMouseDown: (e) => this.handleButtonMouseDown(e), onTouchStart: (e) => this.handleButtonTouchStart(e), "aria-grabbed": this.isButtonDragging, "aria-describedby": isDraggable ? "chat-button-drag-hint" : undefined }, hasCustomIcon ? h("img", { src: this.iconUrl, alt: "" }) : h(OcsWidgetAvatar, null), isDraggable && (h("span", { id: "chat-button-drag-hint", style: { display: 'none' } }, "Draggable. Use mouse or touch to reposition."))));
1081
+ return (h("button", { ref: el => (this.buttonRef = el), class: buttonClasses, "aria-label": openLabel, title: openLabel, style: buttonStyle, onClick: () => this.handleButtonClick(), onMouseDown: e => this.handleButtonMouseDown(e), onTouchStart: e => this.handleButtonTouchStart(e), "aria-grabbed": this.isButtonDragging, "aria-describedby": isDraggable ? 'chat-button-drag-hint' : undefined }, hasCustomIcon ? h("img", { src: this.iconUrl, alt: "" }) : h(OcsWidgetAvatar, null), isDraggable && (h("span", { id: "chat-button-drag-hint", style: { display: 'none' } }, "Draggable. Use mouse or touch to reposition."))));
976
1082
  }
977
1083
  }
978
1084
  getStorageKeys() {
979
1085
  return {
980
1086
  sessionId: `ocs-chat-session-${this.chatbotId}`,
981
1087
  messages: `ocs-chat-messages-${this.chatbotId}`,
982
- lastActivity: `ocs-chat-activity-${this.chatbotId}`
1088
+ lastActivity: `ocs-chat-activity-${this.chatbotId}`,
1089
+ visible: `ocs-chat-visible-${this.chatbotId}`,
983
1090
  };
984
1091
  }
985
1092
  saveSessionToStorage() {
986
- if (!this.persistentSession) {
1093
+ if (!this.persistentSession || this.isSessionBound()) {
987
1094
  return;
988
1095
  }
989
1096
  const keys = this.getStorageKeys();
990
1097
  try {
991
- if (this.sessionId) {
992
- localStorage.setItem(keys.sessionId, this.sessionId);
1098
+ if (this.activeSessionId) {
1099
+ localStorage.setItem(keys.sessionId, this.activeSessionId);
993
1100
  localStorage.setItem(keys.lastActivity, new Date().toISOString());
994
1101
  }
995
1102
  localStorage.setItem(keys.messages, JSON.stringify(this.messages));
@@ -1042,30 +1149,73 @@ export class OcsChat {
1042
1149
  return this.generatedUserId;
1043
1150
  }
1044
1151
  const storageKey = `ocs-user-id`;
1045
- const stored = localStorage.getItem(storageKey);
1152
+ let stored = null;
1153
+ try {
1154
+ stored = localStorage.getItem(storageKey);
1155
+ }
1156
+ catch (_a) {
1157
+ // localStorage blocked; fall through to in-memory id generation
1158
+ }
1046
1159
  if (stored) {
1047
1160
  this.generatedUserId = stored;
1048
1161
  return stored;
1049
1162
  }
1050
1163
  const array = new Uint8Array(9);
1051
1164
  window.crypto.getRandomValues(array);
1052
- const randomString = Array.from(array, byte => byte.toString(36)).join('').substr(0, 9);
1165
+ const randomString = Array.from(array, byte => byte.toString(36))
1166
+ .join('')
1167
+ .substr(0, 9);
1053
1168
  const newUserId = `ocs:${Date.now()}_${randomString}`;
1054
1169
  this.generatedUserId = newUserId;
1055
- localStorage.setItem(storageKey, newUserId);
1170
+ try {
1171
+ localStorage.setItem(storageKey, newUserId);
1172
+ }
1173
+ catch (_b) {
1174
+ // localStorage blocked; the generated id lives in component state for this page
1175
+ }
1056
1176
  return newUserId;
1057
1177
  }
1178
+ saveVisibleState(visible) {
1179
+ if (!this.persistentSession)
1180
+ return;
1181
+ try {
1182
+ const keys = this.getStorageKeys();
1183
+ localStorage.setItem(keys.visible, visible ? '1' : '0');
1184
+ }
1185
+ catch (_a) {
1186
+ // ignore
1187
+ }
1188
+ }
1189
+ restoreVisibleState() {
1190
+ try {
1191
+ const keys = this.getStorageKeys();
1192
+ const stored = localStorage.getItem(keys.visible);
1193
+ if (stored === '1') {
1194
+ this.visible = true;
1195
+ }
1196
+ }
1197
+ catch (_a) {
1198
+ // ignore
1199
+ }
1200
+ }
1058
1201
  clearSessionStorage() {
1059
1202
  const keys = this.getStorageKeys();
1060
1203
  try {
1061
1204
  localStorage.removeItem(keys.sessionId);
1062
1205
  localStorage.removeItem(keys.messages);
1063
1206
  localStorage.removeItem(keys.lastActivity);
1207
+ localStorage.removeItem(keys.visible);
1064
1208
  }
1065
1209
  catch (error) {
1066
1210
  console.warn('Failed to clear chat session from localStorage:', error);
1067
1211
  }
1068
1212
  }
1213
+ isKioskMode() {
1214
+ return this.mode === 'kiosk';
1215
+ }
1216
+ isSessionBound() {
1217
+ return !!this.sessionId;
1218
+ }
1069
1219
  isLocalStorageAvailable() {
1070
1220
  try {
1071
1221
  localStorage.setItem(OcsChat.LOCALSTORAGE_TEST_KEY, 'test');
@@ -1091,8 +1241,11 @@ export class OcsChat {
1091
1241
  * will start when the user sends a message.
1092
1242
  */
1093
1243
  async clearSession() {
1244
+ this.sessionEpoch += 1;
1094
1245
  this.clearSessionStorage();
1095
- this.sessionId = undefined;
1246
+ // A session provided by the host page (session-id prop) cannot be cleared;
1247
+ // stay bound to it. Unbound widgets start a new session on the next message.
1248
+ this.activeSessionId = this.sessionId;
1096
1249
  this.messages = [];
1097
1250
  this.isTyping = false;
1098
1251
  this.currentPollTaskId = '';
@@ -1100,6 +1253,11 @@ export class OcsChat {
1100
1253
  this.selectedFiles = [];
1101
1254
  }
1102
1255
  this.cleanup();
1256
+ if (this.isSessionBound()) {
1257
+ // The host-owned session cannot be cleared: reload its history and
1258
+ // resume polling so the widget doesn't end up in a dead state.
1259
+ void this.loadBoundSessionHistory();
1260
+ }
1103
1261
  }
1104
1262
  toggleFullscreen() {
1105
1263
  this.isFullscreen = !this.isFullscreen;
@@ -1108,21 +1266,15 @@ export class OcsChat {
1108
1266
  }
1109
1267
  render() {
1110
1268
  // Only show error state for critical errors that prevent the widget from functioning
1111
- if (this.error && !this.sessionId) {
1269
+ if (this.error && !this.activeSessionId) {
1112
1270
  return (h(Host, null, h("p", { class: "error-message" }, this.error)));
1113
1271
  }
1114
- return (h(Host, null, this.renderButton(), this.visible && (h("div", { ref: (el) => this.chatWindowRef = el, id: "ocs-chat-window", class: this.getPositionClasses(), style: this.getPositionStyles() }, h("div", { class: `chat-header ${this.isDragging ? 'chat-header-dragging' : 'chat-header-draggable'}`, onMouseDown: this.handleMouseDown, onTouchStart: this.handleTouchStart }, h("div", { class: "drag-indicator" }, h("div", { class: "drag-dots header-button" }, h(GripDotsVerticalIcon, null))), h("div", { class: "header-text" }, this.translationManager.get('branding.headerText', this.headerText)), h("div", { class: "header-buttons" }, this.messages.length > 0 && (h("button", { class: "header-button", onClick: () => this.showConfirmationDialog(), title: this.translationManager.get('window.newChat'), "aria-label": this.translationManager.get('window.newChat') }, h(PlusWithCircleIcon, null))), this.allowFullScreen && h("button", { class: "header-button fullscreen-button", onClick: () => this.toggleFullscreen(), title: this.isFullscreen ? this.translationManager.get('window.exitFullscreen') : this.translationManager.get('window.fullscreen'), "aria-label": this.isFullscreen ? this.translationManager.get('window.exitFullscreen') : this.translationManager.get('window.fullscreen') }, this.isFullscreen ? h(ArrowsPointingInIcon, null) : h(ArrowsPointingOutIcon, null)), h("button", { class: "header-button", onClick: () => this.visible = false, "aria-label": this.translationManager.get('window.close') }, h(XMarkIcon, null)))), this.showNewChatConfirmation && (h("div", { class: "confirmation-overlay" }, h("div", { class: "confirmation-dialog" }, h("div", { class: "confirmation-content" }, h("h3", { class: "confirmation-title" }, this.translationManager.get('modal.newChatTitle')), h("p", { class: "confirmation-message" }, this.translationManager.get('modal.newChatBody', this.newChatConfirmationMessage)), h("div", { class: "confirmation-buttons" }, h("button", { class: "confirmation-button confirmation-button-cancel", onClick: () => this.hideConfirmationDialog() }, this.translationManager.get('modal.cancel')), h("button", { class: "confirmation-button confirmation-button-confirm", onClick: () => this.confirmNewChat() }, this.translationManager.get('modal.confirm'))))))), h("div", { class: "chat-content" }, this.isLoading && !this.sessionId && (h("div", { class: "loading-container" }, h("div", { class: "loading-spinner" }), h("span", { class: "loading-text" }, this.translationManager.get('status.starting')))), (h("div", { ref: (el) => this.messageListRef = el, class: "messages-container" }, this.messages.length === 0 && this.getWelcomeMessages().length > 0 && (h("div", { class: "welcome-messages" }, this.getWelcomeMessages().map((message, index) => (h("div", { key: `welcome-${index}`, class: "message-row message-row-assistant" }, h("div", { class: "message-bubble message-bubble-assistant" }, h("div", { class: "chat-markdown", innerHTML: renderMarkdownComplete(message) }))))))), this.messages.map((message, index) => (h("div", { key: index, class: `message-row ${message.role === 'user' ? 'message-row-user' : 'message-row-assistant'}` }, h("div", { class: `message-bubble ${message.role === 'user'
1115
- ? 'message-bubble-user'
1116
- : message.role === 'assistant'
1117
- ? 'message-bubble-assistant'
1118
- : 'message-bubble-system'}` }, h("div", { class: "chat-markdown", innerHTML: renderMarkdownComplete(message.content) }), message.attachments && message.attachments.length > 0 && (h("div", { class: "message-attachments" }, message.attachments.map((attachment, attachmentIndex) => (h("div", { key: attachmentIndex, class: "flex items-center gap-[0.5em]" }, h("span", { class: "message-attachment-icon" }, h(PaperClipIcon, null)), h("span", { class: "message-attachment-name" }, attachment.name)))))), h("div", { class: "message-timestamp" }, this.formatTime(message.created_at)))))), this.isTyping && (h("div", null, h("div", { class: "typing-indicator" }, h("div", { class: "typing-progress" })), h("div", { class: "typing-text" }, h("span", null, this.translationManager.get('status.typing', this.typingIndicatorText)), h("span", { class: "typing-dots loading" })))))), this.messages.length === 0 && this.getStarterQuestions().length > 0 && (h("div", { class: "starter-questions" }, this.getStarterQuestions().map((question, index) => (h("div", { key: `starter-${index}`, class: "starter-question-row" }, h("button", { class: "starter-question", onClick: () => this.handleStarterQuestionClick(question) }, question)))))), this.allowAttachments && this.selectedFiles.length > 0 && (h("div", { class: "selected-files-container" }, h("div", { class: "space-y-[0.25em]" }, this.selectedFiles.map((selectedFile, index) => (h("div", { key: index, class: "selected-file-item" }, h("div", { class: "flex items-center gap-[0.5em]" }, h("span", { class: "selected-file-icon" }, h(PaperClipIcon, null)), h("span", null, selectedFile.file.name), h("span", { class: "selected-file-size" }, "(", this.formatFileSize(selectedFile.file.size), ")"), selectedFile.error && (h("span", { class: "selected-file-error" }, selectedFile.error)), selectedFile.uploaded && (h("span", { class: "selected-file-success-icon" }, h(CheckDocumentIcon, null)))), h("button", { onClick: () => this.removeSelectedFile(index), class: "selected-file-remove-button", "aria-label": this.translationManager.get('attach.remove') }, h(XIcon, null)))))))), h("div", { class: "input-area" }, h("div", { class: "input-container" }, h("textarea", { ref: (el) => this.textareaRef = el, class: "message-textarea", rows: 1, placeholder: this.translationManager.get('composer.placeholder'), value: this.messageInput, onInput: (e) => this.handleInputChange(e), onKeyPress: (e) => this.handleKeyPress(e), disabled: this.isTyping || this.isUploadingFiles || this.isLoading }), this.allowAttachments && (h("input", { ref: (el) => {
1272
+ return (h(Host, null, this.renderButton(), this.visible && (h("div", { ref: el => (this.chatWindowRef = el), id: "ocs-chat-window", class: this.getPositionClasses(), style: this.getPositionStyles() }, !this.isKioskMode() && (h("div", { class: `chat-header ${this.isDragging ? 'chat-header-dragging' : 'chat-header-draggable'}`, onMouseDown: this.handleMouseDown, onTouchStart: this.handleTouchStart }, h("div", { class: "drag-indicator" }, h("div", { class: "drag-dots header-button" }, h(GripDotsVerticalIcon, null))), h("div", { class: "header-text" }, this.translationManager.get('branding.headerText', this.headerText)), h("div", { class: "header-buttons" }, this.messages.length > 0 && !this.isSessionBound() && (h("button", { class: "header-button", onClick: () => this.showConfirmationDialog(), title: this.translationManager.get('window.newChat'), "aria-label": this.translationManager.get('window.newChat') }, h(PlusWithCircleIcon, null))), this.allowFullScreen && (h("button", { class: "header-button fullscreen-button", onClick: () => this.toggleFullscreen(), title: this.isFullscreen ? this.translationManager.get('window.exitFullscreen') : this.translationManager.get('window.fullscreen'), "aria-label": this.isFullscreen ? this.translationManager.get('window.exitFullscreen') : this.translationManager.get('window.fullscreen') }, this.isFullscreen ? h(ArrowsPointingInIcon, null) : h(ArrowsPointingOutIcon, null))), h("button", { class: "header-button", onClick: () => (this.visible = false), "aria-label": this.translationManager.get('window.close') }, h(XMarkIcon, null))))), !this.isKioskMode() && this.showNewChatConfirmation && (h("div", { class: "confirmation-overlay" }, h("div", { class: "confirmation-dialog" }, h("div", { class: "confirmation-content" }, h("h3", { class: "confirmation-title" }, this.translationManager.get('modal.newChatTitle')), h("p", { class: "confirmation-message" }, this.translationManager.get('modal.newChatBody', this.newChatConfirmationMessage)), h("div", { class: "confirmation-buttons" }, h("button", { class: "confirmation-button confirmation-button-cancel", onClick: () => this.hideConfirmationDialog() }, this.translationManager.get('modal.cancel')), h("button", { class: "confirmation-button confirmation-button-confirm", onClick: () => this.confirmNewChat() }, this.translationManager.get('modal.confirm'))))))), h("div", { class: "chat-content" }, this.isLoading && !this.activeSessionId && (h("div", { class: "loading-container" }, h("div", { class: "loading-spinner" }), h("span", { class: "loading-text" }, this.translationManager.get('status.starting')))), h("div", { ref: el => (this.messageListRef = el), class: "messages-container" }, this.messages.length === 0 && this.getWelcomeMessages().length > 0 && (h("div", { class: "welcome-messages" }, this.getWelcomeMessages().map((message, index) => (h("div", { key: `welcome-${index}`, class: "message-row message-row-assistant" }, h("div", { class: "message-bubble message-bubble-assistant" }, h("div", { class: "chat-markdown", innerHTML: renderMarkdownComplete(message) }))))))), this.messages.map((message, index) => (h("div", { key: index, class: `message-row ${message.role === 'user' ? 'message-row-user' : 'message-row-assistant'}` }, h("div", { class: `message-bubble ${message.role === 'user' ? 'message-bubble-user' : message.role === 'assistant' ? 'message-bubble-assistant' : 'message-bubble-system'}` }, h("div", { class: "chat-markdown", innerHTML: renderMarkdownComplete(message.content) }), message.attachments && message.attachments.length > 0 && (h("div", { class: "message-attachments" }, message.attachments.map((attachment, attachmentIndex) => (h("div", { key: attachmentIndex, class: "flex items-center gap-[0.5em]" }, h("span", { class: "message-attachment-icon" }, h(PaperClipIcon, null)), h("span", { class: "message-attachment-name" }, attachment.name)))))), h("div", { class: "message-timestamp" }, this.formatTime(message.created_at)))))), this.isTyping && (h("div", null, h("div", { class: "typing-indicator" }, h("div", { class: "typing-progress" })), h("div", { class: "typing-text" }, h("span", null, this.typingProgressMessage || this.translationManager.get('status.typing', this.typingIndicatorText)), h("span", { class: "typing-dots loading" }))))), this.messages.length === 0 && this.getStarterQuestions().length > 0 && (h("div", { class: "starter-questions" }, this.getStarterQuestions().map((question, index) => (h("div", { key: `starter-${index}`, class: "starter-question-row" }, h("button", { class: "starter-question", onClick: () => this.handleStarterQuestionClick(question) }, question)))))), this.allowAttachments && this.selectedFiles.length > 0 && (h("div", { class: "selected-files-container" }, h("div", { class: "space-y-[0.25em]" }, this.selectedFiles.map((selectedFile, index) => (h("div", { key: index, class: "selected-file-item" }, h("div", { class: "flex items-center gap-[0.5em]" }, h("span", { class: "selected-file-icon" }, h(PaperClipIcon, null)), h("span", null, selectedFile.file.name), h("span", { class: "selected-file-size" }, "(", this.formatFileSize(selectedFile.file.size), ")"), selectedFile.error && h("span", { class: "selected-file-error" }, selectedFile.error), selectedFile.uploaded && (h("span", { class: "selected-file-success-icon" }, h(CheckDocumentIcon, null)))), h("button", { onClick: () => this.removeSelectedFile(index), class: "selected-file-remove-button", "aria-label": this.translationManager.get('attach.remove') }, h(XIcon, null)))))))), h("div", { class: "input-area" }, h("div", { class: "input-container" }, h("textarea", { ref: el => (this.textareaRef = el), class: "message-textarea", rows: 1, placeholder: this.translationManager.get('composer.placeholder'), value: this.messageInput, onInput: e => this.handleInputChange(e), onKeyPress: e => this.handleKeyPress(e), disabled: this.isTyping || this.isUploadingFiles || this.isLoading }), this.allowAttachments && (h("input", { ref: el => {
1119
1273
  // Unclear why but after removing all attachments this is being set to `null`.
1120
1274
  if (el) {
1121
1275
  this.fileInputRef = el;
1122
1276
  }
1123
- }, id: "ocs-file-input", type: "file", multiple: true, accept: OcsChat.SUPPORTED_FILE_EXTENSIONS.join(',') + ',text/*', onChange: (e) => this.handleFileSelect(e), class: "hidden" })), this.allowAttachments && (h("button", { class: "file-attachment-button", onClick: () => { var _a; return (_a = this.fileInputRef) === null || _a === void 0 ? void 0 : _a.click(); }, disabled: this.isTyping || this.isUploadingFiles || this.isLoading, title: this.translationManager.get('attach.add'), "aria-label": this.translationManager.get('attach.add') }, h(PaperClipIcon, null))), h("button", { class: `send-button ${!this.isTyping && !this.isLoading && !!this.messageInput.trim()
1124
- ? 'send-button-enabled'
1125
- : 'send-button-disabled'}`, onClick: () => this.sendMessage(this.messageInput), disabled: this.isTyping || this.isUploadingFiles || this.isLoading || !this.messageInput.trim() }, this.isUploadingFiles ? `${this.translationManager.get('status.uploading')}...` : this.translationManager.get('composer.send')))), h("div", { class: "flex items-center justify-center text-[0.8em] font-light w-full text-slate-500 py-[2px]" }, h("p", null, this.translationManager.get('branding.poweredBy'), ' ', " ", h("a", { class: "underline", href: "https://www.dimagi.com", target: "_blank" }, "Dimagi"))))))));
1277
+ }, id: "ocs-file-input", type: "file", multiple: true, accept: OcsChat.SUPPORTED_FILE_EXTENSIONS.join(',') + ',text/*', onChange: e => this.handleFileSelect(e), class: "hidden" })), this.allowAttachments && (h("button", { class: "file-attachment-button", onClick: () => { var _a; return (_a = this.fileInputRef) === null || _a === void 0 ? void 0 : _a.click(); }, disabled: this.isTyping || this.isUploadingFiles || this.isLoading, title: this.translationManager.get('attach.add'), "aria-label": this.translationManager.get('attach.add') }, h(PaperClipIcon, null))), h("button", { class: `send-button ${!this.isTyping && !this.isLoading && !!this.messageInput.trim() ? 'send-button-enabled' : 'send-button-disabled'}`, onClick: () => this.sendMessage(this.messageInput), disabled: this.isTyping || this.isUploadingFiles || this.isLoading || !this.messageInput.trim() }, this.isUploadingFiles ? `${this.translationManager.get('status.uploading')}...` : this.translationManager.get('composer.send')))), h("div", { class: "flex items-center justify-center text-[0.8em] font-light w-full text-slate-500 py-[2px]" }, h("p", null, this.translationManager.get('branding.poweredBy'), ' ', h("a", { class: "underline", href: "https://www.dimagi.com", target: "_blank", rel: "noopener noreferrer" }, "Dimagi"))))))));
1126
1278
  }
1127
1279
  static get is() { return "open-chat-studio-widget"; }
1128
1280
  static get encapsulation() { return "shadow"; }
@@ -1175,7 +1327,7 @@ export class OcsChat {
1175
1327
  "getter": false,
1176
1328
  "setter": false,
1177
1329
  "reflect": false,
1178
- "defaultValue": "\"https://www.openchatstudio.com\""
1330
+ "defaultValue": "'https://www.openchatstudio.com'"
1179
1331
  },
1180
1332
  "buttonText": {
1181
1333
  "type": "string",
@@ -1254,6 +1406,46 @@ export class OcsChat {
1254
1406
  "reflect": false,
1255
1407
  "defaultValue": "'square'"
1256
1408
  },
1409
+ "showButton": {
1410
+ "type": "boolean",
1411
+ "attribute": "show-button",
1412
+ "mutable": false,
1413
+ "complexType": {
1414
+ "original": "boolean",
1415
+ "resolved": "boolean",
1416
+ "references": {}
1417
+ },
1418
+ "required": false,
1419
+ "optional": false,
1420
+ "docs": {
1421
+ "tags": [],
1422
+ "text": "Whether to show the launcher button. Set to false to hide the button\nand open the chat window programmatically via the `visible` property."
1423
+ },
1424
+ "getter": false,
1425
+ "setter": false,
1426
+ "reflect": false,
1427
+ "defaultValue": "true"
1428
+ },
1429
+ "mode": {
1430
+ "type": "string",
1431
+ "attribute": "mode",
1432
+ "mutable": false,
1433
+ "complexType": {
1434
+ "original": "'standard' | 'kiosk'",
1435
+ "resolved": "\"kiosk\" | \"standard\"",
1436
+ "references": {}
1437
+ },
1438
+ "required": false,
1439
+ "optional": false,
1440
+ "docs": {
1441
+ "tags": [],
1442
+ "text": "The operating mode of the widget.\n- 'standard': Default floating window with launcher button.\n- 'kiosk': Fills parent container, always visible, no header or launcher button.\n The parent element must establish a containing block (e.g. `position: relative`)."
1443
+ },
1444
+ "getter": false,
1445
+ "setter": false,
1446
+ "reflect": false,
1447
+ "defaultValue": "'standard'"
1448
+ },
1257
1449
  "headerText": {
1258
1450
  "type": "string",
1259
1451
  "attribute": "header-text",
@@ -1421,7 +1613,7 @@ export class OcsChat {
1421
1613
  "optional": false,
1422
1614
  "docs": {
1423
1615
  "tags": [],
1424
- "text": "Whether to persist session data to local storage to allow resuming previous conversations after page reload."
1616
+ "text": "Whether to persist session data to local storage to allow resuming previous conversations after page reload.\nIgnored when `sessionId` is provided."
1425
1617
  },
1426
1618
  "getter": false,
1427
1619
  "setter": false,
@@ -1567,6 +1759,47 @@ export class OcsChat {
1567
1759
  },
1568
1760
  "getter": false,
1569
1761
  "setter": false
1762
+ },
1763
+ "versionNumber": {
1764
+ "type": "number",
1765
+ "attribute": "version-number",
1766
+ "mutable": false,
1767
+ "complexType": {
1768
+ "original": "number",
1769
+ "resolved": "number",
1770
+ "references": {}
1771
+ },
1772
+ "required": false,
1773
+ "optional": true,
1774
+ "docs": {
1775
+ "tags": [{
1776
+ "name": "internal",
1777
+ "text": "Optional version number of the chatbot to use. Requires authentication.\nThis is for internal use only and is not intended for public-facing widgets."
1778
+ }],
1779
+ "text": ""
1780
+ },
1781
+ "getter": false,
1782
+ "setter": false,
1783
+ "reflect": false
1784
+ },
1785
+ "sessionId": {
1786
+ "type": "string",
1787
+ "attribute": "session-id",
1788
+ "mutable": false,
1789
+ "complexType": {
1790
+ "original": "string",
1791
+ "resolved": "string",
1792
+ "references": {}
1793
+ },
1794
+ "required": false,
1795
+ "optional": true,
1796
+ "docs": {
1797
+ "tags": [],
1798
+ "text": "The ID of an existing chat session to connect to. When provided, the widget\nis bound to that session: local session persistence is disabled and the\nmessage history is loaded from the server. Intended for host pages that\ncreate the session server-side (e.g. the OCS web chat page)."
1799
+ },
1800
+ "getter": false,
1801
+ "setter": false,
1802
+ "reflect": false
1570
1803
  }
1571
1804
  };
1572
1805
  }
@@ -1574,9 +1807,10 @@ export class OcsChat {
1574
1807
  return {
1575
1808
  "error": {},
1576
1809
  "messages": {},
1577
- "sessionId": {},
1810
+ "activeSessionId": {},
1578
1811
  "isLoading": {},
1579
1812
  "isTyping": {},
1813
+ "typingProgressMessage": {},
1580
1814
  "messageInput": {},
1581
1815
  "currentPollTaskId": {},
1582
1816
  "isDragging": {},
@@ -1599,6 +1833,12 @@ export class OcsChat {
1599
1833
  return [{
1600
1834
  "propName": "pageContext",
1601
1835
  "methodName": "pageContextHandler"
1836
+ }, {
1837
+ "propName": "chatbotId",
1838
+ "methodName": "chatbotConfigHandler"
1839
+ }, {
1840
+ "propName": "versionNumber",
1841
+ "methodName": "chatbotConfigHandler"
1602
1842
  }, {
1603
1843
  "propName": "visible",
1604
1844
  "methodName": "visibilityHandler"
@@ -1615,7 +1855,39 @@ OcsChat.WINDOW_MARGIN = 20;
1615
1855
  OcsChat.LOCALSTORAGE_TEST_KEY = '__ocs_test__';
1616
1856
  OcsChat.MAX_FILE_SIZE_MB = 50;
1617
1857
  OcsChat.MAX_TOTAL_SIZE_MB = 50;
1618
- OcsChat.SUPPORTED_FILE_EXTENSIONS = ['.txt', '.pdf', '.doc', '.docx', '.xls', '.xlsx', '.csv', '.jpg',
1619
- '.jpeg', '.png', '.gif', '.bmp', '.webp', '.svg', '.mp4', '.mov', '.avi', '.mp3', '.wav', '.html', '.htm', '.css',
1620
- '.js', '.xml', '.md', '.ics', '.vcf', '.rtf', '.tsv', '.yaml', '.yml', '.py', '.c'];
1858
+ OcsChat.SUPPORTED_FILE_EXTENSIONS = [
1859
+ '.txt',
1860
+ '.pdf',
1861
+ '.doc',
1862
+ '.docx',
1863
+ '.xls',
1864
+ '.xlsx',
1865
+ '.csv',
1866
+ '.jpg',
1867
+ '.jpeg',
1868
+ '.png',
1869
+ '.gif',
1870
+ '.bmp',
1871
+ '.webp',
1872
+ '.svg',
1873
+ '.mp4',
1874
+ '.mov',
1875
+ '.avi',
1876
+ '.mp3',
1877
+ '.wav',
1878
+ '.html',
1879
+ '.htm',
1880
+ '.css',
1881
+ '.js',
1882
+ '.xml',
1883
+ '.md',
1884
+ '.ics',
1885
+ '.vcf',
1886
+ '.rtf',
1887
+ '.tsv',
1888
+ '.yaml',
1889
+ '.yml',
1890
+ '.py',
1891
+ '.c',
1892
+ ];
1621
1893
  //# sourceMappingURL=ocs-chat.js.map