@speechos/client 0.2.4 → 0.2.6
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.cjs +320 -41
- package/dist/index.cjs.map +1 -1
- package/dist/index.iife.js +324 -41
- package/dist/index.iife.js.map +1 -1
- package/dist/index.iife.min.js +165 -67
- package/dist/index.iife.min.js.map +1 -1
- package/dist/index.js +321 -42
- package/dist/index.js.map +1 -1
- package/dist/ui/mic-button.d.ts +4 -2
- package/dist/ui/mic-button.d.ts.map +1 -1
- package/dist/ui/mic-button.test.d.ts +1 -1
- package/dist/ui/widget.d.ts +27 -4
- package/dist/ui/widget.d.ts.map +1 -1
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { state, events,
|
|
1
|
+
import { state, events, getConfig, getBackend, setConfig, updateUserId } from '@speechos/core';
|
|
2
2
|
export { DEFAULT_HOST, events, getConfig, livekit, resetConfig, setConfig, state } from '@speechos/core';
|
|
3
3
|
|
|
4
4
|
/**
|
|
@@ -1757,7 +1757,8 @@ let SpeechOSMicButton = class SpeechOSMicButton extends i$1 {
|
|
|
1757
1757
|
this.activeAction = null;
|
|
1758
1758
|
this.editPreviewText = "";
|
|
1759
1759
|
this.errorMessage = null;
|
|
1760
|
-
this.
|
|
1760
|
+
this.actionFeedback = null;
|
|
1761
|
+
this.showNoAudioWarning = false;
|
|
1761
1762
|
}
|
|
1762
1763
|
static { this.styles = [
|
|
1763
1764
|
themeStyles,
|
|
@@ -2347,8 +2348,9 @@ let SpeechOSMicButton = class SpeechOSMicButton extends i$1 {
|
|
|
2347
2348
|
background-position: center;
|
|
2348
2349
|
}
|
|
2349
2350
|
|
|
2350
|
-
/* Command feedback badge - no match state (neutral gray) */
|
|
2351
|
-
.status-label.command-none
|
|
2351
|
+
/* Command/edit feedback badge - no match/empty state (neutral gray) */
|
|
2352
|
+
.status-label.command-none,
|
|
2353
|
+
.status-label.edit-empty {
|
|
2352
2354
|
background: #4b5563;
|
|
2353
2355
|
box-shadow: 0 4px 12px rgba(75, 85, 99, 0.3);
|
|
2354
2356
|
animation: command-feedback-in 0.3s cubic-bezier(0.34, 1.56, 0.64, 1)
|
|
@@ -2505,6 +2507,60 @@ let SpeechOSMicButton = class SpeechOSMicButton extends i$1 {
|
|
|
2505
2507
|
border-color: rgba(255, 255, 255, 0.5);
|
|
2506
2508
|
}
|
|
2507
2509
|
|
|
2510
|
+
/* No audio warning banner */
|
|
2511
|
+
.no-audio-warning {
|
|
2512
|
+
position: absolute;
|
|
2513
|
+
bottom: 120px; /* Above button and waveform visualizer */
|
|
2514
|
+
left: 50%;
|
|
2515
|
+
transform: translateX(-50%) translateY(8px);
|
|
2516
|
+
display: flex;
|
|
2517
|
+
align-items: center;
|
|
2518
|
+
gap: 8px;
|
|
2519
|
+
padding: 10px 14px;
|
|
2520
|
+
border-radius: 12px;
|
|
2521
|
+
background: linear-gradient(135deg, #f59e0b 0%, #d97706 100%);
|
|
2522
|
+
box-shadow: 0 4px 12px rgba(245, 158, 11, 0.3);
|
|
2523
|
+
transition: all 0.2s cubic-bezier(0.34, 1.56, 0.64, 1);
|
|
2524
|
+
pointer-events: none;
|
|
2525
|
+
opacity: 0;
|
|
2526
|
+
white-space: nowrap;
|
|
2527
|
+
}
|
|
2528
|
+
|
|
2529
|
+
.no-audio-warning.visible {
|
|
2530
|
+
opacity: 1;
|
|
2531
|
+
transform: translateX(-50%) translateY(0);
|
|
2532
|
+
pointer-events: auto;
|
|
2533
|
+
}
|
|
2534
|
+
|
|
2535
|
+
.no-audio-warning .warning-icon {
|
|
2536
|
+
flex-shrink: 0;
|
|
2537
|
+
color: white;
|
|
2538
|
+
}
|
|
2539
|
+
|
|
2540
|
+
.no-audio-warning .warning-text {
|
|
2541
|
+
font-size: 13px;
|
|
2542
|
+
font-weight: 500;
|
|
2543
|
+
color: white;
|
|
2544
|
+
}
|
|
2545
|
+
|
|
2546
|
+
.no-audio-warning .settings-link {
|
|
2547
|
+
background: rgba(255, 255, 255, 0.2);
|
|
2548
|
+
border: 1px solid rgba(255, 255, 255, 0.3);
|
|
2549
|
+
border-radius: 6px;
|
|
2550
|
+
padding: 4px 10px;
|
|
2551
|
+
font-size: 12px;
|
|
2552
|
+
font-weight: 600;
|
|
2553
|
+
color: white;
|
|
2554
|
+
cursor: pointer;
|
|
2555
|
+
transition: all 0.15s;
|
|
2556
|
+
white-space: nowrap;
|
|
2557
|
+
}
|
|
2558
|
+
|
|
2559
|
+
.no-audio-warning .settings-link:hover {
|
|
2560
|
+
background: rgba(255, 255, 255, 0.3);
|
|
2561
|
+
border-color: rgba(255, 255, 255, 0.5);
|
|
2562
|
+
}
|
|
2563
|
+
|
|
2508
2564
|
/* Mobile styles - 30% larger */
|
|
2509
2565
|
@media (max-width: 768px) and (hover: none) {
|
|
2510
2566
|
.mic-button {
|
|
@@ -2634,6 +2690,21 @@ let SpeechOSMicButton = class SpeechOSMicButton extends i$1 {
|
|
|
2634
2690
|
padding: 8px 14px;
|
|
2635
2691
|
font-size: 14px;
|
|
2636
2692
|
}
|
|
2693
|
+
|
|
2694
|
+
.no-audio-warning {
|
|
2695
|
+
padding: 12px 16px;
|
|
2696
|
+
gap: 10px;
|
|
2697
|
+
bottom: 145px; /* Above button and waveform on mobile */
|
|
2698
|
+
}
|
|
2699
|
+
|
|
2700
|
+
.no-audio-warning .warning-text {
|
|
2701
|
+
font-size: 15px;
|
|
2702
|
+
}
|
|
2703
|
+
|
|
2704
|
+
.no-audio-warning .settings-link {
|
|
2705
|
+
padding: 6px 12px;
|
|
2706
|
+
font-size: 14px;
|
|
2707
|
+
}
|
|
2637
2708
|
}
|
|
2638
2709
|
`,
|
|
2639
2710
|
]; }
|
|
@@ -2691,6 +2762,14 @@ let SpeechOSMicButton = class SpeechOSMicButton extends i$1 {
|
|
|
2691
2762
|
composed: true,
|
|
2692
2763
|
}));
|
|
2693
2764
|
}
|
|
2765
|
+
handleOpenSettings(e) {
|
|
2766
|
+
e.stopPropagation();
|
|
2767
|
+
e.preventDefault();
|
|
2768
|
+
this.dispatchEvent(new CustomEvent("open-settings", {
|
|
2769
|
+
bubbles: true,
|
|
2770
|
+
composed: true,
|
|
2771
|
+
}));
|
|
2772
|
+
}
|
|
2694
2773
|
getButtonClass() {
|
|
2695
2774
|
const classes = ["mic-button"];
|
|
2696
2775
|
if (this.expanded && this.recordingState === "idle") {
|
|
@@ -2775,13 +2854,16 @@ let SpeechOSMicButton = class SpeechOSMicButton extends i$1 {
|
|
|
2775
2854
|
}
|
|
2776
2855
|
return this.recordingState;
|
|
2777
2856
|
}
|
|
2778
|
-
|
|
2779
|
-
if (this.
|
|
2857
|
+
getActionFeedbackLabel() {
|
|
2858
|
+
if (this.actionFeedback === "command-success") {
|
|
2780
2859
|
return "Got it!";
|
|
2781
2860
|
}
|
|
2782
|
-
if (this.
|
|
2861
|
+
if (this.actionFeedback === "command-none") {
|
|
2783
2862
|
return "No command matched";
|
|
2784
2863
|
}
|
|
2864
|
+
if (this.actionFeedback === "edit-empty") {
|
|
2865
|
+
return "Couldn't understand edit";
|
|
2866
|
+
}
|
|
2785
2867
|
return "";
|
|
2786
2868
|
}
|
|
2787
2869
|
render() {
|
|
@@ -2791,9 +2873,9 @@ let SpeechOSMicButton = class SpeechOSMicButton extends i$1 {
|
|
|
2791
2873
|
const showSiriEdit = this.recordingState === "processing" && this.activeAction === "edit";
|
|
2792
2874
|
const statusLabel = this.getStatusLabel();
|
|
2793
2875
|
const showVisualizer = this.shouldShowVisualizer();
|
|
2794
|
-
// Show status label during recording (either visualizer or edit text) OR
|
|
2795
|
-
const
|
|
2796
|
-
const showStatus = this.recordingState === "recording" ||
|
|
2876
|
+
// Show status label during recording (either visualizer or edit text) OR action feedback
|
|
2877
|
+
const showActionFeedback = this.recordingState === "idle" && this.actionFeedback !== null;
|
|
2878
|
+
const showStatus = this.recordingState === "recording" || showActionFeedback;
|
|
2797
2879
|
const showCancel = this.recordingState === "connecting" ||
|
|
2798
2880
|
this.recordingState === "recording" ||
|
|
2799
2881
|
this.recordingState === "processing";
|
|
@@ -2832,6 +2914,35 @@ let SpeechOSMicButton = class SpeechOSMicButton extends i$1 {
|
|
|
2832
2914
|
`
|
|
2833
2915
|
: ""}
|
|
2834
2916
|
|
|
2917
|
+
<div
|
|
2918
|
+
class="no-audio-warning ${this.showNoAudioWarning &&
|
|
2919
|
+
this.recordingState === "recording"
|
|
2920
|
+
? "visible"
|
|
2921
|
+
: ""}"
|
|
2922
|
+
>
|
|
2923
|
+
<svg
|
|
2924
|
+
class="warning-icon"
|
|
2925
|
+
width="16"
|
|
2926
|
+
height="16"
|
|
2927
|
+
viewBox="0 0 24 24"
|
|
2928
|
+
fill="none"
|
|
2929
|
+
stroke="currentColor"
|
|
2930
|
+
stroke-width="2"
|
|
2931
|
+
stroke-linecap="round"
|
|
2932
|
+
stroke-linejoin="round"
|
|
2933
|
+
>
|
|
2934
|
+
<path
|
|
2935
|
+
d="M10.29 3.86L1.82 18a2 2 0 0 0 1.71 3h16.94a2 2 0 0 0 1.71-3L13.71 3.86a2 2 0 0 0-3.42 0z"
|
|
2936
|
+
/>
|
|
2937
|
+
<line x1="12" y1="9" x2="12" y2="13" />
|
|
2938
|
+
<line x1="12" y1="17" x2="12.01" y2="17" />
|
|
2939
|
+
</svg>
|
|
2940
|
+
<span class="warning-text">We're not hearing anything</span>
|
|
2941
|
+
<button class="settings-link" @click="${this.handleOpenSettings}">
|
|
2942
|
+
Check Settings
|
|
2943
|
+
</button>
|
|
2944
|
+
</div>
|
|
2945
|
+
|
|
2835
2946
|
<button
|
|
2836
2947
|
class="${this.getButtonClass()}"
|
|
2837
2948
|
@click="${this.handleClick}"
|
|
@@ -2844,14 +2955,14 @@ let SpeechOSMicButton = class SpeechOSMicButton extends i$1 {
|
|
|
2844
2955
|
</button>
|
|
2845
2956
|
|
|
2846
2957
|
<span
|
|
2847
|
-
class="status-label ${showStatus ? "visible" : ""} ${
|
|
2848
|
-
?
|
|
2958
|
+
class="status-label ${showStatus ? "visible" : ""} ${showActionFeedback
|
|
2959
|
+
? this.actionFeedback
|
|
2849
2960
|
: showVisualizer
|
|
2850
2961
|
? "visualizer"
|
|
2851
2962
|
: this.getStatusClass()}"
|
|
2852
2963
|
>
|
|
2853
|
-
${
|
|
2854
|
-
? this.
|
|
2964
|
+
${showActionFeedback
|
|
2965
|
+
? this.getActionFeedbackLabel()
|
|
2855
2966
|
: showVisualizer
|
|
2856
2967
|
? b `<speechos-audio-visualizer
|
|
2857
2968
|
?active="${showVisualizer}"
|
|
@@ -2899,7 +3010,10 @@ __decorate([
|
|
|
2899
3010
|
], SpeechOSMicButton.prototype, "errorMessage", void 0);
|
|
2900
3011
|
__decorate([
|
|
2901
3012
|
n({ type: String })
|
|
2902
|
-
], SpeechOSMicButton.prototype, "
|
|
3013
|
+
], SpeechOSMicButton.prototype, "actionFeedback", void 0);
|
|
3014
|
+
__decorate([
|
|
3015
|
+
n({ type: Boolean })
|
|
3016
|
+
], SpeechOSMicButton.prototype, "showNoAudioWarning", void 0);
|
|
2903
3017
|
SpeechOSMicButton = __decorate([
|
|
2904
3018
|
t$1("speechos-mic-button")
|
|
2905
3019
|
], SpeechOSMicButton);
|
|
@@ -6273,15 +6387,27 @@ var SpeechOSWidget_1;
|
|
|
6273
6387
|
* duration so users can see the visual feedback before transitioning to recording.
|
|
6274
6388
|
*/
|
|
6275
6389
|
const MIN_CONNECTING_ANIMATION_MS = 200;
|
|
6390
|
+
/**
|
|
6391
|
+
* Time to wait for a transcription event before showing the "no audio" warning (in milliseconds).
|
|
6392
|
+
* If no transcription:interim event is received within this time during recording,
|
|
6393
|
+
* it indicates the server isn't receiving/processing audio.
|
|
6394
|
+
*/
|
|
6395
|
+
const NO_AUDIO_WARNING_TIMEOUT_MS = 5000;
|
|
6396
|
+
/**
|
|
6397
|
+
* Number of consecutive actions with empty results before showing warning on next action.
|
|
6398
|
+
*/
|
|
6399
|
+
const CONSECUTIVE_NO_AUDIO_THRESHOLD = 2;
|
|
6276
6400
|
let SpeechOSWidget = class SpeechOSWidget extends i$1 {
|
|
6277
6401
|
constructor() {
|
|
6278
6402
|
super(...arguments);
|
|
6279
6403
|
this.widgetState = state.getState();
|
|
6280
6404
|
this.settingsOpen = false;
|
|
6405
|
+
this.settingsOpenFromWarning = false;
|
|
6281
6406
|
this.dictationModalOpen = false;
|
|
6282
6407
|
this.dictationModalText = "";
|
|
6283
6408
|
this.editHelpModalOpen = false;
|
|
6284
|
-
this.
|
|
6409
|
+
this.actionFeedback = null;
|
|
6410
|
+
this.showNoAudioWarning = false;
|
|
6285
6411
|
this.dictationTargetElement = null;
|
|
6286
6412
|
this.editTargetElement = null;
|
|
6287
6413
|
this.dictationCursorStart = null;
|
|
@@ -6293,7 +6419,7 @@ let SpeechOSWidget = class SpeechOSWidget extends i$1 {
|
|
|
6293
6419
|
this.modalElement = null;
|
|
6294
6420
|
this.dictationModalElement = null;
|
|
6295
6421
|
this.editHelpModalElement = null;
|
|
6296
|
-
this.
|
|
6422
|
+
this.actionFeedbackTimeout = null;
|
|
6297
6423
|
this.customPosition = null;
|
|
6298
6424
|
this.isDragging = false;
|
|
6299
6425
|
this.dragStartPos = null;
|
|
@@ -6303,6 +6429,11 @@ let SpeechOSWidget = class SpeechOSWidget extends i$1 {
|
|
|
6303
6429
|
this.suppressNextClick = false;
|
|
6304
6430
|
this.boundViewportResizeHandler = null;
|
|
6305
6431
|
this.boundScrollHandler = null;
|
|
6432
|
+
// No-audio warning state tracking
|
|
6433
|
+
this.consecutiveNoAudioActions = 0;
|
|
6434
|
+
this.transcriptionReceived = false;
|
|
6435
|
+
this.noAudioWarningTimeout = null;
|
|
6436
|
+
this.transcriptionInterimUnsubscribe = null;
|
|
6306
6437
|
}
|
|
6307
6438
|
static { SpeechOSWidget_1 = this; }
|
|
6308
6439
|
static { this.styles = [
|
|
@@ -6385,6 +6516,7 @@ let SpeechOSWidget = class SpeechOSWidget extends i$1 {
|
|
|
6385
6516
|
this.modalElement = document.createElement("speechos-settings-modal");
|
|
6386
6517
|
this.modalElement.addEventListener("modal-close", () => {
|
|
6387
6518
|
this.settingsOpen = false;
|
|
6519
|
+
this.settingsOpenFromWarning = false;
|
|
6388
6520
|
});
|
|
6389
6521
|
document.body.appendChild(this.modalElement);
|
|
6390
6522
|
// Mount dictation output modal
|
|
@@ -6400,7 +6532,17 @@ let SpeechOSWidget = class SpeechOSWidget extends i$1 {
|
|
|
6400
6532
|
});
|
|
6401
6533
|
document.body.appendChild(this.editHelpModalElement);
|
|
6402
6534
|
this.stateUnsubscribe = state.subscribe((newState) => {
|
|
6403
|
-
if (!newState.isVisible
|
|
6535
|
+
if (!newState.isVisible) {
|
|
6536
|
+
if (getConfig().debug && this.settingsOpen) {
|
|
6537
|
+
console.log("[SpeechOS] Closing settings modal: widget hidden");
|
|
6538
|
+
}
|
|
6539
|
+
this.settingsOpen = false;
|
|
6540
|
+
this.settingsOpenFromWarning = false;
|
|
6541
|
+
}
|
|
6542
|
+
else if (!newState.isExpanded && !this.settingsOpenFromWarning) {
|
|
6543
|
+
if (getConfig().debug && this.settingsOpen) {
|
|
6544
|
+
console.log("[SpeechOS] Closing settings modal: widget collapsed");
|
|
6545
|
+
}
|
|
6404
6546
|
this.settingsOpen = false;
|
|
6405
6547
|
}
|
|
6406
6548
|
// Clear custom position when focused element changes (re-anchor to new element)
|
|
@@ -6446,9 +6588,9 @@ let SpeechOSWidget = class SpeechOSWidget extends i$1 {
|
|
|
6446
6588
|
this.editHelpModalElement.remove();
|
|
6447
6589
|
this.editHelpModalElement = null;
|
|
6448
6590
|
}
|
|
6449
|
-
if (this.
|
|
6450
|
-
clearTimeout(this.
|
|
6451
|
-
this.
|
|
6591
|
+
if (this.actionFeedbackTimeout) {
|
|
6592
|
+
clearTimeout(this.actionFeedbackTimeout);
|
|
6593
|
+
this.actionFeedbackTimeout = null;
|
|
6452
6594
|
}
|
|
6453
6595
|
if (this.stateUnsubscribe) {
|
|
6454
6596
|
this.stateUnsubscribe();
|
|
@@ -6476,6 +6618,7 @@ let SpeechOSWidget = class SpeechOSWidget extends i$1 {
|
|
|
6476
6618
|
window.removeEventListener("scroll", this.boundScrollHandler);
|
|
6477
6619
|
this.boundScrollHandler = null;
|
|
6478
6620
|
}
|
|
6621
|
+
this.cleanupNoAudioWarningTracking();
|
|
6479
6622
|
}
|
|
6480
6623
|
updated(changedProperties) {
|
|
6481
6624
|
if (changedProperties.has("settingsOpen") && this.modalElement) {
|
|
@@ -6653,7 +6796,7 @@ let SpeechOSWidget = class SpeechOSWidget extends i$1 {
|
|
|
6653
6796
|
}
|
|
6654
6797
|
if (this.widgetState.recordingState === "idle") {
|
|
6655
6798
|
// Clear command feedback on any mic click
|
|
6656
|
-
this.
|
|
6799
|
+
this.clearActionFeedback();
|
|
6657
6800
|
// If we're expanding, prefetch the token to reduce latency when user selects an action
|
|
6658
6801
|
if (!this.widgetState.isExpanded) {
|
|
6659
6802
|
// Fire and forget - we don't need to wait for this (LiveKit only)
|
|
@@ -6674,6 +6817,8 @@ let SpeechOSWidget = class SpeechOSWidget extends i$1 {
|
|
|
6674
6817
|
}
|
|
6675
6818
|
}
|
|
6676
6819
|
async handleStopRecording() {
|
|
6820
|
+
// Clean up no-audio warning tracking
|
|
6821
|
+
this.cleanupNoAudioWarningTracking();
|
|
6677
6822
|
if (this.widgetState.activeAction === "edit") {
|
|
6678
6823
|
await this.handleStopEdit();
|
|
6679
6824
|
}
|
|
@@ -6685,6 +6830,8 @@ let SpeechOSWidget = class SpeechOSWidget extends i$1 {
|
|
|
6685
6830
|
const backend = getBackend();
|
|
6686
6831
|
try {
|
|
6687
6832
|
const transcription = await this.withMinDisplayTime(backend.stopVoiceSession(), 300);
|
|
6833
|
+
// Track result for consecutive failure detection
|
|
6834
|
+
this.trackActionResult(!!transcription);
|
|
6688
6835
|
if (transcription) {
|
|
6689
6836
|
// Check if we have a target element to insert into
|
|
6690
6837
|
if (this.dictationTargetElement) {
|
|
@@ -6704,6 +6851,8 @@ let SpeechOSWidget = class SpeechOSWidget extends i$1 {
|
|
|
6704
6851
|
backend.startAutoRefresh?.();
|
|
6705
6852
|
}
|
|
6706
6853
|
catch (error) {
|
|
6854
|
+
// Track as failed result
|
|
6855
|
+
this.trackActionResult(false);
|
|
6707
6856
|
const errorMessage = error instanceof Error ? error.message : "Failed to transcribe audio";
|
|
6708
6857
|
if (errorMessage !== "Disconnected") {
|
|
6709
6858
|
state.setError(errorMessage);
|
|
@@ -6713,6 +6862,8 @@ let SpeechOSWidget = class SpeechOSWidget extends i$1 {
|
|
|
6713
6862
|
}
|
|
6714
6863
|
}
|
|
6715
6864
|
async handleCancelOperation() {
|
|
6865
|
+
// Clean up no-audio warning tracking
|
|
6866
|
+
this.cleanupNoAudioWarningTracking();
|
|
6716
6867
|
await getBackend().disconnect();
|
|
6717
6868
|
if (this.widgetState.recordingState === "error") {
|
|
6718
6869
|
state.clearError();
|
|
@@ -6742,7 +6893,7 @@ let SpeechOSWidget = class SpeechOSWidget extends i$1 {
|
|
|
6742
6893
|
}
|
|
6743
6894
|
}
|
|
6744
6895
|
handleCloseWidget() {
|
|
6745
|
-
this.
|
|
6896
|
+
this.clearActionFeedback();
|
|
6746
6897
|
getBackend().stopAutoRefresh?.();
|
|
6747
6898
|
state.hide();
|
|
6748
6899
|
}
|
|
@@ -6871,7 +7022,7 @@ let SpeechOSWidget = class SpeechOSWidget extends i$1 {
|
|
|
6871
7022
|
handleActionSelect(event) {
|
|
6872
7023
|
const { action } = event.detail;
|
|
6873
7024
|
// Clear any existing command feedback when a new action is selected
|
|
6874
|
-
this.
|
|
7025
|
+
this.clearActionFeedback();
|
|
6875
7026
|
state.setActiveAction(action);
|
|
6876
7027
|
if (action === "dictate") {
|
|
6877
7028
|
this.startDictation();
|
|
@@ -6934,10 +7085,12 @@ let SpeechOSWidget = class SpeechOSWidget extends i$1 {
|
|
|
6934
7085
|
if (remainingDelay > 0) {
|
|
6935
7086
|
setTimeout(() => {
|
|
6936
7087
|
state.setRecordingState("recording");
|
|
7088
|
+
this.startNoAudioWarningTracking();
|
|
6937
7089
|
}, remainingDelay);
|
|
6938
7090
|
}
|
|
6939
7091
|
else {
|
|
6940
7092
|
state.setRecordingState("recording");
|
|
7093
|
+
this.startNoAudioWarningTracking();
|
|
6941
7094
|
}
|
|
6942
7095
|
},
|
|
6943
7096
|
});
|
|
@@ -6945,7 +7098,10 @@ let SpeechOSWidget = class SpeechOSWidget extends i$1 {
|
|
|
6945
7098
|
catch (error) {
|
|
6946
7099
|
const errorMessage = error instanceof Error ? error.message : "Connection failed";
|
|
6947
7100
|
if (errorMessage !== "Disconnected") {
|
|
6948
|
-
state
|
|
7101
|
+
// Only set error if not already in error state (error event may have already set it)
|
|
7102
|
+
if (this.widgetState.recordingState !== "error") {
|
|
7103
|
+
state.setError(`Failed to connect: ${errorMessage}`);
|
|
7104
|
+
}
|
|
6949
7105
|
await backend.disconnect();
|
|
6950
7106
|
}
|
|
6951
7107
|
}
|
|
@@ -7000,10 +7156,12 @@ let SpeechOSWidget = class SpeechOSWidget extends i$1 {
|
|
|
7000
7156
|
if (remainingDelay > 0) {
|
|
7001
7157
|
setTimeout(() => {
|
|
7002
7158
|
state.setRecordingState("recording");
|
|
7159
|
+
this.startNoAudioWarningTracking();
|
|
7003
7160
|
}, remainingDelay);
|
|
7004
7161
|
}
|
|
7005
7162
|
else {
|
|
7006
7163
|
state.setRecordingState("recording");
|
|
7164
|
+
this.startNoAudioWarningTracking();
|
|
7007
7165
|
}
|
|
7008
7166
|
},
|
|
7009
7167
|
});
|
|
@@ -7011,7 +7169,10 @@ let SpeechOSWidget = class SpeechOSWidget extends i$1 {
|
|
|
7011
7169
|
catch (error) {
|
|
7012
7170
|
const errorMessage = error instanceof Error ? error.message : "Connection failed";
|
|
7013
7171
|
if (errorMessage !== "Disconnected") {
|
|
7014
|
-
state
|
|
7172
|
+
// Only set error if not already in error state (error event may have already set it)
|
|
7173
|
+
if (this.widgetState.recordingState !== "error") {
|
|
7174
|
+
state.setError(`Failed to connect: ${errorMessage}`);
|
|
7175
|
+
}
|
|
7015
7176
|
await backend.disconnect();
|
|
7016
7177
|
}
|
|
7017
7178
|
}
|
|
@@ -7022,12 +7183,30 @@ let SpeechOSWidget = class SpeechOSWidget extends i$1 {
|
|
|
7022
7183
|
const backend = getBackend();
|
|
7023
7184
|
try {
|
|
7024
7185
|
const editedText = await this.withMinDisplayTime(backend.requestEditText(originalContent), 300);
|
|
7186
|
+
// Check if server returned no change (couldn't understand edit)
|
|
7187
|
+
const noChange = editedText.trim() === originalContent.trim();
|
|
7188
|
+
if (noChange) {
|
|
7189
|
+
this.trackActionResult(false);
|
|
7190
|
+
this.showActionFeedback("edit-empty");
|
|
7191
|
+
state.completeRecording();
|
|
7192
|
+
this.editTargetElement = null;
|
|
7193
|
+
this.editSelectionStart = null;
|
|
7194
|
+
this.editSelectionEnd = null;
|
|
7195
|
+
this.editSelectedText = "";
|
|
7196
|
+
backend.disconnect().catch(() => { });
|
|
7197
|
+
backend.startAutoRefresh?.();
|
|
7198
|
+
return;
|
|
7199
|
+
}
|
|
7200
|
+
// Track result - got a meaningful change
|
|
7201
|
+
this.trackActionResult(true);
|
|
7025
7202
|
this.applyEdit(editedText);
|
|
7026
7203
|
backend.disconnect().catch(() => { });
|
|
7027
7204
|
// Start auto-refresh to keep token fresh for subsequent commands (LiveKit only)
|
|
7028
7205
|
backend.startAutoRefresh?.();
|
|
7029
7206
|
}
|
|
7030
7207
|
catch (error) {
|
|
7208
|
+
// Track as failed result
|
|
7209
|
+
this.trackActionResult(false);
|
|
7031
7210
|
const errorMessage = error instanceof Error ? error.message : "Failed to apply edit";
|
|
7032
7211
|
if (errorMessage !== "Disconnected") {
|
|
7033
7212
|
state.setError(errorMessage);
|
|
@@ -7053,10 +7232,12 @@ let SpeechOSWidget = class SpeechOSWidget extends i$1 {
|
|
|
7053
7232
|
if (remainingDelay > 0) {
|
|
7054
7233
|
setTimeout(() => {
|
|
7055
7234
|
state.setRecordingState("recording");
|
|
7235
|
+
this.startNoAudioWarningTracking();
|
|
7056
7236
|
}, remainingDelay);
|
|
7057
7237
|
}
|
|
7058
7238
|
else {
|
|
7059
7239
|
state.setRecordingState("recording");
|
|
7240
|
+
this.startNoAudioWarningTracking();
|
|
7060
7241
|
}
|
|
7061
7242
|
},
|
|
7062
7243
|
});
|
|
@@ -7064,7 +7245,10 @@ let SpeechOSWidget = class SpeechOSWidget extends i$1 {
|
|
|
7064
7245
|
catch (error) {
|
|
7065
7246
|
const errorMessage = error instanceof Error ? error.message : "Connection failed";
|
|
7066
7247
|
if (errorMessage !== "Disconnected") {
|
|
7067
|
-
state
|
|
7248
|
+
// Only set error if not already in error state (error event may have already set it)
|
|
7249
|
+
if (this.widgetState.recordingState !== "error") {
|
|
7250
|
+
state.setError(`Failed to connect: ${errorMessage}`);
|
|
7251
|
+
}
|
|
7068
7252
|
await backend.disconnect();
|
|
7069
7253
|
}
|
|
7070
7254
|
}
|
|
@@ -7076,6 +7260,8 @@ let SpeechOSWidget = class SpeechOSWidget extends i$1 {
|
|
|
7076
7260
|
const backend = getBackend();
|
|
7077
7261
|
try {
|
|
7078
7262
|
const result = await this.withMinDisplayTime(backend.requestCommand(commands), 300);
|
|
7263
|
+
// Track result - null result means no command matched (possibly no audio)
|
|
7264
|
+
this.trackActionResult(result !== null);
|
|
7079
7265
|
// Get input text from the backend if available
|
|
7080
7266
|
const inputText = backend.getLastInputText?.();
|
|
7081
7267
|
// Save to transcript store
|
|
@@ -7093,12 +7279,14 @@ let SpeechOSWidget = class SpeechOSWidget extends i$1 {
|
|
|
7093
7279
|
// Keep widget visible but collapsed (just mic button, no action bubbles)
|
|
7094
7280
|
state.setState({ isExpanded: false });
|
|
7095
7281
|
// Show command feedback
|
|
7096
|
-
this.
|
|
7282
|
+
this.showActionFeedback(result ? "command-success" : "command-none");
|
|
7097
7283
|
backend.disconnect().catch(() => { });
|
|
7098
7284
|
// Start auto-refresh to keep token fresh for subsequent commands (LiveKit only)
|
|
7099
7285
|
backend.startAutoRefresh?.();
|
|
7100
7286
|
}
|
|
7101
7287
|
catch (error) {
|
|
7288
|
+
// Track as failed result
|
|
7289
|
+
this.trackActionResult(false);
|
|
7102
7290
|
const errorMessage = error instanceof Error ? error.message : "Failed to process command";
|
|
7103
7291
|
if (errorMessage !== "Disconnected") {
|
|
7104
7292
|
state.setError(errorMessage);
|
|
@@ -7106,24 +7294,110 @@ let SpeechOSWidget = class SpeechOSWidget extends i$1 {
|
|
|
7106
7294
|
}
|
|
7107
7295
|
}
|
|
7108
7296
|
}
|
|
7109
|
-
|
|
7110
|
-
this.
|
|
7297
|
+
showActionFeedback(feedback) {
|
|
7298
|
+
this.actionFeedback = feedback;
|
|
7111
7299
|
// Clear any existing timeout
|
|
7112
|
-
if (this.
|
|
7113
|
-
clearTimeout(this.
|
|
7300
|
+
if (this.actionFeedbackTimeout) {
|
|
7301
|
+
clearTimeout(this.actionFeedbackTimeout);
|
|
7114
7302
|
}
|
|
7115
7303
|
// Auto-dismiss after 4 seconds
|
|
7116
|
-
this.
|
|
7117
|
-
this.
|
|
7118
|
-
this.
|
|
7304
|
+
this.actionFeedbackTimeout = window.setTimeout(() => {
|
|
7305
|
+
this.actionFeedback = null;
|
|
7306
|
+
this.actionFeedbackTimeout = null;
|
|
7119
7307
|
}, 4000);
|
|
7120
7308
|
}
|
|
7121
|
-
|
|
7122
|
-
if (this.
|
|
7123
|
-
clearTimeout(this.
|
|
7124
|
-
this.
|
|
7309
|
+
clearActionFeedback() {
|
|
7310
|
+
if (this.actionFeedbackTimeout) {
|
|
7311
|
+
clearTimeout(this.actionFeedbackTimeout);
|
|
7312
|
+
this.actionFeedbackTimeout = null;
|
|
7125
7313
|
}
|
|
7126
|
-
this.
|
|
7314
|
+
this.actionFeedback = null;
|
|
7315
|
+
}
|
|
7316
|
+
/**
|
|
7317
|
+
* Start tracking for no-audio warning when recording begins.
|
|
7318
|
+
*/
|
|
7319
|
+
startNoAudioWarningTracking() {
|
|
7320
|
+
this.transcriptionReceived = false;
|
|
7321
|
+
this.showNoAudioWarning = false;
|
|
7322
|
+
// If we had consecutive failures, show warning immediately
|
|
7323
|
+
if (this.consecutiveNoAudioActions >= CONSECUTIVE_NO_AUDIO_THRESHOLD) {
|
|
7324
|
+
this.showNoAudioWarning = true;
|
|
7325
|
+
}
|
|
7326
|
+
// Start timeout - if no transcription within 5s, show warning
|
|
7327
|
+
this.noAudioWarningTimeout = window.setTimeout(() => {
|
|
7328
|
+
if (!this.transcriptionReceived &&
|
|
7329
|
+
this.widgetState.recordingState === "recording") {
|
|
7330
|
+
this.showNoAudioWarning = true;
|
|
7331
|
+
}
|
|
7332
|
+
}, NO_AUDIO_WARNING_TIMEOUT_MS);
|
|
7333
|
+
// Subscribe to transcription:interim events
|
|
7334
|
+
this.transcriptionInterimUnsubscribe = events.on("transcription:interim", () => {
|
|
7335
|
+
this.transcriptionReceived = true;
|
|
7336
|
+
if (this.showNoAudioWarning) {
|
|
7337
|
+
this.showNoAudioWarning = false;
|
|
7338
|
+
}
|
|
7339
|
+
});
|
|
7340
|
+
}
|
|
7341
|
+
/**
|
|
7342
|
+
* Clean up no-audio warning tracking when recording stops.
|
|
7343
|
+
*/
|
|
7344
|
+
cleanupNoAudioWarningTracking() {
|
|
7345
|
+
if (this.noAudioWarningTimeout !== null) {
|
|
7346
|
+
clearTimeout(this.noAudioWarningTimeout);
|
|
7347
|
+
this.noAudioWarningTimeout = null;
|
|
7348
|
+
}
|
|
7349
|
+
if (this.transcriptionInterimUnsubscribe) {
|
|
7350
|
+
this.transcriptionInterimUnsubscribe();
|
|
7351
|
+
this.transcriptionInterimUnsubscribe = null;
|
|
7352
|
+
}
|
|
7353
|
+
this.showNoAudioWarning = false;
|
|
7354
|
+
}
|
|
7355
|
+
/**
|
|
7356
|
+
* Track the result of an action for consecutive failure detection.
|
|
7357
|
+
*/
|
|
7358
|
+
trackActionResult(hasContent) {
|
|
7359
|
+
if (hasContent) {
|
|
7360
|
+
this.consecutiveNoAudioActions = 0;
|
|
7361
|
+
}
|
|
7362
|
+
else {
|
|
7363
|
+
this.consecutiveNoAudioActions++;
|
|
7364
|
+
}
|
|
7365
|
+
}
|
|
7366
|
+
/**
|
|
7367
|
+
* Handle opening settings from the no-audio warning.
|
|
7368
|
+
* Stops the current dictation session immediately, then opens settings.
|
|
7369
|
+
*/
|
|
7370
|
+
async handleOpenSettingsFromWarning() {
|
|
7371
|
+
if (getConfig().debug) {
|
|
7372
|
+
console.log("[SpeechOS] No-audio settings link clicked");
|
|
7373
|
+
}
|
|
7374
|
+
// Clean up no-audio warning tracking first
|
|
7375
|
+
this.cleanupNoAudioWarningTracking();
|
|
7376
|
+
// Keep settings open even if widget collapses
|
|
7377
|
+
this.settingsOpenFromWarning = true;
|
|
7378
|
+
// Stop audio capture and disconnect immediately (don't wait for transcription)
|
|
7379
|
+
// Kick this off before opening settings so audio stops fast, but don't block UI.
|
|
7380
|
+
const disconnectPromise = getBackend().disconnect().catch((error) => {
|
|
7381
|
+
if (getConfig().debug) {
|
|
7382
|
+
console.log("[SpeechOS] Disconnect failed while opening settings", error);
|
|
7383
|
+
}
|
|
7384
|
+
});
|
|
7385
|
+
// Update UI state to idle
|
|
7386
|
+
state.cancelRecording();
|
|
7387
|
+
// Clear target elements
|
|
7388
|
+
this.dictationTargetElement = null;
|
|
7389
|
+
this.editTargetElement = null;
|
|
7390
|
+
this.dictationCursorStart = null;
|
|
7391
|
+
this.dictationCursorEnd = null;
|
|
7392
|
+
this.editSelectionStart = null;
|
|
7393
|
+
this.editSelectionEnd = null;
|
|
7394
|
+
this.editSelectedText = "";
|
|
7395
|
+
// Open settings modal
|
|
7396
|
+
this.settingsOpen = true;
|
|
7397
|
+
if (getConfig().debug) {
|
|
7398
|
+
console.log("[SpeechOS] Settings modal opened from no-audio warning");
|
|
7399
|
+
}
|
|
7400
|
+
await disconnectPromise;
|
|
7127
7401
|
}
|
|
7128
7402
|
supportsSelection(element) {
|
|
7129
7403
|
if (element.tagName.toLowerCase() === "textarea") {
|
|
@@ -7242,12 +7516,14 @@ let SpeechOSWidget = class SpeechOSWidget extends i$1 {
|
|
|
7242
7516
|
activeAction="${this.widgetState.activeAction || ""}"
|
|
7243
7517
|
editPreviewText="${this.editSelectedText}"
|
|
7244
7518
|
errorMessage="${this.widgetState.errorMessage || ""}"
|
|
7245
|
-
.
|
|
7519
|
+
.actionFeedback="${this.actionFeedback}"
|
|
7520
|
+
?showNoAudioWarning="${this.showNoAudioWarning}"
|
|
7246
7521
|
@mic-click="${this.handleMicClick}"
|
|
7247
7522
|
@stop-recording="${this.handleStopRecording}"
|
|
7248
7523
|
@cancel-operation="${this.handleCancelOperation}"
|
|
7249
7524
|
@retry-connection="${this.handleRetryConnection}"
|
|
7250
7525
|
@close-widget="${this.handleCloseWidget}"
|
|
7526
|
+
@open-settings="${this.handleOpenSettingsFromWarning}"
|
|
7251
7527
|
></speechos-mic-button>
|
|
7252
7528
|
</div>
|
|
7253
7529
|
</div>
|
|
@@ -7271,7 +7547,10 @@ __decorate([
|
|
|
7271
7547
|
], SpeechOSWidget.prototype, "editHelpModalOpen", void 0);
|
|
7272
7548
|
__decorate([
|
|
7273
7549
|
r()
|
|
7274
|
-
], SpeechOSWidget.prototype, "
|
|
7550
|
+
], SpeechOSWidget.prototype, "actionFeedback", void 0);
|
|
7551
|
+
__decorate([
|
|
7552
|
+
r()
|
|
7553
|
+
], SpeechOSWidget.prototype, "showNoAudioWarning", void 0);
|
|
7275
7554
|
SpeechOSWidget = SpeechOSWidget_1 = __decorate([
|
|
7276
7555
|
t$1("speechos-widget")
|
|
7277
7556
|
], SpeechOSWidget);
|