@speechos/client 0.2.3 → 0.2.4

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.
@@ -26959,6 +26959,28 @@
26959
26959
  userId
26960
26960
  };
26961
26961
  }
26962
+ /**
26963
+ * LocalStorage key for anonymous ID persistence
26964
+ */
26965
+ const ANONYMOUS_ID_KEY = "speechos_anonymous_id";
26966
+ /**
26967
+ * Get or generate a persistent anonymous ID for Mixpanel tracking.
26968
+ *
26969
+ * This ID is stored in localStorage to persist across sessions,
26970
+ * allowing consistent anonymous user tracking without identifying
26971
+ * the account owner's customers.
26972
+ *
26973
+ * @returns A UUID string for anonymous identification
26974
+ */
26975
+ function getAnonymousId() {
26976
+ if (typeof localStorage === "undefined") return crypto.randomUUID();
26977
+ let anonymousId = localStorage.getItem(ANONYMOUS_ID_KEY);
26978
+ if (!anonymousId) {
26979
+ anonymousId = crypto.randomUUID();
26980
+ localStorage.setItem(ANONYMOUS_ID_KEY, anonymousId);
26981
+ }
26982
+ return anonymousId;
26983
+ }
26962
26984
 
26963
26985
  //#endregion
26964
26986
  //#region src/events.ts
@@ -28416,10 +28438,12 @@
28416
28438
  const config = getConfig();
28417
28439
  const audioFormat = getSupportedAudioFormat();
28418
28440
  const settings = this.sessionSettings;
28441
+ const anonymousId = getAnonymousId();
28419
28442
  const authMessage = {
28420
28443
  type: MESSAGE_TYPE_AUTH,
28421
28444
  api_key: config.apiKey,
28422
28445
  user_id: config.userId || null,
28446
+ anonymous_id: anonymousId,
28423
28447
  input_language: settings.inputLanguageCode ?? "en-US",
28424
28448
  output_language: settings.outputLanguageCode ?? "en-US",
28425
28449
  smart_format: settings.smartFormat ?? true,
@@ -28728,6 +28752,84 @@
28728
28752
  return websocketBackend;
28729
28753
  }
28730
28754
 
28755
+ /**
28756
+ * Client configuration for SpeechOS
28757
+ * Extends core config with UI/widget-specific options
28758
+ */
28759
+ /**
28760
+ * Default client configuration values
28761
+ */
28762
+ const defaultClientConfig = {
28763
+ commands: [],
28764
+ zIndex: 999999,
28765
+ alwaysVisible: false,
28766
+ };
28767
+ /**
28768
+ * Current client configuration singleton
28769
+ */
28770
+ let currentClientConfig = { ...defaultClientConfig };
28771
+ /**
28772
+ * Validate and resolve client-specific config
28773
+ * @param config - User-provided configuration
28774
+ * @returns Resolved client configuration
28775
+ */
28776
+ function validateClientConfig(config) {
28777
+ const resolved = {
28778
+ commands: config.commands ?? defaultClientConfig.commands,
28779
+ zIndex: config.zIndex ?? defaultClientConfig.zIndex,
28780
+ alwaysVisible: config.alwaysVisible ?? defaultClientConfig.alwaysVisible,
28781
+ };
28782
+ // Validate zIndex
28783
+ if (typeof resolved.zIndex !== "number" || resolved.zIndex < 0) {
28784
+ console.warn(`Invalid zIndex "${resolved.zIndex}". Using default ${defaultClientConfig.zIndex}.`);
28785
+ resolved.zIndex = defaultClientConfig.zIndex;
28786
+ }
28787
+ return resolved;
28788
+ }
28789
+ /**
28790
+ * Set the client configuration
28791
+ * @param config - Client configuration to set
28792
+ */
28793
+ function setClientConfig(config) {
28794
+ currentClientConfig = validateClientConfig(config);
28795
+ }
28796
+ /**
28797
+ * Get the current client configuration
28798
+ */
28799
+ function getClientConfig() {
28800
+ return { ...currentClientConfig };
28801
+ }
28802
+ /**
28803
+ * Reset client configuration to defaults
28804
+ */
28805
+ function resetClientConfig() {
28806
+ currentClientConfig = { ...defaultClientConfig };
28807
+ }
28808
+ /**
28809
+ * Check if commands are configured (for showing Command button in widget)
28810
+ */
28811
+ function hasCommands() {
28812
+ return currentClientConfig.commands.length > 0;
28813
+ }
28814
+ /**
28815
+ * Get configured commands
28816
+ */
28817
+ function getCommands() {
28818
+ return [...currentClientConfig.commands];
28819
+ }
28820
+ /**
28821
+ * Get widget z-index
28822
+ */
28823
+ function getZIndex() {
28824
+ return currentClientConfig.zIndex;
28825
+ }
28826
+ /**
28827
+ * Check if widget should always be visible
28828
+ */
28829
+ function isAlwaysVisible() {
28830
+ return currentClientConfig.alwaysVisible;
28831
+ }
28832
+
28731
28833
  /**
28732
28834
  * Form field focus detection for SpeechOS Client SDK
28733
28835
  * Detects when users focus on form fields and manages widget visibility
@@ -28815,12 +28917,12 @@
28815
28917
  relatedTarget === widget);
28816
28918
  // If focus is going to an element with data-speechos-no-close, don't hide
28817
28919
  const goingToNoCloseElement = Boolean(relatedTarget?.closest("[data-speechos-no-close]"));
28818
- console.log("[SpeechOS] blurHandler:", {
28819
- relatedTarget,
28820
- goingToFormField,
28821
- goingToWidget,
28822
- goingToNoCloseElement,
28823
- });
28920
+ // console.log("[SpeechOS] blurHandler:", {
28921
+ // relatedTarget,
28922
+ // goingToFormField,
28923
+ // goingToWidget,
28924
+ // goingToNoCloseElement,
28925
+ // });
28824
28926
  if (goingToFormField || goingToWidget || goingToNoCloseElement) {
28825
28927
  console.log("[SpeechOS] blurHandler: early return, not hiding");
28826
28928
  return;
@@ -28839,11 +28941,15 @@
28839
28941
  // Check if focus is on an element with data-speechos-no-close
28840
28942
  const isNoCloseElementFocused = Boolean(activeElement?.closest("[data-speechos-no-close]"));
28841
28943
  // Only hide if no form field is focused AND widget isn't focused AND not a no-close element
28944
+ // AND alwaysVisible is not enabled
28842
28945
  if (!isFormField(activeElement) &&
28843
28946
  !isWidgetFocused &&
28844
28947
  !isNoCloseElementFocused) {
28845
28948
  state.setFocusedElement(null);
28846
- state.hide();
28949
+ // Don't hide if alwaysVisible is enabled
28950
+ if (!isAlwaysVisible()) {
28951
+ state.hide();
28952
+ }
28847
28953
  events.emit("form:blur", { element: null });
28848
28954
  }
28849
28955
  }, 150);
@@ -28917,7 +29023,10 @@
28917
29023
  // Reset state
28918
29024
  this.isWidgetBeingInteracted = false;
28919
29025
  state.setFocusedElement(null);
28920
- state.hide();
29026
+ // Don't hide if alwaysVisible is enabled
29027
+ if (!isAlwaysVisible()) {
29028
+ state.hide();
29029
+ }
28921
29030
  this.isActive = false;
28922
29031
  }
28923
29032
  /**
@@ -28930,76 +29039,6 @@
28930
29039
  // Export singleton instance
28931
29040
  const formDetector = new FormDetector();
28932
29041
 
28933
- /**
28934
- * Client configuration for SpeechOS
28935
- * Extends core config with UI/widget-specific options
28936
- */
28937
- /**
28938
- * Default client configuration values
28939
- */
28940
- const defaultClientConfig = {
28941
- commands: [],
28942
- zIndex: 999999,
28943
- };
28944
- /**
28945
- * Current client configuration singleton
28946
- */
28947
- let currentClientConfig = { ...defaultClientConfig };
28948
- /**
28949
- * Validate and resolve client-specific config
28950
- * @param config - User-provided configuration
28951
- * @returns Resolved client configuration
28952
- */
28953
- function validateClientConfig(config) {
28954
- const resolved = {
28955
- commands: config.commands ?? defaultClientConfig.commands,
28956
- zIndex: config.zIndex ?? defaultClientConfig.zIndex,
28957
- };
28958
- // Validate zIndex
28959
- if (typeof resolved.zIndex !== "number" || resolved.zIndex < 0) {
28960
- console.warn(`Invalid zIndex "${resolved.zIndex}". Using default ${defaultClientConfig.zIndex}.`);
28961
- resolved.zIndex = defaultClientConfig.zIndex;
28962
- }
28963
- return resolved;
28964
- }
28965
- /**
28966
- * Set the client configuration
28967
- * @param config - Client configuration to set
28968
- */
28969
- function setClientConfig(config) {
28970
- currentClientConfig = validateClientConfig(config);
28971
- }
28972
- /**
28973
- * Get the current client configuration
28974
- */
28975
- function getClientConfig() {
28976
- return { ...currentClientConfig };
28977
- }
28978
- /**
28979
- * Reset client configuration to defaults
28980
- */
28981
- function resetClientConfig() {
28982
- currentClientConfig = { ...defaultClientConfig };
28983
- }
28984
- /**
28985
- * Check if commands are configured (for showing Command button in widget)
28986
- */
28987
- function hasCommands() {
28988
- return currentClientConfig.commands.length > 0;
28989
- }
28990
- /**
28991
- * Get configured commands
28992
- */
28993
- function getCommands() {
28994
- return [...currentClientConfig.commands];
28995
- }
28996
- /**
28997
- * Get widget z-index
28998
- */
28999
- function getZIndex() {
29000
- return currentClientConfig.zIndex;
29001
- }
29002
-
29003
29042
  /**
29004
29043
  * Text input handler for SpeechOS Client SDK
29005
29044
  * Abstracts cursor/selection detection and text insertion/replacement operations
@@ -30204,6 +30243,11 @@
30204
30243
  `;
30205
30244
  return b `${o(svgString)}`;
30206
30245
  };
30246
+ /**
30247
+ * Check icon for "Keep" action
30248
+ * Lucide Check icon paths
30249
+ */
30250
+ const checkIcon = (size = 18) => createIcon('<path d="M20 6 9 17l-5-5"/>', size);
30207
30251
  /**
30208
30252
  * X icon for cancel action
30209
30253
  * Lucide X icon paths
@@ -30464,6 +30508,7 @@
30464
30508
  this.activeAction = null;
30465
30509
  this.editPreviewText = "";
30466
30510
  this.errorMessage = null;
30511
+ this.commandFeedback = null;
30467
30512
  }
30468
30513
  static { this.styles = [
30469
30514
  themeStyles,
@@ -31031,6 +31076,47 @@
31031
31076
  }
31032
31077
  }
31033
31078
 
31079
+ /* Command feedback badge - success state (amber/orange) */
31080
+ .status-label.command-success {
31081
+ background: linear-gradient(135deg, #f59e0b 0%, #d97706 100%);
31082
+ box-shadow: 0 4px 12px rgba(245, 158, 11, 0.3);
31083
+ padding-left: 24px;
31084
+ animation: command-feedback-in 0.3s cubic-bezier(0.34, 1.56, 0.64, 1)
31085
+ forwards;
31086
+ }
31087
+
31088
+ .status-label.command-success::before {
31089
+ content: "";
31090
+ position: absolute;
31091
+ top: 50%;
31092
+ left: 8px;
31093
+ width: 12px;
31094
+ height: 12px;
31095
+ transform: translateY(-50%);
31096
+ background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='12' height='12' viewBox='0 0 24 24' fill='none' stroke='white' stroke-width='3' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpath d='M20 6 9 17l-5-5'/%3E%3C/svg%3E");
31097
+ background-repeat: no-repeat;
31098
+ background-position: center;
31099
+ }
31100
+
31101
+ /* Command feedback badge - no match state (neutral gray) */
31102
+ .status-label.command-none {
31103
+ background: #4b5563;
31104
+ box-shadow: 0 4px 12px rgba(75, 85, 99, 0.3);
31105
+ animation: command-feedback-in 0.3s cubic-bezier(0.34, 1.56, 0.64, 1)
31106
+ forwards;
31107
+ }
31108
+
31109
+ @keyframes command-feedback-in {
31110
+ 0% {
31111
+ opacity: 0;
31112
+ transform: translateX(-50%) scale(0.8) translateY(4px);
31113
+ }
31114
+ 100% {
31115
+ opacity: 1;
31116
+ transform: translateX(-50%) scale(1) translateY(0);
31117
+ }
31118
+ }
31119
+
31034
31120
  /* Cancel button - positioned to the right of the main mic button */
31035
31121
  .cancel-button {
31036
31122
  position: absolute;
@@ -31278,6 +31364,16 @@
31278
31364
  left: 10px;
31279
31365
  }
31280
31366
 
31367
+ .status-label.command-success {
31368
+ padding-left: 30px;
31369
+ }
31370
+
31371
+ .status-label.command-success::before {
31372
+ left: 10px;
31373
+ width: 14px;
31374
+ height: 14px;
31375
+ }
31376
+
31281
31377
  .error-message {
31282
31378
  font-size: 15px;
31283
31379
  padding: 14px 18px;
@@ -31430,6 +31526,15 @@
31430
31526
  }
31431
31527
  return this.recordingState;
31432
31528
  }
31529
+ getCommandFeedbackLabel() {
31530
+ if (this.commandFeedback === "success") {
31531
+ return "Got it!";
31532
+ }
31533
+ if (this.commandFeedback === "none") {
31534
+ return "No command matched";
31535
+ }
31536
+ return "";
31537
+ }
31433
31538
  render() {
31434
31539
  const showPulse = this.recordingState === "recording";
31435
31540
  const showSiriConnecting = this.recordingState === "connecting";
@@ -31437,13 +31542,14 @@
31437
31542
  const showSiriEdit = this.recordingState === "processing" && this.activeAction === "edit";
31438
31543
  const statusLabel = this.getStatusLabel();
31439
31544
  const showVisualizer = this.shouldShowVisualizer();
31440
- // Show status label during recording (either visualizer or edit text)
31441
- const showStatus = this.recordingState === "recording";
31545
+ // Show status label during recording (either visualizer or edit text) OR command feedback
31546
+ const showCommandFeedback = this.recordingState === "idle" && this.commandFeedback !== null;
31547
+ const showStatus = this.recordingState === "recording" || showCommandFeedback;
31442
31548
  const showCancel = this.recordingState === "connecting" ||
31443
31549
  this.recordingState === "recording" ||
31444
31550
  this.recordingState === "processing";
31445
31551
  const showError = this.recordingState === "error" && this.errorMessage;
31446
- // Show close button in idle state (both solo mic and expanded)
31552
+ // Show close button in idle state (both solo mic and expanded), including when showing command feedback
31447
31553
  const showClose = this.recordingState === "idle";
31448
31554
  return b `
31449
31555
  <div class="button-wrapper">
@@ -31489,15 +31595,19 @@
31489
31595
  </button>
31490
31596
 
31491
31597
  <span
31492
- class="status-label ${showStatus ? "visible" : ""} ${showVisualizer
31493
- ? "visualizer"
31494
- : this.getStatusClass()}"
31598
+ class="status-label ${showStatus ? "visible" : ""} ${showCommandFeedback
31599
+ ? `command-${this.commandFeedback}`
31600
+ : showVisualizer
31601
+ ? "visualizer"
31602
+ : this.getStatusClass()}"
31495
31603
  >
31496
- ${showVisualizer
31497
- ? b `<speechos-audio-visualizer
31498
- ?active="${showVisualizer}"
31499
- ></speechos-audio-visualizer>`
31500
- : statusLabel}
31604
+ ${showCommandFeedback
31605
+ ? this.getCommandFeedbackLabel()
31606
+ : showVisualizer
31607
+ ? b `<speechos-audio-visualizer
31608
+ ?active="${showVisualizer}"
31609
+ ></speechos-audio-visualizer>`
31610
+ : statusLabel}
31501
31611
  </span>
31502
31612
 
31503
31613
  <button
@@ -31538,6 +31648,9 @@
31538
31648
  __decorate([
31539
31649
  n({ type: String })
31540
31650
  ], SpeechOSMicButton.prototype, "errorMessage", void 0);
31651
+ __decorate([
31652
+ n({ type: String })
31653
+ ], SpeechOSMicButton.prototype, "commandFeedback", void 0);
31541
31654
  SpeechOSMicButton = __decorate([
31542
31655
  t$1("speechos-mic-button")
31543
31656
  ], SpeechOSMicButton);
@@ -34315,56 +34428,649 @@
34315
34428
  ], SpeechOSSettingsModal);
34316
34429
 
34317
34430
  /**
34318
- * Main widget container component
34319
- * Composes mic button and action bubbles with state management
34320
- */
34321
- var SpeechOSWidget_1;
34322
- /**
34323
- * Minimum duration to show the connecting animation (in milliseconds).
34324
- * Since mic capture starts almost instantly, we enforce a minimum animation
34325
- * duration so users can see the visual feedback before transitioning to recording.
34431
+ * Shared styles for lightweight popup modals
34432
+ * Used by dictation-output-modal and edit-help-modal
34326
34433
  */
34327
- const MIN_CONNECTING_ANIMATION_MS = 200;
34328
- let SpeechOSWidget = class SpeechOSWidget extends i$1 {
34329
- constructor() {
34330
- super(...arguments);
34331
- this.widgetState = state.getState();
34332
- this.settingsOpen = false;
34333
- this.dictationTargetElement = null;
34334
- this.editTargetElement = null;
34335
- this.dictationCursorStart = null;
34336
- this.dictationCursorEnd = null;
34337
- this.editSelectionStart = null;
34338
- this.editSelectionEnd = null;
34339
- this.editSelectedText = "";
34340
- this.boundClickOutsideHandler = null;
34341
- this.modalElement = null;
34342
- this.customPosition = null;
34343
- this.isDragging = false;
34344
- this.dragStartPos = null;
34345
- this.dragOffset = { x: 0, y: 0 };
34346
- this.boundDragMove = null;
34347
- this.boundDragEnd = null;
34348
- this.suppressNextClick = false;
34349
- this.boundViewportResizeHandler = null;
34350
- this.boundScrollHandler = null;
34351
- }
34352
- static { SpeechOSWidget_1 = this; }
34353
- static { this.styles = [
34354
- themeStyles,
34355
- animations,
34356
- i$4 `
34357
- :host {
34358
- position: fixed;
34359
- bottom: var(--speechos-spacing-md); /* 12px - same as spacing above */
34360
- z-index: var(--speechos-z-base);
34361
- pointer-events: none;
34362
- }
34434
+ /** Base popup modal styles - simpler than full settings modal */
34435
+ const popupModalStyles = i$4 `
34436
+ :host {
34437
+ position: fixed;
34438
+ inset: 0;
34439
+ pointer-events: none;
34440
+ z-index: calc(var(--speechos-z-base) + 100);
34441
+ }
34363
34442
 
34364
- :host {
34365
- left: 50%;
34366
- transform: translateX(-50%);
34367
- }
34443
+ .modal-overlay {
34444
+ position: fixed;
34445
+ inset: 0;
34446
+ background: rgba(0, 0, 0, 0.5);
34447
+ display: flex;
34448
+ align-items: center;
34449
+ justify-content: center;
34450
+ z-index: calc(var(--speechos-z-base) + 100);
34451
+ opacity: 0;
34452
+ visibility: hidden;
34453
+ transition: all 0.2s ease;
34454
+ pointer-events: none;
34455
+ backdrop-filter: blur(4px);
34456
+ }
34457
+
34458
+ .modal-overlay.open {
34459
+ opacity: 1;
34460
+ visibility: visible;
34461
+ pointer-events: auto;
34462
+ }
34463
+
34464
+ .modal-card {
34465
+ background: #1a1d24;
34466
+ border-radius: 16px;
34467
+ width: 90%;
34468
+ max-width: 400px;
34469
+ display: flex;
34470
+ flex-direction: column;
34471
+ box-shadow: 0 24px 48px rgba(0, 0, 0, 0.4),
34472
+ 0 0 0 1px rgba(255, 255, 255, 0.05);
34473
+ transform: scale(0.95) translateY(10px);
34474
+ transition: transform 0.25s cubic-bezier(0.16, 1, 0.3, 1);
34475
+ pointer-events: auto;
34476
+ overflow: hidden;
34477
+ }
34478
+
34479
+ .modal-overlay.open .modal-card {
34480
+ transform: scale(1) translateY(0);
34481
+ }
34482
+
34483
+ .modal-header {
34484
+ display: flex;
34485
+ align-items: center;
34486
+ justify-content: space-between;
34487
+ padding: 16px 20px;
34488
+ border-bottom: 1px solid rgba(255, 255, 255, 0.06);
34489
+ background: rgba(0, 0, 0, 0.2);
34490
+ }
34491
+
34492
+ .modal-title {
34493
+ font-size: 16px;
34494
+ font-weight: 600;
34495
+ color: white;
34496
+ margin: 0;
34497
+ letter-spacing: -0.01em;
34498
+ }
34499
+
34500
+ .close-button {
34501
+ width: 32px;
34502
+ height: 32px;
34503
+ border-radius: 8px;
34504
+ background: transparent;
34505
+ border: none;
34506
+ cursor: pointer;
34507
+ display: flex;
34508
+ align-items: center;
34509
+ justify-content: center;
34510
+ color: rgba(255, 255, 255, 0.5);
34511
+ transition: all 0.15s ease;
34512
+ }
34513
+
34514
+ .close-button:hover {
34515
+ background: rgba(255, 255, 255, 0.08);
34516
+ color: white;
34517
+ }
34518
+
34519
+ .modal-body {
34520
+ padding: 20px;
34521
+ }
34522
+
34523
+ .modal-footer {
34524
+ display: flex;
34525
+ justify-content: flex-end;
34526
+ gap: 10px;
34527
+ padding: 16px 20px;
34528
+ border-top: 1px solid rgba(255, 255, 255, 0.06);
34529
+ background: rgba(0, 0, 0, 0.1);
34530
+ }
34531
+
34532
+ .btn {
34533
+ padding: 10px 18px;
34534
+ border-radius: 8px;
34535
+ font-size: 13px;
34536
+ font-weight: 600;
34537
+ cursor: pointer;
34538
+ transition: all 0.15s ease;
34539
+ border: none;
34540
+ display: inline-flex;
34541
+ align-items: center;
34542
+ gap: 6px;
34543
+ }
34544
+
34545
+ .btn-secondary {
34546
+ background: rgba(255, 255, 255, 0.08);
34547
+ color: rgba(255, 255, 255, 0.8);
34548
+ }
34549
+
34550
+ .btn-secondary:hover {
34551
+ background: rgba(255, 255, 255, 0.12);
34552
+ color: white;
34553
+ }
34554
+
34555
+ .btn-primary {
34556
+ background: linear-gradient(135deg, #10b981 0%, #059669 100%);
34557
+ color: white;
34558
+ box-shadow: 0 2px 8px rgba(16, 185, 129, 0.2);
34559
+ }
34560
+
34561
+ .btn-primary:hover {
34562
+ transform: translateY(-1px);
34563
+ box-shadow: 0 4px 12px rgba(16, 185, 129, 0.3);
34564
+ }
34565
+
34566
+ .btn-primary:active {
34567
+ transform: translateY(0);
34568
+ }
34569
+
34570
+ /* Success state for copy button */
34571
+ .btn-success {
34572
+ background: linear-gradient(135deg, #34d399 0%, #10b981 100%);
34573
+ }
34574
+
34575
+ /* Text display area */
34576
+ .text-display {
34577
+ background: rgba(0, 0, 0, 0.3);
34578
+ border: 1px solid rgba(255, 255, 255, 0.08);
34579
+ border-radius: 10px;
34580
+ padding: 14px 16px;
34581
+ color: white;
34582
+ font-size: 14px;
34583
+ line-height: 1.5;
34584
+ max-height: 200px;
34585
+ overflow-y: auto;
34586
+ white-space: pre-wrap;
34587
+ word-break: break-word;
34588
+ }
34589
+
34590
+ /* Scrollbar styling */
34591
+ .text-display::-webkit-scrollbar {
34592
+ width: 6px;
34593
+ }
34594
+
34595
+ .text-display::-webkit-scrollbar-track {
34596
+ background: transparent;
34597
+ }
34598
+
34599
+ .text-display::-webkit-scrollbar-thumb {
34600
+ background: rgba(255, 255, 255, 0.15);
34601
+ border-radius: 3px;
34602
+ }
34603
+
34604
+ .text-display::-webkit-scrollbar-thumb:hover {
34605
+ background: rgba(255, 255, 255, 0.25);
34606
+ }
34607
+
34608
+ /* Instruction list styling */
34609
+ .instruction-list {
34610
+ list-style: none;
34611
+ padding: 0;
34612
+ margin: 0;
34613
+ }
34614
+
34615
+ .instruction-item {
34616
+ display: flex;
34617
+ align-items: flex-start;
34618
+ gap: 12px;
34619
+ padding: 12px 0;
34620
+ border-bottom: 1px solid rgba(255, 255, 255, 0.06);
34621
+ }
34622
+
34623
+ .instruction-item:last-child {
34624
+ border-bottom: none;
34625
+ }
34626
+
34627
+ .instruction-number {
34628
+ width: 24px;
34629
+ height: 24px;
34630
+ border-radius: 50%;
34631
+ background: linear-gradient(135deg, #10b981 0%, #059669 100%);
34632
+ color: white;
34633
+ font-size: 12px;
34634
+ font-weight: 700;
34635
+ display: flex;
34636
+ align-items: center;
34637
+ justify-content: center;
34638
+ flex-shrink: 0;
34639
+ }
34640
+
34641
+ .instruction-text {
34642
+ font-size: 14px;
34643
+ color: rgba(255, 255, 255, 0.85);
34644
+ line-height: 1.5;
34645
+ padding-top: 2px;
34646
+ }
34647
+
34648
+ /* Mobile adjustments */
34649
+ @media (max-width: 480px) {
34650
+ .modal-card {
34651
+ width: 95%;
34652
+ max-width: none;
34653
+ border-radius: 12px;
34654
+ }
34655
+
34656
+ .modal-header {
34657
+ padding: 14px 16px;
34658
+ }
34659
+
34660
+ .modal-body {
34661
+ padding: 16px;
34662
+ }
34663
+
34664
+ .modal-footer {
34665
+ padding: 14px 16px;
34666
+ }
34667
+
34668
+ .modal-title {
34669
+ font-size: 15px;
34670
+ }
34671
+ }
34672
+ `;
34673
+
34674
+ /**
34675
+ * Dictation output modal component
34676
+ * Displays transcribed text with copy functionality when no field is focused
34677
+ */
34678
+ let SpeechOSDictationOutputModal = class SpeechOSDictationOutputModal extends i$1 {
34679
+ constructor() {
34680
+ super(...arguments);
34681
+ this.open = false;
34682
+ this.text = "";
34683
+ this.copied = false;
34684
+ this.copyTimeout = null;
34685
+ }
34686
+ static { this.styles = [
34687
+ themeStyles,
34688
+ popupModalStyles,
34689
+ i$4 `
34690
+ .header-content {
34691
+ display: flex;
34692
+ align-items: center;
34693
+ gap: 12px;
34694
+ }
34695
+
34696
+ .logo-icon {
34697
+ width: 32px;
34698
+ height: 32px;
34699
+ border-radius: 8px;
34700
+ background: linear-gradient(135deg, #10b981 0%, #8b5cf6 100%);
34701
+ display: flex;
34702
+ align-items: center;
34703
+ justify-content: center;
34704
+ flex-shrink: 0;
34705
+ }
34706
+
34707
+ .logo-icon svg {
34708
+ width: 18px;
34709
+ height: 18px;
34710
+ color: white;
34711
+ }
34712
+
34713
+ .modal-title {
34714
+ background: linear-gradient(135deg, #34d399 0%, #a78bfa 100%);
34715
+ -webkit-background-clip: text;
34716
+ -webkit-text-fill-color: transparent;
34717
+ background-clip: text;
34718
+ }
34719
+
34720
+ .btn-primary {
34721
+ background: linear-gradient(135deg, #10b981 0%, #059669 100%);
34722
+ box-shadow: 0 4px 12px rgba(16, 185, 129, 0.3);
34723
+ border-radius: 999px;
34724
+ }
34725
+
34726
+ .btn-primary:hover {
34727
+ background: linear-gradient(135deg, #34d399 0%, #10b981 100%);
34728
+ transform: translateY(-2px);
34729
+ box-shadow: 0 6px 16px rgba(16, 185, 129, 0.4);
34730
+ }
34731
+
34732
+ .btn-primary:active {
34733
+ transform: translateY(0);
34734
+ }
34735
+
34736
+ .btn-success {
34737
+ background: linear-gradient(135deg, #34d399 0%, #10b981 100%);
34738
+ box-shadow: 0 4px 12px rgba(52, 211, 153, 0.3);
34739
+ border-radius: 999px;
34740
+ }
34741
+
34742
+ .btn-secondary {
34743
+ border-radius: 999px;
34744
+ }
34745
+
34746
+ .hint {
34747
+ display: flex;
34748
+ align-items: center;
34749
+ gap: 6px;
34750
+ margin-top: 12px;
34751
+ padding: 8px 12px;
34752
+ background: rgba(16, 185, 129, 0.08);
34753
+ border-radius: 8px;
34754
+ font-size: 12px;
34755
+ color: rgba(255, 255, 255, 0.6);
34756
+ }
34757
+
34758
+ .hint-icon {
34759
+ color: #10b981;
34760
+ flex-shrink: 0;
34761
+ }
34762
+ `,
34763
+ ]; }
34764
+ disconnectedCallback() {
34765
+ super.disconnectedCallback();
34766
+ if (this.copyTimeout) {
34767
+ clearTimeout(this.copyTimeout);
34768
+ this.copyTimeout = null;
34769
+ }
34770
+ }
34771
+ updated(changedProperties) {
34772
+ if (changedProperties.has("open")) {
34773
+ if (!this.open) {
34774
+ // Reset copied state when modal closes
34775
+ this.copied = false;
34776
+ if (this.copyTimeout) {
34777
+ clearTimeout(this.copyTimeout);
34778
+ this.copyTimeout = null;
34779
+ }
34780
+ }
34781
+ }
34782
+ }
34783
+ handleOverlayClick(e) {
34784
+ if (e.target === e.currentTarget) {
34785
+ this.close();
34786
+ }
34787
+ }
34788
+ handleClose() {
34789
+ this.close();
34790
+ }
34791
+ close() {
34792
+ this.dispatchEvent(new CustomEvent("modal-close", {
34793
+ bubbles: true,
34794
+ composed: true,
34795
+ }));
34796
+ }
34797
+ async handleCopy() {
34798
+ try {
34799
+ await navigator.clipboard.writeText(this.text);
34800
+ this.copied = true;
34801
+ // Reset copied state after 2 seconds
34802
+ if (this.copyTimeout) {
34803
+ clearTimeout(this.copyTimeout);
34804
+ }
34805
+ this.copyTimeout = window.setTimeout(() => {
34806
+ this.copied = false;
34807
+ this.copyTimeout = null;
34808
+ }, 2000);
34809
+ }
34810
+ catch (err) {
34811
+ console.error("[SpeechOS] Failed to copy text:", err);
34812
+ }
34813
+ }
34814
+ render() {
34815
+ return b `
34816
+ <div
34817
+ class="modal-overlay ${this.open ? "open" : ""}"
34818
+ @click="${this.handleOverlayClick}"
34819
+ >
34820
+ <div class="modal-card">
34821
+ <div class="modal-header">
34822
+ <div class="header-content">
34823
+ <div class="logo-icon">${micIcon(18)}</div>
34824
+ <h2 class="modal-title">Dictation Complete</h2>
34825
+ </div>
34826
+ <button
34827
+ class="close-button"
34828
+ @click="${this.handleClose}"
34829
+ aria-label="Close"
34830
+ >
34831
+ ${xIcon(16)}
34832
+ </button>
34833
+ </div>
34834
+
34835
+ <div class="modal-body">
34836
+ <div class="text-display">${this.text}</div>
34837
+ <div class="hint">
34838
+ <svg class="hint-icon" width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
34839
+ <circle cx="12" cy="12" r="10"/><path d="M12 16v-4M12 8h.01"/>
34840
+ </svg>
34841
+ <span>Tip: Focus a text field first to auto-insert next time</span>
34842
+ </div>
34843
+ </div>
34844
+
34845
+ <div class="modal-footer">
34846
+ <button
34847
+ class="btn ${this.copied ? "btn-success" : "btn-primary"}"
34848
+ @click="${this.handleCopy}"
34849
+ >
34850
+ ${this.copied ? checkIcon(16) : copyIcon(16)}
34851
+ ${this.copied ? "Copied!" : "Copy"}
34852
+ </button>
34853
+ <button class="btn btn-secondary" @click="${this.handleClose}">
34854
+ Done
34855
+ </button>
34856
+ </div>
34857
+ </div>
34858
+ </div>
34859
+ `;
34860
+ }
34861
+ };
34862
+ __decorate([
34863
+ n({ type: Boolean })
34864
+ ], SpeechOSDictationOutputModal.prototype, "open", void 0);
34865
+ __decorate([
34866
+ n({ type: String })
34867
+ ], SpeechOSDictationOutputModal.prototype, "text", void 0);
34868
+ __decorate([
34869
+ r()
34870
+ ], SpeechOSDictationOutputModal.prototype, "copied", void 0);
34871
+ SpeechOSDictationOutputModal = __decorate([
34872
+ t$1("speechos-dictation-output-modal")
34873
+ ], SpeechOSDictationOutputModal);
34874
+
34875
+ /**
34876
+ * Edit help modal component
34877
+ * Displays usage instructions for the edit feature when no text is selected
34878
+ */
34879
+ let SpeechOSEditHelpModal = class SpeechOSEditHelpModal extends i$1 {
34880
+ constructor() {
34881
+ super(...arguments);
34882
+ this.open = false;
34883
+ }
34884
+ static { this.styles = [
34885
+ themeStyles,
34886
+ popupModalStyles,
34887
+ i$4 `
34888
+ .header-content {
34889
+ display: flex;
34890
+ align-items: center;
34891
+ gap: 12px;
34892
+ }
34893
+
34894
+ .logo-icon {
34895
+ width: 32px;
34896
+ height: 32px;
34897
+ border-radius: 8px;
34898
+ background: linear-gradient(135deg, #8b5cf6 0%, #6366f1 100%);
34899
+ display: flex;
34900
+ align-items: center;
34901
+ justify-content: center;
34902
+ flex-shrink: 0;
34903
+ }
34904
+
34905
+ .logo-icon svg {
34906
+ width: 18px;
34907
+ height: 18px;
34908
+ color: white;
34909
+ }
34910
+
34911
+ .modal-title {
34912
+ background: linear-gradient(135deg, #a78bfa 0%, #818cf8 100%);
34913
+ -webkit-background-clip: text;
34914
+ -webkit-text-fill-color: transparent;
34915
+ background-clip: text;
34916
+ }
34917
+
34918
+ .instruction-number {
34919
+ background: linear-gradient(135deg, #8b5cf6 0%, #6366f1 100%);
34920
+ }
34921
+
34922
+ .btn-primary {
34923
+ background: linear-gradient(135deg, #8b5cf6 0%, #7c3aed 100%);
34924
+ box-shadow: 0 4px 12px rgba(139, 92, 246, 0.3);
34925
+ border-radius: 999px;
34926
+ }
34927
+
34928
+ .btn-primary:hover {
34929
+ background: linear-gradient(135deg, #a78bfa 0%, #8b5cf6 100%);
34930
+ transform: translateY(-2px);
34931
+ box-shadow: 0 6px 16px rgba(139, 92, 246, 0.4);
34932
+ }
34933
+
34934
+ .btn-primary:active {
34935
+ transform: translateY(0);
34936
+ }
34937
+ `,
34938
+ ]; }
34939
+ handleOverlayClick(e) {
34940
+ if (e.target === e.currentTarget) {
34941
+ this.close();
34942
+ }
34943
+ }
34944
+ handleClose() {
34945
+ this.close();
34946
+ }
34947
+ close() {
34948
+ this.dispatchEvent(new CustomEvent("modal-close", {
34949
+ bubbles: true,
34950
+ composed: true,
34951
+ }));
34952
+ }
34953
+ render() {
34954
+ return b `
34955
+ <div
34956
+ class="modal-overlay ${this.open ? "open" : ""}"
34957
+ @click="${this.handleOverlayClick}"
34958
+ >
34959
+ <div class="modal-card">
34960
+ <div class="modal-header">
34961
+ <div class="header-content">
34962
+ <div class="logo-icon">${editIcon(18)}</div>
34963
+ <h2 class="modal-title">How to Use Edit</h2>
34964
+ </div>
34965
+ <button
34966
+ class="close-button"
34967
+ @click="${this.handleClose}"
34968
+ aria-label="Close"
34969
+ >
34970
+ ${xIcon(16)}
34971
+ </button>
34972
+ </div>
34973
+
34974
+ <div class="modal-body">
34975
+ <ol class="instruction-list">
34976
+ <li class="instruction-item">
34977
+ <span class="instruction-number">1</span>
34978
+ <span class="instruction-text">
34979
+ Click on a text field to focus it, or select the text you want
34980
+ to edit
34981
+ </span>
34982
+ </li>
34983
+ <li class="instruction-item">
34984
+ <span class="instruction-number">2</span>
34985
+ <span class="instruction-text">
34986
+ Click the Edit button in the SpeechOS widget
34987
+ </span>
34988
+ </li>
34989
+ <li class="instruction-item">
34990
+ <span class="instruction-number">3</span>
34991
+ <span class="instruction-text">
34992
+ Speak your editing instructions (e.g., "make it more formal"
34993
+ or "fix the grammar")
34994
+ </span>
34995
+ </li>
34996
+ </ol>
34997
+ </div>
34998
+
34999
+ <div class="modal-footer">
35000
+ <button class="btn btn-primary" @click="${this.handleClose}">
35001
+ Got it
35002
+ </button>
35003
+ </div>
35004
+ </div>
35005
+ </div>
35006
+ `;
35007
+ }
35008
+ };
35009
+ __decorate([
35010
+ n({ type: Boolean })
35011
+ ], SpeechOSEditHelpModal.prototype, "open", void 0);
35012
+ SpeechOSEditHelpModal = __decorate([
35013
+ t$1("speechos-edit-help-modal")
35014
+ ], SpeechOSEditHelpModal);
35015
+
35016
+ /**
35017
+ * Main widget container component
35018
+ * Composes mic button and action bubbles with state management
35019
+ */
35020
+ var SpeechOSWidget_1;
35021
+ /**
35022
+ * Minimum duration to show the connecting animation (in milliseconds).
35023
+ * Since mic capture starts almost instantly, we enforce a minimum animation
35024
+ * duration so users can see the visual feedback before transitioning to recording.
35025
+ */
35026
+ const MIN_CONNECTING_ANIMATION_MS = 200;
35027
+ let SpeechOSWidget = class SpeechOSWidget extends i$1 {
35028
+ constructor() {
35029
+ super(...arguments);
35030
+ this.widgetState = state.getState();
35031
+ this.settingsOpen = false;
35032
+ this.dictationModalOpen = false;
35033
+ this.dictationModalText = "";
35034
+ this.editHelpModalOpen = false;
35035
+ this.commandFeedback = null;
35036
+ this.dictationTargetElement = null;
35037
+ this.editTargetElement = null;
35038
+ this.dictationCursorStart = null;
35039
+ this.dictationCursorEnd = null;
35040
+ this.editSelectionStart = null;
35041
+ this.editSelectionEnd = null;
35042
+ this.editSelectedText = "";
35043
+ this.boundClickOutsideHandler = null;
35044
+ this.modalElement = null;
35045
+ this.dictationModalElement = null;
35046
+ this.editHelpModalElement = null;
35047
+ this.commandFeedbackTimeout = null;
35048
+ this.customPosition = null;
35049
+ this.isDragging = false;
35050
+ this.dragStartPos = null;
35051
+ this.dragOffset = { x: 0, y: 0 };
35052
+ this.boundDragMove = null;
35053
+ this.boundDragEnd = null;
35054
+ this.suppressNextClick = false;
35055
+ this.boundViewportResizeHandler = null;
35056
+ this.boundScrollHandler = null;
35057
+ }
35058
+ static { SpeechOSWidget_1 = this; }
35059
+ static { this.styles = [
35060
+ themeStyles,
35061
+ animations,
35062
+ i$4 `
35063
+ :host {
35064
+ position: fixed;
35065
+ bottom: var(--speechos-spacing-md); /* 12px - same as spacing above */
35066
+ z-index: var(--speechos-z-base);
35067
+ pointer-events: none;
35068
+ }
35069
+
35070
+ :host {
35071
+ left: 50%;
35072
+ transform: translateX(-50%);
35073
+ }
34368
35074
 
34369
35075
  :host(.custom-position) {
34370
35076
  right: unset;
@@ -34432,6 +35138,18 @@
34432
35138
  this.settingsOpen = false;
34433
35139
  });
34434
35140
  document.body.appendChild(this.modalElement);
35141
+ // Mount dictation output modal
35142
+ this.dictationModalElement = document.createElement("speechos-dictation-output-modal");
35143
+ this.dictationModalElement.addEventListener("modal-close", () => {
35144
+ this.dictationModalOpen = false;
35145
+ });
35146
+ document.body.appendChild(this.dictationModalElement);
35147
+ // Mount edit help modal
35148
+ this.editHelpModalElement = document.createElement("speechos-edit-help-modal");
35149
+ this.editHelpModalElement.addEventListener("modal-close", () => {
35150
+ this.editHelpModalOpen = false;
35151
+ });
35152
+ document.body.appendChild(this.editHelpModalElement);
34435
35153
  this.stateUnsubscribe = state.subscribe((newState) => {
34436
35154
  if (!newState.isVisible || !newState.isExpanded) {
34437
35155
  this.settingsOpen = false;
@@ -34471,6 +35189,18 @@
34471
35189
  this.modalElement.remove();
34472
35190
  this.modalElement = null;
34473
35191
  }
35192
+ if (this.dictationModalElement) {
35193
+ this.dictationModalElement.remove();
35194
+ this.dictationModalElement = null;
35195
+ }
35196
+ if (this.editHelpModalElement) {
35197
+ this.editHelpModalElement.remove();
35198
+ this.editHelpModalElement = null;
35199
+ }
35200
+ if (this.commandFeedbackTimeout) {
35201
+ clearTimeout(this.commandFeedbackTimeout);
35202
+ this.commandFeedbackTimeout = null;
35203
+ }
34474
35204
  if (this.stateUnsubscribe) {
34475
35205
  this.stateUnsubscribe();
34476
35206
  }
@@ -34502,6 +35232,15 @@
34502
35232
  if (changedProperties.has("settingsOpen") && this.modalElement) {
34503
35233
  this.modalElement.open = this.settingsOpen;
34504
35234
  }
35235
+ if (changedProperties.has("dictationModalOpen") && this.dictationModalElement) {
35236
+ this.dictationModalElement.open = this.dictationModalOpen;
35237
+ }
35238
+ if (changedProperties.has("dictationModalText") && this.dictationModalElement) {
35239
+ this.dictationModalElement.text = this.dictationModalText;
35240
+ }
35241
+ if (changedProperties.has("editHelpModalOpen") && this.editHelpModalElement) {
35242
+ this.editHelpModalElement.open = this.editHelpModalOpen;
35243
+ }
34505
35244
  }
34506
35245
  handleClickOutside(event) {
34507
35246
  const target = event.target;
@@ -34539,7 +35278,10 @@
34539
35278
  return;
34540
35279
  }
34541
35280
  if (!clickedInWidget) {
34542
- state.hide();
35281
+ // Don't hide if alwaysVisible is enabled
35282
+ if (!isAlwaysVisible()) {
35283
+ state.hide();
35284
+ }
34543
35285
  }
34544
35286
  }
34545
35287
  isFormField(element) {
@@ -34660,6 +35402,8 @@
34660
35402
  return;
34661
35403
  }
34662
35404
  if (this.widgetState.recordingState === "idle") {
35405
+ // Clear command feedback on any mic click
35406
+ this.clearCommandFeedback();
34663
35407
  // If we're expanding, prefetch the token to reduce latency when user selects an action
34664
35408
  if (!this.widgetState.isExpanded) {
34665
35409
  // Fire and forget - we don't need to wait for this (LiveKit only)
@@ -34684,12 +35428,19 @@
34684
35428
  }
34685
35429
  else {
34686
35430
  state.stopRecording();
34687
- getConfig();
34688
35431
  const backend = getBackend();
34689
35432
  try {
34690
35433
  const transcription = await this.withMinDisplayTime(backend.stopVoiceSession(), 300);
34691
35434
  if (transcription) {
34692
- this.insertTranscription(transcription);
35435
+ // Check if we have a target element to insert into
35436
+ if (this.dictationTargetElement) {
35437
+ this.insertTranscription(transcription);
35438
+ }
35439
+ else {
35440
+ // No target element - show dictation output modal
35441
+ this.dictationModalText = transcription;
35442
+ this.dictationModalOpen = true;
35443
+ }
34693
35444
  transcriptStore.saveTranscript(transcription, "dictate");
34694
35445
  }
34695
35446
  state.completeRecording();
@@ -34737,6 +35488,7 @@
34737
35488
  }
34738
35489
  }
34739
35490
  handleCloseWidget() {
35491
+ this.clearCommandFeedback();
34740
35492
  state.hide();
34741
35493
  }
34742
35494
  handleSettingsClick() {
@@ -34863,11 +35615,20 @@
34863
35615
  }
34864
35616
  handleActionSelect(event) {
34865
35617
  const { action } = event.detail;
35618
+ // Clear any existing command feedback when a new action is selected
35619
+ this.clearCommandFeedback();
34866
35620
  state.setActiveAction(action);
34867
35621
  if (action === "dictate") {
34868
35622
  this.startDictation();
34869
35623
  }
34870
35624
  else if (action === "edit") {
35625
+ // Check if there's a focused element before starting edit
35626
+ if (!this.widgetState.focusedElement) {
35627
+ // No focused element - show edit help modal
35628
+ this.editHelpModalOpen = true;
35629
+ state.setActiveAction(null);
35630
+ return;
35631
+ }
34871
35632
  this.startEdit();
34872
35633
  }
34873
35634
  else if (action === "command") {
@@ -35074,6 +35835,10 @@
35074
35835
  // Note: command:complete event is already emitted by the backend
35075
35836
  // when the command_result message is received, so we don't emit here
35076
35837
  state.completeRecording();
35838
+ // Keep widget visible but collapsed (just mic button, no action bubbles)
35839
+ state.setState({ isExpanded: false });
35840
+ // Show command feedback
35841
+ this.showCommandFeedback(result ? "success" : "none");
35077
35842
  backend.disconnect().catch(() => { });
35078
35843
  // Start auto-refresh to keep token fresh for subsequent commands (LiveKit only)
35079
35844
  backend.startAutoRefresh?.();
@@ -35086,6 +35851,25 @@
35086
35851
  }
35087
35852
  }
35088
35853
  }
35854
+ showCommandFeedback(feedback) {
35855
+ this.commandFeedback = feedback;
35856
+ // Clear any existing timeout
35857
+ if (this.commandFeedbackTimeout) {
35858
+ clearTimeout(this.commandFeedbackTimeout);
35859
+ }
35860
+ // Auto-dismiss after 4 seconds
35861
+ this.commandFeedbackTimeout = window.setTimeout(() => {
35862
+ this.commandFeedback = null;
35863
+ this.commandFeedbackTimeout = null;
35864
+ }, 4000);
35865
+ }
35866
+ clearCommandFeedback() {
35867
+ if (this.commandFeedbackTimeout) {
35868
+ clearTimeout(this.commandFeedbackTimeout);
35869
+ this.commandFeedbackTimeout = null;
35870
+ }
35871
+ this.commandFeedback = null;
35872
+ }
35089
35873
  supportsSelection(element) {
35090
35874
  if (element.tagName.toLowerCase() === "textarea") {
35091
35875
  return true;
@@ -35203,6 +35987,7 @@
35203
35987
  activeAction="${this.widgetState.activeAction || ""}"
35204
35988
  editPreviewText="${this.editSelectedText}"
35205
35989
  errorMessage="${this.widgetState.errorMessage || ""}"
35990
+ .commandFeedback="${this.commandFeedback}"
35206
35991
  @mic-click="${this.handleMicClick}"
35207
35992
  @stop-recording="${this.handleStopRecording}"
35208
35993
  @cancel-operation="${this.handleCancelOperation}"
@@ -35220,6 +36005,18 @@
35220
36005
  __decorate([
35221
36006
  r()
35222
36007
  ], SpeechOSWidget.prototype, "settingsOpen", void 0);
36008
+ __decorate([
36009
+ r()
36010
+ ], SpeechOSWidget.prototype, "dictationModalOpen", void 0);
36011
+ __decorate([
36012
+ r()
36013
+ ], SpeechOSWidget.prototype, "dictationModalText", void 0);
36014
+ __decorate([
36015
+ r()
36016
+ ], SpeechOSWidget.prototype, "editHelpModalOpen", void 0);
36017
+ __decorate([
36018
+ r()
36019
+ ], SpeechOSWidget.prototype, "commandFeedback", void 0);
35223
36020
  SpeechOSWidget = SpeechOSWidget_1 = __decorate([
35224
36021
  t$1("speechos-widget")
35225
36022
  ], SpeechOSWidget);
@@ -35300,6 +36097,10 @@
35300
36097
  }
35301
36098
  // Create and mount widget
35302
36099
  this.mountWidget();
36100
+ // If alwaysVisible is enabled, show the widget immediately
36101
+ if (isAlwaysVisible()) {
36102
+ state.show();
36103
+ }
35303
36104
  this.isInitialized = true;
35304
36105
  // Log initialization in debug mode
35305
36106
  if (finalConfig.debug) {