@supernal/tts-widget 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md ADDED
@@ -0,0 +1,340 @@
1
+ # @supernal/tts-widget
2
+
3
+ Embeddable Text-to-Speech widget for blogs and websites with automatic updates via CDN.
4
+
5
+ ## Features
6
+
7
+ ✅ **Always up-to-date** - Get latest features and bug fixes automatically
8
+ ✅ **Smart loading** - CDN with automatic fallback to bundled version
9
+ ✅ **Zero-config** - Works out of the box with sensible defaults
10
+ ✅ **CSP-safe** - Graceful fallback for Content Security Policy restrictions
11
+ ✅ **Version control** - Pin to specific versions when needed
12
+
13
+ ## Installation
14
+
15
+ ```bash
16
+ npm install @supernal/tts-widget
17
+ ```
18
+
19
+ ## Quick Start
20
+
21
+ ### Option 1: Auto-Updating via CDN (Recommended for New Projects)
22
+
23
+ ```javascript
24
+ import { WidgetLoader } from '@supernal/tts-widget/loader';
25
+ import '@supernal/tts-widget/widget.css';
26
+
27
+ // Load widget (tries CDN @1, falls back to bundled)
28
+ const widget = await WidgetLoader.load();
29
+
30
+ widget.init({
31
+ apiUrl: 'https://tts.supernal.ai',
32
+ provider: 'openai',
33
+ voice: 'alloy'
34
+ });
35
+ ```
36
+
37
+ **How it works:**
38
+ 1. Tries to load from CDN (`unpkg.com/@supernal/tts-widget@1`)
39
+ 2. Gets latest v1.x.x automatically (patches and minors)
40
+ 3. Falls back to bundled version if CDN unavailable (CSP/firewall)
41
+
42
+ **Use this if:** You want automatic bug fixes and feature updates
43
+
44
+ ### Option 2: Bundled (Stable, No CDN)
45
+
46
+ ```javascript
47
+ import { SupernalTTS } from '@supernal/tts-widget';
48
+ import '@supernal/tts-widget/widget.css';
49
+
50
+ SupernalTTS.init({
51
+ apiUrl: 'https://tts.supernal.ai',
52
+ provider: 'openai',
53
+ voice: 'alloy'
54
+ });
55
+ ```
56
+
57
+ **Use this if:**
58
+ - You have strict CSP policies (no external scripts)
59
+ - You're behind a corporate firewall
60
+ - You want predictable builds (manual upgrades)
61
+
62
+ ### React/Next.js
63
+
64
+ ```tsx
65
+ import { TTSInitializer } from '@supernal/tts-widget/react';
66
+ import '@supernal/tts-widget/widget.css';
67
+
68
+ export default function App() {
69
+ return (
70
+ <>
71
+ <TTSInitializer
72
+ apiUrl="https://tts.supernal.ai"
73
+ mode="auto" // Try CDN, fallback to bundled (default)
74
+ version="major" // Load latest v1.x.x (default)
75
+ />
76
+ <YourContent />
77
+ </>
78
+ );
79
+ }
80
+ ```
81
+
82
+ ### HTML/Static Sites (No npm)
83
+
84
+ ```html
85
+ <!DOCTYPE html>
86
+ <html>
87
+ <head>
88
+ <link rel="stylesheet" href="https://unpkg.com/@supernal/tts-widget@1/dist/widget.css">
89
+ </head>
90
+ <body>
91
+ <div class="tts-widget" data-text="This content can be read aloud!">
92
+ <p>This content can be read aloud!</p>
93
+ </div>
94
+
95
+ <script type="module">
96
+ import { SupernalTTS } from 'https://unpkg.com/@supernal/tts-widget@1/dist/widget.js';
97
+ SupernalTTS.init({
98
+ apiUrl: 'https://tts.supernal.ai',
99
+ provider: 'openai',
100
+ voice: 'alloy'
101
+ });
102
+ </script>
103
+ </body>
104
+ </html>
105
+ ```
106
+
107
+ **Note:** Use `@1` for major version (auto-updates) or `@1.3.0` to pin to specific version.
108
+
109
+ ## Loading Options
110
+
111
+ ### Auto Mode (Default - Recommended)
112
+
113
+ ```javascript
114
+ import { WidgetLoader } from '@supernal/tts-widget/loader';
115
+
116
+ // Try CDN, fallback to bundled
117
+ const widget = await WidgetLoader.load({
118
+ mode: 'auto', // Default: Try CDN, fallback to bundled
119
+ version: 'major' // Default: Load latest v1.x.x
120
+ });
121
+ ```
122
+
123
+ ### Bundled Mode (CSP-Safe)
124
+
125
+ ```javascript
126
+ import { WidgetLoader } from '@supernal/tts-widget/loader';
127
+
128
+ // Always use bundled version (no CDN)
129
+ const widget = await WidgetLoader.load({
130
+ mode: 'bundled'
131
+ });
132
+ ```
133
+
134
+ **Or use the default bundled export:**
135
+
136
+ ```javascript
137
+ import { SupernalTTS } from '@supernal/tts-widget';
138
+ // No loader needed - always uses bundled version
139
+ ```
140
+
141
+ ### Version Pinning
142
+
143
+ ```javascript
144
+ import { WidgetLoader } from '@supernal/tts-widget/loader';
145
+
146
+ // Pin to specific version
147
+ const widget = await WidgetLoader.load({
148
+ version: '1.3.0' // Lock to exact version
149
+ });
150
+ ```
151
+
152
+ ### Advanced Options
153
+
154
+ ```javascript
155
+ import { WidgetLoader } from '@supernal/tts-widget/loader';
156
+
157
+ const widget = await WidgetLoader.load({
158
+ mode: 'auto',
159
+ version: 'major',
160
+ timeout: 5000, // CDN timeout in ms (default: 3000)
161
+ cdnUrl: 'https://cdn.example.com/widget', // Custom CDN
162
+
163
+ onCdnSuccess: () => {
164
+ console.log('Loaded from CDN');
165
+ },
166
+
167
+ onCdnFail: (error) => {
168
+ console.warn('CDN failed, using bundled:', error);
169
+ }
170
+ });
171
+ ```
172
+
173
+ ## Widget Configuration
174
+
175
+ ```javascript
176
+ SupernalTTS.init({
177
+ apiUrl: 'https://tts.supernal.ai', // Required: API endpoint
178
+ provider: 'openai', // Optional: TTS provider (default: 'openai')
179
+ voice: 'alloy', // Optional: Voice selection
180
+ speed: 1.0, // Optional: Playback speed (0.25-4.0)
181
+ apiKey: 'your-api-key', // Optional: For authenticated requests
182
+ devMode: false, // Optional: Enable dev mode features
183
+ clientSideSpeed: true // Optional: Use browser pitch-preserving time-stretching (default: true)
184
+ // Saves generation costs! Set to false for server-side regeneration
185
+ });
186
+ ```
187
+
188
+ ### Client-Side Speed Adjustment
189
+
190
+ By default (`clientSideSpeed: true`), the widget uses the browser's native `preservesPitch` feature with `playbackRate` to adjust speed instantly without regenerating audio. This:
191
+
192
+ - ✅ **Saves significant costs** - no need to generate audio at multiple speeds
193
+ - ✅ **Instant speed changes** - no loading/regeneration delay
194
+ - ✅ **Maintains pitch quality** - uses browser's time-stretching algorithm
195
+ - ✅ Works with already-cached audio
196
+
197
+ Set `clientSideSpeed: false` if you need server-side speed generation with provider-specific pitch correction (more expensive but may have slightly different quality characteristics for extreme speed changes).
198
+
199
+ ## Widget Data Attributes
200
+
201
+ ```html
202
+ <!-- Custom voice per widget -->
203
+ <div class="tts-widget"
204
+ data-text="Professional voice example"
205
+ data-voice="coral"
206
+ data-provider="openai">
207
+ Professional voice example
208
+ </div>
209
+
210
+ <!-- Custom speed -->
211
+ <div class="tts-widget"
212
+ data-text="Fast speech"
213
+ data-speed="1.5">
214
+ Fast speech
215
+ </div>
216
+ ```
217
+
218
+ ## Available Voices
219
+
220
+ ### OpenAI
221
+ - `alloy`, `echo`, `fable`, `onyx`, `nova`, `shimmer`, `coral`, `sage`, `verse`
222
+
223
+ ### Mock (Testing)
224
+ - `mock-voice-1`, `mock-voice-2`, `mock-voice-3`
225
+
226
+ ## When to Use What?
227
+
228
+ | Scenario | Recommended Approach | Why |
229
+ |----------|---------------------|-----|
230
+ | **New projects** | `import { WidgetLoader } from '.../ loader'` | Auto-updates, has fallback |
231
+ | **Existing code** | `import { SupernalTTS } from '...'` | No changes needed |
232
+ | **Corporate firewall** | `import { SupernalTTS } from '...'` | Works offline/air-gapped |
233
+ | **Static HTML blog** | Direct CDN `@1` | Simple, no build step |
234
+ | **Need predictability** | `import { SupernalTTS } from '...'` | Manual upgrades only |
235
+ | **CSP restrictions** | `import { SupernalTTS } from '...'` | No external scripts |
236
+
237
+ ## Migration from v1.2.x
238
+
239
+ See [MIGRATION.md](MIGRATION.md) for detailed upgrade guide.
240
+
241
+ **TL;DR:** No breaking changes. Existing code works as-is. New smart loader is opt-in.
242
+
243
+ ```javascript
244
+ // Old (v1.2.x - still works exactly the same)
245
+ import { SupernalTTS } from '@supernal/tts-widget';
246
+ SupernalTTS.init({ ... });
247
+
248
+ // New (v1.3.0 - opt-in for auto-updates)
249
+ import { WidgetLoader } from '@supernal/tts-widget/loader';
250
+ const widget = await WidgetLoader.load();
251
+ widget.init({ ... });
252
+ ```
253
+
254
+ **No action required for existing code!** The default export is unchanged.
255
+
256
+ ## Documentation
257
+
258
+ Full documentation: [https://tts.supernal.ai](https://tts.supernal.ai)
259
+
260
+ ---
261
+
262
+ ## Development
263
+
264
+ This is the **SOURCE OF TRUTH** for the Supernal TTS widget. All changes should be made here.
265
+
266
+ ### Directory Structure
267
+
268
+ ```
269
+ packages/@supernal/tts-widget/
270
+ ├── src/
271
+ │ ├── widget.ts # TypeScript source (EDIT THIS)
272
+ │ └── widget.css # Styles
273
+ ├── dist/ # Built output (DO NOT EDIT)
274
+ │ ├── widget.js # Bundled JavaScript
275
+ │ ├── widget.css # Copied styles
276
+ │ └── widget.d.ts # TypeScript declarations
277
+ ├── package.json
278
+ └── tsconfig.json
279
+ ```
280
+
281
+ ### Build Process
282
+
283
+ 1. **Build the widget**:
284
+ ```bash
285
+ npm run build
286
+ ```
287
+
288
+ This runs:
289
+ - `tsc` - Compiles TypeScript to JavaScript
290
+ - `esbuild` - Bundles and minifies to a single IIFE file
291
+ - `cp` - Copies CSS to dist
292
+
293
+ 2. **From monorepo root**:
294
+ ```bash
295
+ npm run build:widget
296
+ ```
297
+
298
+ 3. **Docs site** automatically copies widget on build/start:
299
+ ```bash
300
+ cd docs-site
301
+ npm run copy-widget # or `npm start` / `npm run build`
302
+ ```
303
+
304
+ ### Features
305
+
306
+ - **TypeScript**: Proper typing and IDE support
307
+ - **Dev Mode Cache Clear**: Red button in dev mode to clear local cache
308
+ - **Absolute URLs**: Handles both relative and absolute audio URLs
309
+ - **Branding**: Optional Supernal Intelligence badge
310
+ - **Responsive**: Mobile-friendly design
311
+ - **Dark Mode**: Automatic dark mode support
312
+
313
+ ### Usage in Docs Site
314
+
315
+ The docs-site imports the widget from this package via:
316
+ - **Build script**: `copy-widget` in `docs-site/package.json` copies built files to `static/`
317
+ - **Component**: `TTSWidget` in `docs-site/src/components/TTSWidget/` loads from `/js/widget.js`
318
+
319
+ **DO NOT** edit files in `docs-site/static/js/` or `docs-site/static/css/` directly!
320
+
321
+ ### Making Changes
322
+
323
+ 1. Edit `src/widget.ts` or `src/widget.css`
324
+ 2. Run `npm run build` (or `npm run dev` for watch mode)
325
+ 3. Test in docs-site: `cd ../../../docs-site && npm start`
326
+ 4. The widget will be automatically copied and loaded
327
+
328
+ ### Publishing
329
+
330
+ When ready to publish to npm:
331
+
332
+ ```bash
333
+ npm version patch|minor|major
334
+ npm publish
335
+ ```
336
+
337
+ ## Integration
338
+
339
+ See the main [README](../../../README.md) and [docs-site](../../../docs-site/) for integration examples.
340
+
@@ -0,0 +1,78 @@
1
+ /**
2
+ * Smart Widget Loader
3
+ *
4
+ * Dynamically loads the latest compatible widget version from unpkg CDN
5
+ * with automatic fallback to bundled version for CSP/firewall scenarios.
6
+ *
7
+ * @example
8
+ * ```typescript
9
+ * import { WidgetLoader } from '@supernal-tts/widget';
10
+ *
11
+ * // Default: CDN @1 with fallback
12
+ * const widget = await WidgetLoader.load();
13
+ *
14
+ * // Force bundled (CSP-safe)
15
+ * const widget = await WidgetLoader.load({ mode: 'bundled' });
16
+ *
17
+ * // Pin specific version
18
+ * const widget = await WidgetLoader.load({ version: '1.2.1' });
19
+ * ```
20
+ */
21
+ export interface LoaderOptions {
22
+ /**
23
+ * Version strategy:
24
+ * - 'major': Load latest v1.x.x (default, recommended)
25
+ * - 'latest': Load absolute latest (risky, may break)
26
+ * - '1.2.1': Pin to specific version
27
+ */
28
+ version?: 'latest' | 'major' | string;
29
+ /**
30
+ * Loading mode:
31
+ * - 'auto': Try CDN, fallback to bundled (default)
32
+ * - 'cdn': Force CDN load, error if unavailable
33
+ * - 'bundled': Always use bundled version (CSP-safe)
34
+ */
35
+ mode?: 'cdn' | 'bundled' | 'auto';
36
+ /**
37
+ * Custom CDN URL (default: https://unpkg.com/@supernal/tts-widget)
38
+ */
39
+ cdnUrl?: string;
40
+ /**
41
+ * CDN fetch timeout in milliseconds (default: 3000)
42
+ */
43
+ timeout?: number;
44
+ /**
45
+ * Callback when CDN load fails and fallback is used
46
+ */
47
+ onCdnFail?: (error: Error) => void;
48
+ /**
49
+ * Callback when CDN load succeeds
50
+ */
51
+ onCdnSuccess?: () => void;
52
+ }
53
+ export interface SupernalTTSModule {
54
+ SupernalTTS: any;
55
+ }
56
+ export declare class WidgetLoader {
57
+ private static instance;
58
+ private static loadPromise;
59
+ /**
60
+ * Load the Supernal TTS widget with smart CDN loading and fallback
61
+ */
62
+ static load(options?: LoaderOptions): Promise<any>;
63
+ private static loadWidget;
64
+ private static loadFromCdn;
65
+ private static loadBundled;
66
+ private static resolveVersionPath;
67
+ private static getMajorVersion;
68
+ /**
69
+ * Reset the loader (useful for testing)
70
+ */
71
+ static reset(): void;
72
+ /**
73
+ * Get current loaded instance (if any)
74
+ */
75
+ static getInstance(): any | null;
76
+ }
77
+ export default WidgetLoader;
78
+ //# sourceMappingURL=loader.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"loader.d.ts","sourceRoot":"","sources":["../src/loader.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;GAmBG;AAEH,MAAM,WAAW,aAAa;IAC5B;;;;;OAKG;IACH,OAAO,CAAC,EAAE,QAAQ,GAAG,OAAO,GAAG,MAAM,CAAC;IAEtC;;;;;OAKG;IACH,IAAI,CAAC,EAAE,KAAK,GAAG,SAAS,GAAG,MAAM,CAAC;IAElC;;OAEG;IACH,MAAM,CAAC,EAAE,MAAM,CAAC;IAEhB;;OAEG;IACH,OAAO,CAAC,EAAE,MAAM,CAAC;IAEjB;;OAEG;IACH,SAAS,CAAC,EAAE,CAAC,KAAK,EAAE,KAAK,KAAK,IAAI,CAAC;IAEnC;;OAEG;IACH,YAAY,CAAC,EAAE,MAAM,IAAI,CAAC;CAC3B;AAED,MAAM,WAAW,iBAAiB;IAChC,WAAW,EAAE,GAAG,CAAC;CAClB;AAKD,qBAAa,YAAY;IACvB,OAAO,CAAC,MAAM,CAAC,QAAQ,CAAa;IACpC,OAAO,CAAC,MAAM,CAAC,WAAW,CAA6B;IAEvD;;OAEG;WACU,IAAI,CAAC,OAAO,GAAE,aAAkB,GAAG,OAAO,CAAC,GAAG,CAAC;mBAiCvC,UAAU;mBAiDV,WAAW;mBAuCX,WAAW;IAkBhC,OAAO,CAAC,MAAM,CAAC,kBAAkB;IAgBjC,OAAO,CAAC,MAAM,CAAC,eAAe;IAc9B;;OAEG;IACH,MAAM,CAAC,KAAK,IAAI,IAAI;IAKpB;;OAEG;IACH,MAAM,CAAC,WAAW,IAAI,GAAG,GAAG,IAAI;CAGjC;AAGD,eAAe,YAAY,CAAC"}
package/dist/loader.js ADDED
@@ -0,0 +1,96 @@
1
+ var M=Object.defineProperty;var x=(S,e)=>()=>(S&&(e=S(S=0)),e);var P=(S,e)=>{for(var t in e)M(S,t,{get:e[t],enumerable:!0})};var A={};P(A,{SupernalTTS:()=>L,default:()=>B});var L,B,C=x(()=>{"use strict";L=class S{constructor(e={}){this.currentAudio=null;this.currentButton=null;this.isProcessing=!1;this.lastClickTime=0;this.debounceDelay=300;this.progressUpdateInterval=null;this.audioQueue=[];this.apiUrl=e.apiUrl||"https://www.tts.supernal.ai",this.defaultVoice=e.voice||"default",this.defaultProvider=e.provider||"openai",this.defaultSpeed=e.speed||1,this.autoHash=e.autoHash!==!1,this.cacheExpiry=e.cacheExpiry||7*24*60*60*1e3,this.apiKey=e.apiKey,console.log("[TTS Widget] Constructor - API Key provided:",!!e.apiKey),console.log("[TTS Widget] Constructor - API Key saved:",!!this.apiKey),this.showBranding=e.showBranding!==!1,this.version=e.version||"1.0.0",this.devMode=e.devMode===!0,this.clientSideSpeed=e.clientSideSpeed!==!1,this.generationMode=e.generationMode||"standard",this.progressiveThreshold=e.progressiveThreshold||2e3,this.cache=new Map,this.loadCacheFromStorage(),this.init()}init(){document.readyState==="loading"?document.addEventListener("DOMContentLoaded",()=>this.initializeWidgets()):this.initializeWidgets(),this.observeDOM()}observeDOM(){new MutationObserver(t=>{t.forEach(s=>{s.addedNodes.forEach(i=>{if(i.nodeType===Node.ELEMENT_NODE){let o=i;o.classList?.contains("supernal-tts-widget")?this.setupWidget(o):o.querySelectorAll?.(".supernal-tts-widget")?.forEach(r=>this.setupWidget(r))}})})}).observe(document.body,{childList:!0,subtree:!0})}initializeWidgets(){let e=document.querySelectorAll(".supernal-tts-widget");e.forEach(t=>this.setupWidget(t)),this.devMode&&e.length>0&&this.addDevModeClearButton()}setupWidget(e){if(e.classList.contains("supernal-tts-widget-initialized"))return;let t=this.parseWidgetConfig(e);if(!t.text){console.warn("TTS Widget: No text found for widget",e);return}let s=this.generateHash(t.text,{voice:t.voice,provider:t.provider,speed:t.speed}),i=e.dataset.controls;if(i==="advanced"||i==="true"){this.setupAdvancedWidget(e,t.text,{voice:t.voice,provider:t.provider,speed:t.speed,hash:s,apiKey:t.apiKey});return}else if(i==="compact"){this.setupCompactWidget(e,t.text,{voice:t.voice,provider:t.provider,speed:t.speed,hash:s,apiKey:t.apiKey});return}this.setupModularWidget(e,t,s)}parseWidgetConfig(e){let t=e.dataset.text||e.textContent?.trim()||"",s=e.dataset.voice||this.defaultVoice,i=e.dataset.provider||this.defaultProvider,o=e.dataset.speed?parseFloat(e.dataset.speed):this.defaultSpeed,n=e.dataset.voices||"",r=n?n.split(",").map(p=>p.trim()).filter(Boolean):[],a=e.dataset.enableSpeed==="true",l=e.dataset.enableProgress==="true",d=e.dataset.apiKey||this.apiKey;return{text:t,voices:r,enableSpeed:a,enableProgress:l,speed:o,provider:i,voice:s,apiKey:d}}setupBasicWidget(e,t,s){let{voice:i,provider:o,speed:n,hash:r,apiKey:a}=s;if(e.classList.contains("supernal-tts-widget-initialized"))return;let l=document.createElement("div");l.className="supernal-tts-button-container";let d=e.querySelector(".supernal-tts-play");if(d||(d=this.createPlayButton()),l.appendChild(d),this.showBranding&&!e.querySelector(".supernal-badge")){let p=this.createBrandingBadge();l.appendChild(p)}e.appendChild(l),e.classList.add("supernal-tts-widget-initialized"),d.addEventListener("click",p=>{p.preventDefault(),this.handlePlayClick(d,t,{voice:i,provider:o,speed:n,hash:r,apiKey:a})}),e._ttsWidget={text:t,voice:i,provider:o,speed:n,hash:r,playButton:d}}setupCompactWidget(e,t,s){let{hash:i,apiKey:o}=s,{voice:n,provider:r,speed:a}=s;if(e.classList.contains("supernal-tts-widget-initialized"))return;let l=document.createElement("div");l.className="supernal-tts-compact-widget";let d=document.createElement("div");d.className="supernal-tts-top-row";let p=this.createPlayButton(),g=document.createElement("select");if(g.className="supernal-tts-voice-select",g.innerHTML=`
2
+ <option value="alloy" ${n==="alloy"?"selected":""}>Alloy</option>
3
+ <option value="echo" ${n==="echo"?"selected":""}>Echo</option>
4
+ <option value="fable" ${n==="fable"?"selected":""}>Fable</option>
5
+ <option value="onyx" ${n==="onyx"?"selected":""}>Onyx</option>
6
+ <option value="nova" ${n==="nova"?"selected":""}>Nova</option>
7
+ <option value="shimmer" ${n==="shimmer"?"selected":""}>Shimmer</option>
8
+ <option value="coral" ${n==="coral"?"selected":""}>Coral</option>
9
+ `,d.appendChild(p),d.appendChild(g),this.showBranding){let m=this.createBrandingBadge();d.appendChild(m)}let v=document.createElement("div");v.className="supernal-tts-speed-control",v.innerHTML=`
10
+ <label>Speed: <span class="supernal-tts-speed-value">${a}x</span></label>
11
+ <input type="range" class="supernal-tts-speed-slider" min="0.25" max="4.0" step="0.25" value="${a}">
12
+ `,l.appendChild(d),l.appendChild(v),e.innerHTML="",e.appendChild(l),e.classList.add("supernal-tts-widget-initialized","supernal-tts-widget-compact");let f=v.querySelector(".supernal-tts-speed-slider"),c=v.querySelector(".supernal-tts-speed-value");p.addEventListener("click",m=>{m.preventDefault(),a=parseFloat(f.value),n=g.value;let T=this.generateHash(t,{voice:n,provider:r,speed:a});this.handlePlayClick(p,t,{voice:n,provider:r,speed:a,hash:T,apiKey:o})}),f.addEventListener("input",m=>{a=parseFloat(m.target.value),c.textContent=`${a}x`}),g.addEventListener("change",()=>{n=g.value}),e._ttsWidget={text:t,voice:n,provider:r,speed:a,hash:i,playButton:p,compact:!0}}setupAdvancedWidget(e,t,s){let{hash:i,apiKey:o}=s,{voice:n,provider:r,speed:a}=s,l=document.createElement("div");l.className="supernal-tts-advanced-widget";let d=document.createElement("div");d.className="supernal-tts-button-group";let p=this.createPlayButton(),g=this.createStopButton();d.appendChild(p),d.appendChild(g);let v=document.createElement("select");v.className="supernal-tts-voice-select",v.innerHTML=`
13
+ <option value="alloy" ${n==="alloy"?"selected":""}>Alloy</option>
14
+ <option value="echo" ${n==="echo"?"selected":""}>Echo</option>
15
+ <option value="fable" ${n==="fable"?"selected":""}>Fable</option>
16
+ <option value="onyx" ${n==="onyx"?"selected":""}>Onyx</option>
17
+ <option value="nova" ${n==="nova"?"selected":""}>Nova</option>
18
+ <option value="shimmer" ${n==="shimmer"?"selected":""}>Shimmer</option>
19
+ <option value="coral" ${n==="coral"?"selected":""}>Coral</option>
20
+ `;let f=document.createElement("div");f.className="supernal-tts-speed-control",f.innerHTML=`
21
+ <label>Speed: <span class="supernal-tts-speed-value">${a}x</span></label>
22
+ <input type="range" class="supernal-tts-speed-slider" min="0.25" max="4.0" step="0.25" value="${a}">
23
+ `;let c=document.createElement("div");if(c.className="supernal-tts-progress",c.innerHTML=`
24
+ <input type="range" class="supernal-tts-progress-slider" min="0" max="100" value="0" step="0.1">
25
+ <div class="supernal-tts-time"><span class="supernal-tts-current-time">0:00</span> / <span class="supernal-tts-duration">0:00</span></div>
26
+ `,l.appendChild(d),l.appendChild(v),l.appendChild(f),l.appendChild(c),this.showBranding){let h=this.createBrandingBadge();l.appendChild(h)}e.innerHTML="",e.appendChild(l),e.classList.add("supernal-tts-widget-initialized","supernal-tts-widget-advanced");let m=f.querySelector(".supernal-tts-speed-slider"),T=f.querySelector(".supernal-tts-speed-value"),y=c.querySelector(".supernal-tts-progress-slider");p.addEventListener("click",h=>{h.preventDefault(),a=parseFloat(m.value),n=v.value;let E=this.generateHash(t,{voice:n,provider:r,speed:a});this.handlePlayClick(p,t,{voice:n,provider:r,speed:a,hash:E,apiKey:o})}),g.addEventListener("click",h=>{h.preventDefault(),this.stopAudio()}),m.addEventListener("input",h=>{a=parseFloat(h.target.value),T.textContent=`${a}x`}),v.addEventListener("change",()=>{n=v.value}),y.addEventListener("input",h=>{if(this.currentAudio){let E=parseFloat(h.target.value)/100;this.currentAudio.currentTime=this.currentAudio.duration*E}}),setInterval(()=>{if(this.currentAudio&&e._ttsWidget===this.currentWidget){let h=this.currentAudio.currentTime/this.currentAudio.duration*100;y.value=h.toString();let E=this.formatTime(this.currentAudio.currentTime),w=this.formatTime(this.currentAudio.duration);c.querySelector(".supernal-tts-current-time").textContent=E,c.querySelector(".supernal-tts-duration").textContent=w}},100),e._ttsWidget={text:t,voice:n,provider:r,speed:a,hash:i,playButton:p,stopButton:g,voiceSelect:v,speedSlider:m,progressSlider:y,advanced:!0},this.currentWidget=e._ttsWidget}setupModularWidget(e,t,s){let i=t.voice,o=t.speed,{text:n,voices:r,enableSpeed:a,enableProgress:l,provider:d,apiKey:p}=t,g=0,v=document.createElement("div");v.className="supernal-tts-modular-widget";let f=document.createElement("div");f.className="supernal-tts-controls-row";let c=this.createPlayButton(!0);f.appendChild(c);let m=null;r.length>0&&(m=this.createVoiceDropdown(r,i),f.appendChild(m),m.addEventListener("voice-changed",u=>{let h=u.detail.voice;if(h!==i&&this.currentButton===c&&this.currentAudio){i=h,this.currentAudio.duration>0&&(g=this.currentAudio.currentTime/this.currentAudio.duration);let w=e._ttsWidget?.progressBar;this.cleanStopAudioKeepProgress(w),setTimeout(()=>{this.handlePlayClickFromPosition(c,n,{voice:i,provider:d,speed:o,hash:this.generateHash(n,{voice:i,provider:d,speed:o}),progressBar:w,apiKey:p},g)},50)}else i=h}));let T=null;if(a&&(T=this.createSpeedDropdown(o),f.appendChild(T),T.addEventListener("speed-changed",u=>{let h=u.detail.speed;if(h!==o){if(o=h,this.clientSideSpeed&&this.currentAudio&&this.currentButton===c)this.currentAudio.playbackRate=h;else if(!this.clientSideSpeed&&this.currentButton===c&&this.currentAudio){this.currentAudio.duration>0&&(g=this.currentAudio.currentTime/this.currentAudio.duration);let w=e._ttsWidget?.progressBar;this.cleanStopAudioKeepProgress(w),setTimeout(()=>{this.handlePlayClickFromPosition(c,n,{voice:i,provider:d,speed:o,hash:this.generateHash(n,{voice:i,provider:d,speed:o}),progressBar:w,apiKey:p},g)},50)}}})),this.showBranding){let u=this.createBrandingBadge();f.appendChild(u)}v.appendChild(f);let y=null;l&&(y=this.createProgressBar(),v.appendChild(y)),e.insertBefore(v,e.firstChild),e.classList.add("supernal-tts-widget-initialized","supernal-tts-widget-modular"),l&&e.classList.add("has-progress"),c.addEventListener("click",u=>{u.preventDefault();let h=this.generateHash(n,{voice:i,provider:d,speed:o});this.handlePlayClick(c,n,{voice:i,provider:d,speed:o,hash:h,progressBar:y,apiKey:p})}),e._ttsWidget={text:n,voice:i,provider:d,speed:o,hash:s,playButton:c,voiceDropdown:m,speedDropdown:T,progressBar:y}}createVoiceDropdown(e,t){let s=document.createElement("div");s.className="supernal-tts-voice-control";let i=document.createElement("button");i.className="supernal-tts-voice-toggle",i.setAttribute("aria-label","Select voice"),i.setAttribute("aria-expanded","false"),i.innerHTML=`
27
+ <svg class="supernal-tts-voice-icon" width="20" height="20" viewBox="0 0 24 24" fill="currentColor">
28
+ <path d="M12 2C6.48 2 2 6.48 2 12s4.48 10 10 10 10-4.48 10-10S17.52 2 12 2zm0 3c1.66 0 3 1.34 3 3s-1.34 3-3 3-3-1.34-3-3 1.34-3 3-3zm0 14.2c-2.5 0-4.71-1.28-6-3.22.03-1.99 4-3.08 6-3.08 1.99 0 5.97 1.09 6 3.08-1.29 1.94-3.5 3.22-6 3.22z"/>
29
+ </svg>
30
+ `;let o=document.createElement("div");o.className="supernal-tts-voice-dropdown hidden",o.setAttribute("role","menu"),e.forEach(r=>{let a=document.createElement("button");a.className="supernal-tts-voice-option",a.textContent=this.formatVoiceName(r),a.dataset.voice=r,a.setAttribute("role","menuitem"),r===t&&a.classList.add("active"),a.addEventListener("click",l=>{l.stopPropagation(),l.preventDefault(),o.querySelectorAll(".supernal-tts-voice-option").forEach(d=>{d.classList.remove("active")}),a.classList.add("active"),s.dispatchEvent(new CustomEvent("voice-changed",{detail:{voice:r}}))}),o.appendChild(a)});let n=null;return s.addEventListener("mouseenter",()=>{n&&(clearTimeout(n),n=null),o.classList.remove("hidden"),i.setAttribute("aria-expanded","true"),setTimeout(()=>{let r=o.getBoundingClientRect(),a=window.innerHeight;r.bottom>a&&r.top>r.height?(o.style.bottom="100%",o.style.top="auto"):(o.style.top="calc(100% + 4px)",o.style.bottom="auto")},0)}),s.addEventListener("mouseleave",r=>{n=window.setTimeout(()=>{o.classList.add("hidden"),i.setAttribute("aria-expanded","false")},150)}),o.addEventListener("mouseenter",()=>{n&&(clearTimeout(n),n=null)}),o.addEventListener("mouseleave",()=>{n=window.setTimeout(()=>{o.classList.add("hidden"),i.setAttribute("aria-expanded","false")},150)}),i.addEventListener("click",r=>{r.stopPropagation();let a=o.classList.contains("hidden");o.classList.toggle("hidden"),i.setAttribute("aria-expanded",a?"true":"false")}),s.appendChild(i),s.appendChild(o),s}createSpeedDropdown(e){let t=document.createElement("div");t.className="supernal-tts-speed-control";let s=document.createElement("button");s.className="supernal-tts-speed-toggle",s.setAttribute("aria-label","Adjust playback speed"),s.innerHTML=`
31
+ <svg class="supernal-tts-speed-icon" width="20" height="20" viewBox="0 0 24 24" fill="currentColor">
32
+ <path d="M20.38 8.57l-1.23 1.85a8 8 0 0 1-.22 7.58H5.07A8 8 0 0 1 15.58 6.85l1.85-1.23A10 10 0 0 0 3.35 19a2 2 0 0 0 1.72 1h13.85a2 2 0 0 0 1.74-1 10 10 0 0 0-.27-10.44z"/>
33
+ <path d="M10.59 15.41a2 2 0 0 0 2.83 0l5.66-8.49-8.49 5.66a2 2 0 0 0 0 2.83z"/>
34
+ </svg>
35
+ `;let i=document.createElement("div");i.className="supernal-tts-speed-dropdown hidden",i.setAttribute("role","menu");let o=[];for(let r=.6;r<=3;r+=.2)o.push(Math.round(r*10)/10);o.forEach(r=>{let a=document.createElement("button");a.className="supernal-tts-speed-option",a.textContent=`${r.toFixed(1)}x`,a.dataset.speed=r.toString(),a.setAttribute("role","menuitem"),Math.abs(r-e)<.01&&a.classList.add("active"),a.addEventListener("click",l=>{l.stopPropagation(),l.preventDefault(),i.querySelectorAll(".supernal-tts-speed-option").forEach(d=>{d.classList.remove("active")}),a.classList.add("active"),t.dispatchEvent(new CustomEvent("speed-changed",{detail:{speed:r}}))}),i.appendChild(a)});let n=null;return t.addEventListener("mouseenter",()=>{n&&(clearTimeout(n),n=null),i.classList.remove("hidden"),setTimeout(()=>{let r=i.getBoundingClientRect(),a=window.innerHeight;r.bottom>a&&r.top>r.height?(i.style.bottom="100%",i.style.top="auto"):(i.style.top="calc(100% + 4px)",i.style.bottom="auto")},0)}),t.addEventListener("mouseleave",()=>{n=window.setTimeout(()=>{i.classList.add("hidden")},150)}),i.addEventListener("mouseenter",()=>{n&&(clearTimeout(n),n=null)}),i.addEventListener("mouseleave",()=>{n=window.setTimeout(()=>{i.classList.add("hidden")},150)}),t.appendChild(s),t.appendChild(i),t}createProgressBar(){let e=document.createElement("div");e.className="supernal-tts-progress-container";let t=document.createElement("input");t.type="range",t.className="supernal-tts-progress-slider",t.min="0",t.max="100",t.value="0",t.step="0.1",t.setAttribute("aria-label","Seek audio position");let s=document.createElement("div");return s.className="supernal-tts-time-display",s.innerHTML='<span class="current">0:00</span> / <span class="duration">0:00</span>',t.addEventListener("input",i=>{if(this.currentAudio){let o=parseFloat(i.target.value);this.currentAudio.currentTime=o/100*this.currentAudio.duration}}),e.appendChild(t),e.appendChild(s),e}formatTime(e){if(!e||isNaN(e))return"0:00";let t=Math.floor(e/60),s=Math.floor(e%60);return`${t}:${s.toString().padStart(2,"0")}`}formatVoiceName(e){return e.replace(/[-_]/g," ").split(" ").map(t=>t.charAt(0).toUpperCase()+t.slice(1)).join(" ")}createStopButton(){let e=document.createElement("button");return e.className="supernal-tts-stop",e.innerHTML=`
36
+ <svg class="supernal-tts-icon" width="16" height="16" viewBox="0 0 24 24" fill="currentColor">
37
+ <rect x="6" y="6" width="12" height="12"/>
38
+ </svg>
39
+ <span class="supernal-tts-text">Stop</span>
40
+ `,e.setAttribute("aria-label","Stop text-to-speech"),e}createPlayButton(e=!1){let t=document.createElement("button");return t.className="supernal-tts-play",e?(t.classList.add("supernal-tts-play-compact"),t.innerHTML=`
41
+ <svg class="supernal-tts-icon supernal-tts-play-icon" width="20" height="20" viewBox="0 0 24 24" fill="currentColor">
42
+ <path d="M8 5v14l11-7z"/>
43
+ </svg>
44
+ <svg class="supernal-tts-icon supernal-tts-loading-icon" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
45
+ <circle cx="12" cy="12" r="10" opacity="0.25"></circle>
46
+ <path d="M12 2a10 10 0 0 1 10 10" stroke-linecap="round"></path>
47
+ </svg>
48
+ `):t.innerHTML=`
49
+ <svg class="supernal-tts-icon supernal-tts-play-icon" width="16" height="16" viewBox="0 0 24 24" fill="currentColor">
50
+ <path d="M8 5v14l11-7z"/>
51
+ </svg>
52
+ <svg class="supernal-tts-icon supernal-tts-loading-icon" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
53
+ <circle cx="12" cy="12" r="10" opacity="0.25"></circle>
54
+ <path d="M12 2a10 10 0 0 1 10 10" stroke-linecap="round"></path>
55
+ </svg>
56
+ <span class="supernal-tts-text">Listen</span>
57
+ `,t.setAttribute("aria-label","Play text-to-speech"),t}createBrandingBadge(){let e=document.createElement("a");e.className="supernal-badge",e.href="https://www.tts.supernal.ai",e.target="_blank",e.rel="noopener noreferrer",e.title="Powered by Supernal TTS",e.setAttribute("aria-label","Powered by Supernal TTS");let t=document.createElement("img");return t.src="https://unpkg.com/@supernal/tts-widget@latest/dist/supernal-tts-logo.svg",t.alt="Supernal TTS",t.className="supernal-logo-img",t.style.display="block",t.style.width="24px",t.style.height="24px",t.onerror=()=>{e.innerHTML="~+",e.style.fontSize="16px",e.style.fontWeight="700",e.style.display="flex",e.style.color="#0066ff"},e.appendChild(t),e}addDevModeClearButton(){if(document.getElementById("supernal-tts-dev-clear-cache"))return;let e=document.createElement("div");e.id="supernal-tts-dev-clear-cache",e.style.cssText=`
58
+ position: fixed;
59
+ bottom: 20px;
60
+ right: 20px;
61
+ z-index: 100;
62
+ font-family: system-ui, -apple-system, sans-serif;
63
+ `;let t=document.createElement("button");t.className="supernal-tts-dev-button-minimized",t.innerHTML="\u{1F5D1}\uFE0F",t.title="Clear TTS Cache (Dev Mode)",t.style.cssText=`
64
+ display: none;
65
+ background: #6c757d;
66
+ color: white;
67
+ border: none;
68
+ padding: 8px;
69
+ border-radius: 50%;
70
+ width: 36px;
71
+ height: 36px;
72
+ font-size: 16px;
73
+ cursor: pointer;
74
+ box-shadow: 0 2px 8px rgba(0,0,0,0.3);
75
+ transition: all 0.2s ease;
76
+ `;let s=document.createElement("button");s.className="supernal-tts-dev-button",s.style.cssText=`
77
+ background: #dc3545;
78
+ color: white;
79
+ border: none;
80
+ padding: 8px 12px;
81
+ border-radius: 5px;
82
+ font-size: 12px;
83
+ cursor: pointer;
84
+ box-shadow: 0 2px 8px rgba(0,0,0,0.3);
85
+ transition: all 0.2s ease;
86
+ display: flex;
87
+ align-items: center;
88
+ gap: 6px;
89
+ `,s.innerHTML=`
90
+ <span>\u{1F5D1}\uFE0F</span>
91
+ <span>Clear Cache</span>
92
+ <span style="opacity: 0.7; font-size: 10px;">\xD7</span>
93
+ `;let i=r=>{r.addEventListener("mouseenter",()=>{r.style.transform="scale(1.05)",r.style.boxShadow="0 4px 12px rgba(0,0,0,0.4)"}),r.addEventListener("mouseleave",()=>{r.style.transform="scale(1)",r.style.boxShadow="0 2px 8px rgba(0,0,0,0.3)"})};i(s),i(t);let o=()=>{let r=this.cache.size;this.cache.clear(),localStorage.removeItem("supernal-tts-cache"),s.innerHTML=`<span>\u2713</span><span>Cleared ${r}</span><span style="opacity: 0.7; font-size: 10px;">\xD7</span>`,s.style.background="#28a745",setTimeout(()=>{s.innerHTML='<span>\u{1F5D1}\uFE0F</span><span>Clear Cache</span><span style="opacity: 0.7; font-size: 10px;">\xD7</span>',s.style.background="#dc3545"},2e3),console.log(`[Supernal TTS Dev] Cleared ${r} cached items`)};s.addEventListener("click",r=>{r.preventDefault();let a=r.clientX-r.target.getBoundingClientRect().left,l=s.getBoundingClientRect().width;a>l*.8?(s.style.display="none",t.style.display="block",localStorage.setItem("supernal-tts-dev-minimized","true")):o()}),t.addEventListener("click",r=>{r.preventDefault(),t.style.display="none",s.style.display="flex",localStorage.setItem("supernal-tts-dev-minimized","false")}),localStorage.getItem("supernal-tts-dev-minimized")==="true"&&(s.style.display="none",t.style.display="block"),e.appendChild(s),e.appendChild(t),document.body?document.body.appendChild(e):document.addEventListener("DOMContentLoaded",()=>{document.body.appendChild(e)})}async handlePlayClick(e,t,s){let{hash:i,progressBar:o}=s;if(e.classList.contains("supernal-tts-playing")&&this.currentButton===e){this.pauseAudio();return}if(e.classList.contains("supernal-tts-paused")&&this.currentButton===e){this.resumeAudio();return}if(!this.isProcessing){this.currentAudio&&this.currentButton&&this.currentButton!==e&&this.stopAudio();try{this.isProcessing=!0,this.setButtonState(e,"loading");let n=this.getCachedAudioUrl(i);if(!n)if(this.generationMode==="progressive"||this.generationMode==="standard"&&t.length>this.progressiveThreshold){await this.generateAudioProgressive(t,s,e,o);return}else{let a=await this.generateAudio(t,s);n=a.audioUrl.startsWith("http")?a.audioUrl:`${this.apiUrl}${a.audioUrl}`,this.cacheAudioUrl(i,n,a)}await this.playAudio(n,e,o)}catch(n){console.error("TTS Error:",n),this.setButtonState(e,"error"),this.showError(e,n.message),setTimeout(()=>this.setButtonState(e,"ready"),3e3),this.isProcessing=!1}}}async generateAudio(e,t){let s={text:e,options:{provider:t.provider,voice:t.voice,speed:t.speed||1}},i={"Content-Type":"application/json"},o=t.apiKey||this.apiKey;console.log("[TTS Widget] Generate Audio - API Key present:",!!o),console.log("[TTS Widget] Generate Audio - this.apiKey:",!!this.apiKey),console.log("[TTS Widget] Generate Audio - options.apiKey:",!!t.apiKey),o?(i.Authorization=`Bearer ${o}`,console.log("[TTS Widget] Generate Audio - Authorization header added")):console.warn("[TTS Widget] Generate Audio - NO API KEY, Authorization header NOT added");try{let n=await fetch(`${this.apiUrl}/api/v1/generate`,{method:"POST",headers:i,body:JSON.stringify(s)});if(!n.ok){let a=await n.json().catch(()=>({}));throw new Error(a.error||`HTTP ${n.status}: ${n.statusText}`)}let r=await n.json();return{audioUrl:`${this.apiUrl}${r.audioUrl}`,hash:r.hash,cached:r.cached,duration:r.duration,cost:r.cost}}catch(n){if(console.error("TTS generation failed:",n),n.message.includes("Failed to fetch")||n.message.includes("NetworkError")){let r=n.message;throw new Error(`TTS API unreachable: ${r}. Check if ${this.apiUrl} is accessible.`)}throw n}}async generateAudioProgressive(e,t,s,i){let o={text:e,options:{provider:t.provider,voice:t.voice,speed:t.speed||1}},n={"Content-Type":"application/json",Accept:"text/event-stream"},r=t.apiKey||this.apiKey;r&&(n.Authorization=`Bearer ${r}`);try{let a=await fetch(`${this.apiUrl}/api/v1/generate-progressive`,{method:"POST",headers:n,body:JSON.stringify(o)});if(!a.ok){let c=await a.json().catch(()=>({}));throw new Error(c.error||`HTTP ${a.status}: ${a.statusText}`)}if(!a.body)throw new Error("No response body for SSE stream");let l=a.body.getReader(),d=new TextDecoder,p="",g=0,v=0,f=(c,m)=>{if(i){let T=c/m*100,y=i.querySelector(".supernal-tts-progress-slider");y&&(y.value=T.toString());let u=i.querySelector(".current");u&&(u.textContent=`Chunk ${c}/${m}`)}};for(;;){let{done:c,value:m}=await l.read();if(c)break;p+=d.decode(m,{stream:!0});let T=p.split(`
94
+
95
+ `);p=T.pop()||"";for(let y of T)if(y.startsWith("data: "))try{let u=JSON.parse(y.substring(6));switch(u.type){case"started":g=u.totalChunks,console.log(`[Progressive TTS] Started: ${g} chunks`),this.setButtonState(s,"loading");break;case"progress":v=u.completed,console.log(`[Progressive TTS] Progress: ${v}/${g}`),f(v,g);break;case"complete":console.log("[Progressive TTS] Complete:",u);let h=u.audioUrl.startsWith("http")?u.audioUrl:`${this.apiUrl}${u.audioUrl}`;this.cacheAudioUrl(t.hash,h,{hash:u.hash,cached:!1,duration:u.duration,cost:u.cost}),await this.playAudio(h,s,i);break;case"error":throw new Error(u.message||"Progressive generation failed")}}catch(u){console.warn("[Progressive TTS] Failed to parse event:",y,u)}}}catch(a){throw console.error("Progressive TTS generation failed:",a),this.isProcessing=!1,a}}async playAudio(e,t,s,i){return new Promise((o,n)=>{let r=new Audio(e);this.currentAudio=r,this.currentButton=t,this.clientSideSpeed&&(r.preservesPitch=!0,r.mozPreservesPitch=!0,r.webkitPreservesPitch=!0),i!==void 0&&this.clientSideSpeed&&(r.playbackRate=i),this.progressUpdateInterval&&(clearInterval(this.progressUpdateInterval),this.progressUpdateInterval=null),r.addEventListener("loadstart",()=>{this.currentAudio===r&&this.setButtonState(t,"loading")}),r.addEventListener("canplay",()=>{if(this.currentAudio===r){this.setButtonState(t,"playing"),this.isProcessing=!1;let a=r.play();a!==void 0&&a.catch(l=>{console.error("Audio play failed:",l),this.currentAudio===r&&(this.setButtonState(t,"error"),this.currentAudio=null,this.currentButton=null,this.isProcessing=!1,n(new Error("Audio playback failed")))}),s&&(s.classList.add("playing"),this.progressUpdateInterval=window.setInterval(()=>{this.updateProgressBar(r,s)},100))}}),r.addEventListener("ended",()=>{if(this.currentAudio===r){if(this.setButtonState(t,"ready"),this.currentAudio=null,this.currentButton=null,this.progressUpdateInterval&&(clearInterval(this.progressUpdateInterval),this.progressUpdateInterval=null),s){s.classList.remove("playing");let a=s.querySelector(".supernal-tts-progress-slider");a&&(a.value="0");let l=s.querySelector(".supernal-tts-time-display");l&&(l.innerHTML='<span class="current">0:00</span> / <span class="duration">0:00</span>')}o()}}),r.addEventListener("error",a=>{this.currentAudio===r&&(this.setButtonState(t,"error"),this.currentAudio=null,this.currentButton=null,this.progressUpdateInterval&&(clearInterval(this.progressUpdateInterval),this.progressUpdateInterval=null),s&&s.classList.remove("playing"),n(new Error("Audio playback failed")))}),r.load()})}updateProgressBar(e,t){if(!e||!t)return;let s=t.querySelector(".supernal-tts-progress-slider"),i=t.querySelector(".current"),o=t.querySelector(".duration");if(s&&!isNaN(e.duration)){let n=e.currentTime/e.duration*100;s.value=n.toString()}i&&(i.textContent=this.formatTime(e.currentTime)),o&&!isNaN(e.duration)&&(o.textContent=this.formatTime(e.duration))}pauseAudio(){this.currentAudio&&!this.currentAudio.paused&&this.currentAudio.pause(),this.currentButton&&this.setButtonState(this.currentButton,"paused")}resumeAudio(){if(this.currentAudio&&this.currentAudio.paused){let e=this.currentAudio.play();e!==void 0&&e.then(()=>{this.currentButton&&this.setButtonState(this.currentButton,"playing")}).catch(t=>{console.error("Audio resume failed:",t),this.currentButton&&this.setButtonState(this.currentButton,"error")})}}stopAudio(){this.progressUpdateInterval&&(clearInterval(this.progressUpdateInterval),this.progressUpdateInterval=null),this.currentAudio&&(this.currentAudio.pause(),this.currentAudio.currentTime=0,this.currentAudio=null),this.currentButton&&(this.setButtonState(this.currentButton,"ready"),this.currentButton=null),this.isProcessing=!1}cleanStopAudio(e){if(this.progressUpdateInterval&&(clearInterval(this.progressUpdateInterval),this.progressUpdateInterval=null),this.currentAudio&&(this.currentAudio.pause(),this.currentAudio.currentTime=0,this.currentAudio=null),this.currentButton&&(this.setButtonState(this.currentButton,"ready"),this.currentButton=null),e){e.classList.remove("playing");let t=e.querySelector(".supernal-tts-progress-slider");t&&(t.value="0");let s=e.querySelector(".supernal-tts-time-display");s&&(s.innerHTML='<span class="current">0:00</span> / <span class="duration">0:00</span>')}this.isProcessing=!1}cleanStopAudioKeepProgress(e){this.progressUpdateInterval&&(clearInterval(this.progressUpdateInterval),this.progressUpdateInterval=null),this.currentAudio&&(this.currentAudio.pause(),this.currentAudio.currentTime=0,this.currentAudio=null),this.currentButton&&this.setButtonState(this.currentButton,"loading"),this.isProcessing=!1}async handlePlayClickFromPosition(e,t,s,i){let{hash:o,progressBar:n}=s;if(!this.isProcessing)try{this.isProcessing=!0,this.setButtonState(e,"loading");let r=this.getCachedAudioUrl(o);if(!r){let a=await this.generateAudio(t,s);r=a.audioUrl.startsWith("http")?a.audioUrl:`${this.apiUrl}${a.audioUrl}`,this.cacheAudioUrl(o,r,a)}await this.playAudioFromPosition(r,e,n,i)}catch(r){console.error("TTS Error:",r),this.setButtonState(e,"error"),this.showError(e,r.message),setTimeout(()=>this.setButtonState(e,"ready"),3e3),this.isProcessing=!1}}async playAudioFromPosition(e,t,s,i){return new Promise((o,n)=>{let r=new Audio(e);this.currentAudio=r,this.currentButton=t,this.clientSideSpeed&&(r.preservesPitch=!0,r.mozPreservesPitch=!0,r.webkitPreservesPitch=!0),this.progressUpdateInterval&&(clearInterval(this.progressUpdateInterval),this.progressUpdateInterval=null);let a=!1;r.addEventListener("loadstart",()=>{this.currentAudio===r&&this.setButtonState(t,"loading")}),r.addEventListener("loadedmetadata",()=>{this.currentAudio===r&&!a&&i>0&&(r.currentTime=r.duration*i,a=!0)}),r.addEventListener("canplay",()=>{if(this.currentAudio===r){!a&&i>0&&r.duration>0&&(r.currentTime=r.duration*i,a=!0),this.setButtonState(t,"playing"),this.isProcessing=!1;let l=r.play();l!==void 0&&l.catch(d=>{console.error("Audio play failed:",d),this.currentAudio===r&&(this.setButtonState(t,"error"),this.currentAudio=null,this.currentButton=null,this.isProcessing=!1,n(new Error("Audio playback failed")))}),s&&(s.classList.add("playing"),this.progressUpdateInterval=window.setInterval(()=>{this.updateProgressBar(r,s)},100))}}),r.addEventListener("ended",()=>{if(this.currentAudio===r){if(this.setButtonState(t,"ready"),this.currentAudio=null,this.currentButton=null,this.progressUpdateInterval&&(clearInterval(this.progressUpdateInterval),this.progressUpdateInterval=null),s){s.classList.remove("playing");let l=s.querySelector(".supernal-tts-progress-slider");l&&(l.value="0");let d=s.querySelector(".supernal-tts-time-display");d&&(d.innerHTML='<span class="current">0:00</span> / <span class="duration">0:00</span>')}o()}}),r.addEventListener("error",l=>{this.currentAudio===r&&(this.setButtonState(t,"error"),this.currentAudio=null,this.currentButton=null,this.progressUpdateInterval&&(clearInterval(this.progressUpdateInterval),this.progressUpdateInterval=null),s&&s.classList.remove("playing"),n(new Error("Audio playback failed")))}),r.load()})}setButtonState(e,t){e.classList.remove("supernal-tts-loading","supernal-tts-playing","supernal-tts-paused","supernal-tts-error");let s=e.querySelector(".supernal-tts-play-icon"),i=e.querySelector(".supernal-tts-text");switch(t){case"loading":e.classList.add("supernal-tts-loading"),e.disabled=!0,i&&(i.textContent="Loading...");break;case"playing":e.classList.add("supernal-tts-playing"),e.disabled=!1,s&&(s.innerHTML='<rect x="6" y="4" width="4" height="16"/><rect x="14" y="4" width="4" height="16"/>'),i&&(i.textContent="Pause");break;case"paused":e.classList.add("supernal-tts-paused"),e.disabled=!1,s&&(s.innerHTML='<path d="M8 5v14l11-7z"/>'),i&&(i.textContent="Resume");break;case"error":e.classList.add("supernal-tts-error"),e.disabled=!1,s&&(s.innerHTML='<path d="M12 2L2 7v10c0 5.55 3.84 9.95 9 11 5.16-1.05 9-5.45 9-11V7l-10-5z"/>'),i&&(i.textContent="Error");break;default:e.disabled=!1,s&&(s.innerHTML='<path d="M8 5v14l11-7z"/>'),i&&(i.textContent="Listen");break}}showError(e,t){let s=document.createElement("div");s.className="supernal-tts-error-tooltip",t.includes("development mode")?s.textContent=t:t.includes("Failed to fetch")?s.textContent="Service unavailable (dev mode)":s.textContent="Audio generation failed",e.parentNode?.appendChild(s),setTimeout(()=>{s.parentNode&&s.parentNode.removeChild(s)},3e3)}generateHash(e,t={}){let s=e+JSON.stringify(t),i=0;for(let o=0;o<s.length;o++){let n=s.charCodeAt(o);i=(i<<5)-i+n,i=i&i}return Math.abs(i).toString(36)}getCachedAudioUrl(e){let t=this.cache.get(e);if(!t)return null;if(Date.now()>t.expiry)return this.cache.delete(e),this.saveCacheToStorage(),null;let s=t.audioUrl;return s.startsWith("http")?s:`${this.apiUrl}${s}`}cacheAudioUrl(e,t,s={}){this.cache.set(e,{audioUrl:t,metadata:s,expiry:Date.now()+this.cacheExpiry,timestamp:Date.now()}),this.saveCacheToStorage()}loadCacheFromStorage(){try{let e=localStorage.getItem("supernal-tts-cache");if(e){let t=JSON.parse(e);this.cache=new Map(Object.entries(t))}}catch(e){console.warn("Failed to load TTS cache:",e)}}saveCacheToStorage(){try{let e=Object.fromEntries(this.cache);localStorage.setItem("supernal-tts-cache",JSON.stringify(e))}catch(e){console.warn("Failed to save TTS cache:",e)}}static init(e){return window.SupernalTTSInstance?(console.debug("SupernalTTS already initialized, returning existing instance"),window.SupernalTTSInstance):(window.SupernalTTSInstance=new S(e),window.SupernalTTSInstance)}static getInstance(){return window.SupernalTTSInstance}addWidget(e,t,s={}){let i=null;if(typeof e=="string"?i=document.querySelector(e):i=e,!i){console.error("TTS Widget: Element not found");return}i.classList.add("supernal-tts-widget"),i.dataset.text=t,s.voice&&(i.dataset.voice=s.voice),s.provider&&(i.dataset.provider=s.provider),this.setupWidget(i)}};if(typeof window<"u")if(window.SupernalTTS)console.debug("SupernalTTS already loaded, skipping redeclaration");else{window.SupernalTTS=L;let S=document.querySelector("script[data-supernal-tts-auto-init]");if(S&&S.dataset){let e=JSON.parse(S.dataset.supernalTtsAutoInit||"{}");L.init(e)}}B=L});var b=class{static{this.instance=null}static{this.loadPromise=null}static async load(e={}){if(this.instance)return this.instance;if(this.loadPromise)return this.loadPromise;let{version:t="major",mode:s="auto",cdnUrl:i="https://unpkg.com/@supernal/tts-widget",timeout:o=3e3,onCdnFail:n,onCdnSuccess:r}=e;return this.loadPromise=this.loadWidget({version:t,mode:s,cdnUrl:i,timeout:o,onCdnFail:n,onCdnSuccess:r}),this.loadPromise}static async loadWidget(e){let{mode:t,version:s,cdnUrl:i,timeout:o,onCdnFail:n,onCdnSuccess:r}=e;if(t==="bundled")return console.log("[TTS Widget] Loading bundled version (mode: bundled)"),this.loadBundled();if(t==="cdn"||t==="auto")try{console.log("[TTS Widget] Attempting CDN load...");let a=await this.loadFromCdn(i,s,o);return console.log("[TTS Widget] CDN load successful"),r?.(),this.instance=a,a}catch(a){let l=a instanceof Error?a.message:String(a);if(console.warn("[TTS Widget] CDN load failed:",l),n?.(a instanceof Error?a:new Error(l)),t==="cdn")throw new Error(`Failed to load widget from CDN: ${l}`);return console.log("[TTS Widget] Falling back to bundled version"),this.loadBundled()}return this.loadBundled()}static async loadFromCdn(e,t,s){let i=this.resolveVersionPath(t),o=`${e}${i}/dist/widget.js`;console.log(`[TTS Widget] Loading from CDN: ${o}`);let n=new AbortController,r=setTimeout(()=>n.abort(),s);try{let a=import(o),l=new Promise((p,g)=>{setTimeout(()=>g(new Error(`CDN load timeout after ${s}ms`)),s)}),d=await Promise.race([a,l]);if(clearTimeout(r),!d||!d.SupernalTTS)throw new Error("CDN module missing SupernalTTS export");return this.instance=d.SupernalTTS,this.instance}catch(a){throw clearTimeout(r),a}}static async loadBundled(){try{let e=await Promise.resolve().then(()=>(C(),A));if(!e||!e.SupernalTTS)throw new Error("Bundled widget missing SupernalTTS export");return this.instance=e.SupernalTTS,this.instance}catch(e){let t=e instanceof Error?e.message:String(e);throw new Error(`Failed to load bundled widget: ${t}`)}}static resolveVersionPath(e){return e==="major"?`@${this.getMajorVersion()}`:e==="latest"?"@latest":`@${e}`}static getMajorVersion(){{let e="1.3.1".match(/^(\d+)\./);if(e)return e[1]}return"1"}static reset(){this.instance=null,this.loadPromise=null}static getInstance(){return this.instance}},I=b;export{b as WidgetLoader,I as default};
96
+ //# sourceMappingURL=loader.js.map