@speechos/client 0.2.5 → 0.2.7
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/form-detector.d.ts.map +1 -1
- package/dist/index.cjs +645 -122
- package/dist/index.cjs.map +1 -1
- package/dist/index.iife.js +670 -134
- package/dist/index.iife.js.map +1 -1
- package/dist/index.iife.min.js +248 -107
- package/dist/index.iife.min.js.map +1 -1
- package/dist/index.js +646 -123
- package/dist/index.js.map +1 -1
- package/dist/ui/dictation-output-modal.d.ts +5 -0
- package/dist/ui/dictation-output-modal.d.ts.map +1 -1
- package/dist/ui/mic-button.d.ts +5 -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 +41 -4
- package/dist/ui/widget.d.ts.map +1 -1
- package/package.json +3 -2
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
|
/**
|
|
@@ -142,6 +142,10 @@ class FormDetector {
|
|
|
142
142
|
this.focusHandler = (event) => {
|
|
143
143
|
const target = event.target;
|
|
144
144
|
if (isFormField(target)) {
|
|
145
|
+
console.log("[SpeechOS] FormDetector: focus on form field", {
|
|
146
|
+
element: target,
|
|
147
|
+
tagName: target?.tagName,
|
|
148
|
+
});
|
|
145
149
|
state.setFocusedElement(target);
|
|
146
150
|
state.show();
|
|
147
151
|
events.emit("form:focus", { element: target });
|
|
@@ -1397,6 +1401,71 @@ const transcriptStore = {
|
|
|
1397
1401
|
deleteTranscript: deleteTranscript,
|
|
1398
1402
|
};
|
|
1399
1403
|
|
|
1404
|
+
function isNativeField(field) {
|
|
1405
|
+
return field instanceof HTMLInputElement || field instanceof HTMLTextAreaElement;
|
|
1406
|
+
}
|
|
1407
|
+
/** Call a function after focusing a field and then restore the previous focus afterwards if necessary */
|
|
1408
|
+
function withFocus(field, callback) {
|
|
1409
|
+
const document = field.ownerDocument;
|
|
1410
|
+
const initialFocus = document.activeElement;
|
|
1411
|
+
if (initialFocus === field) {
|
|
1412
|
+
return callback();
|
|
1413
|
+
}
|
|
1414
|
+
try {
|
|
1415
|
+
field.focus();
|
|
1416
|
+
return callback();
|
|
1417
|
+
}
|
|
1418
|
+
finally {
|
|
1419
|
+
field.blur(); // Supports `intialFocus === body`
|
|
1420
|
+
if (initialFocus instanceof HTMLElement) {
|
|
1421
|
+
initialFocus.focus();
|
|
1422
|
+
}
|
|
1423
|
+
}
|
|
1424
|
+
}
|
|
1425
|
+
// This will insert into the focused field. It shouild always be called inside withFocus.
|
|
1426
|
+
// Use this one locally if there are multiple `insertTextIntoField` or `document.execCommand` calls
|
|
1427
|
+
function insertTextWhereverTheFocusIs(document, text) {
|
|
1428
|
+
if (text === '') {
|
|
1429
|
+
// https://github.com/fregante/text-field-edit/issues/16
|
|
1430
|
+
document.execCommand('delete');
|
|
1431
|
+
}
|
|
1432
|
+
else {
|
|
1433
|
+
document.execCommand('insertText', false, text);
|
|
1434
|
+
}
|
|
1435
|
+
}
|
|
1436
|
+
/** Inserts `text` at the cursor’s position, replacing any selection, with **undo** support and by firing the `input` event. */
|
|
1437
|
+
function insertTextIntoField(field, text) {
|
|
1438
|
+
withFocus(field, () => {
|
|
1439
|
+
insertTextWhereverTheFocusIs(field.ownerDocument, text);
|
|
1440
|
+
});
|
|
1441
|
+
}
|
|
1442
|
+
/** Replaces the entire content, equivalent to `field.value = text` but with **undo** support and by firing the `input` event. */
|
|
1443
|
+
function setFieldText(field, text) {
|
|
1444
|
+
if (isNativeField(field)) {
|
|
1445
|
+
field.select();
|
|
1446
|
+
insertTextIntoField(field, text);
|
|
1447
|
+
}
|
|
1448
|
+
else {
|
|
1449
|
+
const document = field.ownerDocument;
|
|
1450
|
+
withFocus(field, () => {
|
|
1451
|
+
document.execCommand('selectAll', false, text);
|
|
1452
|
+
insertTextWhereverTheFocusIs(document, text);
|
|
1453
|
+
});
|
|
1454
|
+
}
|
|
1455
|
+
}
|
|
1456
|
+
/** Get the selected text in a field or an empty string if nothing is selected. */
|
|
1457
|
+
function getFieldSelection(field) {
|
|
1458
|
+
if (isNativeField(field)) {
|
|
1459
|
+
return field.value.slice(field.selectionStart, field.selectionEnd);
|
|
1460
|
+
}
|
|
1461
|
+
const selection = field.ownerDocument.getSelection();
|
|
1462
|
+
if (selection && field.contains(selection.anchorNode)) {
|
|
1463
|
+
// The selection is inside the field
|
|
1464
|
+
return selection.toString();
|
|
1465
|
+
}
|
|
1466
|
+
return '';
|
|
1467
|
+
}
|
|
1468
|
+
|
|
1400
1469
|
/**
|
|
1401
1470
|
* @license
|
|
1402
1471
|
* Copyright 2017 Google LLC
|
|
@@ -1757,7 +1826,9 @@ let SpeechOSMicButton = class SpeechOSMicButton extends i$1 {
|
|
|
1757
1826
|
this.activeAction = null;
|
|
1758
1827
|
this.editPreviewText = "";
|
|
1759
1828
|
this.errorMessage = null;
|
|
1760
|
-
this.
|
|
1829
|
+
this.showRetryButton = true;
|
|
1830
|
+
this.actionFeedback = null;
|
|
1831
|
+
this.showNoAudioWarning = false;
|
|
1761
1832
|
}
|
|
1762
1833
|
static { this.styles = [
|
|
1763
1834
|
themeStyles,
|
|
@@ -2347,8 +2418,9 @@ let SpeechOSMicButton = class SpeechOSMicButton extends i$1 {
|
|
|
2347
2418
|
background-position: center;
|
|
2348
2419
|
}
|
|
2349
2420
|
|
|
2350
|
-
/* Command feedback badge - no match state (neutral gray) */
|
|
2351
|
-
.status-label.command-none
|
|
2421
|
+
/* Command/edit feedback badge - no match/empty state (neutral gray) */
|
|
2422
|
+
.status-label.command-none,
|
|
2423
|
+
.status-label.edit-empty {
|
|
2352
2424
|
background: #4b5563;
|
|
2353
2425
|
box-shadow: 0 4px 12px rgba(75, 85, 99, 0.3);
|
|
2354
2426
|
animation: command-feedback-in 0.3s cubic-bezier(0.34, 1.56, 0.64, 1)
|
|
@@ -2464,10 +2536,14 @@ let SpeechOSMicButton = class SpeechOSMicButton extends i$1 {
|
|
|
2464
2536
|
bottom: 72px; /* Above button */
|
|
2465
2537
|
left: 50%;
|
|
2466
2538
|
transform: translateX(-50%) translateY(8px);
|
|
2539
|
+
min-width: 200px;
|
|
2467
2540
|
max-width: 280px;
|
|
2541
|
+
width: max-content;
|
|
2468
2542
|
font-size: 13px;
|
|
2469
2543
|
color: white;
|
|
2470
2544
|
white-space: normal;
|
|
2545
|
+
word-wrap: break-word;
|
|
2546
|
+
overflow-wrap: break-word;
|
|
2471
2547
|
text-align: center;
|
|
2472
2548
|
padding: 12px 16px;
|
|
2473
2549
|
border-radius: 12px;
|
|
@@ -2505,6 +2581,60 @@ let SpeechOSMicButton = class SpeechOSMicButton extends i$1 {
|
|
|
2505
2581
|
border-color: rgba(255, 255, 255, 0.5);
|
|
2506
2582
|
}
|
|
2507
2583
|
|
|
2584
|
+
/* No audio warning banner */
|
|
2585
|
+
.no-audio-warning {
|
|
2586
|
+
position: absolute;
|
|
2587
|
+
bottom: 120px; /* Above button and waveform visualizer */
|
|
2588
|
+
left: 50%;
|
|
2589
|
+
transform: translateX(-50%) translateY(8px);
|
|
2590
|
+
display: flex;
|
|
2591
|
+
align-items: center;
|
|
2592
|
+
gap: 8px;
|
|
2593
|
+
padding: 10px 14px;
|
|
2594
|
+
border-radius: 12px;
|
|
2595
|
+
background: linear-gradient(135deg, #f59e0b 0%, #d97706 100%);
|
|
2596
|
+
box-shadow: 0 4px 12px rgba(245, 158, 11, 0.3);
|
|
2597
|
+
transition: all 0.2s cubic-bezier(0.34, 1.56, 0.64, 1);
|
|
2598
|
+
pointer-events: none;
|
|
2599
|
+
opacity: 0;
|
|
2600
|
+
white-space: nowrap;
|
|
2601
|
+
}
|
|
2602
|
+
|
|
2603
|
+
.no-audio-warning.visible {
|
|
2604
|
+
opacity: 1;
|
|
2605
|
+
transform: translateX(-50%) translateY(0);
|
|
2606
|
+
pointer-events: auto;
|
|
2607
|
+
}
|
|
2608
|
+
|
|
2609
|
+
.no-audio-warning .warning-icon {
|
|
2610
|
+
flex-shrink: 0;
|
|
2611
|
+
color: white;
|
|
2612
|
+
}
|
|
2613
|
+
|
|
2614
|
+
.no-audio-warning .warning-text {
|
|
2615
|
+
font-size: 13px;
|
|
2616
|
+
font-weight: 500;
|
|
2617
|
+
color: white;
|
|
2618
|
+
}
|
|
2619
|
+
|
|
2620
|
+
.no-audio-warning .settings-link {
|
|
2621
|
+
background: rgba(255, 255, 255, 0.2);
|
|
2622
|
+
border: 1px solid rgba(255, 255, 255, 0.3);
|
|
2623
|
+
border-radius: 6px;
|
|
2624
|
+
padding: 4px 10px;
|
|
2625
|
+
font-size: 12px;
|
|
2626
|
+
font-weight: 600;
|
|
2627
|
+
color: white;
|
|
2628
|
+
cursor: pointer;
|
|
2629
|
+
transition: all 0.15s;
|
|
2630
|
+
white-space: nowrap;
|
|
2631
|
+
}
|
|
2632
|
+
|
|
2633
|
+
.no-audio-warning .settings-link:hover {
|
|
2634
|
+
background: rgba(255, 255, 255, 0.3);
|
|
2635
|
+
border-color: rgba(255, 255, 255, 0.5);
|
|
2636
|
+
}
|
|
2637
|
+
|
|
2508
2638
|
/* Mobile styles - 30% larger */
|
|
2509
2639
|
@media (max-width: 768px) and (hover: none) {
|
|
2510
2640
|
.mic-button {
|
|
@@ -2626,6 +2756,7 @@ let SpeechOSMicButton = class SpeechOSMicButton extends i$1 {
|
|
|
2626
2756
|
.error-message {
|
|
2627
2757
|
font-size: 15px;
|
|
2628
2758
|
padding: 14px 18px;
|
|
2759
|
+
min-width: 220px;
|
|
2629
2760
|
max-width: 300px;
|
|
2630
2761
|
bottom: 94px;
|
|
2631
2762
|
}
|
|
@@ -2634,6 +2765,21 @@ let SpeechOSMicButton = class SpeechOSMicButton extends i$1 {
|
|
|
2634
2765
|
padding: 8px 14px;
|
|
2635
2766
|
font-size: 14px;
|
|
2636
2767
|
}
|
|
2768
|
+
|
|
2769
|
+
.no-audio-warning {
|
|
2770
|
+
padding: 12px 16px;
|
|
2771
|
+
gap: 10px;
|
|
2772
|
+
bottom: 145px; /* Above button and waveform on mobile */
|
|
2773
|
+
}
|
|
2774
|
+
|
|
2775
|
+
.no-audio-warning .warning-text {
|
|
2776
|
+
font-size: 15px;
|
|
2777
|
+
}
|
|
2778
|
+
|
|
2779
|
+
.no-audio-warning .settings-link {
|
|
2780
|
+
padding: 6px 12px;
|
|
2781
|
+
font-size: 14px;
|
|
2782
|
+
}
|
|
2637
2783
|
}
|
|
2638
2784
|
`,
|
|
2639
2785
|
]; }
|
|
@@ -2691,6 +2837,14 @@ let SpeechOSMicButton = class SpeechOSMicButton extends i$1 {
|
|
|
2691
2837
|
composed: true,
|
|
2692
2838
|
}));
|
|
2693
2839
|
}
|
|
2840
|
+
handleOpenSettings(e) {
|
|
2841
|
+
e.stopPropagation();
|
|
2842
|
+
e.preventDefault();
|
|
2843
|
+
this.dispatchEvent(new CustomEvent("open-settings", {
|
|
2844
|
+
bubbles: true,
|
|
2845
|
+
composed: true,
|
|
2846
|
+
}));
|
|
2847
|
+
}
|
|
2694
2848
|
getButtonClass() {
|
|
2695
2849
|
const classes = ["mic-button"];
|
|
2696
2850
|
if (this.expanded && this.recordingState === "idle") {
|
|
@@ -2775,13 +2929,16 @@ let SpeechOSMicButton = class SpeechOSMicButton extends i$1 {
|
|
|
2775
2929
|
}
|
|
2776
2930
|
return this.recordingState;
|
|
2777
2931
|
}
|
|
2778
|
-
|
|
2779
|
-
if (this.
|
|
2932
|
+
getActionFeedbackLabel() {
|
|
2933
|
+
if (this.actionFeedback === "command-success") {
|
|
2780
2934
|
return "Got it!";
|
|
2781
2935
|
}
|
|
2782
|
-
if (this.
|
|
2936
|
+
if (this.actionFeedback === "command-none") {
|
|
2783
2937
|
return "No command matched";
|
|
2784
2938
|
}
|
|
2939
|
+
if (this.actionFeedback === "edit-empty") {
|
|
2940
|
+
return "Couldn't understand edit";
|
|
2941
|
+
}
|
|
2785
2942
|
return "";
|
|
2786
2943
|
}
|
|
2787
2944
|
render() {
|
|
@@ -2791,9 +2948,9 @@ let SpeechOSMicButton = class SpeechOSMicButton extends i$1 {
|
|
|
2791
2948
|
const showSiriEdit = this.recordingState === "processing" && this.activeAction === "edit";
|
|
2792
2949
|
const statusLabel = this.getStatusLabel();
|
|
2793
2950
|
const showVisualizer = this.shouldShowVisualizer();
|
|
2794
|
-
// Show status label during recording (either visualizer or edit text) OR
|
|
2795
|
-
const
|
|
2796
|
-
const showStatus = this.recordingState === "recording" ||
|
|
2951
|
+
// Show status label during recording (either visualizer or edit text) OR action feedback
|
|
2952
|
+
const showActionFeedback = this.recordingState === "idle" && this.actionFeedback !== null;
|
|
2953
|
+
const showStatus = this.recordingState === "recording" || showActionFeedback;
|
|
2797
2954
|
const showCancel = this.recordingState === "connecting" ||
|
|
2798
2955
|
this.recordingState === "recording" ||
|
|
2799
2956
|
this.recordingState === "processing";
|
|
@@ -2825,13 +2982,46 @@ let SpeechOSMicButton = class SpeechOSMicButton extends i$1 {
|
|
|
2825
2982
|
? b `
|
|
2826
2983
|
<div class="error-message ${showError ? "visible" : ""}">
|
|
2827
2984
|
${this.errorMessage}
|
|
2828
|
-
|
|
2829
|
-
|
|
2830
|
-
|
|
2985
|
+
${this.showRetryButton
|
|
2986
|
+
? b `
|
|
2987
|
+
<button class="retry-button" @click="${this.handleRetry}">
|
|
2988
|
+
Retry Connection
|
|
2989
|
+
</button>
|
|
2990
|
+
`
|
|
2991
|
+
: ""}
|
|
2831
2992
|
</div>
|
|
2832
2993
|
`
|
|
2833
2994
|
: ""}
|
|
2834
2995
|
|
|
2996
|
+
<div
|
|
2997
|
+
class="no-audio-warning ${this.showNoAudioWarning &&
|
|
2998
|
+
this.recordingState === "recording"
|
|
2999
|
+
? "visible"
|
|
3000
|
+
: ""}"
|
|
3001
|
+
>
|
|
3002
|
+
<svg
|
|
3003
|
+
class="warning-icon"
|
|
3004
|
+
width="16"
|
|
3005
|
+
height="16"
|
|
3006
|
+
viewBox="0 0 24 24"
|
|
3007
|
+
fill="none"
|
|
3008
|
+
stroke="currentColor"
|
|
3009
|
+
stroke-width="2"
|
|
3010
|
+
stroke-linecap="round"
|
|
3011
|
+
stroke-linejoin="round"
|
|
3012
|
+
>
|
|
3013
|
+
<path
|
|
3014
|
+
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"
|
|
3015
|
+
/>
|
|
3016
|
+
<line x1="12" y1="9" x2="12" y2="13" />
|
|
3017
|
+
<line x1="12" y1="17" x2="12.01" y2="17" />
|
|
3018
|
+
</svg>
|
|
3019
|
+
<span class="warning-text">We're not hearing anything</span>
|
|
3020
|
+
<button class="settings-link" @click="${this.handleOpenSettings}">
|
|
3021
|
+
Check Settings
|
|
3022
|
+
</button>
|
|
3023
|
+
</div>
|
|
3024
|
+
|
|
2835
3025
|
<button
|
|
2836
3026
|
class="${this.getButtonClass()}"
|
|
2837
3027
|
@click="${this.handleClick}"
|
|
@@ -2844,14 +3034,14 @@ let SpeechOSMicButton = class SpeechOSMicButton extends i$1 {
|
|
|
2844
3034
|
</button>
|
|
2845
3035
|
|
|
2846
3036
|
<span
|
|
2847
|
-
class="status-label ${showStatus ? "visible" : ""} ${
|
|
2848
|
-
?
|
|
3037
|
+
class="status-label ${showStatus ? "visible" : ""} ${showActionFeedback
|
|
3038
|
+
? this.actionFeedback
|
|
2849
3039
|
: showVisualizer
|
|
2850
3040
|
? "visualizer"
|
|
2851
3041
|
: this.getStatusClass()}"
|
|
2852
3042
|
>
|
|
2853
|
-
${
|
|
2854
|
-
? this.
|
|
3043
|
+
${showActionFeedback
|
|
3044
|
+
? this.getActionFeedbackLabel()
|
|
2855
3045
|
: showVisualizer
|
|
2856
3046
|
? b `<speechos-audio-visualizer
|
|
2857
3047
|
?active="${showVisualizer}"
|
|
@@ -2897,9 +3087,15 @@ __decorate([
|
|
|
2897
3087
|
__decorate([
|
|
2898
3088
|
n({ type: String })
|
|
2899
3089
|
], SpeechOSMicButton.prototype, "errorMessage", void 0);
|
|
3090
|
+
__decorate([
|
|
3091
|
+
n({ type: Boolean })
|
|
3092
|
+
], SpeechOSMicButton.prototype, "showRetryButton", void 0);
|
|
2900
3093
|
__decorate([
|
|
2901
3094
|
n({ type: String })
|
|
2902
|
-
], SpeechOSMicButton.prototype, "
|
|
3095
|
+
], SpeechOSMicButton.prototype, "actionFeedback", void 0);
|
|
3096
|
+
__decorate([
|
|
3097
|
+
n({ type: Boolean })
|
|
3098
|
+
], SpeechOSMicButton.prototype, "showNoAudioWarning", void 0);
|
|
2903
3099
|
SpeechOSMicButton = __decorate([
|
|
2904
3100
|
t$1("speechos-mic-button")
|
|
2905
3101
|
], SpeechOSMicButton);
|
|
@@ -5929,6 +6125,7 @@ let SpeechOSDictationOutputModal = class SpeechOSDictationOutputModal extends i$
|
|
|
5929
6125
|
super(...arguments);
|
|
5930
6126
|
this.open = false;
|
|
5931
6127
|
this.text = "";
|
|
6128
|
+
this.mode = "dictation";
|
|
5932
6129
|
this.copied = false;
|
|
5933
6130
|
this.copyTimeout = null;
|
|
5934
6131
|
}
|
|
@@ -6008,6 +6205,41 @@ let SpeechOSDictationOutputModal = class SpeechOSDictationOutputModal extends i$
|
|
|
6008
6205
|
color: #10b981;
|
|
6009
6206
|
flex-shrink: 0;
|
|
6010
6207
|
}
|
|
6208
|
+
|
|
6209
|
+
/* Edit mode styles */
|
|
6210
|
+
:host([mode="edit"]) .logo-icon {
|
|
6211
|
+
background: linear-gradient(135deg, #8b5cf6 0%, #6366f1 100%);
|
|
6212
|
+
}
|
|
6213
|
+
|
|
6214
|
+
:host([mode="edit"]) .modal-title {
|
|
6215
|
+
background: linear-gradient(135deg, #a78bfa 0%, #818cf8 100%);
|
|
6216
|
+
-webkit-background-clip: text;
|
|
6217
|
+
-webkit-text-fill-color: transparent;
|
|
6218
|
+
background-clip: text;
|
|
6219
|
+
}
|
|
6220
|
+
|
|
6221
|
+
:host([mode="edit"]) .hint {
|
|
6222
|
+
background: rgba(139, 92, 246, 0.08);
|
|
6223
|
+
}
|
|
6224
|
+
|
|
6225
|
+
:host([mode="edit"]) .hint-icon {
|
|
6226
|
+
color: #8b5cf6;
|
|
6227
|
+
}
|
|
6228
|
+
|
|
6229
|
+
:host([mode="edit"]) .btn-primary {
|
|
6230
|
+
background: linear-gradient(135deg, #8b5cf6 0%, #7c3aed 100%);
|
|
6231
|
+
box-shadow: 0 4px 12px rgba(139, 92, 246, 0.3);
|
|
6232
|
+
}
|
|
6233
|
+
|
|
6234
|
+
:host([mode="edit"]) .btn-primary:hover {
|
|
6235
|
+
background: linear-gradient(135deg, #a78bfa 0%, #8b5cf6 100%);
|
|
6236
|
+
box-shadow: 0 6px 16px rgba(139, 92, 246, 0.4);
|
|
6237
|
+
}
|
|
6238
|
+
|
|
6239
|
+
:host([mode="edit"]) .btn-success {
|
|
6240
|
+
background: linear-gradient(135deg, #a78bfa 0%, #8b5cf6 100%);
|
|
6241
|
+
box-shadow: 0 4px 12px rgba(167, 139, 250, 0.3);
|
|
6242
|
+
}
|
|
6011
6243
|
`,
|
|
6012
6244
|
]; }
|
|
6013
6245
|
disconnectedCallback() {
|
|
@@ -6060,6 +6292,17 @@ let SpeechOSDictationOutputModal = class SpeechOSDictationOutputModal extends i$
|
|
|
6060
6292
|
console.error("[SpeechOS] Failed to copy text:", err);
|
|
6061
6293
|
}
|
|
6062
6294
|
}
|
|
6295
|
+
get modalTitle() {
|
|
6296
|
+
return this.mode === "edit" ? "Edit Complete" : "Dictation Complete";
|
|
6297
|
+
}
|
|
6298
|
+
get modalIcon() {
|
|
6299
|
+
return this.mode === "edit" ? editIcon(18) : micIcon(18);
|
|
6300
|
+
}
|
|
6301
|
+
get hintText() {
|
|
6302
|
+
return this.mode === "edit"
|
|
6303
|
+
? "Tip: The editor didn't accept the edit. Copy and paste manually."
|
|
6304
|
+
: "Tip: Focus a text field first to auto-insert next time";
|
|
6305
|
+
}
|
|
6063
6306
|
render() {
|
|
6064
6307
|
return b `
|
|
6065
6308
|
<div
|
|
@@ -6069,8 +6312,8 @@ let SpeechOSDictationOutputModal = class SpeechOSDictationOutputModal extends i$
|
|
|
6069
6312
|
<div class="modal-card">
|
|
6070
6313
|
<div class="modal-header">
|
|
6071
6314
|
<div class="header-content">
|
|
6072
|
-
<div class="logo-icon">${
|
|
6073
|
-
<h2 class="modal-title"
|
|
6315
|
+
<div class="logo-icon">${this.modalIcon}</div>
|
|
6316
|
+
<h2 class="modal-title">${this.modalTitle}</h2>
|
|
6074
6317
|
</div>
|
|
6075
6318
|
<button
|
|
6076
6319
|
class="close-button"
|
|
@@ -6087,7 +6330,7 @@ let SpeechOSDictationOutputModal = class SpeechOSDictationOutputModal extends i$
|
|
|
6087
6330
|
<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">
|
|
6088
6331
|
<circle cx="12" cy="12" r="10"/><path d="M12 16v-4M12 8h.01"/>
|
|
6089
6332
|
</svg>
|
|
6090
|
-
<span
|
|
6333
|
+
<span>${this.hintText}</span>
|
|
6091
6334
|
</div>
|
|
6092
6335
|
</div>
|
|
6093
6336
|
|
|
@@ -6114,6 +6357,9 @@ __decorate([
|
|
|
6114
6357
|
__decorate([
|
|
6115
6358
|
n({ type: String })
|
|
6116
6359
|
], SpeechOSDictationOutputModal.prototype, "text", void 0);
|
|
6360
|
+
__decorate([
|
|
6361
|
+
n({ type: String, reflect: true })
|
|
6362
|
+
], SpeechOSDictationOutputModal.prototype, "mode", void 0);
|
|
6117
6363
|
__decorate([
|
|
6118
6364
|
r()
|
|
6119
6365
|
], SpeechOSDictationOutputModal.prototype, "copied", void 0);
|
|
@@ -6273,15 +6519,29 @@ var SpeechOSWidget_1;
|
|
|
6273
6519
|
* duration so users can see the visual feedback before transitioning to recording.
|
|
6274
6520
|
*/
|
|
6275
6521
|
const MIN_CONNECTING_ANIMATION_MS = 200;
|
|
6522
|
+
/**
|
|
6523
|
+
* Time to wait for a transcription event before showing the "no audio" warning (in milliseconds).
|
|
6524
|
+
* If no transcription:interim event is received within this time during recording,
|
|
6525
|
+
* it indicates the server isn't receiving/processing audio.
|
|
6526
|
+
*/
|
|
6527
|
+
const NO_AUDIO_WARNING_TIMEOUT_MS = 5000;
|
|
6528
|
+
/**
|
|
6529
|
+
* Number of consecutive actions with empty results before showing warning on next action.
|
|
6530
|
+
*/
|
|
6531
|
+
const CONSECUTIVE_NO_AUDIO_THRESHOLD = 2;
|
|
6276
6532
|
let SpeechOSWidget = class SpeechOSWidget extends i$1 {
|
|
6277
6533
|
constructor() {
|
|
6278
6534
|
super(...arguments);
|
|
6279
6535
|
this.widgetState = state.getState();
|
|
6280
6536
|
this.settingsOpen = false;
|
|
6537
|
+
this.settingsOpenFromWarning = false;
|
|
6281
6538
|
this.dictationModalOpen = false;
|
|
6282
6539
|
this.dictationModalText = "";
|
|
6540
|
+
this.dictationModalMode = "dictation";
|
|
6283
6541
|
this.editHelpModalOpen = false;
|
|
6284
|
-
this.
|
|
6542
|
+
this.actionFeedback = null;
|
|
6543
|
+
this.showNoAudioWarning = false;
|
|
6544
|
+
this.isErrorRetryable = true;
|
|
6285
6545
|
this.dictationTargetElement = null;
|
|
6286
6546
|
this.editTargetElement = null;
|
|
6287
6547
|
this.dictationCursorStart = null;
|
|
@@ -6293,7 +6553,7 @@ let SpeechOSWidget = class SpeechOSWidget extends i$1 {
|
|
|
6293
6553
|
this.modalElement = null;
|
|
6294
6554
|
this.dictationModalElement = null;
|
|
6295
6555
|
this.editHelpModalElement = null;
|
|
6296
|
-
this.
|
|
6556
|
+
this.actionFeedbackTimeout = null;
|
|
6297
6557
|
this.customPosition = null;
|
|
6298
6558
|
this.isDragging = false;
|
|
6299
6559
|
this.dragStartPos = null;
|
|
@@ -6303,6 +6563,11 @@ let SpeechOSWidget = class SpeechOSWidget extends i$1 {
|
|
|
6303
6563
|
this.suppressNextClick = false;
|
|
6304
6564
|
this.boundViewportResizeHandler = null;
|
|
6305
6565
|
this.boundScrollHandler = null;
|
|
6566
|
+
// No-audio warning state tracking
|
|
6567
|
+
this.consecutiveNoAudioActions = 0;
|
|
6568
|
+
this.transcriptionReceived = false;
|
|
6569
|
+
this.noAudioWarningTimeout = null;
|
|
6570
|
+
this.transcriptionInterimUnsubscribe = null;
|
|
6306
6571
|
}
|
|
6307
6572
|
static { SpeechOSWidget_1 = this; }
|
|
6308
6573
|
static { this.styles = [
|
|
@@ -6385,6 +6650,7 @@ let SpeechOSWidget = class SpeechOSWidget extends i$1 {
|
|
|
6385
6650
|
this.modalElement = document.createElement("speechos-settings-modal");
|
|
6386
6651
|
this.modalElement.addEventListener("modal-close", () => {
|
|
6387
6652
|
this.settingsOpen = false;
|
|
6653
|
+
this.settingsOpenFromWarning = false;
|
|
6388
6654
|
});
|
|
6389
6655
|
document.body.appendChild(this.modalElement);
|
|
6390
6656
|
// Mount dictation output modal
|
|
@@ -6400,7 +6666,17 @@ let SpeechOSWidget = class SpeechOSWidget extends i$1 {
|
|
|
6400
6666
|
});
|
|
6401
6667
|
document.body.appendChild(this.editHelpModalElement);
|
|
6402
6668
|
this.stateUnsubscribe = state.subscribe((newState) => {
|
|
6403
|
-
if (!newState.isVisible
|
|
6669
|
+
if (!newState.isVisible) {
|
|
6670
|
+
if (getConfig().debug && this.settingsOpen) {
|
|
6671
|
+
console.log("[SpeechOS] Closing settings modal: widget hidden");
|
|
6672
|
+
}
|
|
6673
|
+
this.settingsOpen = false;
|
|
6674
|
+
this.settingsOpenFromWarning = false;
|
|
6675
|
+
}
|
|
6676
|
+
else if (!newState.isExpanded && !this.settingsOpenFromWarning) {
|
|
6677
|
+
if (getConfig().debug && this.settingsOpen) {
|
|
6678
|
+
console.log("[SpeechOS] Closing settings modal: widget collapsed");
|
|
6679
|
+
}
|
|
6404
6680
|
this.settingsOpen = false;
|
|
6405
6681
|
}
|
|
6406
6682
|
// Clear custom position when focused element changes (re-anchor to new element)
|
|
@@ -6414,6 +6690,8 @@ let SpeechOSWidget = class SpeechOSWidget extends i$1 {
|
|
|
6414
6690
|
this.errorEventUnsubscribe = events.on("error", (payload) => {
|
|
6415
6691
|
if (this.widgetState.recordingState !== "idle" &&
|
|
6416
6692
|
this.widgetState.recordingState !== "error") {
|
|
6693
|
+
// Check if this is a non-retryable error (e.g., CSP blocked connection)
|
|
6694
|
+
this.isErrorRetryable = payload.code !== "connection_blocked";
|
|
6417
6695
|
state.setError(payload.message);
|
|
6418
6696
|
getBackend().disconnect().catch(() => { });
|
|
6419
6697
|
}
|
|
@@ -6446,9 +6724,9 @@ let SpeechOSWidget = class SpeechOSWidget extends i$1 {
|
|
|
6446
6724
|
this.editHelpModalElement.remove();
|
|
6447
6725
|
this.editHelpModalElement = null;
|
|
6448
6726
|
}
|
|
6449
|
-
if (this.
|
|
6450
|
-
clearTimeout(this.
|
|
6451
|
-
this.
|
|
6727
|
+
if (this.actionFeedbackTimeout) {
|
|
6728
|
+
clearTimeout(this.actionFeedbackTimeout);
|
|
6729
|
+
this.actionFeedbackTimeout = null;
|
|
6452
6730
|
}
|
|
6453
6731
|
if (this.stateUnsubscribe) {
|
|
6454
6732
|
this.stateUnsubscribe();
|
|
@@ -6476,6 +6754,7 @@ let SpeechOSWidget = class SpeechOSWidget extends i$1 {
|
|
|
6476
6754
|
window.removeEventListener("scroll", this.boundScrollHandler);
|
|
6477
6755
|
this.boundScrollHandler = null;
|
|
6478
6756
|
}
|
|
6757
|
+
this.cleanupNoAudioWarningTracking();
|
|
6479
6758
|
}
|
|
6480
6759
|
updated(changedProperties) {
|
|
6481
6760
|
if (changedProperties.has("settingsOpen") && this.modalElement) {
|
|
@@ -6487,6 +6766,9 @@ let SpeechOSWidget = class SpeechOSWidget extends i$1 {
|
|
|
6487
6766
|
if (changedProperties.has("dictationModalText") && this.dictationModalElement) {
|
|
6488
6767
|
this.dictationModalElement.text = this.dictationModalText;
|
|
6489
6768
|
}
|
|
6769
|
+
if (changedProperties.has("dictationModalMode") && this.dictationModalElement) {
|
|
6770
|
+
this.dictationModalElement.mode = this.dictationModalMode;
|
|
6771
|
+
}
|
|
6490
6772
|
if (changedProperties.has("editHelpModalOpen") && this.editHelpModalElement) {
|
|
6491
6773
|
this.editHelpModalElement.open = this.editHelpModalOpen;
|
|
6492
6774
|
}
|
|
@@ -6653,7 +6935,7 @@ let SpeechOSWidget = class SpeechOSWidget extends i$1 {
|
|
|
6653
6935
|
}
|
|
6654
6936
|
if (this.widgetState.recordingState === "idle") {
|
|
6655
6937
|
// Clear command feedback on any mic click
|
|
6656
|
-
this.
|
|
6938
|
+
this.clearActionFeedback();
|
|
6657
6939
|
// If we're expanding, prefetch the token to reduce latency when user selects an action
|
|
6658
6940
|
if (!this.widgetState.isExpanded) {
|
|
6659
6941
|
// Fire and forget - we don't need to wait for this (LiveKit only)
|
|
@@ -6674,6 +6956,8 @@ let SpeechOSWidget = class SpeechOSWidget extends i$1 {
|
|
|
6674
6956
|
}
|
|
6675
6957
|
}
|
|
6676
6958
|
async handleStopRecording() {
|
|
6959
|
+
// Clean up no-audio warning tracking
|
|
6960
|
+
this.cleanupNoAudioWarningTracking();
|
|
6677
6961
|
if (this.widgetState.activeAction === "edit") {
|
|
6678
6962
|
await this.handleStopEdit();
|
|
6679
6963
|
}
|
|
@@ -6685,14 +6969,27 @@ let SpeechOSWidget = class SpeechOSWidget extends i$1 {
|
|
|
6685
6969
|
const backend = getBackend();
|
|
6686
6970
|
try {
|
|
6687
6971
|
const transcription = await this.withMinDisplayTime(backend.stopVoiceSession(), 300);
|
|
6972
|
+
// Track result for consecutive failure detection
|
|
6973
|
+
this.trackActionResult(!!transcription);
|
|
6688
6974
|
if (transcription) {
|
|
6975
|
+
if (getConfig().debug) {
|
|
6976
|
+
console.log("[SpeechOS] Transcription received:", {
|
|
6977
|
+
transcription,
|
|
6978
|
+
dictationTargetElement: this.dictationTargetElement,
|
|
6979
|
+
tagName: this.dictationTargetElement?.tagName,
|
|
6980
|
+
});
|
|
6981
|
+
}
|
|
6689
6982
|
// Check if we have a target element to insert into
|
|
6690
6983
|
if (this.dictationTargetElement) {
|
|
6691
6984
|
this.insertTranscription(transcription);
|
|
6692
6985
|
}
|
|
6693
6986
|
else {
|
|
6694
6987
|
// No target element - show dictation output modal
|
|
6988
|
+
if (getConfig().debug) {
|
|
6989
|
+
console.log("[SpeechOS] No target element, showing dictation modal");
|
|
6990
|
+
}
|
|
6695
6991
|
this.dictationModalText = transcription;
|
|
6992
|
+
this.dictationModalMode = "dictation";
|
|
6696
6993
|
this.dictationModalOpen = true;
|
|
6697
6994
|
}
|
|
6698
6995
|
transcriptStore.saveTranscript(transcription, "dictate");
|
|
@@ -6704,6 +7001,8 @@ let SpeechOSWidget = class SpeechOSWidget extends i$1 {
|
|
|
6704
7001
|
backend.startAutoRefresh?.();
|
|
6705
7002
|
}
|
|
6706
7003
|
catch (error) {
|
|
7004
|
+
// Track as failed result
|
|
7005
|
+
this.trackActionResult(false);
|
|
6707
7006
|
const errorMessage = error instanceof Error ? error.message : "Failed to transcribe audio";
|
|
6708
7007
|
if (errorMessage !== "Disconnected") {
|
|
6709
7008
|
state.setError(errorMessage);
|
|
@@ -6713,6 +7012,8 @@ let SpeechOSWidget = class SpeechOSWidget extends i$1 {
|
|
|
6713
7012
|
}
|
|
6714
7013
|
}
|
|
6715
7014
|
async handleCancelOperation() {
|
|
7015
|
+
// Clean up no-audio warning tracking
|
|
7016
|
+
this.cleanupNoAudioWarningTracking();
|
|
6716
7017
|
await getBackend().disconnect();
|
|
6717
7018
|
if (this.widgetState.recordingState === "error") {
|
|
6718
7019
|
state.clearError();
|
|
@@ -6742,7 +7043,7 @@ let SpeechOSWidget = class SpeechOSWidget extends i$1 {
|
|
|
6742
7043
|
}
|
|
6743
7044
|
}
|
|
6744
7045
|
handleCloseWidget() {
|
|
6745
|
-
this.
|
|
7046
|
+
this.clearActionFeedback();
|
|
6746
7047
|
getBackend().stopAutoRefresh?.();
|
|
6747
7048
|
state.hide();
|
|
6748
7049
|
}
|
|
@@ -6833,45 +7134,70 @@ let SpeechOSWidget = class SpeechOSWidget extends i$1 {
|
|
|
6833
7134
|
return;
|
|
6834
7135
|
}
|
|
6835
7136
|
const tagName = target.tagName.toLowerCase();
|
|
7137
|
+
const originalContent = this.getElementContent(target) || "";
|
|
6836
7138
|
if (tagName === "input" || tagName === "textarea") {
|
|
6837
7139
|
const inputEl = target;
|
|
7140
|
+
// Restore cursor position before inserting
|
|
6838
7141
|
const start = this.dictationCursorStart ?? inputEl.value.length;
|
|
6839
7142
|
const end = this.dictationCursorEnd ?? inputEl.value.length;
|
|
6840
|
-
|
|
6841
|
-
|
|
6842
|
-
inputEl
|
|
6843
|
-
if (this.supportsSelection(inputEl)) {
|
|
6844
|
-
const newCursorPos = start + text.length;
|
|
6845
|
-
inputEl.setSelectionRange(newCursorPos, newCursorPos);
|
|
6846
|
-
}
|
|
6847
|
-
inputEl.dispatchEvent(new Event("input", { bubbles: true }));
|
|
6848
|
-
inputEl.focus();
|
|
7143
|
+
inputEl.setSelectionRange(start, end);
|
|
7144
|
+
// Use text-field-edit to insert text (handles undo, events, etc.)
|
|
7145
|
+
insertTextIntoField(inputEl, text);
|
|
6849
7146
|
state.setFocusedElement(inputEl);
|
|
6850
7147
|
}
|
|
6851
7148
|
else if (target.isContentEditable) {
|
|
6852
7149
|
target.focus();
|
|
6853
7150
|
state.setFocusedElement(target);
|
|
6854
|
-
|
|
6855
|
-
target
|
|
6856
|
-
const selection = window.getSelection();
|
|
6857
|
-
if (selection) {
|
|
6858
|
-
const range = document.createRange();
|
|
6859
|
-
range.selectNodeContents(textNode);
|
|
6860
|
-
range.collapse(false);
|
|
6861
|
-
selection.removeAllRanges();
|
|
6862
|
-
selection.addRange(range);
|
|
6863
|
-
}
|
|
6864
|
-
target.dispatchEvent(new Event("input", { bubbles: true }));
|
|
7151
|
+
// Use text-field-edit for contentEditable elements
|
|
7152
|
+
insertTextIntoField(target, text);
|
|
6865
7153
|
}
|
|
6866
7154
|
events.emit("transcription:inserted", { text, element: target });
|
|
7155
|
+
// Verify insertion was applied after DOM updates
|
|
7156
|
+
this.verifyInsertionApplied(target, text, originalContent);
|
|
6867
7157
|
this.dictationTargetElement = null;
|
|
6868
7158
|
this.dictationCursorStart = null;
|
|
6869
7159
|
this.dictationCursorEnd = null;
|
|
6870
7160
|
}
|
|
7161
|
+
/**
|
|
7162
|
+
* Verify that a dictation insertion was actually applied to the target element.
|
|
7163
|
+
* Some custom editors (CodeMirror, Monaco, Slate, etc.) don't respond to
|
|
7164
|
+
* standard DOM editing methods. If the insertion fails, show a fallback modal.
|
|
7165
|
+
*/
|
|
7166
|
+
verifyInsertionApplied(target, insertedText, originalContent) {
|
|
7167
|
+
// Use requestAnimationFrame to check after DOM updates
|
|
7168
|
+
requestAnimationFrame(() => {
|
|
7169
|
+
const tagName = target.tagName.toLowerCase();
|
|
7170
|
+
let currentContent = "";
|
|
7171
|
+
if (tagName === "input" || tagName === "textarea") {
|
|
7172
|
+
currentContent = target.value;
|
|
7173
|
+
}
|
|
7174
|
+
else if (target.isContentEditable) {
|
|
7175
|
+
currentContent = target.textContent || "";
|
|
7176
|
+
}
|
|
7177
|
+
// Check if the insertion was applied:
|
|
7178
|
+
// - Content should contain the inserted text
|
|
7179
|
+
// - Or content should be different from original (for empty fields)
|
|
7180
|
+
const insertionApplied = currentContent.includes(insertedText) ||
|
|
7181
|
+
(originalContent === "" && currentContent !== "");
|
|
7182
|
+
if (!insertionApplied) {
|
|
7183
|
+
if (getConfig().debug) {
|
|
7184
|
+
console.log("[SpeechOS] Dictation failed to insert, showing fallback modal", {
|
|
7185
|
+
insertedText,
|
|
7186
|
+
currentContent,
|
|
7187
|
+
originalContent,
|
|
7188
|
+
});
|
|
7189
|
+
}
|
|
7190
|
+
// Show fallback modal with dictation mode styling
|
|
7191
|
+
this.dictationModalText = insertedText;
|
|
7192
|
+
this.dictationModalMode = "dictation";
|
|
7193
|
+
this.dictationModalOpen = true;
|
|
7194
|
+
}
|
|
7195
|
+
});
|
|
7196
|
+
}
|
|
6871
7197
|
handleActionSelect(event) {
|
|
6872
7198
|
const { action } = event.detail;
|
|
6873
7199
|
// Clear any existing command feedback when a new action is selected
|
|
6874
|
-
this.
|
|
7200
|
+
this.clearActionFeedback();
|
|
6875
7201
|
state.setActiveAction(action);
|
|
6876
7202
|
if (action === "dictate") {
|
|
6877
7203
|
this.startDictation();
|
|
@@ -6906,6 +7232,13 @@ let SpeechOSWidget = class SpeechOSWidget extends i$1 {
|
|
|
6906
7232
|
this.dictationTargetElement = this.widgetState.focusedElement;
|
|
6907
7233
|
this.dictationCursorStart = null;
|
|
6908
7234
|
this.dictationCursorEnd = null;
|
|
7235
|
+
if (getConfig().debug) {
|
|
7236
|
+
console.log("[SpeechOS] startDictation:", {
|
|
7237
|
+
focusedElement: this.widgetState.focusedElement,
|
|
7238
|
+
dictationTargetElement: this.dictationTargetElement,
|
|
7239
|
+
tagName: this.dictationTargetElement?.tagName,
|
|
7240
|
+
});
|
|
7241
|
+
}
|
|
6909
7242
|
if (this.dictationTargetElement) {
|
|
6910
7243
|
const tagName = this.dictationTargetElement.tagName.toLowerCase();
|
|
6911
7244
|
if (tagName === "input" || tagName === "textarea") {
|
|
@@ -6931,13 +7264,18 @@ let SpeechOSWidget = class SpeechOSWidget extends i$1 {
|
|
|
6931
7264
|
// Ensure minimum animation duration before transitioning to recording
|
|
6932
7265
|
const elapsed = Date.now() - connectingStartTime;
|
|
6933
7266
|
const remainingDelay = MIN_CONNECTING_ANIMATION_MS - elapsed;
|
|
7267
|
+
const startRecording = () => {
|
|
7268
|
+
if (state.getState().recordingState === "error") {
|
|
7269
|
+
return;
|
|
7270
|
+
}
|
|
7271
|
+
state.setRecordingState("recording");
|
|
7272
|
+
this.startNoAudioWarningTracking();
|
|
7273
|
+
};
|
|
6934
7274
|
if (remainingDelay > 0) {
|
|
6935
|
-
setTimeout(
|
|
6936
|
-
state.setRecordingState("recording");
|
|
6937
|
-
}, remainingDelay);
|
|
7275
|
+
setTimeout(startRecording, remainingDelay);
|
|
6938
7276
|
}
|
|
6939
7277
|
else {
|
|
6940
|
-
|
|
7278
|
+
startRecording();
|
|
6941
7279
|
}
|
|
6942
7280
|
},
|
|
6943
7281
|
});
|
|
@@ -6945,7 +7283,10 @@ let SpeechOSWidget = class SpeechOSWidget extends i$1 {
|
|
|
6945
7283
|
catch (error) {
|
|
6946
7284
|
const errorMessage = error instanceof Error ? error.message : "Connection failed";
|
|
6947
7285
|
if (errorMessage !== "Disconnected") {
|
|
6948
|
-
state
|
|
7286
|
+
// Only set error if not already in error state (error event may have already set it)
|
|
7287
|
+
if (this.widgetState.recordingState !== "error") {
|
|
7288
|
+
state.setError(`Failed to connect: ${errorMessage}`);
|
|
7289
|
+
}
|
|
6949
7290
|
await backend.disconnect();
|
|
6950
7291
|
}
|
|
6951
7292
|
}
|
|
@@ -6955,6 +7296,13 @@ let SpeechOSWidget = class SpeechOSWidget extends i$1 {
|
|
|
6955
7296
|
this.editSelectionStart = null;
|
|
6956
7297
|
this.editSelectionEnd = null;
|
|
6957
7298
|
this.editSelectedText = "";
|
|
7299
|
+
if (getConfig().debug) {
|
|
7300
|
+
console.log("[SpeechOS] startEdit:", {
|
|
7301
|
+
focusedElement: this.widgetState.focusedElement,
|
|
7302
|
+
editTargetElement: this.editTargetElement,
|
|
7303
|
+
tagName: this.editTargetElement?.tagName,
|
|
7304
|
+
});
|
|
7305
|
+
}
|
|
6958
7306
|
if (this.editTargetElement) {
|
|
6959
7307
|
const tagName = this.editTargetElement.tagName.toLowerCase();
|
|
6960
7308
|
if (tagName === "input" || tagName === "textarea") {
|
|
@@ -6965,7 +7313,8 @@ let SpeechOSWidget = class SpeechOSWidget extends i$1 {
|
|
|
6965
7313
|
const start = this.editSelectionStart ?? 0;
|
|
6966
7314
|
const end = this.editSelectionEnd ?? 0;
|
|
6967
7315
|
if (start !== end) {
|
|
6968
|
-
|
|
7316
|
+
// Use getFieldSelection from text-field-edit
|
|
7317
|
+
this.editSelectedText = getFieldSelection(inputEl);
|
|
6969
7318
|
}
|
|
6970
7319
|
}
|
|
6971
7320
|
else {
|
|
@@ -6974,13 +7323,11 @@ let SpeechOSWidget = class SpeechOSWidget extends i$1 {
|
|
|
6974
7323
|
}
|
|
6975
7324
|
}
|
|
6976
7325
|
else if (this.editTargetElement.isContentEditable) {
|
|
6977
|
-
|
|
6978
|
-
|
|
6979
|
-
|
|
6980
|
-
|
|
6981
|
-
|
|
6982
|
-
this.editSelectedText = selectedText;
|
|
6983
|
-
}
|
|
7326
|
+
// Use getFieldSelection from text-field-edit for contentEditable too
|
|
7327
|
+
const selectedText = getFieldSelection(this.editTargetElement);
|
|
7328
|
+
this.editSelectionStart = 0;
|
|
7329
|
+
this.editSelectionEnd = selectedText.length;
|
|
7330
|
+
this.editSelectedText = selectedText;
|
|
6984
7331
|
}
|
|
6985
7332
|
}
|
|
6986
7333
|
// Capture the content to edit at start time (sent with auth message)
|
|
@@ -6997,13 +7344,18 @@ let SpeechOSWidget = class SpeechOSWidget extends i$1 {
|
|
|
6997
7344
|
// Ensure minimum animation duration before transitioning to recording
|
|
6998
7345
|
const elapsed = Date.now() - connectingStartTime;
|
|
6999
7346
|
const remainingDelay = MIN_CONNECTING_ANIMATION_MS - elapsed;
|
|
7347
|
+
const startRecording = () => {
|
|
7348
|
+
if (state.getState().recordingState === "error") {
|
|
7349
|
+
return;
|
|
7350
|
+
}
|
|
7351
|
+
state.setRecordingState("recording");
|
|
7352
|
+
this.startNoAudioWarningTracking();
|
|
7353
|
+
};
|
|
7000
7354
|
if (remainingDelay > 0) {
|
|
7001
|
-
setTimeout(
|
|
7002
|
-
state.setRecordingState("recording");
|
|
7003
|
-
}, remainingDelay);
|
|
7355
|
+
setTimeout(startRecording, remainingDelay);
|
|
7004
7356
|
}
|
|
7005
7357
|
else {
|
|
7006
|
-
|
|
7358
|
+
startRecording();
|
|
7007
7359
|
}
|
|
7008
7360
|
},
|
|
7009
7361
|
});
|
|
@@ -7011,7 +7363,10 @@ let SpeechOSWidget = class SpeechOSWidget extends i$1 {
|
|
|
7011
7363
|
catch (error) {
|
|
7012
7364
|
const errorMessage = error instanceof Error ? error.message : "Connection failed";
|
|
7013
7365
|
if (errorMessage !== "Disconnected") {
|
|
7014
|
-
state
|
|
7366
|
+
// Only set error if not already in error state (error event may have already set it)
|
|
7367
|
+
if (this.widgetState.recordingState !== "error") {
|
|
7368
|
+
state.setError(`Failed to connect: ${errorMessage}`);
|
|
7369
|
+
}
|
|
7015
7370
|
await backend.disconnect();
|
|
7016
7371
|
}
|
|
7017
7372
|
}
|
|
@@ -7022,12 +7377,30 @@ let SpeechOSWidget = class SpeechOSWidget extends i$1 {
|
|
|
7022
7377
|
const backend = getBackend();
|
|
7023
7378
|
try {
|
|
7024
7379
|
const editedText = await this.withMinDisplayTime(backend.requestEditText(originalContent), 300);
|
|
7380
|
+
// Check if server returned no change (couldn't understand edit)
|
|
7381
|
+
const noChange = editedText.trim() === originalContent.trim();
|
|
7382
|
+
if (noChange) {
|
|
7383
|
+
this.trackActionResult(false);
|
|
7384
|
+
this.showActionFeedback("edit-empty");
|
|
7385
|
+
state.completeRecording();
|
|
7386
|
+
this.editTargetElement = null;
|
|
7387
|
+
this.editSelectionStart = null;
|
|
7388
|
+
this.editSelectionEnd = null;
|
|
7389
|
+
this.editSelectedText = "";
|
|
7390
|
+
backend.disconnect().catch(() => { });
|
|
7391
|
+
backend.startAutoRefresh?.();
|
|
7392
|
+
return;
|
|
7393
|
+
}
|
|
7394
|
+
// Track result - got a meaningful change
|
|
7395
|
+
this.trackActionResult(true);
|
|
7025
7396
|
this.applyEdit(editedText);
|
|
7026
7397
|
backend.disconnect().catch(() => { });
|
|
7027
7398
|
// Start auto-refresh to keep token fresh for subsequent commands (LiveKit only)
|
|
7028
7399
|
backend.startAutoRefresh?.();
|
|
7029
7400
|
}
|
|
7030
7401
|
catch (error) {
|
|
7402
|
+
// Track as failed result
|
|
7403
|
+
this.trackActionResult(false);
|
|
7031
7404
|
const errorMessage = error instanceof Error ? error.message : "Failed to apply edit";
|
|
7032
7405
|
if (errorMessage !== "Disconnected") {
|
|
7033
7406
|
state.setError(errorMessage);
|
|
@@ -7050,13 +7423,18 @@ let SpeechOSWidget = class SpeechOSWidget extends i$1 {
|
|
|
7050
7423
|
// Ensure minimum animation duration before transitioning to recording
|
|
7051
7424
|
const elapsed = Date.now() - connectingStartTime;
|
|
7052
7425
|
const remainingDelay = MIN_CONNECTING_ANIMATION_MS - elapsed;
|
|
7426
|
+
const startRecording = () => {
|
|
7427
|
+
if (state.getState().recordingState === "error") {
|
|
7428
|
+
return;
|
|
7429
|
+
}
|
|
7430
|
+
state.setRecordingState("recording");
|
|
7431
|
+
this.startNoAudioWarningTracking();
|
|
7432
|
+
};
|
|
7053
7433
|
if (remainingDelay > 0) {
|
|
7054
|
-
setTimeout(
|
|
7055
|
-
state.setRecordingState("recording");
|
|
7056
|
-
}, remainingDelay);
|
|
7434
|
+
setTimeout(startRecording, remainingDelay);
|
|
7057
7435
|
}
|
|
7058
7436
|
else {
|
|
7059
|
-
|
|
7437
|
+
startRecording();
|
|
7060
7438
|
}
|
|
7061
7439
|
},
|
|
7062
7440
|
});
|
|
@@ -7064,7 +7442,10 @@ let SpeechOSWidget = class SpeechOSWidget extends i$1 {
|
|
|
7064
7442
|
catch (error) {
|
|
7065
7443
|
const errorMessage = error instanceof Error ? error.message : "Connection failed";
|
|
7066
7444
|
if (errorMessage !== "Disconnected") {
|
|
7067
|
-
state
|
|
7445
|
+
// Only set error if not already in error state (error event may have already set it)
|
|
7446
|
+
if (this.widgetState.recordingState !== "error") {
|
|
7447
|
+
state.setError(`Failed to connect: ${errorMessage}`);
|
|
7448
|
+
}
|
|
7068
7449
|
await backend.disconnect();
|
|
7069
7450
|
}
|
|
7070
7451
|
}
|
|
@@ -7076,6 +7457,8 @@ let SpeechOSWidget = class SpeechOSWidget extends i$1 {
|
|
|
7076
7457
|
const backend = getBackend();
|
|
7077
7458
|
try {
|
|
7078
7459
|
const result = await this.withMinDisplayTime(backend.requestCommand(commands), 300);
|
|
7460
|
+
// Track result - null result means no command matched (possibly no audio)
|
|
7461
|
+
this.trackActionResult(result !== null);
|
|
7079
7462
|
// Get input text from the backend if available
|
|
7080
7463
|
const inputText = backend.getLastInputText?.();
|
|
7081
7464
|
// Save to transcript store
|
|
@@ -7093,12 +7476,14 @@ let SpeechOSWidget = class SpeechOSWidget extends i$1 {
|
|
|
7093
7476
|
// Keep widget visible but collapsed (just mic button, no action bubbles)
|
|
7094
7477
|
state.setState({ isExpanded: false });
|
|
7095
7478
|
// Show command feedback
|
|
7096
|
-
this.
|
|
7479
|
+
this.showActionFeedback(result ? "command-success" : "command-none");
|
|
7097
7480
|
backend.disconnect().catch(() => { });
|
|
7098
7481
|
// Start auto-refresh to keep token fresh for subsequent commands (LiveKit only)
|
|
7099
7482
|
backend.startAutoRefresh?.();
|
|
7100
7483
|
}
|
|
7101
7484
|
catch (error) {
|
|
7485
|
+
// Track as failed result
|
|
7486
|
+
this.trackActionResult(false);
|
|
7102
7487
|
const errorMessage = error instanceof Error ? error.message : "Failed to process command";
|
|
7103
7488
|
if (errorMessage !== "Disconnected") {
|
|
7104
7489
|
state.setError(errorMessage);
|
|
@@ -7106,24 +7491,110 @@ let SpeechOSWidget = class SpeechOSWidget extends i$1 {
|
|
|
7106
7491
|
}
|
|
7107
7492
|
}
|
|
7108
7493
|
}
|
|
7109
|
-
|
|
7110
|
-
this.
|
|
7494
|
+
showActionFeedback(feedback) {
|
|
7495
|
+
this.actionFeedback = feedback;
|
|
7111
7496
|
// Clear any existing timeout
|
|
7112
|
-
if (this.
|
|
7113
|
-
clearTimeout(this.
|
|
7497
|
+
if (this.actionFeedbackTimeout) {
|
|
7498
|
+
clearTimeout(this.actionFeedbackTimeout);
|
|
7114
7499
|
}
|
|
7115
7500
|
// Auto-dismiss after 4 seconds
|
|
7116
|
-
this.
|
|
7117
|
-
this.
|
|
7118
|
-
this.
|
|
7501
|
+
this.actionFeedbackTimeout = window.setTimeout(() => {
|
|
7502
|
+
this.actionFeedback = null;
|
|
7503
|
+
this.actionFeedbackTimeout = null;
|
|
7119
7504
|
}, 4000);
|
|
7120
7505
|
}
|
|
7121
|
-
|
|
7122
|
-
if (this.
|
|
7123
|
-
clearTimeout(this.
|
|
7124
|
-
this.
|
|
7506
|
+
clearActionFeedback() {
|
|
7507
|
+
if (this.actionFeedbackTimeout) {
|
|
7508
|
+
clearTimeout(this.actionFeedbackTimeout);
|
|
7509
|
+
this.actionFeedbackTimeout = null;
|
|
7510
|
+
}
|
|
7511
|
+
this.actionFeedback = null;
|
|
7512
|
+
}
|
|
7513
|
+
/**
|
|
7514
|
+
* Start tracking for no-audio warning when recording begins.
|
|
7515
|
+
*/
|
|
7516
|
+
startNoAudioWarningTracking() {
|
|
7517
|
+
this.transcriptionReceived = false;
|
|
7518
|
+
this.showNoAudioWarning = false;
|
|
7519
|
+
// If we had consecutive failures, show warning immediately
|
|
7520
|
+
if (this.consecutiveNoAudioActions >= CONSECUTIVE_NO_AUDIO_THRESHOLD) {
|
|
7521
|
+
this.showNoAudioWarning = true;
|
|
7522
|
+
}
|
|
7523
|
+
// Start timeout - if no transcription within 5s, show warning
|
|
7524
|
+
this.noAudioWarningTimeout = window.setTimeout(() => {
|
|
7525
|
+
if (!this.transcriptionReceived &&
|
|
7526
|
+
this.widgetState.recordingState === "recording") {
|
|
7527
|
+
this.showNoAudioWarning = true;
|
|
7528
|
+
}
|
|
7529
|
+
}, NO_AUDIO_WARNING_TIMEOUT_MS);
|
|
7530
|
+
// Subscribe to transcription:interim events
|
|
7531
|
+
this.transcriptionInterimUnsubscribe = events.on("transcription:interim", () => {
|
|
7532
|
+
this.transcriptionReceived = true;
|
|
7533
|
+
if (this.showNoAudioWarning) {
|
|
7534
|
+
this.showNoAudioWarning = false;
|
|
7535
|
+
}
|
|
7536
|
+
});
|
|
7537
|
+
}
|
|
7538
|
+
/**
|
|
7539
|
+
* Clean up no-audio warning tracking when recording stops.
|
|
7540
|
+
*/
|
|
7541
|
+
cleanupNoAudioWarningTracking() {
|
|
7542
|
+
if (this.noAudioWarningTimeout !== null) {
|
|
7543
|
+
clearTimeout(this.noAudioWarningTimeout);
|
|
7544
|
+
this.noAudioWarningTimeout = null;
|
|
7125
7545
|
}
|
|
7126
|
-
this.
|
|
7546
|
+
if (this.transcriptionInterimUnsubscribe) {
|
|
7547
|
+
this.transcriptionInterimUnsubscribe();
|
|
7548
|
+
this.transcriptionInterimUnsubscribe = null;
|
|
7549
|
+
}
|
|
7550
|
+
this.showNoAudioWarning = false;
|
|
7551
|
+
}
|
|
7552
|
+
/**
|
|
7553
|
+
* Track the result of an action for consecutive failure detection.
|
|
7554
|
+
*/
|
|
7555
|
+
trackActionResult(hasContent) {
|
|
7556
|
+
if (hasContent) {
|
|
7557
|
+
this.consecutiveNoAudioActions = 0;
|
|
7558
|
+
}
|
|
7559
|
+
else {
|
|
7560
|
+
this.consecutiveNoAudioActions++;
|
|
7561
|
+
}
|
|
7562
|
+
}
|
|
7563
|
+
/**
|
|
7564
|
+
* Handle opening settings from the no-audio warning.
|
|
7565
|
+
* Stops the current dictation session immediately, then opens settings.
|
|
7566
|
+
*/
|
|
7567
|
+
async handleOpenSettingsFromWarning() {
|
|
7568
|
+
if (getConfig().debug) {
|
|
7569
|
+
console.log("[SpeechOS] No-audio settings link clicked");
|
|
7570
|
+
}
|
|
7571
|
+
// Clean up no-audio warning tracking first
|
|
7572
|
+
this.cleanupNoAudioWarningTracking();
|
|
7573
|
+
// Keep settings open even if widget collapses
|
|
7574
|
+
this.settingsOpenFromWarning = true;
|
|
7575
|
+
// Stop audio capture and disconnect immediately (don't wait for transcription)
|
|
7576
|
+
// Kick this off before opening settings so audio stops fast, but don't block UI.
|
|
7577
|
+
const disconnectPromise = getBackend().disconnect().catch((error) => {
|
|
7578
|
+
if (getConfig().debug) {
|
|
7579
|
+
console.log("[SpeechOS] Disconnect failed while opening settings", error);
|
|
7580
|
+
}
|
|
7581
|
+
});
|
|
7582
|
+
// Update UI state to idle
|
|
7583
|
+
state.cancelRecording();
|
|
7584
|
+
// Clear target elements
|
|
7585
|
+
this.dictationTargetElement = null;
|
|
7586
|
+
this.editTargetElement = null;
|
|
7587
|
+
this.dictationCursorStart = null;
|
|
7588
|
+
this.dictationCursorEnd = null;
|
|
7589
|
+
this.editSelectionStart = null;
|
|
7590
|
+
this.editSelectionEnd = null;
|
|
7591
|
+
this.editSelectedText = "";
|
|
7592
|
+
// Open settings modal
|
|
7593
|
+
this.settingsOpen = true;
|
|
7594
|
+
if (getConfig().debug) {
|
|
7595
|
+
console.log("[SpeechOS] Settings modal opened from no-audio warning");
|
|
7596
|
+
}
|
|
7597
|
+
await disconnectPromise;
|
|
7127
7598
|
}
|
|
7128
7599
|
supportsSelection(element) {
|
|
7129
7600
|
if (element.tagName.toLowerCase() === "textarea") {
|
|
@@ -7139,21 +7610,14 @@ let SpeechOSWidget = class SpeechOSWidget extends i$1 {
|
|
|
7139
7610
|
const tagName = element.tagName.toLowerCase();
|
|
7140
7611
|
if (tagName === "input" || tagName === "textarea") {
|
|
7141
7612
|
const inputEl = element;
|
|
7142
|
-
const
|
|
7143
|
-
|
|
7144
|
-
|
|
7145
|
-
const hasSelection = start !== end;
|
|
7146
|
-
if (hasSelection) {
|
|
7147
|
-
return fullContent.substring(start, end);
|
|
7148
|
-
}
|
|
7149
|
-
return fullContent;
|
|
7613
|
+
const selectedText = getFieldSelection(inputEl);
|
|
7614
|
+
// If there's selected text, return it; otherwise return full content
|
|
7615
|
+
return selectedText || inputEl.value;
|
|
7150
7616
|
}
|
|
7151
7617
|
else if (element.isContentEditable) {
|
|
7152
|
-
const
|
|
7153
|
-
|
|
7154
|
-
|
|
7155
|
-
}
|
|
7156
|
-
return element.textContent || "";
|
|
7618
|
+
const selectedText = getFieldSelection(element);
|
|
7619
|
+
// If there's selected text, return it; otherwise return full content
|
|
7620
|
+
return selectedText || element.textContent || "";
|
|
7157
7621
|
}
|
|
7158
7622
|
return "";
|
|
7159
7623
|
}
|
|
@@ -7168,40 +7632,44 @@ let SpeechOSWidget = class SpeechOSWidget extends i$1 {
|
|
|
7168
7632
|
if (tagName === "input" || tagName === "textarea") {
|
|
7169
7633
|
const inputEl = target;
|
|
7170
7634
|
originalContent = inputEl.value;
|
|
7171
|
-
|
|
7172
|
-
|
|
7173
|
-
|
|
7174
|
-
|
|
7175
|
-
|
|
7176
|
-
|
|
7177
|
-
|
|
7178
|
-
|
|
7179
|
-
else {
|
|
7180
|
-
inputEl.setSelectionRange(0, inputEl.value.length);
|
|
7181
|
-
}
|
|
7182
|
-
document.execCommand("insertText", false, editedText);
|
|
7635
|
+
// Restore the original selection/cursor position
|
|
7636
|
+
const selectionStart = this.editSelectionStart ?? 0;
|
|
7637
|
+
const selectionEnd = this.editSelectionEnd ?? inputEl.value.length;
|
|
7638
|
+
const hasSelection = selectionStart !== selectionEnd;
|
|
7639
|
+
if (hasSelection) {
|
|
7640
|
+
// Restore selection, then use insertTextIntoField() to replace it
|
|
7641
|
+
inputEl.setSelectionRange(selectionStart, selectionEnd);
|
|
7642
|
+
insertTextIntoField(inputEl, editedText);
|
|
7183
7643
|
}
|
|
7184
7644
|
else {
|
|
7185
|
-
|
|
7186
|
-
inputEl
|
|
7645
|
+
// No selection - replace entire content using setFieldText()
|
|
7646
|
+
setFieldText(inputEl, editedText);
|
|
7187
7647
|
}
|
|
7188
7648
|
state.setFocusedElement(inputEl);
|
|
7189
7649
|
}
|
|
7190
7650
|
else if (target.isContentEditable) {
|
|
7191
7651
|
originalContent = target.textContent || "";
|
|
7192
|
-
target.focus();
|
|
7193
|
-
state.setFocusedElement(target);
|
|
7194
7652
|
const hasSelection = this.editSelectionStart !== null &&
|
|
7195
7653
|
this.editSelectionEnd !== null &&
|
|
7196
7654
|
this.editSelectionStart !== this.editSelectionEnd;
|
|
7197
|
-
if (
|
|
7655
|
+
if (hasSelection) {
|
|
7656
|
+
// Selection exists - focus and insert (assumes selection is still active or we restore it)
|
|
7657
|
+
target.focus();
|
|
7658
|
+
insertTextIntoField(target, editedText);
|
|
7659
|
+
}
|
|
7660
|
+
else {
|
|
7661
|
+
// No selection - select all content first, then replace with insertTextIntoField()
|
|
7662
|
+
target.focus();
|
|
7198
7663
|
const selection = window.getSelection();
|
|
7199
|
-
|
|
7200
|
-
|
|
7201
|
-
|
|
7202
|
-
|
|
7664
|
+
if (selection) {
|
|
7665
|
+
const range = document.createRange();
|
|
7666
|
+
range.selectNodeContents(target);
|
|
7667
|
+
selection.removeAllRanges();
|
|
7668
|
+
selection.addRange(range);
|
|
7669
|
+
}
|
|
7670
|
+
insertTextIntoField(target, editedText);
|
|
7203
7671
|
}
|
|
7204
|
-
|
|
7672
|
+
state.setFocusedElement(target);
|
|
7205
7673
|
}
|
|
7206
7674
|
transcriptStore.saveTranscript(editedText, "edit", originalContent);
|
|
7207
7675
|
events.emit("edit:applied", {
|
|
@@ -7210,11 +7678,54 @@ let SpeechOSWidget = class SpeechOSWidget extends i$1 {
|
|
|
7210
7678
|
element: target,
|
|
7211
7679
|
});
|
|
7212
7680
|
state.completeRecording();
|
|
7681
|
+
// Verify edit was applied after DOM updates
|
|
7682
|
+
this.verifyEditApplied(target, editedText, originalContent);
|
|
7213
7683
|
this.editTargetElement = null;
|
|
7214
7684
|
this.editSelectionStart = null;
|
|
7215
7685
|
this.editSelectionEnd = null;
|
|
7216
7686
|
this.editSelectedText = "";
|
|
7217
7687
|
}
|
|
7688
|
+
/**
|
|
7689
|
+
* Verify that an edit was actually applied to the target element.
|
|
7690
|
+
* Some custom editors (CodeMirror, Monaco, Slate, etc.) don't respond to
|
|
7691
|
+
* standard DOM editing methods. If the edit fails, show a fallback modal.
|
|
7692
|
+
*/
|
|
7693
|
+
verifyEditApplied(target, editedText, originalContent) {
|
|
7694
|
+
// Use requestAnimationFrame to check after DOM updates
|
|
7695
|
+
requestAnimationFrame(() => {
|
|
7696
|
+
const tagName = target.tagName.toLowerCase();
|
|
7697
|
+
let currentContent = "";
|
|
7698
|
+
if (tagName === "input" || tagName === "textarea") {
|
|
7699
|
+
currentContent = target.value;
|
|
7700
|
+
}
|
|
7701
|
+
else if (target.isContentEditable) {
|
|
7702
|
+
currentContent = target.textContent || "";
|
|
7703
|
+
}
|
|
7704
|
+
// Normalize whitespace for comparison
|
|
7705
|
+
const normalizedCurrent = currentContent.trim();
|
|
7706
|
+
const normalizedEdited = editedText.trim();
|
|
7707
|
+
const normalizedOriginal = originalContent.trim();
|
|
7708
|
+
// Check if the edit was applied:
|
|
7709
|
+
// - Content should be different from original (unless edit was no-op)
|
|
7710
|
+
// - Content should contain or match the edited text
|
|
7711
|
+
const editApplied = normalizedCurrent !== normalizedOriginal ||
|
|
7712
|
+
normalizedCurrent === normalizedEdited ||
|
|
7713
|
+
normalizedCurrent.includes(normalizedEdited);
|
|
7714
|
+
if (!editApplied) {
|
|
7715
|
+
if (getConfig().debug) {
|
|
7716
|
+
console.log("[SpeechOS] Edit failed to apply, showing fallback modal", {
|
|
7717
|
+
expected: editedText,
|
|
7718
|
+
actual: currentContent,
|
|
7719
|
+
original: originalContent,
|
|
7720
|
+
});
|
|
7721
|
+
}
|
|
7722
|
+
// Show fallback modal with edit mode styling
|
|
7723
|
+
this.dictationModalText = editedText;
|
|
7724
|
+
this.dictationModalMode = "edit";
|
|
7725
|
+
this.dictationModalOpen = true;
|
|
7726
|
+
}
|
|
7727
|
+
});
|
|
7728
|
+
}
|
|
7218
7729
|
render() {
|
|
7219
7730
|
if (!this.widgetState.isVisible) {
|
|
7220
7731
|
this.setAttribute("hidden", "");
|
|
@@ -7242,12 +7753,15 @@ let SpeechOSWidget = class SpeechOSWidget extends i$1 {
|
|
|
7242
7753
|
activeAction="${this.widgetState.activeAction || ""}"
|
|
7243
7754
|
editPreviewText="${this.editSelectedText}"
|
|
7244
7755
|
errorMessage="${this.widgetState.errorMessage || ""}"
|
|
7245
|
-
|
|
7756
|
+
?showRetryButton="${this.isErrorRetryable}"
|
|
7757
|
+
.actionFeedback="${this.actionFeedback}"
|
|
7758
|
+
?showNoAudioWarning="${this.showNoAudioWarning}"
|
|
7246
7759
|
@mic-click="${this.handleMicClick}"
|
|
7247
7760
|
@stop-recording="${this.handleStopRecording}"
|
|
7248
7761
|
@cancel-operation="${this.handleCancelOperation}"
|
|
7249
7762
|
@retry-connection="${this.handleRetryConnection}"
|
|
7250
7763
|
@close-widget="${this.handleCloseWidget}"
|
|
7764
|
+
@open-settings="${this.handleOpenSettingsFromWarning}"
|
|
7251
7765
|
></speechos-mic-button>
|
|
7252
7766
|
</div>
|
|
7253
7767
|
</div>
|
|
@@ -7266,12 +7780,21 @@ __decorate([
|
|
|
7266
7780
|
__decorate([
|
|
7267
7781
|
r()
|
|
7268
7782
|
], SpeechOSWidget.prototype, "dictationModalText", void 0);
|
|
7783
|
+
__decorate([
|
|
7784
|
+
r()
|
|
7785
|
+
], SpeechOSWidget.prototype, "dictationModalMode", void 0);
|
|
7269
7786
|
__decorate([
|
|
7270
7787
|
r()
|
|
7271
7788
|
], SpeechOSWidget.prototype, "editHelpModalOpen", void 0);
|
|
7272
7789
|
__decorate([
|
|
7273
7790
|
r()
|
|
7274
|
-
], SpeechOSWidget.prototype, "
|
|
7791
|
+
], SpeechOSWidget.prototype, "actionFeedback", void 0);
|
|
7792
|
+
__decorate([
|
|
7793
|
+
r()
|
|
7794
|
+
], SpeechOSWidget.prototype, "showNoAudioWarning", void 0);
|
|
7795
|
+
__decorate([
|
|
7796
|
+
r()
|
|
7797
|
+
], SpeechOSWidget.prototype, "isErrorRetryable", void 0);
|
|
7275
7798
|
SpeechOSWidget = SpeechOSWidget_1 = __decorate([
|
|
7276
7799
|
t$1("speechos-widget")
|
|
7277
7800
|
], SpeechOSWidget);
|