@crestapps/ai-chat-ui 2.0.0-preview.197 → 2.0.0-preview.203

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.
@@ -419,12 +419,73 @@
419
419
  display: inline-block;
420
420
  vertical-align: top;
421
421
  margin-top: 0.5rem;
422
+ /* The positioning context for the download that sits inside it. */
423
+ position: relative;
422
424
  }
423
425
 
424
426
  .ai-chat-widget-messages .generated-image-container img {
425
427
  max-width: 100% !important;
426
428
  max-height: 50vh;
427
429
  object-fit: contain;
430
+ /* The panel is 380px wide, so a figure in it is a thumbnail whatever it was drawn at. Pointing at it
431
+ says it can be opened to the size of the window. */
432
+ cursor: zoom-in;
433
+ }
434
+
435
+ /* Deliberately not scoped to the panel. The opened picture is a copy the library appends to the document
436
+ body, which is outside the widget entirely, so a rule under .ai-chat-widget-messages never reaches it.
437
+ It is repeated from ai-chat.css because a page that mounts only the widget loads only this file. The
438
+ copy carries the thumbnail's classes, so the frame and the cap are taken off here -- both are scaled up
439
+ with the drawing and would otherwise ring the picture with a broad white border. */
440
+ img.medium-zoom-image--opened {
441
+ max-width: none !important;
442
+ max-height: none !important;
443
+ padding: 0;
444
+ border: 0;
445
+ background: none;
446
+ }
447
+
448
+ /* Over the picture's lower corner rather than below it. In a panel this narrow a row of its own cost a
449
+ band of vertical space per figure, which a conversation full of them pays for once each. */
450
+ .ai-chat-widget-messages .generated-image-container .ai-download-image {
451
+ position: absolute;
452
+ right: 0.5rem;
453
+ bottom: 0.5rem;
454
+ z-index: 2;
455
+ opacity: 0;
456
+ color: #1f2933;
457
+ background-color: rgba(255, 255, 255, 0.92);
458
+ border-color: rgba(0, 0, 0, 0.15);
459
+ transition: opacity 0.15s ease-in-out;
460
+ }
461
+
462
+ /* The outline button this is built from inverts on hover: it fills itself with its own colour and turns
463
+ its label white. Against the pale ground kept here that leaves a white icon on white -- the button is
464
+ still there and still works, but there is nothing to see. Both colours are therefore stated for every
465
+ state the button can be in rather than only the resting one. */
466
+ .ai-chat-widget-messages .generated-image-container .ai-download-image:hover,
467
+ .ai-chat-widget-messages .generated-image-container .ai-download-image:focus,
468
+ .ai-chat-widget-messages .generated-image-container .ai-download-image:focus-visible,
469
+ .ai-chat-widget-messages .generated-image-container .ai-download-image:active {
470
+ color: #1f2933;
471
+ background-color: #ffffff;
472
+ border-color: rgba(0, 0, 0, 0.35);
473
+ }
474
+
475
+ /* Revealed by pointing at the figure. focus-within is what keeps it reachable by keyboard: the button is
476
+ invisible until it takes focus, and an invisible control that cannot be brought into view cannot be
477
+ used at all. */
478
+ .ai-chat-widget-messages .generated-image-container:hover .ai-download-image,
479
+ .ai-chat-widget-messages .generated-image-container:focus-within .ai-download-image {
480
+ opacity: 1;
481
+ }
482
+
483
+ /* A touch screen has no hover to reveal it with, so there it is simply always shown. The widget is the
484
+ surface most likely to be read on one. */
485
+ @media (hover: none) {
486
+ .ai-chat-widget-messages .generated-image-container .ai-download-image {
487
+ opacity: 1;
488
+ }
428
489
  }
429
490
 
430
491
  /* ── Tables inside widget ── */
@@ -1 +1 @@
1
- {"version":3,"sources":["chat-widget.css"],"names":[],"mappings":"AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA","file":"chat-widget.css","sourcesContent":["/* CrestApps AI Chat Widget */\n:root {\n --ai-widget-primary: var(--bs-secondary, #6c757d);\n --ai-widget-primary-rgb: var(--bs-secondary-rgb, 108, 117, 125);\n --ai-widget-width: 380px;\n --ai-widget-height: 520px;\n}\n\n.ai-chat-widget-toggle {\n position: fixed;\n bottom: 1.25rem;\n right: 1.25rem;\n z-index: 10000;\n width: 52px;\n height: 52px;\n border-radius: 50%;\n background: var(--ai-widget-primary);\n color: var(--bs-white, #fff);\n border: none;\n cursor: pointer;\n display: flex;\n align-items: center;\n justify-content: center;\n font-size: 1.5rem;\n box-shadow: 0 4px 12px rgba(var(--bs-body-color-rgb, 33, 37, 41), 0.2);\n transition: transform 0.2s, box-shadow 0.2s;\n}\n\n.ai-chat-widget-toggle:hover {\n transform: scale(1.1);\n box-shadow: 0 6px 16px rgba(var(--bs-body-color-rgb, 33, 37, 41), 0.3);\n}\n\n.ai-chat-widget-container {\n position: fixed;\n bottom: 5rem;\n right: 1.25rem;\n z-index: 10001;\n width: var(--ai-widget-width);\n height: var(--ai-widget-height);\n background: var(--bs-body-bg, #fff);\n color: var(--bs-body-color, #212529);\n border: 1px solid var(--bs-border-color, #dee2e6);\n border-radius: 12px;\n box-shadow: 0 8px 32px rgba(var(--bs-body-color-rgb, 33, 37, 41), 0.18);\n display: none;\n flex-direction: column;\n overflow: hidden;\n}\n\n.ai-chat-widget-container.ai-chat-widget-resizable {\n resize: both;\n min-width: 320px;\n min-height: 420px;\n max-width: min(90vw, 960px);\n max-height: min(90vh, 960px);\n}\n\n.ai-chat-widget-container.open {\n display: flex;\n}\n\n.ai-chat-widget-header {\n display: flex;\n align-items: center;\n justify-content: space-between;\n padding: 0.75rem 1rem;\n background: var(--ai-widget-primary);\n color: var(--bs-white, #fff);\n}\n\n.ai-chat-widget-header.ai-chat-widget-drag-handle {\n cursor: move;\n user-select: none;\n touch-action: none;\n}\n\n.ai-chat-widget-header-actions {\n display: flex;\n align-items: center;\n gap: 0.15rem;\n}\n\n.ai-chat-widget-toggle.ai-chat-widget-draggable {\n touch-action: none;\n}\n\n.ai-chat-widget-container.ai-chat-widget-dragging,\n.ai-chat-widget-toggle.ai-chat-widget-dragging {\n transition: none;\n}\n\n.ai-chat-widget-toggle.ai-chat-widget-dragging:hover {\n transform: none;\n}\n\n.ai-chat-widget-header .title {\n font-weight: 600;\n font-size: 0.95rem;\n}\n\n.ai-chat-widget-header button {\n background: none;\n border: none;\n color: var(--bs-white, #fff);\n cursor: pointer;\n font-size: 1rem;\n padding: 0 0.25rem;\n opacity: 0.8;\n}\n\n.ai-chat-widget-header button:hover {\n opacity: 1;\n}\n\n.ai-chat-widget-profile-select {\n padding: 0.5rem 0.75rem;\n border-bottom: 1px solid var(--bs-border-color, #dee2e6);\n background: var(--bs-tertiary-bg, #f8f9fa);\n}\n\n.ai-chat-widget-profile-select select {\n width: 100%;\n padding: 0.35rem 0.5rem;\n color: var(--bs-body-color, #212529);\n background-color: var(--bs-body-bg, #fff);\n border: 1px solid var(--bs-border-color, #ced4da);\n border-radius: 6px;\n font-size: 0.85rem;\n}\n\n.ai-chat-widget-messages {\n flex: 1;\n overflow-y: auto;\n padding: 0.75rem;\n font-size: 0.9rem;\n}\n\n/* ── Widget message items (ai-chat.js Vue template) ── */\n.ai-chat-widget-messages .ai-chat-messages {\n padding: 0.5rem;\n}\n\n.ai-chat-widget-messages .ai-chat-message-item {\n position: relative;\n padding: 0.5rem 0;\n}\n\n.ai-chat-widget-messages .ai-chat-message-item + .ai-chat-message-item::before {\n content: '';\n display: block;\n width: 100%;\n margin-bottom: 0.5rem;\n border-top: 1px solid rgba(var(--bs-body-color-rgb, 33, 37, 41), 0.08);\n}\n\n.ai-chat-widget-messages .ai-chat-message-heading {\n margin-bottom: 0.35rem;\n}\n\n.ai-chat-widget-messages .ai-chat-message-heading .ai-chat-msg-role {\n margin-bottom: 0;\n letter-spacing: 0.03em;\n}\n\n.ai-chat-widget-messages .ai-chat-message-heading-assistant .ai-chat-msg-role {\n display: inline-flex;\n align-items: center;\n gap: 0.35rem;\n}\n\n.ai-chat-widget-messages .ai-chat-message-role-label {\n display: inline-block;\n text-transform: inherit;\n}\n\n.ai-chat-widget-messages .ai-chat-msg-role {\n font-weight: 600;\n margin-bottom: 2px;\n text-transform: uppercase;\n}\n\n.ai-chat-widget-messages .ai-chat-msg-role-user {\n color: var(--bs-primary, #0d6efd);\n}\n\n.ai-chat-widget-messages .ai-chat-msg-role-assistant {\n color: var(--ai-widget-primary, #6f42c1);\n}\n\n.ai-chat-widget-messages .ai-bot-icon {\n color: currentColor;\n display: inline-block;\n animation: none;\n}\n\n@keyframes ai-widget-streaming-effect {\n 0% { opacity: 1; transform: rotate(0deg); }\n 10% { opacity: 0.3; transform: rotate(0deg); }\n 20% { opacity: 1; transform: rotate(0deg); }\n 30% { opacity: 0.3; transform: rotate(0deg); }\n 40% { opacity: 1; transform: rotate(0deg); }\n 50% { opacity: 0.3; transform: rotate(0deg); }\n 60% { opacity: 1; transform: rotate(0deg); }\n 80% { opacity: 1; transform: rotate(360deg); }\n 100% { opacity: 1; transform: rotate(360deg); }\n}\n\n.ai-chat-widget-messages .ai-streaming-icon {\n color: currentColor;\n display: inline-block;\n animation: ai-widget-streaming-effect 4s ease-in-out infinite;\n}\n\n.ai-chat-widget-messages .ai-chat-message-body {\n position: relative;\n padding: 0;\n overflow-wrap: break-word;\n word-break: break-word;\n min-width: 0;\n}\n\n.ai-chat-widget-messages .ai-chat-message-body p:last-child {\n margin-bottom: 0;\n}\n\n/* ── Message action buttons (copy, thumbs up/down) ── */\n.ai-chat-widget-messages .message-buttons-container {\n position: absolute;\n right: 0.1rem;\n bottom: 0;\n display: inline-flex;\n align-items: center;\n gap: 0.35rem;\n width: auto;\n max-width: max-content;\n opacity: 0;\n pointer-events: none;\n transition: opacity 0.15s ease-in-out;\n background: rgba(var(--bs-body-bg-rgb, 255, 255, 255), 0.96);\n padding: 0.2rem 0.35rem;\n border-radius: 999px;\n box-shadow: 0 1px 3px rgba(var(--bs-body-color-rgb, 33, 37, 41), 0.08);\n margin-bottom: 1rem;\n transform: translateY(50%);\n z-index: 1;\n}\n\n.ai-chat-widget-messages .ai-chat-message-item:hover .message-buttons-container,\n.ai-chat-widget-messages .ai-chat-message-item:focus-within .message-buttons-container {\n opacity: 1;\n pointer-events: auto;\n}\n\n.ai-chat-widget-messages .message-buttons-container:has(.tts-playing) {\n opacity: 1;\n pointer-events: auto;\n}\n\n.ai-chat-widget-messages .message-buttons-container > .button-message-toolbox {\n display: inline-flex;\n align-items: center;\n justify-content: center;\n flex: 0 0 auto;\n width: 1.75rem;\n height: 1.75rem;\n margin: 0 !important;\n padding: 0 !important;\n border-radius: 50%;\n color: var(--bs-secondary-color, #6c757d) !important;\n text-decoration: none;\n}\n\n.ai-chat-widget-messages .message-buttons-container .ai-chat-message-assistant-feedback {\n display: inline-flex;\n align-items: center;\n gap: 0.35rem;\n}\n\n.ai-chat-widget-messages .message-buttons-container > .button-message-toolbox:hover,\n.ai-chat-widget-messages .message-buttons-container > .button-message-toolbox:focus {\n color: var(--bs-body-color, #212529) !important;\n text-decoration: none;\n}\n\n.ai-chat-widget-messages .message-buttons-container > .button-message-toolbox.text-success,\n.ai-chat-widget-messages .message-buttons-container > .button-message-toolbox.text-success:hover,\n.ai-chat-widget-messages .message-buttons-container > .button-message-toolbox.text-success:focus {\n color: var(--bs-success, #198754) !important;\n text-decoration: none;\n}\n\n.ai-chat-widget-messages .message-buttons-container > .button-message-toolbox.text-success svg {\n color: inherit !important;\n fill: currentColor;\n}\n\n.ai-chat-widget-messages .message-buttons-container > .button-message-toolbox.tts-playing {\n color: var(--ai-widget-primary, #6f42c1) !important;\n opacity: 1;\n pointer-events: auto;\n}\n\n.ai-chat-widget-history-panel {\n position: absolute;\n top: 3.5rem;\n left: 0;\n right: 0;\n bottom: 0;\n background: var(--bs-body-bg, #fff);\n padding: 1rem;\n z-index: 10;\n display: none;\n overflow-y: auto;\n flex-direction: column;\n}\n\n.ai-chat-widget-history-panel.show {\n display: flex;\n}\n\n.ai-chat-widget-history-panel .history-header {\n display: flex;\n justify-content: space-between;\n align-items: center;\n margin-bottom: 0.75rem;\n flex-shrink: 0;\n}\n\n.ai-chat-widget-history-panel .history-header h5 {\n margin: 0;\n font-size: 1rem;\n}\n\n.ai-chat-widget-history-list {\n list-style: none;\n padding: 0;\n margin: 0;\n overflow-y: auto;\n flex: 1 1 auto;\n}\n\n.ai-chat-widget-history-list li {\n margin-bottom: 0;\n}\n\n.ai-chat-widget-history-list .chat-session-history-item {\n display: block;\n padding: 0.5rem 0.75rem;\n text-decoration: none;\n color: var(--bs-body-color, #212529);\n font-size: 0.85rem;\n white-space: nowrap;\n overflow: hidden;\n text-overflow: ellipsis;\n transition: background 0.15s;\n cursor: pointer;\n border-bottom: 1px solid var(--bs-border-color, #dee2e6);\n}\n\n.ai-chat-widget-history-list li:last-child .chat-session-history-item {\n border-bottom: none;\n}\n\n.ai-chat-widget-history-list .chat-session-history-item:hover {\n background: var(--bs-tertiary-bg, #f8f9fa);\n}\n\n/* ── Notifications ── */\n.ai-chat-widget-messages .ai-chat-notification {\n margin: 0.75rem 0.5rem 0;\n padding: 0.625rem 0.75rem;\n border: 1px solid rgba(var(--bs-body-color-rgb, 33, 37, 41), 0.12);\n border-radius: 0.5rem;\n background: rgba(var(--bs-tertiary-bg-rgb, 248, 249, 250), 0.98);\n}\n\n.ai-chat-widget-messages .ai-chat-notification-content {\n display: flex;\n align-items: flex-start;\n gap: 0.5rem;\n}\n\n.ai-chat-widget-messages .ai-chat-notification-icon {\n margin-top: 0.1rem;\n color: var(--bs-secondary-color, #6c757d);\n}\n\n.ai-chat-widget-messages .ai-chat-notification-text {\n flex: 1 1 auto;\n min-width: 0;\n}\n\n.ai-chat-widget-messages .ai-chat-notification-dismiss {\n margin-left: auto !important;\n color: var(--bs-secondary-color, #6c757d) !important;\n}\n\n.ai-chat-widget-messages .ai-chat-notification-actions {\n display: flex;\n gap: 0.5rem;\n margin-top: 0.5rem;\n}\n\n/* ── Chart containers inside widget ── */\n.ai-chat-widget-messages .chart-container {\n width: 100%;\n max-width: 100% !important;\n min-height: 280px;\n}\n\n/* ── Embedded images inside widget ── */\n/* The panel is narrower than the renderer's inline pixel cap, so an image overflows it horizontally\n without this. The chart container above is bounded the same way and for the same reason. */\n.ai-chat-widget-messages .generated-image-container {\n /* The wrapper is a span so a figure marker written mid-sentence does not close the paragraph it\n sits in. Inline-block restores the box behaviour the layout below assumes, and a top margin\n keeps a figure off the line of text it follows. */\n display: inline-block;\n vertical-align: top;\n margin-top: 0.5rem;\n}\n\n.ai-chat-widget-messages .generated-image-container img {\n max-width: 100% !important;\n max-height: 50vh;\n object-fit: contain;\n}\n\n/* ── Tables inside widget ── */\n/* The panel is 380px wide, so an ingested table almost always exceeds it. Scrolling the table itself keeps\n the conversation column intact; the alternative is a sideways-scrolling chat. */\n.ai-chat-widget-messages table {\n display: block;\n width: max-content;\n min-width: 100%;\n max-width: 100%;\n overflow-x: auto;\n margin-bottom: 0.75rem;\n border-collapse: collapse;\n font-size: 0.8125rem;\n}\n\n.ai-chat-widget-messages table th,\n.ai-chat-widget-messages table td {\n padding: 0.3rem 0.5rem;\n border: 1px solid var(--bs-border-color, #dee2e6);\n text-align: left;\n vertical-align: top;\n}\n\n.ai-chat-widget-messages table thead th {\n background-color: var(--bs-secondary-bg, #e9ecef);\n font-weight: 600;\n white-space: nowrap;\n}\n\n.ai-chat-widget-messages table tbody tr:nth-child(even) {\n background-color: var(--bs-tertiary-bg, #f8f9fa);\n}\n\n/* ── Code blocks & inline code ── */\n.ai-chat-widget-messages .ai-chat-message-body pre {\n background: var(--bs-dark-bg-subtle, #2d2d2d);\n color: var(--bs-emphasis-color, #f8f8f2);\n padding: 0.5rem;\n border-radius: 4px;\n overflow-x: auto;\n font-size: 0.8rem;\n}\n\n.ai-chat-widget-messages .ai-chat-message-body code {\n font-size: 0.85em;\n}\n\n.ai-chat-widget-input {\n border-top: 1px solid var(--bs-border-color, #dee2e6);\n padding: 0.5rem 0.75rem;\n display: flex;\n flex-wrap: wrap;\n gap: 0.5rem;\n align-items: flex-end;\n}\n\n.ai-chat-widget-input .ai-chat-doc-bar {\n flex: 0 0 100%;\n margin: -0.5rem -0.75rem 0.5rem;\n padding: 0.5rem 0.75rem;\n max-height: 4.5rem;\n overflow-y: auto;\n}\n\n.ai-chat-widget-input textarea {\n flex: 1;\n min-width: 0;\n color: var(--bs-body-color, #212529);\n background-color: var(--bs-body-bg, #fff);\n border: 1px solid var(--bs-border-color, #ced4da);\n border-radius: 8px;\n padding: 0.4rem 0.6rem;\n font-size: 0.9rem;\n resize: none;\n max-height: 80px;\n min-height: 36px;\n line-height: 1.4;\n outline: none;\n}\n\n.ai-chat-widget-input textarea:focus {\n border-color: var(--ai-widget-primary);\n box-shadow: 0 0 0 2px rgba(var(--ai-widget-primary-rgb, 108, 117, 125), 0.15);\n}\n\n.ai-chat-widget-input button {\n background: var(--ai-widget-primary);\n color: var(--bs-white, #fff);\n border: none;\n border-radius: 8px;\n padding: 0.4rem 0.75rem;\n cursor: pointer;\n font-size: 0.9rem;\n white-space: nowrap;\n}\n\n.ai-chat-widget-input .btn {\n background: var(--bs-body-bg, #fff);\n color: var(--bs-body-color, #212529);\n border: 1px solid var(--bs-border-color, #ced4da);\n}\n\n.ai-chat-widget-input .btn.btn-link {\n background: transparent;\n color: var(--bs-secondary-color, #6c757d);\n border: none;\n padding: 0.4rem;\n}\n\n.ai-chat-widget-input .btn.btn-outline-dark {\n background: var(--bs-dark, #212529);\n color: var(--bs-white, #fff);\n border-color: var(--bs-dark, #212529);\n}\n\n.ai-chat-widget-input .btn.btn-outline-secondary {\n color: var(--bs-secondary-color, #6c757d);\n}\n\n.ai-chat-widget-input button:disabled {\n opacity: 0.5;\n cursor: not-allowed;\n}\n\n.ai-chat-widget-status {\n padding: 0.25rem 0.75rem;\n font-size: 0.8rem;\n color: var(--bs-secondary-color, #6c757d);\n display: none;\n}\n\n.ai-chat-widget-status.visible {\n display: block;\n}\n\n.ai-chat-widget-welcome {\n text-align: center;\n color: var(--bs-secondary-color, #6c757d);\n padding: 2rem 1rem;\n font-size: 0.9rem;\n}\n\n@media (max-width: 576px) {\n .ai-chat-widget-container {\n width: calc(100vw - 1rem);\n height: calc(100vh - 6rem);\n right: 0.5rem;\n bottom: 4.5rem;\n }\n}\n"]}
1
+ {"version":3,"sources":["chat-widget.css"],"names":[],"mappings":"AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA","file":"chat-widget.css","sourcesContent":["/* CrestApps AI Chat Widget */\n:root {\n --ai-widget-primary: var(--bs-secondary, #6c757d);\n --ai-widget-primary-rgb: var(--bs-secondary-rgb, 108, 117, 125);\n --ai-widget-width: 380px;\n --ai-widget-height: 520px;\n}\n\n.ai-chat-widget-toggle {\n position: fixed;\n bottom: 1.25rem;\n right: 1.25rem;\n z-index: 10000;\n width: 52px;\n height: 52px;\n border-radius: 50%;\n background: var(--ai-widget-primary);\n color: var(--bs-white, #fff);\n border: none;\n cursor: pointer;\n display: flex;\n align-items: center;\n justify-content: center;\n font-size: 1.5rem;\n box-shadow: 0 4px 12px rgba(var(--bs-body-color-rgb, 33, 37, 41), 0.2);\n transition: transform 0.2s, box-shadow 0.2s;\n}\n\n.ai-chat-widget-toggle:hover {\n transform: scale(1.1);\n box-shadow: 0 6px 16px rgba(var(--bs-body-color-rgb, 33, 37, 41), 0.3);\n}\n\n.ai-chat-widget-container {\n position: fixed;\n bottom: 5rem;\n right: 1.25rem;\n z-index: 10001;\n width: var(--ai-widget-width);\n height: var(--ai-widget-height);\n background: var(--bs-body-bg, #fff);\n color: var(--bs-body-color, #212529);\n border: 1px solid var(--bs-border-color, #dee2e6);\n border-radius: 12px;\n box-shadow: 0 8px 32px rgba(var(--bs-body-color-rgb, 33, 37, 41), 0.18);\n display: none;\n flex-direction: column;\n overflow: hidden;\n}\n\n.ai-chat-widget-container.ai-chat-widget-resizable {\n resize: both;\n min-width: 320px;\n min-height: 420px;\n max-width: min(90vw, 960px);\n max-height: min(90vh, 960px);\n}\n\n.ai-chat-widget-container.open {\n display: flex;\n}\n\n.ai-chat-widget-header {\n display: flex;\n align-items: center;\n justify-content: space-between;\n padding: 0.75rem 1rem;\n background: var(--ai-widget-primary);\n color: var(--bs-white, #fff);\n}\n\n.ai-chat-widget-header.ai-chat-widget-drag-handle {\n cursor: move;\n user-select: none;\n touch-action: none;\n}\n\n.ai-chat-widget-header-actions {\n display: flex;\n align-items: center;\n gap: 0.15rem;\n}\n\n.ai-chat-widget-toggle.ai-chat-widget-draggable {\n touch-action: none;\n}\n\n.ai-chat-widget-container.ai-chat-widget-dragging,\n.ai-chat-widget-toggle.ai-chat-widget-dragging {\n transition: none;\n}\n\n.ai-chat-widget-toggle.ai-chat-widget-dragging:hover {\n transform: none;\n}\n\n.ai-chat-widget-header .title {\n font-weight: 600;\n font-size: 0.95rem;\n}\n\n.ai-chat-widget-header button {\n background: none;\n border: none;\n color: var(--bs-white, #fff);\n cursor: pointer;\n font-size: 1rem;\n padding: 0 0.25rem;\n opacity: 0.8;\n}\n\n.ai-chat-widget-header button:hover {\n opacity: 1;\n}\n\n.ai-chat-widget-profile-select {\n padding: 0.5rem 0.75rem;\n border-bottom: 1px solid var(--bs-border-color, #dee2e6);\n background: var(--bs-tertiary-bg, #f8f9fa);\n}\n\n.ai-chat-widget-profile-select select {\n width: 100%;\n padding: 0.35rem 0.5rem;\n color: var(--bs-body-color, #212529);\n background-color: var(--bs-body-bg, #fff);\n border: 1px solid var(--bs-border-color, #ced4da);\n border-radius: 6px;\n font-size: 0.85rem;\n}\n\n.ai-chat-widget-messages {\n flex: 1;\n overflow-y: auto;\n padding: 0.75rem;\n font-size: 0.9rem;\n}\n\n/* ── Widget message items (ai-chat.js Vue template) ── */\n.ai-chat-widget-messages .ai-chat-messages {\n padding: 0.5rem;\n}\n\n.ai-chat-widget-messages .ai-chat-message-item {\n position: relative;\n padding: 0.5rem 0;\n}\n\n.ai-chat-widget-messages .ai-chat-message-item + .ai-chat-message-item::before {\n content: '';\n display: block;\n width: 100%;\n margin-bottom: 0.5rem;\n border-top: 1px solid rgba(var(--bs-body-color-rgb, 33, 37, 41), 0.08);\n}\n\n.ai-chat-widget-messages .ai-chat-message-heading {\n margin-bottom: 0.35rem;\n}\n\n.ai-chat-widget-messages .ai-chat-message-heading .ai-chat-msg-role {\n margin-bottom: 0;\n letter-spacing: 0.03em;\n}\n\n.ai-chat-widget-messages .ai-chat-message-heading-assistant .ai-chat-msg-role {\n display: inline-flex;\n align-items: center;\n gap: 0.35rem;\n}\n\n.ai-chat-widget-messages .ai-chat-message-role-label {\n display: inline-block;\n text-transform: inherit;\n}\n\n.ai-chat-widget-messages .ai-chat-msg-role {\n font-weight: 600;\n margin-bottom: 2px;\n text-transform: uppercase;\n}\n\n.ai-chat-widget-messages .ai-chat-msg-role-user {\n color: var(--bs-primary, #0d6efd);\n}\n\n.ai-chat-widget-messages .ai-chat-msg-role-assistant {\n color: var(--ai-widget-primary, #6f42c1);\n}\n\n.ai-chat-widget-messages .ai-bot-icon {\n color: currentColor;\n display: inline-block;\n animation: none;\n}\n\n@keyframes ai-widget-streaming-effect {\n 0% { opacity: 1; transform: rotate(0deg); }\n 10% { opacity: 0.3; transform: rotate(0deg); }\n 20% { opacity: 1; transform: rotate(0deg); }\n 30% { opacity: 0.3; transform: rotate(0deg); }\n 40% { opacity: 1; transform: rotate(0deg); }\n 50% { opacity: 0.3; transform: rotate(0deg); }\n 60% { opacity: 1; transform: rotate(0deg); }\n 80% { opacity: 1; transform: rotate(360deg); }\n 100% { opacity: 1; transform: rotate(360deg); }\n}\n\n.ai-chat-widget-messages .ai-streaming-icon {\n color: currentColor;\n display: inline-block;\n animation: ai-widget-streaming-effect 4s ease-in-out infinite;\n}\n\n.ai-chat-widget-messages .ai-chat-message-body {\n position: relative;\n padding: 0;\n overflow-wrap: break-word;\n word-break: break-word;\n min-width: 0;\n}\n\n.ai-chat-widget-messages .ai-chat-message-body p:last-child {\n margin-bottom: 0;\n}\n\n/* ── Message action buttons (copy, thumbs up/down) ── */\n.ai-chat-widget-messages .message-buttons-container {\n position: absolute;\n right: 0.1rem;\n bottom: 0;\n display: inline-flex;\n align-items: center;\n gap: 0.35rem;\n width: auto;\n max-width: max-content;\n opacity: 0;\n pointer-events: none;\n transition: opacity 0.15s ease-in-out;\n background: rgba(var(--bs-body-bg-rgb, 255, 255, 255), 0.96);\n padding: 0.2rem 0.35rem;\n border-radius: 999px;\n box-shadow: 0 1px 3px rgba(var(--bs-body-color-rgb, 33, 37, 41), 0.08);\n margin-bottom: 1rem;\n transform: translateY(50%);\n z-index: 1;\n}\n\n.ai-chat-widget-messages .ai-chat-message-item:hover .message-buttons-container,\n.ai-chat-widget-messages .ai-chat-message-item:focus-within .message-buttons-container {\n opacity: 1;\n pointer-events: auto;\n}\n\n.ai-chat-widget-messages .message-buttons-container:has(.tts-playing) {\n opacity: 1;\n pointer-events: auto;\n}\n\n.ai-chat-widget-messages .message-buttons-container > .button-message-toolbox {\n display: inline-flex;\n align-items: center;\n justify-content: center;\n flex: 0 0 auto;\n width: 1.75rem;\n height: 1.75rem;\n margin: 0 !important;\n padding: 0 !important;\n border-radius: 50%;\n color: var(--bs-secondary-color, #6c757d) !important;\n text-decoration: none;\n}\n\n.ai-chat-widget-messages .message-buttons-container .ai-chat-message-assistant-feedback {\n display: inline-flex;\n align-items: center;\n gap: 0.35rem;\n}\n\n.ai-chat-widget-messages .message-buttons-container > .button-message-toolbox:hover,\n.ai-chat-widget-messages .message-buttons-container > .button-message-toolbox:focus {\n color: var(--bs-body-color, #212529) !important;\n text-decoration: none;\n}\n\n.ai-chat-widget-messages .message-buttons-container > .button-message-toolbox.text-success,\n.ai-chat-widget-messages .message-buttons-container > .button-message-toolbox.text-success:hover,\n.ai-chat-widget-messages .message-buttons-container > .button-message-toolbox.text-success:focus {\n color: var(--bs-success, #198754) !important;\n text-decoration: none;\n}\n\n.ai-chat-widget-messages .message-buttons-container > .button-message-toolbox.text-success svg {\n color: inherit !important;\n fill: currentColor;\n}\n\n.ai-chat-widget-messages .message-buttons-container > .button-message-toolbox.tts-playing {\n color: var(--ai-widget-primary, #6f42c1) !important;\n opacity: 1;\n pointer-events: auto;\n}\n\n.ai-chat-widget-history-panel {\n position: absolute;\n top: 3.5rem;\n left: 0;\n right: 0;\n bottom: 0;\n background: var(--bs-body-bg, #fff);\n padding: 1rem;\n z-index: 10;\n display: none;\n overflow-y: auto;\n flex-direction: column;\n}\n\n.ai-chat-widget-history-panel.show {\n display: flex;\n}\n\n.ai-chat-widget-history-panel .history-header {\n display: flex;\n justify-content: space-between;\n align-items: center;\n margin-bottom: 0.75rem;\n flex-shrink: 0;\n}\n\n.ai-chat-widget-history-panel .history-header h5 {\n margin: 0;\n font-size: 1rem;\n}\n\n.ai-chat-widget-history-list {\n list-style: none;\n padding: 0;\n margin: 0;\n overflow-y: auto;\n flex: 1 1 auto;\n}\n\n.ai-chat-widget-history-list li {\n margin-bottom: 0;\n}\n\n.ai-chat-widget-history-list .chat-session-history-item {\n display: block;\n padding: 0.5rem 0.75rem;\n text-decoration: none;\n color: var(--bs-body-color, #212529);\n font-size: 0.85rem;\n white-space: nowrap;\n overflow: hidden;\n text-overflow: ellipsis;\n transition: background 0.15s;\n cursor: pointer;\n border-bottom: 1px solid var(--bs-border-color, #dee2e6);\n}\n\n.ai-chat-widget-history-list li:last-child .chat-session-history-item {\n border-bottom: none;\n}\n\n.ai-chat-widget-history-list .chat-session-history-item:hover {\n background: var(--bs-tertiary-bg, #f8f9fa);\n}\n\n/* ── Notifications ── */\n.ai-chat-widget-messages .ai-chat-notification {\n margin: 0.75rem 0.5rem 0;\n padding: 0.625rem 0.75rem;\n border: 1px solid rgba(var(--bs-body-color-rgb, 33, 37, 41), 0.12);\n border-radius: 0.5rem;\n background: rgba(var(--bs-tertiary-bg-rgb, 248, 249, 250), 0.98);\n}\n\n.ai-chat-widget-messages .ai-chat-notification-content {\n display: flex;\n align-items: flex-start;\n gap: 0.5rem;\n}\n\n.ai-chat-widget-messages .ai-chat-notification-icon {\n margin-top: 0.1rem;\n color: var(--bs-secondary-color, #6c757d);\n}\n\n.ai-chat-widget-messages .ai-chat-notification-text {\n flex: 1 1 auto;\n min-width: 0;\n}\n\n.ai-chat-widget-messages .ai-chat-notification-dismiss {\n margin-left: auto !important;\n color: var(--bs-secondary-color, #6c757d) !important;\n}\n\n.ai-chat-widget-messages .ai-chat-notification-actions {\n display: flex;\n gap: 0.5rem;\n margin-top: 0.5rem;\n}\n\n/* ── Chart containers inside widget ── */\n.ai-chat-widget-messages .chart-container {\n width: 100%;\n max-width: 100% !important;\n min-height: 280px;\n}\n\n/* ── Embedded images inside widget ── */\n/* The panel is narrower than the renderer's inline pixel cap, so an image overflows it horizontally\n without this. The chart container above is bounded the same way and for the same reason. */\n.ai-chat-widget-messages .generated-image-container {\n /* The wrapper is a span so a figure marker written mid-sentence does not close the paragraph it\n sits in. Inline-block restores the box behaviour the layout below assumes, and a top margin\n keeps a figure off the line of text it follows. */\n display: inline-block;\n vertical-align: top;\n margin-top: 0.5rem;\n /* The positioning context for the download that sits inside it. */\n position: relative;\n}\n\n.ai-chat-widget-messages .generated-image-container img {\n max-width: 100% !important;\n max-height: 50vh;\n object-fit: contain;\n /* The panel is 380px wide, so a figure in it is a thumbnail whatever it was drawn at. Pointing at it\n says it can be opened to the size of the window. */\n cursor: zoom-in;\n}\n\n/* Deliberately not scoped to the panel. The opened picture is a copy the library appends to the document\n body, which is outside the widget entirely, so a rule under .ai-chat-widget-messages never reaches it.\n It is repeated from ai-chat.css because a page that mounts only the widget loads only this file. The\n copy carries the thumbnail's classes, so the frame and the cap are taken off here -- both are scaled up\n with the drawing and would otherwise ring the picture with a broad white border. */\nimg.medium-zoom-image--opened {\n max-width: none !important;\n max-height: none !important;\n padding: 0;\n border: 0;\n background: none;\n}\n\n/* Over the picture's lower corner rather than below it. In a panel this narrow a row of its own cost a\n band of vertical space per figure, which a conversation full of them pays for once each. */\n.ai-chat-widget-messages .generated-image-container .ai-download-image {\n position: absolute;\n right: 0.5rem;\n bottom: 0.5rem;\n z-index: 2;\n opacity: 0;\n color: #1f2933;\n background-color: rgba(255, 255, 255, 0.92);\n border-color: rgba(0, 0, 0, 0.15);\n transition: opacity 0.15s ease-in-out;\n}\n\n/* The outline button this is built from inverts on hover: it fills itself with its own colour and turns\n its label white. Against the pale ground kept here that leaves a white icon on white -- the button is\n still there and still works, but there is nothing to see. Both colours are therefore stated for every\n state the button can be in rather than only the resting one. */\n.ai-chat-widget-messages .generated-image-container .ai-download-image:hover,\n.ai-chat-widget-messages .generated-image-container .ai-download-image:focus,\n.ai-chat-widget-messages .generated-image-container .ai-download-image:focus-visible,\n.ai-chat-widget-messages .generated-image-container .ai-download-image:active {\n color: #1f2933;\n background-color: #ffffff;\n border-color: rgba(0, 0, 0, 0.35);\n}\n\n/* Revealed by pointing at the figure. focus-within is what keeps it reachable by keyboard: the button is\n invisible until it takes focus, and an invisible control that cannot be brought into view cannot be\n used at all. */\n.ai-chat-widget-messages .generated-image-container:hover .ai-download-image,\n.ai-chat-widget-messages .generated-image-container:focus-within .ai-download-image {\n opacity: 1;\n}\n\n/* A touch screen has no hover to reveal it with, so there it is simply always shown. The widget is the\n surface most likely to be read on one. */\n@media (hover: none) {\n .ai-chat-widget-messages .generated-image-container .ai-download-image {\n opacity: 1;\n }\n}\n\n/* ── Tables inside widget ── */\n/* The panel is 380px wide, so an ingested table almost always exceeds it. Scrolling the table itself keeps\n the conversation column intact; the alternative is a sideways-scrolling chat. */\n.ai-chat-widget-messages table {\n display: block;\n width: max-content;\n min-width: 100%;\n max-width: 100%;\n overflow-x: auto;\n margin-bottom: 0.75rem;\n border-collapse: collapse;\n font-size: 0.8125rem;\n}\n\n.ai-chat-widget-messages table th,\n.ai-chat-widget-messages table td {\n padding: 0.3rem 0.5rem;\n border: 1px solid var(--bs-border-color, #dee2e6);\n text-align: left;\n vertical-align: top;\n}\n\n.ai-chat-widget-messages table thead th {\n background-color: var(--bs-secondary-bg, #e9ecef);\n font-weight: 600;\n white-space: nowrap;\n}\n\n.ai-chat-widget-messages table tbody tr:nth-child(even) {\n background-color: var(--bs-tertiary-bg, #f8f9fa);\n}\n\n/* ── Code blocks & inline code ── */\n.ai-chat-widget-messages .ai-chat-message-body pre {\n background: var(--bs-dark-bg-subtle, #2d2d2d);\n color: var(--bs-emphasis-color, #f8f8f2);\n padding: 0.5rem;\n border-radius: 4px;\n overflow-x: auto;\n font-size: 0.8rem;\n}\n\n.ai-chat-widget-messages .ai-chat-message-body code {\n font-size: 0.85em;\n}\n\n.ai-chat-widget-input {\n border-top: 1px solid var(--bs-border-color, #dee2e6);\n padding: 0.5rem 0.75rem;\n display: flex;\n flex-wrap: wrap;\n gap: 0.5rem;\n align-items: flex-end;\n}\n\n.ai-chat-widget-input .ai-chat-doc-bar {\n flex: 0 0 100%;\n margin: -0.5rem -0.75rem 0.5rem;\n padding: 0.5rem 0.75rem;\n max-height: 4.5rem;\n overflow-y: auto;\n}\n\n.ai-chat-widget-input textarea {\n flex: 1;\n min-width: 0;\n color: var(--bs-body-color, #212529);\n background-color: var(--bs-body-bg, #fff);\n border: 1px solid var(--bs-border-color, #ced4da);\n border-radius: 8px;\n padding: 0.4rem 0.6rem;\n font-size: 0.9rem;\n resize: none;\n max-height: 80px;\n min-height: 36px;\n line-height: 1.4;\n outline: none;\n}\n\n.ai-chat-widget-input textarea:focus {\n border-color: var(--ai-widget-primary);\n box-shadow: 0 0 0 2px rgba(var(--ai-widget-primary-rgb, 108, 117, 125), 0.15);\n}\n\n.ai-chat-widget-input button {\n background: var(--ai-widget-primary);\n color: var(--bs-white, #fff);\n border: none;\n border-radius: 8px;\n padding: 0.4rem 0.75rem;\n cursor: pointer;\n font-size: 0.9rem;\n white-space: nowrap;\n}\n\n.ai-chat-widget-input .btn {\n background: var(--bs-body-bg, #fff);\n color: var(--bs-body-color, #212529);\n border: 1px solid var(--bs-border-color, #ced4da);\n}\n\n.ai-chat-widget-input .btn.btn-link {\n background: transparent;\n color: var(--bs-secondary-color, #6c757d);\n border: none;\n padding: 0.4rem;\n}\n\n.ai-chat-widget-input .btn.btn-outline-dark {\n background: var(--bs-dark, #212529);\n color: var(--bs-white, #fff);\n border-color: var(--bs-dark, #212529);\n}\n\n.ai-chat-widget-input .btn.btn-outline-secondary {\n color: var(--bs-secondary-color, #6c757d);\n}\n\n.ai-chat-widget-input button:disabled {\n opacity: 0.5;\n cursor: not-allowed;\n}\n\n.ai-chat-widget-status {\n padding: 0.25rem 0.75rem;\n font-size: 0.8rem;\n color: var(--bs-secondary-color, #6c757d);\n display: none;\n}\n\n.ai-chat-widget-status.visible {\n display: block;\n}\n\n.ai-chat-widget-welcome {\n text-align: center;\n color: var(--bs-secondary-color, #6c757d);\n padding: 2rem 1rem;\n font-size: 0.9rem;\n}\n\n@media (max-width: 576px) {\n .ai-chat-widget-container {\n width: calc(100vw - 1rem);\n height: calc(100vh - 6rem);\n right: 0.5rem;\n bottom: 4.5rem;\n }\n}\n"]}
@@ -1 +1 @@
1
- :root{--ai-widget-primary:var(--bs-secondary, #6c757d);--ai-widget-primary-rgb:var(--bs-secondary-rgb, 108, 117, 125);--ai-widget-width:380px;--ai-widget-height:520px}.ai-chat-widget-toggle{position:fixed;bottom:1.25rem;right:1.25rem;z-index:10000;width:52px;height:52px;border-radius:50%;background:var(--ai-widget-primary);color:var(--bs-white,#fff);border:none;cursor:pointer;display:flex;align-items:center;justify-content:center;font-size:1.5rem;box-shadow:0 4px 12px rgba(var(--bs-body-color-rgb,33,37,41),.2);transition:transform .2s,box-shadow .2s}.ai-chat-widget-toggle:hover{transform:scale(1.1);box-shadow:0 6px 16px rgba(var(--bs-body-color-rgb,33,37,41),.3)}.ai-chat-widget-container{position:fixed;bottom:5rem;right:1.25rem;z-index:10001;width:var(--ai-widget-width);height:var(--ai-widget-height);background:var(--bs-body-bg,#fff);color:var(--bs-body-color,#212529);border:1px solid var(--bs-border-color,#dee2e6);border-radius:12px;box-shadow:0 8px 32px rgba(var(--bs-body-color-rgb,33,37,41),.18);display:none;flex-direction:column;overflow:hidden}.ai-chat-widget-container.ai-chat-widget-resizable{resize:both;min-width:320px;min-height:420px;max-width:min(90vw,960px);max-height:min(90vh,960px)}.ai-chat-widget-container.open{display:flex}.ai-chat-widget-header{display:flex;align-items:center;justify-content:space-between;padding:.75rem 1rem;background:var(--ai-widget-primary);color:var(--bs-white,#fff)}.ai-chat-widget-header.ai-chat-widget-drag-handle{cursor:move;user-select:none;touch-action:none}.ai-chat-widget-header-actions{display:flex;align-items:center;gap:.15rem}.ai-chat-widget-toggle.ai-chat-widget-draggable{touch-action:none}.ai-chat-widget-container.ai-chat-widget-dragging,.ai-chat-widget-toggle.ai-chat-widget-dragging{transition:none}.ai-chat-widget-toggle.ai-chat-widget-dragging:hover{transform:none}.ai-chat-widget-header .title{font-weight:600;font-size:.95rem}.ai-chat-widget-header button{background:0 0;border:none;color:var(--bs-white,#fff);cursor:pointer;font-size:1rem;padding:0 .25rem;opacity:.8}.ai-chat-widget-header button:hover{opacity:1}.ai-chat-widget-profile-select{padding:.5rem .75rem;border-bottom:1px solid var(--bs-border-color,#dee2e6);background:var(--bs-tertiary-bg,#f8f9fa)}.ai-chat-widget-profile-select select{width:100%;padding:.35rem .5rem;color:var(--bs-body-color,#212529);background-color:var(--bs-body-bg,#fff);border:1px solid var(--bs-border-color,#ced4da);border-radius:6px;font-size:.85rem}.ai-chat-widget-messages{flex:1;overflow-y:auto;padding:.75rem;font-size:.9rem}.ai-chat-widget-messages .ai-chat-messages{padding:.5rem}.ai-chat-widget-messages .ai-chat-message-item{position:relative;padding:.5rem 0}.ai-chat-widget-messages .ai-chat-message-item+.ai-chat-message-item::before{content:'';display:block;width:100%;margin-bottom:.5rem;border-top:1px solid rgba(var(--bs-body-color-rgb,33,37,41),.08)}.ai-chat-widget-messages .ai-chat-message-heading{margin-bottom:.35rem}.ai-chat-widget-messages .ai-chat-message-heading .ai-chat-msg-role{margin-bottom:0;letter-spacing:.03em}.ai-chat-widget-messages .ai-chat-message-heading-assistant .ai-chat-msg-role{display:inline-flex;align-items:center;gap:.35rem}.ai-chat-widget-messages .ai-chat-message-role-label{display:inline-block;text-transform:inherit}.ai-chat-widget-messages .ai-chat-msg-role{font-weight:600;margin-bottom:2px;text-transform:uppercase}.ai-chat-widget-messages .ai-chat-msg-role-user{color:var(--bs-primary,#0d6efd)}.ai-chat-widget-messages .ai-chat-msg-role-assistant{color:var(--ai-widget-primary,#6f42c1)}.ai-chat-widget-messages .ai-bot-icon{color:currentColor;display:inline-block;animation:none}@keyframes ai-widget-streaming-effect{0%{opacity:1;transform:rotate(0)}10%{opacity:.3;transform:rotate(0)}20%{opacity:1;transform:rotate(0)}30%{opacity:.3;transform:rotate(0)}40%{opacity:1;transform:rotate(0)}50%{opacity:.3;transform:rotate(0)}60%{opacity:1;transform:rotate(0)}80%{opacity:1;transform:rotate(360deg)}100%{opacity:1;transform:rotate(360deg)}}.ai-chat-widget-messages .ai-streaming-icon{color:currentColor;display:inline-block;animation:ai-widget-streaming-effect 4s ease-in-out infinite}.ai-chat-widget-messages .ai-chat-message-body{position:relative;padding:0;overflow-wrap:break-word;word-break:break-word;min-width:0}.ai-chat-widget-messages .ai-chat-message-body p:last-child{margin-bottom:0}.ai-chat-widget-messages .message-buttons-container{position:absolute;right:.1rem;bottom:0;display:inline-flex;align-items:center;gap:.35rem;width:auto;max-width:max-content;opacity:0;pointer-events:none;transition:opacity .15s ease-in-out;background:rgba(var(--bs-body-bg-rgb,255,255,255),.96);padding:.2rem .35rem;border-radius:999px;box-shadow:0 1px 3px rgba(var(--bs-body-color-rgb,33,37,41),.08);margin-bottom:1rem;transform:translateY(50%);z-index:1}.ai-chat-widget-messages .ai-chat-message-item:focus-within .message-buttons-container,.ai-chat-widget-messages .ai-chat-message-item:hover .message-buttons-container{opacity:1;pointer-events:auto}.ai-chat-widget-messages .message-buttons-container:has(.tts-playing){opacity:1;pointer-events:auto}.ai-chat-widget-messages .message-buttons-container>.button-message-toolbox{display:inline-flex;align-items:center;justify-content:center;flex:0 0 auto;width:1.75rem;height:1.75rem;margin:0!important;padding:0!important;border-radius:50%;color:var(--bs-secondary-color,#6c757d)!important;text-decoration:none}.ai-chat-widget-messages .message-buttons-container .ai-chat-message-assistant-feedback{display:inline-flex;align-items:center;gap:.35rem}.ai-chat-widget-messages .message-buttons-container>.button-message-toolbox:focus,.ai-chat-widget-messages .message-buttons-container>.button-message-toolbox:hover{color:var(--bs-body-color,#212529)!important;text-decoration:none}.ai-chat-widget-messages .message-buttons-container>.button-message-toolbox.text-success,.ai-chat-widget-messages .message-buttons-container>.button-message-toolbox.text-success:focus,.ai-chat-widget-messages .message-buttons-container>.button-message-toolbox.text-success:hover{color:var(--bs-success,#198754)!important;text-decoration:none}.ai-chat-widget-messages .message-buttons-container>.button-message-toolbox.text-success svg{color:inherit!important;fill:currentColor}.ai-chat-widget-messages .message-buttons-container>.button-message-toolbox.tts-playing{color:var(--ai-widget-primary,#6f42c1)!important;opacity:1;pointer-events:auto}.ai-chat-widget-history-panel{position:absolute;top:3.5rem;left:0;right:0;bottom:0;background:var(--bs-body-bg,#fff);padding:1rem;z-index:10;display:none;overflow-y:auto;flex-direction:column}.ai-chat-widget-history-panel.show{display:flex}.ai-chat-widget-history-panel .history-header{display:flex;justify-content:space-between;align-items:center;margin-bottom:.75rem;flex-shrink:0}.ai-chat-widget-history-panel .history-header h5{margin:0;font-size:1rem}.ai-chat-widget-history-list{list-style:none;padding:0;margin:0;overflow-y:auto;flex:1 1 auto}.ai-chat-widget-history-list li{margin-bottom:0}.ai-chat-widget-history-list .chat-session-history-item{display:block;padding:.5rem .75rem;text-decoration:none;color:var(--bs-body-color,#212529);font-size:.85rem;white-space:nowrap;overflow:hidden;text-overflow:ellipsis;transition:background .15s;cursor:pointer;border-bottom:1px solid var(--bs-border-color,#dee2e6)}.ai-chat-widget-history-list li:last-child .chat-session-history-item{border-bottom:none}.ai-chat-widget-history-list .chat-session-history-item:hover{background:var(--bs-tertiary-bg,#f8f9fa)}.ai-chat-widget-messages .ai-chat-notification{margin:.75rem .5rem 0;padding:.625rem .75rem;border:1px solid rgba(var(--bs-body-color-rgb,33,37,41),.12);border-radius:.5rem;background:rgba(var(--bs-tertiary-bg-rgb,248,249,250),.98)}.ai-chat-widget-messages .ai-chat-notification-content{display:flex;align-items:flex-start;gap:.5rem}.ai-chat-widget-messages .ai-chat-notification-icon{margin-top:.1rem;color:var(--bs-secondary-color,#6c757d)}.ai-chat-widget-messages .ai-chat-notification-text{flex:1 1 auto;min-width:0}.ai-chat-widget-messages .ai-chat-notification-dismiss{margin-left:auto!important;color:var(--bs-secondary-color,#6c757d)!important}.ai-chat-widget-messages .ai-chat-notification-actions{display:flex;gap:.5rem;margin-top:.5rem}.ai-chat-widget-messages .chart-container{width:100%;max-width:100%!important;min-height:280px}.ai-chat-widget-messages .generated-image-container{display:inline-block;vertical-align:top;margin-top:.5rem}.ai-chat-widget-messages .generated-image-container img{max-width:100%!important;max-height:50vh;object-fit:contain}.ai-chat-widget-messages table{display:block;width:max-content;min-width:100%;max-width:100%;overflow-x:auto;margin-bottom:.75rem;border-collapse:collapse;font-size:.8125rem}.ai-chat-widget-messages table td,.ai-chat-widget-messages table th{padding:.3rem .5rem;border:1px solid var(--bs-border-color,#dee2e6);text-align:left;vertical-align:top}.ai-chat-widget-messages table thead th{background-color:var(--bs-secondary-bg,#e9ecef);font-weight:600;white-space:nowrap}.ai-chat-widget-messages table tbody tr:nth-child(even){background-color:var(--bs-tertiary-bg,#f8f9fa)}.ai-chat-widget-messages .ai-chat-message-body pre{background:var(--bs-dark-bg-subtle,#2d2d2d);color:var(--bs-emphasis-color,#f8f8f2);padding:.5rem;border-radius:4px;overflow-x:auto;font-size:.8rem}.ai-chat-widget-messages .ai-chat-message-body code{font-size:.85em}.ai-chat-widget-input{border-top:1px solid var(--bs-border-color,#dee2e6);padding:.5rem .75rem;display:flex;flex-wrap:wrap;gap:.5rem;align-items:flex-end}.ai-chat-widget-input .ai-chat-doc-bar{flex:0 0 100%;margin:-.5rem -.75rem .5rem;padding:.5rem .75rem;max-height:4.5rem;overflow-y:auto}.ai-chat-widget-input textarea{flex:1;min-width:0;color:var(--bs-body-color,#212529);background-color:var(--bs-body-bg,#fff);border:1px solid var(--bs-border-color,#ced4da);border-radius:8px;padding:.4rem .6rem;font-size:.9rem;resize:none;max-height:80px;min-height:36px;line-height:1.4;outline:0}.ai-chat-widget-input textarea:focus{border-color:var(--ai-widget-primary);box-shadow:0 0 0 2px rgba(var(--ai-widget-primary-rgb,108,117,125),.15)}.ai-chat-widget-input button{background:var(--ai-widget-primary);color:var(--bs-white,#fff);border:none;border-radius:8px;padding:.4rem .75rem;cursor:pointer;font-size:.9rem;white-space:nowrap}.ai-chat-widget-input .btn{background:var(--bs-body-bg,#fff);color:var(--bs-body-color,#212529);border:1px solid var(--bs-border-color,#ced4da)}.ai-chat-widget-input .btn.btn-link{background:0 0;color:var(--bs-secondary-color,#6c757d);border:none;padding:.4rem}.ai-chat-widget-input .btn.btn-outline-dark{background:var(--bs-dark,#212529);color:var(--bs-white,#fff);border-color:var(--bs-dark,#212529)}.ai-chat-widget-input .btn.btn-outline-secondary{color:var(--bs-secondary-color,#6c757d)}.ai-chat-widget-input button:disabled{opacity:.5;cursor:not-allowed}.ai-chat-widget-status{padding:.25rem .75rem;font-size:.8rem;color:var(--bs-secondary-color,#6c757d);display:none}.ai-chat-widget-status.visible{display:block}.ai-chat-widget-welcome{text-align:center;color:var(--bs-secondary-color,#6c757d);padding:2rem 1rem;font-size:.9rem}@media (max-width:576px){.ai-chat-widget-container{width:calc(100vw - 1rem);height:calc(100vh - 6rem);right:.5rem;bottom:4.5rem}}
1
+ :root{--ai-widget-primary:var(--bs-secondary, #6c757d);--ai-widget-primary-rgb:var(--bs-secondary-rgb, 108, 117, 125);--ai-widget-width:380px;--ai-widget-height:520px}.ai-chat-widget-toggle{position:fixed;bottom:1.25rem;right:1.25rem;z-index:10000;width:52px;height:52px;border-radius:50%;background:var(--ai-widget-primary);color:var(--bs-white,#fff);border:none;cursor:pointer;display:flex;align-items:center;justify-content:center;font-size:1.5rem;box-shadow:0 4px 12px rgba(var(--bs-body-color-rgb,33,37,41),.2);transition:transform .2s,box-shadow .2s}.ai-chat-widget-toggle:hover{transform:scale(1.1);box-shadow:0 6px 16px rgba(var(--bs-body-color-rgb,33,37,41),.3)}.ai-chat-widget-container{position:fixed;bottom:5rem;right:1.25rem;z-index:10001;width:var(--ai-widget-width);height:var(--ai-widget-height);background:var(--bs-body-bg,#fff);color:var(--bs-body-color,#212529);border:1px solid var(--bs-border-color,#dee2e6);border-radius:12px;box-shadow:0 8px 32px rgba(var(--bs-body-color-rgb,33,37,41),.18);display:none;flex-direction:column;overflow:hidden}.ai-chat-widget-container.ai-chat-widget-resizable{resize:both;min-width:320px;min-height:420px;max-width:min(90vw,960px);max-height:min(90vh,960px)}.ai-chat-widget-container.open{display:flex}.ai-chat-widget-header{display:flex;align-items:center;justify-content:space-between;padding:.75rem 1rem;background:var(--ai-widget-primary);color:var(--bs-white,#fff)}.ai-chat-widget-header.ai-chat-widget-drag-handle{cursor:move;user-select:none;touch-action:none}.ai-chat-widget-header-actions{display:flex;align-items:center;gap:.15rem}.ai-chat-widget-toggle.ai-chat-widget-draggable{touch-action:none}.ai-chat-widget-container.ai-chat-widget-dragging,.ai-chat-widget-toggle.ai-chat-widget-dragging{transition:none}.ai-chat-widget-toggle.ai-chat-widget-dragging:hover{transform:none}.ai-chat-widget-header .title{font-weight:600;font-size:.95rem}.ai-chat-widget-header button{background:0 0;border:none;color:var(--bs-white,#fff);cursor:pointer;font-size:1rem;padding:0 .25rem;opacity:.8}.ai-chat-widget-header button:hover{opacity:1}.ai-chat-widget-profile-select{padding:.5rem .75rem;border-bottom:1px solid var(--bs-border-color,#dee2e6);background:var(--bs-tertiary-bg,#f8f9fa)}.ai-chat-widget-profile-select select{width:100%;padding:.35rem .5rem;color:var(--bs-body-color,#212529);background-color:var(--bs-body-bg,#fff);border:1px solid var(--bs-border-color,#ced4da);border-radius:6px;font-size:.85rem}.ai-chat-widget-messages{flex:1;overflow-y:auto;padding:.75rem;font-size:.9rem}.ai-chat-widget-messages .ai-chat-messages{padding:.5rem}.ai-chat-widget-messages .ai-chat-message-item{position:relative;padding:.5rem 0}.ai-chat-widget-messages .ai-chat-message-item+.ai-chat-message-item::before{content:'';display:block;width:100%;margin-bottom:.5rem;border-top:1px solid rgba(var(--bs-body-color-rgb,33,37,41),.08)}.ai-chat-widget-messages .ai-chat-message-heading{margin-bottom:.35rem}.ai-chat-widget-messages .ai-chat-message-heading .ai-chat-msg-role{margin-bottom:0;letter-spacing:.03em}.ai-chat-widget-messages .ai-chat-message-heading-assistant .ai-chat-msg-role{display:inline-flex;align-items:center;gap:.35rem}.ai-chat-widget-messages .ai-chat-message-role-label{display:inline-block;text-transform:inherit}.ai-chat-widget-messages .ai-chat-msg-role{font-weight:600;margin-bottom:2px;text-transform:uppercase}.ai-chat-widget-messages .ai-chat-msg-role-user{color:var(--bs-primary,#0d6efd)}.ai-chat-widget-messages .ai-chat-msg-role-assistant{color:var(--ai-widget-primary,#6f42c1)}.ai-chat-widget-messages .ai-bot-icon{color:currentColor;display:inline-block;animation:none}@keyframes ai-widget-streaming-effect{0%{opacity:1;transform:rotate(0)}10%{opacity:.3;transform:rotate(0)}20%{opacity:1;transform:rotate(0)}30%{opacity:.3;transform:rotate(0)}40%{opacity:1;transform:rotate(0)}50%{opacity:.3;transform:rotate(0)}60%{opacity:1;transform:rotate(0)}80%{opacity:1;transform:rotate(360deg)}100%{opacity:1;transform:rotate(360deg)}}.ai-chat-widget-messages .ai-streaming-icon{color:currentColor;display:inline-block;animation:ai-widget-streaming-effect 4s ease-in-out infinite}.ai-chat-widget-messages .ai-chat-message-body{position:relative;padding:0;overflow-wrap:break-word;word-break:break-word;min-width:0}.ai-chat-widget-messages .ai-chat-message-body p:last-child{margin-bottom:0}.ai-chat-widget-messages .message-buttons-container{position:absolute;right:.1rem;bottom:0;display:inline-flex;align-items:center;gap:.35rem;width:auto;max-width:max-content;opacity:0;pointer-events:none;transition:opacity .15s ease-in-out;background:rgba(var(--bs-body-bg-rgb,255,255,255),.96);padding:.2rem .35rem;border-radius:999px;box-shadow:0 1px 3px rgba(var(--bs-body-color-rgb,33,37,41),.08);margin-bottom:1rem;transform:translateY(50%);z-index:1}.ai-chat-widget-messages .ai-chat-message-item:focus-within .message-buttons-container,.ai-chat-widget-messages .ai-chat-message-item:hover .message-buttons-container{opacity:1;pointer-events:auto}.ai-chat-widget-messages .message-buttons-container:has(.tts-playing){opacity:1;pointer-events:auto}.ai-chat-widget-messages .message-buttons-container>.button-message-toolbox{display:inline-flex;align-items:center;justify-content:center;flex:0 0 auto;width:1.75rem;height:1.75rem;margin:0!important;padding:0!important;border-radius:50%;color:var(--bs-secondary-color,#6c757d)!important;text-decoration:none}.ai-chat-widget-messages .message-buttons-container .ai-chat-message-assistant-feedback{display:inline-flex;align-items:center;gap:.35rem}.ai-chat-widget-messages .message-buttons-container>.button-message-toolbox:focus,.ai-chat-widget-messages .message-buttons-container>.button-message-toolbox:hover{color:var(--bs-body-color,#212529)!important;text-decoration:none}.ai-chat-widget-messages .message-buttons-container>.button-message-toolbox.text-success,.ai-chat-widget-messages .message-buttons-container>.button-message-toolbox.text-success:focus,.ai-chat-widget-messages .message-buttons-container>.button-message-toolbox.text-success:hover{color:var(--bs-success,#198754)!important;text-decoration:none}.ai-chat-widget-messages .message-buttons-container>.button-message-toolbox.text-success svg{color:inherit!important;fill:currentColor}.ai-chat-widget-messages .message-buttons-container>.button-message-toolbox.tts-playing{color:var(--ai-widget-primary,#6f42c1)!important;opacity:1;pointer-events:auto}.ai-chat-widget-history-panel{position:absolute;top:3.5rem;left:0;right:0;bottom:0;background:var(--bs-body-bg,#fff);padding:1rem;z-index:10;display:none;overflow-y:auto;flex-direction:column}.ai-chat-widget-history-panel.show{display:flex}.ai-chat-widget-history-panel .history-header{display:flex;justify-content:space-between;align-items:center;margin-bottom:.75rem;flex-shrink:0}.ai-chat-widget-history-panel .history-header h5{margin:0;font-size:1rem}.ai-chat-widget-history-list{list-style:none;padding:0;margin:0;overflow-y:auto;flex:1 1 auto}.ai-chat-widget-history-list li{margin-bottom:0}.ai-chat-widget-history-list .chat-session-history-item{display:block;padding:.5rem .75rem;text-decoration:none;color:var(--bs-body-color,#212529);font-size:.85rem;white-space:nowrap;overflow:hidden;text-overflow:ellipsis;transition:background .15s;cursor:pointer;border-bottom:1px solid var(--bs-border-color,#dee2e6)}.ai-chat-widget-history-list li:last-child .chat-session-history-item{border-bottom:none}.ai-chat-widget-history-list .chat-session-history-item:hover{background:var(--bs-tertiary-bg,#f8f9fa)}.ai-chat-widget-messages .ai-chat-notification{margin:.75rem .5rem 0;padding:.625rem .75rem;border:1px solid rgba(var(--bs-body-color-rgb,33,37,41),.12);border-radius:.5rem;background:rgba(var(--bs-tertiary-bg-rgb,248,249,250),.98)}.ai-chat-widget-messages .ai-chat-notification-content{display:flex;align-items:flex-start;gap:.5rem}.ai-chat-widget-messages .ai-chat-notification-icon{margin-top:.1rem;color:var(--bs-secondary-color,#6c757d)}.ai-chat-widget-messages .ai-chat-notification-text{flex:1 1 auto;min-width:0}.ai-chat-widget-messages .ai-chat-notification-dismiss{margin-left:auto!important;color:var(--bs-secondary-color,#6c757d)!important}.ai-chat-widget-messages .ai-chat-notification-actions{display:flex;gap:.5rem;margin-top:.5rem}.ai-chat-widget-messages .chart-container{width:100%;max-width:100%!important;min-height:280px}.ai-chat-widget-messages .generated-image-container{display:inline-block;vertical-align:top;margin-top:.5rem;position:relative}.ai-chat-widget-messages .generated-image-container img{max-width:100%!important;max-height:50vh;object-fit:contain;cursor:zoom-in}img.medium-zoom-image--opened{max-width:none!important;max-height:none!important;padding:0;border:0;background:0 0}.ai-chat-widget-messages .generated-image-container .ai-download-image{position:absolute;right:.5rem;bottom:.5rem;z-index:2;opacity:0;color:#1f2933;background-color:rgba(255,255,255,.92);border-color:rgba(0,0,0,.15);transition:opacity .15s ease-in-out}.ai-chat-widget-messages .generated-image-container .ai-download-image:active,.ai-chat-widget-messages .generated-image-container .ai-download-image:focus,.ai-chat-widget-messages .generated-image-container .ai-download-image:focus-visible,.ai-chat-widget-messages .generated-image-container .ai-download-image:hover{color:#1f2933;background-color:#fff;border-color:rgba(0,0,0,.35)}.ai-chat-widget-messages .generated-image-container:focus-within .ai-download-image,.ai-chat-widget-messages .generated-image-container:hover .ai-download-image{opacity:1}@media (hover:none){.ai-chat-widget-messages .generated-image-container .ai-download-image{opacity:1}}.ai-chat-widget-messages table{display:block;width:max-content;min-width:100%;max-width:100%;overflow-x:auto;margin-bottom:.75rem;border-collapse:collapse;font-size:.8125rem}.ai-chat-widget-messages table td,.ai-chat-widget-messages table th{padding:.3rem .5rem;border:1px solid var(--bs-border-color,#dee2e6);text-align:left;vertical-align:top}.ai-chat-widget-messages table thead th{background-color:var(--bs-secondary-bg,#e9ecef);font-weight:600;white-space:nowrap}.ai-chat-widget-messages table tbody tr:nth-child(even){background-color:var(--bs-tertiary-bg,#f8f9fa)}.ai-chat-widget-messages .ai-chat-message-body pre{background:var(--bs-dark-bg-subtle,#2d2d2d);color:var(--bs-emphasis-color,#f8f8f2);padding:.5rem;border-radius:4px;overflow-x:auto;font-size:.8rem}.ai-chat-widget-messages .ai-chat-message-body code{font-size:.85em}.ai-chat-widget-input{border-top:1px solid var(--bs-border-color,#dee2e6);padding:.5rem .75rem;display:flex;flex-wrap:wrap;gap:.5rem;align-items:flex-end}.ai-chat-widget-input .ai-chat-doc-bar{flex:0 0 100%;margin:-.5rem -.75rem .5rem;padding:.5rem .75rem;max-height:4.5rem;overflow-y:auto}.ai-chat-widget-input textarea{flex:1;min-width:0;color:var(--bs-body-color,#212529);background-color:var(--bs-body-bg,#fff);border:1px solid var(--bs-border-color,#ced4da);border-radius:8px;padding:.4rem .6rem;font-size:.9rem;resize:none;max-height:80px;min-height:36px;line-height:1.4;outline:0}.ai-chat-widget-input textarea:focus{border-color:var(--ai-widget-primary);box-shadow:0 0 0 2px rgba(var(--ai-widget-primary-rgb,108,117,125),.15)}.ai-chat-widget-input button{background:var(--ai-widget-primary);color:var(--bs-white,#fff);border:none;border-radius:8px;padding:.4rem .75rem;cursor:pointer;font-size:.9rem;white-space:nowrap}.ai-chat-widget-input .btn{background:var(--bs-body-bg,#fff);color:var(--bs-body-color,#212529);border:1px solid var(--bs-border-color,#ced4da)}.ai-chat-widget-input .btn.btn-link{background:0 0;color:var(--bs-secondary-color,#6c757d);border:none;padding:.4rem}.ai-chat-widget-input .btn.btn-outline-dark{background:var(--bs-dark,#212529);color:var(--bs-white,#fff);border-color:var(--bs-dark,#212529)}.ai-chat-widget-input .btn.btn-outline-secondary{color:var(--bs-secondary-color,#6c757d)}.ai-chat-widget-input button:disabled{opacity:.5;cursor:not-allowed}.ai-chat-widget-status{padding:.25rem .75rem;font-size:.8rem;color:var(--bs-secondary-color,#6c757d);display:none}.ai-chat-widget-status.visible{display:block}.ai-chat-widget-welcome{text-align:center;color:var(--bs-secondary-color,#6c757d);padding:2rem 1rem;font-size:.9rem}@media (max-width:576px){.ai-chat-widget-container{width:calc(100vw - 1rem);height:calc(100vh - 6rem);right:.5rem;bottom:4.5rem}}
@@ -798,6 +798,10 @@
798
798
  realtimeMicDeviceId = '',
799
799
  realtimeOutputDeviceId = '',
800
800
  realtimeLanguage = '',
801
+ // The listener's saved speed (null for the normal pace), and what each session reports about its own.
802
+ realtimeSpeechSpeed = null,
803
+ realtimeSessionSpeechSpeed = 1,
804
+ realtimeSpeechSpeedAdjustable = false,
801
805
  realtimeAudioSettingsBuilt = false,
802
806
  // The settings popover this instance built. A page can host two chat clients at once -- its own chat
803
807
  // and the admin widget -- and both popovers carry the same element id, so looking one up by id would
@@ -814,6 +818,14 @@
814
818
  // name merely looked like a room microphone, and the earlier repair deliberately left that alone, so users
815
819
  // found interruptions "off by default" with no idea why.
816
820
  var REALTIME_PREFS_VERSION = 3;
821
+
822
+ // Mirrors RealtimeSpeechSpeedRange on the server.
823
+ var REALTIME_SPEECH_SPEED_MIN = 0.75,
824
+ REALTIME_SPEECH_SPEED_MAX = 1.5,
825
+ REALTIME_SPEECH_SPEED_DEFAULT = 1;
826
+ function clampRealtimeSpeechSpeed(speed) {
827
+ return Math.round(Math.min(REALTIME_SPEECH_SPEED_MAX, Math.max(REALTIME_SPEECH_SPEED_MIN, speed)) * 100) / 100;
828
+ }
817
829
  function loadRealtimeAudioPrefs() {
818
830
  var prefs = {
819
831
  bargeIn: true,
@@ -822,6 +834,7 @@
822
834
  micDeviceId: '',
823
835
  outputDeviceId: '',
824
836
  language: '',
837
+ speechSpeed: null,
825
838
  version: 0
826
839
  };
827
840
  try {
@@ -846,6 +859,9 @@
846
859
  if (typeof parsed.language === 'string') {
847
860
  prefs.language = parsed.language;
848
861
  }
862
+ if (typeof parsed.speechSpeed === 'number' && isFinite(parsed.speechSpeed)) {
863
+ prefs.speechSpeed = clampRealtimeSpeechSpeed(parsed.speechSpeed);
864
+ }
849
865
  if (typeof parsed.version === 'number') {
850
866
  prefs.version = parsed.version;
851
867
  }
@@ -875,6 +891,7 @@
875
891
  realtimeMicDeviceId = prefs.micDeviceId || '';
876
892
  realtimeOutputDeviceId = prefs.outputDeviceId || '';
877
893
  realtimeLanguage = prefs.language || '';
894
+ realtimeSpeechSpeed = typeof prefs.speechSpeed === 'number' ? prefs.speechSpeed : null;
878
895
  // The gate runs on the audio thread and cannot see these variables; push the change to it.
879
896
  syncRealtimeGateMode();
880
897
  if (realtimeGain) {
@@ -1021,7 +1038,7 @@
1021
1038
  gear.className = 'btn btn-outline-secondary';
1022
1039
  gear.title = localize('settingsTitle', 'Voice settings');
1023
1040
  gear.setAttribute('aria-label', localize('settingsTitle', 'Voice settings'));
1024
- gear.innerHTML = '<i class="fa-solid fa-gear"></i>';
1041
+ gear.innerHTML = '<i class="fa-solid fa-headset"></i>';
1025
1042
  var langs = [['en', 'English'], ['es', 'Spanish'], ['fr', 'French'], ['de', 'German'], ['it', 'Italian'], ['pt', 'Portuguese'], ['nl', 'Dutch'], ['zh', 'Chinese'], ['ja', 'Japanese'], ['ko', 'Korean'], ['ar', 'Arabic'], ['hi', 'Hindi'], ['ru', 'Russian']];
1026
1043
  var langOptions = '<option value=""' + (prefs.language === '' ? ' selected' : '') + '>' + localize('languageAuto', 'Automatic') + '</option>' + langs.map(function (l) {
1027
1044
  return '<option value="' + l[0] + '"' + (prefs.language === l[0] ? ' selected' : '') + '>' + l[1] + '</option>';
@@ -1029,7 +1046,9 @@
1029
1046
  var panel = document.createElement('div');
1030
1047
  panel.className = 'card shadow-sm';
1031
1048
  panel.style.cssText = 'position:absolute;bottom:calc(100% + 6px);right:0;z-index:1080;width:290px;max-height:70vh;overflow:auto;display:none;';
1032
- panel.innerHTML = '<div class="card-body p-3">' + '<div class="fw-semibold mb-2" style="font-size:0.85rem;">' + localize('settingsTitle', 'Voice settings') + '</div>' + '<label class="form-label mb-1 d-block" style="font-size:0.8rem;">' + localize('microphone', 'Microphone') + '</label>' + '<select class="form-select form-select-sm js-mic mb-2"><option value="">' + localize('defaultMicrophone', 'Default microphone') + '</option></select>' + '<label class="form-label mb-1 d-block" style="font-size:0.8rem;">' + localize('speaker', 'Speaker') + '</label>' + '<select class="form-select form-select-sm js-out mb-1"><option value="">' + localize('automatic', 'Automatic') + '</option></select>' + '<button type="button" class="btn btn-outline-secondary btn-sm mb-2 js-pick-out" style="display:none;">' + localize('chooseSpeaker', 'Choose speaker…') + '</button>' + '<div class="form-text mb-2 js-out-help">' + localize('speakerHelp', 'Automatic uses the device your system uses for calls. If you cannot hear the assistant, pick the speakers you are actually listening to.') + '</div>' + '<label class="form-label mb-1 d-block" style="font-size:0.8rem;">' + localize('assistantVolume', 'Assistant volume') + ': <strong class="js-vol-val">' + Math.round(prefs.volume * 100) + '</strong>%</label>' + '<input type="range" class="form-range js-vol mb-2" min="0" max="100" step="5" value="' + Math.round(prefs.volume * 100) + '">' + '<label class="form-label mb-1 d-block" style="font-size:0.8rem;">' + localize('language', 'Language') + '</label>' + '<select class="form-select form-select-sm js-lang mb-2">' + langOptions + '</select>' + '<div class="form-check form-switch mb-1">' + '<input class="form-check-input js-barge" type="checkbox" id="realtime-setting-barge"' + (prefs.bargeIn ? ' checked' : '') + '>' + '<label class="form-check-label" for="realtime-setting-barge">' + localize('allowInterruptions', 'Allow interruptions') + '</label>' + '</div>' + '<div class="form-text mb-2">' + localize('allowInterruptionsHelp', 'Talk over the assistant to interrupt it. Turn this off if the assistant keeps hearing itself through your speakers.') + '</div>' + '<div class="form-check form-switch mb-1">' + '<input class="form-check-input js-ptt" type="checkbox" id="realtime-setting-ptt"' + (prefs.pushToTalk ? ' checked' : '') + '>' + '<label class="form-check-label" for="realtime-setting-ptt">' + localize('pushToTalk', 'Push-to-talk') + '</label>' + '</div>' + '<div class="form-text">' + localize('pushToTalkHelp', 'Hold <kbd>Space</kbd> (or the button) to talk. For very noisy places.') + '</div>' + '</div>';
1049
+ panel.innerHTML = '<div class="card-body p-3">' + '<div class="fw-semibold mb-2" style="font-size:0.85rem;">' + localize('settingsTitle', 'Voice settings') + '</div>' + '<label class="form-label mb-1 d-block" style="font-size:0.8rem;">' + localize('microphone', 'Microphone') + '</label>' + '<select class="form-select form-select-sm js-mic mb-2"><option value="">' + localize('defaultMicrophone', 'Default microphone') + '</option></select>' + '<label class="form-label mb-1 d-block" style="font-size:0.8rem;">' + localize('speaker', 'Speaker') + '</label>' + '<select class="form-select form-select-sm js-out mb-1"><option value="">' + localize('automatic', 'Automatic') + '</option></select>' + '<button type="button" class="btn btn-outline-secondary btn-sm mb-2 js-pick-out" style="display:none;">' + localize('chooseSpeaker', 'Choose speaker…') + '</button>' + '<div class="form-text mb-2 js-out-help">' + localize('speakerHelp', 'Automatic uses the device your system uses for calls. If you cannot hear the assistant, pick the speakers you are actually listening to.') + '</div>' + '<label class="form-label mb-1 d-block" style="font-size:0.8rem;">' + localize('assistantVolume', 'Assistant volume') + ': <strong class="js-vol-val">' + Math.round(prefs.volume * 100) + '</strong>%</label>' + '<input type="range" class="form-range js-vol mb-2" min="0" max="100" step="5" value="' + Math.round(prefs.volume * 100) + '">' +
1050
+ // Hidden until the session reports that its speed can change.
1051
+ '<div class="js-speed-row" hidden>' + '<label class="form-label mb-1 d-flex align-items-baseline" style="font-size:0.8rem;">' + '<span>' + localize('speechSpeed', 'Speaking speed') + ': <strong class="js-speed-val">1.00</strong>×</span>' + '<button type="button" class="btn btn-link btn-sm p-0 ms-auto js-speed-reset" style="font-size:0.8rem;" hidden>' + localize('speechSpeedReset', 'Reset') + '</button>' + '</label>' + '<input type="range" class="form-range js-speed mb-0" min="' + REALTIME_SPEECH_SPEED_MIN + '" max="' + REALTIME_SPEECH_SPEED_MAX + '" step="0.05" value="' + REALTIME_SPEECH_SPEED_DEFAULT + '">' + '<div class="form-text mt-0 mb-2">' + localize('speechSpeedHelp', 'Applies from the assistant\'s next reply.') + '</div>' + '</div>' + '<label class="form-label mb-1 d-block" style="font-size:0.8rem;">' + localize('language', 'Language') + '</label>' + '<select class="form-select form-select-sm js-lang mb-2">' + langOptions + '</select>' + '<div class="form-check form-switch mb-1">' + '<input class="form-check-input js-barge" type="checkbox" id="realtime-setting-barge"' + (prefs.bargeIn ? ' checked' : '') + '>' + '<label class="form-check-label" for="realtime-setting-barge">' + localize('allowInterruptions', 'Allow interruptions') + '</label>' + '</div>' + '<div class="form-text mb-2">' + localize('allowInterruptionsHelp', 'Talk over the assistant to interrupt it. Turn this off if the assistant keeps hearing itself through your speakers.') + '</div>' + '<div class="form-check form-switch mb-1">' + '<input class="form-check-input js-ptt" type="checkbox" id="realtime-setting-ptt"' + (prefs.pushToTalk ? ' checked' : '') + '>' + '<label class="form-check-label" for="realtime-setting-ptt">' + localize('pushToTalk', 'Push-to-talk') + '</label>' + '</div>' + '<div class="form-text">' + localize('pushToTalkHelp', 'Hold <kbd>Space</kbd> (or the button) to talk. For very noisy places.') + '</div>' + '</div>';
1033
1052
  wrap.appendChild(gear);
1034
1053
  wrap.appendChild(panel);
1035
1054
  realtimeBtn.insertAdjacentElement('afterend', wrap);
@@ -1042,20 +1061,35 @@
1042
1061
  var outSelect = panel.querySelector('.js-out');
1043
1062
  var outPickButton = panel.querySelector('.js-pick-out');
1044
1063
  var langSelect = panel.querySelector('.js-lang');
1045
- function persist() {
1046
- var next = {
1064
+ var speedInput = panel.querySelector('.js-speed');
1065
+ var speedVal = panel.querySelector('.js-speed-val');
1066
+ var speedReset = panel.querySelector('.js-speed-reset');
1067
+ function readPanelPrefs() {
1068
+ return {
1047
1069
  bargeIn: bargeInput.checked,
1048
1070
  pushToTalk: pttInput.checked,
1049
1071
  volume: (parseInt(volInput.value, 10) || 0) / 100,
1050
1072
  micDeviceId: micSelect.value || '',
1051
1073
  outputDeviceId: outSelect.value || '',
1052
1074
  language: langSelect.value || '',
1075
+ speechSpeed: realtimeSpeechSpeed,
1053
1076
  version: REALTIME_PREFS_VERSION
1054
1077
  };
1078
+ }
1079
+ function persist() {
1080
+ var next = readPanelPrefs();
1055
1081
  applyRealtimeAudioPrefs(next);
1056
1082
  saveRealtimeAudioPrefs(next);
1057
1083
  pushRealtimeSettingsToServer();
1058
1084
  }
1085
+
1086
+ // Kept out of persist(), which resends turn detection on every volume tick.
1087
+ function persistSpeechSpeed(speed) {
1088
+ realtimeSpeechSpeed = speed;
1089
+ saveRealtimeAudioPrefs(readPanelPrefs());
1090
+ syncRealtimeSpeechSpeedControl();
1091
+ pushRealtimeSpeechSpeedToServer();
1092
+ }
1059
1093
  function populateDevices() {
1060
1094
  if (!navigator.mediaDevices || !navigator.mediaDevices.enumerateDevices) {
1061
1095
  return;
@@ -1154,6 +1188,17 @@
1154
1188
  volVal.textContent = volInput.value;
1155
1189
  persist();
1156
1190
  });
1191
+ // The label follows the drag; the speed is sent once, on release.
1192
+ speedInput.addEventListener('input', function () {
1193
+ speedVal.textContent = formatRealtimeSpeechSpeed(parseFloat(speedInput.value));
1194
+ });
1195
+ speedInput.addEventListener('change', function () {
1196
+ persistSpeechSpeed(clampRealtimeSpeechSpeed(parseFloat(speedInput.value)));
1197
+ });
1198
+ // Back to the model's normal pace.
1199
+ speedReset.addEventListener('click', function () {
1200
+ persistSpeechSpeed(null);
1201
+ });
1157
1202
  micSelect.addEventListener('change', persist);
1158
1203
  langSelect.addEventListener('change', persist);
1159
1204
  outSelect.addEventListener('change', function () {
@@ -1294,6 +1339,50 @@
1294
1339
  connection.send('UpdateRealtimeSettings', realtimeBargeIn, null, null);
1295
1340
  } catch (err) {}
1296
1341
  }
1342
+ function formatRealtimeSpeechSpeed(speed) {
1343
+ return (isFinite(speed) ? speed : REALTIME_SPEECH_SPEED_DEFAULT).toFixed(2);
1344
+ }
1345
+
1346
+ // The listener's saved speed wins over the session's own.
1347
+ function effectiveRealtimeSpeechSpeed() {
1348
+ return realtimeSpeechSpeed !== null ? realtimeSpeechSpeed : realtimeSessionSpeechSpeed;
1349
+ }
1350
+ function syncRealtimeSpeechSpeedControl() {
1351
+ if (!realtimeAudioSettingsElement) {
1352
+ return;
1353
+ }
1354
+ var row = realtimeAudioSettingsElement.querySelector('.js-speed-row');
1355
+ var input = realtimeAudioSettingsElement.querySelector('.js-speed');
1356
+ var value = realtimeAudioSettingsElement.querySelector('.js-speed-val');
1357
+ var reset = realtimeAudioSettingsElement.querySelector('.js-speed-reset');
1358
+ if (!row || !input || !value || !reset) {
1359
+ return;
1360
+ }
1361
+ var speed = effectiveRealtimeSpeechSpeed();
1362
+ row.hidden = !realtimeSpeechSpeedAdjustable;
1363
+ input.value = String(speed);
1364
+ value.textContent = formatRealtimeSpeechSpeed(speed);
1365
+ reset.hidden = realtimeSpeechSpeed === null;
1366
+ }
1367
+ function pushRealtimeSpeechSpeedToServer() {
1368
+ if (!isRealtimeActive || !realtimeSpeechSpeedAdjustable || !connection || typeof connection.send !== 'function') {
1369
+ return;
1370
+ }
1371
+ try {
1372
+ connection.send('UpdateRealtimeSpeechSpeed', effectiveRealtimeSpeechSpeed());
1373
+ } catch (err) {}
1374
+ }
1375
+
1376
+ // An empty payload means this session's speed can't change; otherwise a saved speed is applied before anyone speaks.
1377
+ function applySessionSpeechSpeed(payload) {
1378
+ var speed = parseFloat(payload);
1379
+ realtimeSpeechSpeedAdjustable = isFinite(speed);
1380
+ realtimeSessionSpeechSpeed = realtimeSpeechSpeedAdjustable ? speed : REALTIME_SPEECH_SPEED_DEFAULT;
1381
+ syncRealtimeSpeechSpeedControl();
1382
+ if (realtimeSpeechSpeed !== null && realtimeSpeechSpeed !== realtimeSessionSpeechSpeed) {
1383
+ pushRealtimeSpeechSpeedToServer();
1384
+ }
1385
+ }
1297
1386
 
1298
1387
  // Reports the session's state to the host so it can show something more honest than a button that flips to
1299
1388
  // "End Conversation" before anything has connected.
@@ -1337,7 +1426,9 @@
1337
1426
  if (!isRealtimeActive) {
1338
1427
  return;
1339
1428
  }
1340
- if (type === 'session_ready') {
1429
+ if (type === 'speech_speed') {
1430
+ applySessionSpeechSpeed(payload);
1431
+ } else if (type === 'session_ready') {
1341
1432
  realtimeSessionReady = true;
1342
1433
  setRealtimeState('listening');
1343
1434
  } else if (type === 'session_ended') {
@@ -1565,6 +1656,9 @@
1565
1656
  var attempt = ++realtimeAttemptToken;
1566
1657
  realtimeFellBack = false;
1567
1658
  realtimeSessionReady = false;
1659
+ // Hidden until this session reports its own speed.
1660
+ realtimeSpeechSpeedAdjustable = false;
1661
+ syncRealtimeSpeechSpeedControl();
1568
1662
  realtimeEndedNotice = null;
1569
1663
  clearRealtimeStatus();
1570
1664
  bindRealtimeLifecycleHandlers();