- Add initLlmsSection() IIFE to app.js: copy buttons, API key persistence with eye toggle and saved badge, local service URL persistence, Connect/Disconnect toggle with server-side probe via /api/probe-url (avoids CORS), card turns green on success / red on failure - Substitute 0.0.0.0 → host.docker.internal before probing (0.0.0.0 not routable from Docker) - Add /api/local-containers, /api/probe-url, start/stop/restart endpoints to server.py - Rewrite AI Backends section into Local / Online API categories with Docker stack grid, local service cards (LLM/STT/TTS) with icons and editable URL inputs, online cloud API cards - Add bind mounts for static/ and server.py so changes take effect without image rebuild - Add dc-grid, llm-local-grid CSS with uniform minmax(310px,1fr) card layout - Fix VOICE_HOST_DIR default via .env so voice folders survive container recreation Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
216 lines
12 KiB
HTML
216 lines
12 KiB
HTML
<div class="section-head">
|
|
<span class="section-icon">▶</span>
|
|
<div class="section-title">
|
|
<h2>Try It Out</h2>
|
|
<p>Generate speech from text using any backend and voice. Also transcribe audio and re-speak it.</p>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- TTS Generation Playground -->
|
|
<div class="tab-content" id="tab-generation">
|
|
<div class="card">
|
|
<h2>TTS generation playground</h2>
|
|
<p class="card-subtitle">Pick any reachable TTS backend, fetch its voices, then synthesize text. WAV/NVIDIA clone backends preserve reference identity; instruction-control backends follow style better.</p>
|
|
</div>
|
|
<div class="card">
|
|
<h2>Generate speech</h2>
|
|
<p class="card-subtitle">Select a backend, fetch its voice list, then synthesize any text with optional style instruction.</p>
|
|
<div class="btn-row" style="align-items:flex-end;flex-wrap:wrap;gap:10px">
|
|
<div class="field">
|
|
<label>Backend</label>
|
|
<select id="tts-backend-select"><option value="">Checking backends...</option></select>
|
|
</div>
|
|
<div class="backend-help" id="tts-backend-help" aria-live="polite">
|
|
<strong>Checking available TTS backends...</strong>
|
|
</div>
|
|
<div class="field">
|
|
<label>Backend voice</label>
|
|
<div style="display:flex;gap:8px">
|
|
<button class="btn-secondary" id="fetch-tts-voices-btn">Fetch voices</button>
|
|
<select id="tts-voice-select"><option value="">— select after fetch —</option></select>
|
|
</div>
|
|
</div>
|
|
<div class="field">
|
|
<label>Playback</label>
|
|
<select id="preview-playback-mode">
|
|
<option value="settings">Settings default</option>
|
|
<option value="streaming">Streaming only</option>
|
|
<option value="auto">Stream with fallback</option>
|
|
<option value="buffered">Buffered WAV</option>
|
|
</select>
|
|
</div>
|
|
</div>
|
|
<p class="note" style="margin-top:-4px;margin-bottom:10px">After changing active voices, restart the TTS container so the engine reads the updated voice folder.</p>
|
|
<div class="preview-match-panel" id="preview-match-panel" hidden>
|
|
<div class="preview-match-meta">
|
|
<div class="preview-match-title" id="preview-match-title">Reference voice</div>
|
|
<div class="preview-match-detail" id="preview-match-detail"></div>
|
|
<div class="preview-match-warning" id="preview-match-warning"></div>
|
|
<div class="preview-match-actions">
|
|
<button class="btn-secondary" id="preview-ref-play" type="button">Play reference WAV</button>
|
|
<button class="btn-secondary" id="preview-ref-use-text" type="button">Use reference text</button>
|
|
<button class="btn-primary" id="preview-ref-synth" type="button">Synthesize reference text</button>
|
|
</div>
|
|
</div>
|
|
<div class="preview-match-meta">
|
|
<div class="preview-match-transcript" id="preview-match-transcript">No reference text saved for this voice.</div>
|
|
<audio id="preview-ref-audio" controls></audio>
|
|
</div>
|
|
</div>
|
|
<div class="field">
|
|
<label>Target Text (text to synthesize)</label>
|
|
<textarea id="preview-text-area" placeholder="Enter the text you want to synthesize…">Hello! This is a voice preview from TTS Voice Creator - Clone and Design.</textarea>
|
|
</div>
|
|
<div class="field">
|
|
<label>Style Instruction <span style="font-weight:400">(optional)</span></label>
|
|
<input type="text" id="preview-style-instruction" placeholder="Optional style/emotion instruction, e.g. speak slowly and calmly, excited tone">
|
|
<span class="note">This is sent as <code>instruct</code>. Voice Clone/Base and Streaming are fastest; CustomVoice and Voice Design are style-aware.</span>
|
|
</div>
|
|
<div class="btn-row">
|
|
<button class="btn-primary" id="preview-btn">▶ Generate & play</button>
|
|
<button class="btn-secondary" id="save-preview-mp3-btn" disabled>↓ Save MP3</button>
|
|
<button class="btn-secondary" id="save-preview-btn" disabled>↓ Save WAV</button>
|
|
</div>
|
|
<audio id="preview-audio" controls style="display:none"></audio>
|
|
</div>
|
|
</div><!-- /tab-generation -->
|
|
|
|
<!-- STT -> TTS -->
|
|
<div class="tab-content" id="tab-stt-tts">
|
|
<div class="card">
|
|
<h2>STT → TTS workspace</h2>
|
|
<p class="card-subtitle">Upload speech audio, transcribe it with the configured STT endpoint, then synthesize the resulting text with any available TTS backend.</p>
|
|
</div>
|
|
<div class="card">
|
|
<h2>Source speech</h2>
|
|
<p class="card-subtitle">Record from your microphone or upload an audio file, then transcribe it.</p>
|
|
<div class="settings-grid">
|
|
<div class="s-field">
|
|
<label>Speech recognition</label>
|
|
<div style="display:flex;gap:8px">
|
|
<select id="stt-tts-stt-backend"><option value="configured">Configured Whisper/STT</option></select>
|
|
<button class="btn-secondary" id="stt-tts-refresh-stt-btn" type="button">Refresh</button>
|
|
</div>
|
|
<span class="s-hint" id="stt-tts-stt-help">Uses Settings → Whisper/STT URL by default.</span>
|
|
</div>
|
|
<div class="s-field">
|
|
<label>Speech audio</label>
|
|
<div class="btn-row" style="gap:8px;align-items:center">
|
|
<input type="file" id="stt-tts-file" accept="audio/*,video/*">
|
|
<button class="btn-secondary" id="stt-tts-rec-start" type="button" title="Record from microphone">🎤 Record</button>
|
|
<button class="btn-secondary" id="stt-tts-rec-stop" type="button" disabled>Stop</button>
|
|
<span class="mic-timer" id="stt-tts-rec-time">0:00</span>
|
|
</div>
|
|
<span class="s-hint">Record or upload speech audio, then transcribe it with the selected recognition engine.</span>
|
|
</div>
|
|
<div class="s-field">
|
|
<label>Source preview</label>
|
|
<audio id="stt-tts-source-audio" controls style="display:none;width:100%"></audio>
|
|
<span class="s-hint" id="stt-tts-source-status">No source audio loaded.</span>
|
|
</div>
|
|
</div>
|
|
<div class="btn-row">
|
|
<button class="btn-secondary" id="stt-tts-transcribe-btn" disabled>Transcribe</button>
|
|
<button class="btn-secondary" id="stt-tts-copy-preview-btn" disabled>Copy to TTS playground</button>
|
|
</div>
|
|
<div class="field">
|
|
<label>Transcribed text</label>
|
|
<textarea id="stt-tts-text" placeholder="Transcribed text appears here. Edit before synthesis if needed."></textarea>
|
|
</div>
|
|
</div>
|
|
<div class="card">
|
|
<h2>Synthesize transcription</h2>
|
|
<p class="card-subtitle">Choose a TTS backend and voice, then generate audio from the transcribed text.</p>
|
|
<div class="btn-row" style="align-items:flex-end;flex-wrap:wrap;gap:10px">
|
|
<div class="field">
|
|
<label>Backend</label>
|
|
<select id="stt-tts-backend-select"><option value="">Checking backends...</option></select>
|
|
</div>
|
|
<div class="backend-help" id="stt-tts-backend-help" aria-live="polite">
|
|
<strong>Checking available TTS backends...</strong>
|
|
</div>
|
|
<div class="field">
|
|
<label>Backend voice</label>
|
|
<div style="display:flex;gap:8px">
|
|
<button class="btn-secondary" id="stt-tts-fetch-voices-btn">Fetch voices</button>
|
|
<select id="stt-tts-voice-select"><option value="">-- select after fetch --</option></select>
|
|
</div>
|
|
</div>
|
|
<div class="field">
|
|
<label>Playback</label>
|
|
<select id="stt-tts-playback-mode">
|
|
<option value="settings">Settings default</option>
|
|
<option value="streaming">Streaming only</option>
|
|
<option value="auto">Stream with fallback</option>
|
|
<option value="buffered">Buffered WAV</option>
|
|
</select>
|
|
</div>
|
|
</div>
|
|
<div class="field">
|
|
<label>Style Instruction <span style="font-weight:400">(optional)</span></label>
|
|
<input type="text" id="stt-tts-style-instruction" placeholder="Optional style/emotion instruction">
|
|
</div>
|
|
<div class="btn-row">
|
|
<button class="btn-primary" id="stt-tts-generate-btn">Generate & play</button>
|
|
<button class="btn-secondary" id="stt-tts-save-mp3-btn" disabled>Save MP3</button>
|
|
<button class="btn-secondary" id="stt-tts-save-wav-btn" disabled>Save WAV</button>
|
|
</div>
|
|
<audio id="stt-tts-output-audio" controls style="display:none"></audio>
|
|
</div>
|
|
</div><!-- /tab-stt-tts -->
|
|
|
|
<!-- Performance benchmarking -->
|
|
<div class="tab-content" id="tab-performance">
|
|
<div class="card">
|
|
<h2>Performance benchmark</h2>
|
|
<p class="card-subtitle">Measure synthesis latency and real-time factor for any backend and voice.</p>
|
|
<div class="btn-row" style="align-items:flex-end;flex-wrap:wrap;gap:10px">
|
|
<div class="field">
|
|
<label>Backend</label>
|
|
<select id="perf-backend-select"><option value="">Checking backends...</option></select>
|
|
</div>
|
|
<div class="field">
|
|
<label>Voice</label>
|
|
<div style="display:flex;gap:8px">
|
|
<button class="btn-secondary" id="perf-fetch-voices-btn">Fetch voices</button>
|
|
<select id="perf-voice-select"><option value="">— select after fetch —</option></select>
|
|
</div>
|
|
</div>
|
|
<div class="field">
|
|
<label>Runs</label>
|
|
<select id="perf-runs">
|
|
<option value="1">1 run</option>
|
|
<option value="3" selected>3 runs</option>
|
|
<option value="5">5 runs</option>
|
|
<option value="10">10 runs</option>
|
|
</select>
|
|
</div>
|
|
</div>
|
|
<div class="field">
|
|
<label>Sample text</label>
|
|
<textarea id="perf-text" rows="3">Hello, how are you today? Please read this sample clearly for a fair voice benchmark.</textarea>
|
|
</div>
|
|
<div class="btn-row">
|
|
<button class="btn-primary" id="perf-run-btn">▶ Run benchmark</button>
|
|
<button class="btn-secondary" id="perf-clear-btn">Clear results</button>
|
|
</div>
|
|
<div id="perf-progress" class="note" style="display:none"></div>
|
|
</div>
|
|
<div class="card" id="perf-results-card" style="display:none">
|
|
<h2>Results</h2>
|
|
<p class="card-subtitle">Latency per run. RTF = synthesis time / audio duration (lower is better).</p>
|
|
<div class="perf-summary" id="perf-summary"></div>
|
|
<div class="perf-table-wrap">
|
|
<table class="perf-table" id="perf-table">
|
|
<thead>
|
|
<tr>
|
|
<th>#</th><th>Backend</th><th>Voice</th>
|
|
<th>Latency (ms)</th><th>Audio (s)</th><th>RTF</th><th>Status</th>
|
|
</tr>
|
|
</thead>
|
|
<tbody id="perf-tbody"></tbody>
|
|
</table>
|
|
</div>
|
|
</div>
|
|
</div><!-- /tab-performance -->
|