Widget Generator Specification
Technical specification for the Supernal TTS widget generator
Widget Code Generator - Specification & Implementation
Overview
Interactive page where users configure widget options and get copy-paste ready code.
Location
tts.supernal.ai/docs/embed or /docs/widget-generator
Features
Step 1: Choose Widget Mode
○ Basic - Just a play button (data-controls not specified)
○ Compact - Play button + voice selector + speed control (data-controls="compact")
○ Advanced - Full controls with progress bar (data-controls="advanced") [Coming Soon]
Step 2: Configure Options
Text Source:
- Use element content (default - widget reads text from child elements)
- Specify text directly (
data-text="Your text here")
Voice & Provider:
- Provider:
openai(default),espeak,azure,cartesia - Voice options by provider:
- OpenAI:
alloy,echo,fable,onyx,nova,shimmer,coral - eSpeak: Various language/gender combinations
- Azure: Multiple neural voices
- Cartesia: High-quality streaming voices
- OpenAI:
- Speed slider: 0.25x to 4x (default 1.0x, step 0.25)
Styling:
- Show Supernal branding (visible on hover only by default)
- Theme: Controlled by CSS variables (supports light/dark themes)
- Custom CSS classes available:
.supernal-tts-widget-{mode}
API Configuration:
- API URL:
https://tts.supernal.ai(default production endpoint) - API Key: Optional (required for authenticated usage, billing, analytics)
Step 3: Preview
Live preview of widget with current settings
Step 4: Generated Code
Tab 1: HTML
<!-- Add to your <head> -->
<link rel="stylesheet" href="https://unpkg.com/@supernal-tts/widget/dist/widget.css">
<script src="https://unpkg.com/@supernal-tts/widget/dist/widget.js"></script>
<!-- Add to your content -->
<div class="supernal-tts-widget"
data-controls="compact"
data-provider="openai"
data-voice="fable"
data-speed="1.0">
<p>Your content here will be read aloud</p>
</div>
<script>
// Initialize the widget
SupernalTTS.init({
apiUrl: 'https://tts.supernal.ai',
apiKey: 'YOUR_API_KEY' // Optional
});
</script>
Tab 2: React
import { useEffect } from 'react';
import '@supernal-tts/widget/dist/widget.css';
export function MyComponent() {
useEffect(() => {
// Import widget dynamically
import('@supernal-tts/widget/dist/widget.js').then(({ SupernalTTS }) => {
SupernalTTS.init({
apiUrl: 'https://tts.supernal.ai',
apiKey: process.env.NEXT_PUBLIC_TTS_API_KEY
});
});
}, []);
return (
<div className="supernal-tts-widget"
data-controls="compact"
data-provider="openai"
data-voice="fable">
<p>Your content here</p>
</div>
);
}
Tab 3: npm Package
npm install @supernal-tts/widget
import { SupernalTTS } from '@supernal-tts/widget';
import '@supernal-tts/widget/dist/widget.css';
const tts = new SupernalTTS({
apiUrl: 'https://tts.supernal.ai',
provider: 'openai',
voice: 'fable'
});
tts.addWidget(element, 'Text to speak', { controls: 'compact' });
Step 5: Copy Button
One-click copy for each code tab
Implementation
Technology
- Docusaurus MDX page with React components
- State management: React useState
- Styling: Docusaurus CSS + custom
File Structure
docs-site/
├── docs/
│ └── generator.mdx ← New page
├── src/
│ └── components/
│ └── WidgetGenerator/
│ ├── index.tsx ← Main component
│ ├── ConfigPanel.tsx
│ ├── PreviewPanel.tsx
│ ├── CodePanel.tsx
│ └── styles.module.css
Component Structure
// docs-site/src/components/WidgetGenerator/index.tsx
import React, { useState, useEffect, useRef } from 'react';
import CodeBlock from '@theme/CodeBlock';
// Voice options by provider
const VOICE_OPTIONS = {
openai: ['alloy', 'echo', 'fable', 'onyx', 'nova', 'shimmer', 'coral'],
espeak: ['en-us-male', 'en-us-female', 'en-gb-male', 'en-gb-female'],
azure: ['en-US-JennyNeural', 'en-US-GuyNeural', 'en-GB-SoniaNeural'],
cartesia: ['default', 'professional', 'conversational']
};
export function WidgetGenerator() {
const [config, setConfig] = useState({
mode: 'compact', // 'basic', 'compact', 'advanced'
textSource: 'element', // 'element' or 'attribute'
sampleText: 'Welcome to Supernal TTS! Try changing the settings to customize your widget.',
provider: 'openai',
voice: 'fable',
speed: 1.0,
showBranding: true,
apiUrl: 'https://tts.supernal.ai',
apiKey: ''
});
const previewRef = useRef<HTMLDivElement>(null);
// Update available voices when provider changes
useEffect(() => {
const voices = VOICE_OPTIONS[config.provider as keyof typeof VOICE_OPTIONS] || [];
if (!voices.includes(config.voice)) {
setConfig(prev => ({ ...prev, voice: voices[0] || 'default' }));
}
}, [config.provider]);
// Reinitialize widget when config changes
useEffect(() => {
if (typeof window !== 'undefined' && window.SupernalTTS && previewRef.current) {
// Clear previous widget
previewRef.current.innerHTML = '';
// Create new widget element
const widgetEl = document.createElement('div');
widgetEl.className = 'supernal-tts-widget';
if (config.mode !== 'basic') {
widgetEl.dataset.controls = config.mode;
}
widgetEl.dataset.provider = config.provider;
widgetEl.dataset.voice = config.voice;
widgetEl.dataset.speed = config.speed.toString();
if (config.textSource === 'attribute') {
widgetEl.dataset.text = config.sampleText;
widgetEl.innerHTML = `<p>${config.sampleText}</p>`;
} else {
widgetEl.innerHTML = `<p>${config.sampleText}</p>`;
}
previewRef.current.appendChild(widgetEl);
// Initialize widget
window.SupernalTTS.init({
apiUrl: config.apiUrl,
apiKey: config.apiKey || undefined
});
}
}, [config]);
const generateHTML = () => {
const attrs = [
config.mode !== 'basic' && `data-controls="${config.mode}"`,
config.provider !== 'openai' && `data-provider="${config.provider}"`,
config.voice !== 'fable' && `data-voice="${config.voice}"`,
config.speed !== 1.0 && `data-speed="${config.speed}"`,
config.textSource === 'attribute' && `data-text="Your content here"`
].filter(Boolean).join('\n ');
return `<!-- Add to your <head> -->
<link rel="stylesheet" href="https://unpkg.com/@supernal-tts/widget/dist/widget.css">
<script src="https://unpkg.com/@supernal-tts/widget/dist/widget.js"></script>
<!-- Add to your content -->
<div class="supernal-tts-widget"${attrs ? '\n ' + attrs : ''}>
<p>Your content here</p>
</div>
<script>
SupernalTTS.init({
apiUrl: '${config.apiUrl}'${config.apiKey ? `,\n apiKey: '${config.apiKey}'` : ''}
});
</script>`;
};
const generateReact = () => {
return `import { useEffect, useRef } from 'react';
import '@supernal-tts/widget/dist/widget.css';
export function TTSContent() {
const widgetRef = useRef<HTMLDivElement>(null);
useEffect(() => {
import('@supernal-tts/widget/dist/widget.js').then(({ SupernalTTS }) => {
SupernalTTS.init({
apiUrl: '${config.apiUrl}'${config.apiKey ? `,\n apiKey: process.env.NEXT_PUBLIC_TTS_API_KEY` : ''}
});
});
}, []);
return (
<div ref={widgetRef}
className="supernal-tts-widget"${config.mode !== 'basic' ? `\n data-controls="${config.mode}"` : ''}${config.provider !== 'openai' ? `\n data-provider="${config.provider}"` : ''}${config.voice !== 'fable' ? `\n data-voice="${config.voice}"` : ''}${config.speed !== 1.0 ? `\n data-speed="${config.speed}"` : ''}>
<p>Your content here</p>
</div>
);
}`;
};
const generateNPM = () => {
return `// 1. Install the package
// npm install @supernal-tts/widget
import { SupernalTTS } from '@supernal-tts/widget';
import '@supernal-tts/widget/dist/widget.css';
// 2. Initialize
const tts = new SupernalTTS({
apiUrl: '${config.apiUrl}',${config.provider !== 'openai' ? `\n provider: '${config.provider}',` : ''}${config.voice !== 'fable' ? `\n voice: '${config.voice}',` : ''}${config.apiKey ? `\n apiKey: '${config.apiKey}'` : ''}
});
// 3. Add widget to an element
const element = document.querySelector('#my-content');
tts.addWidget(element, 'Text to speak', {
controls: '${config.mode}'${config.speed !== 1.0 ? `,\n speed: ${config.speed}` : ''}
});`;
};
return (
<div className="widget-generator">
<ConfigPanel config={config} onChange={setConfig} />
<PreviewPanel ref={previewRef} config={config} />
<CodePanel
html={generateHTML()}
react={generateReact()}
npm={generateNPM()}
/>
</div>
);
}
Styling
/* docs-site/src/components/WidgetGenerator/styles.module.css */
.generator-layout {
display: grid;
grid-template-columns: 300px 1fr;
gap: 24px;
margin: 24px 0;
}
@media (max-width: 996px) {
.generator-layout {
grid-template-columns: 1fr;
}
}
.config-panel {
background: var(--ifm-background-surface-color);
border-radius: 8px;
padding: 20px;
border: 1px solid var(--ifm-color-emphasis-200);
}
.preview-panel {
background: var(--ifm-background-surface-color);
border-radius: 8px;
padding: 24px;
border: 1px solid var(--ifm-color-emphasis-200);
min-height: 200px;
}
.code-panel {
grid-column: 1 / -1;
}
Implementation Details
Current Widget Data Attributes
Based on packages/@supernal-tts/widget/src/widget.ts:
data-text: Text content to speak (optional, defaults to element's textContent)data-voice: Voice to use (e.g., 'fable', 'alloy', 'nova')data-provider: TTS provider (e.g., 'openai', 'espeak', 'azure')data-speed: Playback speed (0.25 to 4.0, default 1.0)data-controls: Control mode ('basic', 'compact', 'advanced')
Widget Initialization Options
interface TTSConfig {
apiUrl?: string; // Default: 'https://tts.supernal.ai'
apiKey?: string; // Optional API key
provider?: string; // Default provider
voice?: string; // Default voice
showBranding?: boolean; // Show branding badge (default: true, hover-only)
}
Control Modes
Basic Mode (data-controls not set or empty):
- Simple play/pause button
- Uses default voice and provider
- Minimal UI footprint
Compact Mode (data-controls="compact"):
- Play/pause button
- Voice selector dropdown
- Speed control slider (0.25x - 4.0x)
- Top row: Play button + Voice selector + Branding badge
- Bottom row: Speed control
Advanced Mode (data-controls="advanced"):
- Full audio player controls
- Progress bar with seeking
- Time display
- All compact mode features
- ⚠️ Currently uses compact mode as fallback
CSS Customization
All widget classes are prefixed with .supernal-tts-:
.supernal-tts-widget- Main widget container.supernal-tts-play- Play button.supernal-tts-compact-widget- Compact mode container.supernal-tts-voice-select- Voice dropdown.supernal-tts-speed-slider- Speed control.supernal-badge- Branding badge (opacity: 0 by default, visible on hover)
CSS variables for theming:
--supernal-tts-bg: Background color--supernal-tts-border: Border color--supernal-tts-primary: Primary button color--supernal-tts-text: Text color
Timeline
- Planning & Spec: ✅ Complete
- UI components: 2 days (ConfigPanel, PreviewPanel, CodePanel)
- Code generation logic: 1 day (HTML, React, npm variants)
- Live preview integration: 1 day (Widget initialization and updates)
- Styling & responsive: 0.5 days
- Testing: 0.5 days
- Total: 5 days