@myned-ai/avatar-chat-widget 0.4.0 → 0.6.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.
package/README.md CHANGED
@@ -23,8 +23,7 @@ Add directly to any HTML page or website builder:
23
23
  AvatarChat.init({
24
24
  container: '#avatar-chat',
25
25
  serverUrl: 'wss://your-backend-server.com/ws',
26
- position: 'bottom-right',
27
- theme: 'light'
26
+ position: 'bottom-right'
28
27
  });
29
28
  </script>
30
29
  ```
@@ -63,7 +62,6 @@ chat.destroy(); // Cleanup
63
62
  | `container` | `string \| HTMLElement` | **required** | CSS selector or DOM element |
64
63
  | `serverUrl` | `string` | **required** | WebSocket server URL (ws:// or wss://) |
65
64
  | `position` | `string` | `'bottom-right'` | `bottom-right`, `bottom-left`, `top-right`, `top-left`, `inline` |
66
- | `theme` | `string` | `'light'` | `light`, `dark`, `auto` (follows system preference) |
67
65
  | `startCollapsed` | `boolean` | `true` | Start minimized as bubble |
68
66
  | `width` | `number` | `380` | Widget width (200-2000px) |
69
67
  | `height` | `number` | `550` | Widget height (300-2000px) |
@@ -1180,6 +1180,10 @@ class AudioInput {
1180
1180
  // 100ms at 24kHz
1181
1181
  async requestPermission() {
1182
1182
  try {
1183
+ if (!navigator.mediaDevices || !navigator.mediaDevices.getUserMedia) {
1184
+ log$e.error("MediaDevices API not available. Microphone requires HTTPS.");
1185
+ throw new Error("Microphone requires a secure connection (HTTPS)");
1186
+ }
1183
1187
  const stream = await navigator.mediaDevices.getUserMedia({
1184
1188
  audio: {
1185
1189
  sampleRate: CONFIG.audio.input.sampleRate,
@@ -1225,6 +1229,9 @@ class AudioInput {
1225
1229
  */
1226
1230
  async startPCM16Recording(onData) {
1227
1231
  try {
1232
+ if (!navigator.mediaDevices || !navigator.mediaDevices.getUserMedia) {
1233
+ throw new Error("Microphone requires a secure connection (HTTPS)");
1234
+ }
1228
1235
  this.mediaStream = await navigator.mediaDevices.getUserMedia({
1229
1236
  audio: {
1230
1237
  channelCount: 1,
@@ -3551,6 +3558,12 @@ class VoiceInputController {
3551
3558
  * Start voice recording
3552
3559
  */
3553
3560
  async start() {
3561
+ if (!navigator.mediaDevices || !navigator.mediaDevices.getUserMedia) {
3562
+ const message = "Voice input requires a secure connection (HTTPS). Please use text input instead.";
3563
+ log$5.warn(message);
3564
+ alert(message);
3565
+ return;
3566
+ }
3554
3567
  try {
3555
3568
  log$5.info("Starting recording (PCM16 24kHz)");
3556
3569
  this.protocolClient.sendAudioStreamStart();
@@ -3569,7 +3582,10 @@ class VoiceInputController {
3569
3582
  log$5.error("Failed to start recording:", error2);
3570
3583
  errorBoundary.handleError(error2, "audio-input");
3571
3584
  this.options.onError?.(error2);
3572
- alert("Microphone access denied. Please enable microphone permissions.");
3585
+ const errorMsg = error2.message || "";
3586
+ if (errorMsg.includes("Permission denied") || errorMsg.includes("NotAllowedError")) {
3587
+ alert("Microphone access was denied. Please allow microphone access in your browser settings to use voice input.");
3588
+ }
3573
3589
  }
3574
3590
  }
3575
3591
  /**
@@ -3696,7 +3712,6 @@ class ChatManager {
3696
3712
  await this.protocolClient.connect();
3697
3713
  log$4.info("WebSocket connected");
3698
3714
  this.avatar.setChatState("Idle");
3699
- await this.audioInput.requestPermission();
3700
3715
  } catch (error2) {
3701
3716
  errorBoundary.handleError(error2, "chat-manager");
3702
3717
  log$4.error("Connection failed");
@@ -3788,6 +3803,9 @@ class ChatManager {
3788
3803
  this.useSyncPlayback = false;
3789
3804
  this.subtitleController.showRemaining();
3790
3805
  this.transcriptManager.finalizeAssistantTurn();
3806
+ setTimeout(() => {
3807
+ this.subtitleController.clear();
3808
+ }, 1500);
3791
3809
  });
3792
3810
  }
3793
3811
  setupAutoScroll() {
@@ -4286,8 +4304,10 @@ const WIDGET_STYLES = `
4286
4304
  @media (max-width: 480px) {
4287
4305
  .widget-root {
4288
4306
  width: 100vw;
4289
- height: 100vh;
4307
+ height: 100vh; /* Fallback for older browsers */
4308
+ height: 100dvh; /* Dynamic viewport height - accounts for mobile browser UI */
4290
4309
  max-height: 100vh;
4310
+ max-height: 100dvh;
4291
4311
  border-radius: 0;
4292
4312
  /* Let the mobile media query at the bottom handle the rest */
4293
4313
  padding-bottom: 90px; /* Ensure input layer space is preserved */
@@ -5207,6 +5227,16 @@ const WIDGET_STYLES = `
5207
5227
  object-fit: cover;
5208
5228
  }
5209
5229
 
5230
+ .avatar-fallback-icon {
5231
+ width: 100%;
5232
+ height: 100%;
5233
+ display: flex;
5234
+ align-items: center;
5235
+ justify-content: center;
5236
+ background: linear-gradient(135deg, #4B4ACF 0%, #2E3A87 100%);
5237
+ color: white;
5238
+ }
5239
+
5210
5240
  .bubble-avatar-preview .status-dot {
5211
5241
  position: absolute;
5212
5242
  bottom: 0;
@@ -5326,7 +5356,8 @@ const WIDGET_STYLES = `
5326
5356
 
5327
5357
  /* Avatar-focus mode on mobile: avatar takes most of the space */
5328
5358
  :host(:not(.collapsed)) [data-drawer-state="avatar-focus"] {
5329
- --avatar-height: calc(100vh - 56px - 90px) !important; /* Full height minus header and input */
5359
+ --avatar-height: calc(100vh - 56px - 90px) !important; /* Fallback */
5360
+ --avatar-height: calc(100dvh - 56px - 90px) !important; /* Full height minus header and input */
5330
5361
  background: transparent !important; /* Let avatar stage show through */
5331
5362
  }
5332
5363
 
@@ -5371,11 +5402,13 @@ const WIDGET_STYLES = `
5371
5402
 
5372
5403
  /* Text-focus mode on mobile: chat takes most of the space, avatar in corner */
5373
5404
  :host(:not(.collapsed)) [data-drawer-state="text-focus"] {
5374
- --chat-height: calc(100vh - 70px - 90px) !important; /* Full height minus header and input */
5405
+ --chat-height: calc(100vh - 70px - 90px) !important; /* Fallback */
5406
+ --chat-height: calc(100dvh - 70px - 90px) !important; /* Full height minus header and input */
5375
5407
  }
5376
5408
 
5377
5409
  :host(:not(.collapsed)) [data-drawer-state="text-focus"] .chat-section {
5378
- height: calc(100vh - 70px - 90px) !important;
5410
+ height: calc(100vh - 70px - 90px) !important; /* Fallback */
5411
+ height: calc(100dvh - 70px - 90px) !important;
5379
5412
  flex: 1;
5380
5413
  }
5381
5414
 
@@ -5424,14 +5457,14 @@ const WIDGET_STYLES = `
5424
5457
  bottom: 110px; /* Similar position to subtitles, above input */
5425
5458
  width: 95%;
5426
5459
  max-width: none;
5427
- gap: 8px;
5428
- padding: 8px;
5460
+ gap: 6px;
5461
+ padding: 4px;
5429
5462
  }
5430
5463
 
5431
5464
  :host(:not(.collapsed)) [data-drawer-state="avatar-focus"] .avatar-suggestions .suggestion-chip {
5432
- padding: 12px 18px;
5433
- font-size: 14px;
5434
- border-radius: 20px;
5465
+ padding: 8px 12px;
5466
+ font-size: 13px;
5467
+ border-radius: 16px;
5435
5468
  }
5436
5469
 
5437
5470
  /* Mist overlay on mobile - covers lower portion without reaching avatar's face */
@@ -5699,7 +5732,13 @@ const BUBBLE_TEMPLATE = `
5699
5732
  </div>
5700
5733
  <div class="chat-bubble" id="chatBubble" role="button" aria-label="Open chat" tabindex="0">
5701
5734
  <div class="bubble-avatar-preview">
5702
- <img src="./asset/avatar.gif" class="avatar-face-img" alt="Nyx Avatar" />
5735
+ <img src="./asset/avatar.gif" class="avatar-face-img" alt="Nyx Avatar" onerror="this.style.display='none';this.nextElementSibling.style.display='flex';" />
5736
+ <div class="avatar-fallback-icon" style="display:none;">
5737
+ <svg width="32" height="32" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round">
5738
+ <path d="M20 21v-2a4 4 0 0 0-4-4H8a4 4 0 0 0-4 4v2"></path>
5739
+ <circle cx="12" cy="7" r="4"></circle>
5740
+ </svg>
5741
+ </div>
5703
5742
  <div class="status-dot"></div>
5704
5743
  </div>
5705
5744
  </div>