@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.iife.js
CHANGED
|
@@ -28518,6 +28518,10 @@
|
|
|
28518
28518
|
}
|
|
28519
28519
|
handleIntermediateTranscription(message) {
|
|
28520
28520
|
const config = getConfig();
|
|
28521
|
+
events.emit("transcription:interim", {
|
|
28522
|
+
transcript: message.transcript,
|
|
28523
|
+
isFinal: message.is_final
|
|
28524
|
+
});
|
|
28521
28525
|
if (config.debug) console.log("[SpeechOS] Intermediate transcription:", message.transcript, "final:", message.is_final);
|
|
28522
28526
|
}
|
|
28523
28527
|
handleFinalTranscript(message) {
|
|
@@ -30508,7 +30512,8 @@
|
|
|
30508
30512
|
this.activeAction = null;
|
|
30509
30513
|
this.editPreviewText = "";
|
|
30510
30514
|
this.errorMessage = null;
|
|
30511
|
-
this.
|
|
30515
|
+
this.actionFeedback = null;
|
|
30516
|
+
this.showNoAudioWarning = false;
|
|
30512
30517
|
}
|
|
30513
30518
|
static { this.styles = [
|
|
30514
30519
|
themeStyles,
|
|
@@ -31098,8 +31103,9 @@
|
|
|
31098
31103
|
background-position: center;
|
|
31099
31104
|
}
|
|
31100
31105
|
|
|
31101
|
-
/* Command feedback badge - no match state (neutral gray) */
|
|
31102
|
-
.status-label.command-none
|
|
31106
|
+
/* Command/edit feedback badge - no match/empty state (neutral gray) */
|
|
31107
|
+
.status-label.command-none,
|
|
31108
|
+
.status-label.edit-empty {
|
|
31103
31109
|
background: #4b5563;
|
|
31104
31110
|
box-shadow: 0 4px 12px rgba(75, 85, 99, 0.3);
|
|
31105
31111
|
animation: command-feedback-in 0.3s cubic-bezier(0.34, 1.56, 0.64, 1)
|
|
@@ -31256,6 +31262,60 @@
|
|
|
31256
31262
|
border-color: rgba(255, 255, 255, 0.5);
|
|
31257
31263
|
}
|
|
31258
31264
|
|
|
31265
|
+
/* No audio warning banner */
|
|
31266
|
+
.no-audio-warning {
|
|
31267
|
+
position: absolute;
|
|
31268
|
+
bottom: 120px; /* Above button and waveform visualizer */
|
|
31269
|
+
left: 50%;
|
|
31270
|
+
transform: translateX(-50%) translateY(8px);
|
|
31271
|
+
display: flex;
|
|
31272
|
+
align-items: center;
|
|
31273
|
+
gap: 8px;
|
|
31274
|
+
padding: 10px 14px;
|
|
31275
|
+
border-radius: 12px;
|
|
31276
|
+
background: linear-gradient(135deg, #f59e0b 0%, #d97706 100%);
|
|
31277
|
+
box-shadow: 0 4px 12px rgba(245, 158, 11, 0.3);
|
|
31278
|
+
transition: all 0.2s cubic-bezier(0.34, 1.56, 0.64, 1);
|
|
31279
|
+
pointer-events: none;
|
|
31280
|
+
opacity: 0;
|
|
31281
|
+
white-space: nowrap;
|
|
31282
|
+
}
|
|
31283
|
+
|
|
31284
|
+
.no-audio-warning.visible {
|
|
31285
|
+
opacity: 1;
|
|
31286
|
+
transform: translateX(-50%) translateY(0);
|
|
31287
|
+
pointer-events: auto;
|
|
31288
|
+
}
|
|
31289
|
+
|
|
31290
|
+
.no-audio-warning .warning-icon {
|
|
31291
|
+
flex-shrink: 0;
|
|
31292
|
+
color: white;
|
|
31293
|
+
}
|
|
31294
|
+
|
|
31295
|
+
.no-audio-warning .warning-text {
|
|
31296
|
+
font-size: 13px;
|
|
31297
|
+
font-weight: 500;
|
|
31298
|
+
color: white;
|
|
31299
|
+
}
|
|
31300
|
+
|
|
31301
|
+
.no-audio-warning .settings-link {
|
|
31302
|
+
background: rgba(255, 255, 255, 0.2);
|
|
31303
|
+
border: 1px solid rgba(255, 255, 255, 0.3);
|
|
31304
|
+
border-radius: 6px;
|
|
31305
|
+
padding: 4px 10px;
|
|
31306
|
+
font-size: 12px;
|
|
31307
|
+
font-weight: 600;
|
|
31308
|
+
color: white;
|
|
31309
|
+
cursor: pointer;
|
|
31310
|
+
transition: all 0.15s;
|
|
31311
|
+
white-space: nowrap;
|
|
31312
|
+
}
|
|
31313
|
+
|
|
31314
|
+
.no-audio-warning .settings-link:hover {
|
|
31315
|
+
background: rgba(255, 255, 255, 0.3);
|
|
31316
|
+
border-color: rgba(255, 255, 255, 0.5);
|
|
31317
|
+
}
|
|
31318
|
+
|
|
31259
31319
|
/* Mobile styles - 30% larger */
|
|
31260
31320
|
@media (max-width: 768px) and (hover: none) {
|
|
31261
31321
|
.mic-button {
|
|
@@ -31385,6 +31445,21 @@
|
|
|
31385
31445
|
padding: 8px 14px;
|
|
31386
31446
|
font-size: 14px;
|
|
31387
31447
|
}
|
|
31448
|
+
|
|
31449
|
+
.no-audio-warning {
|
|
31450
|
+
padding: 12px 16px;
|
|
31451
|
+
gap: 10px;
|
|
31452
|
+
bottom: 145px; /* Above button and waveform on mobile */
|
|
31453
|
+
}
|
|
31454
|
+
|
|
31455
|
+
.no-audio-warning .warning-text {
|
|
31456
|
+
font-size: 15px;
|
|
31457
|
+
}
|
|
31458
|
+
|
|
31459
|
+
.no-audio-warning .settings-link {
|
|
31460
|
+
padding: 6px 12px;
|
|
31461
|
+
font-size: 14px;
|
|
31462
|
+
}
|
|
31388
31463
|
}
|
|
31389
31464
|
`,
|
|
31390
31465
|
]; }
|
|
@@ -31442,6 +31517,14 @@
|
|
|
31442
31517
|
composed: true,
|
|
31443
31518
|
}));
|
|
31444
31519
|
}
|
|
31520
|
+
handleOpenSettings(e) {
|
|
31521
|
+
e.stopPropagation();
|
|
31522
|
+
e.preventDefault();
|
|
31523
|
+
this.dispatchEvent(new CustomEvent("open-settings", {
|
|
31524
|
+
bubbles: true,
|
|
31525
|
+
composed: true,
|
|
31526
|
+
}));
|
|
31527
|
+
}
|
|
31445
31528
|
getButtonClass() {
|
|
31446
31529
|
const classes = ["mic-button"];
|
|
31447
31530
|
if (this.expanded && this.recordingState === "idle") {
|
|
@@ -31526,13 +31609,16 @@
|
|
|
31526
31609
|
}
|
|
31527
31610
|
return this.recordingState;
|
|
31528
31611
|
}
|
|
31529
|
-
|
|
31530
|
-
if (this.
|
|
31612
|
+
getActionFeedbackLabel() {
|
|
31613
|
+
if (this.actionFeedback === "command-success") {
|
|
31531
31614
|
return "Got it!";
|
|
31532
31615
|
}
|
|
31533
|
-
if (this.
|
|
31616
|
+
if (this.actionFeedback === "command-none") {
|
|
31534
31617
|
return "No command matched";
|
|
31535
31618
|
}
|
|
31619
|
+
if (this.actionFeedback === "edit-empty") {
|
|
31620
|
+
return "Couldn't understand edit";
|
|
31621
|
+
}
|
|
31536
31622
|
return "";
|
|
31537
31623
|
}
|
|
31538
31624
|
render() {
|
|
@@ -31542,9 +31628,9 @@
|
|
|
31542
31628
|
const showSiriEdit = this.recordingState === "processing" && this.activeAction === "edit";
|
|
31543
31629
|
const statusLabel = this.getStatusLabel();
|
|
31544
31630
|
const showVisualizer = this.shouldShowVisualizer();
|
|
31545
|
-
// Show status label during recording (either visualizer or edit text) OR
|
|
31546
|
-
const
|
|
31547
|
-
const showStatus = this.recordingState === "recording" ||
|
|
31631
|
+
// Show status label during recording (either visualizer or edit text) OR action feedback
|
|
31632
|
+
const showActionFeedback = this.recordingState === "idle" && this.actionFeedback !== null;
|
|
31633
|
+
const showStatus = this.recordingState === "recording" || showActionFeedback;
|
|
31548
31634
|
const showCancel = this.recordingState === "connecting" ||
|
|
31549
31635
|
this.recordingState === "recording" ||
|
|
31550
31636
|
this.recordingState === "processing";
|
|
@@ -31583,6 +31669,35 @@
|
|
|
31583
31669
|
`
|
|
31584
31670
|
: ""}
|
|
31585
31671
|
|
|
31672
|
+
<div
|
|
31673
|
+
class="no-audio-warning ${this.showNoAudioWarning &&
|
|
31674
|
+
this.recordingState === "recording"
|
|
31675
|
+
? "visible"
|
|
31676
|
+
: ""}"
|
|
31677
|
+
>
|
|
31678
|
+
<svg
|
|
31679
|
+
class="warning-icon"
|
|
31680
|
+
width="16"
|
|
31681
|
+
height="16"
|
|
31682
|
+
viewBox="0 0 24 24"
|
|
31683
|
+
fill="none"
|
|
31684
|
+
stroke="currentColor"
|
|
31685
|
+
stroke-width="2"
|
|
31686
|
+
stroke-linecap="round"
|
|
31687
|
+
stroke-linejoin="round"
|
|
31688
|
+
>
|
|
31689
|
+
<path
|
|
31690
|
+
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"
|
|
31691
|
+
/>
|
|
31692
|
+
<line x1="12" y1="9" x2="12" y2="13" />
|
|
31693
|
+
<line x1="12" y1="17" x2="12.01" y2="17" />
|
|
31694
|
+
</svg>
|
|
31695
|
+
<span class="warning-text">We're not hearing anything</span>
|
|
31696
|
+
<button class="settings-link" @click="${this.handleOpenSettings}">
|
|
31697
|
+
Check Settings
|
|
31698
|
+
</button>
|
|
31699
|
+
</div>
|
|
31700
|
+
|
|
31586
31701
|
<button
|
|
31587
31702
|
class="${this.getButtonClass()}"
|
|
31588
31703
|
@click="${this.handleClick}"
|
|
@@ -31595,14 +31710,14 @@
|
|
|
31595
31710
|
</button>
|
|
31596
31711
|
|
|
31597
31712
|
<span
|
|
31598
|
-
class="status-label ${showStatus ? "visible" : ""} ${
|
|
31599
|
-
?
|
|
31713
|
+
class="status-label ${showStatus ? "visible" : ""} ${showActionFeedback
|
|
31714
|
+
? this.actionFeedback
|
|
31600
31715
|
: showVisualizer
|
|
31601
31716
|
? "visualizer"
|
|
31602
31717
|
: this.getStatusClass()}"
|
|
31603
31718
|
>
|
|
31604
|
-
${
|
|
31605
|
-
? this.
|
|
31719
|
+
${showActionFeedback
|
|
31720
|
+
? this.getActionFeedbackLabel()
|
|
31606
31721
|
: showVisualizer
|
|
31607
31722
|
? b `<speechos-audio-visualizer
|
|
31608
31723
|
?active="${showVisualizer}"
|
|
@@ -31650,7 +31765,10 @@
|
|
|
31650
31765
|
], SpeechOSMicButton.prototype, "errorMessage", void 0);
|
|
31651
31766
|
__decorate([
|
|
31652
31767
|
n({ type: String })
|
|
31653
|
-
], SpeechOSMicButton.prototype, "
|
|
31768
|
+
], SpeechOSMicButton.prototype, "actionFeedback", void 0);
|
|
31769
|
+
__decorate([
|
|
31770
|
+
n({ type: Boolean })
|
|
31771
|
+
], SpeechOSMicButton.prototype, "showNoAudioWarning", void 0);
|
|
31654
31772
|
SpeechOSMicButton = __decorate([
|
|
31655
31773
|
t$1("speechos-mic-button")
|
|
31656
31774
|
], SpeechOSMicButton);
|
|
@@ -35024,15 +35142,27 @@
|
|
|
35024
35142
|
* duration so users can see the visual feedback before transitioning to recording.
|
|
35025
35143
|
*/
|
|
35026
35144
|
const MIN_CONNECTING_ANIMATION_MS = 200;
|
|
35145
|
+
/**
|
|
35146
|
+
* Time to wait for a transcription event before showing the "no audio" warning (in milliseconds).
|
|
35147
|
+
* If no transcription:interim event is received within this time during recording,
|
|
35148
|
+
* it indicates the server isn't receiving/processing audio.
|
|
35149
|
+
*/
|
|
35150
|
+
const NO_AUDIO_WARNING_TIMEOUT_MS = 5000;
|
|
35151
|
+
/**
|
|
35152
|
+
* Number of consecutive actions with empty results before showing warning on next action.
|
|
35153
|
+
*/
|
|
35154
|
+
const CONSECUTIVE_NO_AUDIO_THRESHOLD = 2;
|
|
35027
35155
|
let SpeechOSWidget = class SpeechOSWidget extends i$1 {
|
|
35028
35156
|
constructor() {
|
|
35029
35157
|
super(...arguments);
|
|
35030
35158
|
this.widgetState = state.getState();
|
|
35031
35159
|
this.settingsOpen = false;
|
|
35160
|
+
this.settingsOpenFromWarning = false;
|
|
35032
35161
|
this.dictationModalOpen = false;
|
|
35033
35162
|
this.dictationModalText = "";
|
|
35034
35163
|
this.editHelpModalOpen = false;
|
|
35035
|
-
this.
|
|
35164
|
+
this.actionFeedback = null;
|
|
35165
|
+
this.showNoAudioWarning = false;
|
|
35036
35166
|
this.dictationTargetElement = null;
|
|
35037
35167
|
this.editTargetElement = null;
|
|
35038
35168
|
this.dictationCursorStart = null;
|
|
@@ -35044,7 +35174,7 @@
|
|
|
35044
35174
|
this.modalElement = null;
|
|
35045
35175
|
this.dictationModalElement = null;
|
|
35046
35176
|
this.editHelpModalElement = null;
|
|
35047
|
-
this.
|
|
35177
|
+
this.actionFeedbackTimeout = null;
|
|
35048
35178
|
this.customPosition = null;
|
|
35049
35179
|
this.isDragging = false;
|
|
35050
35180
|
this.dragStartPos = null;
|
|
@@ -35054,6 +35184,11 @@
|
|
|
35054
35184
|
this.suppressNextClick = false;
|
|
35055
35185
|
this.boundViewportResizeHandler = null;
|
|
35056
35186
|
this.boundScrollHandler = null;
|
|
35187
|
+
// No-audio warning state tracking
|
|
35188
|
+
this.consecutiveNoAudioActions = 0;
|
|
35189
|
+
this.transcriptionReceived = false;
|
|
35190
|
+
this.noAudioWarningTimeout = null;
|
|
35191
|
+
this.transcriptionInterimUnsubscribe = null;
|
|
35057
35192
|
}
|
|
35058
35193
|
static { SpeechOSWidget_1 = this; }
|
|
35059
35194
|
static { this.styles = [
|
|
@@ -35136,6 +35271,7 @@
|
|
|
35136
35271
|
this.modalElement = document.createElement("speechos-settings-modal");
|
|
35137
35272
|
this.modalElement.addEventListener("modal-close", () => {
|
|
35138
35273
|
this.settingsOpen = false;
|
|
35274
|
+
this.settingsOpenFromWarning = false;
|
|
35139
35275
|
});
|
|
35140
35276
|
document.body.appendChild(this.modalElement);
|
|
35141
35277
|
// Mount dictation output modal
|
|
@@ -35151,7 +35287,17 @@
|
|
|
35151
35287
|
});
|
|
35152
35288
|
document.body.appendChild(this.editHelpModalElement);
|
|
35153
35289
|
this.stateUnsubscribe = state.subscribe((newState) => {
|
|
35154
|
-
if (!newState.isVisible
|
|
35290
|
+
if (!newState.isVisible) {
|
|
35291
|
+
if (getConfig().debug && this.settingsOpen) {
|
|
35292
|
+
console.log("[SpeechOS] Closing settings modal: widget hidden");
|
|
35293
|
+
}
|
|
35294
|
+
this.settingsOpen = false;
|
|
35295
|
+
this.settingsOpenFromWarning = false;
|
|
35296
|
+
}
|
|
35297
|
+
else if (!newState.isExpanded && !this.settingsOpenFromWarning) {
|
|
35298
|
+
if (getConfig().debug && this.settingsOpen) {
|
|
35299
|
+
console.log("[SpeechOS] Closing settings modal: widget collapsed");
|
|
35300
|
+
}
|
|
35155
35301
|
this.settingsOpen = false;
|
|
35156
35302
|
}
|
|
35157
35303
|
// Clear custom position when focused element changes (re-anchor to new element)
|
|
@@ -35197,9 +35343,9 @@
|
|
|
35197
35343
|
this.editHelpModalElement.remove();
|
|
35198
35344
|
this.editHelpModalElement = null;
|
|
35199
35345
|
}
|
|
35200
|
-
if (this.
|
|
35201
|
-
clearTimeout(this.
|
|
35202
|
-
this.
|
|
35346
|
+
if (this.actionFeedbackTimeout) {
|
|
35347
|
+
clearTimeout(this.actionFeedbackTimeout);
|
|
35348
|
+
this.actionFeedbackTimeout = null;
|
|
35203
35349
|
}
|
|
35204
35350
|
if (this.stateUnsubscribe) {
|
|
35205
35351
|
this.stateUnsubscribe();
|
|
@@ -35227,6 +35373,7 @@
|
|
|
35227
35373
|
window.removeEventListener("scroll", this.boundScrollHandler);
|
|
35228
35374
|
this.boundScrollHandler = null;
|
|
35229
35375
|
}
|
|
35376
|
+
this.cleanupNoAudioWarningTracking();
|
|
35230
35377
|
}
|
|
35231
35378
|
updated(changedProperties) {
|
|
35232
35379
|
if (changedProperties.has("settingsOpen") && this.modalElement) {
|
|
@@ -35403,7 +35550,7 @@
|
|
|
35403
35550
|
}
|
|
35404
35551
|
if (this.widgetState.recordingState === "idle") {
|
|
35405
35552
|
// Clear command feedback on any mic click
|
|
35406
|
-
this.
|
|
35553
|
+
this.clearActionFeedback();
|
|
35407
35554
|
// If we're expanding, prefetch the token to reduce latency when user selects an action
|
|
35408
35555
|
if (!this.widgetState.isExpanded) {
|
|
35409
35556
|
// Fire and forget - we don't need to wait for this (LiveKit only)
|
|
@@ -35420,6 +35567,8 @@
|
|
|
35420
35567
|
}
|
|
35421
35568
|
}
|
|
35422
35569
|
async handleStopRecording() {
|
|
35570
|
+
// Clean up no-audio warning tracking
|
|
35571
|
+
this.cleanupNoAudioWarningTracking();
|
|
35423
35572
|
if (this.widgetState.activeAction === "edit") {
|
|
35424
35573
|
await this.handleStopEdit();
|
|
35425
35574
|
}
|
|
@@ -35431,6 +35580,8 @@
|
|
|
35431
35580
|
const backend = getBackend();
|
|
35432
35581
|
try {
|
|
35433
35582
|
const transcription = await this.withMinDisplayTime(backend.stopVoiceSession(), 300);
|
|
35583
|
+
// Track result for consecutive failure detection
|
|
35584
|
+
this.trackActionResult(!!transcription);
|
|
35434
35585
|
if (transcription) {
|
|
35435
35586
|
// Check if we have a target element to insert into
|
|
35436
35587
|
if (this.dictationTargetElement) {
|
|
@@ -35450,6 +35601,8 @@
|
|
|
35450
35601
|
backend.startAutoRefresh?.();
|
|
35451
35602
|
}
|
|
35452
35603
|
catch (error) {
|
|
35604
|
+
// Track as failed result
|
|
35605
|
+
this.trackActionResult(false);
|
|
35453
35606
|
const errorMessage = error instanceof Error ? error.message : "Failed to transcribe audio";
|
|
35454
35607
|
if (errorMessage !== "Disconnected") {
|
|
35455
35608
|
state.setError(errorMessage);
|
|
@@ -35459,6 +35612,8 @@
|
|
|
35459
35612
|
}
|
|
35460
35613
|
}
|
|
35461
35614
|
async handleCancelOperation() {
|
|
35615
|
+
// Clean up no-audio warning tracking
|
|
35616
|
+
this.cleanupNoAudioWarningTracking();
|
|
35462
35617
|
await getBackend().disconnect();
|
|
35463
35618
|
if (this.widgetState.recordingState === "error") {
|
|
35464
35619
|
state.clearError();
|
|
@@ -35488,7 +35643,7 @@
|
|
|
35488
35643
|
}
|
|
35489
35644
|
}
|
|
35490
35645
|
handleCloseWidget() {
|
|
35491
|
-
this.
|
|
35646
|
+
this.clearActionFeedback();
|
|
35492
35647
|
state.hide();
|
|
35493
35648
|
}
|
|
35494
35649
|
handleSettingsClick() {
|
|
@@ -35616,7 +35771,7 @@
|
|
|
35616
35771
|
handleActionSelect(event) {
|
|
35617
35772
|
const { action } = event.detail;
|
|
35618
35773
|
// Clear any existing command feedback when a new action is selected
|
|
35619
|
-
this.
|
|
35774
|
+
this.clearActionFeedback();
|
|
35620
35775
|
state.setActiveAction(action);
|
|
35621
35776
|
if (action === "dictate") {
|
|
35622
35777
|
this.startDictation();
|
|
@@ -35679,10 +35834,12 @@
|
|
|
35679
35834
|
if (remainingDelay > 0) {
|
|
35680
35835
|
setTimeout(() => {
|
|
35681
35836
|
state.setRecordingState("recording");
|
|
35837
|
+
this.startNoAudioWarningTracking();
|
|
35682
35838
|
}, remainingDelay);
|
|
35683
35839
|
}
|
|
35684
35840
|
else {
|
|
35685
35841
|
state.setRecordingState("recording");
|
|
35842
|
+
this.startNoAudioWarningTracking();
|
|
35686
35843
|
}
|
|
35687
35844
|
},
|
|
35688
35845
|
});
|
|
@@ -35690,7 +35847,10 @@
|
|
|
35690
35847
|
catch (error) {
|
|
35691
35848
|
const errorMessage = error instanceof Error ? error.message : "Connection failed";
|
|
35692
35849
|
if (errorMessage !== "Disconnected") {
|
|
35693
|
-
state
|
|
35850
|
+
// Only set error if not already in error state (error event may have already set it)
|
|
35851
|
+
if (this.widgetState.recordingState !== "error") {
|
|
35852
|
+
state.setError(`Failed to connect: ${errorMessage}`);
|
|
35853
|
+
}
|
|
35694
35854
|
await backend.disconnect();
|
|
35695
35855
|
}
|
|
35696
35856
|
}
|
|
@@ -35745,10 +35905,12 @@
|
|
|
35745
35905
|
if (remainingDelay > 0) {
|
|
35746
35906
|
setTimeout(() => {
|
|
35747
35907
|
state.setRecordingState("recording");
|
|
35908
|
+
this.startNoAudioWarningTracking();
|
|
35748
35909
|
}, remainingDelay);
|
|
35749
35910
|
}
|
|
35750
35911
|
else {
|
|
35751
35912
|
state.setRecordingState("recording");
|
|
35913
|
+
this.startNoAudioWarningTracking();
|
|
35752
35914
|
}
|
|
35753
35915
|
},
|
|
35754
35916
|
});
|
|
@@ -35756,7 +35918,10 @@
|
|
|
35756
35918
|
catch (error) {
|
|
35757
35919
|
const errorMessage = error instanceof Error ? error.message : "Connection failed";
|
|
35758
35920
|
if (errorMessage !== "Disconnected") {
|
|
35759
|
-
state
|
|
35921
|
+
// Only set error if not already in error state (error event may have already set it)
|
|
35922
|
+
if (this.widgetState.recordingState !== "error") {
|
|
35923
|
+
state.setError(`Failed to connect: ${errorMessage}`);
|
|
35924
|
+
}
|
|
35760
35925
|
await backend.disconnect();
|
|
35761
35926
|
}
|
|
35762
35927
|
}
|
|
@@ -35767,12 +35932,30 @@
|
|
|
35767
35932
|
const backend = getBackend();
|
|
35768
35933
|
try {
|
|
35769
35934
|
const editedText = await this.withMinDisplayTime(backend.requestEditText(originalContent), 300);
|
|
35935
|
+
// Check if server returned no change (couldn't understand edit)
|
|
35936
|
+
const noChange = editedText.trim() === originalContent.trim();
|
|
35937
|
+
if (noChange) {
|
|
35938
|
+
this.trackActionResult(false);
|
|
35939
|
+
this.showActionFeedback("edit-empty");
|
|
35940
|
+
state.completeRecording();
|
|
35941
|
+
this.editTargetElement = null;
|
|
35942
|
+
this.editSelectionStart = null;
|
|
35943
|
+
this.editSelectionEnd = null;
|
|
35944
|
+
this.editSelectedText = "";
|
|
35945
|
+
backend.disconnect().catch(() => { });
|
|
35946
|
+
backend.startAutoRefresh?.();
|
|
35947
|
+
return;
|
|
35948
|
+
}
|
|
35949
|
+
// Track result - got a meaningful change
|
|
35950
|
+
this.trackActionResult(true);
|
|
35770
35951
|
this.applyEdit(editedText);
|
|
35771
35952
|
backend.disconnect().catch(() => { });
|
|
35772
35953
|
// Start auto-refresh to keep token fresh for subsequent commands (LiveKit only)
|
|
35773
35954
|
backend.startAutoRefresh?.();
|
|
35774
35955
|
}
|
|
35775
35956
|
catch (error) {
|
|
35957
|
+
// Track as failed result
|
|
35958
|
+
this.trackActionResult(false);
|
|
35776
35959
|
const errorMessage = error instanceof Error ? error.message : "Failed to apply edit";
|
|
35777
35960
|
if (errorMessage !== "Disconnected") {
|
|
35778
35961
|
state.setError(errorMessage);
|
|
@@ -35798,10 +35981,12 @@
|
|
|
35798
35981
|
if (remainingDelay > 0) {
|
|
35799
35982
|
setTimeout(() => {
|
|
35800
35983
|
state.setRecordingState("recording");
|
|
35984
|
+
this.startNoAudioWarningTracking();
|
|
35801
35985
|
}, remainingDelay);
|
|
35802
35986
|
}
|
|
35803
35987
|
else {
|
|
35804
35988
|
state.setRecordingState("recording");
|
|
35989
|
+
this.startNoAudioWarningTracking();
|
|
35805
35990
|
}
|
|
35806
35991
|
},
|
|
35807
35992
|
});
|
|
@@ -35809,7 +35994,10 @@
|
|
|
35809
35994
|
catch (error) {
|
|
35810
35995
|
const errorMessage = error instanceof Error ? error.message : "Connection failed";
|
|
35811
35996
|
if (errorMessage !== "Disconnected") {
|
|
35812
|
-
state
|
|
35997
|
+
// Only set error if not already in error state (error event may have already set it)
|
|
35998
|
+
if (this.widgetState.recordingState !== "error") {
|
|
35999
|
+
state.setError(`Failed to connect: ${errorMessage}`);
|
|
36000
|
+
}
|
|
35813
36001
|
await backend.disconnect();
|
|
35814
36002
|
}
|
|
35815
36003
|
}
|
|
@@ -35821,6 +36009,8 @@
|
|
|
35821
36009
|
const backend = getBackend();
|
|
35822
36010
|
try {
|
|
35823
36011
|
const result = await this.withMinDisplayTime(backend.requestCommand(commands), 300);
|
|
36012
|
+
// Track result - null result means no command matched (possibly no audio)
|
|
36013
|
+
this.trackActionResult(result !== null);
|
|
35824
36014
|
// Get input text from the backend if available
|
|
35825
36015
|
const inputText = backend.getLastInputText?.();
|
|
35826
36016
|
// Save to transcript store
|
|
@@ -35838,12 +36028,14 @@
|
|
|
35838
36028
|
// Keep widget visible but collapsed (just mic button, no action bubbles)
|
|
35839
36029
|
state.setState({ isExpanded: false });
|
|
35840
36030
|
// Show command feedback
|
|
35841
|
-
this.
|
|
36031
|
+
this.showActionFeedback(result ? "command-success" : "command-none");
|
|
35842
36032
|
backend.disconnect().catch(() => { });
|
|
35843
36033
|
// Start auto-refresh to keep token fresh for subsequent commands (LiveKit only)
|
|
35844
36034
|
backend.startAutoRefresh?.();
|
|
35845
36035
|
}
|
|
35846
36036
|
catch (error) {
|
|
36037
|
+
// Track as failed result
|
|
36038
|
+
this.trackActionResult(false);
|
|
35847
36039
|
const errorMessage = error instanceof Error ? error.message : "Failed to process command";
|
|
35848
36040
|
if (errorMessage !== "Disconnected") {
|
|
35849
36041
|
state.setError(errorMessage);
|
|
@@ -35851,24 +36043,110 @@
|
|
|
35851
36043
|
}
|
|
35852
36044
|
}
|
|
35853
36045
|
}
|
|
35854
|
-
|
|
35855
|
-
this.
|
|
36046
|
+
showActionFeedback(feedback) {
|
|
36047
|
+
this.actionFeedback = feedback;
|
|
35856
36048
|
// Clear any existing timeout
|
|
35857
|
-
if (this.
|
|
35858
|
-
clearTimeout(this.
|
|
36049
|
+
if (this.actionFeedbackTimeout) {
|
|
36050
|
+
clearTimeout(this.actionFeedbackTimeout);
|
|
35859
36051
|
}
|
|
35860
36052
|
// Auto-dismiss after 4 seconds
|
|
35861
|
-
this.
|
|
35862
|
-
this.
|
|
35863
|
-
this.
|
|
36053
|
+
this.actionFeedbackTimeout = window.setTimeout(() => {
|
|
36054
|
+
this.actionFeedback = null;
|
|
36055
|
+
this.actionFeedbackTimeout = null;
|
|
35864
36056
|
}, 4000);
|
|
35865
36057
|
}
|
|
35866
|
-
|
|
35867
|
-
if (this.
|
|
35868
|
-
clearTimeout(this.
|
|
35869
|
-
this.
|
|
36058
|
+
clearActionFeedback() {
|
|
36059
|
+
if (this.actionFeedbackTimeout) {
|
|
36060
|
+
clearTimeout(this.actionFeedbackTimeout);
|
|
36061
|
+
this.actionFeedbackTimeout = null;
|
|
35870
36062
|
}
|
|
35871
|
-
this.
|
|
36063
|
+
this.actionFeedback = null;
|
|
36064
|
+
}
|
|
36065
|
+
/**
|
|
36066
|
+
* Start tracking for no-audio warning when recording begins.
|
|
36067
|
+
*/
|
|
36068
|
+
startNoAudioWarningTracking() {
|
|
36069
|
+
this.transcriptionReceived = false;
|
|
36070
|
+
this.showNoAudioWarning = false;
|
|
36071
|
+
// If we had consecutive failures, show warning immediately
|
|
36072
|
+
if (this.consecutiveNoAudioActions >= CONSECUTIVE_NO_AUDIO_THRESHOLD) {
|
|
36073
|
+
this.showNoAudioWarning = true;
|
|
36074
|
+
}
|
|
36075
|
+
// Start timeout - if no transcription within 5s, show warning
|
|
36076
|
+
this.noAudioWarningTimeout = window.setTimeout(() => {
|
|
36077
|
+
if (!this.transcriptionReceived &&
|
|
36078
|
+
this.widgetState.recordingState === "recording") {
|
|
36079
|
+
this.showNoAudioWarning = true;
|
|
36080
|
+
}
|
|
36081
|
+
}, NO_AUDIO_WARNING_TIMEOUT_MS);
|
|
36082
|
+
// Subscribe to transcription:interim events
|
|
36083
|
+
this.transcriptionInterimUnsubscribe = events.on("transcription:interim", () => {
|
|
36084
|
+
this.transcriptionReceived = true;
|
|
36085
|
+
if (this.showNoAudioWarning) {
|
|
36086
|
+
this.showNoAudioWarning = false;
|
|
36087
|
+
}
|
|
36088
|
+
});
|
|
36089
|
+
}
|
|
36090
|
+
/**
|
|
36091
|
+
* Clean up no-audio warning tracking when recording stops.
|
|
36092
|
+
*/
|
|
36093
|
+
cleanupNoAudioWarningTracking() {
|
|
36094
|
+
if (this.noAudioWarningTimeout !== null) {
|
|
36095
|
+
clearTimeout(this.noAudioWarningTimeout);
|
|
36096
|
+
this.noAudioWarningTimeout = null;
|
|
36097
|
+
}
|
|
36098
|
+
if (this.transcriptionInterimUnsubscribe) {
|
|
36099
|
+
this.transcriptionInterimUnsubscribe();
|
|
36100
|
+
this.transcriptionInterimUnsubscribe = null;
|
|
36101
|
+
}
|
|
36102
|
+
this.showNoAudioWarning = false;
|
|
36103
|
+
}
|
|
36104
|
+
/**
|
|
36105
|
+
* Track the result of an action for consecutive failure detection.
|
|
36106
|
+
*/
|
|
36107
|
+
trackActionResult(hasContent) {
|
|
36108
|
+
if (hasContent) {
|
|
36109
|
+
this.consecutiveNoAudioActions = 0;
|
|
36110
|
+
}
|
|
36111
|
+
else {
|
|
36112
|
+
this.consecutiveNoAudioActions++;
|
|
36113
|
+
}
|
|
36114
|
+
}
|
|
36115
|
+
/**
|
|
36116
|
+
* Handle opening settings from the no-audio warning.
|
|
36117
|
+
* Stops the current dictation session immediately, then opens settings.
|
|
36118
|
+
*/
|
|
36119
|
+
async handleOpenSettingsFromWarning() {
|
|
36120
|
+
if (getConfig().debug) {
|
|
36121
|
+
console.log("[SpeechOS] No-audio settings link clicked");
|
|
36122
|
+
}
|
|
36123
|
+
// Clean up no-audio warning tracking first
|
|
36124
|
+
this.cleanupNoAudioWarningTracking();
|
|
36125
|
+
// Keep settings open even if widget collapses
|
|
36126
|
+
this.settingsOpenFromWarning = true;
|
|
36127
|
+
// Stop audio capture and disconnect immediately (don't wait for transcription)
|
|
36128
|
+
// Kick this off before opening settings so audio stops fast, but don't block UI.
|
|
36129
|
+
const disconnectPromise = getBackend().disconnect().catch((error) => {
|
|
36130
|
+
if (getConfig().debug) {
|
|
36131
|
+
console.log("[SpeechOS] Disconnect failed while opening settings", error);
|
|
36132
|
+
}
|
|
36133
|
+
});
|
|
36134
|
+
// Update UI state to idle
|
|
36135
|
+
state.cancelRecording();
|
|
36136
|
+
// Clear target elements
|
|
36137
|
+
this.dictationTargetElement = null;
|
|
36138
|
+
this.editTargetElement = null;
|
|
36139
|
+
this.dictationCursorStart = null;
|
|
36140
|
+
this.dictationCursorEnd = null;
|
|
36141
|
+
this.editSelectionStart = null;
|
|
36142
|
+
this.editSelectionEnd = null;
|
|
36143
|
+
this.editSelectedText = "";
|
|
36144
|
+
// Open settings modal
|
|
36145
|
+
this.settingsOpen = true;
|
|
36146
|
+
if (getConfig().debug) {
|
|
36147
|
+
console.log("[SpeechOS] Settings modal opened from no-audio warning");
|
|
36148
|
+
}
|
|
36149
|
+
await disconnectPromise;
|
|
35872
36150
|
}
|
|
35873
36151
|
supportsSelection(element) {
|
|
35874
36152
|
if (element.tagName.toLowerCase() === "textarea") {
|
|
@@ -35987,12 +36265,14 @@
|
|
|
35987
36265
|
activeAction="${this.widgetState.activeAction || ""}"
|
|
35988
36266
|
editPreviewText="${this.editSelectedText}"
|
|
35989
36267
|
errorMessage="${this.widgetState.errorMessage || ""}"
|
|
35990
|
-
.
|
|
36268
|
+
.actionFeedback="${this.actionFeedback}"
|
|
36269
|
+
?showNoAudioWarning="${this.showNoAudioWarning}"
|
|
35991
36270
|
@mic-click="${this.handleMicClick}"
|
|
35992
36271
|
@stop-recording="${this.handleStopRecording}"
|
|
35993
36272
|
@cancel-operation="${this.handleCancelOperation}"
|
|
35994
36273
|
@retry-connection="${this.handleRetryConnection}"
|
|
35995
36274
|
@close-widget="${this.handleCloseWidget}"
|
|
36275
|
+
@open-settings="${this.handleOpenSettingsFromWarning}"
|
|
35996
36276
|
></speechos-mic-button>
|
|
35997
36277
|
</div>
|
|
35998
36278
|
</div>
|
|
@@ -36016,7 +36296,10 @@
|
|
|
36016
36296
|
], SpeechOSWidget.prototype, "editHelpModalOpen", void 0);
|
|
36017
36297
|
__decorate([
|
|
36018
36298
|
r()
|
|
36019
|
-
], SpeechOSWidget.prototype, "
|
|
36299
|
+
], SpeechOSWidget.prototype, "actionFeedback", void 0);
|
|
36300
|
+
__decorate([
|
|
36301
|
+
r()
|
|
36302
|
+
], SpeechOSWidget.prototype, "showNoAudioWarning", void 0);
|
|
36020
36303
|
SpeechOSWidget = SpeechOSWidget_1 = __decorate([
|
|
36021
36304
|
t$1("speechos-widget")
|
|
36022
36305
|
], SpeechOSWidget);
|