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