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
  • 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