@myned-ai/avatar-chat-widget 0.6.0 → 0.7.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
|
@@ -41,6 +41,7 @@ declare class AvatarChatElement extends HTMLElement {
|
|
|
41
41
|
private _isMounted;
|
|
42
42
|
private _isConnected;
|
|
43
43
|
private _isCollapsed;
|
|
44
|
+
private visualViewportHandler;
|
|
44
45
|
constructor();
|
|
45
46
|
/**
|
|
46
47
|
* Configure the widget (call before mount)
|
|
@@ -75,10 +76,23 @@ declare class AvatarChatElement extends HTMLElement {
|
|
|
75
76
|
* Setup UI event listeners
|
|
76
77
|
*/
|
|
77
78
|
private setupUIEvents;
|
|
79
|
+
/**
|
|
80
|
+
* Handle mobile keyboard appearance using VisualViewport API
|
|
81
|
+
* Resizes the avatar to fit when keyboard opens
|
|
82
|
+
*/
|
|
83
|
+
private setupMobileKeyboardHandling;
|
|
78
84
|
/**
|
|
79
85
|
* Escape HTML to prevent XSS in user-provided suggestions
|
|
80
86
|
*/
|
|
81
87
|
private escapeHtml;
|
|
88
|
+
/**
|
|
89
|
+
* Generate CSS overrides for primaryColor and secondaryColor config options
|
|
90
|
+
*/
|
|
91
|
+
private generateColorOverrides;
|
|
92
|
+
/**
|
|
93
|
+
* Darken a hex color by a percentage
|
|
94
|
+
*/
|
|
95
|
+
private darkenColor;
|
|
82
96
|
/**
|
|
83
97
|
* Mark that conversation has messages (shows chat area instead of suggestions)
|
|
84
98
|
*/
|
|
@@ -3566,8 +3566,6 @@ class VoiceInputController {
|
|
|
3566
3566
|
}
|
|
3567
3567
|
try {
|
|
3568
3568
|
log$5.info("Starting recording (PCM16 24kHz)");
|
|
3569
|
-
this.protocolClient.sendAudioStreamStart();
|
|
3570
|
-
log$5.info("Sent audio_stream_start to server");
|
|
3571
3569
|
let chunkCount = 0;
|
|
3572
3570
|
await this.audioInput.startRecording((audioData) => {
|
|
3573
3571
|
chunkCount++;
|
|
@@ -3576,15 +3574,22 @@ class VoiceInputController {
|
|
|
3576
3574
|
}
|
|
3577
3575
|
this.protocolClient.sendAudioData(audioData);
|
|
3578
3576
|
}, "pcm16");
|
|
3579
|
-
|
|
3577
|
+
this.protocolClient.sendAudioStreamStart();
|
|
3578
|
+
log$5.info("Recording started successfully, sent audio_stream_start to server");
|
|
3580
3579
|
this.setRecordingState(true);
|
|
3581
3580
|
} catch (error2) {
|
|
3582
3581
|
log$5.error("Failed to start recording:", error2);
|
|
3583
3582
|
errorBoundary.handleError(error2, "audio-input");
|
|
3584
3583
|
this.options.onError?.(error2);
|
|
3584
|
+
const errorName = error2.name || "";
|
|
3585
3585
|
const errorMsg = error2.message || "";
|
|
3586
|
-
|
|
3586
|
+
const errorStr = `${errorName} ${errorMsg}`.toLowerCase();
|
|
3587
|
+
if (errorStr.includes("policy") || errorStr.includes("not allowed in this document")) {
|
|
3588
|
+
alert("Microphone is blocked by this website's security settings. The site owner needs to enable microphone access.");
|
|
3589
|
+
} else if (errorName === "NotAllowedError" || errorStr.includes("permission denied") || errorStr.includes("denied")) {
|
|
3587
3590
|
alert("Microphone access was denied. Please allow microphone access in your browser settings to use voice input.");
|
|
3591
|
+
} else {
|
|
3592
|
+
alert("Could not access microphone. Please check your browser settings and try again.");
|
|
3588
3593
|
}
|
|
3589
3594
|
}
|
|
3590
3595
|
}
|
|
@@ -4249,6 +4254,7 @@ const WIDGET_STYLES = `
|
|
|
4249
4254
|
box-sizing: border-box;
|
|
4250
4255
|
--primary-color: #4B4ACF;
|
|
4251
4256
|
--primary-gradient: linear-gradient(135deg, #4B4ACF 0%, #2E3A87 100%);
|
|
4257
|
+
--secondary-color: #1F2937;
|
|
4252
4258
|
--bg-color: #ffffff;
|
|
4253
4259
|
--text-color: #1F2937;
|
|
4254
4260
|
--input-bg: #f5f5f7;
|
|
@@ -4414,7 +4420,7 @@ const WIDGET_STYLES = `
|
|
|
4414
4420
|
margin: 0;
|
|
4415
4421
|
font-size: 16px;
|
|
4416
4422
|
font-weight: 700;
|
|
4417
|
-
color: var(--
|
|
4423
|
+
color: var(--secondary-color);
|
|
4418
4424
|
letter-spacing: -0.01em;
|
|
4419
4425
|
}
|
|
4420
4426
|
|
|
@@ -4467,7 +4473,7 @@ const WIDGET_STYLES = `
|
|
|
4467
4473
|
.control-btn {
|
|
4468
4474
|
background: transparent;
|
|
4469
4475
|
border: none;
|
|
4470
|
-
color: var(--
|
|
4476
|
+
color: var(--secondary-color);
|
|
4471
4477
|
width: 32px;
|
|
4472
4478
|
height: 32px;
|
|
4473
4479
|
border-radius: 50%;
|
|
@@ -5233,7 +5239,7 @@ const WIDGET_STYLES = `
|
|
|
5233
5239
|
display: flex;
|
|
5234
5240
|
align-items: center;
|
|
5235
5241
|
justify-content: center;
|
|
5236
|
-
background:
|
|
5242
|
+
background: var(--primary-gradient);
|
|
5237
5243
|
color: white;
|
|
5238
5244
|
}
|
|
5239
5245
|
|
|
@@ -5503,6 +5509,47 @@ const WIDGET_STYLES = `
|
|
|
5503
5509
|
white-space: nowrap;
|
|
5504
5510
|
border-width: 0;
|
|
5505
5511
|
}
|
|
5512
|
+
|
|
5513
|
+
/* ==========================================================================
|
|
5514
|
+
Mobile Keyboard Visible State
|
|
5515
|
+
Resize avatar when virtual keyboard appears on mobile
|
|
5516
|
+
========================================================================== */
|
|
5517
|
+
@media (max-width: 480px) {
|
|
5518
|
+
/* When keyboard is visible, shrink the avatar section */
|
|
5519
|
+
.widget-root.keyboard-visible {
|
|
5520
|
+
--avatar-height: 180px;
|
|
5521
|
+
}
|
|
5522
|
+
|
|
5523
|
+
/* Scale down the avatar render and push it down */
|
|
5524
|
+
.widget-root.keyboard-visible .avatar-render-container {
|
|
5525
|
+
transform: translate(-50%, -35%) scale(0.65) !important;
|
|
5526
|
+
transition: transform 0.3s ease;
|
|
5527
|
+
}
|
|
5528
|
+
|
|
5529
|
+
/* Show subtitles but position them properly */
|
|
5530
|
+
.widget-root.keyboard-visible .avatar-subtitles {
|
|
5531
|
+
display: block !important;
|
|
5532
|
+
bottom: 100px !important;
|
|
5533
|
+
font-size: 14px;
|
|
5534
|
+
}
|
|
5535
|
+
|
|
5536
|
+
/* Hide mist to save space */
|
|
5537
|
+
.widget-root.keyboard-visible .avatar-mist-overlay {
|
|
5538
|
+
display: none !important;
|
|
5539
|
+
}
|
|
5540
|
+
|
|
5541
|
+
/* Move suggestions closer to input */
|
|
5542
|
+
.widget-root.keyboard-visible .avatar-suggestions {
|
|
5543
|
+
bottom: 95px !important;
|
|
5544
|
+
}
|
|
5545
|
+
|
|
5546
|
+
/* In text-focus mode with keyboard, ensure chat doesn't overflow */
|
|
5547
|
+
.widget-root.keyboard-visible[data-drawer-state="text-focus"] .chat-section {
|
|
5548
|
+
flex: 1 1 auto !important;
|
|
5549
|
+
min-height: 0 !important;
|
|
5550
|
+
height: auto !important;
|
|
5551
|
+
}
|
|
5552
|
+
}
|
|
5506
5553
|
`;
|
|
5507
5554
|
const LAYOUT = {
|
|
5508
5555
|
/** Header bar height - contains title, status, minimize button */
|
|
@@ -5795,6 +5842,7 @@ class AvatarChatElement extends HTMLElement {
|
|
|
5795
5842
|
this._isMounted = false;
|
|
5796
5843
|
this._isConnected = false;
|
|
5797
5844
|
this._isCollapsed = false;
|
|
5845
|
+
this.visualViewportHandler = null;
|
|
5798
5846
|
this.shadow = this.attachShadow({ mode: "open" });
|
|
5799
5847
|
}
|
|
5800
5848
|
/**
|
|
@@ -5823,8 +5871,9 @@ class AvatarChatElement extends HTMLElement {
|
|
|
5823
5871
|
return;
|
|
5824
5872
|
}
|
|
5825
5873
|
log$2.info("Mounting widget");
|
|
5874
|
+
const colorOverrides = this.generateColorOverrides();
|
|
5826
5875
|
const styleEl = document.createElement("style");
|
|
5827
|
-
styleEl.textContent = WIDGET_STYLES + (this.config.customStyles || "");
|
|
5876
|
+
styleEl.textContent = WIDGET_STYLES + colorOverrides + (this.config.customStyles || "");
|
|
5828
5877
|
this.shadow.appendChild(styleEl);
|
|
5829
5878
|
if (this.config.position && this.config.position !== "inline") {
|
|
5830
5879
|
this.classList.add(`position-${this.config.position}`);
|
|
@@ -5858,6 +5907,7 @@ class AvatarChatElement extends HTMLElement {
|
|
|
5858
5907
|
this.shadow.appendChild(root);
|
|
5859
5908
|
this.initializeDrawer();
|
|
5860
5909
|
this.setupUIEvents();
|
|
5910
|
+
this.setupMobileKeyboardHandling();
|
|
5861
5911
|
if (!this.config.enableVoice) {
|
|
5862
5912
|
const voiceBtn = this.shadow.getElementById("micBtn");
|
|
5863
5913
|
if (voiceBtn) voiceBtn.style.display = "none";
|
|
@@ -6092,6 +6142,34 @@ class AvatarChatElement extends HTMLElement {
|
|
|
6092
6142
|
});
|
|
6093
6143
|
}
|
|
6094
6144
|
}
|
|
6145
|
+
/**
|
|
6146
|
+
* Handle mobile keyboard appearance using VisualViewport API
|
|
6147
|
+
* Resizes the avatar to fit when keyboard opens
|
|
6148
|
+
*/
|
|
6149
|
+
setupMobileKeyboardHandling() {
|
|
6150
|
+
if (!window.visualViewport) {
|
|
6151
|
+
return;
|
|
6152
|
+
}
|
|
6153
|
+
const widgetRoot = this.shadow.querySelector(".widget-root");
|
|
6154
|
+
if (!widgetRoot) {
|
|
6155
|
+
return;
|
|
6156
|
+
}
|
|
6157
|
+
let initialViewportHeight = window.visualViewport.height;
|
|
6158
|
+
const handleViewportChange = () => {
|
|
6159
|
+
const viewport = window.visualViewport;
|
|
6160
|
+
const currentHeight = viewport.height;
|
|
6161
|
+
const keyboardHeight = initialViewportHeight - currentHeight;
|
|
6162
|
+
if (keyboardHeight > 150) {
|
|
6163
|
+
widgetRoot.classList.add("keyboard-visible");
|
|
6164
|
+
} else {
|
|
6165
|
+
widgetRoot.classList.remove("keyboard-visible");
|
|
6166
|
+
initialViewportHeight = currentHeight;
|
|
6167
|
+
}
|
|
6168
|
+
};
|
|
6169
|
+
window.visualViewport.addEventListener("resize", handleViewportChange);
|
|
6170
|
+
window.visualViewport.addEventListener("scroll", handleViewportChange);
|
|
6171
|
+
this.visualViewportHandler = handleViewportChange;
|
|
6172
|
+
}
|
|
6095
6173
|
/**
|
|
6096
6174
|
* Escape HTML to prevent XSS in user-provided suggestions
|
|
6097
6175
|
*/
|
|
@@ -6100,6 +6178,36 @@ class AvatarChatElement extends HTMLElement {
|
|
|
6100
6178
|
div.textContent = text;
|
|
6101
6179
|
return div.innerHTML;
|
|
6102
6180
|
}
|
|
6181
|
+
/**
|
|
6182
|
+
* Generate CSS overrides for primaryColor and secondaryColor config options
|
|
6183
|
+
*/
|
|
6184
|
+
generateColorOverrides() {
|
|
6185
|
+
const overrides = [];
|
|
6186
|
+
if (this.config.primaryColor) {
|
|
6187
|
+
const primary = this.config.primaryColor;
|
|
6188
|
+
const darkerShade = this.darkenColor(primary, 0.2);
|
|
6189
|
+
overrides.push(`--primary-color: ${primary};`);
|
|
6190
|
+
overrides.push(`--primary-gradient: linear-gradient(135deg, ${primary} 0%, ${darkerShade} 100%);`);
|
|
6191
|
+
}
|
|
6192
|
+
if (this.config.secondaryColor) {
|
|
6193
|
+
overrides.push(`--secondary-color: ${this.config.secondaryColor};`);
|
|
6194
|
+
}
|
|
6195
|
+
if (overrides.length === 0) return "";
|
|
6196
|
+
return `:host { ${overrides.join(" ")} }`;
|
|
6197
|
+
}
|
|
6198
|
+
/**
|
|
6199
|
+
* Darken a hex color by a percentage
|
|
6200
|
+
*/
|
|
6201
|
+
darkenColor(hex, percent) {
|
|
6202
|
+
hex = hex.replace(/^#/, "");
|
|
6203
|
+
let r = parseInt(hex.substring(0, 2), 16);
|
|
6204
|
+
let g = parseInt(hex.substring(2, 4), 16);
|
|
6205
|
+
let b = parseInt(hex.substring(4, 6), 16);
|
|
6206
|
+
r = Math.max(0, Math.floor(r * (1 - percent)));
|
|
6207
|
+
g = Math.max(0, Math.floor(g * (1 - percent)));
|
|
6208
|
+
b = Math.max(0, Math.floor(b * (1 - percent)));
|
|
6209
|
+
return `#${r.toString(16).padStart(2, "0")}${g.toString(16).padStart(2, "0")}${b.toString(16).padStart(2, "0")}`;
|
|
6210
|
+
}
|
|
6103
6211
|
/**
|
|
6104
6212
|
* Mark that conversation has messages (shows chat area instead of suggestions)
|
|
6105
6213
|
*/
|