@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.iife.js
CHANGED
|
@@ -26959,6 +26959,28 @@
|
|
|
26959
26959
|
userId
|
|
26960
26960
|
};
|
|
26961
26961
|
}
|
|
26962
|
+
/**
|
|
26963
|
+
* LocalStorage key for anonymous ID persistence
|
|
26964
|
+
*/
|
|
26965
|
+
const ANONYMOUS_ID_KEY = "speechos_anonymous_id";
|
|
26966
|
+
/**
|
|
26967
|
+
* Get or generate a persistent anonymous ID for Mixpanel tracking.
|
|
26968
|
+
*
|
|
26969
|
+
* This ID is stored in localStorage to persist across sessions,
|
|
26970
|
+
* allowing consistent anonymous user tracking without identifying
|
|
26971
|
+
* the account owner's customers.
|
|
26972
|
+
*
|
|
26973
|
+
* @returns A UUID string for anonymous identification
|
|
26974
|
+
*/
|
|
26975
|
+
function getAnonymousId() {
|
|
26976
|
+
if (typeof localStorage === "undefined") return crypto.randomUUID();
|
|
26977
|
+
let anonymousId = localStorage.getItem(ANONYMOUS_ID_KEY);
|
|
26978
|
+
if (!anonymousId) {
|
|
26979
|
+
anonymousId = crypto.randomUUID();
|
|
26980
|
+
localStorage.setItem(ANONYMOUS_ID_KEY, anonymousId);
|
|
26981
|
+
}
|
|
26982
|
+
return anonymousId;
|
|
26983
|
+
}
|
|
26962
26984
|
|
|
26963
26985
|
//#endregion
|
|
26964
26986
|
//#region src/events.ts
|
|
@@ -28416,10 +28438,12 @@
|
|
|
28416
28438
|
const config = getConfig();
|
|
28417
28439
|
const audioFormat = getSupportedAudioFormat();
|
|
28418
28440
|
const settings = this.sessionSettings;
|
|
28441
|
+
const anonymousId = getAnonymousId();
|
|
28419
28442
|
const authMessage = {
|
|
28420
28443
|
type: MESSAGE_TYPE_AUTH,
|
|
28421
28444
|
api_key: config.apiKey,
|
|
28422
28445
|
user_id: config.userId || null,
|
|
28446
|
+
anonymous_id: anonymousId,
|
|
28423
28447
|
input_language: settings.inputLanguageCode ?? "en-US",
|
|
28424
28448
|
output_language: settings.outputLanguageCode ?? "en-US",
|
|
28425
28449
|
smart_format: settings.smartFormat ?? true,
|
|
@@ -28728,6 +28752,84 @@
|
|
|
28728
28752
|
return websocketBackend;
|
|
28729
28753
|
}
|
|
28730
28754
|
|
|
28755
|
+
/**
|
|
28756
|
+
* Client configuration for SpeechOS
|
|
28757
|
+
* Extends core config with UI/widget-specific options
|
|
28758
|
+
*/
|
|
28759
|
+
/**
|
|
28760
|
+
* Default client configuration values
|
|
28761
|
+
*/
|
|
28762
|
+
const defaultClientConfig = {
|
|
28763
|
+
commands: [],
|
|
28764
|
+
zIndex: 999999,
|
|
28765
|
+
alwaysVisible: false,
|
|
28766
|
+
};
|
|
28767
|
+
/**
|
|
28768
|
+
* Current client configuration singleton
|
|
28769
|
+
*/
|
|
28770
|
+
let currentClientConfig = { ...defaultClientConfig };
|
|
28771
|
+
/**
|
|
28772
|
+
* Validate and resolve client-specific config
|
|
28773
|
+
* @param config - User-provided configuration
|
|
28774
|
+
* @returns Resolved client configuration
|
|
28775
|
+
*/
|
|
28776
|
+
function validateClientConfig(config) {
|
|
28777
|
+
const resolved = {
|
|
28778
|
+
commands: config.commands ?? defaultClientConfig.commands,
|
|
28779
|
+
zIndex: config.zIndex ?? defaultClientConfig.zIndex,
|
|
28780
|
+
alwaysVisible: config.alwaysVisible ?? defaultClientConfig.alwaysVisible,
|
|
28781
|
+
};
|
|
28782
|
+
// Validate zIndex
|
|
28783
|
+
if (typeof resolved.zIndex !== "number" || resolved.zIndex < 0) {
|
|
28784
|
+
console.warn(`Invalid zIndex "${resolved.zIndex}". Using default ${defaultClientConfig.zIndex}.`);
|
|
28785
|
+
resolved.zIndex = defaultClientConfig.zIndex;
|
|
28786
|
+
}
|
|
28787
|
+
return resolved;
|
|
28788
|
+
}
|
|
28789
|
+
/**
|
|
28790
|
+
* Set the client configuration
|
|
28791
|
+
* @param config - Client configuration to set
|
|
28792
|
+
*/
|
|
28793
|
+
function setClientConfig(config) {
|
|
28794
|
+
currentClientConfig = validateClientConfig(config);
|
|
28795
|
+
}
|
|
28796
|
+
/**
|
|
28797
|
+
* Get the current client configuration
|
|
28798
|
+
*/
|
|
28799
|
+
function getClientConfig() {
|
|
28800
|
+
return { ...currentClientConfig };
|
|
28801
|
+
}
|
|
28802
|
+
/**
|
|
28803
|
+
* Reset client configuration to defaults
|
|
28804
|
+
*/
|
|
28805
|
+
function resetClientConfig() {
|
|
28806
|
+
currentClientConfig = { ...defaultClientConfig };
|
|
28807
|
+
}
|
|
28808
|
+
/**
|
|
28809
|
+
* Check if commands are configured (for showing Command button in widget)
|
|
28810
|
+
*/
|
|
28811
|
+
function hasCommands() {
|
|
28812
|
+
return currentClientConfig.commands.length > 0;
|
|
28813
|
+
}
|
|
28814
|
+
/**
|
|
28815
|
+
* Get configured commands
|
|
28816
|
+
*/
|
|
28817
|
+
function getCommands() {
|
|
28818
|
+
return [...currentClientConfig.commands];
|
|
28819
|
+
}
|
|
28820
|
+
/**
|
|
28821
|
+
* Get widget z-index
|
|
28822
|
+
*/
|
|
28823
|
+
function getZIndex() {
|
|
28824
|
+
return currentClientConfig.zIndex;
|
|
28825
|
+
}
|
|
28826
|
+
/**
|
|
28827
|
+
* Check if widget should always be visible
|
|
28828
|
+
*/
|
|
28829
|
+
function isAlwaysVisible() {
|
|
28830
|
+
return currentClientConfig.alwaysVisible;
|
|
28831
|
+
}
|
|
28832
|
+
|
|
28731
28833
|
/**
|
|
28732
28834
|
* Form field focus detection for SpeechOS Client SDK
|
|
28733
28835
|
* Detects when users focus on form fields and manages widget visibility
|
|
@@ -28815,12 +28917,12 @@
|
|
|
28815
28917
|
relatedTarget === widget);
|
|
28816
28918
|
// If focus is going to an element with data-speechos-no-close, don't hide
|
|
28817
28919
|
const goingToNoCloseElement = Boolean(relatedTarget?.closest("[data-speechos-no-close]"));
|
|
28818
|
-
console.log("[SpeechOS] blurHandler:", {
|
|
28819
|
-
|
|
28820
|
-
|
|
28821
|
-
|
|
28822
|
-
|
|
28823
|
-
});
|
|
28920
|
+
// console.log("[SpeechOS] blurHandler:", {
|
|
28921
|
+
// relatedTarget,
|
|
28922
|
+
// goingToFormField,
|
|
28923
|
+
// goingToWidget,
|
|
28924
|
+
// goingToNoCloseElement,
|
|
28925
|
+
// });
|
|
28824
28926
|
if (goingToFormField || goingToWidget || goingToNoCloseElement) {
|
|
28825
28927
|
console.log("[SpeechOS] blurHandler: early return, not hiding");
|
|
28826
28928
|
return;
|
|
@@ -28839,11 +28941,15 @@
|
|
|
28839
28941
|
// Check if focus is on an element with data-speechos-no-close
|
|
28840
28942
|
const isNoCloseElementFocused = Boolean(activeElement?.closest("[data-speechos-no-close]"));
|
|
28841
28943
|
// Only hide if no form field is focused AND widget isn't focused AND not a no-close element
|
|
28944
|
+
// AND alwaysVisible is not enabled
|
|
28842
28945
|
if (!isFormField(activeElement) &&
|
|
28843
28946
|
!isWidgetFocused &&
|
|
28844
28947
|
!isNoCloseElementFocused) {
|
|
28845
28948
|
state.setFocusedElement(null);
|
|
28846
|
-
|
|
28949
|
+
// Don't hide if alwaysVisible is enabled
|
|
28950
|
+
if (!isAlwaysVisible()) {
|
|
28951
|
+
state.hide();
|
|
28952
|
+
}
|
|
28847
28953
|
events.emit("form:blur", { element: null });
|
|
28848
28954
|
}
|
|
28849
28955
|
}, 150);
|
|
@@ -28917,7 +29023,10 @@
|
|
|
28917
29023
|
// Reset state
|
|
28918
29024
|
this.isWidgetBeingInteracted = false;
|
|
28919
29025
|
state.setFocusedElement(null);
|
|
28920
|
-
|
|
29026
|
+
// Don't hide if alwaysVisible is enabled
|
|
29027
|
+
if (!isAlwaysVisible()) {
|
|
29028
|
+
state.hide();
|
|
29029
|
+
}
|
|
28921
29030
|
this.isActive = false;
|
|
28922
29031
|
}
|
|
28923
29032
|
/**
|
|
@@ -28930,76 +29039,6 @@
|
|
|
28930
29039
|
// Export singleton instance
|
|
28931
29040
|
const formDetector = new FormDetector();
|
|
28932
29041
|
|
|
28933
|
-
/**
|
|
28934
|
-
* Client configuration for SpeechOS
|
|
28935
|
-
* Extends core config with UI/widget-specific options
|
|
28936
|
-
*/
|
|
28937
|
-
/**
|
|
28938
|
-
* Default client configuration values
|
|
28939
|
-
*/
|
|
28940
|
-
const defaultClientConfig = {
|
|
28941
|
-
commands: [],
|
|
28942
|
-
zIndex: 999999,
|
|
28943
|
-
};
|
|
28944
|
-
/**
|
|
28945
|
-
* Current client configuration singleton
|
|
28946
|
-
*/
|
|
28947
|
-
let currentClientConfig = { ...defaultClientConfig };
|
|
28948
|
-
/**
|
|
28949
|
-
* Validate and resolve client-specific config
|
|
28950
|
-
* @param config - User-provided configuration
|
|
28951
|
-
* @returns Resolved client configuration
|
|
28952
|
-
*/
|
|
28953
|
-
function validateClientConfig(config) {
|
|
28954
|
-
const resolved = {
|
|
28955
|
-
commands: config.commands ?? defaultClientConfig.commands,
|
|
28956
|
-
zIndex: config.zIndex ?? defaultClientConfig.zIndex,
|
|
28957
|
-
};
|
|
28958
|
-
// Validate zIndex
|
|
28959
|
-
if (typeof resolved.zIndex !== "number" || resolved.zIndex < 0) {
|
|
28960
|
-
console.warn(`Invalid zIndex "${resolved.zIndex}". Using default ${defaultClientConfig.zIndex}.`);
|
|
28961
|
-
resolved.zIndex = defaultClientConfig.zIndex;
|
|
28962
|
-
}
|
|
28963
|
-
return resolved;
|
|
28964
|
-
}
|
|
28965
|
-
/**
|
|
28966
|
-
* Set the client configuration
|
|
28967
|
-
* @param config - Client configuration to set
|
|
28968
|
-
*/
|
|
28969
|
-
function setClientConfig(config) {
|
|
28970
|
-
currentClientConfig = validateClientConfig(config);
|
|
28971
|
-
}
|
|
28972
|
-
/**
|
|
28973
|
-
* Get the current client configuration
|
|
28974
|
-
*/
|
|
28975
|
-
function getClientConfig() {
|
|
28976
|
-
return { ...currentClientConfig };
|
|
28977
|
-
}
|
|
28978
|
-
/**
|
|
28979
|
-
* Reset client configuration to defaults
|
|
28980
|
-
*/
|
|
28981
|
-
function resetClientConfig() {
|
|
28982
|
-
currentClientConfig = { ...defaultClientConfig };
|
|
28983
|
-
}
|
|
28984
|
-
/**
|
|
28985
|
-
* Check if commands are configured (for showing Command button in widget)
|
|
28986
|
-
*/
|
|
28987
|
-
function hasCommands() {
|
|
28988
|
-
return currentClientConfig.commands.length > 0;
|
|
28989
|
-
}
|
|
28990
|
-
/**
|
|
28991
|
-
* Get configured commands
|
|
28992
|
-
*/
|
|
28993
|
-
function getCommands() {
|
|
28994
|
-
return [...currentClientConfig.commands];
|
|
28995
|
-
}
|
|
28996
|
-
/**
|
|
28997
|
-
* Get widget z-index
|
|
28998
|
-
*/
|
|
28999
|
-
function getZIndex() {
|
|
29000
|
-
return currentClientConfig.zIndex;
|
|
29001
|
-
}
|
|
29002
|
-
|
|
29003
29042
|
/**
|
|
29004
29043
|
* Text input handler for SpeechOS Client SDK
|
|
29005
29044
|
* Abstracts cursor/selection detection and text insertion/replacement operations
|
|
@@ -30204,6 +30243,11 @@
|
|
|
30204
30243
|
`;
|
|
30205
30244
|
return b `${o(svgString)}`;
|
|
30206
30245
|
};
|
|
30246
|
+
/**
|
|
30247
|
+
* Check icon for "Keep" action
|
|
30248
|
+
* Lucide Check icon paths
|
|
30249
|
+
*/
|
|
30250
|
+
const checkIcon = (size = 18) => createIcon('<path d="M20 6 9 17l-5-5"/>', size);
|
|
30207
30251
|
/**
|
|
30208
30252
|
* X icon for cancel action
|
|
30209
30253
|
* Lucide X icon paths
|
|
@@ -30464,6 +30508,7 @@
|
|
|
30464
30508
|
this.activeAction = null;
|
|
30465
30509
|
this.editPreviewText = "";
|
|
30466
30510
|
this.errorMessage = null;
|
|
30511
|
+
this.commandFeedback = null;
|
|
30467
30512
|
}
|
|
30468
30513
|
static { this.styles = [
|
|
30469
30514
|
themeStyles,
|
|
@@ -31031,6 +31076,47 @@
|
|
|
31031
31076
|
}
|
|
31032
31077
|
}
|
|
31033
31078
|
|
|
31079
|
+
/* Command feedback badge - success state (amber/orange) */
|
|
31080
|
+
.status-label.command-success {
|
|
31081
|
+
background: linear-gradient(135deg, #f59e0b 0%, #d97706 100%);
|
|
31082
|
+
box-shadow: 0 4px 12px rgba(245, 158, 11, 0.3);
|
|
31083
|
+
padding-left: 24px;
|
|
31084
|
+
animation: command-feedback-in 0.3s cubic-bezier(0.34, 1.56, 0.64, 1)
|
|
31085
|
+
forwards;
|
|
31086
|
+
}
|
|
31087
|
+
|
|
31088
|
+
.status-label.command-success::before {
|
|
31089
|
+
content: "";
|
|
31090
|
+
position: absolute;
|
|
31091
|
+
top: 50%;
|
|
31092
|
+
left: 8px;
|
|
31093
|
+
width: 12px;
|
|
31094
|
+
height: 12px;
|
|
31095
|
+
transform: translateY(-50%);
|
|
31096
|
+
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");
|
|
31097
|
+
background-repeat: no-repeat;
|
|
31098
|
+
background-position: center;
|
|
31099
|
+
}
|
|
31100
|
+
|
|
31101
|
+
/* Command feedback badge - no match state (neutral gray) */
|
|
31102
|
+
.status-label.command-none {
|
|
31103
|
+
background: #4b5563;
|
|
31104
|
+
box-shadow: 0 4px 12px rgba(75, 85, 99, 0.3);
|
|
31105
|
+
animation: command-feedback-in 0.3s cubic-bezier(0.34, 1.56, 0.64, 1)
|
|
31106
|
+
forwards;
|
|
31107
|
+
}
|
|
31108
|
+
|
|
31109
|
+
@keyframes command-feedback-in {
|
|
31110
|
+
0% {
|
|
31111
|
+
opacity: 0;
|
|
31112
|
+
transform: translateX(-50%) scale(0.8) translateY(4px);
|
|
31113
|
+
}
|
|
31114
|
+
100% {
|
|
31115
|
+
opacity: 1;
|
|
31116
|
+
transform: translateX(-50%) scale(1) translateY(0);
|
|
31117
|
+
}
|
|
31118
|
+
}
|
|
31119
|
+
|
|
31034
31120
|
/* Cancel button - positioned to the right of the main mic button */
|
|
31035
31121
|
.cancel-button {
|
|
31036
31122
|
position: absolute;
|
|
@@ -31278,6 +31364,16 @@
|
|
|
31278
31364
|
left: 10px;
|
|
31279
31365
|
}
|
|
31280
31366
|
|
|
31367
|
+
.status-label.command-success {
|
|
31368
|
+
padding-left: 30px;
|
|
31369
|
+
}
|
|
31370
|
+
|
|
31371
|
+
.status-label.command-success::before {
|
|
31372
|
+
left: 10px;
|
|
31373
|
+
width: 14px;
|
|
31374
|
+
height: 14px;
|
|
31375
|
+
}
|
|
31376
|
+
|
|
31281
31377
|
.error-message {
|
|
31282
31378
|
font-size: 15px;
|
|
31283
31379
|
padding: 14px 18px;
|
|
@@ -31430,6 +31526,15 @@
|
|
|
31430
31526
|
}
|
|
31431
31527
|
return this.recordingState;
|
|
31432
31528
|
}
|
|
31529
|
+
getCommandFeedbackLabel() {
|
|
31530
|
+
if (this.commandFeedback === "success") {
|
|
31531
|
+
return "Got it!";
|
|
31532
|
+
}
|
|
31533
|
+
if (this.commandFeedback === "none") {
|
|
31534
|
+
return "No command matched";
|
|
31535
|
+
}
|
|
31536
|
+
return "";
|
|
31537
|
+
}
|
|
31433
31538
|
render() {
|
|
31434
31539
|
const showPulse = this.recordingState === "recording";
|
|
31435
31540
|
const showSiriConnecting = this.recordingState === "connecting";
|
|
@@ -31437,13 +31542,14 @@
|
|
|
31437
31542
|
const showSiriEdit = this.recordingState === "processing" && this.activeAction === "edit";
|
|
31438
31543
|
const statusLabel = this.getStatusLabel();
|
|
31439
31544
|
const showVisualizer = this.shouldShowVisualizer();
|
|
31440
|
-
// Show status label during recording (either visualizer or edit text)
|
|
31441
|
-
const
|
|
31545
|
+
// Show status label during recording (either visualizer or edit text) OR command feedback
|
|
31546
|
+
const showCommandFeedback = this.recordingState === "idle" && this.commandFeedback !== null;
|
|
31547
|
+
const showStatus = this.recordingState === "recording" || showCommandFeedback;
|
|
31442
31548
|
const showCancel = this.recordingState === "connecting" ||
|
|
31443
31549
|
this.recordingState === "recording" ||
|
|
31444
31550
|
this.recordingState === "processing";
|
|
31445
31551
|
const showError = this.recordingState === "error" && this.errorMessage;
|
|
31446
|
-
// Show close button in idle state (both solo mic and expanded)
|
|
31552
|
+
// Show close button in idle state (both solo mic and expanded), including when showing command feedback
|
|
31447
31553
|
const showClose = this.recordingState === "idle";
|
|
31448
31554
|
return b `
|
|
31449
31555
|
<div class="button-wrapper">
|
|
@@ -31489,15 +31595,19 @@
|
|
|
31489
31595
|
</button>
|
|
31490
31596
|
|
|
31491
31597
|
<span
|
|
31492
|
-
class="status-label ${showStatus ? "visible" : ""} ${
|
|
31493
|
-
?
|
|
31494
|
-
:
|
|
31598
|
+
class="status-label ${showStatus ? "visible" : ""} ${showCommandFeedback
|
|
31599
|
+
? `command-${this.commandFeedback}`
|
|
31600
|
+
: showVisualizer
|
|
31601
|
+
? "visualizer"
|
|
31602
|
+
: this.getStatusClass()}"
|
|
31495
31603
|
>
|
|
31496
|
-
${
|
|
31497
|
-
?
|
|
31498
|
-
|
|
31499
|
-
|
|
31500
|
-
|
|
31604
|
+
${showCommandFeedback
|
|
31605
|
+
? this.getCommandFeedbackLabel()
|
|
31606
|
+
: showVisualizer
|
|
31607
|
+
? b `<speechos-audio-visualizer
|
|
31608
|
+
?active="${showVisualizer}"
|
|
31609
|
+
></speechos-audio-visualizer>`
|
|
31610
|
+
: statusLabel}
|
|
31501
31611
|
</span>
|
|
31502
31612
|
|
|
31503
31613
|
<button
|
|
@@ -31538,6 +31648,9 @@
|
|
|
31538
31648
|
__decorate([
|
|
31539
31649
|
n({ type: String })
|
|
31540
31650
|
], SpeechOSMicButton.prototype, "errorMessage", void 0);
|
|
31651
|
+
__decorate([
|
|
31652
|
+
n({ type: String })
|
|
31653
|
+
], SpeechOSMicButton.prototype, "commandFeedback", void 0);
|
|
31541
31654
|
SpeechOSMicButton = __decorate([
|
|
31542
31655
|
t$1("speechos-mic-button")
|
|
31543
31656
|
], SpeechOSMicButton);
|
|
@@ -34315,56 +34428,649 @@
|
|
|
34315
34428
|
], SpeechOSSettingsModal);
|
|
34316
34429
|
|
|
34317
34430
|
/**
|
|
34318
|
-
*
|
|
34319
|
-
*
|
|
34320
|
-
*/
|
|
34321
|
-
var SpeechOSWidget_1;
|
|
34322
|
-
/**
|
|
34323
|
-
* Minimum duration to show the connecting animation (in milliseconds).
|
|
34324
|
-
* Since mic capture starts almost instantly, we enforce a minimum animation
|
|
34325
|
-
* duration so users can see the visual feedback before transitioning to recording.
|
|
34431
|
+
* Shared styles for lightweight popup modals
|
|
34432
|
+
* Used by dictation-output-modal and edit-help-modal
|
|
34326
34433
|
*/
|
|
34327
|
-
|
|
34328
|
-
|
|
34329
|
-
|
|
34330
|
-
|
|
34331
|
-
|
|
34332
|
-
|
|
34333
|
-
|
|
34334
|
-
|
|
34335
|
-
this.dictationCursorStart = null;
|
|
34336
|
-
this.dictationCursorEnd = null;
|
|
34337
|
-
this.editSelectionStart = null;
|
|
34338
|
-
this.editSelectionEnd = null;
|
|
34339
|
-
this.editSelectedText = "";
|
|
34340
|
-
this.boundClickOutsideHandler = null;
|
|
34341
|
-
this.modalElement = null;
|
|
34342
|
-
this.customPosition = null;
|
|
34343
|
-
this.isDragging = false;
|
|
34344
|
-
this.dragStartPos = null;
|
|
34345
|
-
this.dragOffset = { x: 0, y: 0 };
|
|
34346
|
-
this.boundDragMove = null;
|
|
34347
|
-
this.boundDragEnd = null;
|
|
34348
|
-
this.suppressNextClick = false;
|
|
34349
|
-
this.boundViewportResizeHandler = null;
|
|
34350
|
-
this.boundScrollHandler = null;
|
|
34351
|
-
}
|
|
34352
|
-
static { SpeechOSWidget_1 = this; }
|
|
34353
|
-
static { this.styles = [
|
|
34354
|
-
themeStyles,
|
|
34355
|
-
animations,
|
|
34356
|
-
i$4 `
|
|
34357
|
-
:host {
|
|
34358
|
-
position: fixed;
|
|
34359
|
-
bottom: var(--speechos-spacing-md); /* 12px - same as spacing above */
|
|
34360
|
-
z-index: var(--speechos-z-base);
|
|
34361
|
-
pointer-events: none;
|
|
34362
|
-
}
|
|
34434
|
+
/** Base popup modal styles - simpler than full settings modal */
|
|
34435
|
+
const popupModalStyles = i$4 `
|
|
34436
|
+
:host {
|
|
34437
|
+
position: fixed;
|
|
34438
|
+
inset: 0;
|
|
34439
|
+
pointer-events: none;
|
|
34440
|
+
z-index: calc(var(--speechos-z-base) + 100);
|
|
34441
|
+
}
|
|
34363
34442
|
|
|
34364
|
-
|
|
34365
|
-
|
|
34366
|
-
|
|
34367
|
-
|
|
34443
|
+
.modal-overlay {
|
|
34444
|
+
position: fixed;
|
|
34445
|
+
inset: 0;
|
|
34446
|
+
background: rgba(0, 0, 0, 0.5);
|
|
34447
|
+
display: flex;
|
|
34448
|
+
align-items: center;
|
|
34449
|
+
justify-content: center;
|
|
34450
|
+
z-index: calc(var(--speechos-z-base) + 100);
|
|
34451
|
+
opacity: 0;
|
|
34452
|
+
visibility: hidden;
|
|
34453
|
+
transition: all 0.2s ease;
|
|
34454
|
+
pointer-events: none;
|
|
34455
|
+
backdrop-filter: blur(4px);
|
|
34456
|
+
}
|
|
34457
|
+
|
|
34458
|
+
.modal-overlay.open {
|
|
34459
|
+
opacity: 1;
|
|
34460
|
+
visibility: visible;
|
|
34461
|
+
pointer-events: auto;
|
|
34462
|
+
}
|
|
34463
|
+
|
|
34464
|
+
.modal-card {
|
|
34465
|
+
background: #1a1d24;
|
|
34466
|
+
border-radius: 16px;
|
|
34467
|
+
width: 90%;
|
|
34468
|
+
max-width: 400px;
|
|
34469
|
+
display: flex;
|
|
34470
|
+
flex-direction: column;
|
|
34471
|
+
box-shadow: 0 24px 48px rgba(0, 0, 0, 0.4),
|
|
34472
|
+
0 0 0 1px rgba(255, 255, 255, 0.05);
|
|
34473
|
+
transform: scale(0.95) translateY(10px);
|
|
34474
|
+
transition: transform 0.25s cubic-bezier(0.16, 1, 0.3, 1);
|
|
34475
|
+
pointer-events: auto;
|
|
34476
|
+
overflow: hidden;
|
|
34477
|
+
}
|
|
34478
|
+
|
|
34479
|
+
.modal-overlay.open .modal-card {
|
|
34480
|
+
transform: scale(1) translateY(0);
|
|
34481
|
+
}
|
|
34482
|
+
|
|
34483
|
+
.modal-header {
|
|
34484
|
+
display: flex;
|
|
34485
|
+
align-items: center;
|
|
34486
|
+
justify-content: space-between;
|
|
34487
|
+
padding: 16px 20px;
|
|
34488
|
+
border-bottom: 1px solid rgba(255, 255, 255, 0.06);
|
|
34489
|
+
background: rgba(0, 0, 0, 0.2);
|
|
34490
|
+
}
|
|
34491
|
+
|
|
34492
|
+
.modal-title {
|
|
34493
|
+
font-size: 16px;
|
|
34494
|
+
font-weight: 600;
|
|
34495
|
+
color: white;
|
|
34496
|
+
margin: 0;
|
|
34497
|
+
letter-spacing: -0.01em;
|
|
34498
|
+
}
|
|
34499
|
+
|
|
34500
|
+
.close-button {
|
|
34501
|
+
width: 32px;
|
|
34502
|
+
height: 32px;
|
|
34503
|
+
border-radius: 8px;
|
|
34504
|
+
background: transparent;
|
|
34505
|
+
border: none;
|
|
34506
|
+
cursor: pointer;
|
|
34507
|
+
display: flex;
|
|
34508
|
+
align-items: center;
|
|
34509
|
+
justify-content: center;
|
|
34510
|
+
color: rgba(255, 255, 255, 0.5);
|
|
34511
|
+
transition: all 0.15s ease;
|
|
34512
|
+
}
|
|
34513
|
+
|
|
34514
|
+
.close-button:hover {
|
|
34515
|
+
background: rgba(255, 255, 255, 0.08);
|
|
34516
|
+
color: white;
|
|
34517
|
+
}
|
|
34518
|
+
|
|
34519
|
+
.modal-body {
|
|
34520
|
+
padding: 20px;
|
|
34521
|
+
}
|
|
34522
|
+
|
|
34523
|
+
.modal-footer {
|
|
34524
|
+
display: flex;
|
|
34525
|
+
justify-content: flex-end;
|
|
34526
|
+
gap: 10px;
|
|
34527
|
+
padding: 16px 20px;
|
|
34528
|
+
border-top: 1px solid rgba(255, 255, 255, 0.06);
|
|
34529
|
+
background: rgba(0, 0, 0, 0.1);
|
|
34530
|
+
}
|
|
34531
|
+
|
|
34532
|
+
.btn {
|
|
34533
|
+
padding: 10px 18px;
|
|
34534
|
+
border-radius: 8px;
|
|
34535
|
+
font-size: 13px;
|
|
34536
|
+
font-weight: 600;
|
|
34537
|
+
cursor: pointer;
|
|
34538
|
+
transition: all 0.15s ease;
|
|
34539
|
+
border: none;
|
|
34540
|
+
display: inline-flex;
|
|
34541
|
+
align-items: center;
|
|
34542
|
+
gap: 6px;
|
|
34543
|
+
}
|
|
34544
|
+
|
|
34545
|
+
.btn-secondary {
|
|
34546
|
+
background: rgba(255, 255, 255, 0.08);
|
|
34547
|
+
color: rgba(255, 255, 255, 0.8);
|
|
34548
|
+
}
|
|
34549
|
+
|
|
34550
|
+
.btn-secondary:hover {
|
|
34551
|
+
background: rgba(255, 255, 255, 0.12);
|
|
34552
|
+
color: white;
|
|
34553
|
+
}
|
|
34554
|
+
|
|
34555
|
+
.btn-primary {
|
|
34556
|
+
background: linear-gradient(135deg, #10b981 0%, #059669 100%);
|
|
34557
|
+
color: white;
|
|
34558
|
+
box-shadow: 0 2px 8px rgba(16, 185, 129, 0.2);
|
|
34559
|
+
}
|
|
34560
|
+
|
|
34561
|
+
.btn-primary:hover {
|
|
34562
|
+
transform: translateY(-1px);
|
|
34563
|
+
box-shadow: 0 4px 12px rgba(16, 185, 129, 0.3);
|
|
34564
|
+
}
|
|
34565
|
+
|
|
34566
|
+
.btn-primary:active {
|
|
34567
|
+
transform: translateY(0);
|
|
34568
|
+
}
|
|
34569
|
+
|
|
34570
|
+
/* Success state for copy button */
|
|
34571
|
+
.btn-success {
|
|
34572
|
+
background: linear-gradient(135deg, #34d399 0%, #10b981 100%);
|
|
34573
|
+
}
|
|
34574
|
+
|
|
34575
|
+
/* Text display area */
|
|
34576
|
+
.text-display {
|
|
34577
|
+
background: rgba(0, 0, 0, 0.3);
|
|
34578
|
+
border: 1px solid rgba(255, 255, 255, 0.08);
|
|
34579
|
+
border-radius: 10px;
|
|
34580
|
+
padding: 14px 16px;
|
|
34581
|
+
color: white;
|
|
34582
|
+
font-size: 14px;
|
|
34583
|
+
line-height: 1.5;
|
|
34584
|
+
max-height: 200px;
|
|
34585
|
+
overflow-y: auto;
|
|
34586
|
+
white-space: pre-wrap;
|
|
34587
|
+
word-break: break-word;
|
|
34588
|
+
}
|
|
34589
|
+
|
|
34590
|
+
/* Scrollbar styling */
|
|
34591
|
+
.text-display::-webkit-scrollbar {
|
|
34592
|
+
width: 6px;
|
|
34593
|
+
}
|
|
34594
|
+
|
|
34595
|
+
.text-display::-webkit-scrollbar-track {
|
|
34596
|
+
background: transparent;
|
|
34597
|
+
}
|
|
34598
|
+
|
|
34599
|
+
.text-display::-webkit-scrollbar-thumb {
|
|
34600
|
+
background: rgba(255, 255, 255, 0.15);
|
|
34601
|
+
border-radius: 3px;
|
|
34602
|
+
}
|
|
34603
|
+
|
|
34604
|
+
.text-display::-webkit-scrollbar-thumb:hover {
|
|
34605
|
+
background: rgba(255, 255, 255, 0.25);
|
|
34606
|
+
}
|
|
34607
|
+
|
|
34608
|
+
/* Instruction list styling */
|
|
34609
|
+
.instruction-list {
|
|
34610
|
+
list-style: none;
|
|
34611
|
+
padding: 0;
|
|
34612
|
+
margin: 0;
|
|
34613
|
+
}
|
|
34614
|
+
|
|
34615
|
+
.instruction-item {
|
|
34616
|
+
display: flex;
|
|
34617
|
+
align-items: flex-start;
|
|
34618
|
+
gap: 12px;
|
|
34619
|
+
padding: 12px 0;
|
|
34620
|
+
border-bottom: 1px solid rgba(255, 255, 255, 0.06);
|
|
34621
|
+
}
|
|
34622
|
+
|
|
34623
|
+
.instruction-item:last-child {
|
|
34624
|
+
border-bottom: none;
|
|
34625
|
+
}
|
|
34626
|
+
|
|
34627
|
+
.instruction-number {
|
|
34628
|
+
width: 24px;
|
|
34629
|
+
height: 24px;
|
|
34630
|
+
border-radius: 50%;
|
|
34631
|
+
background: linear-gradient(135deg, #10b981 0%, #059669 100%);
|
|
34632
|
+
color: white;
|
|
34633
|
+
font-size: 12px;
|
|
34634
|
+
font-weight: 700;
|
|
34635
|
+
display: flex;
|
|
34636
|
+
align-items: center;
|
|
34637
|
+
justify-content: center;
|
|
34638
|
+
flex-shrink: 0;
|
|
34639
|
+
}
|
|
34640
|
+
|
|
34641
|
+
.instruction-text {
|
|
34642
|
+
font-size: 14px;
|
|
34643
|
+
color: rgba(255, 255, 255, 0.85);
|
|
34644
|
+
line-height: 1.5;
|
|
34645
|
+
padding-top: 2px;
|
|
34646
|
+
}
|
|
34647
|
+
|
|
34648
|
+
/* Mobile adjustments */
|
|
34649
|
+
@media (max-width: 480px) {
|
|
34650
|
+
.modal-card {
|
|
34651
|
+
width: 95%;
|
|
34652
|
+
max-width: none;
|
|
34653
|
+
border-radius: 12px;
|
|
34654
|
+
}
|
|
34655
|
+
|
|
34656
|
+
.modal-header {
|
|
34657
|
+
padding: 14px 16px;
|
|
34658
|
+
}
|
|
34659
|
+
|
|
34660
|
+
.modal-body {
|
|
34661
|
+
padding: 16px;
|
|
34662
|
+
}
|
|
34663
|
+
|
|
34664
|
+
.modal-footer {
|
|
34665
|
+
padding: 14px 16px;
|
|
34666
|
+
}
|
|
34667
|
+
|
|
34668
|
+
.modal-title {
|
|
34669
|
+
font-size: 15px;
|
|
34670
|
+
}
|
|
34671
|
+
}
|
|
34672
|
+
`;
|
|
34673
|
+
|
|
34674
|
+
/**
|
|
34675
|
+
* Dictation output modal component
|
|
34676
|
+
* Displays transcribed text with copy functionality when no field is focused
|
|
34677
|
+
*/
|
|
34678
|
+
let SpeechOSDictationOutputModal = class SpeechOSDictationOutputModal extends i$1 {
|
|
34679
|
+
constructor() {
|
|
34680
|
+
super(...arguments);
|
|
34681
|
+
this.open = false;
|
|
34682
|
+
this.text = "";
|
|
34683
|
+
this.copied = false;
|
|
34684
|
+
this.copyTimeout = null;
|
|
34685
|
+
}
|
|
34686
|
+
static { this.styles = [
|
|
34687
|
+
themeStyles,
|
|
34688
|
+
popupModalStyles,
|
|
34689
|
+
i$4 `
|
|
34690
|
+
.header-content {
|
|
34691
|
+
display: flex;
|
|
34692
|
+
align-items: center;
|
|
34693
|
+
gap: 12px;
|
|
34694
|
+
}
|
|
34695
|
+
|
|
34696
|
+
.logo-icon {
|
|
34697
|
+
width: 32px;
|
|
34698
|
+
height: 32px;
|
|
34699
|
+
border-radius: 8px;
|
|
34700
|
+
background: linear-gradient(135deg, #10b981 0%, #8b5cf6 100%);
|
|
34701
|
+
display: flex;
|
|
34702
|
+
align-items: center;
|
|
34703
|
+
justify-content: center;
|
|
34704
|
+
flex-shrink: 0;
|
|
34705
|
+
}
|
|
34706
|
+
|
|
34707
|
+
.logo-icon svg {
|
|
34708
|
+
width: 18px;
|
|
34709
|
+
height: 18px;
|
|
34710
|
+
color: white;
|
|
34711
|
+
}
|
|
34712
|
+
|
|
34713
|
+
.modal-title {
|
|
34714
|
+
background: linear-gradient(135deg, #34d399 0%, #a78bfa 100%);
|
|
34715
|
+
-webkit-background-clip: text;
|
|
34716
|
+
-webkit-text-fill-color: transparent;
|
|
34717
|
+
background-clip: text;
|
|
34718
|
+
}
|
|
34719
|
+
|
|
34720
|
+
.btn-primary {
|
|
34721
|
+
background: linear-gradient(135deg, #10b981 0%, #059669 100%);
|
|
34722
|
+
box-shadow: 0 4px 12px rgba(16, 185, 129, 0.3);
|
|
34723
|
+
border-radius: 999px;
|
|
34724
|
+
}
|
|
34725
|
+
|
|
34726
|
+
.btn-primary:hover {
|
|
34727
|
+
background: linear-gradient(135deg, #34d399 0%, #10b981 100%);
|
|
34728
|
+
transform: translateY(-2px);
|
|
34729
|
+
box-shadow: 0 6px 16px rgba(16, 185, 129, 0.4);
|
|
34730
|
+
}
|
|
34731
|
+
|
|
34732
|
+
.btn-primary:active {
|
|
34733
|
+
transform: translateY(0);
|
|
34734
|
+
}
|
|
34735
|
+
|
|
34736
|
+
.btn-success {
|
|
34737
|
+
background: linear-gradient(135deg, #34d399 0%, #10b981 100%);
|
|
34738
|
+
box-shadow: 0 4px 12px rgba(52, 211, 153, 0.3);
|
|
34739
|
+
border-radius: 999px;
|
|
34740
|
+
}
|
|
34741
|
+
|
|
34742
|
+
.btn-secondary {
|
|
34743
|
+
border-radius: 999px;
|
|
34744
|
+
}
|
|
34745
|
+
|
|
34746
|
+
.hint {
|
|
34747
|
+
display: flex;
|
|
34748
|
+
align-items: center;
|
|
34749
|
+
gap: 6px;
|
|
34750
|
+
margin-top: 12px;
|
|
34751
|
+
padding: 8px 12px;
|
|
34752
|
+
background: rgba(16, 185, 129, 0.08);
|
|
34753
|
+
border-radius: 8px;
|
|
34754
|
+
font-size: 12px;
|
|
34755
|
+
color: rgba(255, 255, 255, 0.6);
|
|
34756
|
+
}
|
|
34757
|
+
|
|
34758
|
+
.hint-icon {
|
|
34759
|
+
color: #10b981;
|
|
34760
|
+
flex-shrink: 0;
|
|
34761
|
+
}
|
|
34762
|
+
`,
|
|
34763
|
+
]; }
|
|
34764
|
+
disconnectedCallback() {
|
|
34765
|
+
super.disconnectedCallback();
|
|
34766
|
+
if (this.copyTimeout) {
|
|
34767
|
+
clearTimeout(this.copyTimeout);
|
|
34768
|
+
this.copyTimeout = null;
|
|
34769
|
+
}
|
|
34770
|
+
}
|
|
34771
|
+
updated(changedProperties) {
|
|
34772
|
+
if (changedProperties.has("open")) {
|
|
34773
|
+
if (!this.open) {
|
|
34774
|
+
// Reset copied state when modal closes
|
|
34775
|
+
this.copied = false;
|
|
34776
|
+
if (this.copyTimeout) {
|
|
34777
|
+
clearTimeout(this.copyTimeout);
|
|
34778
|
+
this.copyTimeout = null;
|
|
34779
|
+
}
|
|
34780
|
+
}
|
|
34781
|
+
}
|
|
34782
|
+
}
|
|
34783
|
+
handleOverlayClick(e) {
|
|
34784
|
+
if (e.target === e.currentTarget) {
|
|
34785
|
+
this.close();
|
|
34786
|
+
}
|
|
34787
|
+
}
|
|
34788
|
+
handleClose() {
|
|
34789
|
+
this.close();
|
|
34790
|
+
}
|
|
34791
|
+
close() {
|
|
34792
|
+
this.dispatchEvent(new CustomEvent("modal-close", {
|
|
34793
|
+
bubbles: true,
|
|
34794
|
+
composed: true,
|
|
34795
|
+
}));
|
|
34796
|
+
}
|
|
34797
|
+
async handleCopy() {
|
|
34798
|
+
try {
|
|
34799
|
+
await navigator.clipboard.writeText(this.text);
|
|
34800
|
+
this.copied = true;
|
|
34801
|
+
// Reset copied state after 2 seconds
|
|
34802
|
+
if (this.copyTimeout) {
|
|
34803
|
+
clearTimeout(this.copyTimeout);
|
|
34804
|
+
}
|
|
34805
|
+
this.copyTimeout = window.setTimeout(() => {
|
|
34806
|
+
this.copied = false;
|
|
34807
|
+
this.copyTimeout = null;
|
|
34808
|
+
}, 2000);
|
|
34809
|
+
}
|
|
34810
|
+
catch (err) {
|
|
34811
|
+
console.error("[SpeechOS] Failed to copy text:", err);
|
|
34812
|
+
}
|
|
34813
|
+
}
|
|
34814
|
+
render() {
|
|
34815
|
+
return b `
|
|
34816
|
+
<div
|
|
34817
|
+
class="modal-overlay ${this.open ? "open" : ""}"
|
|
34818
|
+
@click="${this.handleOverlayClick}"
|
|
34819
|
+
>
|
|
34820
|
+
<div class="modal-card">
|
|
34821
|
+
<div class="modal-header">
|
|
34822
|
+
<div class="header-content">
|
|
34823
|
+
<div class="logo-icon">${micIcon(18)}</div>
|
|
34824
|
+
<h2 class="modal-title">Dictation Complete</h2>
|
|
34825
|
+
</div>
|
|
34826
|
+
<button
|
|
34827
|
+
class="close-button"
|
|
34828
|
+
@click="${this.handleClose}"
|
|
34829
|
+
aria-label="Close"
|
|
34830
|
+
>
|
|
34831
|
+
${xIcon(16)}
|
|
34832
|
+
</button>
|
|
34833
|
+
</div>
|
|
34834
|
+
|
|
34835
|
+
<div class="modal-body">
|
|
34836
|
+
<div class="text-display">${this.text}</div>
|
|
34837
|
+
<div class="hint">
|
|
34838
|
+
<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">
|
|
34839
|
+
<circle cx="12" cy="12" r="10"/><path d="M12 16v-4M12 8h.01"/>
|
|
34840
|
+
</svg>
|
|
34841
|
+
<span>Tip: Focus a text field first to auto-insert next time</span>
|
|
34842
|
+
</div>
|
|
34843
|
+
</div>
|
|
34844
|
+
|
|
34845
|
+
<div class="modal-footer">
|
|
34846
|
+
<button
|
|
34847
|
+
class="btn ${this.copied ? "btn-success" : "btn-primary"}"
|
|
34848
|
+
@click="${this.handleCopy}"
|
|
34849
|
+
>
|
|
34850
|
+
${this.copied ? checkIcon(16) : copyIcon(16)}
|
|
34851
|
+
${this.copied ? "Copied!" : "Copy"}
|
|
34852
|
+
</button>
|
|
34853
|
+
<button class="btn btn-secondary" @click="${this.handleClose}">
|
|
34854
|
+
Done
|
|
34855
|
+
</button>
|
|
34856
|
+
</div>
|
|
34857
|
+
</div>
|
|
34858
|
+
</div>
|
|
34859
|
+
`;
|
|
34860
|
+
}
|
|
34861
|
+
};
|
|
34862
|
+
__decorate([
|
|
34863
|
+
n({ type: Boolean })
|
|
34864
|
+
], SpeechOSDictationOutputModal.prototype, "open", void 0);
|
|
34865
|
+
__decorate([
|
|
34866
|
+
n({ type: String })
|
|
34867
|
+
], SpeechOSDictationOutputModal.prototype, "text", void 0);
|
|
34868
|
+
__decorate([
|
|
34869
|
+
r()
|
|
34870
|
+
], SpeechOSDictationOutputModal.prototype, "copied", void 0);
|
|
34871
|
+
SpeechOSDictationOutputModal = __decorate([
|
|
34872
|
+
t$1("speechos-dictation-output-modal")
|
|
34873
|
+
], SpeechOSDictationOutputModal);
|
|
34874
|
+
|
|
34875
|
+
/**
|
|
34876
|
+
* Edit help modal component
|
|
34877
|
+
* Displays usage instructions for the edit feature when no text is selected
|
|
34878
|
+
*/
|
|
34879
|
+
let SpeechOSEditHelpModal = class SpeechOSEditHelpModal extends i$1 {
|
|
34880
|
+
constructor() {
|
|
34881
|
+
super(...arguments);
|
|
34882
|
+
this.open = false;
|
|
34883
|
+
}
|
|
34884
|
+
static { this.styles = [
|
|
34885
|
+
themeStyles,
|
|
34886
|
+
popupModalStyles,
|
|
34887
|
+
i$4 `
|
|
34888
|
+
.header-content {
|
|
34889
|
+
display: flex;
|
|
34890
|
+
align-items: center;
|
|
34891
|
+
gap: 12px;
|
|
34892
|
+
}
|
|
34893
|
+
|
|
34894
|
+
.logo-icon {
|
|
34895
|
+
width: 32px;
|
|
34896
|
+
height: 32px;
|
|
34897
|
+
border-radius: 8px;
|
|
34898
|
+
background: linear-gradient(135deg, #8b5cf6 0%, #6366f1 100%);
|
|
34899
|
+
display: flex;
|
|
34900
|
+
align-items: center;
|
|
34901
|
+
justify-content: center;
|
|
34902
|
+
flex-shrink: 0;
|
|
34903
|
+
}
|
|
34904
|
+
|
|
34905
|
+
.logo-icon svg {
|
|
34906
|
+
width: 18px;
|
|
34907
|
+
height: 18px;
|
|
34908
|
+
color: white;
|
|
34909
|
+
}
|
|
34910
|
+
|
|
34911
|
+
.modal-title {
|
|
34912
|
+
background: linear-gradient(135deg, #a78bfa 0%, #818cf8 100%);
|
|
34913
|
+
-webkit-background-clip: text;
|
|
34914
|
+
-webkit-text-fill-color: transparent;
|
|
34915
|
+
background-clip: text;
|
|
34916
|
+
}
|
|
34917
|
+
|
|
34918
|
+
.instruction-number {
|
|
34919
|
+
background: linear-gradient(135deg, #8b5cf6 0%, #6366f1 100%);
|
|
34920
|
+
}
|
|
34921
|
+
|
|
34922
|
+
.btn-primary {
|
|
34923
|
+
background: linear-gradient(135deg, #8b5cf6 0%, #7c3aed 100%);
|
|
34924
|
+
box-shadow: 0 4px 12px rgba(139, 92, 246, 0.3);
|
|
34925
|
+
border-radius: 999px;
|
|
34926
|
+
}
|
|
34927
|
+
|
|
34928
|
+
.btn-primary:hover {
|
|
34929
|
+
background: linear-gradient(135deg, #a78bfa 0%, #8b5cf6 100%);
|
|
34930
|
+
transform: translateY(-2px);
|
|
34931
|
+
box-shadow: 0 6px 16px rgba(139, 92, 246, 0.4);
|
|
34932
|
+
}
|
|
34933
|
+
|
|
34934
|
+
.btn-primary:active {
|
|
34935
|
+
transform: translateY(0);
|
|
34936
|
+
}
|
|
34937
|
+
`,
|
|
34938
|
+
]; }
|
|
34939
|
+
handleOverlayClick(e) {
|
|
34940
|
+
if (e.target === e.currentTarget) {
|
|
34941
|
+
this.close();
|
|
34942
|
+
}
|
|
34943
|
+
}
|
|
34944
|
+
handleClose() {
|
|
34945
|
+
this.close();
|
|
34946
|
+
}
|
|
34947
|
+
close() {
|
|
34948
|
+
this.dispatchEvent(new CustomEvent("modal-close", {
|
|
34949
|
+
bubbles: true,
|
|
34950
|
+
composed: true,
|
|
34951
|
+
}));
|
|
34952
|
+
}
|
|
34953
|
+
render() {
|
|
34954
|
+
return b `
|
|
34955
|
+
<div
|
|
34956
|
+
class="modal-overlay ${this.open ? "open" : ""}"
|
|
34957
|
+
@click="${this.handleOverlayClick}"
|
|
34958
|
+
>
|
|
34959
|
+
<div class="modal-card">
|
|
34960
|
+
<div class="modal-header">
|
|
34961
|
+
<div class="header-content">
|
|
34962
|
+
<div class="logo-icon">${editIcon(18)}</div>
|
|
34963
|
+
<h2 class="modal-title">How to Use Edit</h2>
|
|
34964
|
+
</div>
|
|
34965
|
+
<button
|
|
34966
|
+
class="close-button"
|
|
34967
|
+
@click="${this.handleClose}"
|
|
34968
|
+
aria-label="Close"
|
|
34969
|
+
>
|
|
34970
|
+
${xIcon(16)}
|
|
34971
|
+
</button>
|
|
34972
|
+
</div>
|
|
34973
|
+
|
|
34974
|
+
<div class="modal-body">
|
|
34975
|
+
<ol class="instruction-list">
|
|
34976
|
+
<li class="instruction-item">
|
|
34977
|
+
<span class="instruction-number">1</span>
|
|
34978
|
+
<span class="instruction-text">
|
|
34979
|
+
Click on a text field to focus it, or select the text you want
|
|
34980
|
+
to edit
|
|
34981
|
+
</span>
|
|
34982
|
+
</li>
|
|
34983
|
+
<li class="instruction-item">
|
|
34984
|
+
<span class="instruction-number">2</span>
|
|
34985
|
+
<span class="instruction-text">
|
|
34986
|
+
Click the Edit button in the SpeechOS widget
|
|
34987
|
+
</span>
|
|
34988
|
+
</li>
|
|
34989
|
+
<li class="instruction-item">
|
|
34990
|
+
<span class="instruction-number">3</span>
|
|
34991
|
+
<span class="instruction-text">
|
|
34992
|
+
Speak your editing instructions (e.g., "make it more formal"
|
|
34993
|
+
or "fix the grammar")
|
|
34994
|
+
</span>
|
|
34995
|
+
</li>
|
|
34996
|
+
</ol>
|
|
34997
|
+
</div>
|
|
34998
|
+
|
|
34999
|
+
<div class="modal-footer">
|
|
35000
|
+
<button class="btn btn-primary" @click="${this.handleClose}">
|
|
35001
|
+
Got it
|
|
35002
|
+
</button>
|
|
35003
|
+
</div>
|
|
35004
|
+
</div>
|
|
35005
|
+
</div>
|
|
35006
|
+
`;
|
|
35007
|
+
}
|
|
35008
|
+
};
|
|
35009
|
+
__decorate([
|
|
35010
|
+
n({ type: Boolean })
|
|
35011
|
+
], SpeechOSEditHelpModal.prototype, "open", void 0);
|
|
35012
|
+
SpeechOSEditHelpModal = __decorate([
|
|
35013
|
+
t$1("speechos-edit-help-modal")
|
|
35014
|
+
], SpeechOSEditHelpModal);
|
|
35015
|
+
|
|
35016
|
+
/**
|
|
35017
|
+
* Main widget container component
|
|
35018
|
+
* Composes mic button and action bubbles with state management
|
|
35019
|
+
*/
|
|
35020
|
+
var SpeechOSWidget_1;
|
|
35021
|
+
/**
|
|
35022
|
+
* Minimum duration to show the connecting animation (in milliseconds).
|
|
35023
|
+
* Since mic capture starts almost instantly, we enforce a minimum animation
|
|
35024
|
+
* duration so users can see the visual feedback before transitioning to recording.
|
|
35025
|
+
*/
|
|
35026
|
+
const MIN_CONNECTING_ANIMATION_MS = 200;
|
|
35027
|
+
let SpeechOSWidget = class SpeechOSWidget extends i$1 {
|
|
35028
|
+
constructor() {
|
|
35029
|
+
super(...arguments);
|
|
35030
|
+
this.widgetState = state.getState();
|
|
35031
|
+
this.settingsOpen = false;
|
|
35032
|
+
this.dictationModalOpen = false;
|
|
35033
|
+
this.dictationModalText = "";
|
|
35034
|
+
this.editHelpModalOpen = false;
|
|
35035
|
+
this.commandFeedback = null;
|
|
35036
|
+
this.dictationTargetElement = null;
|
|
35037
|
+
this.editTargetElement = null;
|
|
35038
|
+
this.dictationCursorStart = null;
|
|
35039
|
+
this.dictationCursorEnd = null;
|
|
35040
|
+
this.editSelectionStart = null;
|
|
35041
|
+
this.editSelectionEnd = null;
|
|
35042
|
+
this.editSelectedText = "";
|
|
35043
|
+
this.boundClickOutsideHandler = null;
|
|
35044
|
+
this.modalElement = null;
|
|
35045
|
+
this.dictationModalElement = null;
|
|
35046
|
+
this.editHelpModalElement = null;
|
|
35047
|
+
this.commandFeedbackTimeout = null;
|
|
35048
|
+
this.customPosition = null;
|
|
35049
|
+
this.isDragging = false;
|
|
35050
|
+
this.dragStartPos = null;
|
|
35051
|
+
this.dragOffset = { x: 0, y: 0 };
|
|
35052
|
+
this.boundDragMove = null;
|
|
35053
|
+
this.boundDragEnd = null;
|
|
35054
|
+
this.suppressNextClick = false;
|
|
35055
|
+
this.boundViewportResizeHandler = null;
|
|
35056
|
+
this.boundScrollHandler = null;
|
|
35057
|
+
}
|
|
35058
|
+
static { SpeechOSWidget_1 = this; }
|
|
35059
|
+
static { this.styles = [
|
|
35060
|
+
themeStyles,
|
|
35061
|
+
animations,
|
|
35062
|
+
i$4 `
|
|
35063
|
+
:host {
|
|
35064
|
+
position: fixed;
|
|
35065
|
+
bottom: var(--speechos-spacing-md); /* 12px - same as spacing above */
|
|
35066
|
+
z-index: var(--speechos-z-base);
|
|
35067
|
+
pointer-events: none;
|
|
35068
|
+
}
|
|
35069
|
+
|
|
35070
|
+
:host {
|
|
35071
|
+
left: 50%;
|
|
35072
|
+
transform: translateX(-50%);
|
|
35073
|
+
}
|
|
34368
35074
|
|
|
34369
35075
|
:host(.custom-position) {
|
|
34370
35076
|
right: unset;
|
|
@@ -34432,6 +35138,18 @@
|
|
|
34432
35138
|
this.settingsOpen = false;
|
|
34433
35139
|
});
|
|
34434
35140
|
document.body.appendChild(this.modalElement);
|
|
35141
|
+
// Mount dictation output modal
|
|
35142
|
+
this.dictationModalElement = document.createElement("speechos-dictation-output-modal");
|
|
35143
|
+
this.dictationModalElement.addEventListener("modal-close", () => {
|
|
35144
|
+
this.dictationModalOpen = false;
|
|
35145
|
+
});
|
|
35146
|
+
document.body.appendChild(this.dictationModalElement);
|
|
35147
|
+
// Mount edit help modal
|
|
35148
|
+
this.editHelpModalElement = document.createElement("speechos-edit-help-modal");
|
|
35149
|
+
this.editHelpModalElement.addEventListener("modal-close", () => {
|
|
35150
|
+
this.editHelpModalOpen = false;
|
|
35151
|
+
});
|
|
35152
|
+
document.body.appendChild(this.editHelpModalElement);
|
|
34435
35153
|
this.stateUnsubscribe = state.subscribe((newState) => {
|
|
34436
35154
|
if (!newState.isVisible || !newState.isExpanded) {
|
|
34437
35155
|
this.settingsOpen = false;
|
|
@@ -34471,6 +35189,18 @@
|
|
|
34471
35189
|
this.modalElement.remove();
|
|
34472
35190
|
this.modalElement = null;
|
|
34473
35191
|
}
|
|
35192
|
+
if (this.dictationModalElement) {
|
|
35193
|
+
this.dictationModalElement.remove();
|
|
35194
|
+
this.dictationModalElement = null;
|
|
35195
|
+
}
|
|
35196
|
+
if (this.editHelpModalElement) {
|
|
35197
|
+
this.editHelpModalElement.remove();
|
|
35198
|
+
this.editHelpModalElement = null;
|
|
35199
|
+
}
|
|
35200
|
+
if (this.commandFeedbackTimeout) {
|
|
35201
|
+
clearTimeout(this.commandFeedbackTimeout);
|
|
35202
|
+
this.commandFeedbackTimeout = null;
|
|
35203
|
+
}
|
|
34474
35204
|
if (this.stateUnsubscribe) {
|
|
34475
35205
|
this.stateUnsubscribe();
|
|
34476
35206
|
}
|
|
@@ -34502,6 +35232,15 @@
|
|
|
34502
35232
|
if (changedProperties.has("settingsOpen") && this.modalElement) {
|
|
34503
35233
|
this.modalElement.open = this.settingsOpen;
|
|
34504
35234
|
}
|
|
35235
|
+
if (changedProperties.has("dictationModalOpen") && this.dictationModalElement) {
|
|
35236
|
+
this.dictationModalElement.open = this.dictationModalOpen;
|
|
35237
|
+
}
|
|
35238
|
+
if (changedProperties.has("dictationModalText") && this.dictationModalElement) {
|
|
35239
|
+
this.dictationModalElement.text = this.dictationModalText;
|
|
35240
|
+
}
|
|
35241
|
+
if (changedProperties.has("editHelpModalOpen") && this.editHelpModalElement) {
|
|
35242
|
+
this.editHelpModalElement.open = this.editHelpModalOpen;
|
|
35243
|
+
}
|
|
34505
35244
|
}
|
|
34506
35245
|
handleClickOutside(event) {
|
|
34507
35246
|
const target = event.target;
|
|
@@ -34539,7 +35278,10 @@
|
|
|
34539
35278
|
return;
|
|
34540
35279
|
}
|
|
34541
35280
|
if (!clickedInWidget) {
|
|
34542
|
-
|
|
35281
|
+
// Don't hide if alwaysVisible is enabled
|
|
35282
|
+
if (!isAlwaysVisible()) {
|
|
35283
|
+
state.hide();
|
|
35284
|
+
}
|
|
34543
35285
|
}
|
|
34544
35286
|
}
|
|
34545
35287
|
isFormField(element) {
|
|
@@ -34660,6 +35402,8 @@
|
|
|
34660
35402
|
return;
|
|
34661
35403
|
}
|
|
34662
35404
|
if (this.widgetState.recordingState === "idle") {
|
|
35405
|
+
// Clear command feedback on any mic click
|
|
35406
|
+
this.clearCommandFeedback();
|
|
34663
35407
|
// If we're expanding, prefetch the token to reduce latency when user selects an action
|
|
34664
35408
|
if (!this.widgetState.isExpanded) {
|
|
34665
35409
|
// Fire and forget - we don't need to wait for this (LiveKit only)
|
|
@@ -34684,12 +35428,19 @@
|
|
|
34684
35428
|
}
|
|
34685
35429
|
else {
|
|
34686
35430
|
state.stopRecording();
|
|
34687
|
-
getConfig();
|
|
34688
35431
|
const backend = getBackend();
|
|
34689
35432
|
try {
|
|
34690
35433
|
const transcription = await this.withMinDisplayTime(backend.stopVoiceSession(), 300);
|
|
34691
35434
|
if (transcription) {
|
|
34692
|
-
|
|
35435
|
+
// Check if we have a target element to insert into
|
|
35436
|
+
if (this.dictationTargetElement) {
|
|
35437
|
+
this.insertTranscription(transcription);
|
|
35438
|
+
}
|
|
35439
|
+
else {
|
|
35440
|
+
// No target element - show dictation output modal
|
|
35441
|
+
this.dictationModalText = transcription;
|
|
35442
|
+
this.dictationModalOpen = true;
|
|
35443
|
+
}
|
|
34693
35444
|
transcriptStore.saveTranscript(transcription, "dictate");
|
|
34694
35445
|
}
|
|
34695
35446
|
state.completeRecording();
|
|
@@ -34737,6 +35488,7 @@
|
|
|
34737
35488
|
}
|
|
34738
35489
|
}
|
|
34739
35490
|
handleCloseWidget() {
|
|
35491
|
+
this.clearCommandFeedback();
|
|
34740
35492
|
state.hide();
|
|
34741
35493
|
}
|
|
34742
35494
|
handleSettingsClick() {
|
|
@@ -34863,11 +35615,20 @@
|
|
|
34863
35615
|
}
|
|
34864
35616
|
handleActionSelect(event) {
|
|
34865
35617
|
const { action } = event.detail;
|
|
35618
|
+
// Clear any existing command feedback when a new action is selected
|
|
35619
|
+
this.clearCommandFeedback();
|
|
34866
35620
|
state.setActiveAction(action);
|
|
34867
35621
|
if (action === "dictate") {
|
|
34868
35622
|
this.startDictation();
|
|
34869
35623
|
}
|
|
34870
35624
|
else if (action === "edit") {
|
|
35625
|
+
// Check if there's a focused element before starting edit
|
|
35626
|
+
if (!this.widgetState.focusedElement) {
|
|
35627
|
+
// No focused element - show edit help modal
|
|
35628
|
+
this.editHelpModalOpen = true;
|
|
35629
|
+
state.setActiveAction(null);
|
|
35630
|
+
return;
|
|
35631
|
+
}
|
|
34871
35632
|
this.startEdit();
|
|
34872
35633
|
}
|
|
34873
35634
|
else if (action === "command") {
|
|
@@ -35074,6 +35835,10 @@
|
|
|
35074
35835
|
// Note: command:complete event is already emitted by the backend
|
|
35075
35836
|
// when the command_result message is received, so we don't emit here
|
|
35076
35837
|
state.completeRecording();
|
|
35838
|
+
// Keep widget visible but collapsed (just mic button, no action bubbles)
|
|
35839
|
+
state.setState({ isExpanded: false });
|
|
35840
|
+
// Show command feedback
|
|
35841
|
+
this.showCommandFeedback(result ? "success" : "none");
|
|
35077
35842
|
backend.disconnect().catch(() => { });
|
|
35078
35843
|
// Start auto-refresh to keep token fresh for subsequent commands (LiveKit only)
|
|
35079
35844
|
backend.startAutoRefresh?.();
|
|
@@ -35086,6 +35851,25 @@
|
|
|
35086
35851
|
}
|
|
35087
35852
|
}
|
|
35088
35853
|
}
|
|
35854
|
+
showCommandFeedback(feedback) {
|
|
35855
|
+
this.commandFeedback = feedback;
|
|
35856
|
+
// Clear any existing timeout
|
|
35857
|
+
if (this.commandFeedbackTimeout) {
|
|
35858
|
+
clearTimeout(this.commandFeedbackTimeout);
|
|
35859
|
+
}
|
|
35860
|
+
// Auto-dismiss after 4 seconds
|
|
35861
|
+
this.commandFeedbackTimeout = window.setTimeout(() => {
|
|
35862
|
+
this.commandFeedback = null;
|
|
35863
|
+
this.commandFeedbackTimeout = null;
|
|
35864
|
+
}, 4000);
|
|
35865
|
+
}
|
|
35866
|
+
clearCommandFeedback() {
|
|
35867
|
+
if (this.commandFeedbackTimeout) {
|
|
35868
|
+
clearTimeout(this.commandFeedbackTimeout);
|
|
35869
|
+
this.commandFeedbackTimeout = null;
|
|
35870
|
+
}
|
|
35871
|
+
this.commandFeedback = null;
|
|
35872
|
+
}
|
|
35089
35873
|
supportsSelection(element) {
|
|
35090
35874
|
if (element.tagName.toLowerCase() === "textarea") {
|
|
35091
35875
|
return true;
|
|
@@ -35203,6 +35987,7 @@
|
|
|
35203
35987
|
activeAction="${this.widgetState.activeAction || ""}"
|
|
35204
35988
|
editPreviewText="${this.editSelectedText}"
|
|
35205
35989
|
errorMessage="${this.widgetState.errorMessage || ""}"
|
|
35990
|
+
.commandFeedback="${this.commandFeedback}"
|
|
35206
35991
|
@mic-click="${this.handleMicClick}"
|
|
35207
35992
|
@stop-recording="${this.handleStopRecording}"
|
|
35208
35993
|
@cancel-operation="${this.handleCancelOperation}"
|
|
@@ -35220,6 +36005,18 @@
|
|
|
35220
36005
|
__decorate([
|
|
35221
36006
|
r()
|
|
35222
36007
|
], SpeechOSWidget.prototype, "settingsOpen", void 0);
|
|
36008
|
+
__decorate([
|
|
36009
|
+
r()
|
|
36010
|
+
], SpeechOSWidget.prototype, "dictationModalOpen", void 0);
|
|
36011
|
+
__decorate([
|
|
36012
|
+
r()
|
|
36013
|
+
], SpeechOSWidget.prototype, "dictationModalText", void 0);
|
|
36014
|
+
__decorate([
|
|
36015
|
+
r()
|
|
36016
|
+
], SpeechOSWidget.prototype, "editHelpModalOpen", void 0);
|
|
36017
|
+
__decorate([
|
|
36018
|
+
r()
|
|
36019
|
+
], SpeechOSWidget.prototype, "commandFeedback", void 0);
|
|
35223
36020
|
SpeechOSWidget = SpeechOSWidget_1 = __decorate([
|
|
35224
36021
|
t$1("speechos-widget")
|
|
35225
36022
|
], SpeechOSWidget);
|
|
@@ -35300,6 +36097,10 @@
|
|
|
35300
36097
|
}
|
|
35301
36098
|
// Create and mount widget
|
|
35302
36099
|
this.mountWidget();
|
|
36100
|
+
// If alwaysVisible is enabled, show the widget immediately
|
|
36101
|
+
if (isAlwaysVisible()) {
|
|
36102
|
+
state.show();
|
|
36103
|
+
}
|
|
35303
36104
|
this.isInitialized = true;
|
|
35304
36105
|
// Log initialization in debug mode
|
|
35305
36106
|
if (finalConfig.debug) {
|