@speechos/client 0.2.0 → 0.2.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/form-detector.d.ts +1 -0
- package/dist/form-detector.d.ts.map +1 -0
- package/dist/form-detector.test.d.ts +5 -0
- package/dist/form-detector.test.d.ts.map +1 -0
- package/dist/index.cjs +2785 -314
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.ts +1 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +2756 -248
- package/dist/index.js.map +1 -1
- package/dist/speechos.d.ts +1 -0
- package/dist/speechos.d.ts.map +1 -0
- package/dist/ui/action-bubbles.d.ts +1 -0
- package/dist/ui/action-bubbles.d.ts.map +1 -0
- package/dist/ui/icons.d.ts +1 -0
- package/dist/ui/icons.d.ts.map +1 -0
- package/dist/ui/index.d.ts +1 -0
- package/dist/ui/index.d.ts.map +1 -0
- package/dist/ui/mic-button.d.ts +1 -0
- package/dist/ui/mic-button.d.ts.map +1 -0
- package/dist/ui/settings-button.d.ts +1 -0
- package/dist/ui/settings-button.d.ts.map +1 -0
- package/dist/ui/settings-modal.d.ts +1 -0
- package/dist/ui/settings-modal.d.ts.map +1 -0
- package/dist/ui/styles/theme.d.ts +1 -0
- package/dist/ui/styles/theme.d.ts.map +1 -0
- package/dist/ui/widget.d.ts +1 -0
- package/dist/ui/widget.d.ts.map +1 -0
- package/package.json +4 -6
- package/dist/form-detector.d.cts +0 -25
- package/dist/index.d.cts +0 -13
- package/dist/speechos.d.cts +0 -74
- package/dist/ui/action-bubbles.d.cts +0 -17
- package/dist/ui/icons.d.cts +0 -88
- package/dist/ui/index.d.cts +0 -20
- package/dist/ui/mic-button.d.cts +0 -34
- package/dist/ui/settings-button.d.cts +0 -16
- package/dist/ui/settings-modal.d.cts +0 -34
- package/dist/ui/styles/theme.d.cts +0 -17
- package/dist/ui/widget.d.cts +0 -64
package/dist/index.cjs
CHANGED
|
@@ -1,335 +1,2806 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
1
3
|
Object.defineProperty(exports, '__esModule', { value: true });
|
|
2
|
-
//#region rolldown:runtime
|
|
3
|
-
var __create = Object.create;
|
|
4
|
-
var __defProp = Object.defineProperty;
|
|
5
|
-
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
6
|
-
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
7
|
-
var __getProtoOf = Object.getPrototypeOf;
|
|
8
|
-
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
9
|
-
var __copyProps = (to, from, except, desc) => {
|
|
10
|
-
if (from && typeof from === "object" || typeof from === "function") for (var keys = __getOwnPropNames(from), i = 0, n = keys.length, key; i < n; i++) {
|
|
11
|
-
key = keys[i];
|
|
12
|
-
if (!__hasOwnProp.call(to, key) && key !== except) __defProp(to, key, {
|
|
13
|
-
get: ((k) => from[k]).bind(null, key),
|
|
14
|
-
enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable
|
|
15
|
-
});
|
|
16
|
-
}
|
|
17
|
-
return to;
|
|
18
|
-
};
|
|
19
|
-
var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", {
|
|
20
|
-
value: mod,
|
|
21
|
-
enumerable: true
|
|
22
|
-
}) : target, mod));
|
|
23
4
|
|
|
24
|
-
|
|
25
|
-
const __speechos_core = __toESM(require("@speechos/core"));
|
|
5
|
+
var core = require('@speechos/core');
|
|
26
6
|
|
|
27
|
-
//#region src/form-detector.ts
|
|
28
7
|
/**
|
|
29
|
-
*
|
|
30
|
-
|
|
8
|
+
* Form field focus detection for SpeechOS Client SDK
|
|
9
|
+
* Detects when users focus on form fields and manages widget visibility
|
|
10
|
+
*/
|
|
11
|
+
/**
|
|
12
|
+
* Check if an element is a form field that we should track
|
|
13
|
+
*/
|
|
31
14
|
function isFormField(element) {
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
15
|
+
if (!element || !(element instanceof HTMLElement)) {
|
|
16
|
+
return false;
|
|
17
|
+
}
|
|
18
|
+
const tagName = element.tagName.toLowerCase();
|
|
19
|
+
// Check for input, textarea
|
|
20
|
+
if (tagName === "input" || tagName === "textarea") {
|
|
21
|
+
// Exclude certain input types that don't accept text
|
|
22
|
+
if (tagName === "input") {
|
|
23
|
+
const type = element.type.toLowerCase();
|
|
24
|
+
const excludedTypes = [
|
|
25
|
+
"checkbox",
|
|
26
|
+
"radio",
|
|
27
|
+
"submit",
|
|
28
|
+
"button",
|
|
29
|
+
"reset",
|
|
30
|
+
"file",
|
|
31
|
+
"hidden",
|
|
32
|
+
];
|
|
33
|
+
if (excludedTypes.includes(type)) {
|
|
34
|
+
return false;
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
return true;
|
|
38
|
+
}
|
|
39
|
+
// Check for contenteditable
|
|
40
|
+
if (element.isContentEditable ||
|
|
41
|
+
element.getAttribute("contenteditable") === "true") {
|
|
42
|
+
return true;
|
|
43
|
+
}
|
|
44
|
+
return false;
|
|
52
45
|
}
|
|
53
46
|
/**
|
|
54
|
-
* Form detector class that manages focus tracking
|
|
55
|
-
*/
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
47
|
+
* Form detector class that manages focus tracking
|
|
48
|
+
*/
|
|
49
|
+
class FormDetector {
|
|
50
|
+
constructor() {
|
|
51
|
+
this.isActive = false;
|
|
52
|
+
this.focusHandler = null;
|
|
53
|
+
this.blurHandler = null;
|
|
54
|
+
}
|
|
55
|
+
/**
|
|
56
|
+
* Start detecting form field focus events
|
|
57
|
+
*/
|
|
58
|
+
start() {
|
|
59
|
+
if (this.isActive) {
|
|
60
|
+
console.warn("FormDetector is already active");
|
|
61
|
+
return;
|
|
62
|
+
}
|
|
63
|
+
// Create event handlers
|
|
64
|
+
this.focusHandler = (event) => {
|
|
65
|
+
const target = event.target;
|
|
66
|
+
if (isFormField(target)) {
|
|
67
|
+
core.state.setFocusedElement(target);
|
|
68
|
+
core.state.show();
|
|
69
|
+
core.events.emit("form:focus", { element: target });
|
|
70
|
+
}
|
|
71
|
+
};
|
|
72
|
+
this.blurHandler = (event) => {
|
|
73
|
+
const target = event.target;
|
|
74
|
+
if (isFormField(target)) {
|
|
75
|
+
// Check relatedTarget (where focus is going) immediately
|
|
76
|
+
const relatedTarget = event.relatedTarget;
|
|
77
|
+
const widget = document.querySelector("speechos-widget");
|
|
78
|
+
// If focus is going to another form field or the widget, don't hide
|
|
79
|
+
const goingToFormField = isFormField(relatedTarget);
|
|
80
|
+
const goingToWidget = widget &&
|
|
81
|
+
(widget.contains(relatedTarget) ||
|
|
82
|
+
widget.shadowRoot?.contains(relatedTarget) ||
|
|
83
|
+
relatedTarget === widget);
|
|
84
|
+
if (goingToFormField || goingToWidget) {
|
|
85
|
+
return;
|
|
86
|
+
}
|
|
87
|
+
// Delay hiding to allow for any edge cases
|
|
88
|
+
setTimeout(() => {
|
|
89
|
+
// Double-check: verify focus is still not on a form field or widget
|
|
90
|
+
const activeElement = document.activeElement;
|
|
91
|
+
const isWidgetFocused = widget &&
|
|
92
|
+
(widget.contains(activeElement) ||
|
|
93
|
+
widget.shadowRoot?.contains(activeElement));
|
|
94
|
+
// Only hide if no form field is focused AND widget isn't focused
|
|
95
|
+
if (!isFormField(activeElement) && !isWidgetFocused) {
|
|
96
|
+
core.state.setFocusedElement(null);
|
|
97
|
+
core.state.hide();
|
|
98
|
+
core.events.emit("form:blur", { element: null });
|
|
99
|
+
}
|
|
100
|
+
}, 150);
|
|
101
|
+
}
|
|
102
|
+
};
|
|
103
|
+
// Attach listeners to document
|
|
104
|
+
document.addEventListener("focusin", this.focusHandler, true);
|
|
105
|
+
document.addEventListener("focusout", this.blurHandler, true);
|
|
106
|
+
this.isActive = true;
|
|
107
|
+
}
|
|
108
|
+
/**
|
|
109
|
+
* Stop detecting form field focus events
|
|
110
|
+
*/
|
|
111
|
+
stop() {
|
|
112
|
+
if (!this.isActive) {
|
|
113
|
+
return;
|
|
114
|
+
}
|
|
115
|
+
// Remove event listeners
|
|
116
|
+
if (this.focusHandler) {
|
|
117
|
+
document.removeEventListener("focusin", this.focusHandler, true);
|
|
118
|
+
this.focusHandler = null;
|
|
119
|
+
}
|
|
120
|
+
if (this.blurHandler) {
|
|
121
|
+
document.removeEventListener("focusout", this.blurHandler, true);
|
|
122
|
+
this.blurHandler = null;
|
|
123
|
+
}
|
|
124
|
+
// Reset state
|
|
125
|
+
core.state.setFocusedElement(null);
|
|
126
|
+
core.state.hide();
|
|
127
|
+
this.isActive = false;
|
|
128
|
+
}
|
|
129
|
+
/**
|
|
130
|
+
* Check if the detector is currently active
|
|
131
|
+
*/
|
|
132
|
+
get active() {
|
|
133
|
+
return this.isActive;
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
// Export singleton instance
|
|
123
137
|
const formDetector = new FormDetector();
|
|
124
138
|
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
message: errorMessage,
|
|
153
|
-
source: "init"
|
|
154
|
-
});
|
|
155
|
-
throw error;
|
|
156
|
-
}
|
|
157
|
-
const finalConfig = (0, __speechos_core.getConfig)();
|
|
158
|
-
this.instance = new SpeechOS();
|
|
159
|
-
try {
|
|
160
|
-
if (finalConfig.debug) console.log("[SpeechOS] Fetching LiveKit token...");
|
|
161
|
-
await __speechos_core.livekit.fetchToken();
|
|
162
|
-
if (finalConfig.debug) console.log("[SpeechOS] LiveKit token fetched successfully");
|
|
163
|
-
} catch (error) {
|
|
164
|
-
const errorMessage = error instanceof Error ? error.message : "Failed to fetch token";
|
|
165
|
-
console.error(`[SpeechOS] Error: ${errorMessage} (init_token_fetch)`);
|
|
166
|
-
__speechos_core.events.emit("error", {
|
|
167
|
-
code: "init_token_fetch",
|
|
168
|
-
message: errorMessage,
|
|
169
|
-
source: "init"
|
|
170
|
-
});
|
|
171
|
-
}
|
|
172
|
-
formDetector.start();
|
|
173
|
-
this.mountWidget();
|
|
174
|
-
this.isInitialized = true;
|
|
175
|
-
if (finalConfig.debug) console.log("[SpeechOS] Initialized with config:", finalConfig);
|
|
176
|
-
}
|
|
177
|
-
/**
|
|
178
|
-
* Destroy the SpeechOS SDK and clean up resources
|
|
179
|
-
*/
|
|
180
|
-
static async destroy() {
|
|
181
|
-
if (!this.isInitialized) {
|
|
182
|
-
console.warn("SpeechOS is not initialized");
|
|
183
|
-
return;
|
|
184
|
-
}
|
|
185
|
-
formDetector.stop();
|
|
186
|
-
await __speechos_core.livekit.disconnect();
|
|
187
|
-
this.unmountWidget();
|
|
188
|
-
__speechos_core.events.clear();
|
|
189
|
-
__speechos_core.state.reset();
|
|
190
|
-
this.instance = null;
|
|
191
|
-
this.isInitialized = false;
|
|
192
|
-
const config = (0, __speechos_core.getConfig)();
|
|
193
|
-
if (config.debug) console.log("[SpeechOS] Destroyed and cleaned up");
|
|
194
|
-
}
|
|
195
|
-
/**
|
|
196
|
-
* Check if SpeechOS is initialized
|
|
197
|
-
*/
|
|
198
|
-
static get initialized() {
|
|
199
|
-
return this.isInitialized;
|
|
200
|
-
}
|
|
201
|
-
/**
|
|
202
|
-
* Get the current state
|
|
203
|
-
*/
|
|
204
|
-
static getState() {
|
|
205
|
-
return __speechos_core.state.getState();
|
|
206
|
-
}
|
|
207
|
-
/**
|
|
208
|
-
* Get the event emitter for external listeners
|
|
209
|
-
*/
|
|
210
|
-
static get events() {
|
|
211
|
-
return __speechos_core.events;
|
|
212
|
-
}
|
|
213
|
-
/**
|
|
214
|
-
* Mount the widget to the DOM
|
|
215
|
-
*/
|
|
216
|
-
static mountWidget() {
|
|
217
|
-
if (this.widgetElement) {
|
|
218
|
-
console.warn("Widget is already mounted");
|
|
219
|
-
return;
|
|
220
|
-
}
|
|
221
|
-
const widget = document.createElement("speechos-widget");
|
|
222
|
-
this.widgetElement = widget;
|
|
223
|
-
document.body.appendChild(widget);
|
|
224
|
-
}
|
|
225
|
-
/**
|
|
226
|
-
* Unmount the widget from the DOM
|
|
227
|
-
*/
|
|
228
|
-
static unmountWidget() {
|
|
229
|
-
if (this.widgetElement) {
|
|
230
|
-
this.widgetElement.remove();
|
|
231
|
-
this.widgetElement = null;
|
|
232
|
-
}
|
|
233
|
-
}
|
|
234
|
-
/**
|
|
235
|
-
* Show the widget programmatically
|
|
236
|
-
*/
|
|
237
|
-
static show() {
|
|
238
|
-
__speechos_core.state.show();
|
|
239
|
-
}
|
|
240
|
-
/**
|
|
241
|
-
* Hide the widget programmatically
|
|
242
|
-
*/
|
|
243
|
-
static hide() {
|
|
244
|
-
__speechos_core.state.hide();
|
|
245
|
-
}
|
|
246
|
-
/**
|
|
247
|
-
* Identify the current user
|
|
248
|
-
* Can be called after init() to associate sessions with a user identifier.
|
|
249
|
-
* Clears the cached token so the next voice session uses the new userId.
|
|
250
|
-
*
|
|
251
|
-
* @param userId - User identifier from your system (e.g., user ID, email)
|
|
252
|
-
*
|
|
253
|
-
* @example
|
|
254
|
-
* // Initialize SDK early
|
|
255
|
-
* SpeechOS.init({ apiKey: 'xxx' });
|
|
256
|
-
*
|
|
257
|
-
* // Later, after user logs in
|
|
258
|
-
* SpeechOS.identify('user_123');
|
|
259
|
-
*/
|
|
260
|
-
static identify(userId) {
|
|
261
|
-
if (!this.isInitialized) {
|
|
262
|
-
console.warn("SpeechOS.identify() called before init(). Call init() first.");
|
|
263
|
-
return;
|
|
264
|
-
}
|
|
265
|
-
const config = (0, __speechos_core.getConfig)();
|
|
266
|
-
(0, __speechos_core.updateUserId)(userId);
|
|
267
|
-
__speechos_core.livekit.clearToken();
|
|
268
|
-
if (config.debug) console.log(`[SpeechOS] User identified: ${userId}`);
|
|
269
|
-
}
|
|
270
|
-
/**
|
|
271
|
-
* Private constructor to prevent direct instantiation
|
|
272
|
-
*/
|
|
273
|
-
constructor() {}
|
|
139
|
+
/******************************************************************************
|
|
140
|
+
Copyright (c) Microsoft Corporation.
|
|
141
|
+
|
|
142
|
+
Permission to use, copy, modify, and/or distribute this software for any
|
|
143
|
+
purpose with or without fee is hereby granted.
|
|
144
|
+
|
|
145
|
+
THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH
|
|
146
|
+
REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
|
|
147
|
+
AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT,
|
|
148
|
+
INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
|
|
149
|
+
LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR
|
|
150
|
+
OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
|
|
151
|
+
PERFORMANCE OF THIS SOFTWARE.
|
|
152
|
+
***************************************************************************** */
|
|
153
|
+
/* global Reflect, Promise, SuppressedError, Symbol, Iterator */
|
|
154
|
+
|
|
155
|
+
|
|
156
|
+
function __decorate(decorators, target, key, desc) {
|
|
157
|
+
var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
|
|
158
|
+
if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
|
|
159
|
+
else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
|
|
160
|
+
return c > 3 && r && Object.defineProperty(target, key, r), r;
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
typeof SuppressedError === "function" ? SuppressedError : function (error, suppressed, message) {
|
|
164
|
+
var e = new Error(message);
|
|
165
|
+
return e.name = "SuppressedError", e.error = error, e.suppressed = suppressed, e;
|
|
274
166
|
};
|
|
275
167
|
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
168
|
+
/**
|
|
169
|
+
* @license
|
|
170
|
+
* Copyright 2019 Google LLC
|
|
171
|
+
* SPDX-License-Identifier: BSD-3-Clause
|
|
172
|
+
*/
|
|
173
|
+
const t$3=globalThis,e$4=t$3.ShadowRoot&&(void 0===t$3.ShadyCSS||t$3.ShadyCSS.nativeShadow)&&"adoptedStyleSheets"in Document.prototype&&"replace"in CSSStyleSheet.prototype,s$2=Symbol(),o$5=new WeakMap;let n$3 = class n{constructor(t,e,o){if(this._$cssResult$=true,o!==s$2)throw Error("CSSResult is not constructable. Use `unsafeCSS` or `css` instead.");this.cssText=t,this.t=e;}get styleSheet(){let t=this.o;const s=this.t;if(e$4&&void 0===t){const e=void 0!==s&&1===s.length;e&&(t=o$5.get(s)),void 0===t&&((this.o=t=new CSSStyleSheet).replaceSync(this.cssText),e&&o$5.set(s,t));}return t}toString(){return this.cssText}};const r$4=t=>new n$3("string"==typeof t?t:t+"",void 0,s$2),i$4=(t,...e)=>{const o=1===t.length?t[0]:e.reduce((e,s,o)=>e+(t=>{if(true===t._$cssResult$)return t.cssText;if("number"==typeof t)return t;throw Error("Value passed to 'css' function must be a 'css' function result: "+t+". Use 'unsafeCSS' to pass non-literal values, but take care to ensure page security.")})(s)+t[o+1],t[0]);return new n$3(o,t,s$2)},S$1=(s,o)=>{if(e$4)s.adoptedStyleSheets=o.map(t=>t instanceof CSSStyleSheet?t:t.styleSheet);else for(const e of o){const o=document.createElement("style"),n=t$3.litNonce;void 0!==n&&o.setAttribute("nonce",n),o.textContent=e.cssText,s.appendChild(o);}},c$2=e$4?t=>t:t=>t instanceof CSSStyleSheet?(t=>{let e="";for(const s of t.cssRules)e+=s.cssText;return r$4(e)})(t):t;
|
|
174
|
+
|
|
175
|
+
/**
|
|
176
|
+
* @license
|
|
177
|
+
* Copyright 2017 Google LLC
|
|
178
|
+
* SPDX-License-Identifier: BSD-3-Clause
|
|
179
|
+
*/const{is:i$3,defineProperty:e$3,getOwnPropertyDescriptor:h$1,getOwnPropertyNames:r$3,getOwnPropertySymbols:o$4,getPrototypeOf:n$2}=Object,a$1=globalThis,c$1=a$1.trustedTypes,l$1=c$1?c$1.emptyScript:"",p$1=a$1.reactiveElementPolyfillSupport,d$1=(t,s)=>t,u$1={toAttribute(t,s){switch(s){case Boolean:t=t?l$1:null;break;case Object:case Array:t=null==t?t:JSON.stringify(t);}return t},fromAttribute(t,s){let i=t;switch(s){case Boolean:i=null!==t;break;case Number:i=null===t?null:Number(t);break;case Object:case Array:try{i=JSON.parse(t);}catch(t){i=null;}}return i}},f$1=(t,s)=>!i$3(t,s),b$1={attribute:true,type:String,converter:u$1,reflect:false,useDefault:false,hasChanged:f$1};Symbol.metadata??=Symbol("metadata"),a$1.litPropertyMetadata??=new WeakMap;let y$1 = class y extends HTMLElement{static addInitializer(t){this._$Ei(),(this.l??=[]).push(t);}static get observedAttributes(){return this.finalize(),this._$Eh&&[...this._$Eh.keys()]}static createProperty(t,s=b$1){if(s.state&&(s.attribute=false),this._$Ei(),this.prototype.hasOwnProperty(t)&&((s=Object.create(s)).wrapped=true),this.elementProperties.set(t,s),!s.noAccessor){const i=Symbol(),h=this.getPropertyDescriptor(t,i,s);void 0!==h&&e$3(this.prototype,t,h);}}static getPropertyDescriptor(t,s,i){const{get:e,set:r}=h$1(this.prototype,t)??{get(){return this[s]},set(t){this[s]=t;}};return {get:e,set(s){const h=e?.call(this);r?.call(this,s),this.requestUpdate(t,h,i);},configurable:true,enumerable:true}}static getPropertyOptions(t){return this.elementProperties.get(t)??b$1}static _$Ei(){if(this.hasOwnProperty(d$1("elementProperties")))return;const t=n$2(this);t.finalize(),void 0!==t.l&&(this.l=[...t.l]),this.elementProperties=new Map(t.elementProperties);}static finalize(){if(this.hasOwnProperty(d$1("finalized")))return;if(this.finalized=true,this._$Ei(),this.hasOwnProperty(d$1("properties"))){const t=this.properties,s=[...r$3(t),...o$4(t)];for(const i of s)this.createProperty(i,t[i]);}const t=this[Symbol.metadata];if(null!==t){const s=litPropertyMetadata.get(t);if(void 0!==s)for(const[t,i]of s)this.elementProperties.set(t,i);}this._$Eh=new Map;for(const[t,s]of this.elementProperties){const i=this._$Eu(t,s);void 0!==i&&this._$Eh.set(i,t);}this.elementStyles=this.finalizeStyles(this.styles);}static finalizeStyles(s){const i=[];if(Array.isArray(s)){const e=new Set(s.flat(1/0).reverse());for(const s of e)i.unshift(c$2(s));}else void 0!==s&&i.push(c$2(s));return i}static _$Eu(t,s){const i=s.attribute;return false===i?void 0:"string"==typeof i?i:"string"==typeof t?t.toLowerCase():void 0}constructor(){super(),this._$Ep=void 0,this.isUpdatePending=false,this.hasUpdated=false,this._$Em=null,this._$Ev();}_$Ev(){this._$ES=new Promise(t=>this.enableUpdating=t),this._$AL=new Map,this._$E_(),this.requestUpdate(),this.constructor.l?.forEach(t=>t(this));}addController(t){(this._$EO??=new Set).add(t),void 0!==this.renderRoot&&this.isConnected&&t.hostConnected?.();}removeController(t){this._$EO?.delete(t);}_$E_(){const t=new Map,s=this.constructor.elementProperties;for(const i of s.keys())this.hasOwnProperty(i)&&(t.set(i,this[i]),delete this[i]);t.size>0&&(this._$Ep=t);}createRenderRoot(){const t=this.shadowRoot??this.attachShadow(this.constructor.shadowRootOptions);return S$1(t,this.constructor.elementStyles),t}connectedCallback(){this.renderRoot??=this.createRenderRoot(),this.enableUpdating(true),this._$EO?.forEach(t=>t.hostConnected?.());}enableUpdating(t){}disconnectedCallback(){this._$EO?.forEach(t=>t.hostDisconnected?.());}attributeChangedCallback(t,s,i){this._$AK(t,i);}_$ET(t,s){const i=this.constructor.elementProperties.get(t),e=this.constructor._$Eu(t,i);if(void 0!==e&&true===i.reflect){const h=(void 0!==i.converter?.toAttribute?i.converter:u$1).toAttribute(s,i.type);this._$Em=t,null==h?this.removeAttribute(e):this.setAttribute(e,h),this._$Em=null;}}_$AK(t,s){const i=this.constructor,e=i._$Eh.get(t);if(void 0!==e&&this._$Em!==e){const t=i.getPropertyOptions(e),h="function"==typeof t.converter?{fromAttribute:t.converter}:void 0!==t.converter?.fromAttribute?t.converter:u$1;this._$Em=e;const r=h.fromAttribute(s,t.type);this[e]=r??this._$Ej?.get(e)??r,this._$Em=null;}}requestUpdate(t,s,i,e=false,h){if(void 0!==t){const r=this.constructor;if(false===e&&(h=this[t]),i??=r.getPropertyOptions(t),!((i.hasChanged??f$1)(h,s)||i.useDefault&&i.reflect&&h===this._$Ej?.get(t)&&!this.hasAttribute(r._$Eu(t,i))))return;this.C(t,s,i);} false===this.isUpdatePending&&(this._$ES=this._$EP());}C(t,s,{useDefault:i,reflect:e,wrapped:h},r){i&&!(this._$Ej??=new Map).has(t)&&(this._$Ej.set(t,r??s??this[t]),true!==h||void 0!==r)||(this._$AL.has(t)||(this.hasUpdated||i||(s=void 0),this._$AL.set(t,s)),true===e&&this._$Em!==t&&(this._$Eq??=new Set).add(t));}async _$EP(){this.isUpdatePending=true;try{await this._$ES;}catch(t){Promise.reject(t);}const t=this.scheduleUpdate();return null!=t&&await t,!this.isUpdatePending}scheduleUpdate(){return this.performUpdate()}performUpdate(){if(!this.isUpdatePending)return;if(!this.hasUpdated){if(this.renderRoot??=this.createRenderRoot(),this._$Ep){for(const[t,s]of this._$Ep)this[t]=s;this._$Ep=void 0;}const t=this.constructor.elementProperties;if(t.size>0)for(const[s,i]of t){const{wrapped:t}=i,e=this[s];true!==t||this._$AL.has(s)||void 0===e||this.C(s,void 0,i,e);}}let t=false;const s=this._$AL;try{t=this.shouldUpdate(s),t?(this.willUpdate(s),this._$EO?.forEach(t=>t.hostUpdate?.()),this.update(s)):this._$EM();}catch(s){throw t=false,this._$EM(),s}t&&this._$AE(s);}willUpdate(t){}_$AE(t){this._$EO?.forEach(t=>t.hostUpdated?.()),this.hasUpdated||(this.hasUpdated=true,this.firstUpdated(t)),this.updated(t);}_$EM(){this._$AL=new Map,this.isUpdatePending=false;}get updateComplete(){return this.getUpdateComplete()}getUpdateComplete(){return this._$ES}shouldUpdate(t){return true}update(t){this._$Eq&&=this._$Eq.forEach(t=>this._$ET(t,this[t])),this._$EM();}updated(t){}firstUpdated(t){}};y$1.elementStyles=[],y$1.shadowRootOptions={mode:"open"},y$1[d$1("elementProperties")]=new Map,y$1[d$1("finalized")]=new Map,p$1?.({ReactiveElement:y$1}),(a$1.reactiveElementVersions??=[]).push("2.1.2");
|
|
180
|
+
|
|
181
|
+
/**
|
|
182
|
+
* @license
|
|
183
|
+
* Copyright 2017 Google LLC
|
|
184
|
+
* SPDX-License-Identifier: BSD-3-Clause
|
|
185
|
+
*/
|
|
186
|
+
const t$2=globalThis,i$2=t=>t,s$1=t$2.trustedTypes,e$2=s$1?s$1.createPolicy("lit-html",{createHTML:t=>t}):void 0,h="$lit$",o$3=`lit$${Math.random().toFixed(9).slice(2)}$`,n$1="?"+o$3,r$2=`<${n$1}>`,l=document,c=()=>l.createComment(""),a=t=>null===t||"object"!=typeof t&&"function"!=typeof t,u=Array.isArray,d=t=>u(t)||"function"==typeof t?.[Symbol.iterator],f="[ \t\n\f\r]",v=/<(?:(!--|\/[^a-zA-Z])|(\/?[a-zA-Z][^>\s]*)|(\/?$))/g,_=/-->/g,m=/>/g,p=RegExp(`>|${f}(?:([^\\s"'>=/]+)(${f}*=${f}*(?:[^ \t\n\f\r"'\`<>=]|("|')|))|$)`,"g"),g=/'/g,$=/"/g,y=/^(?:script|style|textarea|title)$/i,x=t=>(i,...s)=>({_$litType$:t,strings:i,values:s}),b=x(1),E=Symbol.for("lit-noChange"),A=Symbol.for("lit-nothing"),C=new WeakMap,P=l.createTreeWalker(l,129);function V(t,i){if(!u(t)||!t.hasOwnProperty("raw"))throw Error("invalid template strings array");return void 0!==e$2?e$2.createHTML(i):i}const N=(t,i)=>{const s=t.length-1,e=[];let n,l=2===i?"<svg>":3===i?"<math>":"",c=v;for(let i=0;i<s;i++){const s=t[i];let a,u,d=-1,f=0;for(;f<s.length&&(c.lastIndex=f,u=c.exec(s),null!==u);)f=c.lastIndex,c===v?"!--"===u[1]?c=_:void 0!==u[1]?c=m:void 0!==u[2]?(y.test(u[2])&&(n=RegExp("</"+u[2],"g")),c=p):void 0!==u[3]&&(c=p):c===p?">"===u[0]?(c=n??v,d=-1):void 0===u[1]?d=-2:(d=c.lastIndex-u[2].length,a=u[1],c=void 0===u[3]?p:'"'===u[3]?$:g):c===$||c===g?c=p:c===_||c===m?c=v:(c=p,n=void 0);const x=c===p&&t[i+1].startsWith("/>")?" ":"";l+=c===v?s+r$2:d>=0?(e.push(a),s.slice(0,d)+h+s.slice(d)+o$3+x):s+o$3+(-2===d?i:x);}return [V(t,l+(t[s]||"<?>")+(2===i?"</svg>":3===i?"</math>":"")),e]};class S{constructor({strings:t,_$litType$:i},e){let r;this.parts=[];let l=0,a=0;const u=t.length-1,d=this.parts,[f,v]=N(t,i);if(this.el=S.createElement(f,e),P.currentNode=this.el.content,2===i||3===i){const t=this.el.content.firstChild;t.replaceWith(...t.childNodes);}for(;null!==(r=P.nextNode())&&d.length<u;){if(1===r.nodeType){if(r.hasAttributes())for(const t of r.getAttributeNames())if(t.endsWith(h)){const i=v[a++],s=r.getAttribute(t).split(o$3),e=/([.?@])?(.*)/.exec(i);d.push({type:1,index:l,name:e[2],strings:s,ctor:"."===e[1]?I:"?"===e[1]?L:"@"===e[1]?z:H}),r.removeAttribute(t);}else t.startsWith(o$3)&&(d.push({type:6,index:l}),r.removeAttribute(t));if(y.test(r.tagName)){const t=r.textContent.split(o$3),i=t.length-1;if(i>0){r.textContent=s$1?s$1.emptyScript:"";for(let s=0;s<i;s++)r.append(t[s],c()),P.nextNode(),d.push({type:2,index:++l});r.append(t[i],c());}}}else if(8===r.nodeType)if(r.data===n$1)d.push({type:2,index:l});else {let t=-1;for(;-1!==(t=r.data.indexOf(o$3,t+1));)d.push({type:7,index:l}),t+=o$3.length-1;}l++;}}static createElement(t,i){const s=l.createElement("template");return s.innerHTML=t,s}}function M(t,i,s=t,e){if(i===E)return i;let h=void 0!==e?s._$Co?.[e]:s._$Cl;const o=a(i)?void 0:i._$litDirective$;return h?.constructor!==o&&(h?._$AO?.(false),void 0===o?h=void 0:(h=new o(t),h._$AT(t,s,e)),void 0!==e?(s._$Co??=[])[e]=h:s._$Cl=h),void 0!==h&&(i=M(t,h._$AS(t,i.values),h,e)),i}class R{constructor(t,i){this._$AV=[],this._$AN=void 0,this._$AD=t,this._$AM=i;}get parentNode(){return this._$AM.parentNode}get _$AU(){return this._$AM._$AU}u(t){const{el:{content:i},parts:s}=this._$AD,e=(t?.creationScope??l).importNode(i,true);P.currentNode=e;let h=P.nextNode(),o=0,n=0,r=s[0];for(;void 0!==r;){if(o===r.index){let i;2===r.type?i=new k(h,h.nextSibling,this,t):1===r.type?i=new r.ctor(h,r.name,r.strings,this,t):6===r.type&&(i=new Z(h,this,t)),this._$AV.push(i),r=s[++n];}o!==r?.index&&(h=P.nextNode(),o++);}return P.currentNode=l,e}p(t){let i=0;for(const s of this._$AV) void 0!==s&&(void 0!==s.strings?(s._$AI(t,s,i),i+=s.strings.length-2):s._$AI(t[i])),i++;}}class k{get _$AU(){return this._$AM?._$AU??this._$Cv}constructor(t,i,s,e){this.type=2,this._$AH=A,this._$AN=void 0,this._$AA=t,this._$AB=i,this._$AM=s,this.options=e,this._$Cv=e?.isConnected??true;}get parentNode(){let t=this._$AA.parentNode;const i=this._$AM;return void 0!==i&&11===t?.nodeType&&(t=i.parentNode),t}get startNode(){return this._$AA}get endNode(){return this._$AB}_$AI(t,i=this){t=M(this,t,i),a(t)?t===A||null==t||""===t?(this._$AH!==A&&this._$AR(),this._$AH=A):t!==this._$AH&&t!==E&&this._(t):void 0!==t._$litType$?this.$(t):void 0!==t.nodeType?this.T(t):d(t)?this.k(t):this._(t);}O(t){return this._$AA.parentNode.insertBefore(t,this._$AB)}T(t){this._$AH!==t&&(this._$AR(),this._$AH=this.O(t));}_(t){this._$AH!==A&&a(this._$AH)?this._$AA.nextSibling.data=t:this.T(l.createTextNode(t)),this._$AH=t;}$(t){const{values:i,_$litType$:s}=t,e="number"==typeof s?this._$AC(t):(void 0===s.el&&(s.el=S.createElement(V(s.h,s.h[0]),this.options)),s);if(this._$AH?._$AD===e)this._$AH.p(i);else {const t=new R(e,this),s=t.u(this.options);t.p(i),this.T(s),this._$AH=t;}}_$AC(t){let i=C.get(t.strings);return void 0===i&&C.set(t.strings,i=new S(t)),i}k(t){u(this._$AH)||(this._$AH=[],this._$AR());const i=this._$AH;let s,e=0;for(const h of t)e===i.length?i.push(s=new k(this.O(c()),this.O(c()),this,this.options)):s=i[e],s._$AI(h),e++;e<i.length&&(this._$AR(s&&s._$AB.nextSibling,e),i.length=e);}_$AR(t=this._$AA.nextSibling,s){for(this._$AP?.(false,true,s);t!==this._$AB;){const s=i$2(t).nextSibling;i$2(t).remove(),t=s;}}setConnected(t){ void 0===this._$AM&&(this._$Cv=t,this._$AP?.(t));}}class H{get tagName(){return this.element.tagName}get _$AU(){return this._$AM._$AU}constructor(t,i,s,e,h){this.type=1,this._$AH=A,this._$AN=void 0,this.element=t,this.name=i,this._$AM=e,this.options=h,s.length>2||""!==s[0]||""!==s[1]?(this._$AH=Array(s.length-1).fill(new String),this.strings=s):this._$AH=A;}_$AI(t,i=this,s,e){const h=this.strings;let o=false;if(void 0===h)t=M(this,t,i,0),o=!a(t)||t!==this._$AH&&t!==E,o&&(this._$AH=t);else {const e=t;let n,r;for(t=h[0],n=0;n<h.length-1;n++)r=M(this,e[s+n],i,n),r===E&&(r=this._$AH[n]),o||=!a(r)||r!==this._$AH[n],r===A?t=A:t!==A&&(t+=(r??"")+h[n+1]),this._$AH[n]=r;}o&&!e&&this.j(t);}j(t){t===A?this.element.removeAttribute(this.name):this.element.setAttribute(this.name,t??"");}}class I extends H{constructor(){super(...arguments),this.type=3;}j(t){this.element[this.name]=t===A?void 0:t;}}class L extends H{constructor(){super(...arguments),this.type=4;}j(t){this.element.toggleAttribute(this.name,!!t&&t!==A);}}class z extends H{constructor(t,i,s,e,h){super(t,i,s,e,h),this.type=5;}_$AI(t,i=this){if((t=M(this,t,i,0)??A)===E)return;const s=this._$AH,e=t===A&&s!==A||t.capture!==s.capture||t.once!==s.once||t.passive!==s.passive,h=t!==A&&(s===A||e);e&&this.element.removeEventListener(this.name,this,s),h&&this.element.addEventListener(this.name,this,t),this._$AH=t;}handleEvent(t){"function"==typeof this._$AH?this._$AH.call(this.options?.host??this.element,t):this._$AH.handleEvent(t);}}class Z{constructor(t,i,s){this.element=t,this.type=6,this._$AN=void 0,this._$AM=i,this.options=s;}get _$AU(){return this._$AM._$AU}_$AI(t){M(this,t);}}const B=t$2.litHtmlPolyfillSupport;B?.(S,k),(t$2.litHtmlVersions??=[]).push("3.3.2");const D=(t,i,s)=>{const e=s?.renderBefore??i;let h=e._$litPart$;if(void 0===h){const t=s?.renderBefore??null;e._$litPart$=h=new k(i.insertBefore(c(),t),t,void 0,s??{});}return h._$AI(t),h};
|
|
187
|
+
|
|
188
|
+
/**
|
|
189
|
+
* @license
|
|
190
|
+
* Copyright 2017 Google LLC
|
|
191
|
+
* SPDX-License-Identifier: BSD-3-Clause
|
|
192
|
+
*/const s=globalThis;let i$1 = class i extends y$1{constructor(){super(...arguments),this.renderOptions={host:this},this._$Do=void 0;}createRenderRoot(){const t=super.createRenderRoot();return this.renderOptions.renderBefore??=t.firstChild,t}update(t){const r=this.render();this.hasUpdated||(this.renderOptions.isConnected=this.isConnected),super.update(t),this._$Do=D(r,this.renderRoot,this.renderOptions);}connectedCallback(){super.connectedCallback(),this._$Do?.setConnected(true);}disconnectedCallback(){super.disconnectedCallback(),this._$Do?.setConnected(false);}render(){return E}};i$1._$litElement$=true,i$1["finalized"]=true,s.litElementHydrateSupport?.({LitElement:i$1});const o$2=s.litElementPolyfillSupport;o$2?.({LitElement:i$1});(s.litElementVersions??=[]).push("4.2.2");
|
|
193
|
+
|
|
194
|
+
/**
|
|
195
|
+
* @license
|
|
196
|
+
* Copyright 2017 Google LLC
|
|
197
|
+
* SPDX-License-Identifier: BSD-3-Clause
|
|
198
|
+
*/
|
|
199
|
+
const t$1=t=>(e,o)=>{ void 0!==o?o.addInitializer(()=>{customElements.define(t,e);}):customElements.define(t,e);};
|
|
280
200
|
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
201
|
+
/**
|
|
202
|
+
* @license
|
|
203
|
+
* Copyright 2017 Google LLC
|
|
204
|
+
* SPDX-License-Identifier: BSD-3-Clause
|
|
205
|
+
*/const o$1={attribute:true,type:String,converter:u$1,reflect:false,hasChanged:f$1},r$1=(t=o$1,e,r)=>{const{kind:n,metadata:i}=r;let s=globalThis.litPropertyMetadata.get(i);if(void 0===s&&globalThis.litPropertyMetadata.set(i,s=new Map),"setter"===n&&((t=Object.create(t)).wrapped=true),s.set(r.name,t),"accessor"===n){const{name:o}=r;return {set(r){const n=e.get.call(this);e.set.call(this,r),this.requestUpdate(o,n,t,true,r);},init(e){return void 0!==e&&this.C(o,void 0,t,e),e}}}if("setter"===n){const{name:o}=r;return function(r){const n=this[o];e.call(this,r),this.requestUpdate(o,n,t,true,r);}}throw Error("Unsupported decorator location: "+n)};function n(t){return (e,o)=>"object"==typeof o?r$1(t,e,o):((t,e,o)=>{const r=e.hasOwnProperty(o);return e.constructor.createProperty(o,t),r?Object.getOwnPropertyDescriptor(e,o):void 0})(t,e,o)}
|
|
206
|
+
|
|
207
|
+
/**
|
|
208
|
+
* @license
|
|
209
|
+
* Copyright 2017 Google LLC
|
|
210
|
+
* SPDX-License-Identifier: BSD-3-Clause
|
|
211
|
+
*/function r(r){return n({...r,state:true,attribute:false})}
|
|
212
|
+
|
|
213
|
+
/**
|
|
214
|
+
* Shared styles and CSS variables for SpeechOS UI components
|
|
215
|
+
*/
|
|
216
|
+
/**
|
|
217
|
+
* CSS variables and theme tokens
|
|
218
|
+
* These can be customized by the host application
|
|
219
|
+
*/
|
|
220
|
+
const themeStyles = i$4 `
|
|
221
|
+
:host {
|
|
222
|
+
/* Color tokens */
|
|
223
|
+
--speechos-primary: #10B981;
|
|
224
|
+
--speechos-primary-hover: #059669;
|
|
225
|
+
--speechos-primary-active: #047857;
|
|
226
|
+
|
|
227
|
+
--speechos-bg: #ffffff;
|
|
228
|
+
--speechos-bg-hover: #f9fafb;
|
|
229
|
+
--speechos-surface: #f3f4f6;
|
|
230
|
+
|
|
231
|
+
--speechos-text: #111827;
|
|
232
|
+
--speechos-text-secondary: #6b7280;
|
|
233
|
+
|
|
234
|
+
--speechos-border: #e5e7eb;
|
|
235
|
+
--speechos-shadow: rgba(0, 0, 0, 0.1);
|
|
236
|
+
--speechos-shadow-lg: rgba(0, 0, 0, 0.15);
|
|
237
|
+
|
|
238
|
+
/* Spacing */
|
|
239
|
+
--speechos-spacing-xs: 4px;
|
|
240
|
+
--speechos-spacing-sm: 8px;
|
|
241
|
+
--speechos-spacing-md: 12px;
|
|
242
|
+
--speechos-spacing-lg: 16px;
|
|
243
|
+
--speechos-spacing-xl: 24px;
|
|
244
|
+
|
|
245
|
+
/* Border radius */
|
|
246
|
+
--speechos-radius-sm: 6px;
|
|
247
|
+
--speechos-radius-md: 8px;
|
|
248
|
+
--speechos-radius-lg: 12px;
|
|
249
|
+
--speechos-radius-full: 9999px;
|
|
250
|
+
|
|
251
|
+
/* Transitions */
|
|
252
|
+
--speechos-transition-fast: 150ms ease;
|
|
253
|
+
--speechos-transition-base: 200ms ease;
|
|
254
|
+
--speechos-transition-slow: 300ms ease;
|
|
255
|
+
|
|
256
|
+
/* Z-index */
|
|
257
|
+
--speechos-z-base: 999999;
|
|
258
|
+
--speechos-z-popup: 1000000;
|
|
286
259
|
}
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
260
|
+
`;
|
|
261
|
+
/**
|
|
262
|
+
* Common animation keyframes
|
|
263
|
+
*/
|
|
264
|
+
const animations = i$4 `
|
|
265
|
+
@keyframes speechos-fadeIn {
|
|
266
|
+
from {
|
|
267
|
+
opacity: 0;
|
|
268
|
+
transform: translateY(8px);
|
|
269
|
+
}
|
|
270
|
+
to {
|
|
271
|
+
opacity: 1;
|
|
272
|
+
transform: translateY(0);
|
|
273
|
+
}
|
|
296
274
|
}
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
275
|
+
|
|
276
|
+
@keyframes speechos-fadeOut {
|
|
277
|
+
from {
|
|
278
|
+
opacity: 1;
|
|
279
|
+
transform: translateY(0);
|
|
280
|
+
}
|
|
281
|
+
to {
|
|
282
|
+
opacity: 0;
|
|
283
|
+
transform: translateY(8px);
|
|
284
|
+
}
|
|
303
285
|
}
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
286
|
+
|
|
287
|
+
@keyframes speechos-pulse {
|
|
288
|
+
0%, 100% {
|
|
289
|
+
opacity: 1;
|
|
290
|
+
transform: scale(1);
|
|
291
|
+
}
|
|
292
|
+
50% {
|
|
293
|
+
opacity: 0.8;
|
|
294
|
+
transform: scale(0.95);
|
|
295
|
+
}
|
|
309
296
|
}
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
297
|
+
|
|
298
|
+
@keyframes speechos-slideUp {
|
|
299
|
+
from {
|
|
300
|
+
opacity: 0;
|
|
301
|
+
transform: translateY(12px) scale(0.95);
|
|
302
|
+
}
|
|
303
|
+
to {
|
|
304
|
+
opacity: 1;
|
|
305
|
+
transform: translateY(0) scale(1);
|
|
306
|
+
}
|
|
315
307
|
}
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
308
|
+
|
|
309
|
+
@keyframes speechos-scaleIn {
|
|
310
|
+
from {
|
|
311
|
+
opacity: 0;
|
|
312
|
+
transform: scale(0.9);
|
|
313
|
+
}
|
|
314
|
+
to {
|
|
315
|
+
opacity: 1;
|
|
316
|
+
transform: scale(1);
|
|
317
|
+
}
|
|
321
318
|
}
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
319
|
+
`;
|
|
320
|
+
/**
|
|
321
|
+
* Utility styles for common patterns
|
|
322
|
+
*/
|
|
323
|
+
i$4 `
|
|
324
|
+
.speechos-hidden {
|
|
325
|
+
display: none !important;
|
|
327
326
|
}
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
327
|
+
|
|
328
|
+
.speechos-visible {
|
|
329
|
+
display: block;
|
|
330
|
+
}
|
|
331
|
+
|
|
332
|
+
.speechos-fade-in {
|
|
333
|
+
animation: speechos-fadeIn var(--speechos-transition-base) forwards;
|
|
334
|
+
}
|
|
335
|
+
|
|
336
|
+
.speechos-fade-out {
|
|
337
|
+
animation: speechos-fadeOut var(--speechos-transition-base) forwards;
|
|
338
|
+
}
|
|
339
|
+
|
|
340
|
+
.speechos-pulse {
|
|
341
|
+
animation: speechos-pulse 2s ease-in-out infinite;
|
|
333
342
|
}
|
|
343
|
+
`;
|
|
344
|
+
|
|
345
|
+
/**
|
|
346
|
+
* @license
|
|
347
|
+
* Copyright 2017 Google LLC
|
|
348
|
+
* SPDX-License-Identifier: BSD-3-Clause
|
|
349
|
+
*/
|
|
350
|
+
const t={CHILD:2},e$1=t=>(...e)=>({_$litDirective$:t,values:e});class i{constructor(t){}get _$AU(){return this._$AM._$AU}_$AT(t,e,i){this._$Ct=t,this._$AM=e,this._$Ci=i;}_$AS(t,e){return this.update(t,e)}update(t,e){return this.render(...e)}}
|
|
351
|
+
|
|
352
|
+
/**
|
|
353
|
+
* @license
|
|
354
|
+
* Copyright 2017 Google LLC
|
|
355
|
+
* SPDX-License-Identifier: BSD-3-Clause
|
|
356
|
+
*/class e extends i{constructor(i){if(super(i),this.it=A,i.type!==t.CHILD)throw Error(this.constructor.directiveName+"() can only be used in child bindings")}render(r){if(r===A||null==r)return this._t=void 0,this.it=r;if(r===E)return r;if("string"!=typeof r)throw Error(this.constructor.directiveName+"() called with a non-string value");if(r===this.it)return this._t;this.it=r;const s=[r];return s.raw=s,this._t={_$litType$:this.constructor.resultType,strings:s,values:[]}}}e.directiveName="unsafeHTML",e.resultType=1;const o=e$1(e);
|
|
357
|
+
|
|
358
|
+
/**
|
|
359
|
+
* Icon imports and templates using inline SVG
|
|
360
|
+
*/
|
|
361
|
+
/**
|
|
362
|
+
* Helper to create an SVG icon template
|
|
363
|
+
*/
|
|
364
|
+
function createIcon(paths, size = 20) {
|
|
365
|
+
const svgString = `
|
|
366
|
+
<svg
|
|
367
|
+
xmlns="http://www.w3.org/2000/svg"
|
|
368
|
+
width="${size}"
|
|
369
|
+
height="${size}"
|
|
370
|
+
viewBox="0 0 24 24"
|
|
371
|
+
fill="none"
|
|
372
|
+
stroke="currentColor"
|
|
373
|
+
stroke-width="2"
|
|
374
|
+
stroke-linecap="round"
|
|
375
|
+
stroke-linejoin="round"
|
|
376
|
+
style="display: block;"
|
|
377
|
+
>
|
|
378
|
+
${paths}
|
|
379
|
+
</svg>
|
|
380
|
+
`;
|
|
381
|
+
return b `${o(svgString)}`;
|
|
382
|
+
}
|
|
383
|
+
/**
|
|
384
|
+
* Microphone icon for the main button
|
|
385
|
+
* Lucide Mic icon paths
|
|
386
|
+
*/
|
|
387
|
+
const micIcon = (size = 20) => createIcon('<path d="M12 2a3 3 0 0 0-3 3v7a3 3 0 0 0 6 0V5a3 3 0 0 0-3-3Z"/><path d="M19 10v2a7 7 0 0 1-14 0v-2"/><line x1="12" x2="12" y1="19" y2="22"/>', size);
|
|
388
|
+
/**
|
|
389
|
+
* Message square icon for dictate action
|
|
390
|
+
* Lucide MessageSquare icon paths
|
|
391
|
+
*/
|
|
392
|
+
const dictateIcon = (size = 18) => createIcon('<path d="M21 15a2 2 0 0 1-2 2H7l-4 4V5a2 2 0 0 1 2-2h14a2 2 0 0 1 2 2z"/>', size);
|
|
393
|
+
/**
|
|
394
|
+
* Pencil icon for edit action
|
|
395
|
+
* Lucide Pencil icon paths
|
|
396
|
+
*/
|
|
397
|
+
const editIcon = (size = 18) => createIcon('<path d="M21.174 6.812a1 1 0 0 0-3.986-3.987L3.842 16.174a2 2 0 0 0-.5.83l-1.321 4.352a.5.5 0 0 0 .623.622l4.353-1.32a2 2 0 0 0 .83-.497z"/><path d="m15 5 4 4"/>', size);
|
|
398
|
+
/**
|
|
399
|
+
* Stop/Square icon for stopping recording
|
|
400
|
+
* Lucide Square icon (filled)
|
|
401
|
+
*/
|
|
402
|
+
const stopIcon = (size = 18) => {
|
|
403
|
+
const svgString = `
|
|
404
|
+
<svg
|
|
405
|
+
xmlns="http://www.w3.org/2000/svg"
|
|
406
|
+
width="${size}"
|
|
407
|
+
height="${size}"
|
|
408
|
+
viewBox="0 0 24 24"
|
|
409
|
+
fill="currentColor"
|
|
410
|
+
stroke="none"
|
|
411
|
+
style="display: block;"
|
|
412
|
+
>
|
|
413
|
+
<rect x="6" y="6" width="12" height="12" rx="2" />
|
|
414
|
+
</svg>
|
|
415
|
+
`;
|
|
416
|
+
return b `${o(svgString)}`;
|
|
417
|
+
};
|
|
418
|
+
/**
|
|
419
|
+
* Loader/spinner icon for connecting state
|
|
420
|
+
*/
|
|
421
|
+
const loaderIcon = (size = 20) => {
|
|
422
|
+
const svgString = `
|
|
423
|
+
<svg
|
|
424
|
+
xmlns="http://www.w3.org/2000/svg"
|
|
425
|
+
width="${size}"
|
|
426
|
+
height="${size}"
|
|
427
|
+
viewBox="0 0 24 24"
|
|
428
|
+
fill="none"
|
|
429
|
+
stroke="currentColor"
|
|
430
|
+
stroke-width="2"
|
|
431
|
+
stroke-linecap="round"
|
|
432
|
+
stroke-linejoin="round"
|
|
433
|
+
style="display: block; animation: spin 1s linear infinite;"
|
|
434
|
+
>
|
|
435
|
+
<path d="M21 12a9 9 0 1 1-6.219-8.56"/>
|
|
436
|
+
</svg>
|
|
437
|
+
`;
|
|
438
|
+
return b `${o(svgString)}`;
|
|
439
|
+
};
|
|
440
|
+
/**
|
|
441
|
+
* X icon for cancel action
|
|
442
|
+
* Lucide X icon paths
|
|
443
|
+
*/
|
|
444
|
+
const xIcon = (size = 14) => createIcon('<path d="M18 6 6 18"/><path d="m6 6 12 12"/>', size);
|
|
445
|
+
/**
|
|
446
|
+
* Clipboard/list icon for transcripts tab
|
|
447
|
+
* Lucide ClipboardList icon paths
|
|
448
|
+
*/
|
|
449
|
+
const clipboardIcon = (size = 16) => createIcon('<rect width="8" height="4" x="8" y="2" rx="1" ry="1"/><path d="M16 4h2a2 2 0 0 1 2 2v14a2 2 0 0 1-2 2H6a2 2 0 0 1-2-2V6a2 2 0 0 1 2-2h2"/><path d="M12 11h4"/><path d="M12 16h4"/><path d="M8 11h.01"/><path d="M8 16h.01"/>', size);
|
|
450
|
+
/**
|
|
451
|
+
* Help circle icon for help tab
|
|
452
|
+
* Lucide HelpCircle icon paths
|
|
453
|
+
*/
|
|
454
|
+
const helpCircleIcon = (size = 16) => createIcon('<circle cx="12" cy="12" r="10"/><path d="M9.09 9a3 3 0 0 1 5.83 1c0 2-3 3-3 3"/><path d="M12 17h.01"/>', size);
|
|
455
|
+
/**
|
|
456
|
+
* Info icon for about tab
|
|
457
|
+
* Lucide Info icon paths
|
|
458
|
+
*/
|
|
459
|
+
const infoIcon = (size = 16) => createIcon('<circle cx="12" cy="12" r="10"/><path d="M12 16v-4"/><path d="M12 8h.01"/>', size);
|
|
460
|
+
/**
|
|
461
|
+
* Settings/cog icon for settings tab
|
|
462
|
+
* Lucide Settings icon paths
|
|
463
|
+
*/
|
|
464
|
+
const settingsIcon = (size = 16) => createIcon('<path d="M12.22 2h-.44a2 2 0 0 0-2 2v.18a2 2 0 0 1-1 1.73l-.43.25a2 2 0 0 1-2 0l-.15-.08a2 2 0 0 0-2.73.73l-.22.38a2 2 0 0 0 .73 2.73l.15.1a2 2 0 0 1 1 1.72v.51a2 2 0 0 1-1 1.74l-.15.09a2 2 0 0 0-.73 2.73l.22.38a2 2 0 0 0 2.73.73l.15-.08a2 2 0 0 1 2 0l.43.25a2 2 0 0 1 1 1.73V20a2 2 0 0 0 2 2h.44a2 2 0 0 0 2-2v-.18a2 2 0 0 1 1-1.73l.43-.25a2 2 0 0 1 2 0l.15.08a2 2 0 0 0 2.73-.73l.22-.39a2 2 0 0 0-.73-2.73l-.15-.08a2 2 0 0 1-1-1.74v-.5a2 2 0 0 1 1-1.74l.15-.09a2 2 0 0 0 .73-2.73l-.22-.38a2 2 0 0 0-2.73-.73l-.15.08a2 2 0 0 1-2 0l-.43-.25a2 2 0 0 1-1-1.73V4a2 2 0 0 0-2-2z"/><circle cx="12" cy="12" r="3"/>', size);
|
|
465
|
+
/**
|
|
466
|
+
* Trash icon for delete action
|
|
467
|
+
* Lucide Trash2 icon paths
|
|
468
|
+
*/
|
|
469
|
+
const trashIcon = (size = 14) => createIcon('<path d="M3 6h18"/><path d="M19 6v14c0 1-1 2-2 2H7c-1 0-2-1-2-2V6"/><path d="M8 6V4c0-1 1-2 2-2h4c1 0 2 1 2 2v2"/><line x1="10" x2="10" y1="11" y2="17"/><line x1="14" x2="14" y1="11" y2="17"/>', size);
|
|
470
|
+
/**
|
|
471
|
+
* External link icon
|
|
472
|
+
* Lucide ExternalLink icon paths
|
|
473
|
+
*/
|
|
474
|
+
const externalLinkIcon = (size = 14) => createIcon('<path d="M15 3h6v6"/><path d="M10 14 21 3"/><path d="M18 13v6a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2V8a2 2 0 0 1 2-2h6"/>', size);
|
|
475
|
+
/**
|
|
476
|
+
* Copy icon
|
|
477
|
+
* Lucide Copy icon paths
|
|
478
|
+
*/
|
|
479
|
+
const copyIcon = (size = 14) => createIcon('<rect width="14" height="14" x="8" y="8" rx="2" ry="2"/><path d="M4 16c-1.1 0-2-.9-2-2V4c0-1.1.9-2 2-2h10c1.1 0 2 .9 2 2"/>', size);
|
|
480
|
+
|
|
481
|
+
/**
|
|
482
|
+
* Microphone button component
|
|
483
|
+
* A circular button that toggles the action bubbles and handles recording states
|
|
484
|
+
*/
|
|
485
|
+
let SpeechOSMicButton = class SpeechOSMicButton extends i$1 {
|
|
486
|
+
constructor() {
|
|
487
|
+
super(...arguments);
|
|
488
|
+
this.expanded = false;
|
|
489
|
+
this.recordingState = "idle";
|
|
490
|
+
this.activeAction = null;
|
|
491
|
+
this.editPreviewText = "";
|
|
492
|
+
this.errorMessage = null;
|
|
493
|
+
}
|
|
494
|
+
static { this.styles = [
|
|
495
|
+
themeStyles,
|
|
496
|
+
animations,
|
|
497
|
+
i$4 `
|
|
498
|
+
:host {
|
|
499
|
+
display: inline-block;
|
|
500
|
+
}
|
|
501
|
+
|
|
502
|
+
.button-wrapper {
|
|
503
|
+
position: relative;
|
|
504
|
+
}
|
|
505
|
+
|
|
506
|
+
.mic-button {
|
|
507
|
+
width: 56px;
|
|
508
|
+
height: 56px;
|
|
509
|
+
border-radius: var(--speechos-radius-full);
|
|
510
|
+
background: linear-gradient(135deg, #10b981 0%, #059669 100%);
|
|
511
|
+
border: none;
|
|
512
|
+
cursor: pointer;
|
|
513
|
+
display: flex;
|
|
514
|
+
align-items: center;
|
|
515
|
+
justify-content: center;
|
|
516
|
+
color: white;
|
|
517
|
+
box-shadow: 0 4px 12px rgba(16, 185, 129, 0.4);
|
|
518
|
+
transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1);
|
|
519
|
+
position: relative;
|
|
520
|
+
overflow: hidden;
|
|
521
|
+
}
|
|
522
|
+
|
|
523
|
+
.mic-button:hover {
|
|
524
|
+
transform: scale(1.05);
|
|
525
|
+
box-shadow: 0 6px 20px rgba(16, 185, 129, 0.5);
|
|
526
|
+
}
|
|
527
|
+
|
|
528
|
+
.mic-button:active {
|
|
529
|
+
transform: scale(0.98);
|
|
530
|
+
}
|
|
531
|
+
|
|
532
|
+
/* Expanded state - bubbles visible */
|
|
533
|
+
.mic-button.expanded {
|
|
534
|
+
background: linear-gradient(135deg, #059669 0%, #047857 100%);
|
|
535
|
+
}
|
|
536
|
+
|
|
537
|
+
/* Connecting state */
|
|
538
|
+
.mic-button.connecting {
|
|
539
|
+
background: linear-gradient(135deg, #10b981 0%, #059669 100%);
|
|
540
|
+
cursor: wait;
|
|
541
|
+
}
|
|
542
|
+
|
|
543
|
+
.mic-button.connecting .button-icon {
|
|
544
|
+
animation: spin 1s linear infinite;
|
|
545
|
+
}
|
|
546
|
+
|
|
547
|
+
/* Recording state - red with pulse */
|
|
548
|
+
.mic-button.recording {
|
|
549
|
+
background: linear-gradient(135deg, #ef4444 0%, #dc2626 100%);
|
|
550
|
+
box-shadow: 0 4px 12px rgba(239, 68, 68, 0.4);
|
|
551
|
+
}
|
|
552
|
+
|
|
553
|
+
.mic-button.recording:hover {
|
|
554
|
+
background: linear-gradient(135deg, #dc2626 0%, #b91c1c 100%);
|
|
555
|
+
box-shadow: 0 6px 16px rgba(239, 68, 68, 0.5);
|
|
556
|
+
}
|
|
557
|
+
|
|
558
|
+
/* Pulse ring animation for recording */
|
|
559
|
+
.pulse-ring {
|
|
560
|
+
position: absolute;
|
|
561
|
+
top: 50%;
|
|
562
|
+
left: 50%;
|
|
563
|
+
width: 100%;
|
|
564
|
+
height: 100%;
|
|
565
|
+
border-radius: 50%;
|
|
566
|
+
transform: translate(-50%, -50%);
|
|
567
|
+
pointer-events: none;
|
|
568
|
+
}
|
|
569
|
+
|
|
570
|
+
.pulse-ring::before,
|
|
571
|
+
.pulse-ring::after {
|
|
572
|
+
content: "";
|
|
573
|
+
position: absolute;
|
|
574
|
+
top: 0;
|
|
575
|
+
left: 0;
|
|
576
|
+
width: 100%;
|
|
577
|
+
height: 100%;
|
|
578
|
+
border-radius: 50%;
|
|
579
|
+
background: rgba(239, 68, 68, 0.4);
|
|
580
|
+
animation: pulse-ring 1.5s ease-out infinite;
|
|
581
|
+
}
|
|
582
|
+
|
|
583
|
+
.pulse-ring::after {
|
|
584
|
+
animation-delay: 0.5s;
|
|
585
|
+
}
|
|
586
|
+
|
|
587
|
+
@keyframes pulse-ring {
|
|
588
|
+
0% {
|
|
589
|
+
transform: scale(1);
|
|
590
|
+
opacity: 0.6;
|
|
591
|
+
}
|
|
592
|
+
100% {
|
|
593
|
+
transform: scale(1.8);
|
|
594
|
+
opacity: 0;
|
|
595
|
+
}
|
|
596
|
+
}
|
|
597
|
+
|
|
598
|
+
/* Processing state (transcribing) */
|
|
599
|
+
.mic-button.processing {
|
|
600
|
+
background: linear-gradient(135deg, #f59e0b 0%, #d97706 100%);
|
|
601
|
+
box-shadow: 0 4px 12px rgba(245, 158, 11, 0.4);
|
|
602
|
+
cursor: wait;
|
|
603
|
+
}
|
|
604
|
+
|
|
605
|
+
.mic-button.processing .button-icon {
|
|
606
|
+
animation: pulse-scale 1s ease-in-out infinite;
|
|
607
|
+
}
|
|
608
|
+
|
|
609
|
+
/* Processing state (editing) - purple */
|
|
610
|
+
.mic-button.processing.editing {
|
|
611
|
+
background: linear-gradient(135deg, #8b5cf6 0%, #7c3aed 100%);
|
|
612
|
+
box-shadow: 0 4px 12px rgba(139, 92, 246, 0.4);
|
|
613
|
+
}
|
|
614
|
+
|
|
615
|
+
@keyframes pulse-scale {
|
|
616
|
+
0%,
|
|
617
|
+
100% {
|
|
618
|
+
transform: scale(1);
|
|
619
|
+
}
|
|
620
|
+
50% {
|
|
621
|
+
transform: scale(0.85);
|
|
622
|
+
}
|
|
623
|
+
}
|
|
624
|
+
|
|
625
|
+
/* Error state */
|
|
626
|
+
.mic-button.error {
|
|
627
|
+
background: linear-gradient(135deg, #ef4444 0%, #dc2626 100%);
|
|
628
|
+
box-shadow: 0 4px 12px rgba(239, 68, 68, 0.4);
|
|
629
|
+
cursor: pointer;
|
|
630
|
+
}
|
|
631
|
+
|
|
632
|
+
.mic-button.error:hover {
|
|
633
|
+
background: linear-gradient(135deg, #dc2626 0%, #b91c1c 100%);
|
|
634
|
+
box-shadow: 0 6px 20px rgba(239, 68, 68, 0.5);
|
|
635
|
+
}
|
|
636
|
+
|
|
637
|
+
@keyframes spin {
|
|
638
|
+
from {
|
|
639
|
+
transform: rotate(0deg);
|
|
640
|
+
}
|
|
641
|
+
to {
|
|
642
|
+
transform: rotate(360deg);
|
|
643
|
+
}
|
|
644
|
+
}
|
|
645
|
+
|
|
646
|
+
.button-icon {
|
|
647
|
+
display: flex;
|
|
648
|
+
align-items: center;
|
|
649
|
+
justify-content: center;
|
|
650
|
+
position: relative;
|
|
651
|
+
z-index: 1;
|
|
652
|
+
transition: transform 0.2s ease;
|
|
653
|
+
}
|
|
654
|
+
|
|
655
|
+
/* Status label bubble below button - always centered */
|
|
656
|
+
.status-label {
|
|
657
|
+
position: absolute;
|
|
658
|
+
bottom: -32px;
|
|
659
|
+
left: 50%;
|
|
660
|
+
transform: translateX(-50%) scale(0.8);
|
|
661
|
+
font-size: 11px;
|
|
662
|
+
font-weight: 600;
|
|
663
|
+
color: white;
|
|
664
|
+
white-space: nowrap;
|
|
665
|
+
opacity: 0;
|
|
666
|
+
text-transform: uppercase;
|
|
667
|
+
letter-spacing: 0.5px;
|
|
668
|
+
padding: 6px 12px;
|
|
669
|
+
border-radius: 20px;
|
|
670
|
+
background: #374151;
|
|
671
|
+
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.15);
|
|
672
|
+
transition: all 0.2s cubic-bezier(0.34, 1.56, 0.64, 1);
|
|
673
|
+
pointer-events: none;
|
|
674
|
+
}
|
|
675
|
+
|
|
676
|
+
/* Pop-in animation when visible */
|
|
677
|
+
.status-label.visible {
|
|
678
|
+
opacity: 1;
|
|
679
|
+
transform: translateX(-50%) scale(1);
|
|
680
|
+
}
|
|
681
|
+
|
|
682
|
+
/* Recording state - red bubble with dot indicator */
|
|
683
|
+
.status-label.recording {
|
|
684
|
+
background: linear-gradient(135deg, #ef4444 0%, #dc2626 100%);
|
|
685
|
+
box-shadow: 0 4px 12px rgba(239, 68, 68, 0.3);
|
|
686
|
+
padding-left: 20px; /* Make room for the pulsing dot */
|
|
687
|
+
}
|
|
688
|
+
|
|
689
|
+
/* Processing state - amber bubble (for dictate/transcribing) */
|
|
690
|
+
.status-label.processing {
|
|
691
|
+
background: linear-gradient(135deg, #f59e0b 0%, #d97706 100%);
|
|
692
|
+
box-shadow: 0 4px 12px rgba(245, 158, 11, 0.3);
|
|
693
|
+
}
|
|
694
|
+
|
|
695
|
+
/* Editing state - purple bubble */
|
|
696
|
+
.status-label.editing {
|
|
697
|
+
background: linear-gradient(135deg, #8b5cf6 0%, #7c3aed 100%);
|
|
698
|
+
box-shadow: 0 4px 12px rgba(139, 92, 246, 0.3);
|
|
699
|
+
}
|
|
700
|
+
|
|
701
|
+
/* Connecting state - emerald bubble */
|
|
702
|
+
.status-label.connecting {
|
|
703
|
+
background: linear-gradient(135deg, #10b981 0%, #059669 100%);
|
|
704
|
+
box-shadow: 0 4px 12px rgba(16, 185, 129, 0.3);
|
|
705
|
+
}
|
|
706
|
+
|
|
707
|
+
/* Subtle pulse animation for the label */
|
|
708
|
+
.status-label.recording::after {
|
|
709
|
+
content: "";
|
|
710
|
+
position: absolute;
|
|
711
|
+
top: 50%;
|
|
712
|
+
left: 8px;
|
|
713
|
+
width: 6px;
|
|
714
|
+
height: 6px;
|
|
715
|
+
background: white;
|
|
716
|
+
border-radius: 50%;
|
|
717
|
+
transform: translateY(-50%);
|
|
718
|
+
animation: label-pulse 1s ease-in-out infinite;
|
|
719
|
+
}
|
|
720
|
+
|
|
721
|
+
@keyframes label-pulse {
|
|
722
|
+
0%,
|
|
723
|
+
100% {
|
|
724
|
+
opacity: 1;
|
|
725
|
+
}
|
|
726
|
+
50% {
|
|
727
|
+
opacity: 0.4;
|
|
728
|
+
}
|
|
729
|
+
}
|
|
730
|
+
|
|
731
|
+
/* Cancel button - positioned to the right of the main mic button */
|
|
732
|
+
.cancel-button {
|
|
733
|
+
position: absolute;
|
|
734
|
+
top: 50%;
|
|
735
|
+
right: -36px;
|
|
736
|
+
transform: translateY(-50%) scale(0.6);
|
|
737
|
+
width: 24px;
|
|
738
|
+
height: 24px;
|
|
739
|
+
border-radius: 50%;
|
|
740
|
+
background: rgba(24, 24, 27, 0.8);
|
|
741
|
+
backdrop-filter: blur(8px);
|
|
742
|
+
-webkit-backdrop-filter: blur(8px);
|
|
743
|
+
border: 1px solid rgba(63, 63, 70, 0.5);
|
|
744
|
+
cursor: pointer;
|
|
745
|
+
display: flex;
|
|
746
|
+
align-items: center;
|
|
747
|
+
justify-content: center;
|
|
748
|
+
color: rgba(161, 161, 170, 1);
|
|
749
|
+
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.15);
|
|
750
|
+
transition: all 0.2s cubic-bezier(0.34, 1.56, 0.64, 1);
|
|
751
|
+
pointer-events: none;
|
|
752
|
+
opacity: 0;
|
|
753
|
+
}
|
|
754
|
+
|
|
755
|
+
.cancel-button.visible {
|
|
756
|
+
opacity: 1;
|
|
757
|
+
transform: translateY(-50%) scale(1);
|
|
758
|
+
pointer-events: auto;
|
|
759
|
+
}
|
|
760
|
+
|
|
761
|
+
.cancel-button:hover {
|
|
762
|
+
transform: translateY(-50%) scale(1.15);
|
|
763
|
+
background: rgba(39, 39, 42, 0.9);
|
|
764
|
+
border-color: rgba(63, 63, 70, 0.8);
|
|
765
|
+
color: rgba(250, 250, 250, 1);
|
|
766
|
+
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.25);
|
|
767
|
+
}
|
|
768
|
+
|
|
769
|
+
.cancel-button:active {
|
|
770
|
+
transform: translateY(-50%) scale(0.95);
|
|
771
|
+
}
|
|
772
|
+
|
|
773
|
+
.cancel-button svg {
|
|
774
|
+
stroke-width: 2.5;
|
|
775
|
+
}
|
|
776
|
+
|
|
777
|
+
/* Close/dismiss button - positioned to the right of the main mic button */
|
|
778
|
+
.close-button {
|
|
779
|
+
position: absolute;
|
|
780
|
+
top: 50%;
|
|
781
|
+
right: -36px;
|
|
782
|
+
transform: translateY(-50%) scale(0.6);
|
|
783
|
+
width: 24px;
|
|
784
|
+
height: 24px;
|
|
785
|
+
border-radius: 50%;
|
|
786
|
+
background: rgba(24, 24, 27, 0.8);
|
|
787
|
+
backdrop-filter: blur(8px);
|
|
788
|
+
-webkit-backdrop-filter: blur(8px);
|
|
789
|
+
border: 1px solid rgba(63, 63, 70, 0.5);
|
|
790
|
+
cursor: pointer;
|
|
791
|
+
display: flex;
|
|
792
|
+
align-items: center;
|
|
793
|
+
justify-content: center;
|
|
794
|
+
color: rgba(161, 161, 170, 1);
|
|
795
|
+
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.15);
|
|
796
|
+
transition: all 0.2s cubic-bezier(0.34, 1.56, 0.64, 1);
|
|
797
|
+
pointer-events: none;
|
|
798
|
+
opacity: 0;
|
|
799
|
+
}
|
|
800
|
+
|
|
801
|
+
.close-button.visible {
|
|
802
|
+
opacity: 1;
|
|
803
|
+
transform: translateY(-50%) scale(1);
|
|
804
|
+
pointer-events: auto;
|
|
805
|
+
}
|
|
806
|
+
|
|
807
|
+
.close-button:hover {
|
|
808
|
+
transform: translateY(-50%) scale(1.15);
|
|
809
|
+
background: rgba(39, 39, 42, 0.9);
|
|
810
|
+
border-color: rgba(63, 63, 70, 0.8);
|
|
811
|
+
color: rgba(250, 250, 250, 1);
|
|
812
|
+
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.25);
|
|
813
|
+
}
|
|
814
|
+
|
|
815
|
+
.close-button:active {
|
|
816
|
+
transform: translateY(-50%) scale(0.95);
|
|
817
|
+
}
|
|
818
|
+
|
|
819
|
+
.close-button svg {
|
|
820
|
+
stroke-width: 2.5;
|
|
821
|
+
}
|
|
822
|
+
|
|
823
|
+
/* Error message display */
|
|
824
|
+
.error-message {
|
|
825
|
+
position: absolute;
|
|
826
|
+
bottom: 72px; /* Above button */
|
|
827
|
+
left: 50%;
|
|
828
|
+
transform: translateX(-50%) translateY(8px);
|
|
829
|
+
max-width: 280px;
|
|
830
|
+
font-size: 13px;
|
|
831
|
+
color: white;
|
|
832
|
+
white-space: normal;
|
|
833
|
+
text-align: center;
|
|
834
|
+
padding: 12px 16px;
|
|
835
|
+
border-radius: 12px;
|
|
836
|
+
background: linear-gradient(135deg, #ef4444 0%, #dc2626 100%);
|
|
837
|
+
box-shadow: 0 4px 12px rgba(239, 68, 68, 0.3);
|
|
838
|
+
transition: all 0.2s cubic-bezier(0.34, 1.56, 0.64, 1);
|
|
839
|
+
pointer-events: none;
|
|
840
|
+
opacity: 0;
|
|
841
|
+
line-height: 1.4;
|
|
842
|
+
}
|
|
843
|
+
|
|
844
|
+
.error-message.visible {
|
|
845
|
+
opacity: 1;
|
|
846
|
+
transform: translateX(-50%) translateY(0);
|
|
847
|
+
}
|
|
848
|
+
|
|
849
|
+
/* Retry button */
|
|
850
|
+
.retry-button {
|
|
851
|
+
display: block;
|
|
852
|
+
margin: 8px auto 0;
|
|
853
|
+
padding: 6px 12px;
|
|
854
|
+
background: rgba(255, 255, 255, 0.2);
|
|
855
|
+
border: 1px solid rgba(255, 255, 255, 0.3);
|
|
856
|
+
border-radius: 6px;
|
|
857
|
+
color: white;
|
|
858
|
+
font-size: 12px;
|
|
859
|
+
font-weight: 600;
|
|
860
|
+
cursor: pointer;
|
|
861
|
+
transition: all 0.15s;
|
|
862
|
+
pointer-events: auto;
|
|
863
|
+
}
|
|
864
|
+
|
|
865
|
+
.retry-button:hover {
|
|
866
|
+
background: rgba(255, 255, 255, 0.3);
|
|
867
|
+
border-color: rgba(255, 255, 255, 0.5);
|
|
868
|
+
}
|
|
869
|
+
`,
|
|
870
|
+
]; }
|
|
871
|
+
handleClick(e) {
|
|
872
|
+
e.stopPropagation();
|
|
873
|
+
e.preventDefault();
|
|
874
|
+
// Different behavior based on recording state
|
|
875
|
+
if (this.recordingState === "recording") {
|
|
876
|
+
// Stop recording
|
|
877
|
+
this.dispatchEvent(new CustomEvent("stop-recording", {
|
|
878
|
+
bubbles: true,
|
|
879
|
+
composed: true,
|
|
880
|
+
}));
|
|
881
|
+
}
|
|
882
|
+
else if (this.recordingState === "idle") {
|
|
883
|
+
// Toggle bubbles menu
|
|
884
|
+
this.dispatchEvent(new CustomEvent("mic-click", {
|
|
885
|
+
bubbles: true,
|
|
886
|
+
composed: true,
|
|
887
|
+
}));
|
|
888
|
+
}
|
|
889
|
+
else if (this.recordingState === "error") {
|
|
890
|
+
// Clear error on click
|
|
891
|
+
this.dispatchEvent(new CustomEvent("cancel-operation", {
|
|
892
|
+
bubbles: true,
|
|
893
|
+
composed: true,
|
|
894
|
+
}));
|
|
895
|
+
}
|
|
896
|
+
// Do nothing during connecting or processing states
|
|
897
|
+
}
|
|
898
|
+
handleCancel(e) {
|
|
899
|
+
e.stopPropagation();
|
|
900
|
+
e.preventDefault();
|
|
901
|
+
this.dispatchEvent(new CustomEvent("cancel-operation", {
|
|
902
|
+
bubbles: true,
|
|
903
|
+
composed: true,
|
|
904
|
+
}));
|
|
905
|
+
}
|
|
906
|
+
stopEvent(e) {
|
|
907
|
+
e.stopPropagation();
|
|
908
|
+
}
|
|
909
|
+
handleClose(e) {
|
|
910
|
+
e.stopPropagation();
|
|
911
|
+
e.preventDefault();
|
|
912
|
+
this.dispatchEvent(new CustomEvent("close-widget", {
|
|
913
|
+
bubbles: true,
|
|
914
|
+
composed: true,
|
|
915
|
+
}));
|
|
916
|
+
}
|
|
917
|
+
handleRetry(e) {
|
|
918
|
+
e.stopPropagation();
|
|
919
|
+
e.preventDefault();
|
|
920
|
+
this.dispatchEvent(new CustomEvent("retry-connection", {
|
|
921
|
+
bubbles: true,
|
|
922
|
+
composed: true,
|
|
923
|
+
}));
|
|
924
|
+
}
|
|
925
|
+
getButtonClass() {
|
|
926
|
+
const classes = ["mic-button"];
|
|
927
|
+
if (this.expanded && this.recordingState === "idle") {
|
|
928
|
+
classes.push("expanded");
|
|
929
|
+
}
|
|
930
|
+
if (this.recordingState !== "idle") {
|
|
931
|
+
classes.push(this.recordingState);
|
|
932
|
+
// Add editing class for purple styling during edit processing
|
|
933
|
+
if (this.recordingState === "processing" &&
|
|
934
|
+
this.activeAction === "edit") {
|
|
935
|
+
classes.push("editing");
|
|
936
|
+
}
|
|
937
|
+
}
|
|
938
|
+
return classes.join(" ");
|
|
939
|
+
}
|
|
940
|
+
renderIcon() {
|
|
941
|
+
switch (this.recordingState) {
|
|
942
|
+
case "connecting":
|
|
943
|
+
return loaderIcon(24);
|
|
944
|
+
case "recording":
|
|
945
|
+
return stopIcon(20);
|
|
946
|
+
case "processing":
|
|
947
|
+
return loaderIcon(24);
|
|
948
|
+
case "error":
|
|
949
|
+
return xIcon(20);
|
|
950
|
+
default:
|
|
951
|
+
return micIcon(24);
|
|
952
|
+
}
|
|
953
|
+
}
|
|
954
|
+
getAriaLabel() {
|
|
955
|
+
switch (this.recordingState) {
|
|
956
|
+
case "connecting":
|
|
957
|
+
return "Connecting...";
|
|
958
|
+
case "recording":
|
|
959
|
+
return "Stop recording";
|
|
960
|
+
case "processing":
|
|
961
|
+
return "Processing...";
|
|
962
|
+
case "error":
|
|
963
|
+
return "Connection error - click to dismiss";
|
|
964
|
+
default:
|
|
965
|
+
return "Open voice commands";
|
|
966
|
+
}
|
|
967
|
+
}
|
|
968
|
+
/**
|
|
969
|
+
* Truncate and format preview text for display
|
|
970
|
+
*/
|
|
971
|
+
formatPreviewText(text, maxLength = 10) {
|
|
972
|
+
// Trim whitespace and collapse multiple spaces
|
|
973
|
+
const trimmed = text.trim().replace(/\s+/g, " ");
|
|
974
|
+
if (!trimmed)
|
|
975
|
+
return "";
|
|
976
|
+
if (trimmed.length <= maxLength) {
|
|
977
|
+
return `"${trimmed}"`;
|
|
978
|
+
}
|
|
979
|
+
return `"${trimmed.substring(0, maxLength)}…"`;
|
|
980
|
+
}
|
|
981
|
+
getStatusLabel() {
|
|
982
|
+
switch (this.recordingState) {
|
|
983
|
+
case "connecting":
|
|
984
|
+
return "Connecting...";
|
|
985
|
+
case "recording":
|
|
986
|
+
if (this.activeAction === "edit" && this.editPreviewText) {
|
|
987
|
+
const preview = this.formatPreviewText(this.editPreviewText);
|
|
988
|
+
return `Editing ${preview}...`;
|
|
989
|
+
}
|
|
990
|
+
return "Listening";
|
|
991
|
+
case "processing":
|
|
992
|
+
if (this.activeAction === "edit") {
|
|
993
|
+
if (this.editPreviewText) {
|
|
994
|
+
const preview = this.formatPreviewText(this.editPreviewText);
|
|
995
|
+
return `Editing ${preview}...`;
|
|
996
|
+
}
|
|
997
|
+
return "Editing...";
|
|
998
|
+
}
|
|
999
|
+
return "Transcribing...";
|
|
1000
|
+
default:
|
|
1001
|
+
return "";
|
|
1002
|
+
}
|
|
1003
|
+
}
|
|
1004
|
+
getStatusClass() {
|
|
1005
|
+
if (this.recordingState === "processing" && this.activeAction === "edit") {
|
|
1006
|
+
return "editing";
|
|
1007
|
+
}
|
|
1008
|
+
return this.recordingState;
|
|
1009
|
+
}
|
|
1010
|
+
render() {
|
|
1011
|
+
const showPulse = this.recordingState === "recording";
|
|
1012
|
+
const statusLabel = this.getStatusLabel();
|
|
1013
|
+
const showStatus = this.recordingState !== "idle";
|
|
1014
|
+
const showCancel = this.recordingState === "connecting" ||
|
|
1015
|
+
this.recordingState === "recording" ||
|
|
1016
|
+
this.recordingState === "processing";
|
|
1017
|
+
const showError = this.recordingState === "error" && this.errorMessage;
|
|
1018
|
+
// Show close button in idle state (both solo mic and expanded)
|
|
1019
|
+
const showClose = this.recordingState === "idle";
|
|
1020
|
+
return b `
|
|
1021
|
+
<div class="button-wrapper">
|
|
1022
|
+
${showPulse ? b `<div class="pulse-ring"></div>` : ""}
|
|
1023
|
+
${showError
|
|
1024
|
+
? b `
|
|
1025
|
+
<div class="error-message ${showError ? "visible" : ""}">
|
|
1026
|
+
${this.errorMessage}
|
|
1027
|
+
<button class="retry-button" @click="${this.handleRetry}">
|
|
1028
|
+
Retry Connection
|
|
1029
|
+
</button>
|
|
1030
|
+
</div>
|
|
1031
|
+
`
|
|
1032
|
+
: ""}
|
|
1033
|
+
|
|
1034
|
+
<button
|
|
1035
|
+
class="${this.getButtonClass()}"
|
|
1036
|
+
@click="${this.handleClick}"
|
|
1037
|
+
aria-label="${this.getAriaLabel()}"
|
|
1038
|
+
title="${this.getAriaLabel()}"
|
|
1039
|
+
?disabled="${this.recordingState === "connecting" ||
|
|
1040
|
+
this.recordingState === "processing"}"
|
|
1041
|
+
>
|
|
1042
|
+
<span class="button-icon">${this.renderIcon()}</span>
|
|
1043
|
+
</button>
|
|
1044
|
+
|
|
1045
|
+
<span
|
|
1046
|
+
class="status-label ${showStatus
|
|
1047
|
+
? "visible"
|
|
1048
|
+
: ""} ${this.getStatusClass()}"
|
|
1049
|
+
>
|
|
1050
|
+
${statusLabel}
|
|
1051
|
+
</span>
|
|
1052
|
+
|
|
1053
|
+
<button
|
|
1054
|
+
class="close-button ${showClose ? "visible" : ""}"
|
|
1055
|
+
@click="${this.handleClose}"
|
|
1056
|
+
@mousedown="${this.stopEvent}"
|
|
1057
|
+
aria-label="Close"
|
|
1058
|
+
title="Close"
|
|
1059
|
+
>
|
|
1060
|
+
${xIcon(12)}
|
|
1061
|
+
</button>
|
|
1062
|
+
|
|
1063
|
+
<button
|
|
1064
|
+
class="cancel-button ${showCancel ? "visible" : ""}"
|
|
1065
|
+
@click="${this.handleCancel}"
|
|
1066
|
+
@mousedown="${this.stopEvent}"
|
|
1067
|
+
aria-label="Cancel"
|
|
1068
|
+
title="Cancel"
|
|
1069
|
+
>
|
|
1070
|
+
${xIcon(12)}
|
|
1071
|
+
</button>
|
|
1072
|
+
</div>
|
|
1073
|
+
`;
|
|
1074
|
+
}
|
|
1075
|
+
};
|
|
1076
|
+
__decorate([
|
|
1077
|
+
n({ type: Boolean })
|
|
1078
|
+
], SpeechOSMicButton.prototype, "expanded", void 0);
|
|
1079
|
+
__decorate([
|
|
1080
|
+
n({ type: String })
|
|
1081
|
+
], SpeechOSMicButton.prototype, "recordingState", void 0);
|
|
1082
|
+
__decorate([
|
|
1083
|
+
n({ type: String })
|
|
1084
|
+
], SpeechOSMicButton.prototype, "activeAction", void 0);
|
|
1085
|
+
__decorate([
|
|
1086
|
+
n({ type: String })
|
|
1087
|
+
], SpeechOSMicButton.prototype, "editPreviewText", void 0);
|
|
1088
|
+
__decorate([
|
|
1089
|
+
n({ type: String })
|
|
1090
|
+
], SpeechOSMicButton.prototype, "errorMessage", void 0);
|
|
1091
|
+
SpeechOSMicButton = __decorate([
|
|
1092
|
+
t$1("speechos-mic-button")
|
|
1093
|
+
], SpeechOSMicButton);
|
|
1094
|
+
|
|
1095
|
+
/**
|
|
1096
|
+
* Action bubbles component
|
|
1097
|
+
* Displays available actions (Dictate, Edit) above the mic button
|
|
1098
|
+
*/
|
|
1099
|
+
let SpeechOSActionBubbles = class SpeechOSActionBubbles extends i$1 {
|
|
1100
|
+
constructor() {
|
|
1101
|
+
super(...arguments);
|
|
1102
|
+
this.visible = false;
|
|
1103
|
+
this.actions = [
|
|
1104
|
+
{
|
|
1105
|
+
id: "dictate",
|
|
1106
|
+
label: "Dictate",
|
|
1107
|
+
icon: dictateIcon(),
|
|
1108
|
+
},
|
|
1109
|
+
{
|
|
1110
|
+
id: "edit",
|
|
1111
|
+
label: "Edit",
|
|
1112
|
+
icon: editIcon(),
|
|
1113
|
+
},
|
|
1114
|
+
];
|
|
1115
|
+
}
|
|
1116
|
+
static { this.styles = [
|
|
1117
|
+
themeStyles,
|
|
1118
|
+
animations,
|
|
1119
|
+
i$4 `
|
|
1120
|
+
:host {
|
|
1121
|
+
display: block;
|
|
1122
|
+
}
|
|
1123
|
+
|
|
1124
|
+
.bubbles-container {
|
|
1125
|
+
display: flex;
|
|
1126
|
+
gap: var(--speechos-spacing-sm);
|
|
1127
|
+
margin-bottom: var(--speechos-spacing-md);
|
|
1128
|
+
justify-content: center;
|
|
1129
|
+
}
|
|
1130
|
+
|
|
1131
|
+
.action-button {
|
|
1132
|
+
display: flex;
|
|
1133
|
+
align-items: center;
|
|
1134
|
+
gap: var(--speechos-spacing-xs);
|
|
1135
|
+
padding: var(--speechos-spacing-sm) var(--speechos-spacing-md);
|
|
1136
|
+
border: none;
|
|
1137
|
+
border-radius: var(--speechos-radius-full);
|
|
1138
|
+
cursor: pointer;
|
|
1139
|
+
font-size: 14px;
|
|
1140
|
+
font-weight: 600;
|
|
1141
|
+
color: white;
|
|
1142
|
+
transition: all var(--speechos-transition-fast);
|
|
1143
|
+
animation: speechos-slideUp var(--speechos-transition-base) ease-out;
|
|
1144
|
+
white-space: nowrap;
|
|
1145
|
+
}
|
|
1146
|
+
|
|
1147
|
+
/* Dictate button - Emerald (matches connecting state) */
|
|
1148
|
+
.action-button.dictate {
|
|
1149
|
+
background: linear-gradient(135deg, #10b981 0%, #059669 100%);
|
|
1150
|
+
box-shadow: 0 4px 12px rgba(16, 185, 129, 0.3);
|
|
1151
|
+
}
|
|
1152
|
+
|
|
1153
|
+
.action-button.dictate:hover {
|
|
1154
|
+
background: linear-gradient(135deg, #34d399 0%, #10b981 100%);
|
|
1155
|
+
transform: translateY(-2px);
|
|
1156
|
+
box-shadow: 0 6px 16px rgba(16, 185, 129, 0.4);
|
|
1157
|
+
}
|
|
1158
|
+
|
|
1159
|
+
/* Edit button - Purple (matches editing state) */
|
|
1160
|
+
.action-button.edit {
|
|
1161
|
+
background: linear-gradient(135deg, #8b5cf6 0%, #7c3aed 100%);
|
|
1162
|
+
box-shadow: 0 4px 12px rgba(139, 92, 246, 0.3);
|
|
1163
|
+
}
|
|
1164
|
+
|
|
1165
|
+
.action-button.edit:hover {
|
|
1166
|
+
background: linear-gradient(135deg, #a78bfa 0%, #8b5cf6 100%);
|
|
1167
|
+
transform: translateY(-2px);
|
|
1168
|
+
box-shadow: 0 6px 16px rgba(139, 92, 246, 0.4);
|
|
1169
|
+
}
|
|
1170
|
+
|
|
1171
|
+
.action-button:active {
|
|
1172
|
+
transform: translateY(0);
|
|
1173
|
+
}
|
|
1174
|
+
|
|
1175
|
+
.action-icon {
|
|
1176
|
+
display: flex;
|
|
1177
|
+
align-items: center;
|
|
1178
|
+
justify-content: center;
|
|
1179
|
+
color: white;
|
|
1180
|
+
}
|
|
1181
|
+
|
|
1182
|
+
/* Staggered animation delay */
|
|
1183
|
+
.action-button:nth-child(1) {
|
|
1184
|
+
animation-delay: 0ms;
|
|
1185
|
+
}
|
|
1186
|
+
|
|
1187
|
+
.action-button:nth-child(2) {
|
|
1188
|
+
animation-delay: 50ms;
|
|
1189
|
+
}
|
|
1190
|
+
|
|
1191
|
+
.action-button:nth-child(3) {
|
|
1192
|
+
animation-delay: 100ms;
|
|
1193
|
+
}
|
|
1194
|
+
|
|
1195
|
+
.action-button:nth-child(4) {
|
|
1196
|
+
animation-delay: 150ms;
|
|
1197
|
+
}
|
|
1198
|
+
`,
|
|
1199
|
+
]; }
|
|
1200
|
+
handleActionClick(e, action) {
|
|
1201
|
+
// Stop propagation to prevent bubbling issues
|
|
1202
|
+
e.stopPropagation();
|
|
1203
|
+
e.preventDefault();
|
|
1204
|
+
core.events.emit("action:select", { action });
|
|
1205
|
+
this.dispatchEvent(new CustomEvent("action-select", {
|
|
1206
|
+
bubbles: true,
|
|
1207
|
+
composed: true,
|
|
1208
|
+
detail: { action },
|
|
1209
|
+
}));
|
|
1210
|
+
}
|
|
1211
|
+
render() {
|
|
1212
|
+
if (!this.visible) {
|
|
1213
|
+
return b ``;
|
|
1214
|
+
}
|
|
1215
|
+
return b `
|
|
1216
|
+
<div class="bubbles-container">
|
|
1217
|
+
${this.actions.map((action) => b `
|
|
1218
|
+
<button
|
|
1219
|
+
class="action-button ${action.id}"
|
|
1220
|
+
@click="${(e) => this.handleActionClick(e, action.id)}"
|
|
1221
|
+
aria-label="${action.label}"
|
|
1222
|
+
title="${action.label}"
|
|
1223
|
+
>
|
|
1224
|
+
<span class="action-icon">${action.icon}</span>
|
|
1225
|
+
<span>${action.label}</span>
|
|
1226
|
+
</button>
|
|
1227
|
+
`)}
|
|
1228
|
+
</div>
|
|
1229
|
+
`;
|
|
1230
|
+
}
|
|
1231
|
+
};
|
|
1232
|
+
__decorate([
|
|
1233
|
+
n({ type: Boolean })
|
|
1234
|
+
], SpeechOSActionBubbles.prototype, "visible", void 0);
|
|
1235
|
+
SpeechOSActionBubbles = __decorate([
|
|
1236
|
+
t$1("speechos-action-bubbles")
|
|
1237
|
+
], SpeechOSActionBubbles);
|
|
1238
|
+
|
|
1239
|
+
/**
|
|
1240
|
+
* Settings button component
|
|
1241
|
+
* A small gear icon button that opens the settings modal
|
|
1242
|
+
*/
|
|
1243
|
+
let SpeechOSSettingsButton = class SpeechOSSettingsButton extends i$1 {
|
|
1244
|
+
constructor() {
|
|
1245
|
+
super(...arguments);
|
|
1246
|
+
this.visible = false;
|
|
1247
|
+
}
|
|
1248
|
+
static { this.styles = [
|
|
1249
|
+
themeStyles,
|
|
1250
|
+
i$4 `
|
|
1251
|
+
:host {
|
|
1252
|
+
display: block;
|
|
1253
|
+
}
|
|
1254
|
+
|
|
1255
|
+
.settings-button {
|
|
1256
|
+
width: 28px;
|
|
1257
|
+
height: 28px;
|
|
1258
|
+
border-radius: 50%;
|
|
1259
|
+
background: rgba(24, 24, 27, 0.8);
|
|
1260
|
+
backdrop-filter: blur(8px);
|
|
1261
|
+
-webkit-backdrop-filter: blur(8px);
|
|
1262
|
+
border: 1px solid rgba(63, 63, 70, 0.5);
|
|
1263
|
+
cursor: pointer;
|
|
1264
|
+
display: flex;
|
|
1265
|
+
align-items: center;
|
|
1266
|
+
justify-content: center;
|
|
1267
|
+
color: rgba(161, 161, 170, 1);
|
|
1268
|
+
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.15);
|
|
1269
|
+
transition: all 0.2s ease;
|
|
1270
|
+
opacity: 0;
|
|
1271
|
+
transform: scale(0.8);
|
|
1272
|
+
pointer-events: none;
|
|
1273
|
+
}
|
|
1274
|
+
|
|
1275
|
+
.settings-button.visible {
|
|
1276
|
+
opacity: 1;
|
|
1277
|
+
transform: scale(1);
|
|
1278
|
+
pointer-events: auto;
|
|
1279
|
+
}
|
|
1280
|
+
|
|
1281
|
+
.settings-button:hover {
|
|
1282
|
+
background: rgba(39, 39, 42, 0.9);
|
|
1283
|
+
border-color: rgba(63, 63, 70, 0.8);
|
|
1284
|
+
color: rgba(250, 250, 250, 1);
|
|
1285
|
+
transform: scale(1.05);
|
|
1286
|
+
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.25);
|
|
1287
|
+
}
|
|
1288
|
+
|
|
1289
|
+
.settings-button:active {
|
|
1290
|
+
transform: scale(0.95);
|
|
1291
|
+
}
|
|
1292
|
+
`,
|
|
1293
|
+
]; }
|
|
1294
|
+
handleClick(e) {
|
|
1295
|
+
e.stopPropagation();
|
|
1296
|
+
e.preventDefault();
|
|
1297
|
+
this.dispatchEvent(new CustomEvent("settings-click", {
|
|
1298
|
+
bubbles: true,
|
|
1299
|
+
composed: true,
|
|
1300
|
+
}));
|
|
1301
|
+
}
|
|
1302
|
+
render() {
|
|
1303
|
+
return b `
|
|
1304
|
+
<button
|
|
1305
|
+
class="settings-button ${this.visible ? "visible" : ""}"
|
|
1306
|
+
@click="${this.handleClick}"
|
|
1307
|
+
aria-label="Settings"
|
|
1308
|
+
title="Settings"
|
|
1309
|
+
>
|
|
1310
|
+
${settingsIcon(14)}
|
|
1311
|
+
</button>
|
|
1312
|
+
`;
|
|
1313
|
+
}
|
|
1314
|
+
};
|
|
1315
|
+
__decorate([
|
|
1316
|
+
n({ type: Boolean })
|
|
1317
|
+
], SpeechOSSettingsButton.prototype, "visible", void 0);
|
|
1318
|
+
SpeechOSSettingsButton = __decorate([
|
|
1319
|
+
t$1("speechos-settings-button")
|
|
1320
|
+
], SpeechOSSettingsButton);
|
|
1321
|
+
|
|
1322
|
+
/**
|
|
1323
|
+
* Settings modal component
|
|
1324
|
+
* Displays transcript history, help, about, and settings in a tabbed interface
|
|
1325
|
+
*/
|
|
1326
|
+
let SpeechOSSettingsModal = class SpeechOSSettingsModal extends i$1 {
|
|
1327
|
+
constructor() {
|
|
1328
|
+
super(...arguments);
|
|
1329
|
+
this.open = false;
|
|
1330
|
+
this.activeTab = "transcripts";
|
|
1331
|
+
this.transcripts = [];
|
|
1332
|
+
this.tabs = [
|
|
1333
|
+
{ id: "transcripts", label: "History", icon: clipboardIcon(16) },
|
|
1334
|
+
{ id: "help", label: "Help", icon: helpCircleIcon(16) },
|
|
1335
|
+
{ id: "about", label: "About", icon: infoIcon(16) },
|
|
1336
|
+
{ id: "settings", label: "Settings", icon: settingsIcon(16) },
|
|
1337
|
+
];
|
|
1338
|
+
}
|
|
1339
|
+
static { this.styles = [
|
|
1340
|
+
themeStyles,
|
|
1341
|
+
i$4 `
|
|
1342
|
+
:host {
|
|
1343
|
+
/* Position at viewport level to escape widget positioning */
|
|
1344
|
+
position: fixed;
|
|
1345
|
+
inset: 0;
|
|
1346
|
+
pointer-events: none;
|
|
1347
|
+
z-index: calc(var(--speechos-z-base) + 100);
|
|
1348
|
+
}
|
|
1349
|
+
|
|
1350
|
+
/* Overlay */
|
|
1351
|
+
.modal-overlay {
|
|
1352
|
+
position: fixed;
|
|
1353
|
+
inset: 0;
|
|
1354
|
+
background: rgba(0, 0, 0, 0.5);
|
|
1355
|
+
display: flex;
|
|
1356
|
+
align-items: center;
|
|
1357
|
+
justify-content: center;
|
|
1358
|
+
z-index: calc(var(--speechos-z-base) + 100);
|
|
1359
|
+
opacity: 0;
|
|
1360
|
+
visibility: hidden;
|
|
1361
|
+
transition: all 0.2s ease;
|
|
1362
|
+
pointer-events: none;
|
|
1363
|
+
}
|
|
1364
|
+
|
|
1365
|
+
.modal-overlay.open {
|
|
1366
|
+
opacity: 1;
|
|
1367
|
+
visibility: visible;
|
|
1368
|
+
pointer-events: auto;
|
|
1369
|
+
}
|
|
1370
|
+
|
|
1371
|
+
/* Modal container */
|
|
1372
|
+
.modal {
|
|
1373
|
+
background: #1f2937;
|
|
1374
|
+
border-radius: 12px;
|
|
1375
|
+
width: 90%;
|
|
1376
|
+
min-width: 360px;
|
|
1377
|
+
max-width: 500px;
|
|
1378
|
+
min-height: 450px;
|
|
1379
|
+
max-height: 85vh;
|
|
1380
|
+
display: flex;
|
|
1381
|
+
flex-direction: column;
|
|
1382
|
+
box-shadow: 0 20px 40px rgba(0, 0, 0, 0.3);
|
|
1383
|
+
transform: scale(0.95);
|
|
1384
|
+
transition: transform 0.2s ease;
|
|
1385
|
+
pointer-events: auto;
|
|
1386
|
+
}
|
|
1387
|
+
|
|
1388
|
+
.modal-overlay.open .modal {
|
|
1389
|
+
transform: scale(1);
|
|
1390
|
+
}
|
|
1391
|
+
|
|
1392
|
+
/* Header */
|
|
1393
|
+
.modal-header {
|
|
1394
|
+
display: flex;
|
|
1395
|
+
align-items: center;
|
|
1396
|
+
justify-content: space-between;
|
|
1397
|
+
padding: 12px 16px;
|
|
1398
|
+
border-bottom: 1px solid rgba(255, 255, 255, 0.1);
|
|
1399
|
+
}
|
|
1400
|
+
|
|
1401
|
+
.modal-title {
|
|
1402
|
+
font-size: 16px;
|
|
1403
|
+
font-weight: 600;
|
|
1404
|
+
color: white;
|
|
1405
|
+
margin: 0;
|
|
1406
|
+
}
|
|
1407
|
+
|
|
1408
|
+
.close-button {
|
|
1409
|
+
width: 28px;
|
|
1410
|
+
height: 28px;
|
|
1411
|
+
border-radius: 6px;
|
|
1412
|
+
background: transparent;
|
|
1413
|
+
border: none;
|
|
1414
|
+
cursor: pointer;
|
|
1415
|
+
display: flex;
|
|
1416
|
+
align-items: center;
|
|
1417
|
+
justify-content: center;
|
|
1418
|
+
color: rgba(255, 255, 255, 0.6);
|
|
1419
|
+
transition: all 0.15s ease;
|
|
1420
|
+
}
|
|
1421
|
+
|
|
1422
|
+
.close-button:hover {
|
|
1423
|
+
background: rgba(255, 255, 255, 0.1);
|
|
1424
|
+
color: white;
|
|
1425
|
+
}
|
|
1426
|
+
|
|
1427
|
+
/* Tabs */
|
|
1428
|
+
.tabs {
|
|
1429
|
+
display: flex;
|
|
1430
|
+
gap: 2px;
|
|
1431
|
+
padding: 8px 12px;
|
|
1432
|
+
border-bottom: 1px solid rgba(255, 255, 255, 0.1);
|
|
1433
|
+
background: rgba(0, 0, 0, 0.2);
|
|
1434
|
+
}
|
|
1435
|
+
|
|
1436
|
+
.tab {
|
|
1437
|
+
flex: 1;
|
|
1438
|
+
display: flex;
|
|
1439
|
+
flex-direction: column;
|
|
1440
|
+
align-items: center;
|
|
1441
|
+
gap: 2px;
|
|
1442
|
+
padding: 8px 4px;
|
|
1443
|
+
border: none;
|
|
1444
|
+
border-radius: 6px;
|
|
1445
|
+
background: transparent;
|
|
1446
|
+
cursor: pointer;
|
|
1447
|
+
color: rgba(255, 255, 255, 0.5);
|
|
1448
|
+
transition: all 0.15s ease;
|
|
1449
|
+
}
|
|
1450
|
+
|
|
1451
|
+
.tab:hover {
|
|
1452
|
+
background: rgba(255, 255, 255, 0.05);
|
|
1453
|
+
color: rgba(255, 255, 255, 0.8);
|
|
1454
|
+
}
|
|
1455
|
+
|
|
1456
|
+
.tab.active {
|
|
1457
|
+
background: rgba(255, 255, 255, 0.1);
|
|
1458
|
+
color: white;
|
|
1459
|
+
}
|
|
1460
|
+
|
|
1461
|
+
.tab-label {
|
|
1462
|
+
font-size: 12px;
|
|
1463
|
+
font-weight: 500;
|
|
1464
|
+
}
|
|
1465
|
+
|
|
1466
|
+
/* Content */
|
|
1467
|
+
.modal-content {
|
|
1468
|
+
flex: 1;
|
|
1469
|
+
overflow-y: auto;
|
|
1470
|
+
padding: 16px;
|
|
1471
|
+
min-height: 200px;
|
|
1472
|
+
}
|
|
1473
|
+
|
|
1474
|
+
/* Transcripts tab */
|
|
1475
|
+
.transcripts-list {
|
|
1476
|
+
display: flex;
|
|
1477
|
+
flex-direction: column;
|
|
1478
|
+
gap: 8px;
|
|
1479
|
+
}
|
|
1480
|
+
|
|
1481
|
+
.transcript-item {
|
|
1482
|
+
background: rgba(255, 255, 255, 0.05);
|
|
1483
|
+
border-radius: 8px;
|
|
1484
|
+
padding: 10px 12px;
|
|
1485
|
+
}
|
|
1486
|
+
|
|
1487
|
+
.transcript-header {
|
|
1488
|
+
display: flex;
|
|
1489
|
+
align-items: center;
|
|
1490
|
+
justify-content: space-between;
|
|
1491
|
+
margin-bottom: 6px;
|
|
1492
|
+
}
|
|
1493
|
+
|
|
1494
|
+
.transcript-badge {
|
|
1495
|
+
display: inline-flex;
|
|
1496
|
+
align-items: center;
|
|
1497
|
+
gap: 4px;
|
|
1498
|
+
font-size: 12px;
|
|
1499
|
+
font-weight: 600;
|
|
1500
|
+
text-transform: uppercase;
|
|
1501
|
+
padding: 3px 8px;
|
|
1502
|
+
border-radius: 4px;
|
|
1503
|
+
}
|
|
1504
|
+
|
|
1505
|
+
.transcript-badge.dictate {
|
|
1506
|
+
background: rgba(16, 185, 129, 0.2);
|
|
1507
|
+
color: #34d399;
|
|
1508
|
+
}
|
|
1509
|
+
|
|
1510
|
+
.transcript-badge.edit {
|
|
1511
|
+
background: rgba(139, 92, 246, 0.2);
|
|
1512
|
+
color: #a78bfa;
|
|
1513
|
+
}
|
|
1514
|
+
|
|
1515
|
+
.transcript-time {
|
|
1516
|
+
font-size: 13px;
|
|
1517
|
+
color: rgba(255, 255, 255, 0.4);
|
|
1518
|
+
}
|
|
1519
|
+
|
|
1520
|
+
.transcript-text {
|
|
1521
|
+
font-size: 15px;
|
|
1522
|
+
color: rgba(255, 255, 255, 0.9);
|
|
1523
|
+
line-height: 1.5;
|
|
1524
|
+
word-break: break-word;
|
|
1525
|
+
display: -webkit-box;
|
|
1526
|
+
-webkit-line-clamp: 3;
|
|
1527
|
+
-webkit-box-orient: vertical;
|
|
1528
|
+
overflow: hidden;
|
|
1529
|
+
}
|
|
1530
|
+
|
|
1531
|
+
.transcript-actions {
|
|
1532
|
+
display: flex;
|
|
1533
|
+
gap: 8px;
|
|
1534
|
+
margin-top: 8px;
|
|
1535
|
+
padding-top: 8px;
|
|
1536
|
+
border-top: 1px solid rgba(255, 255, 255, 0.05);
|
|
1537
|
+
}
|
|
1538
|
+
|
|
1539
|
+
.transcript-action-btn {
|
|
1540
|
+
display: inline-flex;
|
|
1541
|
+
align-items: center;
|
|
1542
|
+
gap: 6px;
|
|
1543
|
+
padding: 6px 10px;
|
|
1544
|
+
border-radius: 4px;
|
|
1545
|
+
background: rgba(255, 255, 255, 0.05);
|
|
1546
|
+
border: none;
|
|
1547
|
+
cursor: pointer;
|
|
1548
|
+
font-size: 13px;
|
|
1549
|
+
font-weight: 500;
|
|
1550
|
+
color: rgba(255, 255, 255, 0.6);
|
|
1551
|
+
transition: all 0.15s ease;
|
|
1552
|
+
}
|
|
1553
|
+
|
|
1554
|
+
.transcript-action-btn:hover {
|
|
1555
|
+
background: rgba(255, 255, 255, 0.1);
|
|
1556
|
+
color: rgba(255, 255, 255, 0.9);
|
|
1557
|
+
}
|
|
1558
|
+
|
|
1559
|
+
.transcript-action-btn.copied {
|
|
1560
|
+
background: rgba(16, 185, 129, 0.2);
|
|
1561
|
+
color: #34d399;
|
|
1562
|
+
transform: scale(1.05);
|
|
1563
|
+
}
|
|
1564
|
+
|
|
1565
|
+
.transcript-action-btn.delete:hover {
|
|
1566
|
+
background: rgba(239, 68, 68, 0.15);
|
|
1567
|
+
color: #f87171;
|
|
1568
|
+
}
|
|
1569
|
+
|
|
1570
|
+
.empty-state {
|
|
1571
|
+
text-align: center;
|
|
1572
|
+
padding: 32px 16px;
|
|
1573
|
+
color: rgba(255, 255, 255, 0.5);
|
|
1574
|
+
}
|
|
1575
|
+
|
|
1576
|
+
.empty-state-icon {
|
|
1577
|
+
margin-bottom: 12px;
|
|
1578
|
+
opacity: 0.5;
|
|
1579
|
+
}
|
|
1580
|
+
|
|
1581
|
+
.empty-state-title {
|
|
1582
|
+
font-size: 16px;
|
|
1583
|
+
font-weight: 500;
|
|
1584
|
+
margin-bottom: 6px;
|
|
1585
|
+
color: rgba(255, 255, 255, 0.7);
|
|
1586
|
+
}
|
|
1587
|
+
|
|
1588
|
+
.empty-state-text {
|
|
1589
|
+
font-size: 14px;
|
|
1590
|
+
}
|
|
1591
|
+
|
|
1592
|
+
.clear-all-button {
|
|
1593
|
+
display: block;
|
|
1594
|
+
width: 100%;
|
|
1595
|
+
margin-top: 16px;
|
|
1596
|
+
padding: 10px;
|
|
1597
|
+
background: rgba(239, 68, 68, 0.1);
|
|
1598
|
+
border: 1px solid rgba(239, 68, 68, 0.2);
|
|
1599
|
+
border-radius: 6px;
|
|
1600
|
+
color: #f87171;
|
|
1601
|
+
font-size: 14px;
|
|
1602
|
+
font-weight: 500;
|
|
1603
|
+
cursor: pointer;
|
|
1604
|
+
transition: all 0.15s ease;
|
|
1605
|
+
}
|
|
1606
|
+
|
|
1607
|
+
.clear-all-button:hover {
|
|
1608
|
+
background: rgba(239, 68, 68, 0.2);
|
|
1609
|
+
border-color: rgba(239, 68, 68, 0.3);
|
|
1610
|
+
}
|
|
1611
|
+
|
|
1612
|
+
/* Help tab */
|
|
1613
|
+
.help-section {
|
|
1614
|
+
margin-bottom: 20px;
|
|
1615
|
+
}
|
|
1616
|
+
|
|
1617
|
+
.help-section:last-child {
|
|
1618
|
+
margin-bottom: 0;
|
|
1619
|
+
}
|
|
1620
|
+
|
|
1621
|
+
.help-title {
|
|
1622
|
+
display: flex;
|
|
1623
|
+
align-items: center;
|
|
1624
|
+
gap: 8px;
|
|
1625
|
+
font-size: 16px;
|
|
1626
|
+
font-weight: 600;
|
|
1627
|
+
color: white;
|
|
1628
|
+
margin-bottom: 10px;
|
|
1629
|
+
}
|
|
1630
|
+
|
|
1631
|
+
.help-title.dictate {
|
|
1632
|
+
color: #34d399;
|
|
1633
|
+
}
|
|
1634
|
+
|
|
1635
|
+
.help-title.edit {
|
|
1636
|
+
color: #a78bfa;
|
|
1637
|
+
}
|
|
1638
|
+
|
|
1639
|
+
.help-text {
|
|
1640
|
+
font-size: 15px;
|
|
1641
|
+
color: rgba(255, 255, 255, 0.7);
|
|
1642
|
+
line-height: 1.5;
|
|
1643
|
+
margin-bottom: 10px;
|
|
1644
|
+
}
|
|
1645
|
+
|
|
1646
|
+
.help-example {
|
|
1647
|
+
background: rgba(255, 255, 255, 0.05);
|
|
1648
|
+
border-radius: 6px;
|
|
1649
|
+
padding: 12px 14px;
|
|
1650
|
+
font-size: 14px;
|
|
1651
|
+
color: rgba(255, 255, 255, 0.6);
|
|
1652
|
+
font-style: italic;
|
|
1653
|
+
}
|
|
1654
|
+
|
|
1655
|
+
/* About tab */
|
|
1656
|
+
.about-logo {
|
|
1657
|
+
display: flex;
|
|
1658
|
+
align-items: center;
|
|
1659
|
+
gap: 8px;
|
|
1660
|
+
margin-bottom: 16px;
|
|
1661
|
+
}
|
|
1662
|
+
|
|
1663
|
+
.about-logo-text {
|
|
1664
|
+
font-size: 20px;
|
|
1665
|
+
font-weight: 700;
|
|
1666
|
+
color: white;
|
|
1667
|
+
}
|
|
1668
|
+
|
|
1669
|
+
.about-description {
|
|
1670
|
+
font-size: 15px;
|
|
1671
|
+
color: rgba(255, 255, 255, 0.7);
|
|
1672
|
+
line-height: 1.6;
|
|
1673
|
+
margin-bottom: 20px;
|
|
1674
|
+
}
|
|
1675
|
+
|
|
1676
|
+
.about-link {
|
|
1677
|
+
display: inline-flex;
|
|
1678
|
+
align-items: center;
|
|
1679
|
+
gap: 6px;
|
|
1680
|
+
padding: 10px 14px;
|
|
1681
|
+
background: var(--speechos-primary);
|
|
1682
|
+
border-radius: 6px;
|
|
1683
|
+
color: white;
|
|
1684
|
+
font-size: 14px;
|
|
1685
|
+
font-weight: 500;
|
|
1686
|
+
text-decoration: none;
|
|
1687
|
+
transition: all 0.15s ease;
|
|
1688
|
+
}
|
|
1689
|
+
|
|
1690
|
+
.about-link:hover {
|
|
1691
|
+
background: var(--speechos-primary-active);
|
|
1692
|
+
}
|
|
1693
|
+
|
|
1694
|
+
.about-version {
|
|
1695
|
+
margin-top: 20px;
|
|
1696
|
+
font-size: 13px;
|
|
1697
|
+
color: rgba(255, 255, 255, 0.4);
|
|
1698
|
+
}
|
|
1699
|
+
|
|
1700
|
+
/* Settings tab */
|
|
1701
|
+
.settings-placeholder {
|
|
1702
|
+
text-align: center;
|
|
1703
|
+
padding: 32px 16px;
|
|
1704
|
+
color: rgba(255, 255, 255, 0.5);
|
|
1705
|
+
}
|
|
1706
|
+
|
|
1707
|
+
.settings-placeholder-icon {
|
|
1708
|
+
margin-bottom: 12px;
|
|
1709
|
+
opacity: 0.5;
|
|
1710
|
+
}
|
|
1711
|
+
|
|
1712
|
+
.settings-placeholder-text {
|
|
1713
|
+
font-size: 15px;
|
|
1714
|
+
}
|
|
1715
|
+
`,
|
|
1716
|
+
]; }
|
|
1717
|
+
connectedCallback() {
|
|
1718
|
+
super.connectedCallback();
|
|
1719
|
+
this.loadTranscripts();
|
|
1720
|
+
}
|
|
1721
|
+
updated(changedProperties) {
|
|
1722
|
+
if (changedProperties.has("open") && this.open) {
|
|
1723
|
+
this.loadTranscripts();
|
|
1724
|
+
}
|
|
1725
|
+
}
|
|
1726
|
+
loadTranscripts() {
|
|
1727
|
+
this.transcripts = core.transcriptStore.getTranscripts();
|
|
1728
|
+
}
|
|
1729
|
+
handleOverlayClick(e) {
|
|
1730
|
+
if (e.target === e.currentTarget) {
|
|
1731
|
+
this.close();
|
|
1732
|
+
}
|
|
1733
|
+
}
|
|
1734
|
+
handleClose() {
|
|
1735
|
+
this.close();
|
|
1736
|
+
}
|
|
1737
|
+
close() {
|
|
1738
|
+
this.dispatchEvent(new CustomEvent("modal-close", {
|
|
1739
|
+
bubbles: true,
|
|
1740
|
+
composed: true,
|
|
1741
|
+
}));
|
|
1742
|
+
}
|
|
1743
|
+
handleTabClick(tabId) {
|
|
1744
|
+
this.activeTab = tabId;
|
|
1745
|
+
if (tabId === "transcripts") {
|
|
1746
|
+
this.loadTranscripts();
|
|
1747
|
+
}
|
|
1748
|
+
}
|
|
1749
|
+
handleDeleteTranscript(id) {
|
|
1750
|
+
core.transcriptStore.deleteTranscript(id);
|
|
1751
|
+
this.loadTranscripts();
|
|
1752
|
+
}
|
|
1753
|
+
handleClearAll() {
|
|
1754
|
+
core.transcriptStore.clearTranscripts();
|
|
1755
|
+
this.loadTranscripts();
|
|
1756
|
+
}
|
|
1757
|
+
async handleCopyTranscript(text, event) {
|
|
1758
|
+
const button = event.currentTarget;
|
|
1759
|
+
try {
|
|
1760
|
+
await navigator.clipboard.writeText(text);
|
|
1761
|
+
}
|
|
1762
|
+
catch {
|
|
1763
|
+
// Fallback for older browsers
|
|
1764
|
+
const textarea = document.createElement("textarea");
|
|
1765
|
+
textarea.value = text;
|
|
1766
|
+
textarea.style.position = "fixed";
|
|
1767
|
+
textarea.style.opacity = "0";
|
|
1768
|
+
document.body.appendChild(textarea);
|
|
1769
|
+
textarea.select();
|
|
1770
|
+
document.execCommand("copy");
|
|
1771
|
+
document.body.removeChild(textarea);
|
|
1772
|
+
}
|
|
1773
|
+
// Show copied feedback
|
|
1774
|
+
button.classList.add("copied");
|
|
1775
|
+
button.textContent = "Copied!";
|
|
1776
|
+
setTimeout(() => {
|
|
1777
|
+
button.classList.remove("copied");
|
|
1778
|
+
this.requestUpdate(); // Re-render to restore button content
|
|
1779
|
+
}, 1000);
|
|
1780
|
+
}
|
|
1781
|
+
formatTime(timestamp) {
|
|
1782
|
+
const date = new Date(timestamp);
|
|
1783
|
+
const now = new Date();
|
|
1784
|
+
const diffMs = now.getTime() - date.getTime();
|
|
1785
|
+
const diffMins = Math.floor(diffMs / 60000);
|
|
1786
|
+
const diffHours = Math.floor(diffMs / 3600000);
|
|
1787
|
+
const diffDays = Math.floor(diffMs / 86400000);
|
|
1788
|
+
if (diffMins < 1)
|
|
1789
|
+
return "Just now";
|
|
1790
|
+
if (diffMins < 60)
|
|
1791
|
+
return `${diffMins}m ago`;
|
|
1792
|
+
if (diffHours < 24)
|
|
1793
|
+
return `${diffHours}h ago`;
|
|
1794
|
+
if (diffDays < 7)
|
|
1795
|
+
return `${diffDays}d ago`;
|
|
1796
|
+
return date.toLocaleDateString();
|
|
1797
|
+
}
|
|
1798
|
+
renderTranscriptsTab() {
|
|
1799
|
+
if (this.transcripts.length === 0) {
|
|
1800
|
+
return b `
|
|
1801
|
+
<div class="empty-state">
|
|
1802
|
+
<div class="empty-state-icon">${clipboardIcon(40)}</div>
|
|
1803
|
+
<div class="empty-state-title">No transcripts yet</div>
|
|
1804
|
+
<div class="empty-state-text">
|
|
1805
|
+
Your dictation and edit history will appear here.
|
|
1806
|
+
</div>
|
|
1807
|
+
</div>
|
|
1808
|
+
`;
|
|
1809
|
+
}
|
|
1810
|
+
return b `
|
|
1811
|
+
<div class="transcripts-list">
|
|
1812
|
+
${this.transcripts.map((t) => b `
|
|
1813
|
+
<div class="transcript-item">
|
|
1814
|
+
<div class="transcript-header">
|
|
1815
|
+
<span class="transcript-badge ${t.action}">
|
|
1816
|
+
${t.action === "dictate" ? dictateIcon(14) : editIcon(14)}
|
|
1817
|
+
${t.action}
|
|
1818
|
+
</span>
|
|
1819
|
+
<span class="transcript-time"
|
|
1820
|
+
>${this.formatTime(t.timestamp)}</span
|
|
1821
|
+
>
|
|
1822
|
+
</div>
|
|
1823
|
+
<div class="transcript-text">${t.text}</div>
|
|
1824
|
+
<div class="transcript-actions">
|
|
1825
|
+
<button
|
|
1826
|
+
class="transcript-action-btn copy"
|
|
1827
|
+
@click="${(e) => this.handleCopyTranscript(t.text, e)}"
|
|
1828
|
+
>
|
|
1829
|
+
${copyIcon(14)} Copy
|
|
1830
|
+
</button>
|
|
1831
|
+
<button
|
|
1832
|
+
class="transcript-action-btn delete"
|
|
1833
|
+
@click="${() => this.handleDeleteTranscript(t.id)}"
|
|
1834
|
+
>
|
|
1835
|
+
${trashIcon(14)} Delete
|
|
1836
|
+
</button>
|
|
1837
|
+
</div>
|
|
1838
|
+
</div>
|
|
1839
|
+
`)}
|
|
1840
|
+
</div>
|
|
1841
|
+
<button class="clear-all-button" @click="${this.handleClearAll}">
|
|
1842
|
+
Clear All History
|
|
1843
|
+
</button>
|
|
1844
|
+
`;
|
|
1845
|
+
}
|
|
1846
|
+
renderHelpTab() {
|
|
1847
|
+
return b `
|
|
1848
|
+
<div class="help-section">
|
|
1849
|
+
<div class="help-title dictate">${dictateIcon(18)} Dictate</div>
|
|
1850
|
+
<div class="help-text">
|
|
1851
|
+
Speak naturally and your words will be transcribed into text. Perfect
|
|
1852
|
+
for notes, messages, and documents.
|
|
1853
|
+
</div>
|
|
1854
|
+
<div class="help-example">
|
|
1855
|
+
"Hello, this is a test of the dictation feature."
|
|
1856
|
+
</div>
|
|
1857
|
+
</div>
|
|
1858
|
+
|
|
1859
|
+
<div class="help-section">
|
|
1860
|
+
<div class="help-title edit">${editIcon(18)} Edit</div>
|
|
1861
|
+
<div class="help-text">
|
|
1862
|
+
Select text first, then use your voice to describe how to change it.
|
|
1863
|
+
Great for rewriting, formatting, or fixing text.
|
|
1864
|
+
</div>
|
|
1865
|
+
<div class="help-example">
|
|
1866
|
+
"Make this more formal" or "Fix the grammar"
|
|
1867
|
+
</div>
|
|
1868
|
+
</div>
|
|
1869
|
+
`;
|
|
1870
|
+
}
|
|
1871
|
+
renderAboutTab() {
|
|
1872
|
+
return b `
|
|
1873
|
+
<div class="about-logo">
|
|
1874
|
+
<div class="about-logo-text">SpeechOS</div>
|
|
1875
|
+
</div>
|
|
1876
|
+
<div class="about-description">
|
|
1877
|
+
SpeechOS is a voice-to-text SDK that enables voice input for web
|
|
1878
|
+
applications. Speak instead of type to save time on text entry and
|
|
1879
|
+
editing tasks.
|
|
1880
|
+
</div>
|
|
1881
|
+
<a
|
|
1882
|
+
href="https://speechos.io"
|
|
1883
|
+
target="_blank"
|
|
1884
|
+
rel="noopener noreferrer"
|
|
1885
|
+
class="about-link"
|
|
1886
|
+
>
|
|
1887
|
+
Visit SpeechOS ${externalLinkIcon(14)}
|
|
1888
|
+
</a>
|
|
1889
|
+
<div class="about-version">Version 0.1.0</div>
|
|
1890
|
+
`;
|
|
1891
|
+
}
|
|
1892
|
+
renderSettingsTab() {
|
|
1893
|
+
return b `
|
|
1894
|
+
<div class="settings-placeholder">
|
|
1895
|
+
<div class="settings-placeholder-icon">${settingsIcon(40)}</div>
|
|
1896
|
+
<div class="settings-placeholder-text">Settings coming soon</div>
|
|
1897
|
+
</div>
|
|
1898
|
+
`;
|
|
1899
|
+
}
|
|
1900
|
+
renderTabContent() {
|
|
1901
|
+
switch (this.activeTab) {
|
|
1902
|
+
case "transcripts":
|
|
1903
|
+
return this.renderTranscriptsTab();
|
|
1904
|
+
case "help":
|
|
1905
|
+
return this.renderHelpTab();
|
|
1906
|
+
case "about":
|
|
1907
|
+
return this.renderAboutTab();
|
|
1908
|
+
case "settings":
|
|
1909
|
+
return this.renderSettingsTab();
|
|
1910
|
+
}
|
|
1911
|
+
}
|
|
1912
|
+
render() {
|
|
1913
|
+
return b `
|
|
1914
|
+
<div
|
|
1915
|
+
class="modal-overlay ${this.open ? "open" : ""}"
|
|
1916
|
+
@click="${this.handleOverlayClick}"
|
|
1917
|
+
>
|
|
1918
|
+
<div class="modal">
|
|
1919
|
+
<div class="modal-header">
|
|
1920
|
+
<h2 class="modal-title">SpeechOS</h2>
|
|
1921
|
+
<button
|
|
1922
|
+
class="close-button"
|
|
1923
|
+
@click="${this.handleClose}"
|
|
1924
|
+
aria-label="Close"
|
|
1925
|
+
>
|
|
1926
|
+
${xIcon(16)}
|
|
1927
|
+
</button>
|
|
1928
|
+
</div>
|
|
1929
|
+
|
|
1930
|
+
<div class="tabs">
|
|
1931
|
+
${this.tabs.map((tab) => b `
|
|
1932
|
+
<button
|
|
1933
|
+
class="tab ${this.activeTab === tab.id ? "active" : ""}"
|
|
1934
|
+
@click="${() => this.handleTabClick(tab.id)}"
|
|
1935
|
+
>
|
|
1936
|
+
${tab.icon}
|
|
1937
|
+
<span class="tab-label">${tab.label}</span>
|
|
1938
|
+
</button>
|
|
1939
|
+
`)}
|
|
1940
|
+
</div>
|
|
1941
|
+
|
|
1942
|
+
<div class="modal-content">${this.renderTabContent()}</div>
|
|
1943
|
+
</div>
|
|
1944
|
+
</div>
|
|
1945
|
+
`;
|
|
1946
|
+
}
|
|
1947
|
+
};
|
|
1948
|
+
__decorate([
|
|
1949
|
+
n({ type: Boolean })
|
|
1950
|
+
], SpeechOSSettingsModal.prototype, "open", void 0);
|
|
1951
|
+
__decorate([
|
|
1952
|
+
r()
|
|
1953
|
+
], SpeechOSSettingsModal.prototype, "activeTab", void 0);
|
|
1954
|
+
__decorate([
|
|
1955
|
+
r()
|
|
1956
|
+
], SpeechOSSettingsModal.prototype, "transcripts", void 0);
|
|
1957
|
+
SpeechOSSettingsModal = __decorate([
|
|
1958
|
+
t$1("speechos-settings-modal")
|
|
1959
|
+
], SpeechOSSettingsModal);
|
|
1960
|
+
|
|
1961
|
+
/**
|
|
1962
|
+
* Main widget container component
|
|
1963
|
+
* Composes mic button and action bubbles with state management
|
|
1964
|
+
*/
|
|
1965
|
+
var SpeechOSWidget_1;
|
|
1966
|
+
let SpeechOSWidget = class SpeechOSWidget extends i$1 {
|
|
1967
|
+
constructor() {
|
|
1968
|
+
super(...arguments);
|
|
1969
|
+
this.widgetState = core.state.getState();
|
|
1970
|
+
this.settingsOpen = false;
|
|
1971
|
+
this.dictationTargetElement = null;
|
|
1972
|
+
this.editTargetElement = null;
|
|
1973
|
+
this.dictationCursorStart = null;
|
|
1974
|
+
this.dictationCursorEnd = null;
|
|
1975
|
+
this.editSelectionStart = null;
|
|
1976
|
+
this.editSelectionEnd = null;
|
|
1977
|
+
this.editSelectedText = "";
|
|
1978
|
+
this.boundClickOutsideHandler = null;
|
|
1979
|
+
this.modalElement = null;
|
|
1980
|
+
this.customPosition = null;
|
|
1981
|
+
this.isDragging = false;
|
|
1982
|
+
this.dragStartPos = null;
|
|
1983
|
+
this.dragOffset = { x: 0, y: 0 };
|
|
1984
|
+
this.boundDragMove = null;
|
|
1985
|
+
this.boundDragEnd = null;
|
|
1986
|
+
this.suppressNextClick = false;
|
|
1987
|
+
}
|
|
1988
|
+
static { SpeechOSWidget_1 = this; }
|
|
1989
|
+
static { this.styles = [
|
|
1990
|
+
themeStyles,
|
|
1991
|
+
animations,
|
|
1992
|
+
i$4 `
|
|
1993
|
+
:host {
|
|
1994
|
+
position: fixed;
|
|
1995
|
+
bottom: 8px;
|
|
1996
|
+
z-index: var(--speechos-z-base);
|
|
1997
|
+
pointer-events: none;
|
|
1998
|
+
}
|
|
1999
|
+
|
|
2000
|
+
:host([position="bottom-center"]) {
|
|
2001
|
+
left: 50%;
|
|
2002
|
+
transform: translateX(-50%);
|
|
2003
|
+
}
|
|
2004
|
+
|
|
2005
|
+
:host([position="bottom-right"]) {
|
|
2006
|
+
right: var(--speechos-spacing-xl);
|
|
2007
|
+
}
|
|
2008
|
+
|
|
2009
|
+
:host([position="bottom-left"]) {
|
|
2010
|
+
left: var(--speechos-spacing-xl);
|
|
2011
|
+
}
|
|
2012
|
+
|
|
2013
|
+
:host(.custom-position) {
|
|
2014
|
+
right: unset;
|
|
2015
|
+
transform: translateX(-50%);
|
|
2016
|
+
}
|
|
2017
|
+
|
|
2018
|
+
:host([hidden]) {
|
|
2019
|
+
display: none;
|
|
2020
|
+
}
|
|
2021
|
+
|
|
2022
|
+
.widget-container {
|
|
2023
|
+
display: flex;
|
|
2024
|
+
flex-direction: column;
|
|
2025
|
+
align-items: center;
|
|
2026
|
+
pointer-events: auto;
|
|
2027
|
+
animation: speechos-fadeIn var(--speechos-transition-base);
|
|
2028
|
+
cursor: grab;
|
|
2029
|
+
}
|
|
2030
|
+
|
|
2031
|
+
.widget-container:active {
|
|
2032
|
+
cursor: grabbing;
|
|
2033
|
+
}
|
|
2034
|
+
|
|
2035
|
+
.widget-container.hiding {
|
|
2036
|
+
animation: speechos-fadeOut var(--speechos-transition-base);
|
|
2037
|
+
}
|
|
2038
|
+
|
|
2039
|
+
.settings-button-container {
|
|
2040
|
+
margin-top: var(--speechos-spacing-md);
|
|
2041
|
+
}
|
|
2042
|
+
`,
|
|
2043
|
+
]; }
|
|
2044
|
+
static { this.DRAG_THRESHOLD = 5; }
|
|
2045
|
+
connectedCallback() {
|
|
2046
|
+
super.connectedCallback();
|
|
2047
|
+
this.modalElement = document.createElement("speechos-settings-modal");
|
|
2048
|
+
this.modalElement.addEventListener("modal-close", () => {
|
|
2049
|
+
this.settingsOpen = false;
|
|
2050
|
+
});
|
|
2051
|
+
document.body.appendChild(this.modalElement);
|
|
2052
|
+
this.stateUnsubscribe = core.state.subscribe((newState) => {
|
|
2053
|
+
if (!newState.isVisible || !newState.isExpanded) {
|
|
2054
|
+
this.settingsOpen = false;
|
|
2055
|
+
}
|
|
2056
|
+
this.widgetState = newState;
|
|
2057
|
+
this.updatePosition();
|
|
2058
|
+
});
|
|
2059
|
+
this.errorEventUnsubscribe = core.events.on("error", (payload) => {
|
|
2060
|
+
if (this.widgetState.recordingState !== "idle" &&
|
|
2061
|
+
this.widgetState.recordingState !== "error") {
|
|
2062
|
+
core.state.setError(payload.message);
|
|
2063
|
+
core.livekit.disconnect().catch(() => { });
|
|
2064
|
+
}
|
|
2065
|
+
});
|
|
2066
|
+
this.updatePosition();
|
|
2067
|
+
this.boundClickOutsideHandler = this.handleClickOutside.bind(this);
|
|
2068
|
+
document.addEventListener("click", this.boundClickOutsideHandler, true);
|
|
2069
|
+
}
|
|
2070
|
+
disconnectedCallback() {
|
|
2071
|
+
super.disconnectedCallback();
|
|
2072
|
+
if (this.modalElement) {
|
|
2073
|
+
this.modalElement.remove();
|
|
2074
|
+
this.modalElement = null;
|
|
2075
|
+
}
|
|
2076
|
+
if (this.stateUnsubscribe) {
|
|
2077
|
+
this.stateUnsubscribe();
|
|
2078
|
+
}
|
|
2079
|
+
if (this.errorEventUnsubscribe) {
|
|
2080
|
+
this.errorEventUnsubscribe();
|
|
2081
|
+
}
|
|
2082
|
+
if (this.boundClickOutsideHandler) {
|
|
2083
|
+
document.removeEventListener("click", this.boundClickOutsideHandler, true);
|
|
2084
|
+
this.boundClickOutsideHandler = null;
|
|
2085
|
+
}
|
|
2086
|
+
if (this.boundDragMove) {
|
|
2087
|
+
document.removeEventListener("mousemove", this.boundDragMove);
|
|
2088
|
+
this.boundDragMove = null;
|
|
2089
|
+
}
|
|
2090
|
+
if (this.boundDragEnd) {
|
|
2091
|
+
document.removeEventListener("mouseup", this.boundDragEnd);
|
|
2092
|
+
this.boundDragEnd = null;
|
|
2093
|
+
}
|
|
2094
|
+
}
|
|
2095
|
+
updated(changedProperties) {
|
|
2096
|
+
if (changedProperties.has("settingsOpen") && this.modalElement) {
|
|
2097
|
+
this.modalElement.open = this.settingsOpen;
|
|
2098
|
+
}
|
|
2099
|
+
}
|
|
2100
|
+
handleClickOutside(event) {
|
|
2101
|
+
if (!this.widgetState.isExpanded || this.widgetState.recordingState !== "idle") {
|
|
2102
|
+
return;
|
|
2103
|
+
}
|
|
2104
|
+
const target = event.target;
|
|
2105
|
+
const composedPath = event.composedPath ? event.composedPath() : [];
|
|
2106
|
+
const clickedInWidget = this.contains(target) || composedPath.includes(this);
|
|
2107
|
+
const clickedInModal = this.modalElement && (this.modalElement.contains(target) || composedPath.includes(this.modalElement));
|
|
2108
|
+
if (this.settingsOpen && clickedInModal) {
|
|
2109
|
+
return;
|
|
2110
|
+
}
|
|
2111
|
+
if (this.settingsOpen && !clickedInModal) {
|
|
2112
|
+
this.settingsOpen = false;
|
|
2113
|
+
return;
|
|
2114
|
+
}
|
|
2115
|
+
if (!clickedInWidget && this.isFormField(target)) {
|
|
2116
|
+
core.state.setFocusedElement(target);
|
|
2117
|
+
return;
|
|
2118
|
+
}
|
|
2119
|
+
if (!clickedInWidget) {
|
|
2120
|
+
core.state.hide();
|
|
2121
|
+
}
|
|
2122
|
+
}
|
|
2123
|
+
isFormField(element) {
|
|
2124
|
+
if (!element || !(element instanceof HTMLElement)) {
|
|
2125
|
+
return false;
|
|
2126
|
+
}
|
|
2127
|
+
const tagName = element.tagName.toLowerCase();
|
|
2128
|
+
if (tagName === "input" || tagName === "textarea") {
|
|
2129
|
+
if (tagName === "input") {
|
|
2130
|
+
const type = element.type.toLowerCase();
|
|
2131
|
+
const excludedTypes = ["checkbox", "radio", "submit", "button", "reset", "file", "hidden"];
|
|
2132
|
+
if (excludedTypes.includes(type)) {
|
|
2133
|
+
return false;
|
|
2134
|
+
}
|
|
2135
|
+
}
|
|
2136
|
+
return true;
|
|
2137
|
+
}
|
|
2138
|
+
if (element.isContentEditable || element.getAttribute("contenteditable") === "true") {
|
|
2139
|
+
return true;
|
|
2140
|
+
}
|
|
2141
|
+
return false;
|
|
2142
|
+
}
|
|
2143
|
+
updatePosition() {
|
|
2144
|
+
const config = core.getConfig();
|
|
2145
|
+
this.setAttribute("position", config.position);
|
|
2146
|
+
this.style.zIndex = String(config.zIndex);
|
|
2147
|
+
}
|
|
2148
|
+
handleMicClick() {
|
|
2149
|
+
if (this.suppressNextClick) {
|
|
2150
|
+
this.suppressNextClick = false;
|
|
2151
|
+
return;
|
|
2152
|
+
}
|
|
2153
|
+
if (this.widgetState.recordingState === "idle") {
|
|
2154
|
+
const wasExpanded = this.widgetState.isExpanded;
|
|
2155
|
+
core.state.toggleExpanded();
|
|
2156
|
+
if (!wasExpanded) {
|
|
2157
|
+
core.livekit.preWarm().catch(() => { });
|
|
2158
|
+
}
|
|
2159
|
+
}
|
|
2160
|
+
}
|
|
2161
|
+
async handleStopRecording() {
|
|
2162
|
+
if (this.widgetState.activeAction === "edit") {
|
|
2163
|
+
await this.handleStopEdit();
|
|
2164
|
+
}
|
|
2165
|
+
else {
|
|
2166
|
+
core.state.stopRecording();
|
|
2167
|
+
core.getConfig();
|
|
2168
|
+
try {
|
|
2169
|
+
const transcription = await this.withMinDisplayTime(core.livekit.stopVoiceSession(), 300);
|
|
2170
|
+
if (transcription) {
|
|
2171
|
+
this.insertTranscription(transcription);
|
|
2172
|
+
core.transcriptStore.saveTranscript(transcription, "dictate");
|
|
2173
|
+
}
|
|
2174
|
+
core.state.completeRecording();
|
|
2175
|
+
core.events.emit("action:select", { action: "dictate" });
|
|
2176
|
+
core.livekit.disconnect().catch(() => { });
|
|
2177
|
+
}
|
|
2178
|
+
catch (error) {
|
|
2179
|
+
const errorMessage = error instanceof Error ? error.message : "Failed to transcribe audio";
|
|
2180
|
+
if (errorMessage !== "Disconnected") {
|
|
2181
|
+
core.state.setError(errorMessage);
|
|
2182
|
+
core.livekit.disconnect().catch(() => { });
|
|
2183
|
+
}
|
|
2184
|
+
}
|
|
2185
|
+
}
|
|
2186
|
+
}
|
|
2187
|
+
async handleCancelOperation() {
|
|
2188
|
+
await core.livekit.disconnect();
|
|
2189
|
+
if (this.widgetState.recordingState === "error") {
|
|
2190
|
+
core.state.clearError();
|
|
2191
|
+
}
|
|
2192
|
+
else {
|
|
2193
|
+
core.state.cancelRecording();
|
|
2194
|
+
}
|
|
2195
|
+
this.dictationTargetElement = null;
|
|
2196
|
+
this.editTargetElement = null;
|
|
2197
|
+
this.dictationCursorStart = null;
|
|
2198
|
+
this.dictationCursorEnd = null;
|
|
2199
|
+
this.editSelectionStart = null;
|
|
2200
|
+
this.editSelectionEnd = null;
|
|
2201
|
+
this.editSelectedText = "";
|
|
2202
|
+
}
|
|
2203
|
+
async handleRetryConnection() {
|
|
2204
|
+
const action = this.widgetState.activeAction;
|
|
2205
|
+
core.state.clearError();
|
|
2206
|
+
if (action === "edit") {
|
|
2207
|
+
await this.startEdit();
|
|
2208
|
+
}
|
|
2209
|
+
else {
|
|
2210
|
+
await this.startDictation();
|
|
2211
|
+
}
|
|
2212
|
+
}
|
|
2213
|
+
handleCloseWidget() {
|
|
2214
|
+
core.state.hide();
|
|
2215
|
+
}
|
|
2216
|
+
handleSettingsClick() {
|
|
2217
|
+
this.settingsOpen = true;
|
|
2218
|
+
}
|
|
2219
|
+
handleDragStart(e) {
|
|
2220
|
+
if (e.button !== 0)
|
|
2221
|
+
return;
|
|
2222
|
+
const composedPath = e.composedPath();
|
|
2223
|
+
const isInteractiveElement = composedPath.some((el) => {
|
|
2224
|
+
if (!(el instanceof HTMLElement))
|
|
2225
|
+
return false;
|
|
2226
|
+
const tagName = el.tagName.toLowerCase();
|
|
2227
|
+
if (tagName === "speechos-action-bubbles" || tagName === "speechos-settings-button") {
|
|
2228
|
+
return true;
|
|
2229
|
+
}
|
|
2230
|
+
if (tagName === "button") {
|
|
2231
|
+
const className = el.className || "";
|
|
2232
|
+
if (!className.includes("mic-button")) {
|
|
2233
|
+
return true;
|
|
2234
|
+
}
|
|
2235
|
+
}
|
|
2236
|
+
return false;
|
|
2237
|
+
});
|
|
2238
|
+
if (isInteractiveElement)
|
|
2239
|
+
return;
|
|
2240
|
+
this.dragStartPos = { x: e.clientX, y: e.clientY };
|
|
2241
|
+
this.isDragging = false;
|
|
2242
|
+
const rect = this.getBoundingClientRect();
|
|
2243
|
+
this.dragOffset = {
|
|
2244
|
+
x: e.clientX - rect.left - rect.width / 2,
|
|
2245
|
+
y: rect.bottom - e.clientY,
|
|
2246
|
+
};
|
|
2247
|
+
this.boundDragMove = this.handleDragMove.bind(this);
|
|
2248
|
+
this.boundDragEnd = this.handleDragEnd.bind(this);
|
|
2249
|
+
document.addEventListener("mousemove", this.boundDragMove);
|
|
2250
|
+
document.addEventListener("mouseup", this.boundDragEnd);
|
|
2251
|
+
}
|
|
2252
|
+
handleDragMove(e) {
|
|
2253
|
+
if (!this.isDragging && this.dragStartPos) {
|
|
2254
|
+
const dx = e.clientX - this.dragStartPos.x;
|
|
2255
|
+
const dy = e.clientY - this.dragStartPos.y;
|
|
2256
|
+
const distance = Math.sqrt(dx * dx + dy * dy);
|
|
2257
|
+
if (distance >= SpeechOSWidget_1.DRAG_THRESHOLD) {
|
|
2258
|
+
this.isDragging = true;
|
|
2259
|
+
e.preventDefault();
|
|
2260
|
+
}
|
|
2261
|
+
else {
|
|
2262
|
+
return;
|
|
2263
|
+
}
|
|
2264
|
+
}
|
|
2265
|
+
if (!this.isDragging)
|
|
2266
|
+
return;
|
|
2267
|
+
e.preventDefault();
|
|
2268
|
+
this.customPosition = {
|
|
2269
|
+
x: e.clientX - this.dragOffset.x,
|
|
2270
|
+
y: window.innerHeight - e.clientY - this.dragOffset.y,
|
|
2271
|
+
};
|
|
2272
|
+
this.applyCustomPosition();
|
|
2273
|
+
}
|
|
2274
|
+
handleDragEnd() {
|
|
2275
|
+
if (this.isDragging) {
|
|
2276
|
+
this.suppressNextClick = true;
|
|
2277
|
+
}
|
|
2278
|
+
this.isDragging = false;
|
|
2279
|
+
this.dragStartPos = null;
|
|
2280
|
+
if (this.boundDragMove) {
|
|
2281
|
+
document.removeEventListener("mousemove", this.boundDragMove);
|
|
2282
|
+
this.boundDragMove = null;
|
|
2283
|
+
}
|
|
2284
|
+
if (this.boundDragEnd) {
|
|
2285
|
+
document.removeEventListener("mouseup", this.boundDragEnd);
|
|
2286
|
+
this.boundDragEnd = null;
|
|
2287
|
+
}
|
|
2288
|
+
}
|
|
2289
|
+
applyCustomPosition() {
|
|
2290
|
+
if (this.customPosition) {
|
|
2291
|
+
this.classList.add("custom-position");
|
|
2292
|
+
this.style.left = `${this.customPosition.x}px`;
|
|
2293
|
+
this.style.bottom = `${this.customPosition.y}px`;
|
|
2294
|
+
}
|
|
2295
|
+
}
|
|
2296
|
+
insertTranscription(text) {
|
|
2297
|
+
const target = this.dictationTargetElement;
|
|
2298
|
+
if (!target) {
|
|
2299
|
+
return;
|
|
2300
|
+
}
|
|
2301
|
+
const tagName = target.tagName.toLowerCase();
|
|
2302
|
+
if (tagName === "input" || tagName === "textarea") {
|
|
2303
|
+
const inputEl = target;
|
|
2304
|
+
const start = this.dictationCursorStart ?? inputEl.value.length;
|
|
2305
|
+
const end = this.dictationCursorEnd ?? inputEl.value.length;
|
|
2306
|
+
const before = inputEl.value.substring(0, start);
|
|
2307
|
+
const after = inputEl.value.substring(end);
|
|
2308
|
+
inputEl.value = before + text + after;
|
|
2309
|
+
if (this.supportsSelection(inputEl)) {
|
|
2310
|
+
const newCursorPos = start + text.length;
|
|
2311
|
+
inputEl.setSelectionRange(newCursorPos, newCursorPos);
|
|
2312
|
+
}
|
|
2313
|
+
inputEl.dispatchEvent(new Event("input", { bubbles: true }));
|
|
2314
|
+
inputEl.focus();
|
|
2315
|
+
core.state.setFocusedElement(inputEl);
|
|
2316
|
+
}
|
|
2317
|
+
else if (target.isContentEditable) {
|
|
2318
|
+
target.focus();
|
|
2319
|
+
core.state.setFocusedElement(target);
|
|
2320
|
+
const textNode = document.createTextNode(text);
|
|
2321
|
+
target.appendChild(textNode);
|
|
2322
|
+
const selection = window.getSelection();
|
|
2323
|
+
if (selection) {
|
|
2324
|
+
const range = document.createRange();
|
|
2325
|
+
range.selectNodeContents(textNode);
|
|
2326
|
+
range.collapse(false);
|
|
2327
|
+
selection.removeAllRanges();
|
|
2328
|
+
selection.addRange(range);
|
|
2329
|
+
}
|
|
2330
|
+
target.dispatchEvent(new Event("input", { bubbles: true }));
|
|
2331
|
+
}
|
|
2332
|
+
core.events.emit("transcription:inserted", { text, element: target });
|
|
2333
|
+
this.dictationTargetElement = null;
|
|
2334
|
+
this.dictationCursorStart = null;
|
|
2335
|
+
this.dictationCursorEnd = null;
|
|
2336
|
+
}
|
|
2337
|
+
handleActionSelect(event) {
|
|
2338
|
+
const { action } = event.detail;
|
|
2339
|
+
core.state.setActiveAction(action);
|
|
2340
|
+
if (action === "dictate") {
|
|
2341
|
+
this.startDictation();
|
|
2342
|
+
}
|
|
2343
|
+
else if (action === "edit") {
|
|
2344
|
+
this.startEdit();
|
|
2345
|
+
}
|
|
2346
|
+
else {
|
|
2347
|
+
setTimeout(() => {
|
|
2348
|
+
core.state.setState({ isExpanded: false, activeAction: null });
|
|
2349
|
+
}, 300);
|
|
2350
|
+
}
|
|
2351
|
+
}
|
|
2352
|
+
async withMinDisplayTime(operation, minTime) {
|
|
2353
|
+
const [result] = await Promise.all([operation, new Promise((resolve) => setTimeout(resolve, minTime))]);
|
|
2354
|
+
return result;
|
|
2355
|
+
}
|
|
2356
|
+
async startDictation() {
|
|
2357
|
+
this.dictationTargetElement = this.widgetState.focusedElement;
|
|
2358
|
+
this.dictationCursorStart = null;
|
|
2359
|
+
this.dictationCursorEnd = null;
|
|
2360
|
+
if (this.dictationTargetElement) {
|
|
2361
|
+
const tagName = this.dictationTargetElement.tagName.toLowerCase();
|
|
2362
|
+
if (tagName === "input" || tagName === "textarea") {
|
|
2363
|
+
const inputEl = this.dictationTargetElement;
|
|
2364
|
+
if (this.supportsSelection(inputEl)) {
|
|
2365
|
+
this.dictationCursorStart = inputEl.selectionStart;
|
|
2366
|
+
this.dictationCursorEnd = inputEl.selectionEnd;
|
|
2367
|
+
}
|
|
2368
|
+
else {
|
|
2369
|
+
this.dictationCursorStart = inputEl.value.length;
|
|
2370
|
+
this.dictationCursorEnd = inputEl.value.length;
|
|
2371
|
+
}
|
|
2372
|
+
}
|
|
2373
|
+
}
|
|
2374
|
+
core.state.startRecording();
|
|
2375
|
+
try {
|
|
2376
|
+
await core.livekit.startVoiceSession();
|
|
2377
|
+
core.state.setRecordingState("recording");
|
|
2378
|
+
}
|
|
2379
|
+
catch (error) {
|
|
2380
|
+
const errorMessage = error instanceof Error ? error.message : "Connection failed";
|
|
2381
|
+
if (errorMessage !== "Disconnected") {
|
|
2382
|
+
core.state.setError(`Failed to connect: ${errorMessage}`);
|
|
2383
|
+
await core.livekit.disconnect();
|
|
2384
|
+
}
|
|
2385
|
+
}
|
|
2386
|
+
}
|
|
2387
|
+
async startEdit() {
|
|
2388
|
+
this.editTargetElement = this.widgetState.focusedElement;
|
|
2389
|
+
this.editSelectionStart = null;
|
|
2390
|
+
this.editSelectionEnd = null;
|
|
2391
|
+
this.editSelectedText = "";
|
|
2392
|
+
if (this.editTargetElement) {
|
|
2393
|
+
const tagName = this.editTargetElement.tagName.toLowerCase();
|
|
2394
|
+
if (tagName === "input" || tagName === "textarea") {
|
|
2395
|
+
const inputEl = this.editTargetElement;
|
|
2396
|
+
if (this.supportsSelection(inputEl)) {
|
|
2397
|
+
this.editSelectionStart = inputEl.selectionStart;
|
|
2398
|
+
this.editSelectionEnd = inputEl.selectionEnd;
|
|
2399
|
+
const start = this.editSelectionStart ?? 0;
|
|
2400
|
+
const end = this.editSelectionEnd ?? 0;
|
|
2401
|
+
if (start !== end) {
|
|
2402
|
+
this.editSelectedText = inputEl.value.substring(start, end);
|
|
2403
|
+
}
|
|
2404
|
+
}
|
|
2405
|
+
else {
|
|
2406
|
+
this.editSelectionStart = 0;
|
|
2407
|
+
this.editSelectionEnd = 0;
|
|
2408
|
+
}
|
|
2409
|
+
}
|
|
2410
|
+
else if (this.editTargetElement.isContentEditable) {
|
|
2411
|
+
const selection = window.getSelection();
|
|
2412
|
+
if (selection && selection.rangeCount > 0) {
|
|
2413
|
+
const selectedText = selection.toString();
|
|
2414
|
+
this.editSelectionStart = 0;
|
|
2415
|
+
this.editSelectionEnd = selectedText.length;
|
|
2416
|
+
this.editSelectedText = selectedText;
|
|
2417
|
+
}
|
|
2418
|
+
}
|
|
2419
|
+
}
|
|
2420
|
+
core.state.startRecording();
|
|
2421
|
+
try {
|
|
2422
|
+
await core.livekit.startVoiceSession();
|
|
2423
|
+
core.state.setRecordingState("recording");
|
|
2424
|
+
}
|
|
2425
|
+
catch (error) {
|
|
2426
|
+
const errorMessage = error instanceof Error ? error.message : "Connection failed";
|
|
2427
|
+
if (errorMessage !== "Disconnected") {
|
|
2428
|
+
core.state.setError(`Failed to connect: ${errorMessage}`);
|
|
2429
|
+
await core.livekit.disconnect();
|
|
2430
|
+
}
|
|
2431
|
+
}
|
|
2432
|
+
}
|
|
2433
|
+
async handleStopEdit() {
|
|
2434
|
+
core.state.stopRecording();
|
|
2435
|
+
const originalContent = this.getElementContent(this.editTargetElement);
|
|
2436
|
+
try {
|
|
2437
|
+
const editedText = await this.withMinDisplayTime(core.livekit.requestEditText(originalContent), 300);
|
|
2438
|
+
this.applyEdit(editedText);
|
|
2439
|
+
core.livekit.disconnect().catch(() => { });
|
|
2440
|
+
}
|
|
2441
|
+
catch (error) {
|
|
2442
|
+
const errorMessage = error instanceof Error ? error.message : "Failed to apply edit";
|
|
2443
|
+
if (errorMessage !== "Disconnected") {
|
|
2444
|
+
core.state.setError(errorMessage);
|
|
2445
|
+
core.livekit.disconnect().catch(() => { });
|
|
2446
|
+
}
|
|
2447
|
+
}
|
|
2448
|
+
}
|
|
2449
|
+
supportsSelection(element) {
|
|
2450
|
+
if (element.tagName.toLowerCase() === "textarea") {
|
|
2451
|
+
return true;
|
|
2452
|
+
}
|
|
2453
|
+
const supportedTypes = ["text", "search", "url", "tel", "password"];
|
|
2454
|
+
return supportedTypes.includes(element.type || "text");
|
|
2455
|
+
}
|
|
2456
|
+
getElementContent(element) {
|
|
2457
|
+
if (!element) {
|
|
2458
|
+
return "";
|
|
2459
|
+
}
|
|
2460
|
+
const tagName = element.tagName.toLowerCase();
|
|
2461
|
+
if (tagName === "input" || tagName === "textarea") {
|
|
2462
|
+
const inputEl = element;
|
|
2463
|
+
const fullContent = inputEl.value;
|
|
2464
|
+
const start = this.editSelectionStart ?? 0;
|
|
2465
|
+
const end = this.editSelectionEnd ?? fullContent.length;
|
|
2466
|
+
const hasSelection = start !== end;
|
|
2467
|
+
if (hasSelection) {
|
|
2468
|
+
return fullContent.substring(start, end);
|
|
2469
|
+
}
|
|
2470
|
+
return fullContent;
|
|
2471
|
+
}
|
|
2472
|
+
else if (element.isContentEditable) {
|
|
2473
|
+
const selection = window.getSelection();
|
|
2474
|
+
if (selection && selection.toString().length > 0) {
|
|
2475
|
+
return selection.toString();
|
|
2476
|
+
}
|
|
2477
|
+
return element.textContent || "";
|
|
2478
|
+
}
|
|
2479
|
+
return "";
|
|
2480
|
+
}
|
|
2481
|
+
applyEdit(editedText) {
|
|
2482
|
+
const target = this.editTargetElement;
|
|
2483
|
+
if (!target) {
|
|
2484
|
+
core.state.completeRecording();
|
|
2485
|
+
return;
|
|
2486
|
+
}
|
|
2487
|
+
const tagName = target.tagName.toLowerCase();
|
|
2488
|
+
let originalContent = "";
|
|
2489
|
+
if (tagName === "input" || tagName === "textarea") {
|
|
2490
|
+
const inputEl = target;
|
|
2491
|
+
originalContent = inputEl.value;
|
|
2492
|
+
inputEl.focus();
|
|
2493
|
+
if (this.supportsSelection(inputEl)) {
|
|
2494
|
+
const selectionStart = this.editSelectionStart ?? 0;
|
|
2495
|
+
const selectionEnd = this.editSelectionEnd ?? inputEl.value.length;
|
|
2496
|
+
const hasSelection = selectionStart !== selectionEnd;
|
|
2497
|
+
if (hasSelection) {
|
|
2498
|
+
inputEl.setSelectionRange(selectionStart, selectionEnd);
|
|
2499
|
+
}
|
|
2500
|
+
else {
|
|
2501
|
+
inputEl.setSelectionRange(0, inputEl.value.length);
|
|
2502
|
+
}
|
|
2503
|
+
document.execCommand("insertText", false, editedText);
|
|
2504
|
+
}
|
|
2505
|
+
else {
|
|
2506
|
+
inputEl.value = editedText;
|
|
2507
|
+
inputEl.dispatchEvent(new Event("input", { bubbles: true }));
|
|
2508
|
+
}
|
|
2509
|
+
core.state.setFocusedElement(inputEl);
|
|
2510
|
+
}
|
|
2511
|
+
else if (target.isContentEditable) {
|
|
2512
|
+
originalContent = target.textContent || "";
|
|
2513
|
+
target.focus();
|
|
2514
|
+
core.state.setFocusedElement(target);
|
|
2515
|
+
const hasSelection = this.editSelectionStart !== null && this.editSelectionEnd !== null && this.editSelectionStart !== this.editSelectionEnd;
|
|
2516
|
+
if (!hasSelection) {
|
|
2517
|
+
const selection = window.getSelection();
|
|
2518
|
+
const range = document.createRange();
|
|
2519
|
+
range.selectNodeContents(target);
|
|
2520
|
+
selection?.removeAllRanges();
|
|
2521
|
+
selection?.addRange(range);
|
|
2522
|
+
}
|
|
2523
|
+
document.execCommand("insertText", false, editedText);
|
|
2524
|
+
}
|
|
2525
|
+
core.transcriptStore.saveTranscript(editedText, "edit", originalContent);
|
|
2526
|
+
core.events.emit("edit:applied", { originalContent, editedContent: editedText, element: target });
|
|
2527
|
+
core.state.completeRecording();
|
|
2528
|
+
this.editTargetElement = null;
|
|
2529
|
+
this.editSelectionStart = null;
|
|
2530
|
+
this.editSelectionEnd = null;
|
|
2531
|
+
this.editSelectedText = "";
|
|
2532
|
+
}
|
|
2533
|
+
render() {
|
|
2534
|
+
if (!this.widgetState.isVisible) {
|
|
2535
|
+
this.setAttribute("hidden", "");
|
|
2536
|
+
return b ``;
|
|
2537
|
+
}
|
|
2538
|
+
this.removeAttribute("hidden");
|
|
2539
|
+
const showBubbles = this.widgetState.isExpanded && this.widgetState.recordingState === "idle";
|
|
2540
|
+
return b `
|
|
2541
|
+
<div class="widget-container" @mousedown="${this.handleDragStart}">
|
|
2542
|
+
<speechos-action-bubbles ?visible="${showBubbles}" @action-select="${this.handleActionSelect}"></speechos-action-bubbles>
|
|
2543
|
+
<speechos-mic-button
|
|
2544
|
+
?expanded="${this.widgetState.isExpanded}"
|
|
2545
|
+
recordingState="${this.widgetState.recordingState}"
|
|
2546
|
+
activeAction="${this.widgetState.activeAction || ""}"
|
|
2547
|
+
editPreviewText="${this.editSelectedText}"
|
|
2548
|
+
errorMessage="${this.widgetState.errorMessage || ""}"
|
|
2549
|
+
@mic-click="${this.handleMicClick}"
|
|
2550
|
+
@stop-recording="${this.handleStopRecording}"
|
|
2551
|
+
@cancel-operation="${this.handleCancelOperation}"
|
|
2552
|
+
@retry-connection="${this.handleRetryConnection}"
|
|
2553
|
+
@close-widget="${this.handleCloseWidget}"
|
|
2554
|
+
></speechos-mic-button>
|
|
2555
|
+
<div class="settings-button-container">
|
|
2556
|
+
<speechos-settings-button ?visible="${showBubbles}" @settings-click="${this.handleSettingsClick}"></speechos-settings-button>
|
|
2557
|
+
</div>
|
|
2558
|
+
</div>
|
|
2559
|
+
`;
|
|
2560
|
+
}
|
|
2561
|
+
};
|
|
2562
|
+
__decorate([
|
|
2563
|
+
r()
|
|
2564
|
+
], SpeechOSWidget.prototype, "widgetState", void 0);
|
|
2565
|
+
__decorate([
|
|
2566
|
+
r()
|
|
2567
|
+
], SpeechOSWidget.prototype, "settingsOpen", void 0);
|
|
2568
|
+
SpeechOSWidget = SpeechOSWidget_1 = __decorate([
|
|
2569
|
+
t$1("speechos-widget")
|
|
2570
|
+
], SpeechOSWidget);
|
|
2571
|
+
|
|
2572
|
+
/**
|
|
2573
|
+
* Main SpeechOS Client SDK class
|
|
2574
|
+
* Composes core logic with UI components
|
|
2575
|
+
*/
|
|
2576
|
+
/**
|
|
2577
|
+
* Main SpeechOS class for initializing and managing the SDK with UI
|
|
2578
|
+
*/
|
|
2579
|
+
class SpeechOS {
|
|
2580
|
+
static { this.instance = null; }
|
|
2581
|
+
static { this.widgetElement = null; }
|
|
2582
|
+
static { this.isInitialized = false; }
|
|
2583
|
+
/**
|
|
2584
|
+
* Initialize the SpeechOS SDK
|
|
2585
|
+
* @param config - Configuration options
|
|
2586
|
+
* @returns Promise that resolves when initialization is complete
|
|
2587
|
+
* @throws Error if configuration is invalid (e.g., missing apiKey)
|
|
2588
|
+
*/
|
|
2589
|
+
static async init(config = {}) {
|
|
2590
|
+
if (this.isInitialized) {
|
|
2591
|
+
console.warn("SpeechOS is already initialized");
|
|
2592
|
+
return;
|
|
2593
|
+
}
|
|
2594
|
+
try {
|
|
2595
|
+
// Validate and set configuration
|
|
2596
|
+
core.setConfig(config);
|
|
2597
|
+
}
|
|
2598
|
+
catch (error) {
|
|
2599
|
+
// Configuration errors are fatal - log and re-throw
|
|
2600
|
+
const errorMessage = error instanceof Error ? error.message : "Invalid configuration";
|
|
2601
|
+
console.error(`[SpeechOS] Error: ${errorMessage} (init_config)`);
|
|
2602
|
+
// Emit error event before throwing
|
|
2603
|
+
core.events.emit("error", {
|
|
2604
|
+
code: "init_config",
|
|
2605
|
+
message: errorMessage,
|
|
2606
|
+
source: "init",
|
|
2607
|
+
});
|
|
2608
|
+
throw error;
|
|
2609
|
+
}
|
|
2610
|
+
const finalConfig = core.getConfig();
|
|
2611
|
+
// Create singleton instance
|
|
2612
|
+
this.instance = new SpeechOS();
|
|
2613
|
+
// Fetch LiveKit token
|
|
2614
|
+
try {
|
|
2615
|
+
if (finalConfig.debug) {
|
|
2616
|
+
console.log("[SpeechOS] Fetching LiveKit token...");
|
|
2617
|
+
}
|
|
2618
|
+
await core.livekit.fetchToken();
|
|
2619
|
+
if (finalConfig.debug) {
|
|
2620
|
+
console.log("[SpeechOS] LiveKit token fetched successfully");
|
|
2621
|
+
}
|
|
2622
|
+
}
|
|
2623
|
+
catch (error) {
|
|
2624
|
+
const errorMessage = error instanceof Error ? error.message : "Failed to fetch token";
|
|
2625
|
+
console.error(`[SpeechOS] Error: ${errorMessage} (init_token_fetch)`);
|
|
2626
|
+
// Emit error event for consumers
|
|
2627
|
+
core.events.emit("error", {
|
|
2628
|
+
code: "init_token_fetch",
|
|
2629
|
+
message: errorMessage,
|
|
2630
|
+
source: "init",
|
|
2631
|
+
});
|
|
2632
|
+
// Continue initialization even if token fetch fails
|
|
2633
|
+
// The token will be fetched again when needed
|
|
2634
|
+
}
|
|
2635
|
+
// Start form detection
|
|
2636
|
+
formDetector.start();
|
|
2637
|
+
// Create and mount widget
|
|
2638
|
+
this.mountWidget();
|
|
2639
|
+
this.isInitialized = true;
|
|
2640
|
+
// Log initialization in debug mode
|
|
2641
|
+
if (finalConfig.debug) {
|
|
2642
|
+
console.log("[SpeechOS] Initialized with config:", finalConfig);
|
|
2643
|
+
}
|
|
2644
|
+
}
|
|
2645
|
+
/**
|
|
2646
|
+
* Destroy the SpeechOS SDK and clean up resources
|
|
2647
|
+
*/
|
|
2648
|
+
static async destroy() {
|
|
2649
|
+
if (!this.isInitialized) {
|
|
2650
|
+
console.warn("SpeechOS is not initialized");
|
|
2651
|
+
return;
|
|
2652
|
+
}
|
|
2653
|
+
// Stop form detection
|
|
2654
|
+
formDetector.stop();
|
|
2655
|
+
// Disconnect from LiveKit (also clears token)
|
|
2656
|
+
await core.livekit.disconnect();
|
|
2657
|
+
// Remove widget from DOM
|
|
2658
|
+
this.unmountWidget();
|
|
2659
|
+
// Clear all event listeners
|
|
2660
|
+
core.events.clear();
|
|
2661
|
+
// Reset state
|
|
2662
|
+
core.state.reset();
|
|
2663
|
+
// Clear instance
|
|
2664
|
+
this.instance = null;
|
|
2665
|
+
this.isInitialized = false;
|
|
2666
|
+
const config = core.getConfig();
|
|
2667
|
+
if (config.debug) {
|
|
2668
|
+
console.log("[SpeechOS] Destroyed and cleaned up");
|
|
2669
|
+
}
|
|
2670
|
+
}
|
|
2671
|
+
/**
|
|
2672
|
+
* Check if SpeechOS is initialized
|
|
2673
|
+
*/
|
|
2674
|
+
static get initialized() {
|
|
2675
|
+
return this.isInitialized;
|
|
2676
|
+
}
|
|
2677
|
+
/**
|
|
2678
|
+
* Get the current state
|
|
2679
|
+
*/
|
|
2680
|
+
static getState() {
|
|
2681
|
+
return core.state.getState();
|
|
2682
|
+
}
|
|
2683
|
+
/**
|
|
2684
|
+
* Get the event emitter for external listeners
|
|
2685
|
+
*/
|
|
2686
|
+
static get events() {
|
|
2687
|
+
return core.events;
|
|
2688
|
+
}
|
|
2689
|
+
/**
|
|
2690
|
+
* Mount the widget to the DOM
|
|
2691
|
+
*/
|
|
2692
|
+
static mountWidget() {
|
|
2693
|
+
if (this.widgetElement) {
|
|
2694
|
+
console.warn("Widget is already mounted");
|
|
2695
|
+
return;
|
|
2696
|
+
}
|
|
2697
|
+
// Create widget element
|
|
2698
|
+
const widget = document.createElement("speechos-widget");
|
|
2699
|
+
this.widgetElement = widget;
|
|
2700
|
+
// Append to body
|
|
2701
|
+
document.body.appendChild(widget);
|
|
2702
|
+
}
|
|
2703
|
+
/**
|
|
2704
|
+
* Unmount the widget from the DOM
|
|
2705
|
+
*/
|
|
2706
|
+
static unmountWidget() {
|
|
2707
|
+
if (this.widgetElement) {
|
|
2708
|
+
this.widgetElement.remove();
|
|
2709
|
+
this.widgetElement = null;
|
|
2710
|
+
}
|
|
2711
|
+
}
|
|
2712
|
+
/**
|
|
2713
|
+
* Show the widget programmatically
|
|
2714
|
+
*/
|
|
2715
|
+
static show() {
|
|
2716
|
+
core.state.show();
|
|
2717
|
+
}
|
|
2718
|
+
/**
|
|
2719
|
+
* Hide the widget programmatically
|
|
2720
|
+
*/
|
|
2721
|
+
static hide() {
|
|
2722
|
+
core.state.hide();
|
|
2723
|
+
}
|
|
2724
|
+
/**
|
|
2725
|
+
* Identify the current user
|
|
2726
|
+
* Can be called after init() to associate sessions with a user identifier.
|
|
2727
|
+
* Clears the cached token so the next voice session uses the new userId.
|
|
2728
|
+
*
|
|
2729
|
+
* @param userId - User identifier from your system (e.g., user ID, email)
|
|
2730
|
+
*
|
|
2731
|
+
* @example
|
|
2732
|
+
* // Initialize SDK early
|
|
2733
|
+
* SpeechOS.init({ apiKey: 'xxx' });
|
|
2734
|
+
*
|
|
2735
|
+
* // Later, after user logs in
|
|
2736
|
+
* SpeechOS.identify('user_123');
|
|
2737
|
+
*/
|
|
2738
|
+
static identify(userId) {
|
|
2739
|
+
if (!this.isInitialized) {
|
|
2740
|
+
console.warn("SpeechOS.identify() called before init(). Call init() first.");
|
|
2741
|
+
return;
|
|
2742
|
+
}
|
|
2743
|
+
const config = core.getConfig();
|
|
2744
|
+
// Update the userId in config
|
|
2745
|
+
core.updateUserId(userId);
|
|
2746
|
+
// Clear cached token so next session gets a fresh one with the new userId
|
|
2747
|
+
core.livekit.clearToken();
|
|
2748
|
+
if (config.debug) {
|
|
2749
|
+
console.log(`[SpeechOS] User identified: ${userId}`);
|
|
2750
|
+
}
|
|
2751
|
+
}
|
|
2752
|
+
/**
|
|
2753
|
+
* Private constructor to prevent direct instantiation
|
|
2754
|
+
*/
|
|
2755
|
+
constructor() {
|
|
2756
|
+
// Singleton pattern - use SpeechOS.init() instead
|
|
2757
|
+
}
|
|
2758
|
+
}
|
|
2759
|
+
|
|
2760
|
+
/**
|
|
2761
|
+
* @speechos/client
|
|
2762
|
+
*
|
|
2763
|
+
* Vanilla JS client SDK for embedding SpeechOS into web applications.
|
|
2764
|
+
* Includes Web Components UI and DOM-based form detection.
|
|
2765
|
+
*/
|
|
2766
|
+
// Version
|
|
2767
|
+
const VERSION = "0.1.0";
|
|
2768
|
+
|
|
2769
|
+
Object.defineProperty(exports, "DEFAULT_HOST", {
|
|
2770
|
+
enumerable: true,
|
|
2771
|
+
get: function () { return core.DEFAULT_HOST; }
|
|
2772
|
+
});
|
|
2773
|
+
Object.defineProperty(exports, "events", {
|
|
2774
|
+
enumerable: true,
|
|
2775
|
+
get: function () { return core.events; }
|
|
2776
|
+
});
|
|
2777
|
+
Object.defineProperty(exports, "getConfig", {
|
|
2778
|
+
enumerable: true,
|
|
2779
|
+
get: function () { return core.getConfig; }
|
|
2780
|
+
});
|
|
2781
|
+
Object.defineProperty(exports, "livekit", {
|
|
2782
|
+
enumerable: true,
|
|
2783
|
+
get: function () { return core.livekit; }
|
|
2784
|
+
});
|
|
2785
|
+
Object.defineProperty(exports, "resetConfig", {
|
|
2786
|
+
enumerable: true,
|
|
2787
|
+
get: function () { return core.resetConfig; }
|
|
334
2788
|
});
|
|
335
|
-
|
|
2789
|
+
Object.defineProperty(exports, "setConfig", {
|
|
2790
|
+
enumerable: true,
|
|
2791
|
+
get: function () { return core.setConfig; }
|
|
2792
|
+
});
|
|
2793
|
+
Object.defineProperty(exports, "state", {
|
|
2794
|
+
enumerable: true,
|
|
2795
|
+
get: function () { return core.state; }
|
|
2796
|
+
});
|
|
2797
|
+
Object.defineProperty(exports, "transcriptStore", {
|
|
2798
|
+
enumerable: true,
|
|
2799
|
+
get: function () { return core.transcriptStore; }
|
|
2800
|
+
});
|
|
2801
|
+
exports.FormDetector = FormDetector;
|
|
2802
|
+
exports.SpeechOS = SpeechOS;
|
|
2803
|
+
exports.VERSION = VERSION;
|
|
2804
|
+
exports.default = SpeechOS;
|
|
2805
|
+
exports.formDetector = formDetector;
|
|
2806
|
+
//# sourceMappingURL=index.cjs.map
|