@speechos/client 0.2.3 → 0.2.5
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/config.d.ts +11 -0
- package/dist/config.d.ts.map +1 -1
- package/dist/config.test.d.ts +5 -0
- package/dist/config.test.d.ts.map +1 -0
- package/dist/form-detector.d.ts.map +1 -1
- package/dist/index.cjs +925 -148
- package/dist/index.cjs.map +1 -1
- package/dist/index.iife.js +941 -140
- package/dist/index.iife.js.map +1 -1
- package/dist/index.iife.min.js +604 -96
- package/dist/index.iife.min.js.map +1 -1
- package/dist/index.js +925 -148
- package/dist/index.js.map +1 -1
- package/dist/speechos.d.ts.map +1 -1
- package/dist/ui/dictation-output-modal.d.ts +25 -0
- package/dist/ui/dictation-output-modal.d.ts.map +1 -0
- package/dist/ui/dictation-output-modal.test.d.ts +5 -0
- package/dist/ui/dictation-output-modal.test.d.ts.map +1 -0
- package/dist/ui/edit-help-modal.d.ts +19 -0
- package/dist/ui/edit-help-modal.d.ts.map +1 -0
- package/dist/ui/edit-help-modal.test.d.ts +5 -0
- package/dist/ui/edit-help-modal.test.d.ts.map +1 -0
- package/dist/ui/index.d.ts +4 -0
- package/dist/ui/index.d.ts.map +1 -1
- package/dist/ui/mic-button.d.ts +2 -0
- package/dist/ui/mic-button.d.ts.map +1 -1
- package/dist/ui/mic-button.test.d.ts +5 -0
- package/dist/ui/mic-button.test.d.ts.map +1 -0
- package/dist/ui/styles/popup-modal-styles.d.ts +7 -0
- package/dist/ui/styles/popup-modal-styles.d.ts.map +1 -0
- package/dist/ui/widget.d.ts +11 -0
- package/dist/ui/widget.d.ts.map +1 -1
- package/package.json +1 -1
package/dist/index.cjs
CHANGED
|
@@ -4,6 +4,84 @@ Object.defineProperty(exports, '__esModule', { value: true });
|
|
|
4
4
|
|
|
5
5
|
var core = require('@speechos/core');
|
|
6
6
|
|
|
7
|
+
/**
|
|
8
|
+
* Client configuration for SpeechOS
|
|
9
|
+
* Extends core config with UI/widget-specific options
|
|
10
|
+
*/
|
|
11
|
+
/**
|
|
12
|
+
* Default client configuration values
|
|
13
|
+
*/
|
|
14
|
+
const defaultClientConfig = {
|
|
15
|
+
commands: [],
|
|
16
|
+
zIndex: 999999,
|
|
17
|
+
alwaysVisible: false,
|
|
18
|
+
};
|
|
19
|
+
/**
|
|
20
|
+
* Current client configuration singleton
|
|
21
|
+
*/
|
|
22
|
+
let currentClientConfig = { ...defaultClientConfig };
|
|
23
|
+
/**
|
|
24
|
+
* Validate and resolve client-specific config
|
|
25
|
+
* @param config - User-provided configuration
|
|
26
|
+
* @returns Resolved client configuration
|
|
27
|
+
*/
|
|
28
|
+
function validateClientConfig(config) {
|
|
29
|
+
const resolved = {
|
|
30
|
+
commands: config.commands ?? defaultClientConfig.commands,
|
|
31
|
+
zIndex: config.zIndex ?? defaultClientConfig.zIndex,
|
|
32
|
+
alwaysVisible: config.alwaysVisible ?? defaultClientConfig.alwaysVisible,
|
|
33
|
+
};
|
|
34
|
+
// Validate zIndex
|
|
35
|
+
if (typeof resolved.zIndex !== "number" || resolved.zIndex < 0) {
|
|
36
|
+
console.warn(`Invalid zIndex "${resolved.zIndex}". Using default ${defaultClientConfig.zIndex}.`);
|
|
37
|
+
resolved.zIndex = defaultClientConfig.zIndex;
|
|
38
|
+
}
|
|
39
|
+
return resolved;
|
|
40
|
+
}
|
|
41
|
+
/**
|
|
42
|
+
* Set the client configuration
|
|
43
|
+
* @param config - Client configuration to set
|
|
44
|
+
*/
|
|
45
|
+
function setClientConfig(config) {
|
|
46
|
+
currentClientConfig = validateClientConfig(config);
|
|
47
|
+
}
|
|
48
|
+
/**
|
|
49
|
+
* Get the current client configuration
|
|
50
|
+
*/
|
|
51
|
+
function getClientConfig() {
|
|
52
|
+
return { ...currentClientConfig };
|
|
53
|
+
}
|
|
54
|
+
/**
|
|
55
|
+
* Reset client configuration to defaults
|
|
56
|
+
*/
|
|
57
|
+
function resetClientConfig() {
|
|
58
|
+
currentClientConfig = { ...defaultClientConfig };
|
|
59
|
+
}
|
|
60
|
+
/**
|
|
61
|
+
* Check if commands are configured (for showing Command button in widget)
|
|
62
|
+
*/
|
|
63
|
+
function hasCommands() {
|
|
64
|
+
return currentClientConfig.commands.length > 0;
|
|
65
|
+
}
|
|
66
|
+
/**
|
|
67
|
+
* Get configured commands
|
|
68
|
+
*/
|
|
69
|
+
function getCommands() {
|
|
70
|
+
return [...currentClientConfig.commands];
|
|
71
|
+
}
|
|
72
|
+
/**
|
|
73
|
+
* Get widget z-index
|
|
74
|
+
*/
|
|
75
|
+
function getZIndex() {
|
|
76
|
+
return currentClientConfig.zIndex;
|
|
77
|
+
}
|
|
78
|
+
/**
|
|
79
|
+
* Check if widget should always be visible
|
|
80
|
+
*/
|
|
81
|
+
function isAlwaysVisible() {
|
|
82
|
+
return currentClientConfig.alwaysVisible;
|
|
83
|
+
}
|
|
84
|
+
|
|
7
85
|
/**
|
|
8
86
|
* Form field focus detection for SpeechOS Client SDK
|
|
9
87
|
* Detects when users focus on form fields and manages widget visibility
|
|
@@ -91,12 +169,12 @@ class FormDetector {
|
|
|
91
169
|
relatedTarget === widget);
|
|
92
170
|
// If focus is going to an element with data-speechos-no-close, don't hide
|
|
93
171
|
const goingToNoCloseElement = Boolean(relatedTarget?.closest("[data-speechos-no-close]"));
|
|
94
|
-
console.log("[SpeechOS] blurHandler:", {
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
});
|
|
172
|
+
// console.log("[SpeechOS] blurHandler:", {
|
|
173
|
+
// relatedTarget,
|
|
174
|
+
// goingToFormField,
|
|
175
|
+
// goingToWidget,
|
|
176
|
+
// goingToNoCloseElement,
|
|
177
|
+
// });
|
|
100
178
|
if (goingToFormField || goingToWidget || goingToNoCloseElement) {
|
|
101
179
|
console.log("[SpeechOS] blurHandler: early return, not hiding");
|
|
102
180
|
return;
|
|
@@ -115,11 +193,15 @@ class FormDetector {
|
|
|
115
193
|
// Check if focus is on an element with data-speechos-no-close
|
|
116
194
|
const isNoCloseElementFocused = Boolean(activeElement?.closest("[data-speechos-no-close]"));
|
|
117
195
|
// Only hide if no form field is focused AND widget isn't focused AND not a no-close element
|
|
196
|
+
// AND alwaysVisible is not enabled
|
|
118
197
|
if (!isFormField(activeElement) &&
|
|
119
198
|
!isWidgetFocused &&
|
|
120
199
|
!isNoCloseElementFocused) {
|
|
121
200
|
core.state.setFocusedElement(null);
|
|
122
|
-
|
|
201
|
+
// Don't hide if alwaysVisible is enabled
|
|
202
|
+
if (!isAlwaysVisible()) {
|
|
203
|
+
core.state.hide();
|
|
204
|
+
}
|
|
123
205
|
core.events.emit("form:blur", { element: null });
|
|
124
206
|
}
|
|
125
207
|
}, 150);
|
|
@@ -193,7 +275,10 @@ class FormDetector {
|
|
|
193
275
|
// Reset state
|
|
194
276
|
this.isWidgetBeingInteracted = false;
|
|
195
277
|
core.state.setFocusedElement(null);
|
|
196
|
-
|
|
278
|
+
// Don't hide if alwaysVisible is enabled
|
|
279
|
+
if (!isAlwaysVisible()) {
|
|
280
|
+
core.state.hide();
|
|
281
|
+
}
|
|
197
282
|
this.isActive = false;
|
|
198
283
|
}
|
|
199
284
|
/**
|
|
@@ -206,76 +291,6 @@ class FormDetector {
|
|
|
206
291
|
// Export singleton instance
|
|
207
292
|
const formDetector = new FormDetector();
|
|
208
293
|
|
|
209
|
-
/**
|
|
210
|
-
* Client configuration for SpeechOS
|
|
211
|
-
* Extends core config with UI/widget-specific options
|
|
212
|
-
*/
|
|
213
|
-
/**
|
|
214
|
-
* Default client configuration values
|
|
215
|
-
*/
|
|
216
|
-
const defaultClientConfig = {
|
|
217
|
-
commands: [],
|
|
218
|
-
zIndex: 999999,
|
|
219
|
-
};
|
|
220
|
-
/**
|
|
221
|
-
* Current client configuration singleton
|
|
222
|
-
*/
|
|
223
|
-
let currentClientConfig = { ...defaultClientConfig };
|
|
224
|
-
/**
|
|
225
|
-
* Validate and resolve client-specific config
|
|
226
|
-
* @param config - User-provided configuration
|
|
227
|
-
* @returns Resolved client configuration
|
|
228
|
-
*/
|
|
229
|
-
function validateClientConfig(config) {
|
|
230
|
-
const resolved = {
|
|
231
|
-
commands: config.commands ?? defaultClientConfig.commands,
|
|
232
|
-
zIndex: config.zIndex ?? defaultClientConfig.zIndex,
|
|
233
|
-
};
|
|
234
|
-
// Validate zIndex
|
|
235
|
-
if (typeof resolved.zIndex !== "number" || resolved.zIndex < 0) {
|
|
236
|
-
console.warn(`Invalid zIndex "${resolved.zIndex}". Using default ${defaultClientConfig.zIndex}.`);
|
|
237
|
-
resolved.zIndex = defaultClientConfig.zIndex;
|
|
238
|
-
}
|
|
239
|
-
return resolved;
|
|
240
|
-
}
|
|
241
|
-
/**
|
|
242
|
-
* Set the client configuration
|
|
243
|
-
* @param config - Client configuration to set
|
|
244
|
-
*/
|
|
245
|
-
function setClientConfig(config) {
|
|
246
|
-
currentClientConfig = validateClientConfig(config);
|
|
247
|
-
}
|
|
248
|
-
/**
|
|
249
|
-
* Get the current client configuration
|
|
250
|
-
*/
|
|
251
|
-
function getClientConfig() {
|
|
252
|
-
return { ...currentClientConfig };
|
|
253
|
-
}
|
|
254
|
-
/**
|
|
255
|
-
* Reset client configuration to defaults
|
|
256
|
-
*/
|
|
257
|
-
function resetClientConfig() {
|
|
258
|
-
currentClientConfig = { ...defaultClientConfig };
|
|
259
|
-
}
|
|
260
|
-
/**
|
|
261
|
-
* Check if commands are configured (for showing Command button in widget)
|
|
262
|
-
*/
|
|
263
|
-
function hasCommands() {
|
|
264
|
-
return currentClientConfig.commands.length > 0;
|
|
265
|
-
}
|
|
266
|
-
/**
|
|
267
|
-
* Get configured commands
|
|
268
|
-
*/
|
|
269
|
-
function getCommands() {
|
|
270
|
-
return [...currentClientConfig.commands];
|
|
271
|
-
}
|
|
272
|
-
/**
|
|
273
|
-
* Get widget z-index
|
|
274
|
-
*/
|
|
275
|
-
function getZIndex() {
|
|
276
|
-
return currentClientConfig.zIndex;
|
|
277
|
-
}
|
|
278
|
-
|
|
279
294
|
/**
|
|
280
295
|
* Text input handler for SpeechOS Client SDK
|
|
281
296
|
* Abstracts cursor/selection detection and text insertion/replacement operations
|
|
@@ -1480,6 +1495,11 @@ const loaderIcon = (size = 20) => {
|
|
|
1480
1495
|
`;
|
|
1481
1496
|
return b `${o(svgString)}`;
|
|
1482
1497
|
};
|
|
1498
|
+
/**
|
|
1499
|
+
* Check icon for "Keep" action
|
|
1500
|
+
* Lucide Check icon paths
|
|
1501
|
+
*/
|
|
1502
|
+
const checkIcon = (size = 18) => createIcon('<path d="M20 6 9 17l-5-5"/>', size);
|
|
1483
1503
|
/**
|
|
1484
1504
|
* X icon for cancel action
|
|
1485
1505
|
* Lucide X icon paths
|
|
@@ -1740,6 +1760,7 @@ let SpeechOSMicButton = class SpeechOSMicButton extends i$1 {
|
|
|
1740
1760
|
this.activeAction = null;
|
|
1741
1761
|
this.editPreviewText = "";
|
|
1742
1762
|
this.errorMessage = null;
|
|
1763
|
+
this.commandFeedback = null;
|
|
1743
1764
|
}
|
|
1744
1765
|
static { this.styles = [
|
|
1745
1766
|
themeStyles,
|
|
@@ -2307,6 +2328,47 @@ let SpeechOSMicButton = class SpeechOSMicButton extends i$1 {
|
|
|
2307
2328
|
}
|
|
2308
2329
|
}
|
|
2309
2330
|
|
|
2331
|
+
/* Command feedback badge - success state (amber/orange) */
|
|
2332
|
+
.status-label.command-success {
|
|
2333
|
+
background: linear-gradient(135deg, #f59e0b 0%, #d97706 100%);
|
|
2334
|
+
box-shadow: 0 4px 12px rgba(245, 158, 11, 0.3);
|
|
2335
|
+
padding-left: 24px;
|
|
2336
|
+
animation: command-feedback-in 0.3s cubic-bezier(0.34, 1.56, 0.64, 1)
|
|
2337
|
+
forwards;
|
|
2338
|
+
}
|
|
2339
|
+
|
|
2340
|
+
.status-label.command-success::before {
|
|
2341
|
+
content: "";
|
|
2342
|
+
position: absolute;
|
|
2343
|
+
top: 50%;
|
|
2344
|
+
left: 8px;
|
|
2345
|
+
width: 12px;
|
|
2346
|
+
height: 12px;
|
|
2347
|
+
transform: translateY(-50%);
|
|
2348
|
+
background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='12' height='12' viewBox='0 0 24 24' fill='none' stroke='white' stroke-width='3' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpath d='M20 6 9 17l-5-5'/%3E%3C/svg%3E");
|
|
2349
|
+
background-repeat: no-repeat;
|
|
2350
|
+
background-position: center;
|
|
2351
|
+
}
|
|
2352
|
+
|
|
2353
|
+
/* Command feedback badge - no match state (neutral gray) */
|
|
2354
|
+
.status-label.command-none {
|
|
2355
|
+
background: #4b5563;
|
|
2356
|
+
box-shadow: 0 4px 12px rgba(75, 85, 99, 0.3);
|
|
2357
|
+
animation: command-feedback-in 0.3s cubic-bezier(0.34, 1.56, 0.64, 1)
|
|
2358
|
+
forwards;
|
|
2359
|
+
}
|
|
2360
|
+
|
|
2361
|
+
@keyframes command-feedback-in {
|
|
2362
|
+
0% {
|
|
2363
|
+
opacity: 0;
|
|
2364
|
+
transform: translateX(-50%) scale(0.8) translateY(4px);
|
|
2365
|
+
}
|
|
2366
|
+
100% {
|
|
2367
|
+
opacity: 1;
|
|
2368
|
+
transform: translateX(-50%) scale(1) translateY(0);
|
|
2369
|
+
}
|
|
2370
|
+
}
|
|
2371
|
+
|
|
2310
2372
|
/* Cancel button - positioned to the right of the main mic button */
|
|
2311
2373
|
.cancel-button {
|
|
2312
2374
|
position: absolute;
|
|
@@ -2554,6 +2616,16 @@ let SpeechOSMicButton = class SpeechOSMicButton extends i$1 {
|
|
|
2554
2616
|
left: 10px;
|
|
2555
2617
|
}
|
|
2556
2618
|
|
|
2619
|
+
.status-label.command-success {
|
|
2620
|
+
padding-left: 30px;
|
|
2621
|
+
}
|
|
2622
|
+
|
|
2623
|
+
.status-label.command-success::before {
|
|
2624
|
+
left: 10px;
|
|
2625
|
+
width: 14px;
|
|
2626
|
+
height: 14px;
|
|
2627
|
+
}
|
|
2628
|
+
|
|
2557
2629
|
.error-message {
|
|
2558
2630
|
font-size: 15px;
|
|
2559
2631
|
padding: 14px 18px;
|
|
@@ -2706,6 +2778,15 @@ let SpeechOSMicButton = class SpeechOSMicButton extends i$1 {
|
|
|
2706
2778
|
}
|
|
2707
2779
|
return this.recordingState;
|
|
2708
2780
|
}
|
|
2781
|
+
getCommandFeedbackLabel() {
|
|
2782
|
+
if (this.commandFeedback === "success") {
|
|
2783
|
+
return "Got it!";
|
|
2784
|
+
}
|
|
2785
|
+
if (this.commandFeedback === "none") {
|
|
2786
|
+
return "No command matched";
|
|
2787
|
+
}
|
|
2788
|
+
return "";
|
|
2789
|
+
}
|
|
2709
2790
|
render() {
|
|
2710
2791
|
const showPulse = this.recordingState === "recording";
|
|
2711
2792
|
const showSiriConnecting = this.recordingState === "connecting";
|
|
@@ -2713,13 +2794,14 @@ let SpeechOSMicButton = class SpeechOSMicButton extends i$1 {
|
|
|
2713
2794
|
const showSiriEdit = this.recordingState === "processing" && this.activeAction === "edit";
|
|
2714
2795
|
const statusLabel = this.getStatusLabel();
|
|
2715
2796
|
const showVisualizer = this.shouldShowVisualizer();
|
|
2716
|
-
// Show status label during recording (either visualizer or edit text)
|
|
2717
|
-
const
|
|
2797
|
+
// Show status label during recording (either visualizer or edit text) OR command feedback
|
|
2798
|
+
const showCommandFeedback = this.recordingState === "idle" && this.commandFeedback !== null;
|
|
2799
|
+
const showStatus = this.recordingState === "recording" || showCommandFeedback;
|
|
2718
2800
|
const showCancel = this.recordingState === "connecting" ||
|
|
2719
2801
|
this.recordingState === "recording" ||
|
|
2720
2802
|
this.recordingState === "processing";
|
|
2721
2803
|
const showError = this.recordingState === "error" && this.errorMessage;
|
|
2722
|
-
// Show close button in idle state (both solo mic and expanded)
|
|
2804
|
+
// Show close button in idle state (both solo mic and expanded), including when showing command feedback
|
|
2723
2805
|
const showClose = this.recordingState === "idle";
|
|
2724
2806
|
return b `
|
|
2725
2807
|
<div class="button-wrapper">
|
|
@@ -2765,15 +2847,19 @@ let SpeechOSMicButton = class SpeechOSMicButton extends i$1 {
|
|
|
2765
2847
|
</button>
|
|
2766
2848
|
|
|
2767
2849
|
<span
|
|
2768
|
-
class="status-label ${showStatus ? "visible" : ""} ${
|
|
2769
|
-
?
|
|
2770
|
-
:
|
|
2850
|
+
class="status-label ${showStatus ? "visible" : ""} ${showCommandFeedback
|
|
2851
|
+
? `command-${this.commandFeedback}`
|
|
2852
|
+
: showVisualizer
|
|
2853
|
+
? "visualizer"
|
|
2854
|
+
: this.getStatusClass()}"
|
|
2771
2855
|
>
|
|
2772
|
-
${
|
|
2773
|
-
?
|
|
2774
|
-
|
|
2775
|
-
|
|
2776
|
-
|
|
2856
|
+
${showCommandFeedback
|
|
2857
|
+
? this.getCommandFeedbackLabel()
|
|
2858
|
+
: showVisualizer
|
|
2859
|
+
? b `<speechos-audio-visualizer
|
|
2860
|
+
?active="${showVisualizer}"
|
|
2861
|
+
></speechos-audio-visualizer>`
|
|
2862
|
+
: statusLabel}
|
|
2777
2863
|
</span>
|
|
2778
2864
|
|
|
2779
2865
|
<button
|
|
@@ -2814,6 +2900,9 @@ __decorate([
|
|
|
2814
2900
|
__decorate([
|
|
2815
2901
|
n({ type: String })
|
|
2816
2902
|
], SpeechOSMicButton.prototype, "errorMessage", void 0);
|
|
2903
|
+
__decorate([
|
|
2904
|
+
n({ type: String })
|
|
2905
|
+
], SpeechOSMicButton.prototype, "commandFeedback", void 0);
|
|
2817
2906
|
SpeechOSMicButton = __decorate([
|
|
2818
2907
|
t$1("speechos-mic-button")
|
|
2819
2908
|
], SpeechOSMicButton);
|
|
@@ -5591,65 +5680,658 @@ SpeechOSSettingsModal = __decorate([
|
|
|
5591
5680
|
], SpeechOSSettingsModal);
|
|
5592
5681
|
|
|
5593
5682
|
/**
|
|
5594
|
-
*
|
|
5595
|
-
*
|
|
5596
|
-
*/
|
|
5597
|
-
var SpeechOSWidget_1;
|
|
5598
|
-
/**
|
|
5599
|
-
* Minimum duration to show the connecting animation (in milliseconds).
|
|
5600
|
-
* Since mic capture starts almost instantly, we enforce a minimum animation
|
|
5601
|
-
* duration so users can see the visual feedback before transitioning to recording.
|
|
5683
|
+
* Shared styles for lightweight popup modals
|
|
5684
|
+
* Used by dictation-output-modal and edit-help-modal
|
|
5602
5685
|
*/
|
|
5603
|
-
|
|
5604
|
-
|
|
5605
|
-
|
|
5606
|
-
|
|
5607
|
-
|
|
5608
|
-
|
|
5609
|
-
|
|
5610
|
-
|
|
5611
|
-
this.dictationCursorStart = null;
|
|
5612
|
-
this.dictationCursorEnd = null;
|
|
5613
|
-
this.editSelectionStart = null;
|
|
5614
|
-
this.editSelectionEnd = null;
|
|
5615
|
-
this.editSelectedText = "";
|
|
5616
|
-
this.boundClickOutsideHandler = null;
|
|
5617
|
-
this.modalElement = null;
|
|
5618
|
-
this.customPosition = null;
|
|
5619
|
-
this.isDragging = false;
|
|
5620
|
-
this.dragStartPos = null;
|
|
5621
|
-
this.dragOffset = { x: 0, y: 0 };
|
|
5622
|
-
this.boundDragMove = null;
|
|
5623
|
-
this.boundDragEnd = null;
|
|
5624
|
-
this.suppressNextClick = false;
|
|
5625
|
-
this.boundViewportResizeHandler = null;
|
|
5626
|
-
this.boundScrollHandler = null;
|
|
5627
|
-
}
|
|
5628
|
-
static { SpeechOSWidget_1 = this; }
|
|
5629
|
-
static { this.styles = [
|
|
5630
|
-
themeStyles,
|
|
5631
|
-
animations,
|
|
5632
|
-
i$4 `
|
|
5633
|
-
:host {
|
|
5634
|
-
position: fixed;
|
|
5635
|
-
bottom: var(--speechos-spacing-md); /* 12px - same as spacing above */
|
|
5636
|
-
z-index: var(--speechos-z-base);
|
|
5637
|
-
pointer-events: none;
|
|
5638
|
-
}
|
|
5639
|
-
|
|
5640
|
-
:host {
|
|
5641
|
-
left: 50%;
|
|
5642
|
-
transform: translateX(-50%);
|
|
5643
|
-
}
|
|
5686
|
+
/** Base popup modal styles - simpler than full settings modal */
|
|
5687
|
+
const popupModalStyles = i$4 `
|
|
5688
|
+
:host {
|
|
5689
|
+
position: fixed;
|
|
5690
|
+
inset: 0;
|
|
5691
|
+
pointer-events: none;
|
|
5692
|
+
z-index: calc(var(--speechos-z-base) + 100);
|
|
5693
|
+
}
|
|
5644
5694
|
|
|
5645
|
-
|
|
5646
|
-
|
|
5647
|
-
|
|
5648
|
-
|
|
5649
|
-
|
|
5695
|
+
.modal-overlay {
|
|
5696
|
+
position: fixed;
|
|
5697
|
+
inset: 0;
|
|
5698
|
+
background: rgba(0, 0, 0, 0.5);
|
|
5699
|
+
display: flex;
|
|
5700
|
+
align-items: center;
|
|
5701
|
+
justify-content: center;
|
|
5702
|
+
z-index: calc(var(--speechos-z-base) + 100);
|
|
5703
|
+
opacity: 0;
|
|
5704
|
+
visibility: hidden;
|
|
5705
|
+
transition: all 0.2s ease;
|
|
5706
|
+
pointer-events: none;
|
|
5707
|
+
backdrop-filter: blur(4px);
|
|
5708
|
+
}
|
|
5650
5709
|
|
|
5651
|
-
|
|
5652
|
-
|
|
5710
|
+
.modal-overlay.open {
|
|
5711
|
+
opacity: 1;
|
|
5712
|
+
visibility: visible;
|
|
5713
|
+
pointer-events: auto;
|
|
5714
|
+
}
|
|
5715
|
+
|
|
5716
|
+
.modal-card {
|
|
5717
|
+
background: #1a1d24;
|
|
5718
|
+
border-radius: 16px;
|
|
5719
|
+
width: 90%;
|
|
5720
|
+
max-width: 400px;
|
|
5721
|
+
display: flex;
|
|
5722
|
+
flex-direction: column;
|
|
5723
|
+
box-shadow: 0 24px 48px rgba(0, 0, 0, 0.4),
|
|
5724
|
+
0 0 0 1px rgba(255, 255, 255, 0.05);
|
|
5725
|
+
transform: scale(0.95) translateY(10px);
|
|
5726
|
+
transition: transform 0.25s cubic-bezier(0.16, 1, 0.3, 1);
|
|
5727
|
+
pointer-events: auto;
|
|
5728
|
+
overflow: hidden;
|
|
5729
|
+
}
|
|
5730
|
+
|
|
5731
|
+
.modal-overlay.open .modal-card {
|
|
5732
|
+
transform: scale(1) translateY(0);
|
|
5733
|
+
}
|
|
5734
|
+
|
|
5735
|
+
.modal-header {
|
|
5736
|
+
display: flex;
|
|
5737
|
+
align-items: center;
|
|
5738
|
+
justify-content: space-between;
|
|
5739
|
+
padding: 16px 20px;
|
|
5740
|
+
border-bottom: 1px solid rgba(255, 255, 255, 0.06);
|
|
5741
|
+
background: rgba(0, 0, 0, 0.2);
|
|
5742
|
+
}
|
|
5743
|
+
|
|
5744
|
+
.modal-title {
|
|
5745
|
+
font-size: 16px;
|
|
5746
|
+
font-weight: 600;
|
|
5747
|
+
color: white;
|
|
5748
|
+
margin: 0;
|
|
5749
|
+
letter-spacing: -0.01em;
|
|
5750
|
+
}
|
|
5751
|
+
|
|
5752
|
+
.close-button {
|
|
5753
|
+
width: 32px;
|
|
5754
|
+
height: 32px;
|
|
5755
|
+
border-radius: 8px;
|
|
5756
|
+
background: transparent;
|
|
5757
|
+
border: none;
|
|
5758
|
+
cursor: pointer;
|
|
5759
|
+
display: flex;
|
|
5760
|
+
align-items: center;
|
|
5761
|
+
justify-content: center;
|
|
5762
|
+
color: rgba(255, 255, 255, 0.5);
|
|
5763
|
+
transition: all 0.15s ease;
|
|
5764
|
+
}
|
|
5765
|
+
|
|
5766
|
+
.close-button:hover {
|
|
5767
|
+
background: rgba(255, 255, 255, 0.08);
|
|
5768
|
+
color: white;
|
|
5769
|
+
}
|
|
5770
|
+
|
|
5771
|
+
.modal-body {
|
|
5772
|
+
padding: 20px;
|
|
5773
|
+
}
|
|
5774
|
+
|
|
5775
|
+
.modal-footer {
|
|
5776
|
+
display: flex;
|
|
5777
|
+
justify-content: flex-end;
|
|
5778
|
+
gap: 10px;
|
|
5779
|
+
padding: 16px 20px;
|
|
5780
|
+
border-top: 1px solid rgba(255, 255, 255, 0.06);
|
|
5781
|
+
background: rgba(0, 0, 0, 0.1);
|
|
5782
|
+
}
|
|
5783
|
+
|
|
5784
|
+
.btn {
|
|
5785
|
+
padding: 10px 18px;
|
|
5786
|
+
border-radius: 8px;
|
|
5787
|
+
font-size: 13px;
|
|
5788
|
+
font-weight: 600;
|
|
5789
|
+
cursor: pointer;
|
|
5790
|
+
transition: all 0.15s ease;
|
|
5791
|
+
border: none;
|
|
5792
|
+
display: inline-flex;
|
|
5793
|
+
align-items: center;
|
|
5794
|
+
gap: 6px;
|
|
5795
|
+
}
|
|
5796
|
+
|
|
5797
|
+
.btn-secondary {
|
|
5798
|
+
background: rgba(255, 255, 255, 0.08);
|
|
5799
|
+
color: rgba(255, 255, 255, 0.8);
|
|
5800
|
+
}
|
|
5801
|
+
|
|
5802
|
+
.btn-secondary:hover {
|
|
5803
|
+
background: rgba(255, 255, 255, 0.12);
|
|
5804
|
+
color: white;
|
|
5805
|
+
}
|
|
5806
|
+
|
|
5807
|
+
.btn-primary {
|
|
5808
|
+
background: linear-gradient(135deg, #10b981 0%, #059669 100%);
|
|
5809
|
+
color: white;
|
|
5810
|
+
box-shadow: 0 2px 8px rgba(16, 185, 129, 0.2);
|
|
5811
|
+
}
|
|
5812
|
+
|
|
5813
|
+
.btn-primary:hover {
|
|
5814
|
+
transform: translateY(-1px);
|
|
5815
|
+
box-shadow: 0 4px 12px rgba(16, 185, 129, 0.3);
|
|
5816
|
+
}
|
|
5817
|
+
|
|
5818
|
+
.btn-primary:active {
|
|
5819
|
+
transform: translateY(0);
|
|
5820
|
+
}
|
|
5821
|
+
|
|
5822
|
+
/* Success state for copy button */
|
|
5823
|
+
.btn-success {
|
|
5824
|
+
background: linear-gradient(135deg, #34d399 0%, #10b981 100%);
|
|
5825
|
+
}
|
|
5826
|
+
|
|
5827
|
+
/* Text display area */
|
|
5828
|
+
.text-display {
|
|
5829
|
+
background: rgba(0, 0, 0, 0.3);
|
|
5830
|
+
border: 1px solid rgba(255, 255, 255, 0.08);
|
|
5831
|
+
border-radius: 10px;
|
|
5832
|
+
padding: 14px 16px;
|
|
5833
|
+
color: white;
|
|
5834
|
+
font-size: 14px;
|
|
5835
|
+
line-height: 1.5;
|
|
5836
|
+
max-height: 200px;
|
|
5837
|
+
overflow-y: auto;
|
|
5838
|
+
white-space: pre-wrap;
|
|
5839
|
+
word-break: break-word;
|
|
5840
|
+
}
|
|
5841
|
+
|
|
5842
|
+
/* Scrollbar styling */
|
|
5843
|
+
.text-display::-webkit-scrollbar {
|
|
5844
|
+
width: 6px;
|
|
5845
|
+
}
|
|
5846
|
+
|
|
5847
|
+
.text-display::-webkit-scrollbar-track {
|
|
5848
|
+
background: transparent;
|
|
5849
|
+
}
|
|
5850
|
+
|
|
5851
|
+
.text-display::-webkit-scrollbar-thumb {
|
|
5852
|
+
background: rgba(255, 255, 255, 0.15);
|
|
5853
|
+
border-radius: 3px;
|
|
5854
|
+
}
|
|
5855
|
+
|
|
5856
|
+
.text-display::-webkit-scrollbar-thumb:hover {
|
|
5857
|
+
background: rgba(255, 255, 255, 0.25);
|
|
5858
|
+
}
|
|
5859
|
+
|
|
5860
|
+
/* Instruction list styling */
|
|
5861
|
+
.instruction-list {
|
|
5862
|
+
list-style: none;
|
|
5863
|
+
padding: 0;
|
|
5864
|
+
margin: 0;
|
|
5865
|
+
}
|
|
5866
|
+
|
|
5867
|
+
.instruction-item {
|
|
5868
|
+
display: flex;
|
|
5869
|
+
align-items: flex-start;
|
|
5870
|
+
gap: 12px;
|
|
5871
|
+
padding: 12px 0;
|
|
5872
|
+
border-bottom: 1px solid rgba(255, 255, 255, 0.06);
|
|
5873
|
+
}
|
|
5874
|
+
|
|
5875
|
+
.instruction-item:last-child {
|
|
5876
|
+
border-bottom: none;
|
|
5877
|
+
}
|
|
5878
|
+
|
|
5879
|
+
.instruction-number {
|
|
5880
|
+
width: 24px;
|
|
5881
|
+
height: 24px;
|
|
5882
|
+
border-radius: 50%;
|
|
5883
|
+
background: linear-gradient(135deg, #10b981 0%, #059669 100%);
|
|
5884
|
+
color: white;
|
|
5885
|
+
font-size: 12px;
|
|
5886
|
+
font-weight: 700;
|
|
5887
|
+
display: flex;
|
|
5888
|
+
align-items: center;
|
|
5889
|
+
justify-content: center;
|
|
5890
|
+
flex-shrink: 0;
|
|
5891
|
+
}
|
|
5892
|
+
|
|
5893
|
+
.instruction-text {
|
|
5894
|
+
font-size: 14px;
|
|
5895
|
+
color: rgba(255, 255, 255, 0.85);
|
|
5896
|
+
line-height: 1.5;
|
|
5897
|
+
padding-top: 2px;
|
|
5898
|
+
}
|
|
5899
|
+
|
|
5900
|
+
/* Mobile adjustments */
|
|
5901
|
+
@media (max-width: 480px) {
|
|
5902
|
+
.modal-card {
|
|
5903
|
+
width: 95%;
|
|
5904
|
+
max-width: none;
|
|
5905
|
+
border-radius: 12px;
|
|
5906
|
+
}
|
|
5907
|
+
|
|
5908
|
+
.modal-header {
|
|
5909
|
+
padding: 14px 16px;
|
|
5910
|
+
}
|
|
5911
|
+
|
|
5912
|
+
.modal-body {
|
|
5913
|
+
padding: 16px;
|
|
5914
|
+
}
|
|
5915
|
+
|
|
5916
|
+
.modal-footer {
|
|
5917
|
+
padding: 14px 16px;
|
|
5918
|
+
}
|
|
5919
|
+
|
|
5920
|
+
.modal-title {
|
|
5921
|
+
font-size: 15px;
|
|
5922
|
+
}
|
|
5923
|
+
}
|
|
5924
|
+
`;
|
|
5925
|
+
|
|
5926
|
+
/**
|
|
5927
|
+
* Dictation output modal component
|
|
5928
|
+
* Displays transcribed text with copy functionality when no field is focused
|
|
5929
|
+
*/
|
|
5930
|
+
let SpeechOSDictationOutputModal = class SpeechOSDictationOutputModal extends i$1 {
|
|
5931
|
+
constructor() {
|
|
5932
|
+
super(...arguments);
|
|
5933
|
+
this.open = false;
|
|
5934
|
+
this.text = "";
|
|
5935
|
+
this.copied = false;
|
|
5936
|
+
this.copyTimeout = null;
|
|
5937
|
+
}
|
|
5938
|
+
static { this.styles = [
|
|
5939
|
+
themeStyles,
|
|
5940
|
+
popupModalStyles,
|
|
5941
|
+
i$4 `
|
|
5942
|
+
.header-content {
|
|
5943
|
+
display: flex;
|
|
5944
|
+
align-items: center;
|
|
5945
|
+
gap: 12px;
|
|
5946
|
+
}
|
|
5947
|
+
|
|
5948
|
+
.logo-icon {
|
|
5949
|
+
width: 32px;
|
|
5950
|
+
height: 32px;
|
|
5951
|
+
border-radius: 8px;
|
|
5952
|
+
background: linear-gradient(135deg, #10b981 0%, #8b5cf6 100%);
|
|
5953
|
+
display: flex;
|
|
5954
|
+
align-items: center;
|
|
5955
|
+
justify-content: center;
|
|
5956
|
+
flex-shrink: 0;
|
|
5957
|
+
}
|
|
5958
|
+
|
|
5959
|
+
.logo-icon svg {
|
|
5960
|
+
width: 18px;
|
|
5961
|
+
height: 18px;
|
|
5962
|
+
color: white;
|
|
5963
|
+
}
|
|
5964
|
+
|
|
5965
|
+
.modal-title {
|
|
5966
|
+
background: linear-gradient(135deg, #34d399 0%, #a78bfa 100%);
|
|
5967
|
+
-webkit-background-clip: text;
|
|
5968
|
+
-webkit-text-fill-color: transparent;
|
|
5969
|
+
background-clip: text;
|
|
5970
|
+
}
|
|
5971
|
+
|
|
5972
|
+
.btn-primary {
|
|
5973
|
+
background: linear-gradient(135deg, #10b981 0%, #059669 100%);
|
|
5974
|
+
box-shadow: 0 4px 12px rgba(16, 185, 129, 0.3);
|
|
5975
|
+
border-radius: 999px;
|
|
5976
|
+
}
|
|
5977
|
+
|
|
5978
|
+
.btn-primary:hover {
|
|
5979
|
+
background: linear-gradient(135deg, #34d399 0%, #10b981 100%);
|
|
5980
|
+
transform: translateY(-2px);
|
|
5981
|
+
box-shadow: 0 6px 16px rgba(16, 185, 129, 0.4);
|
|
5982
|
+
}
|
|
5983
|
+
|
|
5984
|
+
.btn-primary:active {
|
|
5985
|
+
transform: translateY(0);
|
|
5986
|
+
}
|
|
5987
|
+
|
|
5988
|
+
.btn-success {
|
|
5989
|
+
background: linear-gradient(135deg, #34d399 0%, #10b981 100%);
|
|
5990
|
+
box-shadow: 0 4px 12px rgba(52, 211, 153, 0.3);
|
|
5991
|
+
border-radius: 999px;
|
|
5992
|
+
}
|
|
5993
|
+
|
|
5994
|
+
.btn-secondary {
|
|
5995
|
+
border-radius: 999px;
|
|
5996
|
+
}
|
|
5997
|
+
|
|
5998
|
+
.hint {
|
|
5999
|
+
display: flex;
|
|
6000
|
+
align-items: center;
|
|
6001
|
+
gap: 6px;
|
|
6002
|
+
margin-top: 12px;
|
|
6003
|
+
padding: 8px 12px;
|
|
6004
|
+
background: rgba(16, 185, 129, 0.08);
|
|
6005
|
+
border-radius: 8px;
|
|
6006
|
+
font-size: 12px;
|
|
6007
|
+
color: rgba(255, 255, 255, 0.6);
|
|
6008
|
+
}
|
|
6009
|
+
|
|
6010
|
+
.hint-icon {
|
|
6011
|
+
color: #10b981;
|
|
6012
|
+
flex-shrink: 0;
|
|
6013
|
+
}
|
|
6014
|
+
`,
|
|
6015
|
+
]; }
|
|
6016
|
+
disconnectedCallback() {
|
|
6017
|
+
super.disconnectedCallback();
|
|
6018
|
+
if (this.copyTimeout) {
|
|
6019
|
+
clearTimeout(this.copyTimeout);
|
|
6020
|
+
this.copyTimeout = null;
|
|
6021
|
+
}
|
|
6022
|
+
}
|
|
6023
|
+
updated(changedProperties) {
|
|
6024
|
+
if (changedProperties.has("open")) {
|
|
6025
|
+
if (!this.open) {
|
|
6026
|
+
// Reset copied state when modal closes
|
|
6027
|
+
this.copied = false;
|
|
6028
|
+
if (this.copyTimeout) {
|
|
6029
|
+
clearTimeout(this.copyTimeout);
|
|
6030
|
+
this.copyTimeout = null;
|
|
6031
|
+
}
|
|
6032
|
+
}
|
|
6033
|
+
}
|
|
6034
|
+
}
|
|
6035
|
+
handleOverlayClick(e) {
|
|
6036
|
+
if (e.target === e.currentTarget) {
|
|
6037
|
+
this.close();
|
|
6038
|
+
}
|
|
6039
|
+
}
|
|
6040
|
+
handleClose() {
|
|
6041
|
+
this.close();
|
|
6042
|
+
}
|
|
6043
|
+
close() {
|
|
6044
|
+
this.dispatchEvent(new CustomEvent("modal-close", {
|
|
6045
|
+
bubbles: true,
|
|
6046
|
+
composed: true,
|
|
6047
|
+
}));
|
|
6048
|
+
}
|
|
6049
|
+
async handleCopy() {
|
|
6050
|
+
try {
|
|
6051
|
+
await navigator.clipboard.writeText(this.text);
|
|
6052
|
+
this.copied = true;
|
|
6053
|
+
// Reset copied state after 2 seconds
|
|
6054
|
+
if (this.copyTimeout) {
|
|
6055
|
+
clearTimeout(this.copyTimeout);
|
|
6056
|
+
}
|
|
6057
|
+
this.copyTimeout = window.setTimeout(() => {
|
|
6058
|
+
this.copied = false;
|
|
6059
|
+
this.copyTimeout = null;
|
|
6060
|
+
}, 2000);
|
|
6061
|
+
}
|
|
6062
|
+
catch (err) {
|
|
6063
|
+
console.error("[SpeechOS] Failed to copy text:", err);
|
|
6064
|
+
}
|
|
6065
|
+
}
|
|
6066
|
+
render() {
|
|
6067
|
+
return b `
|
|
6068
|
+
<div
|
|
6069
|
+
class="modal-overlay ${this.open ? "open" : ""}"
|
|
6070
|
+
@click="${this.handleOverlayClick}"
|
|
6071
|
+
>
|
|
6072
|
+
<div class="modal-card">
|
|
6073
|
+
<div class="modal-header">
|
|
6074
|
+
<div class="header-content">
|
|
6075
|
+
<div class="logo-icon">${micIcon(18)}</div>
|
|
6076
|
+
<h2 class="modal-title">Dictation Complete</h2>
|
|
6077
|
+
</div>
|
|
6078
|
+
<button
|
|
6079
|
+
class="close-button"
|
|
6080
|
+
@click="${this.handleClose}"
|
|
6081
|
+
aria-label="Close"
|
|
6082
|
+
>
|
|
6083
|
+
${xIcon(16)}
|
|
6084
|
+
</button>
|
|
6085
|
+
</div>
|
|
6086
|
+
|
|
6087
|
+
<div class="modal-body">
|
|
6088
|
+
<div class="text-display">${this.text}</div>
|
|
6089
|
+
<div class="hint">
|
|
6090
|
+
<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">
|
|
6091
|
+
<circle cx="12" cy="12" r="10"/><path d="M12 16v-4M12 8h.01"/>
|
|
6092
|
+
</svg>
|
|
6093
|
+
<span>Tip: Focus a text field first to auto-insert next time</span>
|
|
6094
|
+
</div>
|
|
6095
|
+
</div>
|
|
6096
|
+
|
|
6097
|
+
<div class="modal-footer">
|
|
6098
|
+
<button
|
|
6099
|
+
class="btn ${this.copied ? "btn-success" : "btn-primary"}"
|
|
6100
|
+
@click="${this.handleCopy}"
|
|
6101
|
+
>
|
|
6102
|
+
${this.copied ? checkIcon(16) : copyIcon(16)}
|
|
6103
|
+
${this.copied ? "Copied!" : "Copy"}
|
|
6104
|
+
</button>
|
|
6105
|
+
<button class="btn btn-secondary" @click="${this.handleClose}">
|
|
6106
|
+
Done
|
|
6107
|
+
</button>
|
|
6108
|
+
</div>
|
|
6109
|
+
</div>
|
|
6110
|
+
</div>
|
|
6111
|
+
`;
|
|
6112
|
+
}
|
|
6113
|
+
};
|
|
6114
|
+
__decorate([
|
|
6115
|
+
n({ type: Boolean })
|
|
6116
|
+
], SpeechOSDictationOutputModal.prototype, "open", void 0);
|
|
6117
|
+
__decorate([
|
|
6118
|
+
n({ type: String })
|
|
6119
|
+
], SpeechOSDictationOutputModal.prototype, "text", void 0);
|
|
6120
|
+
__decorate([
|
|
6121
|
+
r()
|
|
6122
|
+
], SpeechOSDictationOutputModal.prototype, "copied", void 0);
|
|
6123
|
+
SpeechOSDictationOutputModal = __decorate([
|
|
6124
|
+
t$1("speechos-dictation-output-modal")
|
|
6125
|
+
], SpeechOSDictationOutputModal);
|
|
6126
|
+
|
|
6127
|
+
/**
|
|
6128
|
+
* Edit help modal component
|
|
6129
|
+
* Displays usage instructions for the edit feature when no text is selected
|
|
6130
|
+
*/
|
|
6131
|
+
let SpeechOSEditHelpModal = class SpeechOSEditHelpModal extends i$1 {
|
|
6132
|
+
constructor() {
|
|
6133
|
+
super(...arguments);
|
|
6134
|
+
this.open = false;
|
|
6135
|
+
}
|
|
6136
|
+
static { this.styles = [
|
|
6137
|
+
themeStyles,
|
|
6138
|
+
popupModalStyles,
|
|
6139
|
+
i$4 `
|
|
6140
|
+
.header-content {
|
|
6141
|
+
display: flex;
|
|
6142
|
+
align-items: center;
|
|
6143
|
+
gap: 12px;
|
|
6144
|
+
}
|
|
6145
|
+
|
|
6146
|
+
.logo-icon {
|
|
6147
|
+
width: 32px;
|
|
6148
|
+
height: 32px;
|
|
6149
|
+
border-radius: 8px;
|
|
6150
|
+
background: linear-gradient(135deg, #8b5cf6 0%, #6366f1 100%);
|
|
6151
|
+
display: flex;
|
|
6152
|
+
align-items: center;
|
|
6153
|
+
justify-content: center;
|
|
6154
|
+
flex-shrink: 0;
|
|
6155
|
+
}
|
|
6156
|
+
|
|
6157
|
+
.logo-icon svg {
|
|
6158
|
+
width: 18px;
|
|
6159
|
+
height: 18px;
|
|
6160
|
+
color: white;
|
|
6161
|
+
}
|
|
6162
|
+
|
|
6163
|
+
.modal-title {
|
|
6164
|
+
background: linear-gradient(135deg, #a78bfa 0%, #818cf8 100%);
|
|
6165
|
+
-webkit-background-clip: text;
|
|
6166
|
+
-webkit-text-fill-color: transparent;
|
|
6167
|
+
background-clip: text;
|
|
6168
|
+
}
|
|
6169
|
+
|
|
6170
|
+
.instruction-number {
|
|
6171
|
+
background: linear-gradient(135deg, #8b5cf6 0%, #6366f1 100%);
|
|
6172
|
+
}
|
|
6173
|
+
|
|
6174
|
+
.btn-primary {
|
|
6175
|
+
background: linear-gradient(135deg, #8b5cf6 0%, #7c3aed 100%);
|
|
6176
|
+
box-shadow: 0 4px 12px rgba(139, 92, 246, 0.3);
|
|
6177
|
+
border-radius: 999px;
|
|
6178
|
+
}
|
|
6179
|
+
|
|
6180
|
+
.btn-primary:hover {
|
|
6181
|
+
background: linear-gradient(135deg, #a78bfa 0%, #8b5cf6 100%);
|
|
6182
|
+
transform: translateY(-2px);
|
|
6183
|
+
box-shadow: 0 6px 16px rgba(139, 92, 246, 0.4);
|
|
6184
|
+
}
|
|
6185
|
+
|
|
6186
|
+
.btn-primary:active {
|
|
6187
|
+
transform: translateY(0);
|
|
6188
|
+
}
|
|
6189
|
+
`,
|
|
6190
|
+
]; }
|
|
6191
|
+
handleOverlayClick(e) {
|
|
6192
|
+
if (e.target === e.currentTarget) {
|
|
6193
|
+
this.close();
|
|
6194
|
+
}
|
|
6195
|
+
}
|
|
6196
|
+
handleClose() {
|
|
6197
|
+
this.close();
|
|
6198
|
+
}
|
|
6199
|
+
close() {
|
|
6200
|
+
this.dispatchEvent(new CustomEvent("modal-close", {
|
|
6201
|
+
bubbles: true,
|
|
6202
|
+
composed: true,
|
|
6203
|
+
}));
|
|
6204
|
+
}
|
|
6205
|
+
render() {
|
|
6206
|
+
return b `
|
|
6207
|
+
<div
|
|
6208
|
+
class="modal-overlay ${this.open ? "open" : ""}"
|
|
6209
|
+
@click="${this.handleOverlayClick}"
|
|
6210
|
+
>
|
|
6211
|
+
<div class="modal-card">
|
|
6212
|
+
<div class="modal-header">
|
|
6213
|
+
<div class="header-content">
|
|
6214
|
+
<div class="logo-icon">${editIcon(18)}</div>
|
|
6215
|
+
<h2 class="modal-title">How to Use Edit</h2>
|
|
6216
|
+
</div>
|
|
6217
|
+
<button
|
|
6218
|
+
class="close-button"
|
|
6219
|
+
@click="${this.handleClose}"
|
|
6220
|
+
aria-label="Close"
|
|
6221
|
+
>
|
|
6222
|
+
${xIcon(16)}
|
|
6223
|
+
</button>
|
|
6224
|
+
</div>
|
|
6225
|
+
|
|
6226
|
+
<div class="modal-body">
|
|
6227
|
+
<ol class="instruction-list">
|
|
6228
|
+
<li class="instruction-item">
|
|
6229
|
+
<span class="instruction-number">1</span>
|
|
6230
|
+
<span class="instruction-text">
|
|
6231
|
+
Click on a text field to focus it, or select the text you want
|
|
6232
|
+
to edit
|
|
6233
|
+
</span>
|
|
6234
|
+
</li>
|
|
6235
|
+
<li class="instruction-item">
|
|
6236
|
+
<span class="instruction-number">2</span>
|
|
6237
|
+
<span class="instruction-text">
|
|
6238
|
+
Click the Edit button in the SpeechOS widget
|
|
6239
|
+
</span>
|
|
6240
|
+
</li>
|
|
6241
|
+
<li class="instruction-item">
|
|
6242
|
+
<span class="instruction-number">3</span>
|
|
6243
|
+
<span class="instruction-text">
|
|
6244
|
+
Speak your editing instructions (e.g., "make it more formal"
|
|
6245
|
+
or "fix the grammar")
|
|
6246
|
+
</span>
|
|
6247
|
+
</li>
|
|
6248
|
+
</ol>
|
|
6249
|
+
</div>
|
|
6250
|
+
|
|
6251
|
+
<div class="modal-footer">
|
|
6252
|
+
<button class="btn btn-primary" @click="${this.handleClose}">
|
|
6253
|
+
Got it
|
|
6254
|
+
</button>
|
|
6255
|
+
</div>
|
|
6256
|
+
</div>
|
|
6257
|
+
</div>
|
|
6258
|
+
`;
|
|
6259
|
+
}
|
|
6260
|
+
};
|
|
6261
|
+
__decorate([
|
|
6262
|
+
n({ type: Boolean })
|
|
6263
|
+
], SpeechOSEditHelpModal.prototype, "open", void 0);
|
|
6264
|
+
SpeechOSEditHelpModal = __decorate([
|
|
6265
|
+
t$1("speechos-edit-help-modal")
|
|
6266
|
+
], SpeechOSEditHelpModal);
|
|
6267
|
+
|
|
6268
|
+
/**
|
|
6269
|
+
* Main widget container component
|
|
6270
|
+
* Composes mic button and action bubbles with state management
|
|
6271
|
+
*/
|
|
6272
|
+
var SpeechOSWidget_1;
|
|
6273
|
+
/**
|
|
6274
|
+
* Minimum duration to show the connecting animation (in milliseconds).
|
|
6275
|
+
* Since mic capture starts almost instantly, we enforce a minimum animation
|
|
6276
|
+
* duration so users can see the visual feedback before transitioning to recording.
|
|
6277
|
+
*/
|
|
6278
|
+
const MIN_CONNECTING_ANIMATION_MS = 200;
|
|
6279
|
+
let SpeechOSWidget = class SpeechOSWidget extends i$1 {
|
|
6280
|
+
constructor() {
|
|
6281
|
+
super(...arguments);
|
|
6282
|
+
this.widgetState = core.state.getState();
|
|
6283
|
+
this.settingsOpen = false;
|
|
6284
|
+
this.dictationModalOpen = false;
|
|
6285
|
+
this.dictationModalText = "";
|
|
6286
|
+
this.editHelpModalOpen = false;
|
|
6287
|
+
this.commandFeedback = null;
|
|
6288
|
+
this.dictationTargetElement = null;
|
|
6289
|
+
this.editTargetElement = null;
|
|
6290
|
+
this.dictationCursorStart = null;
|
|
6291
|
+
this.dictationCursorEnd = null;
|
|
6292
|
+
this.editSelectionStart = null;
|
|
6293
|
+
this.editSelectionEnd = null;
|
|
6294
|
+
this.editSelectedText = "";
|
|
6295
|
+
this.boundClickOutsideHandler = null;
|
|
6296
|
+
this.modalElement = null;
|
|
6297
|
+
this.dictationModalElement = null;
|
|
6298
|
+
this.editHelpModalElement = null;
|
|
6299
|
+
this.commandFeedbackTimeout = null;
|
|
6300
|
+
this.customPosition = null;
|
|
6301
|
+
this.isDragging = false;
|
|
6302
|
+
this.dragStartPos = null;
|
|
6303
|
+
this.dragOffset = { x: 0, y: 0 };
|
|
6304
|
+
this.boundDragMove = null;
|
|
6305
|
+
this.boundDragEnd = null;
|
|
6306
|
+
this.suppressNextClick = false;
|
|
6307
|
+
this.boundViewportResizeHandler = null;
|
|
6308
|
+
this.boundScrollHandler = null;
|
|
6309
|
+
}
|
|
6310
|
+
static { SpeechOSWidget_1 = this; }
|
|
6311
|
+
static { this.styles = [
|
|
6312
|
+
themeStyles,
|
|
6313
|
+
animations,
|
|
6314
|
+
i$4 `
|
|
6315
|
+
:host {
|
|
6316
|
+
position: fixed;
|
|
6317
|
+
bottom: var(--speechos-spacing-md); /* 12px - same as spacing above */
|
|
6318
|
+
z-index: var(--speechos-z-base);
|
|
6319
|
+
pointer-events: none;
|
|
6320
|
+
}
|
|
6321
|
+
|
|
6322
|
+
:host {
|
|
6323
|
+
left: 50%;
|
|
6324
|
+
transform: translateX(-50%);
|
|
6325
|
+
}
|
|
6326
|
+
|
|
6327
|
+
:host(.custom-position) {
|
|
6328
|
+
right: unset;
|
|
6329
|
+
left: unset;
|
|
6330
|
+
transform: none;
|
|
6331
|
+
}
|
|
6332
|
+
|
|
6333
|
+
:host(.anchored-to-element) {
|
|
6334
|
+
position: absolute;
|
|
5653
6335
|
bottom: unset;
|
|
5654
6336
|
right: unset;
|
|
5655
6337
|
left: unset;
|
|
@@ -5708,6 +6390,18 @@ let SpeechOSWidget = class SpeechOSWidget extends i$1 {
|
|
|
5708
6390
|
this.settingsOpen = false;
|
|
5709
6391
|
});
|
|
5710
6392
|
document.body.appendChild(this.modalElement);
|
|
6393
|
+
// Mount dictation output modal
|
|
6394
|
+
this.dictationModalElement = document.createElement("speechos-dictation-output-modal");
|
|
6395
|
+
this.dictationModalElement.addEventListener("modal-close", () => {
|
|
6396
|
+
this.dictationModalOpen = false;
|
|
6397
|
+
});
|
|
6398
|
+
document.body.appendChild(this.dictationModalElement);
|
|
6399
|
+
// Mount edit help modal
|
|
6400
|
+
this.editHelpModalElement = document.createElement("speechos-edit-help-modal");
|
|
6401
|
+
this.editHelpModalElement.addEventListener("modal-close", () => {
|
|
6402
|
+
this.editHelpModalOpen = false;
|
|
6403
|
+
});
|
|
6404
|
+
document.body.appendChild(this.editHelpModalElement);
|
|
5711
6405
|
this.stateUnsubscribe = core.state.subscribe((newState) => {
|
|
5712
6406
|
if (!newState.isVisible || !newState.isExpanded) {
|
|
5713
6407
|
this.settingsOpen = false;
|
|
@@ -5747,6 +6441,18 @@ let SpeechOSWidget = class SpeechOSWidget extends i$1 {
|
|
|
5747
6441
|
this.modalElement.remove();
|
|
5748
6442
|
this.modalElement = null;
|
|
5749
6443
|
}
|
|
6444
|
+
if (this.dictationModalElement) {
|
|
6445
|
+
this.dictationModalElement.remove();
|
|
6446
|
+
this.dictationModalElement = null;
|
|
6447
|
+
}
|
|
6448
|
+
if (this.editHelpModalElement) {
|
|
6449
|
+
this.editHelpModalElement.remove();
|
|
6450
|
+
this.editHelpModalElement = null;
|
|
6451
|
+
}
|
|
6452
|
+
if (this.commandFeedbackTimeout) {
|
|
6453
|
+
clearTimeout(this.commandFeedbackTimeout);
|
|
6454
|
+
this.commandFeedbackTimeout = null;
|
|
6455
|
+
}
|
|
5750
6456
|
if (this.stateUnsubscribe) {
|
|
5751
6457
|
this.stateUnsubscribe();
|
|
5752
6458
|
}
|
|
@@ -5778,6 +6484,15 @@ let SpeechOSWidget = class SpeechOSWidget extends i$1 {
|
|
|
5778
6484
|
if (changedProperties.has("settingsOpen") && this.modalElement) {
|
|
5779
6485
|
this.modalElement.open = this.settingsOpen;
|
|
5780
6486
|
}
|
|
6487
|
+
if (changedProperties.has("dictationModalOpen") && this.dictationModalElement) {
|
|
6488
|
+
this.dictationModalElement.open = this.dictationModalOpen;
|
|
6489
|
+
}
|
|
6490
|
+
if (changedProperties.has("dictationModalText") && this.dictationModalElement) {
|
|
6491
|
+
this.dictationModalElement.text = this.dictationModalText;
|
|
6492
|
+
}
|
|
6493
|
+
if (changedProperties.has("editHelpModalOpen") && this.editHelpModalElement) {
|
|
6494
|
+
this.editHelpModalElement.open = this.editHelpModalOpen;
|
|
6495
|
+
}
|
|
5781
6496
|
}
|
|
5782
6497
|
handleClickOutside(event) {
|
|
5783
6498
|
const target = event.target;
|
|
@@ -5816,7 +6531,10 @@ let SpeechOSWidget = class SpeechOSWidget extends i$1 {
|
|
|
5816
6531
|
}
|
|
5817
6532
|
if (!clickedInWidget) {
|
|
5818
6533
|
core.getBackend().stopAutoRefresh?.();
|
|
5819
|
-
|
|
6534
|
+
// Don't hide if alwaysVisible is enabled
|
|
6535
|
+
if (!isAlwaysVisible()) {
|
|
6536
|
+
core.state.hide();
|
|
6537
|
+
}
|
|
5820
6538
|
}
|
|
5821
6539
|
}
|
|
5822
6540
|
isFormField(element) {
|
|
@@ -5937,6 +6655,8 @@ let SpeechOSWidget = class SpeechOSWidget extends i$1 {
|
|
|
5937
6655
|
return;
|
|
5938
6656
|
}
|
|
5939
6657
|
if (this.widgetState.recordingState === "idle") {
|
|
6658
|
+
// Clear command feedback on any mic click
|
|
6659
|
+
this.clearCommandFeedback();
|
|
5940
6660
|
// If we're expanding, prefetch the token to reduce latency when user selects an action
|
|
5941
6661
|
if (!this.widgetState.isExpanded) {
|
|
5942
6662
|
// Fire and forget - we don't need to wait for this (LiveKit only)
|
|
@@ -5965,12 +6685,19 @@ let SpeechOSWidget = class SpeechOSWidget extends i$1 {
|
|
|
5965
6685
|
}
|
|
5966
6686
|
else {
|
|
5967
6687
|
core.state.stopRecording();
|
|
5968
|
-
core.getConfig();
|
|
5969
6688
|
const backend = core.getBackend();
|
|
5970
6689
|
try {
|
|
5971
6690
|
const transcription = await this.withMinDisplayTime(backend.stopVoiceSession(), 300);
|
|
5972
6691
|
if (transcription) {
|
|
5973
|
-
|
|
6692
|
+
// Check if we have a target element to insert into
|
|
6693
|
+
if (this.dictationTargetElement) {
|
|
6694
|
+
this.insertTranscription(transcription);
|
|
6695
|
+
}
|
|
6696
|
+
else {
|
|
6697
|
+
// No target element - show dictation output modal
|
|
6698
|
+
this.dictationModalText = transcription;
|
|
6699
|
+
this.dictationModalOpen = true;
|
|
6700
|
+
}
|
|
5974
6701
|
transcriptStore.saveTranscript(transcription, "dictate");
|
|
5975
6702
|
}
|
|
5976
6703
|
core.state.completeRecording();
|
|
@@ -6018,6 +6745,7 @@ let SpeechOSWidget = class SpeechOSWidget extends i$1 {
|
|
|
6018
6745
|
}
|
|
6019
6746
|
}
|
|
6020
6747
|
handleCloseWidget() {
|
|
6748
|
+
this.clearCommandFeedback();
|
|
6021
6749
|
core.getBackend().stopAutoRefresh?.();
|
|
6022
6750
|
core.state.hide();
|
|
6023
6751
|
}
|
|
@@ -6145,11 +6873,20 @@ let SpeechOSWidget = class SpeechOSWidget extends i$1 {
|
|
|
6145
6873
|
}
|
|
6146
6874
|
handleActionSelect(event) {
|
|
6147
6875
|
const { action } = event.detail;
|
|
6876
|
+
// Clear any existing command feedback when a new action is selected
|
|
6877
|
+
this.clearCommandFeedback();
|
|
6148
6878
|
core.state.setActiveAction(action);
|
|
6149
6879
|
if (action === "dictate") {
|
|
6150
6880
|
this.startDictation();
|
|
6151
6881
|
}
|
|
6152
6882
|
else if (action === "edit") {
|
|
6883
|
+
// Check if there's a focused element before starting edit
|
|
6884
|
+
if (!this.widgetState.focusedElement) {
|
|
6885
|
+
// No focused element - show edit help modal
|
|
6886
|
+
this.editHelpModalOpen = true;
|
|
6887
|
+
core.state.setActiveAction(null);
|
|
6888
|
+
return;
|
|
6889
|
+
}
|
|
6153
6890
|
this.startEdit();
|
|
6154
6891
|
}
|
|
6155
6892
|
else if (action === "command") {
|
|
@@ -6356,6 +7093,10 @@ let SpeechOSWidget = class SpeechOSWidget extends i$1 {
|
|
|
6356
7093
|
// Note: command:complete event is already emitted by the backend
|
|
6357
7094
|
// when the command_result message is received, so we don't emit here
|
|
6358
7095
|
core.state.completeRecording();
|
|
7096
|
+
// Keep widget visible but collapsed (just mic button, no action bubbles)
|
|
7097
|
+
core.state.setState({ isExpanded: false });
|
|
7098
|
+
// Show command feedback
|
|
7099
|
+
this.showCommandFeedback(result ? "success" : "none");
|
|
6359
7100
|
backend.disconnect().catch(() => { });
|
|
6360
7101
|
// Start auto-refresh to keep token fresh for subsequent commands (LiveKit only)
|
|
6361
7102
|
backend.startAutoRefresh?.();
|
|
@@ -6368,6 +7109,25 @@ let SpeechOSWidget = class SpeechOSWidget extends i$1 {
|
|
|
6368
7109
|
}
|
|
6369
7110
|
}
|
|
6370
7111
|
}
|
|
7112
|
+
showCommandFeedback(feedback) {
|
|
7113
|
+
this.commandFeedback = feedback;
|
|
7114
|
+
// Clear any existing timeout
|
|
7115
|
+
if (this.commandFeedbackTimeout) {
|
|
7116
|
+
clearTimeout(this.commandFeedbackTimeout);
|
|
7117
|
+
}
|
|
7118
|
+
// Auto-dismiss after 4 seconds
|
|
7119
|
+
this.commandFeedbackTimeout = window.setTimeout(() => {
|
|
7120
|
+
this.commandFeedback = null;
|
|
7121
|
+
this.commandFeedbackTimeout = null;
|
|
7122
|
+
}, 4000);
|
|
7123
|
+
}
|
|
7124
|
+
clearCommandFeedback() {
|
|
7125
|
+
if (this.commandFeedbackTimeout) {
|
|
7126
|
+
clearTimeout(this.commandFeedbackTimeout);
|
|
7127
|
+
this.commandFeedbackTimeout = null;
|
|
7128
|
+
}
|
|
7129
|
+
this.commandFeedback = null;
|
|
7130
|
+
}
|
|
6371
7131
|
supportsSelection(element) {
|
|
6372
7132
|
if (element.tagName.toLowerCase() === "textarea") {
|
|
6373
7133
|
return true;
|
|
@@ -6485,6 +7245,7 @@ let SpeechOSWidget = class SpeechOSWidget extends i$1 {
|
|
|
6485
7245
|
activeAction="${this.widgetState.activeAction || ""}"
|
|
6486
7246
|
editPreviewText="${this.editSelectedText}"
|
|
6487
7247
|
errorMessage="${this.widgetState.errorMessage || ""}"
|
|
7248
|
+
.commandFeedback="${this.commandFeedback}"
|
|
6488
7249
|
@mic-click="${this.handleMicClick}"
|
|
6489
7250
|
@stop-recording="${this.handleStopRecording}"
|
|
6490
7251
|
@cancel-operation="${this.handleCancelOperation}"
|
|
@@ -6502,6 +7263,18 @@ __decorate([
|
|
|
6502
7263
|
__decorate([
|
|
6503
7264
|
r()
|
|
6504
7265
|
], SpeechOSWidget.prototype, "settingsOpen", void 0);
|
|
7266
|
+
__decorate([
|
|
7267
|
+
r()
|
|
7268
|
+
], SpeechOSWidget.prototype, "dictationModalOpen", void 0);
|
|
7269
|
+
__decorate([
|
|
7270
|
+
r()
|
|
7271
|
+
], SpeechOSWidget.prototype, "dictationModalText", void 0);
|
|
7272
|
+
__decorate([
|
|
7273
|
+
r()
|
|
7274
|
+
], SpeechOSWidget.prototype, "editHelpModalOpen", void 0);
|
|
7275
|
+
__decorate([
|
|
7276
|
+
r()
|
|
7277
|
+
], SpeechOSWidget.prototype, "commandFeedback", void 0);
|
|
6505
7278
|
SpeechOSWidget = SpeechOSWidget_1 = __decorate([
|
|
6506
7279
|
t$1("speechos-widget")
|
|
6507
7280
|
], SpeechOSWidget);
|
|
@@ -6582,6 +7355,10 @@ class SpeechOS {
|
|
|
6582
7355
|
}
|
|
6583
7356
|
// Create and mount widget
|
|
6584
7357
|
this.mountWidget();
|
|
7358
|
+
// If alwaysVisible is enabled, show the widget immediately
|
|
7359
|
+
if (isAlwaysVisible()) {
|
|
7360
|
+
core.state.show();
|
|
7361
|
+
}
|
|
6585
7362
|
this.isInitialized = true;
|
|
6586
7363
|
// Log initialization in debug mode
|
|
6587
7364
|
if (finalConfig.debug) {
|