tts-voice-creator-clone-and.../static/sections/s-llms.html
mARTin-B78 2a2c34fb56 Fix invisible Add Custom engine dialog and a TDZ error on Engines tab (v1.14.5)
Two unclosed <div class="card"> tags in the TTS settings markup left the
add-engine dialog nested inside the TTS panel's DOM subtree, so it
rendered at zero size whenever another Engines sub-tab (e.g. Speech
Recognition) was active. Also fixed a `let` declared after its first
use, which threw a ReferenceError on every Engines-tab page load.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-08 21:45:53 +02:00

605 lines
32 KiB
HTML

<style>
.header-cat-block { display: none !important; }
.header-cat-block.is-active { display: flex !important; }
</style>
<div class="section-head engines-page-head">
<div class="engines-head-main">
<span class="section-icon"><span class="mdi mdi-brain"></span></span>
<div class="section-title">
<h2>Engines</h2>
<p>Local and cloud services for language generation, speech recognition, and synthesis.</p>
</div>
</div>
<div class="engines-head-context header-cat-block" data-engines-cat="llm">
<div class="engines-context-copy">
<h3 class="llm-sec-title">Language Models</h3>
<p class="llm-sec-note">Pick the <strong>Active Language Model</strong> below. Cards connect engines and apply a URL with <strong>Use as LLM</strong>.</p>
</div>
</div>
<div class="engines-head-context header-cat-block" data-engines-cat="stt">
<div class="engines-context-copy">
<h3 class="llm-sec-title">Speech Recognition</h3>
<p class="llm-sec-note">Docker stack containers appear first, with local runners and cloud APIs below.</p>
</div>
</div>
<div class="engines-head-context header-cat-block" data-engines-cat="tts">
<div class="engines-context-copy">
<h3 class="llm-sec-title">Text to Speech</h3>
<p class="llm-sec-note">Docker-detected engines and local presets are listed together. Apply a URL with <strong>Use as TTS</strong>.</p>
</div>
<div class="engines-context-actions">
<button class="btn-secondary" type="button" onclick="openAddEngineDialog('tts')"><span class="mdi mdi-plus"></span> Add Custom</button>
<button class="btn-secondary dc-refresh-btn" type="button"><span class="mdi mdi-refresh"></span> Refresh</button>
<span class="llm-free-badge llm-free-local">100% Local</span>
</div>
</div>
</div>
<!-- ── Language Models sub-page ──────────────────────────── -->
<div class="s-engines-page" data-page="llm">
<!-- ── Active Language Model (global picker) ──────────────── -->
<div class="llm-active-panel card">
<div class="llm-active-head">
<span class="llm-active-icon"><span class="mdi mdi-star-four-points"></span></span>
<div>
<h4 class="llm-active-title">Active Language Model</h4>
<p class="llm-active-sub">Default endpoint &amp; model for all LLM tasks. Per-task dropdowns can still override it.</p>
</div>
</div>
<div class="llm-active-controls">
<label class="llm-active-lbl"><span class="mdi mdi-server-network"></span> Endpoint</label>
<select id="llm-active-url" class="llm-active-url-inp" style="appearance: auto; cursor: pointer; padding-right: 25px;">
<option value="">— Select an endpoint —</option>
</select>
<label class="llm-active-lbl"><span class="mdi mdi-brain"></span> Model</label>
<select id="llm-active-model" aria-label="Active language model"><option value="">— fetch models —</option></select>
<button id="llm-active-refresh" class="btn-secondary" type="button" title="Fetch models"><span class="mdi mdi-refresh"></span></button>
<span id="llm-active-status" class="llm-active-status"></span>
</div>
</div>
<div style="display:flex;justify-content:space-between;align-items:center;margin-top:20px;margin-bottom:12px">
<h4 class="llm-local-cat" style="margin:0;text-transform:uppercase">Local</h4>
<button class="btn-primary" type="button" onclick="openAddEngineDialog('llm')"><span class="mdi mdi-plus"></span> Add Custom</button>
</div>
<div id="dc-grid-llm" class="dc-grid" style="margin-bottom:14px"></div>
<div style="display:flex;justify-content:space-between;align-items:center;margin-top:28px;margin-bottom:12px">
<h4 class="llm-local-cat" style="margin:0;text-transform:uppercase"><span class="mdi mdi-earth"></span> Cloud APIs <span class="llm-free-badge" style="margin-left:8px;vertical-align:middle;text-transform:none">Free tiers</span></h4>
<button class="btn-primary" type="button" onclick="openAddEngineDialog('llm')"><span class="mdi mdi-plus"></span> Add Custom</button>
</div>
<div class="llm-service-grid">
<div class="llm-card llm-card-highlight">
<div class="llm-card-head">
<span class="llm-card-icon"><span class="mdi mdi-lightning-bolt"></span></span>
<div>
<div class="llm-card-name">Groq</div>
<div class="llm-card-sub">Ultra-fast inference &middot; OpenAI-compatible</div>
</div>
<span class="llm-tier-badge llm-tier-free">Free</span>
</div>
<div class="llm-card-stats">
<span>30 000 tokens / min</span>
<span>14 400 req / day</span>
<span>Lowest latency</span>
</div>
<div class="llm-field-row">
<label class="llm-label">API key</label>
<input type="password" class="llm-input" placeholder="gsk_&hellip;" data-llm-key="groq_llm">
<a class="llm-link" href="https://console.groq.com" target="_blank" rel="noopener">Get key <span class="mdi mdi-open-in-new link-icon"></span></a>
</div>
<div class="llm-endpoint">
<span class="llm-endpoint-label">Endpoint</span>
<code>https://api.groq.com/openai/v1</code>
</div>
<div class="llm-models">
<span class="llm-model-tag">llama-3.3-70b-versatile</span>
<span class="llm-model-tag">qwen-qwq-32b</span>
<span class="llm-model-tag">deepseek-r1-distill-llama-70b</span>
</div>
</div>
<div class="llm-card">
<div class="llm-card-head">
<span class="llm-card-icon"><span class="mdi mdi-earth"></span></span>
<div>
<div class="llm-card-name">OpenRouter</div>
<div class="llm-card-sub">50+ free models &middot; OpenAI-compatible</div>
</div>
<span class="llm-tier-badge llm-tier-free">Free</span>
</div>
<div class="llm-card-stats">
<span>20 req / min</span>
<span>200 req / day (free models)</span>
<span>Single API for all models</span>
</div>
<div class="llm-field-row">
<label class="llm-label">API key</label>
<input type="password" class="llm-input" placeholder="sk-or-&hellip;" data-llm-key="openrouter">
<a class="llm-link" href="https://openrouter.ai" target="_blank" rel="noopener">Get key <span class="mdi mdi-open-in-new link-icon"></span></a>
</div>
<div class="llm-endpoint">
<span class="llm-endpoint-label">Endpoint</span>
<code>https://openrouter.ai/api/v1</code>
</div>
<div class="llm-models">
<span class="llm-model-tag">qwen/qwen3-235b-a22b:free</span>
<span class="llm-model-tag">deepseek/deepseek-r1-0528:free</span>
<span class="llm-model-tag">mistralai/mistral-7b-instruct:free</span>
</div>
</div>
<div class="llm-card">
<div class="llm-card-head">
<span class="llm-card-icon"><span class="mdi mdi-diamond-outline"></span></span>
<div>
<div class="llm-card-name">Google Gemini</div>
<div class="llm-card-sub">Gemini 2.5 Flash &middot; Generous free tier</div>
</div>
<span class="llm-tier-badge llm-tier-free">Free</span>
</div>
<div class="llm-card-stats">
<span>250 000 tokens / min</span>
<span>15 req / min free</span>
<span>1M context window</span>
</div>
<div class="llm-field-row">
<label class="llm-label">API key</label>
<input type="password" class="llm-input" placeholder="AIza&hellip;" data-llm-key="gemini">
<a class="llm-link" href="https://ai.google.dev" target="_blank" rel="noopener">Get key <span class="mdi mdi-open-in-new link-icon"></span></a>
</div>
<div class="llm-endpoint">
<span class="llm-endpoint-label">Endpoint</span>
<code>https://generativelanguage.googleapis.com/v1beta/openai/</code>
</div>
<div class="llm-info-note">Uses OpenAI-compat wrapper &mdash; use model <code>gemini-2.5-flash</code></div>
</div>
<div class="llm-card">
<div class="llm-card-head">
<span class="llm-card-icon"><span class="mdi mdi-alpha-m-circle-outline"></span></span>
<div>
<div class="llm-card-name">Mistral AI</div>
<div class="llm-card-sub">OpenAI-compatible &middot; EU-based</div>
</div>
<span class="llm-tier-badge llm-tier-free">Free</span>
</div>
<div class="llm-card-stats">
<span>1B tokens / month</span>
<span>2 req / min free</span>
<span>GDPR-compliant</span>
</div>
<div class="llm-field-row">
<label class="llm-label">API key</label>
<input type="password" class="llm-input" placeholder="Mistral key&hellip;" data-llm-key="mistral">
<a class="llm-link" href="https://mistral.ai" target="_blank" rel="noopener">Get key <span class="mdi mdi-open-in-new link-icon"></span></a>
</div>
<div class="llm-endpoint">
<span class="llm-endpoint-label">Endpoint</span>
<code>https://api.mistral.ai/v1</code>
</div>
<div class="llm-models">
<span class="llm-model-tag">mistral-small-latest</span>
<span class="llm-model-tag">mistral-large-latest</span>
</div>
</div>
</div>
<h4 class="llm-local-cat" style="margin-top: 40px; text-transform: uppercase;">Additional Settings</h4>
<div class="card" style="margin-bottom:24px;">
<div class="s-page-head">
<h3>LLM Refinement Defaults</h3>
<p>Automatic text cleanup after transcription</p>
</div>
<div style="display: flex; flex-direction: column; gap: 16px; margin-bottom: 24px;">
<div class="s-field">
<label>Auto-refine after transcription</label>
<select id="s-auto-refine">
<option value="off">Off — show raw transcription</option>
<option value="on">On — refine automatically</option>
</select>
<span class="s-hint">When on, the LLM refinement runs immediately after each capture.</span>
</div>
<div class="s-field">
<label>Refinement model</label>
<input type="text" id="s-refine-model" placeholder="e.g. llama3.2, qwen3:8b, gpt-4o-mini">
<span class="s-hint">Model name sent to the active LLM URL. Leave empty to use the app's current default.</span>
</div>
</div>
<div class="s-toggle-grid">
<label class="s-toggle-row"><input type="checkbox" id="s-refine-fillers"><div><span class="s-toggle-title">Remove filler words</span><span class="s-hint">Strips um, uh, like, you know, sort of.</span></div></label>
<label class="s-toggle-row"><input type="checkbox" id="s-refine-repetitions"><div><span class="s-toggle-title">Remove repetitions</span><span class="s-hint">Collapses repeated words and phrases.</span></div></label>
<label class="s-toggle-row"><input type="checkbox" id="s-refine-corrections"><div><span class="s-toggle-title">Fix grammar &amp; spelling</span><span class="s-hint">Light corrections without changing meaning.</span></div></label>
<label class="s-toggle-row"><input type="checkbox" id="s-refine-punctuation"><div><span class="s-toggle-title">Fix punctuation</span><span class="s-hint">Adds missing commas, periods, and sentence casing.</span></div></label>
</div>
<div class="s-page-actions" style="margin-top: 15px;">
<button class="btn-primary s-save-btn">Save settings</button>
</div>
</div>
</div><!-- /llm -->
<!-- ── Speech to Text sub-page ───────────────────────────── -->
<div class="s-engines-page" data-page="stt">
<div style="display:flex;justify-content:space-between;align-items:center;margin-top:20px;margin-bottom:12px">
<h4 class="llm-local-cat" style="margin:0;text-transform:uppercase">Local</h4>
<button class="btn-primary" type="button" onclick="openAddEngineDialog('stt')"><span class="mdi mdi-plus"></span> Add Custom</button>
</div>
<div id="dc-grid-stt" class="dc-grid" style="margin-bottom:14px"></div>
<div style="display:flex;justify-content:space-between;align-items:center;margin-top:28px;margin-bottom:12px">
<div style="display:flex;gap:8px;align-items:center">
<span class="mdi mdi-cloud-outline"></span>
<h4 class="llm-local-cat" style="margin:0;text-transform:uppercase">Cloud APIs</h4>
<span class="llm-free-badge">Free tiers</span>
</div>
<button class="btn-primary" type="button" onclick="openAddEngineDialog('stt')"><span class="mdi mdi-plus"></span> Add Custom</button>
</div>
<div class="llm-service-grid">
<div class="llm-card llm-card-highlight">
<div class="llm-card-head">
<span class="llm-card-icon"><span class="mdi mdi-lightning-bolt"></span></span>
<div>
<div class="llm-card-name">Groq Whisper</div>
<div class="llm-card-sub">whisper-large-v3-turbo &middot; OpenAI-compatible</div>
</div>
<span class="llm-tier-badge llm-tier-free">Free</span>
</div>
<div class="llm-card-stats">
<span>2 000 req / day</span>
<span><span class="mdi mdi-lightning-bolt"></span> Fastest cloud STT</span>
<span><span class="mdi mdi-memory"></span> Cloud &middot; 0 VRAM</span>
</div>
<div class="llm-field-row">
<label class="llm-label">API key</label>
<input type="password" class="llm-input" placeholder="gsk_&hellip;" data-llm-key="groq_stt">
<a class="llm-link" href="https://console.groq.com" target="_blank" rel="noopener">Get key <span class="mdi mdi-open-in-new link-icon"></span></a>
</div>
<div class="llm-endpoint">
<span class="llm-endpoint-label">Endpoint</span>
<code>https://api.groq.com/openai/v1</code>
</div>
<div class="llm-models">
<span class="llm-model-tag">whisper-large-v3-turbo</span>
<span class="llm-model-tag">whisper-large-v3</span>
<span class="llm-model-tag">distil-whisper-large-v3-en</span>
</div>
<div class="llm-info-note">Save key in <strong>Settings &rarr; Groq API key</strong> to enable <em>Groq Whisper</em> in the STT dropdown.</div>
</div>
<div class="llm-card">
<div class="llm-card-head">
<span class="llm-card-icon"><span class="mdi mdi-emoticon-happy-outline"></span></span>
<div>
<div class="llm-card-name">HuggingFace Inference</div>
<div class="llm-card-sub">Serverless Whisper models</div>
</div>
<span class="llm-tier-badge llm-tier-free">Free</span>
</div>
<div class="llm-card-stats">
<span>~1 000 req / day</span>
<span>Slower cold starts</span>
<span>Many model variants</span>
</div>
<div class="llm-field-row">
<label class="llm-label">API key</label>
<input type="password" class="llm-input" placeholder="hf_&hellip;" data-llm-key="hf_stt">
<a class="llm-link" href="https://huggingface.co/settings/tokens" target="_blank" rel="noopener">Get key <span class="mdi mdi-open-in-new link-icon"></span></a>
</div>
<div class="llm-endpoint">
<span class="llm-endpoint-label">Endpoint</span>
<code>https://api-inference.huggingface.co/models/openai/whisper-large-v3</code>
</div>
</div>
<div class="llm-card">
<div class="llm-card-head">
<span class="llm-card-icon"><span class="mdi mdi-content-copy"></span></span>
<div>
<div class="llm-card-name">AssemblyAI</div>
<div class="llm-card-sub">High-accuracy transcription + speaker diarization</div>
</div>
<span class="llm-tier-badge llm-tier-free">Free</span>
</div>
<div class="llm-card-stats">
<span>100 h lifetime</span>
<span>Speaker labels</span>
<span>Auto-chapters</span>
</div>
<div class="llm-field-row">
<label class="llm-label">API key</label>
<input type="password" class="llm-input" placeholder="AssemblyAI key&hellip;" data-llm-key="assemblyai">
<a class="llm-link" href="https://www.assemblyai.com" target="_blank" rel="noopener">Get key <span class="mdi mdi-open-in-new link-icon"></span></a>
</div>
<div class="llm-endpoint">
<span class="llm-endpoint-label">Endpoint</span>
<code>https://api.assemblyai.com/v2/transcript</code>
</div>
</div>
</div>
<div style="display:flex;justify-content:space-between;align-items:center;margin-top:28px;margin-bottom:12px">
<h4 class="llm-local-cat" style="margin:0;text-transform:uppercase">Additional Settings</h4>
</div>
<div class="card" style="margin-bottom: 24px;">
<div class="s-page-head">
<h3>STT Connections</h3>
<p>Set up endpoints for Speech-to-Text backends.</p>
</div>
<div class="settings-grid stt-settings-grid">
<div class="s-field stt-url-field">
<label>Active STT URL</label>
<input type="text" id="s-whisper-url" placeholder="http://host.docker.internal:8010">
<span class="s-hint">Default recognition endpoint. Expected: <code>POST /v1/audio/transcriptions</code>.</span>
<div class="btn-row settings-mini-actions">
<button class="btn-secondary" id="s-use-parakeet-asr" type="button">Use Parakeet</button>
<button class="btn-secondary" id="s-use-nvidia-router" type="button">Use router</button>
<button class="btn-secondary" id="s-use-faster-whisper" type="button">Use faster-whisper</button>
<button class="btn-secondary" id="s-use-whisper-cpp" type="button">Use whisper.cpp</button>
</div>
</div>
<div class="s-field">
<label>faster-whisper-server URL <span class="s-label-note">(CTranslate2 GPU)</span></label>
<input type="text" id="s-faster-whisper-url" placeholder="http://host.docker.internal:8000">
<span class="s-hint">GPU-accelerated Whisper via CTranslate2. ~70&times; RT &middot; 1.5 GB VRAM.</span>
</div>
<div class="s-field">
<label>whisper.cpp URL <span class="s-label-note">(CPU/CUDA)</span></label>
<input type="text" id="s-whisper-cpp-url" placeholder="http://host.docker.internal:8080">
<span class="s-hint">Lightweight C++ Whisper server. ~8&ndash;15&times; RT CPU &middot; ~1 GB RAM.</span>
</div>
<div class="s-field">
<label>Groq API key <span class="s-label-note">(Groq Whisper &amp; LLM)</span></label>
<div class="s-key-row">
<input type="password" id="s-groq-api-key" placeholder="gsk_&hellip;" autocomplete="off">
<button type="button" class="s-eye-btn" data-target="s-groq-api-key" aria-label="Toggle API key visibility"><span class="mdi mdi-eye-outline"></span></button>
</div>
<span class="s-hint">Used for Groq Whisper STT and LLM. Free: 2 000 req/day.</span>
</div>
</div>
<div class="s-page-actions" style="margin-top: 15px;">
<button class="btn-primary s-save-btn">Save connections</button>
</div>
</div>
<!-- Inline STT test panel -->
<div class="stt-test-panel" id="stt-test-panel">
<div class="stt-test-row">
<span class="stt-test-label">Quick test</span>
<select id="stt-test-backend" class="stt-test-select"></select>
<button id="stt-test-mic-btn" class="stt-test-mic" title="Hold to record, click again to stop">
<span class="mdi mdi-microphone"></span>
</button>
<span id="stt-test-status" class="stt-test-status">Ready</span>
</div>
<div id="stt-test-result" class="stt-test-result" style="display:none"></div>
</div>
<div class="card" style="margin-top: 24px;">
<div class="s-page-head">
<h3>STT Transcription Defaults</h3>
<p>Default language and preferred STT backend for captures.</p>
</div>
<div class="settings-grid settings-behavior-grid">
<div class="s-field">
<label>Default language</label>
<select id="s-stt-language">
<option value="">Auto-detect</option>
<option value="en">English</option>
<option value="de">German</option>
<option value="fr">French</option>
<option value="es">Spanish</option>
<option value="it">Italian</option>
<option value="pt">Portuguese</option>
<option value="nl">Dutch</option>
<option value="ja">Japanese</option>
<option value="zh">Chinese</option>
</select>
</div>
<div class="s-field">
<label>Preferred STT backend</label>
<select id="s-stt-preferred-backend">
<option value="">Use active STT URL (from Connections)</option>
<option value="groq">Groq Whisper — cloud, fastest</option>
<option value="faster-whisper">faster-whisper-server — GPU local</option>
<option value="whisper-cpp">whisper.cpp — CPU/CUDA local</option>
<option value="parakeet">NVIDIA Parakeet — GPU local</option>
</select>
<span class="s-hint">Overrides the active STT URL for STT-TTS panel captures.</span>
</div>
</div>
<div class="s-page-actions" style="margin-top: 15px;">
<button class="btn-primary s-save-btn">Save settings</button>
</div>
</div>
</div><!-- /stt -->
<!-- ── Text to Speech sub-page ───────────────────────────── -->
<div class="s-engines-page" data-page="tts">
<div style="display:flex;justify-content:space-between;align-items:center;margin-top:20px;margin-bottom:12px">
<h4 class="llm-local-cat" style="margin:0;text-transform:uppercase">Local</h4>
<button class="btn-primary" type="button" onclick="openAddEngineDialog('tts')"><span class="mdi mdi-plus"></span> Add Custom</button>
</div>
<div id="dc-grid-tts" class="dc-grid" style="margin-bottom:14px"></div>
<div style="display:flex;justify-content:space-between;align-items:center;margin-top:28px;margin-bottom:12px">
<div style="display:flex;gap:8px;align-items:center">
<span class="mdi mdi-cloud-outline"></span>
<h4 class="llm-local-cat" style="margin:0;text-transform:uppercase">Cloud APIs</h4>
<span class="llm-free-badge">Free tiers</span>
</div>
<button class="btn-primary" type="button" onclick="openAddEngineDialog('tts')"><span class="mdi mdi-plus"></span> Add Custom</button>
</div>
<div class="llm-service-grid">
<div class="llm-card llm-card-highlight">
<div class="llm-card-head">
<span class="llm-card-icon"><span class="mdi mdi-waveform"></span></span>
<div>
<div class="llm-card-name">ElevenLabs</div>
<div class="llm-card-sub">High-quality voice cloning &amp; synthesis</div>
</div>
<span class="llm-tier-badge llm-tier-free">Free</span>
</div>
<div class="llm-card-stats">
<span>10 000 chars / month</span>
<span>2 500 char / request max</span>
<span>Voice cloning supported</span>
</div>
<div class="llm-field-row">
<label class="llm-label">API key</label>
<input type="password" class="llm-input" placeholder="xi-api-key&hellip;" data-llm-key="elevenlabs">
<a class="llm-link" href="https://elevenlabs.io" target="_blank" rel="noopener">Get key <span class="mdi mdi-open-in-new link-icon"></span></a>
</div>
<div class="llm-endpoint">
<span class="llm-endpoint-label">Endpoint</span>
<code>https://api.elevenlabs.io/v1/text-to-speech</code>
</div>
</div>
<div class="llm-card">
<div class="llm-card-head">
<span class="llm-card-icon"><span class="mdi mdi-fish"></span></span>
<div>
<div class="llm-card-name">Fish Audio</div>
<div class="llm-card-sub">Voice cloning &amp; multilingual TTS</div>
</div>
<span class="llm-tier-badge llm-tier-free">Free</span>
</div>
<div class="llm-card-stats">
<span>1 h audio / month</span>
<span>100 req / min</span>
<span>30+ languages</span>
</div>
<div class="llm-field-row">
<label class="llm-label">API key</label>
<input type="password" class="llm-input" placeholder="Fish Audio key&hellip;" data-llm-key="fish_audio">
<a class="llm-link" href="https://fish.audio" target="_blank" rel="noopener">Get key <span class="mdi mdi-open-in-new link-icon"></span></a>
</div>
<div class="llm-endpoint">
<span class="llm-endpoint-label">Endpoint</span>
<code>https://api.fish.audio/v1/tts</code>
</div>
</div>
<div class="llm-card">
<div class="llm-card-head">
<span class="llm-card-icon"><span class="mdi mdi-music-note"></span></span>
<div>
<div class="llm-card-name">Kokoro TTS</div>
<div class="llm-card-sub">82M model &middot; HuggingFace Spaces demo</div>
</div>
<span class="llm-tier-badge llm-tier-demo">Demo</span>
</div>
<div class="llm-card-stats">
<span>Free web demo</span>
<span>&lt;$1 per 1M chars (paid)</span>
<span>High naturalness</span>
</div>
<div class="llm-info-box">
Use the HF Spaces web demo for quick tests, or run Kokoro locally via Docker for production use.
<br>
<a class="llm-link-inline" href="https://huggingface.co/spaces/hexgrad/Kokoro-TTS" target="_blank" rel="noopener"><span class="mdi mdi-arrow-right"></span> Open Kokoro HF Space</a>
</div>
</div>
</div> <!-- CLOSE llm-service-grid -->
<h4 class="llm-local-cat" style="margin-top: 40px; text-transform: uppercase;">Additional Settings</h4>
<div class="card" style="margin-bottom: 24px;">
<div class="s-page-head">
<h3>TTS Playback & API Behavior</h3>
<p>Controls how previews play and how OpenAI-compatible requests are shaped.</p>
</div>
<div class="settings-grid settings-behavior-grid">
<div class="s-field">
<label>TTS preview playback</label>
<select id="s-tts-stream-mode">
<option value="auto">Stream when available, then fall back</option>
<option value="streaming">Streaming only</option>
<option value="buffered">Buffered WAV only</option>
</select>
<span class="s-hint">Buffered keeps Save WAV available. Streaming starts sooner.</span>
</div>
<div class="s-field">
<label>OpenAI-compatible request style</label>
<select id="s-tts-backend">
<option value="openai">Qwen3-TTS / OpenAI</option>
<option value="localai">LocalAI (/tts)</option>
<option value="pocket">Pocket-TTS</option>
<option value="nvidia_magpie">NVIDIA Magpie / OpenAI</option>
</select>
<span class="s-hint">Controls payload shape for the normal TTS API URL.</span>
</div>
</div>
</div>
<div class="card" style="margin-bottom: 24px;">
<div class="s-page-head">
<h3>Default Playback Voice</h3>
<p>Pre-selected voice in the STT-TTS panel</p>
</div>
<div class="settings-grid settings-behavior-grid">
<div class="s-field">
<label>Default voice</label>
<select id="s-captures-default-voice">
<option value="">None — select manually</option>
</select>
</div>
</div>
</div>
<div class="s-page-actions" style="margin-top: 15px;">
<button class="btn-primary s-save-btn">Save settings</button>
<button class="btn-secondary s-reload-btn">Reload settings</button>
</div>
</div><!-- /tts -->
<!-- ── Add Custom Engine dialog ─────────────────────────────── -->
<dialog id="add-engine-dlg" class="add-engine-dlg">
<div class="add-engine-form">
<h3 class="add-engine-title"><span class="mdi mdi-plus-circle-outline"></span> Add Custom Engine</h3>
<div class="add-engine-field">
<label for="aed-name">Name</label>
<input id="aed-name" type="text" placeholder="My TTS Server" spellcheck="false">
</div>
<div class="add-engine-field">
<label for="aed-role">Role</label>
<select id="aed-role">
<option value="tts">Text to Speech (TTS)</option>
<option value="stt">Speech to Text (STT)</option>
<option value="llm">Language Model (LLM)</option>
</select>
</div>
<div class="add-engine-field">
<label for="aed-url">URL</label>
<input id="aed-url" type="text" placeholder="http://192.168.1.100:8080" spellcheck="false">
</div>
<div class="add-engine-field">
<label for="aed-container">Docker container name <span class="add-engine-opt">(optional — enables Stop / Start / Restart)</span></label>
<input id="aed-container" type="text" placeholder="my-tts-container" spellcheck="false">
</div>
<div class="add-engine-field">
<label for="aed-desc">Description <span class="add-engine-opt">(optional)</span></label>
<input id="aed-desc" type="text" placeholder="Short description">
</div>
<div class="add-engine-actions">
<button type="button" class="btn-secondary" id="aed-cancel">Cancel</button>
<button type="button" class="btn-primary" id="aed-save">Add Card</button>
</div>
</div>
</dialog>