diff --git a/static/app.js b/static/app.js index a7773a2..ab0bcde 100644 --- a/static/app.js +++ b/static/app.js @@ -38,7 +38,7 @@ function selectVoice(wrap) { restoreToRow(wrap); wrap.classList.remove('vr-selected'); _selectedVoiceWrap = null; - inspector.innerHTML = '
🔊

Pick a voice on the left
to edit it here

'; + inspector.innerHTML = '

Pick a voice on the left
to edit it here

'; return; } @@ -73,7 +73,7 @@ function selectVoice(wrap) { const flagIconHtml = inspFlagIcon || ''; const makeStars = n => [1,2,3,4,5].map(i => - `` + `` ).join(''); const LANGS = ['EN','DE','IT','ES','FR','PT','NL','PL','ZH','JA','KO','AR','RU','TR','HI','SV','DA','FI','NB','HU','CS','RO','UK']; @@ -529,7 +529,7 @@ applyTheme(localStorage.getItem('vcf-theme') || 'dark'); // ── Language helpers ────────────────────────────────────────────────────── function cc2flag(cc) { - if (!cc || cc.length !== 2) return '🌐'; + if (!cc || cc.length !== 2) return ''; return cc.toUpperCase().replace(/./g, c => String.fromCodePoint(c.charCodeAt(0) + 127397)); } @@ -1789,10 +1789,10 @@ function backendHelpHtml(b, compact = false) { b.true_streaming ? ['good', 'true streaming'] : ['', 'buffered/normal'], ].map(([cls, text]) => `${escHtml(text)}`).join(''); const metricParts = []; - if (b.speed) metricParts.push(`⚡ ${escHtml(b.speed)}`); - if (b.latency) metricParts.push(`⏰ ${escHtml(b.latency)}`); - if (b.quality) metricParts.push(`⭐ ${escHtml(b.quality)}`); - if (b.ram) metricParts.push(`💾 ${escHtml(b.ram)}`); + if (b.speed) metricParts.push(` ${escHtml(b.speed)}`); + if (b.latency) metricParts.push(` ${escHtml(b.latency)}`); + if (b.quality) metricParts.push(` ${escHtml(b.quality)}`); + if (b.ram) metricParts.push(` ${escHtml(b.ram)}`); const metrics = metricParts.length ? `
${metricParts.join('')}
` : ''; const detail = compact ? escHtml(b.best_for || '') : `${escHtml(b.purpose || '')}
Identity: ${escHtml(b.identity || '')}
Style: ${escHtml(b.style || '')}
Best for: ${escHtml(b.best_for || '')}`; return `${escHtml(b.label)}
${tags}
${metrics}
${detail}
`; @@ -1802,10 +1802,10 @@ function sttBackendHelpHtml(b) { if (!b) return 'No STT engine selected.'; const m = b.metrics || {}; const metricParts = []; - if (m.speed) metricParts.push(`⚡ ${escHtml(m.speed)}`); - if (m.latency) metricParts.push(`⏰ ${escHtml(m.latency)}`); - if (m.quality) metricParts.push(`⭐ ${escHtml(m.quality)}`); - if (m.ram) metricParts.push(`💾 ${escHtml(m.ram)}`); + if (m.speed) metricParts.push(` ${escHtml(m.speed)}`); + if (m.latency) metricParts.push(` ${escHtml(m.latency)}`); + if (m.quality) metricParts.push(` ${escHtml(m.quality)}`); + if (m.ram) metricParts.push(` ${escHtml(m.ram)}`); const metrics = metricParts.length ? `
${metricParts.join('')}
` : ''; const modelList = Array.isArray(b.models) && b.models.length ? ' Models: ' + b.models.slice(0, 4).join(', ') + '.' : ''; const avail = b.available ? `ready` : `unavailable`; @@ -1832,7 +1832,7 @@ function updateStyleBackendHelp(scope = document) { const suggestion = styleAwareBacks.length ? ` Try ${escHtml(styleAwareBacks[0].label)} instead.` : ' No style-aware backend is currently reachable.'; - box.innerHTML += `
⚠ This backend ignores the style instruction — output will sound the same regardless of what you type.${suggestion}
`; + box.innerHTML += `
This backend ignores the style instruction — output will sound the same regardless of what you type.${suggestion}
`; } }); } @@ -4195,12 +4195,12 @@ function renderVoiceList() { emptyEl.innerHTML = `
-
📁
+

Wrong folder path?

Set the path where your .wav voice files live inside the container.

- +
or
-
🎤
+

Folder is empty?

Create your first voice from a recording or download ready-made voices.

- - + +
@@ -4262,7 +4262,7 @@ function renderVoiceList() { vefDirList.innerHTML = 'No subdirectories here.'; } else { vefDirList.innerHTML = data.dirs.map(d => - `` + `` ).join(''); vefDirList.querySelectorAll('.vef-dir-item').forEach(b => b.addEventListener('click', () => vefNavigate(b.dataset.path))); } @@ -4413,7 +4413,7 @@ function makeVoiceRow(v) { // Use language-specific variants if there are multiple; fall back to world picker otherwise const flagOpts = langOpts.length > 1 ? langOpts : ALL_FLAGS; const currentFlag = v.flag || LANG_FLAG_DEFAULT[langCode] || ''; - const flagEmoji = currentFlag ? cc2flag(currentFlag) : '🌐'; + const flagEmoji = currentFlag ? cc2flag(currentFlag) : ''; const flagCode = currentFlag ? ccDisplay(currentFlag) : '?'; wrap.dataset.flagEmoji = flagEmoji; wrap.dataset.flagCc = currentFlag || ''; @@ -4432,7 +4432,7 @@ function makeVoiceRow(v) { const gender = v.gender || ''; const starsHtml = [1,2,3,4,5].map(i => - `` + `` ).join(''); const picSrc = v.has_picture ? `/api/voice/picture/${encodeURIComponent(v.id)}` : null; @@ -4468,14 +4468,14 @@ function makeVoiceRow(v) {
-
-
+
+
- ${picSrc ? `` : '
👤
'} + ${picSrc ? `` : '
'}
@@ -4497,8 +4497,8 @@ function makeVoiceRow(v) { ${escHtml(v.id)} - - + +
@@ -4519,15 +4519,15 @@ function makeVoiceRow(v) {
${starsHtml}
- +
- +
- +
@@ -4538,7 +4538,7 @@ function makeVoiceRow(v) {
- +
@@ -5338,7 +5338,7 @@ function makeVoiceRow(v) { // Play original recording or synthesized sample const originalPlayBtn = wrap.querySelector('.vr-play-original button'); const synthPlayBtn = wrap.querySelector('.vr-play-synth button'); - const playIcon = '▶', pauseIcon = '❙❙', generatingIcon = '⌛'; + const playIcon = '', pauseIcon = '', generatingIcon = ''; function setLibraryPlayButtonState(btn, state) { btn.classList.toggle('is-generating', state === 'generating'); btn.innerHTML = state === 'playing' ? pauseIcon : (state === 'generating' ? generatingIcon : playIcon); @@ -5840,7 +5840,7 @@ $('save-preview-btn').addEventListener('click', () => { ${min} ms best ${max} ms worst ${avgRtf.toFixed(2)} avg RTF - ${avgRtf < 1 ? '✅ Real-time capable' : '⚠ Slower than real-time'} + ${avgRtf < 1 ? ' Real-time capable' : ' Slower than real-time'} `; } else { perfSummary.innerHTML = 'All runs failed'; } } @@ -6370,7 +6370,7 @@ loadSettings().then(() => { - ${prev ? `` : ''} + ${prev ? `` : ''} `; } @@ -6448,24 +6448,24 @@ function renderLocalContainers(containers) { const ROLE_LABEL = { tts: 'TTS', stt: 'STT', 'stt+tts': 'STT · TTS', llm: 'LLM' }; const DC_ICONS = { - 'faster-qwen3-tts-voiceclone': '🔊', - 'faster-qwen3-tts-voicedesign': '✨', + 'faster-qwen3-tts-voiceclone': '', + 'faster-qwen3-tts-voicedesign': '', 'faster-qwen3-tts-customvoice': '🎭', - 'faster-qwen3-tts-streaming': '⚡', + 'faster-qwen3-tts-streaming': '', 'parakeet-asr': '🦜', 'magpie-tts': '🐦', 'parakeet-rnnt-nim': '🦜', }; const DC_METRICS = { - 'faster-qwen3-tts-voiceclone': [['⚡','~0.3× GPU'],['⏰','1–3 s'],['⭐','Premium clone'],['💾','6–8 GB VRAM']], - 'faster-qwen3-tts-voicedesign': [['⚡','~0.4× GPU'],['⏰','1–3 s'],['⭐','Premium'], ['💾','6–8 GB VRAM']], - 'faster-qwen3-tts-customvoice': [['⚡','~0.3× GPU'],['⏰','1–3 s'],['⭐','Premium'], ['💾','6–8 GB VRAM']], - 'faster-qwen3-tts-streaming': [['⚡','~0.1× GPU'],['⏰','0.5–1 s'],['⭐','Premium'], ['💾','6–8 GB VRAM']], - 'magpie-tts': [['⚡','~0.05× GPU'],['⏰','0.3–0.8 s'],['⭐','High'], ['💾','4–6 GB VRAM']], - 'parakeet-asr': [['⚡','~200× RT GPU'],['⏰','<0.3 s'],['⭐','Parakeet-TDT'],['💾','2 GB VRAM']], - 'parakeet-rnnt-nim': [['⚡','~200× RT GPU'],['⏰','<0.3 s'],['⭐','Parakeet-1B'], ['💾','2 GB VRAM']], + 'faster-qwen3-tts-voiceclone': [['','~0.3× GPU'],['⏰','1–3 s'],['⭐','Premium clone'],['💾','6–8 GB VRAM']], + 'faster-qwen3-tts-voicedesign': [['','~0.4× GPU'],['⏰','1–3 s'],['⭐','Premium'], ['💾','6–8 GB VRAM']], + 'faster-qwen3-tts-customvoice': [['','~0.3× GPU'],['⏰','1–3 s'],['⭐','Premium'], ['💾','6–8 GB VRAM']], + 'faster-qwen3-tts-streaming': [['','~0.1× GPU'],['⏰','0.5–1 s'],['⭐','Premium'], ['💾','6–8 GB VRAM']], + 'magpie-tts': [['','~0.05× GPU'],['⏰','0.3–0.8 s'],['⭐','High'], ['💾','4–6 GB VRAM']], + 'parakeet-asr': [['','~200× RT GPU'],['⏰','<0.3 s'],['⭐','Parakeet-TDT'],['💾','2 GB VRAM']], + 'parakeet-rnnt-nim': [['','~200× RT GPU'],['⏰','<0.3 s'],['⭐','Parakeet-1B'], ['💾','2 GB VRAM']], }; - const roleIcon = { tts: '🔊', stt: '🎙️', 'stt+tts': '🔄', llm: '🤖' }; + const roleIcon = { tts: '', stt: '️', 'stt+tts': '🔄', llm: '' }; grid.innerHTML = containers.map(c => { const st = c.status || 'not_found'; @@ -6493,7 +6493,7 @@ function renderLocalContainers(containers) { : ` `) : (c.repo - ? `View on GitHub ↗` + ? `View on GitHub ` : ''); return `
@@ -6864,8 +6864,8 @@ function renderHistory() {
${preview}
- - + +
`; @@ -6932,9 +6932,9 @@ function renderPlaylist() { ${escHtml(item.text.length > 70 ? item.text.slice(0,70)+'…' : item.text)}
- - - + + +
`).join(''); diff --git a/static/index.html b/static/index.html index 58d2dec..e28c4d0 100644 --- a/static/index.html +++ b/static/index.html @@ -7,6 +7,7 @@ + @@ -32,17 +33,17 @@ diff --git a/static/sections/s-clone.html b/static/sections/s-clone.html index c387390..55b305f 100644 --- a/static/sections/s-clone.html +++ b/static/sections/s-clone.html @@ -1,5 +1,5 @@
- 🎤 +

Clone a Voice

Upload or record 3-20 seconds of audio, trim it, then save it as a reusable voice clone.

@@ -31,8 +31,8 @@

Microphone

Record a fresh sample with live input level monitoring.

- - + +
0:00
@@ -48,8 +48,8 @@
- - + +
@@ -95,7 +95,7 @@

Transcript (reference text)

The spoken text that matches this audio — type it or auto-transcribe.

- +
@@ -110,7 +110,7 @@

Step 4 — Save to library

Write the cloned voice to your active voices folder.

- +
diff --git a/static/sections/s-connect.html b/static/sections/s-connect.html index 7209ece..bc0f176 100644 --- a/static/sections/s-connect.html +++ b/static/sections/s-connect.html @@ -1,5 +1,5 @@
- 🔗 +

Connect Your Apps

Copy ready-made configuration snippets for SillyTavern, Open WebUI, Home Assistant, and more.

diff --git a/static/sections/s-design.html b/static/sections/s-design.html index e2676cb..16aea87 100644 --- a/static/sections/s-design.html +++ b/static/sections/s-design.html @@ -1,5 +1,5 @@
- +

Design a Voice

Describe a voice in words and let the AI create it. No recording needed.

@@ -70,7 +70,7 @@

This shared reference transcript is used by samples, prompt presets, generation, preview, download, and export to the Voice Clone Library.

- +
@@ -115,9 +115,9 @@
- - - + + +
@@ -101,7 +101,7 @@ ollama pull llama3.3
Developer tab → Start server → Port 1234
 Check "Enable CORS" for browser access
- lmstudio.ai ↗ + lmstudio.ai
@@ -125,7 +125,7 @@ Check "Enable CORS" for browser access -hf QuantFactory/Meta-Llama-3-8B-GGUF \ -hff Meta-Llama-3-8B.Q4_K_M.gguf
- github.com/ggml-org/llama.cpp ↗ + github.com/ggml-org/llama.cpp
@@ -136,15 +136,15 @@ Check "Enable CORS" for browser access
- 🎙️ + faster-whisper-server OpenAI-compat
- ⚡ ~70× RT GPU - ⏰ 0.5–2 s - ⭐ large-v3 - 💾 1.5 GB VRAM + ~70× RT GPU + 0.5–2 s + large-v3 + 1.5 GB VRAM

Drop-in local replacement for the Whisper API. GPU-accelerated via CTranslate2. OpenAI-compatible endpoint.

@@ -153,7 +153,7 @@ Check "Enable CORS" for browser access
- +
@@ -171,7 +171,7 @@ Check "Enable CORS" for browser access reservations: devices: [{driver: nvidia, count: 1, capabilities: [gpu]}]
- github.com/fedirz/faster-whisper-server ↗ + github.com/fedirz/faster-whisper-server
@@ -181,10 +181,10 @@ Check "Enable CORS" for browser access HTTP server
- ⚡ ~8–15× RT CPU - ⏰ 1–5 s - ⭐ large-v3 Q5 - 💾 ~1 GB RAM + ~8–15× RT CPU + 1–5 s + large-v3 Q5 + ~1 GB RAM

Minimal C++ Whisper with a built-in HTTP server. CPU or Metal/CUDA. Low memory, fast on consumer hardware.

@@ -193,7 +193,7 @@ Check "Enable CORS" for browser access
- +
@@ -205,7 +205,7 @@ cd whisper.cpp && cmake -B build && cmake --build build -j ./build/bin/whisper-server \ -m models/ggml-large-v3.bin --port 8080
- github.com/ggml-org/whisper.cpp ↗ + github.com/ggml-org/whisper.cpp
@@ -216,15 +216,15 @@ cd whisper.cpp && cmake -B build && cmake --build build -j
- 🔊 + Piper TTS Fast · offline
- ⚡ ~1× CPU realtime - ⏰ <50 ms - ⭐ Good (VITS) - 💾 ~50 MB RAM + ~1× CPU realtime + <50 ms + Good (VITS) + ~50 MB RAM

Lightning-fast offline TTS. Runs on CPU in real time. 50+ language voices available. Uses Wyoming protocol (port 10200) — not directly OpenAI-compatible.

@@ -241,7 +241,7 @@ cd whisper.cpp && cmake -B build && cmake --build build -j rhasspy/wyoming-piper \ --voice en_US-lessac-medium
- github.com/rhasspy/piper ↗ + github.com/rhasspy/piper
@@ -251,10 +251,10 @@ cd whisper.cpp && cmake -B build && cmake --build build -j OpenAI-compat TTS
- ⚡ ~0.1× CPU RTF - ⏰ ~200 ms - ⭐ High (82M) - 💾 300 MB CPU + ~0.1× CPU RTF + ~200 ms + High (82M) + 300 MB CPU

Kokoro-82M running behind an OpenAI-compatible TTS endpoint. 11 built-in voices (af_bella, bf_emma, am_adam…). Drop-in replacement for OpenAI’s TTS API.

@@ -263,7 +263,7 @@ cd whisper.cpp && cmake -B build && cmake --build build -j
- +
@@ -276,7 +276,7 @@ cd whisper.cpp && cmake -B build && cmake --build build -j docker run -p 8880:8880 --gpus all \ ghcr.io/remsky/kokoro-fastapi-gpu:v0.2.2
- github.com/remsky/Kokoro-FastAPI ↗ + github.com/remsky/Kokoro-FastAPI
@@ -286,10 +286,10 @@ docker run -p 8880:8880 --gpus all \ Voice cloning
- ⚡ ~0.5× GPU RTF - ⏰ 1–3 s - ⭐ High (WAV clone) - 💾 3–4 GB VRAM + ~0.5× GPU RTF + 1–3 s + High (WAV clone) + 3–4 GB VRAM

Coqui XTTS — multilingual voice cloning from a 6-second sample. 17 languages. Compatible with this app’s voice library.

@@ -306,13 +306,13 @@ docker run -p 8880:8880 --gpus all \ -v /voices:/voices \ daswer123/xtts-api-server:latest
- xtts-api-server ↗ + xtts-api-server
-
💡
+
Adding a local service to this app

All OpenAI-compatible services work the same way: open App Routing in the sidebar, add a new backend entry with the local URL, leave the API key field empty, and set it as the default backend for the voice type you want.

@@ -332,12 +332,12 @@ docker run -p 8880:8880 --gpus all \
-

🎤 ASR · Speech-to-Text

+

ASR · Speech-to-Text

- +
Groq Whisper
whisper-large-v3-turbo · OpenAI-compatible
@@ -346,13 +346,13 @@ docker run -p 8880:8880 --gpus all \
2 000 req / day - ⚡ Fastest cloud STT - 💾 Cloud · 0 VRAM + Fastest cloud STT + Cloud · 0 VRAM
- Get key ↗ + Get key
Endpoint @@ -368,7 +368,7 @@ docker run -p 8880:8880 --gpus all \
- 🤗 +
HuggingFace Inference
Serverless Whisper models
@@ -383,7 +383,7 @@ docker run -p 8880:8880 --gpus all \
- Get key ↗ + Get key
Endpoint @@ -393,7 +393,7 @@ docker run -p 8880:8880 --gpus all \
- 📋 +
AssemblyAI
High-accuracy transcription + speaker diarization
@@ -408,7 +408,7 @@ docker run -p 8880:8880 --gpus all \
- Get key ↗ + Get key
Endpoint @@ -419,12 +419,12 @@ docker run -p 8880:8880 --gpus all \
-

🔊 Text-to-Speech

+

Text-to-Speech

- 💉 +
ElevenLabs
High-quality voice cloning & synthesis
@@ -439,7 +439,7 @@ docker run -p 8880:8880 --gpus all \
- Get key ↗ + Get key
Endpoint @@ -449,7 +449,7 @@ docker run -p 8880:8880 --gpus all \
- 🏭 +
Fish Audio
Voice cloning & multilingual TTS
@@ -464,7 +464,7 @@ docker run -p 8880:8880 --gpus all \
- Get key ↗ + Get key
Endpoint @@ -474,7 +474,7 @@ docker run -p 8880:8880 --gpus all \
- 🎵 +
Kokoro TTS
82M model · HuggingFace Spaces demo
@@ -489,19 +489,19 @@ docker run -p 8880:8880 --gpus all \
Use the HF Spaces web demo for quick tests, or run Kokoro locally via Docker for production use.
- → Open Kokoro HF Space + Open Kokoro HF Space
-

🤖 Large Language Models

+

Large Language Models

- +
Groq
Ultra-fast inference · OpenAI-compatible
@@ -516,7 +516,7 @@ docker run -p 8880:8880 --gpus all \
- Get key ↗ + Get key
Endpoint @@ -531,7 +531,7 @@ docker run -p 8880:8880 --gpus all \
- 🌐 +
OpenRouter
50+ free models · OpenAI-compatible
@@ -546,7 +546,7 @@ docker run -p 8880:8880 --gpus all \
- Get key ↗ + Get key
Endpoint @@ -561,7 +561,7 @@ docker run -p 8880:8880 --gpus all \
- 💎 +
Google Gemini
Gemini 2.5 Flash · Generous free tier
@@ -576,7 +576,7 @@ docker run -p 8880:8880 --gpus all \
- Get key ↗ + Get key
Endpoint @@ -587,7 +587,7 @@ docker run -p 8880:8880 --gpus all \
- 🅓 +
Mistral AI
OpenAI-compatible · EU-based
@@ -602,7 +602,7 @@ docker run -p 8880:8880 --gpus all \
- Get key ↗ + Get key
Endpoint diff --git a/static/sections/s-routing.html b/static/sections/s-routing.html index e40997d..7a9c3b6 100644 --- a/static/sections/s-routing.html +++ b/static/sections/s-routing.html @@ -1,5 +1,5 @@
- +

App Routing

Map incoming requests from apps like Open WebUI or SillyTavern to specific voices and backends.

diff --git a/static/sections/s-settings.html b/static/sections/s-settings.html index ee02a4b..7152fcf 100644 --- a/static/sections/s-settings.html +++ b/static/sections/s-settings.html @@ -1,5 +1,5 @@
- +

Settings

Configure backend URLs, API keys, voice folders, and playback preferences.

@@ -130,7 +130,7 @@
- +
Used for Groq Whisper STT (whisper-large-v3-turbo) and Groq LLM. Free: 2 000 req/day. Fastest cloud transcription.
@@ -247,21 +247,21 @@
- +
- +
- +
@@ -277,9 +277,9 @@

Export all voices and settings as a ZIP for backup or migration. Import to restore. API keys are excluded from exports.

- ↓ Export voices + Export voices diff --git a/static/sections/s-tryout.html b/static/sections/s-tryout.html index edfa67b..79f0d24 100644 --- a/static/sections/s-tryout.html +++ b/static/sections/s-tryout.html @@ -1,5 +1,5 @@
- +

Try It Out

Generate speech from text using any backend and voice. Also transcribe audio and re-speak it.

@@ -74,9 +74,9 @@
- - - + + +
@@ -154,7 +154,7 @@
No clips in playlist. Use + Playlist after generating.
- +
@@ -164,7 +164,7 @@
-

STT → TTS workspace

+

STT TTS workspace

Upload speech audio, transcribe it with the configured STT endpoint, then synthesize the resulting text with any available TTS backend.

@@ -183,7 +183,7 @@
- + 0:00
@@ -227,7 +227,7 @@
- +
@@ -305,7 +305,7 @@
- +
diff --git a/static/sections/s-voices.html b/static/sections/s-voices.html index 68b1592..41b9a5f 100644 --- a/static/sections/s-voices.html +++ b/static/sections/s-voices.html @@ -1,5 +1,5 @@
- 🔊 +

My Voices

Pick a voice on the left, edit on the right.

@@ -20,7 +20,7 @@
- ▶▶ + Synth uses
- + @@ -78,7 +78,7 @@
- 🔊 +

Pick a voice on the left
to edit it here

@@ -162,14 +162,14 @@
- - + + 0:00
Input level - -∞ dB + - dB
diff --git a/static/style.css b/static/style.css index 1fb2cb8..7749ead 100644 --- a/static/style.css +++ b/static/style.css @@ -1,3 +1,17 @@ +/* ── MDI icon integration ────────────────────────────────────────────────── */ +.mdi { + vertical-align: -0.125em; + line-height: 1; + display: inline-block; +} +button .mdi, a .mdi { font-size: 15px; } +.nav-icon .mdi { font-size: 15px; line-height: 1; } +.section-icon .mdi { font-size: 22px; line-height: 1; } +.llm-card-icon .mdi { font-size: inherit; } +.llm-local-icon.mdi { font-size: 20px; } +.backend-metric-tag .mdi, .llm-metric-chip .mdi, .dc-metric-chip .mdi { font-size: 11px; vertical-align: -0.05em; } +.link-icon { font-size: 11px; opacity: .7; margin-left: 1px; } + /* ── Theme tokens (warm cream light theme) ──────────────────────────────── */ :root { --bg: #F5F3EE;