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