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