@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/MIGRATION.md +502 -0
- package/README.md +340 -0
- package/dist/loader.d.ts +78 -0
- package/dist/loader.d.ts.map +1 -0
- package/dist/loader.js +96 -0
- package/dist/loader.js.map +7 -0
- package/dist/react/TTSInitializer.d.ts +64 -0
- package/dist/react/TTSInitializer.d.ts.map +1 -0
- package/dist/react/TTSInitializer.js +132 -0
- package/dist/react/TTSInitializer.js.map +1 -0
- package/dist/react/index.d.ts +8 -0
- package/dist/react/index.d.ts.map +1 -0
- package/dist/react/index.js +17 -0
- package/dist/react/index.js.map +1 -0
- package/dist/widget.css +839 -0
- package/dist/widget.d.ts +176 -0
- package/dist/widget.d.ts.map +1 -0
- package/dist/widget.js +96 -0
- package/dist/widget.js.map +7 -0
- package/package.json +84 -0
- package/src/react/TTSInitializer.tsx +163 -0
- package/src/react/index.ts +11 -0
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
|
+
|
package/dist/loader.d.ts
ADDED
|
@@ -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
|