Conversation Playground (new section): - WhatsApp-style chat UI with user/assistant speech bubbles - Click-to-record mic button using MediaRecorder API - STT → LLM streaming → TTS pipeline via SSE (POST /api/conversation/turn) - LLM tokens stream into assistant bubble in real time - Audio auto-plays when TTS synthesises the reply - Right-side stats panel: STT / LLM TTFT / LLM total / TTS / Total with bar chart - Turn history list with per-turn total time and pass/fail indicator - Configurable: STT backend, LLM URL + model, TTS backend + voice, system prompt - Conversation history maintained across turns (last 20 messages sent to LLM) - GET /api/conversation/llm-models proxies model list from any OpenAI-compatible LLM XTTS v2 backend: - Registers xtts as a first-class TTS backend (xtts_url setting, display name, capabilities, health/voice discovery, OpenAI-compatible generation) - Added XTTS URL field to Settings → Connections - Use-as-TTS button now saves to xtts_url (not tts_url) - Batch benchmark backend select now refreshes alongside perf/preview selectors VibeVoice fix: - Added /voices to _TTS_VOICE_ENDPOINTS so VibeVoice voices are discovered Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
589 lines
32 KiB
HTML
589 lines
32 KiB
HTML
<div class="section-head">
|
|
<span class="section-icon"><span class="mdi mdi-cog-outline"></span></span>
|
|
<div class="section-title">
|
|
<h2>Settings</h2>
|
|
<p>Configure backend URLs, API keys, voice folders, and playback preferences.</p>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="tab-content" id="tab-settings">
|
|
|
|
<!-- ── General ─────────────────────────────────────────────── -->
|
|
<div class="s-settings-page" data-page="general">
|
|
<div class="card">
|
|
<div class="s-page-head">
|
|
<h3>General</h3>
|
|
<p>Interface preferences.</p>
|
|
</div>
|
|
<div class="settings-grid settings-behavior-grid">
|
|
<div class="s-field">
|
|
<label>Theme</label>
|
|
<select id="s-theme-select">
|
|
<option value="light">Light</option>
|
|
<option value="dark">Dark</option>
|
|
</select>
|
|
<span class="s-hint">Switch between light and dark interface. Saved instantly.</span>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- ── Connections ─────────────────────────────────────────── -->
|
|
<div class="s-settings-page" data-page="connections">
|
|
<div class="card">
|
|
<div class="s-page-head">
|
|
<div class="s-page-head-row">
|
|
<div>
|
|
<h3>Connections</h3>
|
|
<p>Service URLs for each backend. Change these first when setting up.</p>
|
|
</div>
|
|
<span class="s-stack-badge"><span class="mdi mdi-check-circle-outline"></span> Local stack</span>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="s-group">
|
|
<div class="s-group-head">
|
|
<strong>TTS — Text to Speech</strong>
|
|
<span>Qwen3 engines: clone, design, custom, streaming · Kokoro FastAPI</span>
|
|
</div>
|
|
<div class="settings-grid compact">
|
|
<div class="s-field">
|
|
<label>Voice Clone / Base URL <span class="s-label-note">(WAV voices)</span></label>
|
|
<input type="text" id="s-tts-url" placeholder="http://host.docker.internal:8020">
|
|
<span class="s-hint">Uploaded/cloned WAV voices. Expected: <code>POST /v1/audio/speech</code>.</span>
|
|
</div>
|
|
<div class="s-field">
|
|
<label>Voice Design URL <span class="s-label-note">(instruction voices)</span></label>
|
|
<input type="text" id="s-voice-design-url" placeholder="http://host.docker.internal:8021">
|
|
<span class="s-hint">Prompt-designed voices and <code>vd_...</code> virtual voices.</span>
|
|
</div>
|
|
<div class="s-field">
|
|
<label>CustomVoice URL</label>
|
|
<input type="text" id="s-customvoice-url" placeholder="http://host.docker.internal:8022">
|
|
<span class="s-hint">Style over configured speakers such as Ryan, Vivian, Serena.</span>
|
|
</div>
|
|
<div class="s-field">
|
|
<label>Streaming URL</label>
|
|
<input type="text" id="s-tts-stream-url" placeholder="http://host.docker.internal:8023">
|
|
<span class="s-hint">Progressive low-latency WAV playback.</span>
|
|
</div>
|
|
<div class="s-field">
|
|
<label>Kokoro FastAPI URL <span class="s-label-note">(82M · CPU-friendly)</span></label>
|
|
<input type="text" id="s-kokoro-url" placeholder="http://host.docker.internal:8880/v1">
|
|
<span class="s-hint">OpenAI-compatible TTS. Built-in voices: af_bella, bf_emma, am_adam… ~300 MB RAM.</span>
|
|
</div>
|
|
<div class="s-field">
|
|
<label>VibeVoice URL <span class="s-label-note">(vibevoice-service)</span></label>
|
|
<input type="text" id="s-vibevoice-url" placeholder="http://192.168.178.8:8027">
|
|
<span class="s-hint">Simple text-in audio-out TTS. POST /tts with {"text":"..."}.</span>
|
|
</div>
|
|
<div class="s-field">
|
|
<label>XTTS v2 URL <span class="s-label-note">(xtts-api-server)</span></label>
|
|
<input type="text" id="s-xtts-url" placeholder="http://host.docker.internal:8024">
|
|
<span class="s-hint">XTTS v2 via daswer123/xtts-api-server. Supports <code>GET /speakers</code> and <code>POST /v1/audio/speech</code>.</span>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="s-group">
|
|
<div class="s-group-head">
|
|
<strong>NVIDIA speech stack</strong>
|
|
<span>router, Magpie TTS, Parakeet ASR, clone NIM</span>
|
|
</div>
|
|
<div class="settings-grid compact">
|
|
<div class="s-field">
|
|
<label>NVIDIA router URL <span class="s-label-note">(TTS + STT)</span></label>
|
|
<input type="text" id="s-nvidia-router-url" placeholder="http://host.docker.internal:8090">
|
|
<span class="s-hint">OpenAI-compatible base URL for the NVIDIA speech router.</span>
|
|
</div>
|
|
<div class="s-field">
|
|
<label>NVIDIA Magpie TTS URL</label>
|
|
<input type="text" id="s-nvidia-tts-url" placeholder="http://host.docker.internal:8091">
|
|
<span class="s-hint">Direct Magpie endpoint. Fixed speakers, not WAV cloning.</span>
|
|
</div>
|
|
<div class="s-field">
|
|
<label>NVIDIA Parakeet ASR URL</label>
|
|
<input type="text" id="s-nvidia-asr-url" placeholder="http://host.docker.internal:8092">
|
|
<span class="s-hint">Direct Parakeet endpoint for transcription.</span>
|
|
</div>
|
|
<div class="s-field">
|
|
<label>NVIDIA Zeroshot NIM URL</label>
|
|
<input type="text" id="s-nvidia-zeroshot-url" placeholder="http://host.docker.internal:8093">
|
|
<span class="s-hint">Magpie Zeroshot clone endpoint with <code>audio_prompt</code>.</span>
|
|
</div>
|
|
<div class="s-field">
|
|
<label>NVIDIA Flow NIM URL</label>
|
|
<input type="text" id="s-nvidia-flow-url" placeholder="http://host.docker.internal:8094">
|
|
<span class="s-hint">Magpie Flow clone endpoint with <code>audio_prompt</code> and transcript.</span>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="s-group">
|
|
<div class="s-group-head">
|
|
<strong>STT — Speech to Text</strong>
|
|
<span>Whisper, faster-whisper, whisper.cpp, Groq, Parakeet, NVIDIA router</span>
|
|
</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× RT · 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–15× RT CPU · ~1 GB RAM.</span>
|
|
</div>
|
|
<div class="s-field">
|
|
<label>Groq API key <span class="s-label-note">(Groq Whisper & LLM)</span></label>
|
|
<div class="s-key-row">
|
|
<input type="password" id="s-groq-api-key" placeholder="gsk_…" autocomplete="off">
|
|
<button type="button" class="s-eye-btn" data-target="s-groq-api-key"><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>
|
|
|
|
<div class="s-page-actions">
|
|
<button class="btn-primary s-save-btn">Save settings</button>
|
|
<button class="btn-secondary s-reload-btn">Reload settings</button>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- ── Playback ─────────────────────────────────────────────── -->
|
|
<div class="s-settings-page" data-page="playback">
|
|
<div class="card">
|
|
<div class="s-page-head">
|
|
<h3>Playback</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 class="s-page-actions">
|
|
<button class="btn-primary s-save-btn">Save settings</button>
|
|
<button class="btn-secondary s-reload-btn">Reload settings</button>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- ── Captures ────────────────────────────────────────────── -->
|
|
<div class="s-settings-page" data-page="captures">
|
|
<div class="card">
|
|
<div class="s-page-head">
|
|
<h3>Captures</h3>
|
|
<p>Default behaviour for STT transcription, LLM text refinement, and playback.</p>
|
|
</div>
|
|
|
|
<div class="s-group">
|
|
<div class="s-group-head">
|
|
<strong>Transcription</strong>
|
|
<span>Default language and preferred STT backend</span>
|
|
</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="ru">Russian</option>
|
|
<option value="ja">Japanese</option>
|
|
<option value="zh">Chinese</option>
|
|
<option value="hi">Hindi</option>
|
|
</select>
|
|
<span class="s-hint">Language hint passed to the STT backend. Auto-detect works well in most cases.</span>
|
|
</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>
|
|
|
|
<div class="s-group">
|
|
<div class="s-group-head">
|
|
<strong>LLM server</strong>
|
|
<span>Base URL for persona rewrite, text refinement, and other local LLM features</span>
|
|
</div>
|
|
<div class="settings-grid compact">
|
|
<div class="s-field">
|
|
<label>LLM base URL</label>
|
|
<input type="text" id="s-llm-url" placeholder="http://localhost:11434/v1">
|
|
<span class="s-hint">OpenAI-compatible endpoint. Used for persona rewrites, voice design, and auto-refine.</span>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="s-group">
|
|
<div class="s-group-head">
|
|
<strong>LLM refinement defaults</strong>
|
|
<span>Automatic text cleanup after transcription</span>
|
|
</div>
|
|
<div class="settings-grid settings-behavior-grid">
|
|
<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 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 & 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>
|
|
|
|
<div class="s-group">
|
|
<div class="s-group-head">
|
|
<strong>Default playback voice</strong>
|
|
<span>Pre-selected voice in the STT-TTS panel</span>
|
|
</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>
|
|
<span class="s-hint">Pre-selects this voice in the STT-TTS panel on load.</span>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="s-page-actions">
|
|
<button class="btn-primary s-save-btn">Save settings</button>
|
|
<button class="btn-secondary s-reload-btn">Reload settings</button>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- ── Payloads ─────────────────────────────────────────────── -->
|
|
<div class="s-settings-page" data-page="payloads">
|
|
<div class="card">
|
|
<div class="s-page-head">
|
|
<h3>Payloads</h3>
|
|
<p>Extra JSON fields sent alongside each backend request. Usually only needed for advanced tuning.</p>
|
|
</div>
|
|
<div class="settings-grid three settings-param-grid">
|
|
<div class="s-field">
|
|
<label>Voice Clone / Base params</label>
|
|
<textarea id="s-tts-extra-voice-clone" spellcheck="false" placeholder='{"temperature":0.1,"top_p":0.8,"seed":0}'></textarea>
|
|
<span class="s-hint">Extra fields for the 8020 WAV voice clone/base model.</span>
|
|
</div>
|
|
<div class="s-field">
|
|
<label>Streaming params</label>
|
|
<textarea id="s-tts-extra-streaming" spellcheck="false" placeholder='{"temperature":0.1,"top_p":0.8,"seed":0}'></textarea>
|
|
<span class="s-hint">Extra fields for the 8023 streaming model.</span>
|
|
</div>
|
|
<div class="s-field">
|
|
<label>CustomVoice params</label>
|
|
<textarea id="s-tts-extra-customvoice" spellcheck="false" placeholder='{"temperature":0.1,"top_p":0.8,"seed":0}'></textarea>
|
|
<span class="s-hint">Extra fields for the CustomVoice backend.</span>
|
|
</div>
|
|
<div class="s-field">
|
|
<label>Voice Design params</label>
|
|
<textarea id="s-tts-extra-voice-design" spellcheck="false" placeholder='{"temperature":0.1,"top_p":0.8,"seed":0}'></textarea>
|
|
<span class="s-hint">Extra fields for Voice Design and virtual <code>vd_...</code> voices.</span>
|
|
</div>
|
|
<div class="s-field">
|
|
<label>NVIDIA Magpie params</label>
|
|
<textarea id="s-tts-extra-nvidia-magpie" spellcheck="false" placeholder="{}"></textarea>
|
|
<span class="s-hint">Usually empty. Magpie accepts fixed speakers: sofia, aria, jason, leo, john.</span>
|
|
</div>
|
|
<div class="s-field">
|
|
<label>NVIDIA Zeroshot params</label>
|
|
<textarea id="s-tts-extra-nvidia-zeroshot" spellcheck="false" placeholder='{"zero_shot_quality":20}'></textarea>
|
|
<span class="s-hint">Optional multipart fields. The app supplies <code>text</code>, <code>language</code>, <code>audio_prompt</code>.</span>
|
|
</div>
|
|
<div class="s-field">
|
|
<label>NVIDIA Flow params</label>
|
|
<textarea id="s-tts-extra-nvidia-flow" spellcheck="false" placeholder="{}"></textarea>
|
|
<span class="s-hint">Optional multipart fields. The app also sends the saved reference transcript.</span>
|
|
</div>
|
|
<div class="s-field">
|
|
<label>Kokoro FastAPI params</label>
|
|
<textarea id="s-tts-extra-kokoro" spellcheck="false" placeholder="{}"></textarea>
|
|
<span class="s-hint">Extra JSON fields for Kokoro. Usually empty — voice is selected from the dropdown.</span>
|
|
</div>
|
|
<div class="s-field">
|
|
<label>VibeVoice params</label>
|
|
<textarea id="s-tts-extra-vibevoice" spellcheck="false" placeholder="{}"></textarea>
|
|
<span class="s-hint">Extra JSON fields for VibeVoice. Usually empty — service only requires text.</span>
|
|
</div>
|
|
</div>
|
|
<div class="s-page-actions">
|
|
<button class="btn-primary s-save-btn">Save settings</button>
|
|
<button class="btn-secondary s-reload-btn">Reload settings</button>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- ── Storage ─────────────────────────────────────────────── -->
|
|
<div class="s-settings-page" data-page="storage">
|
|
<div class="card">
|
|
<div class="s-page-head">
|
|
<h3>Storage</h3>
|
|
<p>Container paths for voice files. Match these to your Portainer volume mounts.</p>
|
|
</div>
|
|
<div class="settings-grid">
|
|
<div class="s-field">
|
|
<label>Voice scan directory</label>
|
|
<input type="text" id="s-voices-scan-dir" placeholder="/voices">
|
|
<span class="s-hint">Contains <code>active_voices</code>, <code>hidden_voices</code>, sounds, and metadata.</span>
|
|
</div>
|
|
<div class="s-field">
|
|
<label>Active voices directory</label>
|
|
<input type="text" id="s-output-dir" placeholder="/voices/active_voices">
|
|
<span class="s-hint">New cloned/exported voices are saved here.</span>
|
|
</div>
|
|
</div>
|
|
<div class="s-page-actions">
|
|
<button class="btn-primary s-save-btn">Save settings</button>
|
|
<button class="btn-secondary s-reload-btn">Reload settings</button>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- ── API Keys ─────────────────────────────────────────────── -->
|
|
<div class="s-settings-page" data-page="apikeys">
|
|
<div class="card">
|
|
<div class="s-page-head">
|
|
<h3>API Keys</h3>
|
|
<p>Usually empty for local containers. Set a dummy value like <code>sk-local</code> if a backend requires an Authorization header.</p>
|
|
</div>
|
|
<div class="settings-grid three">
|
|
<div class="s-field">
|
|
<label>TTS API key <span class="s-label-note">(optional)</span></label>
|
|
<div class="s-key-row">
|
|
<input type="password" id="s-tts-key" placeholder="dummy, sk-local, or leave empty" autocomplete="off">
|
|
<button type="button" class="s-eye-btn" data-target="s-tts-key"><span class="mdi mdi-eye-outline"></span></button>
|
|
</div>
|
|
</div>
|
|
<div class="s-field">
|
|
<label>Voice Design API key <span class="s-label-note">(optional)</span></label>
|
|
<div class="s-key-row">
|
|
<input type="password" id="s-vd-key" placeholder="dummy, sk-local, or leave empty" autocomplete="off">
|
|
<button type="button" class="s-eye-btn" data-target="s-vd-key"><span class="mdi mdi-eye-outline"></span></button>
|
|
</div>
|
|
</div>
|
|
<div class="s-field">
|
|
<label>Whisper API key <span class="s-label-note">(optional)</span></label>
|
|
<div class="s-key-row">
|
|
<input type="password" id="s-whisper-key" placeholder="dummy, sk-local, or leave empty" autocomplete="off">
|
|
<button type="button" class="s-eye-btn" data-target="s-whisper-key"><span class="mdi mdi-eye-outline"></span></button>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
<div class="s-page-actions">
|
|
<button class="btn-primary s-save-btn">Save settings</button>
|
|
<button class="btn-secondary s-reload-btn">Reload settings</button>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- ── Backup ─────────────────────────────────────────────── -->
|
|
<div class="s-settings-page" data-page="backup">
|
|
<div class="card">
|
|
<div class="s-page-head">
|
|
<h3>Backup & restore</h3>
|
|
<p>Export all voices and settings as a ZIP for backup or migration. API keys are excluded from exports.</p>
|
|
</div>
|
|
<div class="btn-row" style="gap:10px;flex-wrap:wrap">
|
|
<a class="btn-secondary" id="s-export-voices-btn" href="/api/voices/export" download><span class="mdi mdi-download"></span> Export voices</a>
|
|
<label class="btn-secondary" style="cursor:pointer" title="Import a voices ZIP">
|
|
<span class="mdi mdi-upload"></span> Import voices
|
|
<input type="file" id="s-import-voices-file" accept=".zip" style="display:none">
|
|
</label>
|
|
<span class="note" id="s-import-status"></span>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- ── Logs ─────────────────────────────────────────────────── -->
|
|
<div class="s-settings-page" data-page="logs">
|
|
<div class="card">
|
|
<div class="s-page-head">
|
|
<h3>Logs</h3>
|
|
<p>Recent server activity. Useful for debugging backend connections and API errors.</p>
|
|
</div>
|
|
<div class="s-log-toolbar">
|
|
<button class="btn-secondary" id="s-logs-refresh-btn" type="button"><span class="mdi mdi-refresh"></span> Refresh</button>
|
|
<button class="btn-secondary" id="s-logs-clear-btn" type="button"><span class="mdi mdi-trash-can-outline"></span> Clear</button>
|
|
<label class="s-log-live-label">
|
|
<input type="checkbox" id="s-logs-live-toggle">
|
|
<span>Auto-refresh</span>
|
|
</label>
|
|
<span class="s-log-count" id="s-log-count"></span>
|
|
</div>
|
|
<div class="s-log-filters">
|
|
<button class="s-log-filter is-active" data-log-level="">All</button>
|
|
<button class="s-log-filter" data-log-level="ERROR">Error</button>
|
|
<button class="s-log-filter" data-log-level="WARNING">Warning</button>
|
|
<button class="s-log-filter" data-log-level="INFO">Info</button>
|
|
</div>
|
|
<div class="s-log-viewer" id="s-log-viewer">
|
|
<div class="s-log-empty">Click Refresh to load logs</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- ── About ─────────────────────────────────────────────────── -->
|
|
<div class="s-settings-page" data-page="about">
|
|
<div class="card">
|
|
<div class="s-page-head">
|
|
<h3>About</h3>
|
|
</div>
|
|
<div class="s-about-block">
|
|
<div class="s-about-name"><span class="mdi mdi-microphone-variant"></span> TTS Voice Creator</div>
|
|
<p class="s-about-desc">Clone, design, and deploy custom voices using local AI backends. Compatible with Qwen3-TTS, Kokoro FastAPI, NVIDIA Magpie, and any OpenAI-compatible TTS/STT endpoint.</p>
|
|
<div class="s-about-stack">
|
|
<span class="s-about-chip"><span class="mdi mdi-language-python"></span> FastAPI</span>
|
|
<span class="s-about-chip"><span class="mdi mdi-language-javascript"></span> Vanilla JS</span>
|
|
<span class="s-about-chip"><span class="mdi mdi-music-note"></span> WaveSurfer.js v7</span>
|
|
<span class="s-about-chip"><span class="mdi mdi-vector-square"></span> MDI v7.4.47</span>
|
|
</div>
|
|
<div class="s-about-backends" id="s-about-backends"></div>
|
|
</div>
|
|
|
|
<div class="s-group" style="margin-top:16px">
|
|
<div class="s-group-head">
|
|
<strong><span class="mdi mdi-connection"></span> MCP Server</strong>
|
|
<span>Expose voice tools to Claude Code, Cursor, and other MCP-aware agents</span>
|
|
</div>
|
|
<div class="settings-grid compact">
|
|
<div class="s-field">
|
|
<label>HTTP transport URL</label>
|
|
<code class="s-code-block" id="s-mcp-url">http://localhost:7890/mcp</code>
|
|
<span class="s-hint">Streamable HTTP (JSON-RPC 2.0). Tools: <code>speak</code>, <code>transcribe</code>, <code>list_captures</code>, <code>list_profiles</code>.</span>
|
|
</div>
|
|
<div class="s-field">
|
|
<label>Claude Code one-liner</label>
|
|
<code class="s-code-block">claude mcp add voice-creator --transport http --url http://localhost:7890/mcp --header "X-Voice-Creator-Client-Id: claude-code"</code>
|
|
</div>
|
|
<div class="s-field">
|
|
<label>Any HTTP MCP client (JSON)</label>
|
|
<pre class="s-code-block" style="white-space:pre-wrap">{"mcpServers":{"voice-creator":{"url":"http://localhost:7890/mcp","headers":{"X-Voice-Creator-Client-Id":"my-agent"}}}}</pre>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="s-group" style="margin-top:16px">
|
|
<div class="s-group-head">
|
|
<strong><span class="mdi mdi-bullhorn-outline"></span> /speak REST endpoint</strong>
|
|
<span>Generate audio from any app or script without routing rules</span>
|
|
</div>
|
|
<div class="settings-grid compact">
|
|
<div class="s-field">
|
|
<label>Generate speech (example)</label>
|
|
<pre class="s-code-block" style="white-space:pre-wrap">curl -X POST http://localhost:7890/speak \
|
|
-H "Content-Type: application/json" \
|
|
-H "X-Voice-Creator-Client-Id: my-script" \
|
|
-d '{"text":"Hello world","voice":"EN_F_Anna"}' \
|
|
--output speech.wav</pre>
|
|
</div>
|
|
<div class="s-field">
|
|
<label>Per-client voice binding</label>
|
|
<pre class="s-code-block" style="white-space:pre-wrap">curl -X PUT http://localhost:7890/speak/bindings/my-script \
|
|
-H "Content-Type: application/json" \
|
|
-d '{"voice":"EN_F_Anna"}'</pre>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="s-group" style="margin-top:16px">
|
|
<div class="s-group-head">
|
|
<strong><span class="mdi mdi-keyboard-outline"></span> Global hotkey daemon</strong>
|
|
<span>Push-to-talk transcription that types into any focused window</span>
|
|
</div>
|
|
<div class="settings-grid compact">
|
|
<div class="s-field">
|
|
<label>Install & run (host machine)</label>
|
|
<pre class="s-code-block" style="white-space:pre-wrap">pip install pynput sounddevice soundfile pyperclip requests
|
|
python hotkey_daemon.py --server http://localhost:7890</pre>
|
|
<span class="s-hint">Hold <kbd>Ctrl+Shift+Space</kbd> to record, release to transcribe and type. Linux: install <code>xdotool</code> for direct key injection.</span>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
</div>
|
|
</div>
|
|
|
|
</div><!-- /tab-settings -->
|