@crestapps/ai-chat-ui 1.0.0-preview-22.6 → 1.0.0-preview.8

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/ai-chat.js CHANGED
@@ -34,7 +34,25 @@ window.openAIChatManager = function () {
34
34
  thumbsDownTitle: 'Thumbs down',
35
35
  copyTitle: 'Click here to copy response to clipboard.',
36
36
  codeCopiedText: 'Copied!',
37
- messageTemplate: "\n <div class=\"ai-chat-messages\">\n <div v-for=\"(message, index) in messages\" :key=\"'msg-' + index\" class=\"ai-chat-message-item\">\n <div>\n <div v-if=\"message.role === 'user'\" class=\"ai-chat-msg-role ai-chat-msg-role-user\">{{ userLabel }}</div>\n <div v-else-if=\"message.role !== 'indicator'\" :class=\"getAssistantRoleClasses(message)\">\n <span :class=\"getAssistantIconClasses(message, index)\"><i :class=\"getAssistantIcon(message)\"></i></span>\n {{ getAssistantLabel(message) }}\n </div>\n <div class=\"ai-chat-message-body lh-base\">\n <h4 v-if=\"message.title\">{{ message.title }}</h4>\n <div v-html=\"message.htmlContent\"></div>\n <span class=\"message-buttons-container\" v-if=\"!isIndicator(message)\">\n <template v-if=\"metricsEnabled && message.role === 'assistant'\">\n <span class=\"ai-chat-message-assistant-feedback\" :data-message-id=\"message.id\">\n <button class=\"btn btn-sm btn-link text-success p-0 me-2 button-message-toolbox rate-up-btn\" @click=\"rateMessage(message, true, $event)\" :title=\"thumbsUpTitle\">\n <i class=\"fa-regular fa-thumbs-up\"></i>\n </button>\n <button class=\"btn btn-sm btn-link text-danger p-0 me-2 button-message-toolbox rate-down-btn\" @click=\"rateMessage(message, false, $event)\" :title=\"thumbsDownTitle\">\n <i class=\"fa-regular fa-thumbs-down\"></i>\n </button>\n </span>\n </template>\n <button v-if=\"textToSpeechEnabled && message.role === 'assistant' && !message.isStreaming\" class=\"btn btn-sm btn-link text-secondary p-0 me-1 button-message-toolbox\" :class=\"{ 'tts-playing': ttsPlayingMessageIndex === index }\" @click=\"toggleMessageTts(message, index)\" :title=\"ttsPlayingMessageIndex === index ? 'Pause audio' : 'Read aloud'\">\n <i :class=\"ttsPlayingMessageIndex === index ? 'fa-solid fa-circle-pause' : 'fa-solid fa-circle-play'\"></i>\n </button>\n <button class=\"btn btn-sm btn-link text-secondary p-0 button-message-toolbox\" @click=\"copyResponse(message.content)\" :title=\"copyTitle\">\n <i class=\"fa-solid fa-copy\"></i>\n </button>\n </span>\n </div>\n </div>\n </div>\n <div v-for=\"notification in notifications\" :key=\"'notif-' + notification.type\" class=\"ai-chat-notification\" :class=\"'ai-chat-notification-' + (notification.type || 'info') + ' ' + (notification.cssClass || '')\">\n <div class=\"ai-chat-notification-content\">\n <i v-if=\"notification.icon\" :class=\"notification.icon\" class=\"ai-chat-notification-icon\"></i>\n <span class=\"ai-chat-notification-text\">{{ notification.content }}</span>\n <button v-if=\"notification.dismissible\" class=\"btn btn-sm btn-link p-0 ms-2 ai-chat-notification-dismiss\" @click=\"dismissNotification(notification.type)\" title=\"Dismiss\">\n <i class=\"fa-solid fa-xmark\"></i>\n </button>\n </div>\n <div v-if=\"notification.actions && notification.actions.length\" class=\"ai-chat-notification-actions\">\n <button v-for=\"action in notification.actions\" :key=\"action.name\" class=\"btn btn-sm\" :class=\"action.cssClass || 'btn-outline-secondary'\" @click=\"handleNotificationAction(notification.type, action.name)\">\n <i v-if=\"action.icon\" :class=\"action.icon\" class=\"me-1\"></i>\n {{ action.label }}\n </button>\n </div>\n </div>\n </div>\n ",
37
+ widget: {
38
+ chatWidgetContainer: null,
39
+ chatWidgetStateName: null,
40
+ chatHistorySection: null,
41
+ showHistoryButton: null,
42
+ closeHistoryButton: null,
43
+ newChatButton: null,
44
+ toggleButtonSelector: null,
45
+ resetSizeButtonSelector: null,
46
+ dragHandleSelector: '.ai-chat-widget-header',
47
+ enableDragging: true,
48
+ enableResizing: true,
49
+ persistLayout: true,
50
+ defaultWidth: null,
51
+ defaultHeight: null,
52
+ minWidth: 320,
53
+ minHeight: 420
54
+ },
55
+ messageTemplate: "\n <div class=\"ai-chat-messages\">\n <div v-for=\"(message, index) in messages\" :key=\"'msg-' + index\" class=\"ai-chat-message-item\">\n <div>\n <div v-if=\"message.role === 'user'\" class=\"ai-chat-msg-role ai-chat-msg-role-user\">{{ userLabel }}</div>\n <div v-else-if=\"message.role !== 'indicator'\" :class=\"getAssistantRoleClasses(message)\">\n <span :class=\"getAssistantIconClasses(message, index)\"><i :class=\"getAssistantIcon(message)\"></i></span>\n {{ getAssistantLabel(message) }}\n </div>\n <div class=\"ai-chat-message-body lh-base\">\n <h4 v-if=\"message.title\">{{ message.title }}</h4>\n <div v-html=\"message.htmlContent\"></div>\n <span class=\"message-buttons-container\" v-if=\"!isIndicator(message)\">\n <template v-if=\"metricsEnabled && message.role === 'assistant'\">\n <span class=\"ai-chat-message-assistant-feedback\" :data-message-id=\"message.id\">\n <button class=\"btn btn-sm btn-link text-success p-0 me-2 button-message-toolbox rate-up-btn\" @click=\"rateMessage(message, true, $event)\" :title=\"thumbsUpTitle\">\n <i class=\"fa-regular fa-thumbs-up\"></i>\n </button>\n <button class=\"btn btn-sm btn-link text-danger p-0 me-2 button-message-toolbox rate-down-btn\" @click=\"rateMessage(message, false, $event)\" :title=\"thumbsDownTitle\">\n <i class=\"fa-regular fa-thumbs-down\"></i>\n </button>\n </span>\n </template>\n <button v-if=\"textToSpeechEnabled && !isConversationMode && message.role === 'assistant' && !message.isStreaming\" class=\"btn btn-sm btn-link text-secondary p-0 me-1 button-message-toolbox\" :class=\"{ 'tts-playing': ttsPlayingMessageIndex === index }\" @click=\"toggleMessageTts(message, index)\" :title=\"ttsPlayingMessageIndex === index ? 'Pause audio' : 'Read aloud'\">\n <i :class=\"ttsPlayingMessageIndex === index ? 'fa-solid fa-circle-pause' : 'fa-solid fa-circle-play'\"></i>\n </button>\n <button class=\"btn btn-sm btn-link text-secondary p-0 button-message-toolbox\" @click=\"copyResponse(message.content)\" :title=\"copyTitle\">\n <i class=\"fa-solid fa-copy\"></i>\n </button>\n </span>\n </div>\n </div>\n </div>\n <div v-for=\"notification in notifications\" :key=\"'notif-' + notification.type\" class=\"ai-chat-notification\" :class=\"'ai-chat-notification-' + (notification.type || 'info') + ' ' + (notification.cssClass || '')\">\n <div class=\"ai-chat-notification-content\">\n <i v-if=\"notification.icon\" :class=\"notification.icon\" class=\"ai-chat-notification-icon\"></i>\n <span class=\"ai-chat-notification-text\">{{ notification.content }}</span>\n <button v-if=\"notification.dismissible\" class=\"btn btn-sm btn-link p-0 ms-2 ai-chat-notification-dismiss\" @click=\"dismissNotification(notification.type)\" title=\"Dismiss\">\n <i class=\"fa-solid fa-xmark\"></i>\n </button>\n </div>\n <div v-if=\"notification.actions && notification.actions.length\" class=\"ai-chat-notification-actions\">\n <button v-for=\"action in notification.actions\" :key=\"action.name\" class=\"btn btn-sm\" :class=\"action.cssClass || 'btn-outline-secondary'\" @click=\"handleNotificationAction(notification.type, action.name)\">\n <i v-if=\"action.icon\" :class=\"action.icon\" class=\"me-1\"></i>\n {{ action.label }}\n </button>\n </div>\n </div>\n </div>\n ",
38
56
  indicatorTemplate: "\n <div class=\"ai-chat-msg-role ai-chat-msg-role-assistant\">\n <span class=\"ai-streaming-icon\"><i class=\"fa fa-robot\" style=\"display: inline-block;\"></i></span>\n Assistant\n </div>\n "
39
57
  };
40
58
 
@@ -54,6 +72,19 @@ window.openAIChatManager = function () {
54
72
  span.textContent = text;
55
73
  return span.innerHTML;
56
74
  }
75
+ function parsePixelValue(value) {
76
+ if (typeof value === 'number' && Number.isFinite(value)) {
77
+ return value;
78
+ }
79
+ if (typeof value !== 'string') {
80
+ return null;
81
+ }
82
+ var parsed = parseFloat(value);
83
+ return Number.isFinite(parsed) ? parsed : null;
84
+ }
85
+ function clamp(value, min, max) {
86
+ return Math.min(Math.max(value, min), max);
87
+ }
57
88
  function normalizeReference(reference) {
58
89
  var _ref, _normalized$index, _ref2, _normalized$text, _ref3, _normalized$link;
59
90
  if (!reference || _typeof(reference) !== 'object') {
@@ -330,6 +361,8 @@ window.openAIChatManager = function () {
330
361
  }
331
362
  var initialize = function initialize(instanceConfig) {
332
363
  var config = Object.assign({}, defaultConfig, instanceConfig);
364
+ config.widget = Object.assign({}, defaultConfig.widget || {}, instanceConfig && instanceConfig.widget ? instanceConfig.widget : {});
365
+ var hasWidgetConfig = !!(instanceConfig && instanceConfig.widget && instanceConfig.widget.chatWidgetContainer && instanceConfig.widget.chatWidgetStateName);
333
366
  // Keep defaultConfig in sync so renderers use overridden values
334
367
  defaultConfig = config;
335
368
  if (!config.signalRHubUrl) {
@@ -363,7 +396,15 @@ window.openAIChatManager = function () {
363
396
  isPlaceholderVisible: true,
364
397
  chatWidgetStateName: null,
365
398
  chatWidgetStateSession: null,
399
+ chatWidgetLayoutKey: null,
366
400
  chatHistorySection: null,
401
+ widgetContainerElement: null,
402
+ widgetToggleButtonElement: null,
403
+ widgetResetSizeButtonElement: null,
404
+ widgetResizeObserver: null,
405
+ widgetViewportResizeHandler: null,
406
+ widgetDefaultWidth: null,
407
+ widgetDefaultHeight: null,
367
408
  widgetIsInitialized: false,
368
409
  isStreaming: false,
369
410
  isNavigatingAway: false,
@@ -394,9 +435,11 @@ window.openAIChatManager = function () {
394
435
  audioPlayQueue: [],
395
436
  isPlayingAudio: false,
396
437
  currentAudioElement: null,
438
+ currentAudioUrl: null,
397
439
  ttsButton: null,
398
440
  ttsPlayingMessageIndex: -1,
399
441
  ttsAudioCache: {},
442
+ ttsInstanceId: 'ai-chat-' + Math.random().toString(36).slice(2),
400
443
  singleResponseMode: !!config.singleResponseMode,
401
444
  conversationModeEnabled: config.chatMode === 'Conversation',
402
445
  conversationButton: null,
@@ -418,6 +461,336 @@ window.openAIChatManager = function () {
418
461
  handleBeforeUnload: function handleBeforeUnload() {
419
462
  this.isNavigatingAway = true;
420
463
  },
464
+ getStoredWidgetLayout: function getStoredWidgetLayout() {
465
+ if (!this.chatWidgetLayoutKey || !config.widget.persistLayout) {
466
+ return {};
467
+ }
468
+ try {
469
+ var storedLayout = localStorage.getItem(this.chatWidgetLayoutKey);
470
+ return storedLayout ? JSON.parse(storedLayout) || {} : {};
471
+ } catch (error) {
472
+ console.warn('Failed to read widget layout state.', error);
473
+ return {};
474
+ }
475
+ },
476
+ saveStoredWidgetLayout: function saveStoredWidgetLayout(partialLayout) {
477
+ if (!this.chatWidgetLayoutKey || !config.widget.persistLayout) {
478
+ return;
479
+ }
480
+ var currentLayout = this.getStoredWidgetLayout();
481
+ var nextLayout = Object.assign({}, currentLayout);
482
+ if (partialLayout.panel !== undefined) {
483
+ nextLayout.panel = Object.assign({}, currentLayout.panel || {}, partialLayout.panel || {});
484
+ }
485
+ if (partialLayout.position !== undefined) {
486
+ nextLayout.position = Object.assign({}, currentLayout.position || {}, partialLayout.position || {});
487
+ }
488
+ localStorage.setItem(this.chatWidgetLayoutKey, JSON.stringify(nextLayout));
489
+ },
490
+ getElementSize: function getElementSize(element, fallbackWidth, fallbackHeight) {
491
+ if (!element) {
492
+ return {
493
+ width: fallbackWidth || 0,
494
+ height: fallbackHeight || 0
495
+ };
496
+ }
497
+ var rect = element.getBoundingClientRect();
498
+ var computed = window.getComputedStyle(element);
499
+ return {
500
+ width: Math.round(rect.width || parsePixelValue(computed.width) || fallbackWidth || 0),
501
+ height: Math.round(rect.height || parsePixelValue(computed.height) || fallbackHeight || 0)
502
+ };
503
+ },
504
+ getElementPosition: function getElementPosition(element) {
505
+ if (!element) {
506
+ return {
507
+ left: 0,
508
+ top: 0
509
+ };
510
+ }
511
+ var rect = element.getBoundingClientRect();
512
+ var computed = window.getComputedStyle(element);
513
+ var inlineLeft = parsePixelValue(element.style.left);
514
+ var inlineTop = parsePixelValue(element.style.top);
515
+ var computedLeft = parsePixelValue(computed.left);
516
+ var computedTop = parsePixelValue(computed.top);
517
+ var isHidden = computed.display === 'none';
518
+ return {
519
+ left: Math.round(isHidden && Number.isFinite(inlineLeft) ? inlineLeft : rect.left || inlineLeft || computedLeft || 0),
520
+ top: Math.round(isHidden && Number.isFinite(inlineTop) ? inlineTop : rect.top || inlineTop || computedTop || 0)
521
+ };
522
+ },
523
+ clampWidgetPosition: function clampWidgetPosition(left, top, width, height) {
524
+ var viewportPadding = 8;
525
+ var maxLeft = Math.max(viewportPadding, window.innerWidth - width - viewportPadding);
526
+ var maxTop = Math.max(viewportPadding, window.innerHeight - height - viewportPadding);
527
+ return {
528
+ left: Math.round(clamp(left, viewportPadding, maxLeft)),
529
+ top: Math.round(clamp(top, viewportPadding, maxTop))
530
+ };
531
+ },
532
+ applyAbsolutePosition: function applyAbsolutePosition(element, left, top) {
533
+ if (!element) {
534
+ return;
535
+ }
536
+ element.style.left = left + 'px';
537
+ element.style.top = top + 'px';
538
+ element.style.right = 'auto';
539
+ element.style.bottom = 'auto';
540
+ },
541
+ getWidgetDefaultSize: function getWidgetDefaultSize() {
542
+ return {
543
+ width: this.widgetDefaultWidth || parsePixelValue(config.widget.defaultWidth) || 380,
544
+ height: this.widgetDefaultHeight || parsePixelValue(config.widget.defaultHeight) || 520
545
+ };
546
+ },
547
+ getWidgetToggleGap: function getWidgetToggleGap() {
548
+ return 8;
549
+ },
550
+ isWidgetPanelVisible: function isWidgetPanelVisible() {
551
+ if (!this.widgetContainerElement) {
552
+ return false;
553
+ }
554
+ var computed = window.getComputedStyle(this.widgetContainerElement);
555
+ return this.widgetContainerElement.classList.contains('open') && computed.display !== 'none';
556
+ },
557
+ persistWidgetPanelPosition: function persistWidgetPanelPosition() {
558
+ if (!this.widgetContainerElement || !config.widget.persistLayout) {
559
+ return;
560
+ }
561
+ var panelDefaultSize = this.getWidgetDefaultSize();
562
+ var panelPosition = this.getElementPosition(this.widgetContainerElement);
563
+ var panelSize = this.getElementSize(this.widgetContainerElement, panelDefaultSize.width, panelDefaultSize.height);
564
+ var toggleSize = this.getElementSize(this.widgetToggleButtonElement, 52, 52);
565
+ var linkedPosition = this.clampWidgetPosition(panelPosition.left + panelSize.width - toggleSize.width, panelPosition.top + panelSize.height + this.getWidgetToggleGap(), toggleSize.width, toggleSize.height);
566
+ this.saveStoredWidgetLayout({
567
+ position: {
568
+ left: linkedPosition.left,
569
+ top: linkedPosition.top
570
+ }
571
+ });
572
+ },
573
+ persistWidgetTogglePosition: function persistWidgetTogglePosition() {
574
+ if (!this.widgetToggleButtonElement || !config.widget.persistLayout) {
575
+ return;
576
+ }
577
+ var toggleRect = this.widgetToggleButtonElement.getBoundingClientRect();
578
+ this.saveStoredWidgetLayout({
579
+ position: {
580
+ left: this.getElementPosition(this.widgetToggleButtonElement).left,
581
+ top: this.getElementPosition(this.widgetToggleButtonElement).top
582
+ }
583
+ });
584
+ },
585
+ persistWidgetPanelSize: function persistWidgetPanelSize() {
586
+ if (!this.widgetContainerElement || !config.widget.enableResizing || !config.widget.persistLayout) {
587
+ return;
588
+ }
589
+ var defaultSize = this.getWidgetDefaultSize();
590
+ var currentSize = this.getElementSize(this.widgetContainerElement, defaultSize.width, defaultSize.height);
591
+ this.saveStoredWidgetLayout({
592
+ panel: {
593
+ width: Math.abs(currentSize.width - defaultSize.width) <= 1 ? null : currentSize.width,
594
+ height: Math.abs(currentSize.height - defaultSize.height) <= 1 ? null : currentSize.height
595
+ }
596
+ });
597
+ },
598
+ syncTogglePositionToPanel: function syncTogglePositionToPanel() {
599
+ var persistLayout = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : true;
600
+ if (!this.widgetContainerElement || !this.widgetToggleButtonElement) {
601
+ return;
602
+ }
603
+ var panelDefaultSize = this.getWidgetDefaultSize();
604
+ var panelSize = this.getElementSize(this.widgetContainerElement, panelDefaultSize.width, panelDefaultSize.height);
605
+ var toggleSize = this.getElementSize(this.widgetToggleButtonElement, 52, 52);
606
+ var panelPosition = this.getElementPosition(this.widgetContainerElement);
607
+ var linkedPosition = this.clampWidgetPosition(panelPosition.left + panelSize.width - toggleSize.width, panelPosition.top + panelSize.height + this.getWidgetToggleGap(), toggleSize.width, toggleSize.height);
608
+ this.applyAbsolutePosition(this.widgetToggleButtonElement, linkedPosition.left, linkedPosition.top);
609
+ if (persistLayout && config.widget.persistLayout) {
610
+ this.persistWidgetTogglePosition();
611
+ }
612
+ },
613
+ syncPanelPositionToToggle: function syncPanelPositionToToggle() {
614
+ var persistLayout = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : true;
615
+ if (!this.widgetContainerElement || !this.widgetToggleButtonElement) {
616
+ return;
617
+ }
618
+ var panelDefaultSize = this.getWidgetDefaultSize();
619
+ var panelSize = this.getElementSize(this.widgetContainerElement, panelDefaultSize.width, panelDefaultSize.height);
620
+ var toggleSize = this.getElementSize(this.widgetToggleButtonElement, 52, 52);
621
+ var togglePosition = this.getElementPosition(this.widgetToggleButtonElement);
622
+ var linkedPosition = this.clampWidgetPosition(togglePosition.left + toggleSize.width - panelSize.width, togglePosition.top - panelSize.height - this.getWidgetToggleGap(), panelSize.width, panelSize.height);
623
+ this.applyAbsolutePosition(this.widgetContainerElement, linkedPosition.left, linkedPosition.top);
624
+ if (persistLayout && config.widget.persistLayout) {
625
+ this.persistWidgetPanelPosition();
626
+ }
627
+ },
628
+ updateWidgetResetSizeButtonVisibility: function updateWidgetResetSizeButtonVisibility() {
629
+ if (!this.widgetResetSizeButtonElement) {
630
+ return;
631
+ }
632
+ if (!config.widget.enableResizing || !this.widgetContainerElement) {
633
+ this.widgetResetSizeButtonElement.classList.add('d-none');
634
+ return;
635
+ }
636
+ var defaultSize = this.getWidgetDefaultSize();
637
+ var currentSize = this.getElementSize(this.widgetContainerElement, defaultSize.width, defaultSize.height);
638
+ var isResized = Math.abs(currentSize.width - defaultSize.width) > 1 || Math.abs(currentSize.height - defaultSize.height) > 1 || !!this.widgetContainerElement.style.width || !!this.widgetContainerElement.style.height;
639
+ this.widgetResetSizeButtonElement.classList.toggle('d-none', !isResized);
640
+ },
641
+ resetWidgetSize: function resetWidgetSize() {
642
+ if (!this.widgetContainerElement || !config.widget.enableResizing) {
643
+ return;
644
+ }
645
+ this.widgetContainerElement.style.removeProperty('width');
646
+ this.widgetContainerElement.style.removeProperty('height');
647
+ this.persistWidgetPanelSize();
648
+ this.constrainWidgetElementsToViewport();
649
+ this.updateWidgetResetSizeButtonVisibility();
650
+ },
651
+ restoreWidgetLayout: function restoreWidgetLayout() {
652
+ var storedLayout = this.getStoredWidgetLayout();
653
+ if (this.widgetContainerElement && config.widget.enableResizing) {
654
+ if (Number.isFinite(storedLayout.panel && storedLayout.panel.width)) {
655
+ this.widgetContainerElement.style.width = storedLayout.panel.width + 'px';
656
+ } else {
657
+ this.widgetContainerElement.style.removeProperty('width');
658
+ }
659
+ if (Number.isFinite(storedLayout.panel && storedLayout.panel.height)) {
660
+ this.widgetContainerElement.style.height = storedLayout.panel.height + 'px';
661
+ } else {
662
+ this.widgetContainerElement.style.removeProperty('height');
663
+ }
664
+ }
665
+ if (this.widgetToggleButtonElement && config.widget.enableDragging && Number.isFinite(storedLayout.position && storedLayout.position.left) && Number.isFinite(storedLayout.position && storedLayout.position.top)) {
666
+ this.applyAbsolutePosition(this.widgetToggleButtonElement, storedLayout.position.left, storedLayout.position.top);
667
+ this.syncPanelPositionToToggle(false);
668
+ } else if (this.widgetToggleButtonElement && config.widget.enableDragging && Number.isFinite(storedLayout.toggle && storedLayout.toggle.left) && Number.isFinite(storedLayout.toggle && storedLayout.toggle.top)) {
669
+ this.applyAbsolutePosition(this.widgetToggleButtonElement, storedLayout.toggle.left, storedLayout.toggle.top);
670
+ this.syncPanelPositionToToggle(false);
671
+ } else if (this.widgetContainerElement && config.widget.enableDragging && Number.isFinite(storedLayout.panel && storedLayout.panel.left) && Number.isFinite(storedLayout.panel && storedLayout.panel.top)) {
672
+ this.applyAbsolutePosition(this.widgetContainerElement, storedLayout.panel.left, storedLayout.panel.top);
673
+ this.syncTogglePositionToPanel(false);
674
+ }
675
+ this.constrainWidgetElementsToViewport();
676
+ this.updateWidgetResetSizeButtonVisibility();
677
+ },
678
+ attachDraggableWidgetElement: function attachDraggableWidgetElement(element, handle, layoutKey, suppressClickOnDrag) {
679
+ var _this = this;
680
+ if (!element || !handle || !config.widget.enableDragging) {
681
+ return;
682
+ }
683
+ var activePointerId = null;
684
+ var startX = 0;
685
+ var startY = 0;
686
+ var startLeft = 0;
687
+ var startTop = 0;
688
+ var dragged = false;
689
+ var dragThreshold = 4;
690
+ var interactiveSelector = 'button, a, input, textarea, select, option, label';
691
+ var _stopDragging = function stopDragging(event) {
692
+ if (activePointerId === null) {
693
+ return;
694
+ }
695
+ if (event && event.pointerId !== undefined && event.pointerId !== activePointerId) {
696
+ return;
697
+ }
698
+ document.removeEventListener('pointermove', onPointerMove);
699
+ document.removeEventListener('pointerup', _stopDragging);
700
+ document.removeEventListener('pointercancel', _stopDragging);
701
+ element.classList.remove('ai-chat-widget-dragging');
702
+ activePointerId = null;
703
+ if (!dragged) {
704
+ return;
705
+ }
706
+ if (layoutKey === 'panel') {
707
+ _this.syncTogglePositionToPanel(false);
708
+ _this.persistWidgetPanelPosition();
709
+ } else if (layoutKey === 'toggle') {
710
+ _this.syncPanelPositionToToggle(false);
711
+ _this.persistWidgetTogglePosition();
712
+ }
713
+ if (config.widget.persistLayout) {
714
+ _this.persistWidgetPanelPosition();
715
+ _this.persistWidgetTogglePosition();
716
+ }
717
+ if (suppressClickOnDrag) {
718
+ element.dataset.dragSuppressClick = 'true';
719
+ window.setTimeout(function () {
720
+ if (element.dataset.dragSuppressClick === 'true') {
721
+ element.dataset.dragSuppressClick = 'false';
722
+ }
723
+ }, 0);
724
+ }
725
+ };
726
+ var onPointerMove = function onPointerMove(event) {
727
+ if (activePointerId === null || event.pointerId !== activePointerId) {
728
+ return;
729
+ }
730
+ var deltaX = event.clientX - startX;
731
+ var deltaY = event.clientY - startY;
732
+ if (!dragged && Math.hypot(deltaX, deltaY) < dragThreshold) {
733
+ return;
734
+ }
735
+ dragged = true;
736
+ var defaultSize = _this.getWidgetDefaultSize();
737
+ var currentSize = _this.getElementSize(element, layoutKey === 'panel' ? defaultSize.width : 52, layoutKey === 'panel' ? defaultSize.height : 52);
738
+ var nextPosition = _this.clampWidgetPosition(startLeft + deltaX, startTop + deltaY, currentSize.width, currentSize.height);
739
+ _this.applyAbsolutePosition(element, nextPosition.left, nextPosition.top);
740
+ if (layoutKey === 'panel') {
741
+ _this.syncTogglePositionToPanel(false);
742
+ } else if (layoutKey === 'toggle') {
743
+ _this.syncPanelPositionToToggle(false);
744
+ }
745
+ };
746
+ handle.addEventListener('pointerdown', function (event) {
747
+ if (event.button !== 0) {
748
+ return;
749
+ }
750
+ if (layoutKey === 'panel' && event.target.closest(interactiveSelector)) {
751
+ return;
752
+ }
753
+ var rect = element.getBoundingClientRect();
754
+ startX = event.clientX;
755
+ startY = event.clientY;
756
+ startLeft = rect.left;
757
+ startTop = rect.top;
758
+ dragged = false;
759
+ activePointerId = event.pointerId;
760
+ element.classList.add('ai-chat-widget-dragging');
761
+ if (handle.setPointerCapture) {
762
+ handle.setPointerCapture(event.pointerId);
763
+ }
764
+ document.addEventListener('pointermove', onPointerMove);
765
+ document.addEventListener('pointerup', _stopDragging);
766
+ document.addEventListener('pointercancel', _stopDragging);
767
+ event.preventDefault();
768
+ });
769
+ },
770
+ constrainWidgetElementsToViewport: function constrainWidgetElementsToViewport() {
771
+ if (this.widgetContainerElement && this.widgetContainerElement.style.left && this.isWidgetPanelVisible()) {
772
+ var panelDefaultSize = this.getWidgetDefaultSize();
773
+ var panelSize = this.getElementSize(this.widgetContainerElement, panelDefaultSize.width, panelDefaultSize.height);
774
+ var panelPosition = this.getElementPosition(this.widgetContainerElement);
775
+ var clampedPanel = this.clampWidgetPosition(panelPosition.left, panelPosition.top, panelSize.width, panelSize.height);
776
+ this.applyAbsolutePosition(this.widgetContainerElement, clampedPanel.left, clampedPanel.top);
777
+ this.syncTogglePositionToPanel(false);
778
+ if (config.widget.persistLayout) {
779
+ this.persistWidgetPanelPosition();
780
+ this.persistWidgetTogglePosition();
781
+ }
782
+ } else if (this.widgetToggleButtonElement && this.widgetToggleButtonElement.style.left) {
783
+ var toggleSize = this.getElementSize(this.widgetToggleButtonElement, 52, 52);
784
+ var togglePosition = this.getElementPosition(this.widgetToggleButtonElement);
785
+ var clampedToggle = this.clampWidgetPosition(togglePosition.left, togglePosition.top, toggleSize.width, toggleSize.height);
786
+ this.applyAbsolutePosition(this.widgetToggleButtonElement, clampedToggle.left, clampedToggle.top);
787
+ this.syncPanelPositionToToggle(false);
788
+ if (config.widget.persistLayout) {
789
+ this.persistWidgetTogglePosition();
790
+ this.persistWidgetPanelPosition();
791
+ }
792
+ }
793
+ },
421
794
  handleDragOver: function handleDragOver(e) {
422
795
  if (!config.sessionDocumentsEnabled) return;
423
796
  e.preventDefault();
@@ -458,7 +831,7 @@ window.openAIChatManager = function () {
458
831
  e.target.value = '';
459
832
  },
460
833
  uploadFiles: function uploadFiles(files) {
461
- var _this = this;
834
+ var _this2 = this;
462
835
  return _asyncToGenerator(/*#__PURE__*/_regenerator().m(function _callee() {
463
836
  var sessionId, profileId, formData, i, response, errorText, uploadError, result, j, _t;
464
837
  return _regenerator().w(function (_context) {
@@ -470,8 +843,8 @@ window.openAIChatManager = function () {
470
843
  }
471
844
  return _context.a(2);
472
845
  case 1:
473
- sessionId = _this.getSessionId();
474
- profileId = _this.getProfileId();
846
+ sessionId = _this2.getSessionId();
847
+ profileId = _this2.getProfileId();
475
848
  if (!(!sessionId && !profileId)) {
476
849
  _context.n = 2;
477
850
  break;
@@ -479,9 +852,9 @@ window.openAIChatManager = function () {
479
852
  console.warn('Cannot upload documents without a session or profile.');
480
853
  return _context.a(2);
481
854
  case 2:
482
- _this.isUploading = true;
483
- _this.uploadErrors = [];
484
- _this.renderDocumentBar();
855
+ _this2.isUploading = true;
856
+ _this2.uploadErrors = [];
857
+ _this2.renderDocumentBar();
485
858
  _context.p = 3;
486
859
  formData = new FormData();
487
860
  if (sessionId) {
@@ -507,9 +880,9 @@ window.openAIChatManager = function () {
507
880
  return response.text();
508
881
  case 5:
509
882
  errorText = _context.v;
510
- uploadError = _this.extractReadableErrorMessage(errorText, 'Upload failed. Please try again.');
883
+ uploadError = _this2.extractReadableErrorMessage(errorText, 'Upload failed. Please try again.');
511
884
  console.error('Upload failed:', errorText);
512
- _this.uploadErrors = [{
885
+ _this2.uploadErrors = [{
513
886
  fileName: '',
514
887
  error: uploadError
515
888
  }];
@@ -521,15 +894,15 @@ window.openAIChatManager = function () {
521
894
  result = _context.v;
522
895
  // If the server created a new session, initialize it.
523
896
  if (result.sessionId && !sessionId) {
524
- _this.initializeSession(result.sessionId);
897
+ _this2.initializeSession(result.sessionId);
525
898
  }
526
899
  if (result.uploaded && result.uploaded.length > 0) {
527
900
  for (j = 0; j < result.uploaded.length; j++) {
528
- _this.documents.push(result.uploaded[j]);
901
+ _this2.documents.push(result.uploaded[j]);
529
902
  }
530
903
  }
531
904
  if (result.failed && result.failed.length > 0) {
532
- _this.uploadErrors = result.failed;
905
+ _this2.uploadErrors = result.failed;
533
906
  }
534
907
  _context.n = 9;
535
908
  break;
@@ -537,14 +910,14 @@ window.openAIChatManager = function () {
537
910
  _context.p = 8;
538
911
  _t = _context.v;
539
912
  console.error('Upload error:', _t);
540
- _this.uploadErrors = [{
913
+ _this2.uploadErrors = [{
541
914
  fileName: '',
542
915
  error: 'Upload failed. Please try again.'
543
916
  }];
544
917
  case 9:
545
918
  _context.p = 9;
546
- _this.isUploading = false;
547
- _this.renderDocumentBar();
919
+ _this2.isUploading = false;
920
+ _this2.renderDocumentBar();
548
921
  return _context.f(9);
549
922
  case 10:
550
923
  return _context.a(2);
@@ -553,7 +926,7 @@ window.openAIChatManager = function () {
553
926
  }))();
554
927
  },
555
928
  removeDocument: function removeDocument(doc) {
556
- var _this2 = this;
929
+ var _this3 = this;
557
930
  return _asyncToGenerator(/*#__PURE__*/_regenerator().m(function _callee2() {
558
931
  var sessionId, response, idx, errorText, removeError, _t2;
559
932
  return _regenerator().w(function (_context2) {
@@ -566,7 +939,7 @@ window.openAIChatManager = function () {
566
939
  return _context2.a(2);
567
940
  case 1:
568
941
  _context2.p = 1;
569
- sessionId = _this2.getSessionId();
942
+ sessionId = _this3.getSessionId();
570
943
  _context2.n = 2;
571
944
  return fetch(config.removeDocumentUrl, {
572
945
  method: 'POST',
@@ -584,8 +957,8 @@ window.openAIChatManager = function () {
584
957
  _context2.n = 3;
585
958
  break;
586
959
  }
587
- idx = _this2.documents.indexOf(doc);
588
- if (idx > -1) _this2.documents.splice(idx, 1);
960
+ idx = _this3.documents.indexOf(doc);
961
+ if (idx > -1) _this3.documents.splice(idx, 1);
589
962
  _context2.n = 5;
590
963
  break;
591
964
  case 3:
@@ -593,13 +966,13 @@ window.openAIChatManager = function () {
593
966
  return response.text();
594
967
  case 4:
595
968
  errorText = _context2.v;
596
- removeError = _this2.extractReadableErrorMessage(errorText, 'Failed to remove document. Please try again.');
969
+ removeError = _this3.extractReadableErrorMessage(errorText, 'Failed to remove document. Please try again.');
597
970
  console.error('Failed to remove document:', response.status, errorText);
598
- _this2.uploadErrors = [{
971
+ _this3.uploadErrors = [{
599
972
  fileName: doc.fileName || '',
600
973
  error: removeError
601
974
  }];
602
- _this2.renderDocumentBar();
975
+ _this3.renderDocumentBar();
603
976
  case 5:
604
977
  _context2.n = 7;
605
978
  break;
@@ -607,11 +980,11 @@ window.openAIChatManager = function () {
607
980
  _context2.p = 6;
608
981
  _t2 = _context2.v;
609
982
  console.error('Remove document error:', _t2);
610
- _this2.uploadErrors = [{
983
+ _this3.uploadErrors = [{
611
984
  fileName: doc.fileName || '',
612
985
  error: 'Failed to remove document. Please try again.'
613
986
  }];
614
- _this2.renderDocumentBar();
987
+ _this3.renderDocumentBar();
615
988
  case 7:
616
989
  return _context2.a(2);
617
990
  }
@@ -781,139 +1154,139 @@ window.openAIChatManager = function () {
781
1154
  return !!message && message.isStreaming && index === this.lastAssistantIndex && !(appearance && appearance.disableStreamingAnimation);
782
1155
  },
783
1156
  startConnection: function startConnection() {
784
- var _this3 = this;
1157
+ var _this4 = this;
785
1158
  return _asyncToGenerator(/*#__PURE__*/_regenerator().m(function _callee3() {
786
1159
  var _t3;
787
1160
  return _regenerator().w(function (_context3) {
788
1161
  while (1) switch (_context3.p = _context3.n) {
789
1162
  case 0:
790
- _this3.connection = new signalR.HubConnectionBuilder().withUrl(config.signalRHubUrl).withAutomaticReconnect().build();
1163
+ _this4.connection = new signalR.HubConnectionBuilder().withUrl(config.signalRHubUrl).withAutomaticReconnect().build();
791
1164
 
792
1165
  // Allow long-running operations (e.g., multi-step MCP tool calls)
793
1166
  // without the client disconnecting prematurely.
794
- _this3.connection.serverTimeoutInMilliseconds = 600000;
795
- _this3.connection.keepAliveIntervalInMilliseconds = 15000;
796
- _this3.connection.on("LoadSession", function (data) {
1167
+ _this4.connection.serverTimeoutInMilliseconds = 600000;
1168
+ _this4.connection.keepAliveIntervalInMilliseconds = 15000;
1169
+ _this4.connection.on("LoadSession", function (data) {
797
1170
  var _data$messages;
798
- _this3.initializeSession(data.sessionId, true);
799
- _this3.messages = [];
800
- _this3.documents = data.documents || [];
1171
+ _this4.initializeSession(data.sessionId, true);
1172
+ _this4.messages = [];
1173
+ _this4.documents = data.documents || [];
801
1174
  ((_data$messages = data.messages) !== null && _data$messages !== void 0 ? _data$messages : []).forEach(function (msg) {
802
- _this3.addMessage(msg);
803
- _this3.$nextTick(function () {
1175
+ _this4.addMessage(msg);
1176
+ _this4.$nextTick(function () {
804
1177
  renderChartsInMessage(msg);
805
1178
  });
806
1179
  });
807
1180
 
808
1181
  // Update feedback icons in the DOM after all messages have rendered.
809
- _this3.$nextTick(function () {
810
- _this3.refreshAllFeedbackIcons();
1182
+ _this4.$nextTick(function () {
1183
+ _this4.refreshAllFeedbackIcons();
811
1184
  });
812
1185
 
813
1186
  // When the session is new (no messages) and an initial prompt is configured,
814
1187
  // automatically send it as the first user message to trigger an AI response.
815
- if (_this3.messages.length === 0 && config.initialPrompt) {
816
- _this3.prompt = config.initialPrompt;
817
- _this3.sendMessage();
1188
+ if (_this4.messages.length === 0 && config.initialPrompt) {
1189
+ _this4.prompt = config.initialPrompt;
1190
+ _this4.sendMessage();
818
1191
  }
819
1192
  });
820
- _this3.connection.on("ReceiveError", function (error) {
1193
+ _this4.connection.on("ReceiveError", function (error) {
821
1194
  console.error("SignalR Error: ", error);
822
- if (_this3.isRecording) {
823
- _this3.stopRecording();
1195
+ if (_this4.isRecording) {
1196
+ _this4.stopRecording();
824
1197
  }
825
1198
 
826
1199
  // If this is a widget with a stale cached session (e.g., profile was deleted),
827
1200
  // clear the cached session and start fresh with the current profile.
828
- if (_this3.widgetIsInitialized && !_this3.isSessionStarted && !_this3._attemptedSessionRecovery) {
829
- _this3._attemptedSessionRecovery = true;
830
- localStorage.removeItem(_this3.chatWidgetStateSession);
831
- _this3.startNewSession();
1201
+ if (_this4.widgetIsInitialized && !_this4.isSessionStarted && !_this4._attemptedSessionRecovery) {
1202
+ _this4._attemptedSessionRecovery = true;
1203
+ localStorage.removeItem(_this4.chatWidgetStateSession);
1204
+ _this4.startNewSession();
832
1205
  }
833
1206
  });
834
- _this3.connection.on("MessageRated", function (messageId, userRating) {
835
- var msg = _this3.messages.find(function (m) {
1207
+ _this4.connection.on("MessageRated", function (messageId, userRating) {
1208
+ var msg = _this4.messages.find(function (m) {
836
1209
  return m.id === messageId;
837
1210
  });
838
1211
  if (msg) {
839
1212
  msg.userRating = userRating;
840
1213
  }
841
1214
  });
842
- _this3.connection.on("ReceiveTranscript", function (sessionId, text, isFinal) {
843
- if (_this3.isConversationMode) {
1215
+ _this4.connection.on("ReceiveTranscript", function (sessionId, text, isFinal) {
1216
+ if (_this4.isConversationMode) {
844
1217
  if (!isFinal && text) {
845
- _this3._conversationPartialTranscript = text;
1218
+ _this4._conversationPartialTranscript = text;
846
1219
  var escaped = text.replace(/&/g, '&amp;').replace(/</g, '&lt;').replace(/>/g, '&gt;');
847
1220
  var html = '<p class="ai-partial-transcript">' + escaped + '</p>';
848
1221
 
849
1222
  // Show partial transcript as a live user message.
850
- if (!_this3._conversationPartialMessage) {
851
- _this3.hidePlaceholder();
852
- _this3._conversationPartialMessage = {
1223
+ if (!_this4._conversationPartialMessage) {
1224
+ _this4.hidePlaceholder();
1225
+ _this4._conversationPartialMessage = {
853
1226
  role: 'user',
854
1227
  content: text,
855
1228
  htmlContent: html,
856
1229
  isPartial: true
857
1230
  };
858
- _this3.messages.push(_this3._conversationPartialMessage);
1231
+ _this4.messages.push(_this4._conversationPartialMessage);
859
1232
  } else {
860
- _this3._conversationPartialMessage.content = text;
861
- _this3._conversationPartialMessage.htmlContent = html;
1233
+ _this4._conversationPartialMessage.content = text;
1234
+ _this4._conversationPartialMessage.htmlContent = html;
862
1235
  }
863
- _this3.scrollToBottom();
1236
+ _this4.scrollToBottom();
864
1237
  }
865
1238
  return;
866
1239
  }
867
- if (text && !_this3._audioInputSent) {
868
- _this3.prompt = _this3.preRecordingPrompt + text;
869
- if (_this3.inputElement) {
870
- _this3.inputElement.value = _this3.prompt;
871
- _this3.inputElement.dispatchEvent(new Event('input'));
1240
+ if (text && !_this4._audioInputSent) {
1241
+ _this4.prompt = _this4.preRecordingPrompt + text;
1242
+ if (_this4.inputElement) {
1243
+ _this4.inputElement.value = _this4.prompt;
1244
+ _this4.inputElement.dispatchEvent(new Event('input'));
872
1245
  }
873
1246
  }
874
1247
  });
875
- _this3.connection.on("ReceiveConversationUserMessage", function (sessionId, text) {
1248
+ _this4.connection.on("ReceiveConversationUserMessage", function (sessionId, text) {
876
1249
  if (text) {
877
- _this3.stopAudio();
1250
+ _this4.stopAudio();
878
1251
 
879
1252
  // If there's an interrupted assistant message still streaming,
880
1253
  // mark it as done to stop the spinner animation.
881
- if (_this3._conversationAssistantMessage) {
882
- var oldMsg = _this3.messages[_this3._conversationAssistantMessage.index];
1254
+ if (_this4._conversationAssistantMessage) {
1255
+ var oldMsg = _this4.messages[_this4._conversationAssistantMessage.index];
883
1256
  if (oldMsg) {
884
1257
  oldMsg.isStreaming = false;
885
1258
  }
886
- _this3._conversationAssistantMessage = null;
1259
+ _this4._conversationAssistantMessage = null;
887
1260
  }
888
1261
 
889
1262
  // Replace the partial transcript message with the final one.
890
- if (_this3._conversationPartialMessage) {
1263
+ if (_this4._conversationPartialMessage) {
891
1264
  var escaped = text.replace(/&/g, '&amp;').replace(/</g, '&lt;').replace(/>/g, '&gt;');
892
- _this3._conversationPartialMessage.content = text;
893
- _this3._conversationPartialMessage.htmlContent = '<p>' + escaped + '</p>';
894
- _this3._conversationPartialMessage.isPartial = false;
895
- _this3._conversationPartialMessage = null;
1265
+ _this4._conversationPartialMessage.content = text;
1266
+ _this4._conversationPartialMessage.htmlContent = '<p>' + escaped + '</p>';
1267
+ _this4._conversationPartialMessage.isPartial = false;
1268
+ _this4._conversationPartialMessage = null;
896
1269
  } else {
897
- _this3.addMessage({
1270
+ _this4.addMessage({
898
1271
  role: 'user',
899
1272
  content: text
900
1273
  });
901
1274
  }
902
- _this3.scrollToBottom();
1275
+ _this4.scrollToBottom();
903
1276
  }
904
1277
  });
905
- _this3.connection.on("ReceiveConversationAssistantToken", function (sessionId, messageId, token, responseId, appearance) {
906
- if (!_this3._conversationAssistantMessage) {
907
- _this3.stopAudio();
908
- _this3.hideTypingIndicator();
1278
+ _this4.connection.on("ReceiveConversationAssistantToken", function (sessionId, messageId, token, responseId, appearance) {
1279
+ if (!_this4._conversationAssistantMessage) {
1280
+ _this4.stopAudio();
1281
+ _this4.hideTypingIndicator();
909
1282
 
910
1283
  // Ensure no stale streaming indicators remain from prior messages.
911
- for (var j = 0; j < _this3.messages.length; j++) {
912
- if (_this3.messages[j].isStreaming) {
913
- _this3.messages[j].isStreaming = false;
1284
+ for (var j = 0; j < _this4.messages.length; j++) {
1285
+ if (_this4.messages[j].isStreaming) {
1286
+ _this4.messages[j].isStreaming = false;
914
1287
  }
915
1288
  }
916
- var msgIndex = _this3.messages.length;
1289
+ var msgIndex = _this4.messages.length;
917
1290
  var newMessage = {
918
1291
  id: messageId,
919
1292
  role: "assistant",
@@ -921,72 +1294,72 @@ window.openAIChatManager = function () {
921
1294
  htmlContent: "",
922
1295
  isStreaming: true,
923
1296
  userRating: null,
924
- appearance: _this3.normalizeAssistantAppearance(appearance)
1297
+ appearance: _this4.normalizeAssistantAppearance(appearance)
925
1298
  };
926
- _this3.messages.push(newMessage);
927
- _this3._conversationAssistantMessage = {
1299
+ _this4.messages.push(newMessage);
1300
+ _this4._conversationAssistantMessage = {
928
1301
  index: msgIndex,
929
1302
  content: ''
930
1303
  };
931
1304
  }
932
- _this3._conversationAssistantMessage.content += token;
933
- var msg = _this3.messages[_this3._conversationAssistantMessage.index];
1305
+ _this4._conversationAssistantMessage.content += token;
1306
+ var msg = _this4.messages[_this4._conversationAssistantMessage.index];
934
1307
  if (msg) {
935
1308
  if (!msg.appearance) {
936
- msg.appearance = _this3.normalizeAssistantAppearance(appearance);
1309
+ msg.appearance = _this4.normalizeAssistantAppearance(appearance);
937
1310
  }
938
- msg.content = _this3._conversationAssistantMessage.content;
1311
+ msg.content = _this4._conversationAssistantMessage.content;
939
1312
  msg.htmlContent = parseMarkdownContent(msg.content, msg);
940
- _this3.$nextTick(function () {
1313
+ _this4.$nextTick(function () {
941
1314
  renderChartsInMessage(msg);
942
- _this3.scrollToBottom();
1315
+ _this4.scrollToBottom();
943
1316
  });
944
1317
  }
945
1318
  });
946
- _this3.connection.on("ReceiveConversationAssistantComplete", function (sessionId, messageId) {
947
- if (_this3._conversationAssistantMessage) {
948
- var msg = _this3.messages[_this3._conversationAssistantMessage.index];
1319
+ _this4.connection.on("ReceiveConversationAssistantComplete", function (sessionId, messageId) {
1320
+ if (_this4._conversationAssistantMessage) {
1321
+ var msg = _this4.messages[_this4._conversationAssistantMessage.index];
949
1322
  if (msg) {
950
1323
  msg.isStreaming = false;
951
1324
  }
952
- _this3._conversationAssistantMessage = null;
1325
+ _this4._conversationAssistantMessage = null;
953
1326
  }
954
1327
  });
955
- _this3.connection.on("ReceiveAudioChunk", function (sessionId, base64Audio, contentType) {
1328
+ _this4.connection.on("ReceiveAudioChunk", function (sessionId, base64Audio, contentType) {
956
1329
  if (base64Audio) {
957
1330
  var binaryString = atob(base64Audio);
958
1331
  var bytes = new Uint8Array(binaryString.length);
959
1332
  for (var i = 0; i < binaryString.length; i++) {
960
1333
  bytes[i] = binaryString.charCodeAt(i);
961
1334
  }
962
- _this3.audioChunks.push(bytes);
1335
+ _this4.audioChunks.push(bytes);
963
1336
  }
964
1337
  });
965
- _this3.connection.on("ReceiveAudioComplete", function (sessionId) {
966
- _this3.playCollectedAudio();
1338
+ _this4.connection.on("ReceiveAudioComplete", function (sessionId) {
1339
+ _this4.playCollectedAudio();
967
1340
  });
968
- _this3.connection.on("ReceiveNotification", function (notification) {
969
- _this3.receiveNotification(notification);
1341
+ _this4.connection.on("ReceiveNotification", function (notification) {
1342
+ _this4.receiveNotification(notification);
970
1343
  });
971
- _this3.connection.on("UpdateNotification", function (notification) {
972
- _this3.updateNotification(notification);
1344
+ _this4.connection.on("UpdateNotification", function (notification) {
1345
+ _this4.updateNotification(notification);
973
1346
  });
974
- _this3.connection.on("RemoveNotification", function (notificationType) {
975
- _this3.removeNotification(notificationType);
1347
+ _this4.connection.on("RemoveNotification", function (notificationType) {
1348
+ _this4.removeNotification(notificationType);
976
1349
  });
977
- _this3.connection.onreconnecting(function () {
1350
+ _this4.connection.onreconnecting(function () {
978
1351
  console.warn("SignalR: reconnecting...");
979
1352
  });
980
- _this3.connection.onreconnected(function () {
1353
+ _this4.connection.onreconnected(function () {
981
1354
  console.info("SignalR: reconnected.");
982
- if (_this3.isSessionStarted) {
983
- _this3.reloadCurrentSession();
1355
+ if (_this4.isSessionStarted) {
1356
+ _this4.reloadCurrentSession();
984
1357
  } else if (config.autoCreateSession) {
985
- _this3.startNewSession();
1358
+ _this4.startNewSession();
986
1359
  }
987
1360
  });
988
- _this3.connection.onclose(function (error) {
989
- if (_this3.isNavigatingAway) {
1361
+ _this4.connection.onclose(function (error) {
1362
+ if (_this4.isNavigatingAway) {
990
1363
  return;
991
1364
  }
992
1365
  if (error) {
@@ -995,7 +1368,7 @@ window.openAIChatManager = function () {
995
1368
  });
996
1369
  _context3.p = 1;
997
1370
  _context3.n = 2;
998
- return _this3.connection.start();
1371
+ return _this4.connection.start();
999
1372
  case 2:
1000
1373
  _context3.n = 4;
1001
1374
  break;
@@ -1010,7 +1383,7 @@ window.openAIChatManager = function () {
1010
1383
  }))();
1011
1384
  },
1012
1385
  addMessageInternal: function addMessageInternal(message) {
1013
- var _this4 = this;
1386
+ var _this5 = this;
1014
1387
  if (message.role === 'assistant') {
1015
1388
  message.appearance = this.normalizeAssistantAppearance(message.appearance);
1016
1389
  }
@@ -1024,7 +1397,7 @@ window.openAIChatManager = function () {
1024
1397
  }));
1025
1398
  this.messages.push(message);
1026
1399
  this.$nextTick(function () {
1027
- _this4.fireEvent(new CustomEvent("addedOpenAIPromotMessage", {
1400
+ _this5.fireEvent(new CustomEvent("addedOpenAIPromotMessage", {
1028
1401
  detail: {
1029
1402
  message: message
1030
1403
  }
@@ -1032,7 +1405,7 @@ window.openAIChatManager = function () {
1032
1405
  });
1033
1406
  },
1034
1407
  addMessage: function addMessage(message) {
1035
- var _this5 = this;
1408
+ var _this6 = this;
1036
1409
  // Ensure userRating is always defined for Vue reactivity.
1037
1410
  if (message.userRating === undefined) {
1038
1411
  message.userRating = null;
@@ -1120,17 +1493,17 @@ window.openAIChatManager = function () {
1120
1493
  this.$nextTick(function () {
1121
1494
  // Render any pending charts once the DOM is updated
1122
1495
  renderChartsInMessage(message);
1123
- _this5.scrollToBottom();
1496
+ _this6.scrollToBottom();
1124
1497
  });
1125
1498
  },
1126
1499
  addMessages: function addMessages(messages) {
1127
- var _this6 = this;
1500
+ var _this7 = this;
1128
1501
  for (var i = 0; i < messages.length; i++) {
1129
1502
  this.addMessageInternal(messages[i]);
1130
1503
  }
1131
1504
  this.hidePlaceholder();
1132
1505
  this.$nextTick(function () {
1133
- _this6.scrollToBottom();
1506
+ _this7.scrollToBottom();
1134
1507
  });
1135
1508
  },
1136
1509
  hidePlaceholder: function hidePlaceholder() {
@@ -1207,7 +1580,7 @@ window.openAIChatManager = function () {
1207
1580
  this.prompt = '';
1208
1581
  },
1209
1582
  startRecording: function startRecording() {
1210
- var _this7 = this;
1583
+ var _this8 = this;
1211
1584
  if (this.isRecording || !this.connection) {
1212
1585
  return;
1213
1586
  }
@@ -1219,17 +1592,17 @@ window.openAIChatManager = function () {
1219
1592
  }
1220
1593
  }).then(function (stream) {
1221
1594
  var mimeType = MediaRecorder.isTypeSupported('audio/ogg;codecs=opus') ? 'audio/ogg;codecs=opus' : MediaRecorder.isTypeSupported('audio/webm;codecs=opus') ? 'audio/webm;codecs=opus' : 'audio/webm';
1222
- _this7.mediaRecorder = new MediaRecorder(stream, {
1595
+ _this8.mediaRecorder = new MediaRecorder(stream, {
1223
1596
  mimeType: mimeType,
1224
1597
  audioBitsPerSecond: 128000
1225
1598
  });
1226
- _this7.preRecordingPrompt = _this7.prompt;
1227
- _this7._audioInputSent = false;
1599
+ _this8.preRecordingPrompt = _this8.prompt;
1600
+ _this8._audioInputSent = false;
1228
1601
  var subject = new signalR.Subject();
1229
- var profileId = _this7.getProfileId();
1230
- var sessionId = _this7.getSessionId() || '';
1602
+ var profileId = _this8.getProfileId();
1603
+ var sessionId = _this8.getSessionId() || '';
1231
1604
  var pendingChunk = Promise.resolve();
1232
- _this7.mediaRecorder.addEventListener('dataavailable', function (e) {
1605
+ _this8.mediaRecorder.addEventListener('dataavailable', function (e) {
1233
1606
  if (e.data && e.data.size > 0) {
1234
1607
  pendingChunk = pendingChunk.then(/*#__PURE__*/_asyncToGenerator(/*#__PURE__*/_regenerator().m(function _callee4() {
1235
1608
  var data, uint8Array, binaryString, base64;
@@ -1253,7 +1626,7 @@ window.openAIChatManager = function () {
1253
1626
  })));
1254
1627
  }
1255
1628
  });
1256
- _this7.mediaRecorder.addEventListener('stop', function () {
1629
+ _this8.mediaRecorder.addEventListener('stop', function () {
1257
1630
  stream.getTracks().forEach(function (track) {
1258
1631
  return track.stop();
1259
1632
  });
@@ -1262,10 +1635,10 @@ window.openAIChatManager = function () {
1262
1635
  });
1263
1636
  });
1264
1637
  var language = navigator.language || document.documentElement.lang || 'en-US';
1265
- _this7.connection.send("SendAudioStream", profileId, sessionId, subject, mimeType, language);
1266
- _this7.mediaRecorder.start(250);
1267
- _this7.isRecording = true;
1268
- _this7.updateMicButton();
1638
+ _this8.connection.send("SendAudioStream", profileId, sessionId, subject, mimeType, language);
1639
+ _this8.mediaRecorder.start(250);
1640
+ _this8.isRecording = true;
1641
+ _this8.updateMicButton();
1269
1642
  })["catch"](function (err) {
1270
1643
  console.error('Microphone access denied:', err);
1271
1644
  });
@@ -1298,7 +1671,7 @@ window.openAIChatManager = function () {
1298
1671
  }
1299
1672
  },
1300
1673
  streamMessage: function streamMessage(profileId, trimmedPrompt, sessionProfileId) {
1301
- var _this8 = this;
1674
+ var _this9 = this;
1302
1675
  if (this.stream) {
1303
1676
  this.stream.dispose();
1304
1677
  this.stream = null;
@@ -1315,14 +1688,14 @@ window.openAIChatManager = function () {
1315
1688
  var currentSessionId = this.getSessionId();
1316
1689
  this.stream = this.connection.stream("SendMessage", profileId, trimmedPrompt, currentSessionId, sessionProfileId).subscribe({
1317
1690
  next: function next(chunk) {
1318
- var message = _this8.messages[messageIndex];
1691
+ var message = _this9.messages[messageIndex];
1319
1692
  if (!message) {
1320
1693
  if (chunk.sessionId && !currentSessionId) {
1321
- _this8.initializeSession(chunk.sessionId);
1694
+ _this9.initializeSession(chunk.sessionId);
1322
1695
  }
1323
- _this8.hideTypingIndicator();
1696
+ _this9.hideTypingIndicator();
1324
1697
  // Re-assign the index after hiding the typing indicator.
1325
- messageIndex = _this8.messages.length;
1698
+ messageIndex = _this9.messages.length;
1326
1699
  var newMessage = {
1327
1700
  id: chunk.messageId,
1328
1701
  role: "assistant",
@@ -1332,7 +1705,7 @@ window.openAIChatManager = function () {
1332
1705
  isStreaming: true,
1333
1706
  userRating: null
1334
1707
  };
1335
- _this8.messages.push(newMessage);
1708
+ _this9.messages.push(newMessage);
1336
1709
  message = newMessage;
1337
1710
  }
1338
1711
  if (chunk.title && (!message.title || message.title !== chunk.title)) {
@@ -1372,46 +1745,46 @@ window.openAIChatManager = function () {
1372
1745
  // Update the existing message
1373
1746
  message.content = content;
1374
1747
  message.htmlContent = parseMarkdownContent(content, message);
1375
- _this8.messages[messageIndex] = message;
1376
- _this8.$nextTick(function () {
1748
+ _this9.messages[messageIndex] = message;
1749
+ _this9.$nextTick(function () {
1377
1750
  renderChartsInMessage(message);
1378
- _this8.scrollToBottom();
1751
+ _this9.scrollToBottom();
1379
1752
  });
1380
1753
  },
1381
1754
  complete: function complete() {
1382
- var _this8$stream;
1383
- _this8.processReferences(references, messageIndex);
1384
- _this8.streamingFinished();
1385
- var msg = _this8.messages[messageIndex];
1755
+ var _this9$stream;
1756
+ _this9.processReferences(references, messageIndex);
1757
+ _this9.streamingFinished();
1758
+ var msg = _this9.messages[messageIndex];
1386
1759
  if (msg) {
1387
1760
  msg.isStreaming = false;
1388
1761
  }
1389
1762
  if (!msg || !msg.content) {
1390
1763
  // No content received at all.
1391
- _this8.hideTypingIndicator();
1764
+ _this9.hideTypingIndicator();
1392
1765
  }
1393
1766
 
1394
1767
  // Trigger text-to-speech only in conversation mode.
1395
- if (_this8.isConversationMode && _this8.textToSpeechEnabled && msg && msg.content) {
1396
- _this8.synthesizeSpeech(msg.content);
1768
+ if (_this9.isConversationMode && _this9.textToSpeechEnabled && msg && msg.content) {
1769
+ _this9.synthesizeSpeech(msg.content);
1397
1770
  }
1398
- (_this8$stream = _this8.stream) === null || _this8$stream === void 0 || _this8$stream.dispose();
1399
- _this8.stream = null;
1771
+ (_this9$stream = _this9.stream) === null || _this9$stream === void 0 || _this9$stream.dispose();
1772
+ _this9.stream = null;
1400
1773
  },
1401
1774
  error: function error(err) {
1402
- var _this8$stream2;
1403
- _this8.processReferences(references, messageIndex);
1404
- _this8.streamingFinished();
1405
- var msg = _this8.messages[messageIndex];
1775
+ var _this9$stream2;
1776
+ _this9.processReferences(references, messageIndex);
1777
+ _this9.streamingFinished();
1778
+ var msg = _this9.messages[messageIndex];
1406
1779
  if (msg) {
1407
1780
  msg.isStreaming = false;
1408
1781
  }
1409
- _this8.hideTypingIndicator();
1410
- if (!_this8.isNavigatingAway) {
1411
- _this8.addMessage(_this8.getServiceDownMessage());
1782
+ _this9.hideTypingIndicator();
1783
+ if (!_this9.isNavigatingAway) {
1784
+ _this9.addMessage(_this9.getServiceDownMessage());
1412
1785
  }
1413
- (_this8$stream2 = _this8.stream) === null || _this8$stream2 === void 0 || _this8$stream2.dispose();
1414
- _this8.stream = null;
1786
+ (_this9$stream2 = _this9.stream) === null || _this9$stream2 === void 0 || _this9$stream2.dispose();
1787
+ _this9.stream = null;
1415
1788
  console.error("Stream error:", err);
1416
1789
  }
1417
1790
  });
@@ -1425,7 +1798,7 @@ window.openAIChatManager = function () {
1425
1798
  return newMessage;
1426
1799
  },
1427
1800
  processReferences: function processReferences(references, messageIndex) {
1428
- var _this9 = this;
1801
+ var _this0 = this;
1429
1802
  references = normalizeReferences(references);
1430
1803
  if (Object.keys(references).length) {
1431
1804
  var message = this.messages[messageIndex];
@@ -1510,7 +1883,7 @@ window.openAIChatManager = function () {
1510
1883
  this.messages[messageIndex] = message;
1511
1884
  this.$nextTick(function () {
1512
1885
  renderChartsInMessage(message);
1513
- _this9.scrollToBottom();
1886
+ _this0.scrollToBottom();
1514
1887
  });
1515
1888
  }
1516
1889
  },
@@ -1553,8 +1926,15 @@ window.openAIChatManager = function () {
1553
1926
  }
1554
1927
  }
1555
1928
  },
1929
+ handleExternalTtsStop: function handleExternalTtsStop(e) {
1930
+ var _e$detail;
1931
+ if ((e === null || e === void 0 || (_e$detail = e.detail) === null || _e$detail === void 0 ? void 0 : _e$detail.sourceId) === this.ttsInstanceId) {
1932
+ return;
1933
+ }
1934
+ this.stopAudio(false);
1935
+ },
1556
1936
  synthesizeSpeech: function synthesizeSpeech(text, cacheIndex) {
1557
- var _this0 = this;
1937
+ var _this1 = this;
1558
1938
  if (!this.textToSpeechEnabled || !text || !this.connection) {
1559
1939
  return;
1560
1940
  }
@@ -1563,9 +1943,9 @@ window.openAIChatManager = function () {
1563
1943
  this._ttsCacheIndex = cacheIndex !== undefined ? cacheIndex : -1;
1564
1944
  this.connection.invoke("SynthesizeSpeech", this.getProfileId(), this.getSessionId(), text, this.ttsVoiceName)["catch"](function (err) {
1565
1945
  console.error("TTS synthesis error:", err);
1566
- _this0.isPlayingAudio = false;
1567
- _this0.ttsPlayingMessageIndex = -1;
1568
- _this0._ttsCacheIndex = -1;
1946
+ _this1.isPlayingAudio = false;
1947
+ _this1.ttsPlayingMessageIndex = -1;
1948
+ _this1._ttsCacheIndex = -1;
1569
1949
  });
1570
1950
  },
1571
1951
  toggleMessageTts: function toggleMessageTts(message, index) {
@@ -1573,7 +1953,12 @@ window.openAIChatManager = function () {
1573
1953
  this.stopAudio();
1574
1954
  return;
1575
1955
  }
1576
- this.stopAudio();
1956
+ this.stopAudio(false);
1957
+ window.dispatchEvent(new CustomEvent('crestapps-ai-chat-stop-tts', {
1958
+ detail: {
1959
+ sourceId: this.ttsInstanceId
1960
+ }
1961
+ }));
1577
1962
  this.ttsPlayingMessageIndex = index;
1578
1963
  if (this.ttsAudioCache[index]) {
1579
1964
  this.playAudioBlob(this.ttsAudioCache[index]);
@@ -1585,6 +1970,7 @@ window.openAIChatManager = function () {
1585
1970
  if (this.audioChunks.length === 0) {
1586
1971
  if (!this.currentAudioElement && this.audioPlayQueue.length === 0) {
1587
1972
  this.isPlayingAudio = false;
1973
+ this.ttsPlayingMessageIndex = -1;
1588
1974
  }
1589
1975
  return;
1590
1976
  }
@@ -1623,26 +2009,32 @@ window.openAIChatManager = function () {
1623
2009
  this.playAudioBlob(blob);
1624
2010
  },
1625
2011
  playAudioBlob: function playAudioBlob(blob) {
1626
- var _this1 = this;
2012
+ var _this10 = this;
1627
2013
  var url = URL.createObjectURL(blob);
1628
2014
  var audio = new Audio(url);
2015
+ this.currentAudioUrl = url;
1629
2016
  this.currentAudioElement = audio;
1630
2017
  this.isPlayingAudio = true;
1631
2018
  audio.addEventListener('ended', function () {
2019
+ _this10.currentAudioElement = null;
2020
+ _this10.currentAudioUrl = null;
1632
2021
  URL.revokeObjectURL(url);
1633
- _this1.currentAudioElement = null;
1634
- _this1.playNextInQueue();
2022
+ _this10.playNextInQueue();
1635
2023
  });
1636
2024
  audio.addEventListener('error', function () {
2025
+ _this10.currentAudioElement = null;
2026
+ _this10.currentAudioUrl = null;
1637
2027
  URL.revokeObjectURL(url);
1638
- _this1.currentAudioElement = null;
1639
- _this1.playNextInQueue();
2028
+ _this10.playNextInQueue();
1640
2029
  });
1641
2030
  audio.play()["catch"](function (err) {
1642
2031
  console.error("Audio playback error:", err);
2032
+ _this10.currentAudioElement = null;
2033
+ _this10.currentAudioUrl = null;
1643
2034
  URL.revokeObjectURL(url);
1644
- _this1.currentAudioElement = null;
1645
- _this1.isPlayingAudio = false;
2035
+ _this10.audioPlayQueue = [];
2036
+ _this10.isPlayingAudio = false;
2037
+ _this10.ttsPlayingMessageIndex = -1;
1646
2038
  });
1647
2039
  },
1648
2040
  playNextInQueue: function playNextInQueue() {
@@ -1661,6 +2053,10 @@ window.openAIChatManager = function () {
1661
2053
  this.currentAudioElement.currentTime = 0;
1662
2054
  this.currentAudioElement = null;
1663
2055
  }
2056
+ if (this.currentAudioUrl) {
2057
+ URL.revokeObjectURL(this.currentAudioUrl);
2058
+ this.currentAudioUrl = null;
2059
+ }
1664
2060
  this.audioChunks = [];
1665
2061
  this.audioPlayQueue = [];
1666
2062
  this.isPlayingAudio = false;
@@ -1674,7 +2070,7 @@ window.openAIChatManager = function () {
1674
2070
  }
1675
2071
  },
1676
2072
  startConversationMode: function startConversationMode() {
1677
- var _this10 = this;
2073
+ var _this11 = this;
1678
2074
  if (!this.conversationModeEnabled || this.isConversationMode || !this.connection) {
1679
2075
  return;
1680
2076
  }
@@ -1694,12 +2090,12 @@ window.openAIChatManager = function () {
1694
2090
  }
1695
2091
  }).then(function (stream) {
1696
2092
  var mimeType = MediaRecorder.isTypeSupported('audio/ogg;codecs=opus') ? 'audio/ogg;codecs=opus' : MediaRecorder.isTypeSupported('audio/webm;codecs=opus') ? 'audio/webm;codecs=opus' : 'audio/webm';
1697
- _this10.mediaRecorder = new MediaRecorder(stream, {
2093
+ _this11.mediaRecorder = new MediaRecorder(stream, {
1698
2094
  mimeType: mimeType,
1699
2095
  audioBitsPerSecond: 128000
1700
2096
  });
1701
- _this10._conversationSubject = new signalR.Subject();
1702
- _this10._conversationStream = stream;
2097
+ _this11._conversationSubject = new signalR.Subject();
2098
+ _this11._conversationStream = stream;
1703
2099
 
1704
2100
  // Create an AnalyserNode for volume-based interrupt detection.
1705
2101
  // During TTS playback, detect when the user speaks above
@@ -1708,20 +2104,20 @@ window.openAIChatManager = function () {
1708
2104
  // speaker echo so the STT stream has no gaps.
1709
2105
  var AudioCtx = window.AudioContext || window.webkitAudioContext;
1710
2106
  if (AudioCtx) {
1711
- _this10._conversationAudioCtx = new AudioCtx();
1712
- _this10._conversationAnalyser = _this10._conversationAudioCtx.createAnalyser();
1713
- _this10._conversationAnalyser.fftSize = 256;
1714
- var micSource = _this10._conversationAudioCtx.createMediaStreamSource(stream);
1715
- micSource.connect(_this10._conversationAnalyser);
2107
+ _this11._conversationAudioCtx = new AudioCtx();
2108
+ _this11._conversationAnalyser = _this11._conversationAudioCtx.createAnalyser();
2109
+ _this11._conversationAnalyser.fftSize = 256;
2110
+ var micSource = _this11._conversationAudioCtx.createMediaStreamSource(stream);
2111
+ micSource.connect(_this11._conversationAnalyser);
1716
2112
  }
1717
2113
  var pendingChunk = Promise.resolve();
1718
- var analyser = _this10._conversationAnalyser;
2114
+ var analyser = _this11._conversationAnalyser;
1719
2115
  var interruptVolumeThreshold = 30;
1720
- _this10.mediaRecorder.addEventListener('dataavailable', function (e) {
2116
+ _this11.mediaRecorder.addEventListener('dataavailable', function (e) {
1721
2117
  if (e.data && e.data.size > 0) {
1722
2118
  // During TTS playback, check mic volume to detect
1723
2119
  // user interruption (speaking above threshold).
1724
- if (_this10.isPlayingAudio && analyser) {
2120
+ if (_this11.isPlayingAudio && analyser) {
1725
2121
  var freqData = new Uint8Array(analyser.frequencyBinCount);
1726
2122
  analyser.getByteFrequencyData(freqData);
1727
2123
  var sum = 0;
@@ -1731,7 +2127,7 @@ window.openAIChatManager = function () {
1731
2127
  var avg = sum / freqData.length;
1732
2128
  if (avg >= interruptVolumeThreshold) {
1733
2129
  // User is speaking — interrupt TTS playback.
1734
- _this10.stopAudio();
2130
+ _this11.stopAudio();
1735
2131
  }
1736
2132
  }
1737
2133
 
@@ -1753,7 +2149,7 @@ window.openAIChatManager = function () {
1753
2149
  }, '');
1754
2150
  base64 = btoa(binaryString);
1755
2151
  try {
1756
- _this10._conversationSubject.next(base64);
2152
+ _this11._conversationSubject.next(base64);
1757
2153
  } catch (err) {
1758
2154
  // Subject may have been completed already.
1759
2155
  }
@@ -1764,28 +2160,28 @@ window.openAIChatManager = function () {
1764
2160
  })));
1765
2161
  }
1766
2162
  });
1767
- _this10.mediaRecorder.addEventListener('stop', function () {
2163
+ _this11.mediaRecorder.addEventListener('stop', function () {
1768
2164
  stream.getTracks().forEach(function (track) {
1769
2165
  return track.stop();
1770
2166
  });
1771
2167
  pendingChunk.then(function () {
1772
2168
  try {
1773
- _this10._conversationSubject.complete();
2169
+ _this11._conversationSubject.complete();
1774
2170
  } catch (err) {
1775
2171
  // Already completed.
1776
2172
  }
1777
2173
  });
1778
2174
  });
1779
- var profileId = _this10.getProfileId();
1780
- var sessionId = _this10.getSessionId() || '';
2175
+ var profileId = _this11.getProfileId();
2176
+ var sessionId = _this11.getSessionId() || '';
1781
2177
  var language = navigator.language || document.documentElement.lang || 'en-US';
1782
- _this10.connection.send("StartConversation", profileId, sessionId, _this10._conversationSubject, mimeType, language);
1783
- _this10.mediaRecorder.start(250);
1784
- _this10.isRecording = true;
2178
+ _this11.connection.send("StartConversation", profileId, sessionId, _this11._conversationSubject, mimeType, language);
2179
+ _this11.mediaRecorder.start(250);
2180
+ _this11.isRecording = true;
1785
2181
  })["catch"](function (err) {
1786
2182
  console.error('Microphone access denied:', err);
1787
- _this10.isConversationMode = false;
1788
- _this10.updateConversationButton();
2183
+ _this11.isConversationMode = false;
2184
+ _this11.updateConversationButton();
1789
2185
  });
1790
2186
  },
1791
2187
  stopConversationMode: function stopConversationMode() {
@@ -1912,7 +2308,7 @@ window.openAIChatManager = function () {
1912
2308
  return removedCount;
1913
2309
  },
1914
2310
  receiveNotification: function receiveNotification(notification) {
1915
- var _this11 = this;
2311
+ var _this12 = this;
1916
2312
  if (!notification || !notification.type) {
1917
2313
  return;
1918
2314
  }
@@ -1927,7 +2323,7 @@ window.openAIChatManager = function () {
1927
2323
  }
1928
2324
  this.scheduleNotificationDismiss(notification);
1929
2325
  this.$nextTick(function () {
1930
- _this11.scrollToBottom();
2326
+ _this12.scrollToBottom();
1931
2327
  });
1932
2328
  },
1933
2329
  updateNotification: function updateNotification(notification) {
@@ -1944,12 +2340,12 @@ window.openAIChatManager = function () {
1944
2340
  }
1945
2341
  },
1946
2342
  scheduleNotificationDismiss: function scheduleNotificationDismiss(notification) {
1947
- var _this12 = this;
2343
+ var _this13 = this;
1948
2344
  if (!notification || !notification.type || !notification.autoDismissMs || notification.autoDismissMs <= 0) {
1949
2345
  return;
1950
2346
  }
1951
2347
  this.notificationDismissTimers[notification.type] = setTimeout(function () {
1952
- _this12.removeNotification(notification.type);
2348
+ _this13.removeNotification(notification.type);
1953
2349
  }, notification.autoDismissMs);
1954
2350
  },
1955
2351
  clearNotificationDismiss: function clearNotificationDismiss(notificationType) {
@@ -1979,12 +2375,12 @@ window.openAIChatManager = function () {
1979
2375
  });
1980
2376
  },
1981
2377
  scrollToBottom: function scrollToBottom() {
1982
- var _this13 = this;
2378
+ var _this14 = this;
1983
2379
  if (!this.autoScroll) {
1984
2380
  return;
1985
2381
  }
1986
2382
  setTimeout(function () {
1987
- _this13.chatContainer.scrollTop = _this13.chatContainer.scrollHeight - _this13.chatContainer.clientHeight;
2383
+ _this14.chatContainer.scrollTop = _this14.chatContainer.scrollHeight - _this14.chatContainer.clientHeight;
1988
2384
  }, 50);
1989
2385
  },
1990
2386
  handleUserInput: function handleUserInput(event) {
@@ -2026,16 +2422,16 @@ window.openAIChatManager = function () {
2026
2422
  });
2027
2423
  },
2028
2424
  initializeApp: function initializeApp() {
2029
- var _this14 = this;
2425
+ var _this15 = this;
2030
2426
  this.inputElement = document.querySelector(config.inputElementSelector);
2031
2427
  this.buttonElement = document.querySelector(config.sendButtonElementSelector);
2032
2428
  this.chatContainer = document.querySelector(config.chatContainerElementSelector);
2033
2429
  this.placeholder = document.querySelector(config.placeholderElementSelector);
2034
2430
  this.applyOrchestratorAvailability();
2035
2431
  var sessionId = this.getSessionId();
2036
- if (!config.widget && sessionId) {
2432
+ if (!hasWidgetConfig && sessionId) {
2037
2433
  this.loadSession(sessionId);
2038
- } else if (this.isOrchestratorAvailable() && config.autoCreateSession && !config.widget && !sessionId) {
2434
+ } else if (this.isOrchestratorAvailable() && config.autoCreateSession && !hasWidgetConfig && !sessionId) {
2039
2435
  this.startNewSession();
2040
2436
  }
2041
2437
 
@@ -2055,7 +2451,7 @@ window.openAIChatManager = function () {
2055
2451
  fileInput.accept = config.allowedExtensions;
2056
2452
  }
2057
2453
  fileInput.addEventListener('change', function (e) {
2058
- return _this14.handleFileInputChange(e);
2454
+ return _this15.handleFileInputChange(e);
2059
2455
  });
2060
2456
  this.documentBar.parentElement.appendChild(fileInput);
2061
2457
 
@@ -2063,13 +2459,13 @@ window.openAIChatManager = function () {
2063
2459
  var inputArea = this.inputElement ? this.inputElement.closest('.ai-admin-widget-input, .text-bg-light') : null;
2064
2460
  if (inputArea) {
2065
2461
  inputArea.addEventListener('dragover', function (e) {
2066
- return _this14.handleDragOver(e);
2462
+ return _this15.handleDragOver(e);
2067
2463
  });
2068
2464
  inputArea.addEventListener('dragleave', function (e) {
2069
- return _this14.handleDragLeave(e);
2465
+ return _this15.handleDragLeave(e);
2070
2466
  });
2071
2467
  inputArea.addEventListener('drop', function (e) {
2072
- return _this14.handleDrop(e);
2468
+ return _this15.handleDrop(e);
2073
2469
  });
2074
2470
  }
2075
2471
  }
@@ -2077,55 +2473,55 @@ window.openAIChatManager = function () {
2077
2473
 
2078
2474
  // Pause auto-scroll when the user manually scrolls up during streaming.
2079
2475
  this.chatContainer.addEventListener('scroll', function () {
2080
- if (!_this14.stream) {
2476
+ if (!_this15.stream) {
2081
2477
  return;
2082
2478
  }
2083
2479
  var threshold = 30;
2084
- var atBottom = _this14.chatContainer.scrollHeight - _this14.chatContainer.clientHeight - _this14.chatContainer.scrollTop <= threshold;
2085
- _this14.autoScroll = atBottom;
2480
+ var atBottom = _this15.chatContainer.scrollHeight - _this15.chatContainer.clientHeight - _this15.chatContainer.scrollTop <= threshold;
2481
+ _this15.autoScroll = atBottom;
2086
2482
  });
2087
2483
  this.inputElement.addEventListener('keydown', function (event) {
2088
- if (_this14.stream != null) {
2484
+ if (_this15.stream != null) {
2089
2485
  return;
2090
2486
  }
2091
2487
  if (event.key === "Enter" && !event.shiftKey) {
2092
2488
  event.preventDefault();
2093
- _this14.buttonElement.click();
2489
+ _this15.buttonElement.click();
2094
2490
  }
2095
2491
  });
2096
2492
  this.inputElement.addEventListener('input', function (e) {
2097
- _this14.handleUserInput(e);
2493
+ _this15.handleUserInput(e);
2098
2494
  if (e.target.value.trim()) {
2099
- _this14.buttonElement.removeAttribute('disabled');
2495
+ _this15.buttonElement.removeAttribute('disabled');
2100
2496
  } else {
2101
- _this14.buttonElement.setAttribute('disabled', true);
2497
+ _this15.buttonElement.setAttribute('disabled', true);
2102
2498
  }
2103
2499
  });
2104
2500
  this.buttonElement.addEventListener('click', function () {
2105
- if (_this14.stream != null) {
2106
- _this14.stream.dispose();
2107
- _this14.stream = null;
2108
- _this14.streamingFinished();
2109
- _this14.hideTypingIndicator();
2501
+ if (_this15.stream != null) {
2502
+ _this15.stream.dispose();
2503
+ _this15.stream = null;
2504
+ _this15.streamingFinished();
2505
+ _this15.hideTypingIndicator();
2110
2506
 
2111
2507
  // Clean up: remove empty assistant message or stop streaming animation.
2112
- if (_this14.messages.length > 0) {
2113
- var lastMsg = _this14.messages[_this14.messages.length - 1];
2508
+ if (_this15.messages.length > 0) {
2509
+ var lastMsg = _this15.messages[_this15.messages.length - 1];
2114
2510
  if (lastMsg.role === 'assistant' && !lastMsg.content) {
2115
- _this14.messages.pop();
2511
+ _this15.messages.pop();
2116
2512
  } else if (lastMsg.isStreaming) {
2117
2513
  lastMsg.isStreaming = false;
2118
2514
  }
2119
2515
  }
2120
2516
  return;
2121
2517
  }
2122
- _this14.sendMessage();
2518
+ _this15.sendMessage();
2123
2519
  });
2124
2520
  var promptGenerators = document.getElementsByClassName('profile-generated-prompt');
2125
2521
  for (var i = 0; i < promptGenerators.length; i++) {
2126
2522
  promptGenerators[i].addEventListener('click', function (e) {
2127
2523
  e.preventDefault();
2128
- _this14.generatePrompt(e.target);
2524
+ _this15.generatePrompt(e.target);
2129
2525
  });
2130
2526
  }
2131
2527
  var chatSessions = document.getElementsByClassName('chat-session-history-item');
@@ -2137,8 +2533,8 @@ window.openAIChatManager = function () {
2137
2533
  console.error('an element with the class chat-session-history-item with no data-session-id set.');
2138
2534
  return;
2139
2535
  }
2140
- _this14.loadSession(sessionId);
2141
- _this14.showChatScreen();
2536
+ _this15.loadSession(sessionId);
2537
+ _this15.showChatScreen();
2142
2538
  });
2143
2539
  }
2144
2540
  for (var _i5 = 0; _i5 < config.messages.length; _i5++) {
@@ -2147,7 +2543,7 @@ window.openAIChatManager = function () {
2147
2543
 
2148
2544
  // Update feedback icons in the DOM after initial messages have rendered.
2149
2545
  this.$nextTick(function () {
2150
- _this14.refreshAllFeedbackIcons();
2546
+ _this15.refreshAllFeedbackIcons();
2151
2547
  });
2152
2548
 
2153
2549
  // Delegate click for code block copy buttons.
@@ -2179,7 +2575,7 @@ window.openAIChatManager = function () {
2179
2575
  if (this.micButton) {
2180
2576
  this.micButton.style.display = '';
2181
2577
  this.micButton.addEventListener('click', function () {
2182
- _this14.toggleRecording();
2578
+ _this15.toggleRecording();
2183
2579
  });
2184
2580
  }
2185
2581
  }
@@ -2189,7 +2585,7 @@ window.openAIChatManager = function () {
2189
2585
  this.conversationButton = document.querySelector(config.conversationButtonElementSelector);
2190
2586
  if (this.conversationButton) {
2191
2587
  this.conversationButton.addEventListener('click', function () {
2192
- _this14.toggleConversationMode();
2588
+ _this15.toggleConversationMode();
2193
2589
  });
2194
2590
  }
2195
2591
  }
@@ -2221,7 +2617,7 @@ window.openAIChatManager = function () {
2221
2617
  }
2222
2618
  },
2223
2619
  initializeWidget: function initializeWidget() {
2224
- var _this15 = this;
2620
+ var _this16 = this;
2225
2621
  if (!config.widget.chatWidgetContainer) {
2226
2622
  console.error('The widget chatWidgetContainer is required.');
2227
2623
  return;
@@ -2239,7 +2635,51 @@ window.openAIChatManager = function () {
2239
2635
  }
2240
2636
  this.chatWidgetStateName = config.widget.chatWidgetStateName;
2241
2637
  this.chatWidgetStateSession = config.widget.chatWidgetStateName + 'Session';
2638
+ this.chatWidgetLayoutKey = config.widget.chatWidgetStateName + 'Layout';
2639
+ this.widgetContainerElement = chatWidgetContainer;
2640
+ this.widgetToggleButtonElement = config.widget.toggleButtonSelector ? document.querySelector(config.widget.toggleButtonSelector) : null;
2641
+ this.widgetResetSizeButtonElement = config.widget.resetSizeButtonSelector ? document.querySelector(config.widget.resetSizeButtonSelector) : null;
2642
+ this.widgetDefaultWidth = parsePixelValue(config.widget.defaultWidth) || parsePixelValue(window.getComputedStyle(chatWidgetContainer).width) || 380;
2643
+ this.widgetDefaultHeight = parsePixelValue(config.widget.defaultHeight) || parsePixelValue(window.getComputedStyle(chatWidgetContainer).height) || 520;
2242
2644
  this.widgetIsInitialized = true;
2645
+ if (config.widget.enableResizing) {
2646
+ chatWidgetContainer.classList.add('ai-chat-widget-resizable');
2647
+ chatWidgetContainer.style.minWidth = (parsePixelValue(config.widget.minWidth) || 320) + 'px';
2648
+ chatWidgetContainer.style.minHeight = (parsePixelValue(config.widget.minHeight) || 420) + 'px';
2649
+ if (typeof ResizeObserver !== 'undefined') {
2650
+ this.widgetResizeObserver = new ResizeObserver(function () {
2651
+ _this16.persistWidgetPanelSize();
2652
+ _this16.constrainWidgetElementsToViewport();
2653
+ _this16.updateWidgetResetSizeButtonVisibility();
2654
+ });
2655
+ this.widgetResizeObserver.observe(chatWidgetContainer);
2656
+ }
2657
+ }
2658
+ var dragHandleSelector = config.widget.dragHandleSelector || '.ai-chat-widget-header';
2659
+ var dragHandle = chatWidgetContainer.querySelector(dragHandleSelector);
2660
+ if (config.widget.enableDragging && dragHandle) {
2661
+ dragHandle.classList.add('ai-chat-widget-drag-handle');
2662
+ chatWidgetContainer.classList.add('ai-chat-widget-draggable');
2663
+ this.attachDraggableWidgetElement(chatWidgetContainer, dragHandle, 'panel', false);
2664
+ }
2665
+ if (config.widget.enableDragging && this.widgetToggleButtonElement) {
2666
+ this.widgetToggleButtonElement.classList.add('ai-chat-widget-draggable');
2667
+ this.attachDraggableWidgetElement(this.widgetToggleButtonElement, this.widgetToggleButtonElement, 'toggle', true);
2668
+ }
2669
+ if (this.widgetResetSizeButtonElement) {
2670
+ this.widgetResetSizeButtonElement.addEventListener('click', function (event) {
2671
+ event.preventDefault();
2672
+ _this16.resetWidgetSize();
2673
+ });
2674
+ }
2675
+ this.restoreWidgetLayout();
2676
+ if (!this.widgetViewportResizeHandler) {
2677
+ this.widgetViewportResizeHandler = function () {
2678
+ _this16.constrainWidgetElementsToViewport();
2679
+ _this16.updateWidgetResetSizeButtonVisibility();
2680
+ };
2681
+ window.addEventListener('resize', this.widgetViewportResizeHandler);
2682
+ }
2243
2683
 
2244
2684
  // Auto-load the last session so the user always sees previous chat history.
2245
2685
  this.reloadCurrentSession();
@@ -2250,14 +2690,14 @@ window.openAIChatManager = function () {
2250
2690
  var showHistoryButton = document.querySelector(config.widget.showHistoryButton);
2251
2691
  if (showHistoryButton) {
2252
2692
  showHistoryButton.addEventListener('click', function () {
2253
- _this15.chatHistorySection.classList.toggle('show');
2693
+ _this16.chatHistorySection.classList.toggle('show');
2254
2694
  });
2255
2695
  }
2256
2696
  if (config.widget.closeHistoryButton) {
2257
2697
  var closeHistoryButton = document.querySelector(config.widget.closeHistoryButton);
2258
2698
  if (closeHistoryButton) {
2259
2699
  closeHistoryButton.addEventListener('click', function () {
2260
- _this15.showChatScreen();
2700
+ _this16.showChatScreen();
2261
2701
  });
2262
2702
  }
2263
2703
  }
@@ -2266,8 +2706,8 @@ window.openAIChatManager = function () {
2266
2706
  var newChatButton = document.querySelector(config.widget.newChatButton);
2267
2707
  if (newChatButton) {
2268
2708
  newChatButton.addEventListener('click', function () {
2269
- _this15.resetSession();
2270
- _this15.showChatScreen();
2709
+ _this16.resetSession();
2710
+ _this16.showChatScreen();
2271
2711
  });
2272
2712
  }
2273
2713
  }
@@ -2403,17 +2843,17 @@ window.openAIChatManager = function () {
2403
2843
  }
2404
2844
  },
2405
2845
  mounted: function mounted() {
2406
- var _this16 = this;
2846
+ var _this17 = this;
2407
2847
  _asyncToGenerator(/*#__PURE__*/_regenerator().m(function _callee6() {
2408
2848
  return _regenerator().w(function (_context6) {
2409
2849
  while (1) switch (_context6.n) {
2410
2850
  case 0:
2411
2851
  _context6.n = 1;
2412
- return _this16.startConnection();
2852
+ return _this17.startConnection();
2413
2853
  case 1:
2414
- _this16.initializeApp();
2415
- if (config.widget) {
2416
- _this16.initializeWidget();
2854
+ _this17.initializeApp();
2855
+ if (hasWidgetConfig) {
2856
+ _this17.initializeWidget();
2417
2857
  }
2418
2858
  case 2:
2419
2859
  return _context6.a(2);
@@ -2421,9 +2861,12 @@ window.openAIChatManager = function () {
2421
2861
  }, _callee6);
2422
2862
  }))();
2423
2863
  window.addEventListener('beforeunload', this.handleBeforeUnload);
2864
+ window.addEventListener('crestapps-ai-chat-stop-tts', this.handleExternalTtsStop);
2424
2865
  },
2425
2866
  beforeUnmount: function beforeUnmount() {
2426
2867
  window.removeEventListener('beforeunload', this.handleBeforeUnload);
2868
+ window.removeEventListener('crestapps-ai-chat-stop-tts', this.handleExternalTtsStop);
2869
+ this.stopAudio(false);
2427
2870
  if (this.stream) {
2428
2871
  this.stream.dispose();
2429
2872
  this.stream = null;